zephyr_build/devicetree/
output.rs

1//! Outputting the devicetree into Rust.
2
3// We output the device tree in a module tree in Rust that mirrors the DTS tree.  Devicetree names
4// are made into valid Rust identifiers by the simple rule that invalid characters are replaced with
5// underscores.
6//
7// The actual output is somewhat specialized, and driven by the data, and the compatible values.
8// Support for particular devices should also be added to the device tree here, so that the nodes
9// make sense for that device, and that there are general accessors that return wrapped node types.
10
11use std::io::Write;
12
13use anyhow::Result;
14use proc_macro2::{Ident, TokenStream};
15use quote::{format_ident, quote};
16
17use super::{augment::Augment, DeviceTree, Node, Property, Value, Word};
18
19impl DeviceTree {
20    /// Generate a TokenStream for the Rust representation of this device tree.
21    pub fn to_tokens(&self, augments: &[Box<dyn Augment>]) -> TokenStream {
22        // Root is a little special.  Since we don't want a module for this (it will be provided
23        // above where it is included, so it can get documentation and attributes), we use None for
24        // the name.
25        self.node_walk(self.root.as_ref(), None, augments)
26    }
27
28    // Write, to the given writer, CFG lines so that Rust code can conditionalize based on the DT.
29    pub fn output_node_paths<W: Write>(&self, write: &mut W) -> Result<()> {
30        self.root.as_ref().output_path_walk(write, None)?;
31
32        // Also, output all of the labels.  Technically, this depends on the labels augment being
33        // present.
34        writeln!(write, "cargo:rustc-cfg=dt=\"labels\"")?;
35        for label in self.labels.keys() {
36            writeln!(write, "cargo:rustc-cfg=dt=\"labels::{}\"", fix_id(label))?;
37        }
38        Ok(())
39    }
40
41    fn node_walk(
42        &self,
43        node: &Node,
44        name: Option<&str>,
45        augments: &[Box<dyn Augment>],
46    ) -> TokenStream {
47        let children = node
48            .children
49            .iter()
50            .map(|child| self.node_walk(child.as_ref(), Some(&child.name), augments));
51        // Simplistic first pass, turn the properties into constents of the formatted text of the
52        // property.
53        let props = node.properties.iter().map(|prop| self.property_walk(prop));
54        let ord = node.ord;
55
56        // Open the parent as a submodule.  This is the same as 'super', so not particularly useful.
57        /*
58        let parent = if let Some(parent) = node.parent.borrow().as_ref() {
59            let route = parent.route_to_rust();
60            quote! {
61                pub mod silly_super {
62                    pub use #route::*;
63                }
64            }
65        } else {
66            TokenStream::new()
67        };
68        */
69
70        // If this is compatible with an augment, use the augment to add any additional properties.
71        let augs = augments.iter().map(|aug| aug.augment(node, self));
72
73        if let Some(name) = name {
74            let name_id = dt_to_lower_id(name);
75            quote! {
76                pub mod #name_id {
77                    pub const ORD: usize = #ord;
78                    #(#props)*
79                    #(#children)*
80                    // #parent
81                    #(#augs)*
82                }
83            }
84        } else {
85            quote! {
86                #(#props)*
87                #(#children)*
88                #(#augs)*
89            }
90        }
91    }
92
93    // This is the "fun" part.  We try to find some patterns that can be formatted more nicely, but
94    // otherwise they are just somewhat simply converted.
95    fn property_walk(&self, prop: &Property) -> TokenStream {
96        // Pattern matching is rather messy at this point.
97        if let Some(value) = prop.get_single_value() {
98            match value {
99                Value::Words(ref words) => {
100                    if words.len() == 1 {
101                        if let Word::Number(n) = &words[0] {
102                            let tag = dt_to_upper_id(&prop.name);
103                            return quote! {
104                                pub const #tag: u32 = #n;
105                            };
106                        }
107                    }
108                }
109                Value::Phandle(ref ph) => {
110                    let target = ph.node_ref();
111                    let route = target.route_to_rust();
112                    let tag = dt_to_lower_id(&prop.name);
113                    return quote! {
114                        pub mod #tag {
115                            pub use #route::*;
116                        }
117                    };
118                }
119                _ => (),
120            }
121        }
122        general_property(prop)
123    }
124}
125
126impl Node {
127    /// Return the route to this node, as a Rust token stream giving a fully resolved name of the
128    /// route.
129    pub fn route_to_rust(&self) -> TokenStream {
130        let route: Vec<_> = self.route.iter().map(|p| dt_to_lower_id(p)).collect();
131        quote! {
132            crate :: devicetree #(:: #route)*
133        }
134    }
135
136    /// Walk this tree of nodes, writing out the path names of the nodes that are present.  The name
137    /// of None, indicates the root node.
138    fn output_path_walk<W: Write>(&self, write: &mut W, name: Option<&str>) -> Result<()> {
139        for child in &self.children {
140            let fixed_name = fix_id(&child.name);
141            let child_name = if let Some(name) = name {
142                format!("{}::{}", name, fixed_name)
143            } else {
144                fixed_name
145            };
146
147            writeln!(write, "cargo:rustc-cfg=dt=\"{}\"", child_name)?;
148
149            for prop in &child.properties {
150                prop.output_path(write, &child_name)?;
151            }
152
153            child.output_path_walk(write, Some(&child_name))?;
154        }
155
156        Ok(())
157    }
158}
159
160impl Property {
161    // Return property values that consist of a single value.
162    fn get_single_value(&self) -> Option<&Value> {
163        if self.value.len() == 1 {
164            Some(&self.value[0])
165        } else {
166            None
167        }
168    }
169
170    // If this property is a single top-level phandle, output that a that path is valid.  It isn't a
171    // real node, but acts like one.
172    fn output_path<W: Write>(&self, write: &mut W, name: &str) -> Result<()> {
173        if let Some(Value::Phandle(_)) = self.get_single_value() {
174            writeln!(
175                write,
176                "cargo:rustc-cfg=dt=\"{}::{}\"",
177                name,
178                fix_id(&self.name)
179            )?;
180        }
181        Ok(())
182    }
183}
184
185fn general_property(prop: &Property) -> TokenStream {
186    let text = format!("{:?}", prop.value);
187    let tag = format!("{}_DEBUG", prop.name);
188    let tag = dt_to_upper_id(&tag);
189    quote! {
190        pub const #tag: &str = #text;
191    }
192}
193
194/// Given a DT name, return an identifier for a lower-case version.
195pub fn dt_to_lower_id(text: &str) -> Ident {
196    format_ident!("{}", fix_id(text))
197}
198
199pub fn dt_to_upper_id(text: &str) -> Ident {
200    format_ident!("{}", fix_id(&text.to_uppercase()))
201}
202
203/// Fix a devicetree identifier to be safe as a rust identifier.
204fn fix_id(text: &str) -> String {
205    let mut result = String::new();
206    for ch in text.chars() {
207        match ch {
208            '#' => result.push('N'),
209            '-' => result.push('_'),
210            '@' => result.push('_'),
211            ',' => result.push('_'),
212            ch => result.push(ch),
213        }
214    }
215    result
216}