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}