zephyr_build/devicetree.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
//! Incorporating Zephyr's devicetree into Rust.
//!
//! Zephyr depends fairly heavily on the devicetree for configuration. The build system reads
//! multiple DTS files, and coalesces this into a single devicetree. This tree is output in a few
//! different ways:
//!
//! - Canonical DTS. There is a single DTS file (`build/zephyr/zephyr.dts`) that contains the final
//! tree, but still in DTS format (the DTB file would have information discarded).
//!
//! - Generated. The C header `devicetree_generated.h` contains all of the definitions. This isn't
//! a particularly friendly file to read or parse, but it does have one piece of information that is
//! not represented anywhere else: the mapping between devicetree nodes and their "ORD" index. The
//! device nodes in the system are indexed by this number, and we need this in order to be able to
//! reference the nodes from Rust.
//!
//! Beyond the ORD field, it seems easier to deal with the DTS file itself. Parsing is fairly
//! straightforward, as it is a subset of the DTS format, and we only have to be able to deal with
//! the files that are generated by the Zephyr build process.
// TODO: Turn this off.
#![allow(dead_code)]
use ordmap::OrdMap;
use std::{cell::RefCell, collections::BTreeMap, path::Path, rc::Rc};
mod augment;
mod ordmap;
mod output;
mod parse;
pub use augment::{load_augments, Augment};
/// Representation of a parsed device tree.
pub struct DeviceTree {
/// The root of the tree.
root: Rc<Node>,
/// All of the labels.
/// Note that this is a BTree so that the output will be deterministic.
labels: BTreeMap<String, Rc<Node>>,
}
// A single node in a [`DeviceTree`].
pub struct Node {
// The name of the node itself.
name: String,
// The full path of this node in the tree.
path: String,
// The "route" is the path, but still as separate entries.
route: Vec<String>,
// The ord index in this particular Zephyr build.
ord: usize,
// Labels attached to this node.
labels: Vec<String>,
// Any properties set in this node.
properties: Vec<Property>,
// Children nodes.
children: Vec<Rc<Node>>,
// The parent. Should be non-null except at the root node.
parent: RefCell<Option<Rc<Node>>>,
}
#[derive(Debug)]
pub struct Property {
pub name: String,
pub value: Vec<Value>,
}
// Although the real device flattends all of these into bytes, Zephyr takes advantage of them at a
// slightly higher level.
#[derive(Debug)]
pub enum Value {
Words(Vec<Word>),
Bytes(Vec<u8>),
Phandle(Phandle),
String(String),
}
/// A phandle is a named reference to a labeled part of the DT. We resolve this by making the
/// reference optional, and filling them in afterwards.
pub struct Phandle {
/// The label of our target. Keep this because it may be useful to know which label was used,
/// as nodes often have multiple labels.
name: String,
/// The inside of the node, inner mutability so this can be looked up and cached.
node: RefCell<Option<Rc<Node>>>,
}
#[derive(Debug)]
pub enum Word {
Number(u32),
Phandle(Phandle),
}
impl DeviceTree {
/// Decode the `zephyr.dts` and `devicetree_generated.h` files from the build and build an internal
/// representation of the devicetree itself.
pub fn new<P1: AsRef<Path>, P2: AsRef<Path>>(dts_path: P1, dt_gen: P2) -> DeviceTree {
let ords = OrdMap::new(dt_gen);
let dts = std::fs::read_to_string(dts_path).expect("Reading zephyr.dts file");
let dt = parse::parse(&dts, &ords);
// Walk the node tree, fixing any phandles to include their reference.
dt.root.phandle_walk(&dt.labels);
// Walk the node tree, setting each node's parent appropriately.
dt.root.parent_walk();
dt
}
}
impl Node {
fn phandle_walk(&self, labels: &BTreeMap<String, Rc<Node>>) {
for prop in &self.properties {
for value in &prop.value {
value.phandle_walk(labels);
}
}
for child in &self.children {
child.phandle_walk(labels);
}
}
fn parent_walk(self: &Rc<Self>) {
for child in &self.children {
*(child.parent.borrow_mut()) = Some(self.clone());
child.parent_walk()
}
}
fn is_compatible(&self, name: &str) -> bool {
self.properties
.iter()
.filter(|p| p.name == "compatible")
.flat_map(|prop| prop.value.iter())
.any(|v| matches!(v, Value::String(vn) if name == vn))
}
/// A richer compatible test. Walks a series of names, in reverse. Any that are "Some(x)" must
/// be compatible with "x" at that level.
fn compatible_path(&self, path: &[Option<&str>]) -> bool {
if let Some(first) = path.first() {
if !matches!(first, Some(name) if !self.is_compatible(name)) {
return false;
}
// Walk down the tree with the remainder of the path.
if let Some(child) = self.parent.borrow().as_ref() {
child.compatible_path(&path[1..])
} else {
// We've run out of nodes, so this is considered not matching.
false
}
} else {
// The empty path always matches.
true
}
}
/// Returns `true` if there is a property with this name.
fn has_prop(&self, name: &str) -> bool {
self.properties.iter().any(|p| p.name == name)
}
/// Returns the slice of values of a property with this name as `Some` or `None` if the property
/// does not exist.
fn get_property(&self, name: &str) -> Option<&[Value]> {
self.properties.iter().find_map(|p| {
if p.name == name {
Some(p.value.as_slice())
} else {
None
}
})
}
/// Attempt to retrieve the named property, as a single entry of Words.
fn get_words(&self, name: &str) -> Option<&[Word]> {
self.get_property(name).and_then(|p| match p {
&[Value::Words(ref w)] => Some(w.as_ref()),
_ => None,
})
}
/// Get a property that consists of a single number.
fn get_number(&self, name: &str) -> Option<u32> {
self.get_words(name).and_then(|p| {
if let &[Word::Number(n)] = p {
Some(n)
} else {
None
}
})
}
/// Get a property that consists of multiple numbers.
fn get_numbers(&self, name: &str) -> Option<Vec<u32>> {
let mut result = vec![];
for word in self.get_words(name)? {
if let Word::Number(n) = word {
result.push(*n);
} else {
return None;
}
}
Some(result)
}
/// Get a property that is a single string.
fn get_single_string(&self, name: &str) -> Option<&str> {
self.get_property(name).and_then(|p| {
if let &[Value::String(ref text)] = p {
Some(text.as_ref())
} else {
None
}
})
}
}
impl Value {
fn phandle_walk(&self, labels: &BTreeMap<String, Rc<Node>>) {
match self {
Self::Phandle(ph) => ph.phandle_resolve(labels),
Self::Words(words) => {
for w in words {
if let Word::Phandle(ph) = w {
ph.phandle_resolve(labels);
}
}
}
_ => (),
}
}
}
impl Phandle {
/// Construct a phandle that is unresolved.
pub fn new(name: String) -> Self {
Self {
name,
node: RefCell::new(None),
}
}
/// Resolve this phandle, with the given label for lookup.
fn phandle_resolve(&self, labels: &BTreeMap<String, Rc<Node>>) {
// If already resolve, just return.
if self.node.borrow().is_some() {
return;
}
let node = labels.get(&self.name).cloned().expect("Missing phandle");
*self.node.borrow_mut() = Some(node);
}
/// Get the child node, panicing if it wasn't resolved properly.
fn node_ref(&self) -> Rc<Node> {
self.node.borrow().as_ref().unwrap().clone()
}
}
impl Word {
pub fn as_number(&self) -> Option<u32> {
match self {
Self::Number(n) => Some(*n),
_ => None,
}
}
}
// To avoid recursion, the debug printer for Phandle just prints the name.
impl std::fmt::Debug for Phandle {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("Phandle")
.field("name", &self.name)
.finish_non_exhaustive()
}
}