From 3bca48e8542276a4cbb8a63a0b625a77fec1bfb0 Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 18 Jul 2024 14:34:34 -0600 Subject: [PATCH 01/30] rust: zephyr-build: Conversion of Device Tree This code parses the DTS file generated by the Zephyr build, along with a few entries from the generated header file, to build a representation of the device tree. There is a notion of "augments" that add various methods. This is currently just hard-coded. Signed-off-by: David Brown --- CMakeLists.txt | 2 + zephyr-build/Cargo.toml | 4 + zephyr-build/src/devicetree.rs | 304 +++++++++++++++++++++++++ zephyr-build/src/devicetree/augment.rs | 256 +++++++++++++++++++++ zephyr-build/src/devicetree/dts.pest | 77 +++++++ zephyr-build/src/devicetree/ordmap.rs | 41 ++++ zephyr-build/src/devicetree/output.rs | 157 +++++++++++++ zephyr-build/src/devicetree/parse.rs | 269 ++++++++++++++++++++++ zephyr-build/src/lib.rs | 60 +++++ zephyr-sys/build.rs | 9 + zephyr-sys/wrapper.h | 13 +- zephyr/build.rs | 1 + zephyr/src/lib.rs | 3 + 13 files changed, 1193 insertions(+), 3 deletions(-) create mode 100644 zephyr-build/src/devicetree.rs create mode 100644 zephyr-build/src/devicetree/augment.rs create mode 100644 zephyr-build/src/devicetree/dts.pest create mode 100644 zephyr-build/src/devicetree/ordmap.rs create mode 100644 zephyr-build/src/devicetree/output.rs create mode 100644 zephyr-build/src/devicetree/parse.rs diff --git a/CMakeLists.txt b/CMakeLists.txt index 13a8bc43..6baef049 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,6 +138,7 @@ ZEPHYR_DTS = \"${ZEPHYR_DTS}\" INCLUDE_DIRS = \"${include_dirs}\" INCLUDE_DEFINES = \"${include_defines}\" WRAPPER_FILE = \"${WRAPPER_FILE}\" +BINARY_DIR_INCLUDE_GENERATED = \"${BINARY_DIR_INCLUDE_GENERATED}\" [patch.crates-io] ${config_paths} @@ -161,6 +162,7 @@ ${config_paths} INCLUDE_DIRS="${include_dirs}" INCLUDE_DEFINES="${include_defines}" WRAPPER_FILE="${WRAPPER_FILE}" + BINARY_DIR_INCLUDE_GENERATED="${BINARY_DIR_INCLUDE_GENERATED}" cargo build ${rust_build_type_arg} diff --git a/zephyr-build/Cargo.toml b/zephyr-build/Cargo.toml index c73a95e6..5d0b57d0 100644 --- a/zephyr-build/Cargo.toml +++ b/zephyr-build/Cargo.toml @@ -15,3 +15,7 @@ Provides utilities for accessing Kconfig and devicetree information. # used by the core Zephyr tree, but are needed by zephyr applications. [dependencies] regex = "1.10.3" +pest = "2.6" +pest_derive = "2.6" +quote = "1.0" +proc-macro2 = "1.0.86" diff --git a/zephyr-build/src/devicetree.rs b/zephyr-build/src/devicetree.rs new file mode 100644 index 00000000..a977c15e --- /dev/null +++ b/zephyr-build/src/devicetree.rs @@ -0,0 +1,304 @@ +//! 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 struct DeviceTree { + /// The root of the tree. + root: Rc, + /// All of the labels. + labels: BTreeMap>, +} + +// This is a single node in the 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, + // The ord index in this particular Zephyr build. + ord: usize, + // Labels attached to this node. + labels: Vec, + // Any properties set in this node. + properties: Vec, + // Children nodes. + children: Vec>, + // The parent. Should be non-null except at the root node. + parent: RefCell>>, +} + +#[derive(Debug)] +pub struct Property { + pub name: String, + pub value: Vec, +} + +// 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), + Bytes(Vec), + Phandle(Phandle), // TODO + 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>>, +} + +#[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, P2: AsRef>(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); + dt.resolve_phandles(); + dt.set_parents(); + dt + } + + /// Walk the node tree, fixing any phandles to include their reference. + fn resolve_phandles(&self) { + self.root.phandle_walk(&self.labels); + } + + /// Walk the node tree, setting each node's parent appropriately. + fn set_parents(&self) { + self.root.parent_walk(); + } +} + +impl Node { + fn phandle_walk(&self, labels: &BTreeMap>) { + 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.parent.borrow_mut()) = Some(parent.clone()); + + for child in &self.children { + *(child.parent.borrow_mut()) = Some(self.clone()); + child.parent_walk() + } + } + + fn is_compatible(&self, name: &str) -> bool { + if let Some(prop) = self.properties.iter().find(|p| p.name == "compatible") { + prop.value.iter().any(|v| { + match v { + Value::String(vn) if name == vn => true, + _ => false, + } + }) + } else { + // If there is no compatible field, we are clearly not compatible. + false + } + } + + /// 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 { + let res = self.path_walk(path, 0); + // println!("compatible? {}: {} {:?}", res, self.path, path); + res + } + + /// Recursive path walk, to make borrowing simpler. + fn path_walk(&self, path: &[Option<&str>], pos: usize) -> bool { + if pos >= path.len() { + // Once past the end, we consider everything a match. + return true; + } + + // Check the failure condition, where this node isn't compatible with this section of the path. + if let Some(name) = path[pos] { + if !self.is_compatible(name) { + return false; + } + } + + // Walk down the tree. We have to check for None here, as we can't recurse on the none + // case. + if let Some(child) = self.parent.borrow().as_ref() { + child.path_walk(path, pos + 1) + } else { + // We've run out of nodes, so this is considered not matching. + false + } + } + + /// Is the named property present? + fn has_prop(&self, name: &str) -> bool { + self.properties.iter().any(|p| p.name == name) + } + + /// Get this property in its entirety. + fn get_property(&self, name: &str) -> Option<&[Value]> { + for p in &self.properties { + if p.name == name { + return Some(&p.value); + } + } + return 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 { + 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> { + 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>) { + match self { + Value::Phandle(ph) => ph.phandle_resolve(labels), + Value::Words(words) => { + for w in words { + match w { + Word::Phandle(ph) => ph.phandle_resolve(labels), + _ => (), + } + } + } + _ => (), + } + } +} + +impl Phandle { + /// Construct a phandle that is unresolved. + pub fn new(name: String) -> Phandle { + Phandle { + name, + node: RefCell::new(None), + } + } + + /// Resolve this phandle, with the given label for lookup. + fn phandle_resolve(&self, labels: &BTreeMap>) { + // 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 { + self.node.borrow().as_ref().unwrap().clone() + } +} + +impl Word { + pub fn get_number(&self) -> Option { + match self { + Word::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 { + write!(fmt, "Phandle({:?})", self.name) + } +} diff --git a/zephyr-build/src/devicetree/augment.rs b/zephyr-build/src/devicetree/augment.rs new file mode 100644 index 00000000..b39a7196 --- /dev/null +++ b/zephyr-build/src/devicetree/augment.rs @@ -0,0 +1,256 @@ +//! Support for augmenting the device tree. + +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::devicetree::output::dt_to_lower_id; + +use super::{DeviceTree, Node, Word}; + +/// This action is given to each node in the device tree, and it is given a chance to return +/// additional code to be included in the module associated with that entry. These are all +/// assembled together and included in the final generated devicetree.rs. +pub trait Augment { + /// The default implementation checks if this node matches and calls a generator if it does, or + /// does nothing if not. + fn augment(&self, node: &Node, tree: &DeviceTree) -> TokenStream { + if self.is_compatible(node) { + self.generate(node, tree) + } else { + TokenStream::new() + } + } + + /// A query if this node is compatible with this augment. A simple case might check the node's + /// compatible field, but also makes sense to check a parent's compatible. + fn is_compatible(&self, node: &Node) -> bool; + + /// A generator to be called when we are compatible. + fn generate(&self, node: &Node, tree: &DeviceTree) -> TokenStream; +} + +/// Return all of the Augments we wish to apply. +pub fn get_augments() -> Vec> { + vec![ + Box::new(GpioAugment), + Box::new(GpioLedsAugment), + Box::new(FlashControllerAugment), + Box::new(FlashPartitionAugment), + Box::new(SinglePartitionAugment), + Box::new(LabelAugment), + ] +} + +/// Augment gpio nodes. Gpio nodes are indicated by the 'gpio-controller' property, and not a +/// particular compatible. +struct GpioAugment; + +impl Augment for GpioAugment { + fn is_compatible(&self, node: &Node) -> bool { + node.has_prop("gpio-controller") + } + + fn generate(&self, node: &Node, _tree: &DeviceTree) -> TokenStream { + let ord = node.ord; + let device = format_ident!("__device_dts_ord_{}", ord); + quote! { + pub unsafe fn get_instance_raw() -> *const crate::raw::device { + &crate::raw::#device + } + pub fn get_instance() -> crate::sys::gpio::Gpio { + let device = unsafe { get_instance_raw() }; + crate::sys::gpio::Gpio { + device, + } + } + } + } +} + +/// Augment the individual led nodes. This provides a safe wrapper that can be used to directly use +/// these nodes. +struct GpioLedsAugment; + +impl Augment for GpioLedsAugment { + // GPIO Leds are nodes whose parent is compatible with "gpio-leds". + fn is_compatible(&self, node: &Node) -> bool { + if let Some(parent) = node.parent.borrow().as_ref() { + parent.is_compatible("gpio-leds") + } else { + false + } + } + + fn generate(&self, node: &Node, _tree: &DeviceTree) -> TokenStream { + // TODO: Generalize this. + let words = node.get_words("gpios").unwrap(); + let target = if let Word::Phandle(gpio) = &words[0] { + gpio.node_ref() + } else { + panic!("gpios property in node is empty"); + }; + + // At this point, we support gpio-cells of 2. + if target.get_number("#gpio-cells") != Some(2) { + panic!("gpios only support with #gpio-cells of 2"); + } + + if words.len() != 3 { + panic!("gpio-leds, gpios property expected to have only one entry"); + } + + let pin = words[1].get_number().unwrap(); + let flags = words[2].get_number().unwrap(); + + let gpio_route = target.route_to_rust(); + quote! { + pub fn get_instance() -> crate::sys::gpio::GpioPin { + unsafe { + let port = #gpio_route :: get_instance_raw(); + crate::sys::gpio::GpioPin { + pin: crate::raw::gpio_dt_spec { + port, + pin: #pin as crate::raw::gpio_pin_t, + dt_flags: #flags as crate::raw::gpio_dt_flags_t, + } + } + } + } + } + } +} + +/// Augment flash controllers. +/// +/// Flash controllers are a little weird, because there is no unified compatible that says they +/// implement the flash interface. In fact, they seem to generally not be accessed through the +/// device tree at all. +struct FlashControllerAugment; + +impl Augment for FlashControllerAugment { + // For now, just look for specific ones we know. + fn is_compatible(&self, node: &Node) -> bool { + node.is_compatible("nordic,nrf52-flash-controller") || + node.is_compatible("raspberrypi,pico-flash-controller") + } + + fn generate(&self, node: &Node, _tree: &DeviceTree) -> TokenStream { + // For now, just return the device node. + let ord = node.ord; + let device = format_ident!("__device_dts_ord_{}", ord); + quote! { + pub unsafe fn get_instance_raw() -> *const crate::raw::device { + &crate::raw::#device + } + + pub fn get_instance() -> crate::sys::flash::FlashController { + let device = unsafe { get_instance_raw() }; + crate::sys::flash::FlashController { + device, + } + } + } + } +} + +/// Augment flash partitions. +/// +/// This provides access to the individual partitions via named modules under the flash device. +struct FlashPartitionAugment; + +impl Augment for FlashPartitionAugment { + fn is_compatible(&self, node: &Node) -> bool { + node.compatible_path(&[Some("fixed-partitions"), Some("soc-nv-flash")]) + } + + fn generate(&self, node: &Node, _tree: &DeviceTree) -> TokenStream { + let children = node.children.iter().map(|child| { + let label = child.get_single_string("label").unwrap(); + let label = dt_to_lower_id(label); + let reg = child.get_numbers("reg").unwrap(); + if reg.len() != 2 { + panic!("flash partition entry must have 2 reg values"); + } + let offset = reg[0]; + let size = reg[1]; + + quote! { + pub mod #label { + pub fn get_instance() -> crate::sys::flash::FlashPartition { + let controller = super::super::super::get_instance(); + crate::sys::flash::FlashPartition { + controller, + offset: #offset, + size: #size, + } + } + } + } + }); + quote! { + #(#children)* + } + } +} + +/// Augment the partitions themselves rather than the whole partition table. +struct SinglePartitionAugment; + +impl Augment for SinglePartitionAugment { + fn is_compatible(&self, node: &Node) -> bool { + node.compatible_path(&[None, Some("fixed-partitions"), Some("soc-nv-flash")]) + } + + fn generate(&self, node: &Node, _tree: &DeviceTree) -> TokenStream { + let reg = node.get_numbers("reg").unwrap(); + if reg.len() != 2 { + panic!("flash partition entry must have 2 reg values"); + } + let offset = reg[0]; + let size = reg[1]; + + quote! { + pub fn get_instance() -> crate::sys::flash::FlashPartition { + let controller = super::super::super::get_instance(); + crate::sys::flash::FlashPartition { + controller, + offset: #offset, + size: #size, + } + } + } + } +} + +/// Add in all of the labels. +struct LabelAugment; + +impl Augment for LabelAugment { + fn is_compatible(&self, node: &Node) -> bool { + // Insert the labels at the root node. + println!("Augment check: {}", node.path); + if let Some(parent) = node.parent.borrow().as_ref() { + println!(" parent: {}", parent.path); + } + node.parent.borrow().is_none() + } + + fn generate(&self, _node: &Node, tree: &DeviceTree) -> TokenStream { + let nodes = tree.labels.iter().map(|(k, v)| { + let name = dt_to_lower_id(k); + let path = v.route_to_rust(); + quote! { + pub mod #name { + pub use #path::*; + } + } + }); + + quote! { + // This does assume the devicetree doesn't have a "labels" node at the root. + pub mod labels { + #(#nodes)* + } + } + } +} diff --git a/zephyr-build/src/devicetree/dts.pest b/zephyr-build/src/devicetree/dts.pest new file mode 100644 index 00000000..8a521580 --- /dev/null +++ b/zephyr-build/src/devicetree/dts.pest @@ -0,0 +1,77 @@ +// Device Tree Source file +// +// This is a pest parser for a subset of the DTS +// format that will be seen by the output of dtc. + +file = _{ SOI ~ header ~ node ~ EOI } + +header = _{ "/dts-v1/" ~ ";" } + +node = { + node_path ~ + "{" ~ + entry* ~ + "}" ~ ";" +} + +node_path = _{ + (label ~ ":")* + ~("/" | nodename) +} + +entry = _{ + property | + node +} + +property = { + (nodename ~ "=" ~ values ~ ";") | + (nodename ~ ";") +} + +values = _{ value ~ ("," ~ value)* } +value = _{ string | words | bytes | phandle } + +words = { + "<" ~ + (number | phandle)+ ~ + ">" +} + +bytes = { + "[" ~ + plain_hex_number+ ~ + "]" +} + +number = _{ decimal_number | hex_number } + +decimal_number = @{ + ('1'..'9') ~ + ASCII_DIGIT* +} + +hex_number = @{ + ("0x" | "0X") ~ + ASCII_HEX_DIGIT+ +} + +plain_hex_number = @{ + ASCII_HEX_DIGIT+ +} + +// Simple strings, no escapes or such. +string = @{ + "\"" ~ + (!("\"" | "\n") ~ ANY)* ~ + "\"" +} + +phandle = @{ "&" ~ label } + +label = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } +nodename = @{ + (ASCII_ALPHANUMERIC | "_" | "," | "." | "?" | "-" | "@" | "#")+ +} + +WHITESPACE = _{ " " | "\n" | "\t" } diff --git a/zephyr-build/src/devicetree/ordmap.rs b/zephyr-build/src/devicetree/ordmap.rs new file mode 100644 index 00000000..02bc0ec5 --- /dev/null +++ b/zephyr-build/src/devicetree/ordmap.rs @@ -0,0 +1,41 @@ +//! Devicetree ordmap +//! +//! The OrdMap provides a mapping between nodes on the devicetree, and their "ord" index. + +use std::{collections::BTreeMap, fs::File, io::{BufRead, BufReader}, path::Path, str::FromStr}; + +use regex::Regex; + +pub struct OrdMap(pub BTreeMap); + +impl OrdMap { + pub fn new>(path: P) -> OrdMap { + let mut result = BTreeMap::new(); + + let path_re = Regex::new(r#"^#define DT_(.*)_PATH "(.*)"$"#).unwrap(); + let ord_re = Regex::new(r#"^#define DT_(.*)_ORD (.*)$"#).unwrap(); + + // The last C name seen. + let mut c_name = "".to_string(); + let mut dt_path = "".to_string(); + + let fd = File::open(path) + .expect("Opening devicetree_generated.h"); + for line in BufReader::new(fd).lines() { + let line = line.expect("Reading from devicetree_generated.h"); + + if let Some(caps) = path_re.captures(&line) { + // println!("Path: {:?} => {:?}", &caps[1], &caps[2]); + c_name = caps[1].to_string(); + dt_path = caps[2].to_string(); + } else if let Some(caps) = ord_re.captures(&line) { + // println!("Ord: {:?} => {:?}", &caps[1], &caps[2]); + let ord = usize::from_str(&caps[2]).unwrap(); + assert_eq!(caps[1].to_string(), c_name); + result.insert(dt_path.clone(), ord); + } + } + + OrdMap(result) + } +} diff --git a/zephyr-build/src/devicetree/output.rs b/zephyr-build/src/devicetree/output.rs new file mode 100644 index 00000000..93b0e806 --- /dev/null +++ b/zephyr-build/src/devicetree/output.rs @@ -0,0 +1,157 @@ +//! Outputting the devicetree into Rust. + +// We output the device tree in a module tree in Rust that mirrors the DTS tree. Devicetree names +// are made into valid Rust identifiers by the simple rule that invalid characters are replaced with +// underscores. +// +// The actual output is somewhat specialized, and driven by the data, and the compatible values. +// Support for particular devices should also be added to the device tree here, so that the nodes +// make sense for that device, and that there are general accessors that return wrapped node types. + +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; + +use super::{augment::{get_augments, Augment}, DeviceTree, Node, Property, Value, Word}; + +impl DeviceTree { + /// Generate a TokenStream for the Rust representation of this device tree. + pub fn to_tokens(&self) -> TokenStream { + let augments = get_augments(); + + // Root is a little special, as we don't represent the name of the node as it's actual name, + // but as just 'devicetree'. + self.node_walk(self.root.as_ref(), "devicetree", &augments) + } + + fn node_walk(&self, node: &Node, name: &str, augments: &[Box]) -> TokenStream { + let name_id = dt_to_lower_id(name); + let children = node.children.iter().map(|child| { + self.node_walk(child.as_ref(), &child.name, augments) + }); + // Simplistic first pass, turn the properties into constents of the formatted text of the + // property. + let props = node.properties.iter().map(|prop| { + self.property_walk(prop) + }); + let ord = node.ord; + + // Open the parent as a submodule. This is the same as 'super', so not particularly useful. + /* + let parent = if let Some(parent) = node.parent.borrow().as_ref() { + let route = parent.route_to_rust(); + quote! { + pub mod silly_super { + pub use #route::*; + } + } + } else { + TokenStream::new() + }; + */ + + // If this is compatible with an augment, use the augment to add any additional properties. + let augs = augments.iter().map(|aug| aug.augment(node, self)); + + quote! { + pub mod #name_id { + pub const ORD: usize = #ord; + #(#props)* + #(#children)* + // #parent + #(#augs)* + } + } + } + + // This is the "fun" part. We try to find some patterns that can be formatted more nicely, but + // otherwise they are just somewhat simply converted. + fn property_walk(&self, prop: &Property) -> TokenStream { + // Pattern matching is rather messy at this point. + if let Some(value) = prop.get_single_value() { + match value { + Value::Words(ref words) => { + if words.len() == 1 { + match &words[0] { + Word::Number(n) => { + let tag = dt_to_upper_id(&prop.name); + return quote! { + pub const #tag: u32 = #n; + }; + } + _ => return general_property(prop), + } + } else { + return general_property(prop); + } + } + Value::Phandle(ref ph) => { + let target = ph.node_ref(); + let route = target.route_to_rust(); + let tag = dt_to_lower_id(&prop.name); + return quote! { + pub mod #tag { + pub use #route::*; + } + } + } + _ => return general_property(prop), + } + } + general_property(prop) + } +} + +impl Node { + /// Return the route to this node, as a Rust token stream giving a fully resolved name of the + /// route. + pub fn route_to_rust(&self) -> TokenStream { + let route: Vec<_> = self.route.iter().map(|p| dt_to_lower_id(p)).collect(); + quote! { + crate :: devicetree #(:: #route)* + } + } +} + +impl Property { + // Return property values that consist of a single value. + fn get_single_value(&self) -> Option<&Value> { + if self.value.len() == 1 { + Some(&self.value[0]) + } else { + None + } + } +} + +fn general_property(prop: &Property) -> TokenStream { + let text = format!("{:?}", prop.value); + let tag = format!("{}_DEBUG", prop.name); + let tag = dt_to_upper_id(&tag); + quote! { + pub const #tag: &'static str = #text; + } +} + +/// Given a DT name, return an identifier for a lower-case version. +pub fn dt_to_lower_id(text: &str) -> Ident { + format_ident!("{}", fix_id(&text)) +} + +pub fn dt_to_upper_id(text: &str) -> Ident { + format_ident!("{}", fix_id(&text.to_uppercase())) +} + +/// Fix a devicetree identifier to be safe as a rust identifier. +fn fix_id(text: &str) -> String { + let mut result = String::new(); + for ch in text.chars() { + match ch { + '#' => result.push('N'), + '-' => result.push('_'), + '@' => result.push('_'), + ',' => result.push('_'), + ch => result.push(ch), + } + } + result +} diff --git a/zephyr-build/src/devicetree/parse.rs b/zephyr-build/src/devicetree/parse.rs new file mode 100644 index 00000000..44a42899 --- /dev/null +++ b/zephyr-build/src/devicetree/parse.rs @@ -0,0 +1,269 @@ +//! DTS Parser +//! +//! Parse a limited subset of the devicetree source file that is output by the device tree compiler. +//! This is used to parse the `zephyr.dts` file generated as a part of a Zephyr build. + +use std::{cell::RefCell, collections::BTreeMap, rc::Rc}; + +use pest::{iterators::{Pair, Pairs}, Parser}; +use pest_derive::Parser; + +use crate::devicetree::Phandle; + +use super::{ordmap::OrdMap, DeviceTree, Node, Property, Value, Word}; + +#[derive(Parser)] +#[grammar = "devicetree/dts.pest"] +pub struct Dts; + +pub fn parse(text: &str, ords: &OrdMap) -> DeviceTree { + let pairs = Dts::parse(Rule::file, text) + .expect("Parsing zephyr.dts"); + + let b = TreeBuilder::new(ords); + b.walk(pairs) +} + +struct TreeBuilder<'a> { + ords: &'a OrdMap, + /// All labels. + labels: BTreeMap>, +} + +impl<'a> TreeBuilder<'a> { + fn new(ords: &'a OrdMap) -> TreeBuilder<'a> { + TreeBuilder { + ords, + labels: BTreeMap::new(), + } + } + + fn walk(mut self, pairs: Pairs<'_, Rule>) -> DeviceTree { + // There is a single node at the top. + let node = pairs.into_iter().next().unwrap(); + assert_eq!(node.as_rule(), Rule::node); + + DeviceTree { + root: self.walk_node(node, "", &[]), + labels: self.labels, + } + } + + // This is a single node in the DTS. The name should match one of the ordmap entries. + // The root node doesn't get a nodename. + fn walk_node(&mut self, node: Pair<'_, Rule>, path: &str, route: &[String]) -> Rc { + /* + let ord = self.ords.0.get(name) + .expect("Unexpected node path"); + println!("Root: {:?} {}", name, ord); + */ + + let mut name = LazyName::new(path, route.to_owned(), &self.ords); + let mut labels = Vec::new(); + let mut properties = Vec::new(); + let mut children = Vec::new(); + + for pair in node.into_inner() { + match pair.as_rule() { + Rule::nodename => { + let text = pair.as_str(); + name.set(text.to_string()); + } + Rule::label => { + labels.push(pair.as_str().to_string()); + } + Rule::property => { + properties.push(decode_property(pair)); + } + Rule::node => { + let child_path = name.path_ref(); + children.push(self.walk_node(pair, child_path, &name.route_ref())); + } + r => panic!("node: {:?}", r), + } + } + + // Make a clone of the labels, as we need them cloned anyway. + let labels2 = labels.clone(); + + // Build this node. + // println!("Node: {:?}", name.path_ref()); + let mut result = name.into_node(); + result.labels = labels; + result.properties = properties; + result.children = children; + let node = Rc::new(result); + + // Insert all of the labels. + for lab in labels2 { + self.labels.insert(lab, node.clone()); + } + node + } +} + +/// Decode a property node in the parse tree. +fn decode_property(node: Pair<'_, Rule>) -> Property { + let mut name = None; + let mut value = Vec::new(); + for pair in node.into_inner() { + match pair.as_rule() { + Rule::nodename => { + name = Some(pair.as_str().to_string()); + } + Rule::words => { + value.push(Value::Words(decode_words(pair))); + } + Rule::phandle => { + // TODO: Decode these. + // println!("phandle: {:?}", pair.as_str()); + value.push(Value::Phandle(Phandle::new(pair.as_str()[1..].to_string()))); + } + Rule::string => { + // No escapes at this point. + let text = pair.as_str(); + // Remove the quotes. + let text = &text[1..text.len()-1]; + value.push(Value::String(text.to_string())); + } + Rule::bytes => { + value.push(Value::Bytes(decode_bytes(pair))); + } + r => panic!("rule: {:?}", r), + } + } + Property { name: name.unwrap(), value } +} + +fn decode_words<'i>(node: Pair<'i, Rule>) -> Vec { + let mut value = Vec::new(); + for pair in node.into_inner() { + match pair.as_rule() { + Rule::hex_number => { + let text = pair.as_str(); + let num = u32::from_str_radix(&text[2..], 16).unwrap(); + value.push(Word::Number(num)); + } + Rule::decimal_number => { + let text = pair.as_str(); + let num = u32::from_str_radix(text, 10).unwrap(); + value.push(Word::Number(num)); + } + Rule::phandle => { + // println!("phandle: {:?}", pair.as_str()); + let text = pair.as_str(); + value.push(Word::Phandle(Phandle::new(text[1..].to_string()))); + } + _ => unreachable!(), + } + } + value +} + +fn decode_bytes<'i>(node: Pair<'i, Rule>) -> Vec { + let mut value = Vec::new(); + for pair in node.into_inner() { + match pair.as_rule() { + Rule::plain_hex_number => { + let text = pair.as_str(); + let num = u8::from_str_radix(text, 16).unwrap(); + value.push(num) + } + _ => unreachable!(), + } + } + value +} + +// Lazily track the path and node name. The parse tree has the nodename for a given node come after +// entering the node, but before child nodes are seen. +struct LazyName<'a, 'b> { + // The parent path leading up to this node. Will be the empty string for the root node. + path: &'a str, + route: Vec, + ords: &'b OrdMap, + // Our information, once we have it. + info: Option, +} + +struct Info { + name: String, + // Our path, the parent path combined with our name. + path: String, + ord: usize, +} + +impl<'a, 'b> LazyName<'a, 'b> { + fn new(path: &'a str, route: Vec, ords: &'b OrdMap) -> LazyName<'a, 'b> { + if path.is_empty() { + let ord = ords.0["/"]; + LazyName { + path, + route, + ords, + info: Some(Info { + name: "/".to_string(), + path: "/".to_string(), + ord, + }) + } + } else { + LazyName { + path, + route, + ords, + info: None, + } + } + } + + /// Indicate that we now know our name. + fn set(&mut self, name: String) { + if self.info.is_some() { + panic!("Grammar error, node has multiple names"); + } + + self.route.push(name.clone()); + + let mut path = self.path.to_string(); + if path.len() > 1 { + path.push('/'); + } + path.push_str(&name); + // println!("node: {:?}", path); + let ord = self.ords.0[&path]; + self.info = Some(Info { + name, + path, + ord, + }); + } + + fn path_ref(&self) -> &str { + &self.info.as_ref().unwrap().path + } + + fn route_ref(&self) -> &[String] { + &self.route + } + + fn ord(&self) -> usize { + self.info.as_ref().unwrap().ord + } + + // Turn this into a template for a node, with the properties, labels and children as empty. + fn into_node(self) -> Node { + let info = self.info.unwrap(); + + Node { + name: info.name, + path: info.path, + route: self.route, + ord: info.ord, + labels: Vec::new(), + properties: Vec::new(), + children: Vec::new(), + parent: RefCell::new(None), + } + } +} diff --git a/zephyr-build/src/lib.rs b/zephyr-build/src/lib.rs index bec71347..e1b9921a 100644 --- a/zephyr-build/src/lib.rs +++ b/zephyr-build/src/lib.rs @@ -15,9 +15,15 @@ use std::io::{BufRead, BufReader, Write}; use std::env; use std::fs::File; use std::path::Path; +use std::process::{Command, Stdio}; +use proc_macro2::TokenStream; use regex::Regex; +use devicetree::DeviceTree; + +mod devicetree; + /// Export boolean Kconfig entries. This must happen in any crate that wishes to access the /// configuration settings. pub fn export_bool_kconfig() { @@ -73,3 +79,57 @@ pub fn build_kconfig_mod() { } } } + +/// Parse the finalized DTS file, generating the Rust devicetree file. +pub fn build_dts() { + let zephyr_dts = env::var("ZEPHYR_DTS").expect("ZEPHYR_DTS must be set"); + let outdir = env::var("OUT_DIR").expect("OUT_DIR must be set"); + let gen_include = env::var("BINARY_DIR_INCLUDE_GENERATED") + .expect("BINARY_DIR_INCLUDE_GENERATED"); + + let generated = format!("{}/devicetree_generated.h", gen_include); + let dt = DeviceTree::new(&zephyr_dts, generated); + let _ = dt; + + let out_path = Path::new(&outdir).join("devicetree.rs"); + let mut out = File::create(&out_path).expect("Unable to create devicetree.rs"); + + let tokens = dt.to_tokens(); + if has_rustfmt() { + write_formatted(out, tokens); + } else { + writeln!(out, "{}", tokens).unwrap(); + }; +} + +/// Determine if `rustfmt` is in the path, and can be excecuted. Returns false on any kind of error. +pub fn has_rustfmt() -> bool { + match Command::new("rustfmt") + .arg("--version") + .status() + { + Ok(st) if st.success() => true, + _ => false, + } +} + +/// Attempt to write the contents to a file, using rustfmt. If there is an error running rustfmt, +/// print a warning, and then just directly write the file. +fn write_formatted(file: File, tokens: TokenStream) { + let mut rustfmt = Command::new("rustfmt") + .args(["--emit", "stdout"]) + .stdin(Stdio::piped()) + .stdout(file) + .stderr(Stdio::inherit()) + .spawn() + .expect("Failed to run rustfmt"); + // TODO: Handle the above failing. + + let mut stdin = rustfmt.stdin.as_ref().expect("Stdin should have been opened by spawn"); + writeln!(stdin, "{}", tokens).expect("Writing to rustfmt"); + + match rustfmt.wait() { + Ok(st) if st.success() => (), + _ => panic!("Failure running rustfmt"), + } +} diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index 5d43b42d..efd8c8be 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -70,6 +70,15 @@ fn main() -> Result<()> { .derive_copy(false) .allowlist_function("k_.*") .allowlist_function("gpio_.*") + .allowlist_function("flash_.*") + .allowlist_item("GPIO_.*") + .allowlist_item("FLASH_.*") + .allowlist_item("Z_.*") + .allowlist_item("ZR_.*") + .allowlist_item("K_.*") + // Each DT node has a device entry that is a static. + .allowlist_item("__device_dts_ord.*") + .allowlist_function("device_.*") .allowlist_function("sys_.*") .allowlist_function("z_log.*") .allowlist_function("bt_.*") diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 8c3ca158..9300f37d 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -11,6 +11,12 @@ * are output. */ +/* + * This is getting build with KERNEL defined, which causes syscalls to not be implemented. Work + * around this by just undefining this symbol. + */ +#undef KERNEL + #ifdef RUST_BINDGEN /* errno is coming from somewhere in Zephyr's build. Add the symbol when running bindgen so that it * is defined here. @@ -35,11 +41,12 @@ extern int errno; #include #include #include +#include /* - * bindgen will output #defined constant that resolve to simple numbers. There are some symbols - * that we want exported that, at least in some situations, are more complex, usually with a type - * case. + * bindgen will only output #defined constants that resolve to simple numbers. These are some + * symbols that we want exported that, at least in some situations, are more complex, usually with a + * type cast. * * We'll use the prefix "ZR_" to avoid conflicts with other symbols. */ diff --git a/zephyr/build.rs b/zephyr/build.rs index f4345e95..84f3a782 100644 --- a/zephyr/build.rs +++ b/zephyr/build.rs @@ -14,4 +14,5 @@ fn main() { zephyr_build::export_bool_kconfig(); zephyr_build::build_kconfig_mod(); + zephyr_build::build_dts(); } diff --git a/zephyr/src/lib.rs b/zephyr/src/lib.rs index c92ceb4c..94b858ba 100644 --- a/zephyr/src/lib.rs +++ b/zephyr/src/lib.rs @@ -43,6 +43,9 @@ pub mod kconfig { include!(concat!(env!("OUT_DIR"), "/kconfig.rs")); } +// And the generated devicetree. +include!(concat!(env!("OUT_DIR"), "/devicetree.rs")); + // Ensure that Rust is enabled. #[cfg(not(CONFIG_RUST))] compile_error!("CONFIG_RUST must be set to build Rust in Zephyr"); From 0b7f3775951825d6411dc2f18c0ad872e03ce784 Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 16 Sep 2024 13:06:00 -0600 Subject: [PATCH 02/30] Create blinky application Blinky is a rust port of the samples/blinky application from the main zephyr repo. It performs the same function, but using the DT and GPIO abstractions provided in the zephyr::sys module. Signed-off-by: David Brown --- samples/blinky/CMakeLists.txt | 7 +++ samples/blinky/Cargo.toml | 17 ++++++ samples/blinky/README.rst | 97 +++++++++++++++++++++++++++++++++++ samples/blinky/prj.conf | 10 ++++ samples/blinky/sample.yaml | 12 +++++ samples/blinky/src/lib.rs | 57 ++++++++++++++++++++ samples/blinky/src/main.c | 48 +++++++++++++++++ 7 files changed, 248 insertions(+) create mode 100644 samples/blinky/CMakeLists.txt create mode 100644 samples/blinky/Cargo.toml create mode 100644 samples/blinky/README.rst create mode 100644 samples/blinky/prj.conf create mode 100644 samples/blinky/sample.yaml create mode 100644 samples/blinky/src/lib.rs create mode 100644 samples/blinky/src/main.c diff --git a/samples/blinky/CMakeLists.txt b/samples/blinky/CMakeLists.txt new file mode 100644 index 00000000..fdbabbc8 --- /dev/null +++ b/samples/blinky/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(blinky) + +rust_cargo_application() diff --git a/samples/blinky/Cargo.toml b/samples/blinky/Cargo.toml new file mode 100644 index 00000000..cc62c8b9 --- /dev/null +++ b/samples/blinky/Cargo.toml @@ -0,0 +1,17 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +[package] +# This must be rustapp for now. +name = "rustapp" +version = "0.1.0" +edition = "2021" +description = "A sample hello world application in Rust" +license = "Apache-2.0 or MIT" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +zephyr = "3.7.0" +log = "0.4.22" diff --git a/samples/blinky/README.rst b/samples/blinky/README.rst new file mode 100644 index 00000000..ec23fe54 --- /dev/null +++ b/samples/blinky/README.rst @@ -0,0 +1,97 @@ +.. zephyr:code-sample:: blinky + :name: Blinky + :relevant-api: gpio_interface + + Blink an LED forever using the GPIO API. + +Overview +******** + +The Blinky sample blinks an LED forever using the :ref:`GPIO API `. + +The source code shows how to: + +#. Get a pin specification from the :ref:`devicetree ` as a + :c:struct:`gpio_dt_spec` +#. Configure the GPIO pin as an output +#. Toggle the pin forever + +See :zephyr:code-sample:`pwm-blinky` for a similar sample that uses the PWM API instead. + +.. _blinky-sample-requirements: + +Requirements +************ + +Your board must: + +#. Have an LED connected via a GPIO pin (these are called "User LEDs" on many of + Zephyr's :ref:`boards`). +#. Have the LED configured using the ``led0`` devicetree alias. + +Building and Running +******************** + +Build and flash Blinky as follows, changing ``reel_board`` for your board: + +.. zephyr-app-commands:: + :zephyr-app: samples/basic/blinky + :board: reel_board + :goals: build flash + :compact: + +After flashing, the LED starts to blink and messages with the current LED state +are printed on the console. If a runtime error occurs, the sample exits without +printing to the console. + +Build errors +************ + +You will see a build error at the source code line defining the ``struct +gpio_dt_spec led`` variable if you try to build Blinky for an unsupported +board. + +On GCC-based toolchains, the error looks like this: + +.. code-block:: none + + error: '__device_dts_ord_DT_N_ALIAS_led_P_gpios_IDX_0_PH_ORD' undeclared here (not in a function) + +Adding board support +******************** + +To add support for your board, add something like this to your devicetree: + +.. code-block:: DTS + + / { + aliases { + led0 = &myled0; + }; + + leds { + compatible = "gpio-leds"; + myled0: led_0 { + gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; + }; + }; + }; + +The above sets your board's ``led0`` alias to use pin 13 on GPIO controller +``gpio0``. The pin flags :c:macro:`GPIO_ACTIVE_HIGH` mean the LED is on when +the pin is set to its high state, and off when the pin is in its low state. + +Tips: + +- See :dtcompatible:`gpio-leds` for more information on defining GPIO-based LEDs + in devicetree. + +- If you're not sure what to do, check the devicetrees for supported boards which + use the same SoC as your target. See :ref:`get-devicetree-outputs` for details. + +- See :zephyr_file:`include/zephyr/dt-bindings/gpio/gpio.h` for the flags you can use + in devicetree. + +- If the LED is built in to your board hardware, the alias should be defined in + your :ref:`BOARD.dts file `. Otherwise, you can + define one in a :ref:`devicetree overlay `. diff --git a/samples/blinky/prj.conf b/samples/blinky/prj.conf new file mode 100644 index 00000000..1ff6fb75 --- /dev/null +++ b/samples/blinky/prj.conf @@ -0,0 +1,10 @@ +CONFIG_GPIO=y + +CONFIG_RUST=y +CONFIG_RUST_ALLOC=y + +CONFIG_DEBUG=y +CONFIG_MAIN_STACK_SIZE=8192 + +# Verify that userspace builds work. +# CONFIG_USERSPACE=y diff --git a/samples/blinky/sample.yaml b/samples/blinky/sample.yaml new file mode 100644 index 00000000..de711910 --- /dev/null +++ b/samples/blinky/sample.yaml @@ -0,0 +1,12 @@ +sample: + name: Blinky Sample +tests: + sample.basic.blinky: + tags: + - LED + - gpio + filter: dt_enabled_alias_with_parent_compat("led0", "gpio-leds") + depends_on: gpio + harness: led + integration_platforms: + - frdm_k64f diff --git a/samples/blinky/src/lib.rs b/samples/blinky/src/lib.rs new file mode 100644 index 00000000..763cd90e --- /dev/null +++ b/samples/blinky/src/lib.rs @@ -0,0 +1,57 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +use log::warn; + +use core::ffi::c_void; + +use zephyr::raw::GPIO_OUTPUT_ACTIVE; +use zephyr::time::{ Duration, sleep }; + +#[no_mangle] +extern "C" fn rust_main() { + zephyr::set_logger(); + + warn!("Starting blinky"); + // println!("Blinky!"); + // Invoke "blink" as a user thread. + // blink(); + if false { + unsafe { + zephyr::raw::k_thread_user_mode_enter + (Some(blink), + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut()); + } + } else { + unsafe { + blink(core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut()); + } + } +} + +// fn blink() { +unsafe extern "C" fn blink(_p1: *mut c_void, _p2: *mut c_void, _p3: *mut c_void) { + warn!("Inside of blinky"); + + let mut led0 = zephyr::devicetree::aliases::led0::get_instance(); + + if !led0.is_ready() { + warn!("LED is not ready"); + loop { + } + // return; + } + + led0.configure(GPIO_OUTPUT_ACTIVE); + let duration = Duration::millis_at_least(500); + loop { + led0.toggle_pin(); + sleep(duration); + } +} diff --git a/samples/blinky/src/main.c b/samples/blinky/src/main.c new file mode 100644 index 00000000..4cab4969 --- /dev/null +++ b/samples/blinky/src/main.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +/* 1000 msec = 1 sec */ +#define SLEEP_TIME_MS 1000 + +/* The devicetree node identifier for the "led0" alias. */ +#define LED0_NODE DT_ALIAS(led0) + +/* + * A build error on this line means your board is unsupported. + * See the sample documentation for information on how to fix this. + */ +static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); + +int main(void) +{ + int ret; + bool led_state = true; + + if (!gpio_is_ready_dt(&led)) { + return 0; + } + + ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); + if (ret < 0) { + return 0; + } + + while (1) { + ret = gpio_pin_toggle_dt(&led); + if (ret < 0) { + return 0; + } + + led_state = !led_state; + printf("LED state: %s\n", led_state ? "ON" : "OFF"); + k_msleep(SLEEP_TIME_MS); + } + return 0; +} From 1139afb368ee73f7f80e1037cc7219a827c6472c Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 16 Sep 2024 12:51:15 -0600 Subject: [PATCH 03/30] zephyr: sys: Create wrappers for gpio and flash Create two modules with wrappers for Zephyr gpio and flash devices. These have no methods, but allow the DT generated code to compile, with `get_instance` methods that return these values. Signed-off-by: David Brown --- zephyr/src/sys.rs | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/zephyr/src/sys.rs b/zephyr/src/sys.rs index f85069a1..fc771a43 100644 --- a/zephyr/src/sys.rs +++ b/zephyr/src/sys.rs @@ -78,3 +78,51 @@ pub mod critical { } } } + +pub mod gpio { + //! Most devices in Zephyr operate on a `struct device`. This provides untyped access to + //! devices. We want to have stronger typing in the Zephyr interfaces, so most of these types + //! will be wrapped in another structure. This wraps a Gpio device, and provides methods to + //! most of the operations on gpios. + + use crate::raw; + + /// A single instance of a zephyr device to manage a gpio controller. A gpio controller + /// represents a set of gpio pins, that are generally operated on by the same hardware block. + pub struct Gpio { + /// The underlying device itself. + #[allow(dead_code)] + pub(crate) device: *const raw::device, + } + + /// A GpioPin represents a single pin on a gpio device. This is a lightweight wrapper around + /// the Zephyr `gpio_dt_spec` structure. + #[allow(dead_code)] + pub struct GpioPin { + pub(crate) pin: raw::gpio_dt_spec, + } +} + +pub mod flash { + //! Device wrappers for flash controllers, and flash partitions. + + use crate::raw; + + #[allow(dead_code)] + pub struct FlashController { + pub(crate) device: *const raw::device, + } + + /// A wrapper for flash partitions. There is no Zephyr struct that corresponds with this + /// information, which is typically used in a more direct underlying manner. + #[allow(dead_code)] + pub struct FlashPartition { + /// The underlying controller. + #[allow(dead_code)] + pub(crate) controller: FlashController, + #[allow(dead_code)] + pub(crate) offset: u32, + #[allow(dead_code)] + pub(crate) size: u32, + } +} From 85bfbdeeb809889e52c93317573dc452e6a3ca4a Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 16 Sep 2024 15:57:56 -0600 Subject: [PATCH 04/30] zephyr: sys: gpio: Add is_ready method The is_ready method on both `Gpio` and `GpioPin` will ultimately call the underlying `device_is_ready` entry. Signed-off-by: David Brown --- zephyr/src/sys.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/zephyr/src/sys.rs b/zephyr/src/sys.rs index fc771a43..036b9639 100644 --- a/zephyr/src/sys.rs +++ b/zephyr/src/sys.rs @@ -95,12 +95,37 @@ pub mod gpio { pub(crate) device: *const raw::device, } + impl Gpio { + /// Verify that the device is ready for use. At a minimum, this means the device has been + /// successfully initialized. + pub fn is_ready(&self) -> bool { + unsafe { + raw::device_is_ready(self.device) + } + } + } + /// A GpioPin represents a single pin on a gpio device. This is a lightweight wrapper around /// the Zephyr `gpio_dt_spec` structure. #[allow(dead_code)] pub struct GpioPin { pub(crate) pin: raw::gpio_dt_spec, } + + impl GpioPin { + /// Verify that the device is ready for use. At a minimum, this means the device has been + /// successfully initialized. + pub fn is_ready(&self) -> bool { + self.get_gpio().is_ready() + } + + /// Get the underlying Gpio device. + pub fn get_gpio(&self) -> Gpio { + Gpio { + device: self.pin.port, + } + } + } } pub mod flash { From 0104c3360f93d5fc56ac7dd65faba91837a23392 Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 16 Sep 2024 16:04:23 -0600 Subject: [PATCH 05/30] zephyr: sys: gpio: Add configure and pin toggle Create wrappers for config and pin toggle on the gpios. This is enough to allow the blink app to work. Signed-off-by: David Brown --- zephyr/src/sys.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/zephyr/src/sys.rs b/zephyr/src/sys.rs index 036b9639..cd308f7f 100644 --- a/zephyr/src/sys.rs +++ b/zephyr/src/sys.rs @@ -125,6 +125,24 @@ pub mod gpio { device: self.pin.port, } } + + /// Configure a single pin. + pub fn configure(&mut self, extra_flags: raw::gpio_flags_t) { + // TODO: Error? + unsafe { + raw::gpio_pin_configure(self.pin.port, + self.pin.pin, + self.pin.dt_flags as raw::gpio_flags_t | extra_flags); + } + } + + /// Toggle pin level. + pub fn toggle_pin(&mut self) { + // TODO: Error? + unsafe { + raw::gpio_pin_toggle_dt(&self.pin); + } + } } } From 09b58d105752f362441c55c2817703cde31b8664 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 16 Oct 2024 16:20:50 -0600 Subject: [PATCH 06/30] Eliminate warnings in the auto-generated device tree Move the module declaration for the device tree up into `lib.rs` to allow insertion of allow directives to eliminate documentation warnings on the generated devicetree. Signed-off-by: David Brown --- zephyr-build/src/devicetree/output.rs | 29 ++++++++++++++++++--------- zephyr/src/lib.rs | 17 ++++++++++++++-- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/zephyr-build/src/devicetree/output.rs b/zephyr-build/src/devicetree/output.rs index 93b0e806..b900d764 100644 --- a/zephyr-build/src/devicetree/output.rs +++ b/zephyr-build/src/devicetree/output.rs @@ -18,15 +18,15 @@ impl DeviceTree { pub fn to_tokens(&self) -> TokenStream { let augments = get_augments(); - // Root is a little special, as we don't represent the name of the node as it's actual name, - // but as just 'devicetree'. - self.node_walk(self.root.as_ref(), "devicetree", &augments) + // Root is a little special. Since we don't want a module for this (it will be provided + // above where it is included, so it can get documentation and attributes), we use None for + // the name. + self.node_walk(self.root.as_ref(), None, &augments) } - fn node_walk(&self, node: &Node, name: &str, augments: &[Box]) -> TokenStream { - let name_id = dt_to_lower_id(name); + fn node_walk(&self, node: &Node, name: Option<&str>, augments: &[Box]) -> TokenStream { let children = node.children.iter().map(|child| { - self.node_walk(child.as_ref(), &child.name, augments) + self.node_walk(child.as_ref(), Some(&child.name), augments) }); // Simplistic first pass, turn the properties into constents of the formatted text of the // property. @@ -52,12 +52,21 @@ impl DeviceTree { // If this is compatible with an augment, use the augment to add any additional properties. let augs = augments.iter().map(|aug| aug.augment(node, self)); - quote! { - pub mod #name_id { - pub const ORD: usize = #ord; + if let Some(name) = name { + let name_id = dt_to_lower_id(name); + quote! { + pub mod #name_id { + pub const ORD: usize = #ord; + #(#props)* + #(#children)* + // #parent + #(#augs)* + } + } + } else { + quote! { #(#props)* #(#children)* - // #parent #(#augs)* } } diff --git a/zephyr/src/lib.rs b/zephyr/src/lib.rs index 94b858ba..9629488e 100644 --- a/zephyr/src/lib.rs +++ b/zephyr/src/lib.rs @@ -43,8 +43,21 @@ pub mod kconfig { include!(concat!(env!("OUT_DIR"), "/kconfig.rs")); } -// And the generated devicetree. -include!(concat!(env!("OUT_DIR"), "/devicetree.rs")); +pub mod devicetree { + //! Zephyr device tree + //! + //! This is an auto-generated module that represents the device tree for a given build. The + //! hierarchy here should match the device tree, with an additional top-level module "labels" + //! that contains submodules for all of the labels. + //! + //! **Note**: Unless you are viewing docs generated for a specific build, the values below are + //! unlikely to directly correspond to those in a given build. + + // Don't enforce doc comments on the generated device tree. + #![allow(missing_docs)] + + include!(concat!(env!("OUT_DIR"), "/devicetree.rs")); +} // Ensure that Rust is enabled. #[cfg(not(CONFIG_RUST))] From 206bfe7c81ff39e7345f0c9827beed5c1fe0646e Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 17 Oct 2024 23:28:56 -0600 Subject: [PATCH 07/30] zephyr-build: YAML-based augment code Instead of hardcoding all of the augments in the code, put these augments into a data structure. Load the actual rules from a yaml file (with a future ability to extend this with per-app or per-module defined augments. Convert all of the existing hardcoded augment rules into the initial augment file. --- CMakeLists.txt | 5 + dt-rust.yaml | 86 +++++++ zephyr-build/Cargo.toml | 3 + zephyr-build/src/devicetree.rs | 3 + zephyr-build/src/devicetree/config.rs | 309 ++++++++++++++++++++++++++ zephyr-build/src/devicetree/output.rs | 5 +- zephyr-build/src/lib.rs | 29 ++- 7 files changed, 435 insertions(+), 5 deletions(-) create mode 100644 dt-rust.yaml create mode 100644 zephyr-build/src/devicetree/config.rs diff --git a/CMakeLists.txt b/CMakeLists.txt index 6baef049..887a3265 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,9 @@ set(RUST_MODULE_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "") +# Initially, we just have a single DT augment file. +set(DT_AUGMENTS "${CMAKE_CURRENT_LIST_DIR}/dt-rust.yaml" CACHE INTERNAL "") + # Zephyr targets are defined through Kconfig. We need to map these to # an appropriate llvm target triple. This sets `RUST_TARGET` in the # parent scope, or an error if the target is not yet supported by @@ -139,6 +142,7 @@ INCLUDE_DIRS = \"${include_dirs}\" INCLUDE_DEFINES = \"${include_defines}\" WRAPPER_FILE = \"${WRAPPER_FILE}\" BINARY_DIR_INCLUDE_GENERATED = \"${BINARY_DIR_INCLUDE_GENERATED}\" +DT_AUGMENTS = \"${DT_AUGMENTS}\" [patch.crates-io] ${config_paths} @@ -162,6 +166,7 @@ ${config_paths} INCLUDE_DIRS="${include_dirs}" INCLUDE_DEFINES="${include_defines}" WRAPPER_FILE="${WRAPPER_FILE}" + DT_AUGMENTS="${DT_AUGMENTS}" BINARY_DIR_INCLUDE_GENERATED="${BINARY_DIR_INCLUDE_GENERATED}" cargo build ${rust_build_type_arg} diff --git a/dt-rust.yaml b/dt-rust.yaml new file mode 100644 index 00000000..8d2340a4 --- /dev/null +++ b/dt-rust.yaml @@ -0,0 +1,86 @@ +# Description of how to augment the devicetree for Rust. +# +# Each entry describes an augmentation that will be added to matching nodes in the device tree. +# The full syntax is described (indirectly) in `zephyr-build/src/devicetree/config.rs`. + +# Gpio controllers match for every node that has a `gpio-controller` property. This is one of the +# few instances were we can actually just match on a property. +- name: gpio-controller + rules: + - type: has_prop + value: gpio-controller + actions: + - type: instance + value: + raw: + type: myself + device: crate::sys::gpio::Gpio + +# The gpio-leds node will have #children nodes describing each led. We'll match on the parent +# having this compatible property. The nodes themselves are built out of the properties associated +# with each gpio. +- name: gpio-leds + rules: + - type: compatible + value: + names: + - gpio-leds + level: 1 + actions: + - type: instance + value: + raw: + type: phandle + value: gpios + device: crate::sys::gpio::GpioPin + +# Flash controllers don't have any particular property to identify them, so we need a list of +# compatible values that should match. +- name: flash-controller + rules: + - type: compatible + value: + names: + - "nordic,nrf52-flash-controller" + - "raspberrypi,pico-flash-controller" + level: 0 + actions: + - type: instance + value: + raw: + type: myself + device: crate::sys::flash::FlashController + +# Flash partitions exist as children of a node compatible with "soc-nv-flash" that itself is a child +# of the controller itself. +# TODO: Get the write and erase property from the DT if present. +- name: flash-partition + rules: + - type: compatible + value: + names: + - "fixed-partitions" + level: 1 + - type: compatible + value: + names: + - "soc-nv-flash" + level: 2 + actions: + - type: instance + value: + raw: + type: parent + value: + level: 3 + args: + - type: reg + device: "crate::sys::flash::FlashPartition" + +# Generate a pseudo node that matches all of the labels across the tree with their nodes. +- name: labels + rules: + - type: root + actions: + - type: labels + diff --git a/zephyr-build/Cargo.toml b/zephyr-build/Cargo.toml index 5d0b57d0..409f7b93 100644 --- a/zephyr-build/Cargo.toml +++ b/zephyr-build/Cargo.toml @@ -19,3 +19,6 @@ pest = "2.6" pest_derive = "2.6" quote = "1.0" proc-macro2 = "1.0.86" +serde = { version = "1.0", features = ["derive"] } +serde_yaml_ng = "0.10" +anyhow = "1.0.89" diff --git a/zephyr-build/src/devicetree.rs b/zephyr-build/src/devicetree.rs index a977c15e..2ec6b64b 100644 --- a/zephyr-build/src/devicetree.rs +++ b/zephyr-build/src/devicetree.rs @@ -24,10 +24,13 @@ use ordmap::OrdMap; use std::{cell::RefCell, collections::BTreeMap, path::Path, rc::Rc}; mod augment; +pub mod config; mod ordmap; mod output; mod parse; +pub use augment::Augment; + pub struct DeviceTree { /// The root of the tree. root: Rc, diff --git a/zephyr-build/src/devicetree/config.rs b/zephyr-build/src/devicetree/config.rs new file mode 100644 index 00000000..94d14138 --- /dev/null +++ b/zephyr-build/src/devicetree/config.rs @@ -0,0 +1,309 @@ +//! Config handling. +//! +//! There are various aspects of the device tree in Zephyr whose semantics are only indirectly +//! defined by the behavior of C code. Rather than trying to decipher this at build time, we will +//! use one or more yaml files that describe aspects of the device tree. +//! +//! This module is responsible for the format of this config file and the parsed contents will be +//! used to generate the [`Augment`] objects that will do the actual augmentation of the generated +//! device tree. +//! +//! Each augment is described by a top-level yaml element in an array. + +use std::{fs::File, path::Path}; + +use anyhow::Result; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; +use serde::{Deserialize, Serialize}; + +use crate::devicetree::{output::dt_to_lower_id, Word}; + +use super::{augment::Augment, DeviceTree, Node}; + +/// A top level augmentation. +/// +/// This top level augmentation describes how to match a given node within the device tree, and then +/// what kind of action to describe upon that. +#[derive(Debug, Serialize, Deserialize)] +pub struct Augmentation { + /// A name for this augmentation. Used for diagnostic purposes. + name: String, + /// What to match. This is an array, and all must match for a given node to be considered. + /// This does mean that if this is an empty array, it will match on every node. + rules: Vec, + /// What to do when a given node matches. + actions: Vec, +} + +impl Augment for Augmentation { + fn is_compatible(&self, node: &Node) -> bool { + self.rules.iter().all(|n| n.is_compatible(node)) + } + + fn generate(&self, node: &Node, tree: &DeviceTree) -> TokenStream { + let name = format_ident!("{}", dt_to_lower_id(&self.name)); + let actions = self.actions.iter().map(|a| a.generate(&name, node, tree)); + + quote! { + #(#actions)* + } + } +} + +/// A matching rule. +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case", content = "value")] +pub enum Rule { + /// A set of "or" matches. + Or(Vec), + /// A set of "and" matches. Not needed at the top level, as the top level vec is an implicit + /// and. + And(Vec), + /// Matches if the node has the given property. + HasProp(String), + /// Matches if this node has one of the listed compatible strings. The the 'level' property + /// indicates how many levels up in the tree. Zero means match the current node, 1 means the + /// parent node, and so on. + Compatible { + names: Vec, + level: usize, + }, + /// Matches at the root of tree. + Root, +} + +impl Rule { + fn is_compatible(&self, node: &Node) -> bool { + match self { + Rule::Or(rules) => rules.iter().any(|n| n.is_compatible(node)), + Rule::And(rules) => rules.iter().all(|n| n.is_compatible(node)), + Rule::HasProp(name) => node.has_prop(name), + Rule::Compatible { names, level } => parent_compatible(node, names, *level), + Rule::Root => node.parent.borrow().is_none(), + } + } +} + +/// Determine if a node is compatible, looking `levels` levels up in the tree, where 0 means this +/// node. +fn parent_compatible(node: &Node, names: &[String], level: usize) -> bool { + // Writing this recursively simplifies the borrowing a lot. Otherwise, we'd have to clone the + // RCs. Our choice is the extra clone, or keeping the borrowed values on the stack. This code + // runs on the host, so the stack is easier. + if level == 0 { + names.iter().any(|n| node.is_compatible(n)) + } else { + if let Some(parent) = node.parent.borrow().as_ref() { + parent_compatible(parent, names, level - 1) + } else { + false + } + } +} + +/// An action to perform +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case", content = "value")] +pub enum Action { + /// Generate an "instance" with a specific device name. + Instance { + /// Where to get the raw device information. + raw: RawInfo, + /// The name of the full path (within the zephyr-sys crate) for the wrapper node for this + /// device. + device: String, + }, + /// Generate all of the labels as its own node. + Labels, +} + +impl Action { + fn generate(&self, _name: &Ident, node: &Node, tree: &DeviceTree) -> TokenStream { + match self { + Action::Instance { raw, device } => { + raw.generate(node, device) + } + Action::Labels => { + let nodes = tree.labels.iter().map(|(k, v)| { + let name = dt_to_lower_id(k); + let path = v.route_to_rust(); + quote! { + pub mod #name { + pub use #path::*; + } + } + }); + + quote! { + // This does assume the devicetree doesn't have a "labels" node at the root. + pub mod labels { + /// All of the labeles in the device tree. The device tree compiler + /// enforces that these are unique, allowing references such as + /// `zephyr::devicetree::labels::labelname::get_instance()`. + #(#nodes)* + } + } + } + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case", content = "value")] +pub enum RawInfo { + /// Get the raw device directly from this node. + Myself, + /// Get the reference from a parent of this node, at a given level. + Parent { + /// How many levels to look up. 0 would refer to this node (but would also be an error). + level: usize, + args: Vec, + }, + /// Get the raw device from a phandle property. Additional parameters in the phandle will be + /// passed as additional arguments to the `new` constructor on the wrapper type. + Phandle(String), +} + +impl RawInfo { + fn generate(&self, node: &Node, device: &str) -> TokenStream { + let device_id = str_to_path(device); + match self { + RawInfo::Myself => { + let ord = node.ord; + let rawdev = format_ident!("__device_dts_ord_{}", ord); + quote! { + /// Get the raw `const struct device *` of the device tree generated node. + pub unsafe fn get_instance_raw() -> *const crate::raw::device { + &crate::raw::#rawdev + } + pub fn get_instance() -> #device_id { + unsafe { + let device = get_instance_raw(); + #device_id::new(device) + } + } + } + } + RawInfo::Phandle(pname) => { + let words = node.get_words(pname).unwrap(); + // We assume that elt 0 is the phandle, and that the rest are numbers. + let target = if let Word::Phandle(handle) = &words[0] { + handle.node_ref() + } else { + panic!("phandle property {:?} in node is empty", pname); + }; + + // TODO: We would try to correlate with parent node's notion of number of cells, and + // try to handle cases where there is more than one reference. It is unclear when + // this will be needed. + let args: Vec = words[1..].iter().map(|n| n.get_number().unwrap()).collect(); + + let target_route = target.route_to_rust(); + + quote! { + pub fn get_instance() -> #device_id { + unsafe { + let device = #target_route :: get_instance_raw(); + #device_id::new(device, #(#args),*) + } + } + } + } + RawInfo::Parent { level, args } => { + let get_args = args.iter().map(|arg| arg.args(node)); + + assert!(*level > 0); + let mut path = quote! {super}; + for _ in 1..*level { + path = quote! { #path :: super }; + } + + quote! { + pub fn get_instance() -> #device_id { + unsafe { + let device = #path :: get_instance_raw(); + #device_id::new(device, #(#get_args),*) + } + } + } + } + } + } +} + +/// Information about where to get constructor properties for arguments. +/// +/// At this point, we assume these all come from the current node. +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case", content = "value")] +pub enum ArgInfo { + /// The arguments come from a 'reg' property. + Reg, +} + +impl ArgInfo { + /// Extra properties for the argument, assembling the arguents that should be passed in. + fn args(&self, node: &Node) -> TokenStream { + match self { + ArgInfo::Reg => { + let reg = node.get_numbers("reg").unwrap(); + quote! { + #(#reg),* + } + } + } + } +} + +/// Split a path given by a user into a token stream. +fn str_to_path(path: &str) -> TokenStream { + let names = path.split("::").map(|n| format_ident!("{}", n)); + quote! { + #(#names)::* + } +} + +/// Load a file of the given name. +pub fn load>(name: P) -> Result> { + let fd = File::open(name)?; + let augs: Vec = serde_yaml_ng::from_reader(fd)?; + Ok(augs) +} + +/// Output a sample yaml file, so we can understand the format. +pub fn sample() { + use std::fs::File; + + let data = vec![ + Augmentation { + name: "gpio-controller".to_string(), + rules: vec![ + Rule::HasProp("gpio-controller".to_string()), + ], + actions: vec![ + Action::Instance { + raw: RawInfo::Myself, + device: "crate::sys::gpio::Gpio".to_string(), + }, + ], + }, + Augmentation { + name: "gpio-leds".to_string(), + rules: vec![ + Rule::Compatible { + names: vec!["gpio-leds".to_string()], + level: 1, + }, + ], + actions: vec![ + Action::Instance { + raw: RawInfo::Phandle("gpios".to_string()), + device: "crate::sys::gpio::GpioPin".to_string(), + } + ], + }, + ]; + let fd = File::create("dt-sample.yaml").unwrap(); + serde_yaml_ng::to_writer(fd, &data).unwrap(); +} diff --git a/zephyr-build/src/devicetree/output.rs b/zephyr-build/src/devicetree/output.rs index b900d764..6fae6ec3 100644 --- a/zephyr-build/src/devicetree/output.rs +++ b/zephyr-build/src/devicetree/output.rs @@ -11,12 +11,11 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; -use super::{augment::{get_augments, Augment}, DeviceTree, Node, Property, Value, Word}; +use super::{augment::Augment, DeviceTree, Node, Property, Value, Word}; impl DeviceTree { /// Generate a TokenStream for the Rust representation of this device tree. - pub fn to_tokens(&self) -> TokenStream { - let augments = get_augments(); + pub fn to_tokens(&self, augments: &[Box]) -> TokenStream { // Root is a little special. Since we don't want a module for this (it will be provided // above where it is included, so it can get documentation and attributes), we use None for diff --git a/zephyr-build/src/lib.rs b/zephyr-build/src/lib.rs index e1b9921a..e9be1320 100644 --- a/zephyr-build/src/lib.rs +++ b/zephyr-build/src/lib.rs @@ -20,10 +20,13 @@ use std::process::{Command, Stdio}; use proc_macro2::TokenStream; use regex::Regex; -use devicetree::DeviceTree; +use devicetree::{Augment, DeviceTree}; mod devicetree; +/// For debugging. +pub use devicetree::config::sample; + /// Export boolean Kconfig entries. This must happen in any crate that wishes to access the /// configuration settings. pub fn export_bool_kconfig() { @@ -87,6 +90,28 @@ pub fn build_dts() { let gen_include = env::var("BINARY_DIR_INCLUDE_GENERATED") .expect("BINARY_DIR_INCLUDE_GENERATED"); + let augments = env::var("DT_AUGMENTS").expect("DT_AUGMENTS must be set"); + let augments: Vec = augments.split_whitespace().map(String::from).collect(); + + // Make sure that cargo knows to run if this changes, or any file mentioned changes. + println!("cargo:rerun-if-env-changed=DT_AUGMENTS"); + for name in &augments { + println!("cargo:rerun-if-changed={}", name); + } + + let mut augs = Vec::new(); + for aug in &augments { + // println!("Load augment: {:?}", aug); + let mut aug = devicetree::config::load(aug).expect("Loading augment file"); + augs.append(&mut aug); + } + // For now, just print it out. + // println!("augments: {:#?}", augs); + let augs: Vec<_> = augs + .into_iter() + .map(|aug| Box::new(aug) as Box) + .collect(); + let generated = format!("{}/devicetree_generated.h", gen_include); let dt = DeviceTree::new(&zephyr_dts, generated); let _ = dt; @@ -94,7 +119,7 @@ pub fn build_dts() { let out_path = Path::new(&outdir).join("devicetree.rs"); let mut out = File::create(&out_path).expect("Unable to create devicetree.rs"); - let tokens = dt.to_tokens(); + let tokens = dt.to_tokens(&augs); if has_rustfmt() { write_formatted(out, tokens); } else { From 129332a268d435a6727f0978581fff2869522baa Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 10:12:26 -0600 Subject: [PATCH 08/30] zephyr: Add unsafe constructor to device types Add constructors to the individual device types. These are unsafe, and are all referenced from the generated devicetree code. --- zephyr/src/sys.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/zephyr/src/sys.rs b/zephyr/src/sys.rs index cd308f7f..86258b48 100644 --- a/zephyr/src/sys.rs +++ b/zephyr/src/sys.rs @@ -96,6 +96,13 @@ pub mod gpio { } impl Gpio { + /// Constructor, used by the devicetree generated code. + /// + /// TODO: Guarantee single instancing. + pub unsafe fn new(device: *const raw::device) -> Gpio { + Gpio { device } + } + /// Verify that the device is ready for use. At a minimum, this means the device has been /// successfully initialized. pub fn is_ready(&self) -> bool { @@ -113,6 +120,19 @@ pub mod gpio { } impl GpioPin { + /// Constructor, used by the devicetree generated code. + /// + /// TODO: Guarantee single instancing. + pub unsafe fn new(device: *const raw::device, pin: u32, dt_flags: u32) -> GpioPin { + GpioPin { + pin: raw::gpio_dt_spec { + port: device, + pin: pin as raw::gpio_pin_t, + dt_flags: dt_flags as raw::gpio_dt_flags_t, + } + } + } + /// Verify that the device is ready for use. At a minimum, this means the device has been /// successfully initialized. pub fn is_ready(&self) -> bool { @@ -156,6 +176,15 @@ pub mod flash { pub(crate) device: *const raw::device, } + impl FlashController { + /// Constructor, intended to be called by devicetree generated code. + /// + /// TODO: Instance safety + pub unsafe fn new(device: *const raw::device) -> FlashController { + FlashController { device } + } + } + /// A wrapper for flash partitions. There is no Zephyr struct that corresponds with this /// information, which is typically used in a more direct underlying manner. #[allow(dead_code)] @@ -168,4 +197,14 @@ pub mod flash { #[allow(dead_code)] pub(crate) size: u32, } + + impl FlashPartition { + /// Constructor, intended to be called by devicetree generated code. + pub unsafe fn new(device: *const raw::device, offset: u32, size: u32) -> FlashPartition { + // The `get_instance` on the flash controller would try to guarantee a unique instance, + // but in this case, we need one for each device, so just construct it here. + let controller = FlashController::new(device); + FlashPartition { controller, offset, size } + } + } } From a0ae72610e854e851f51baa23570c9cfd64c7c48 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 10:36:55 -0600 Subject: [PATCH 09/30] zephyr: Enforce uniqueness on device tree wrappers Move all of the device implementations into a `zephyr::device` module. In this module, add a `Unique` type that supports the constructors on the device requiring a unique instance. The device tree augmentation code adds a declaration for these uniqueness markers for each instance, passing it into the constructor. Signed-off-by: David Brown --- dt-rust.yaml | 8 +- samples/blinky/src/lib.rs | 2 +- zephyr-build/src/devicetree/config.rs | 16 ++- zephyr/src/device.rs | 191 ++++++++++++++++++++++++++ zephyr/src/lib.rs | 1 + zephyr/src/sys.rs | 130 ------------------ 6 files changed, 207 insertions(+), 141 deletions(-) create mode 100644 zephyr/src/device.rs diff --git a/dt-rust.yaml b/dt-rust.yaml index 8d2340a4..4f4fb085 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -14,7 +14,7 @@ value: raw: type: myself - device: crate::sys::gpio::Gpio + device: crate::device::gpio::Gpio # The gpio-leds node will have #children nodes describing each led. We'll match on the parent # having this compatible property. The nodes themselves are built out of the properties associated @@ -32,7 +32,7 @@ raw: type: phandle value: gpios - device: crate::sys::gpio::GpioPin + device: crate::device::gpio::GpioPin # Flash controllers don't have any particular property to identify them, so we need a list of # compatible values that should match. @@ -49,7 +49,7 @@ value: raw: type: myself - device: crate::sys::flash::FlashController + device: crate::device::flash::FlashController # Flash partitions exist as children of a node compatible with "soc-nv-flash" that itself is a child # of the controller itself. @@ -75,7 +75,7 @@ level: 3 args: - type: reg - device: "crate::sys::flash::FlashPartition" + device: "crate::device::flash::FlashPartition" # Generate a pseudo node that matches all of the labels across the tree with their nodes. - name: labels diff --git a/samples/blinky/src/lib.rs b/samples/blinky/src/lib.rs index 763cd90e..50ce8de5 100644 --- a/samples/blinky/src/lib.rs +++ b/samples/blinky/src/lib.rs @@ -39,7 +39,7 @@ extern "C" fn rust_main() { unsafe extern "C" fn blink(_p1: *mut c_void, _p2: *mut c_void, _p3: *mut c_void) { warn!("Inside of blinky"); - let mut led0 = zephyr::devicetree::aliases::led0::get_instance(); + let mut led0 = zephyr::devicetree::aliases::led0::get_instance().unwrap(); if !led0.is_ready() { warn!("LED is not ready"); diff --git a/zephyr-build/src/devicetree/config.rs b/zephyr-build/src/devicetree/config.rs index 94d14138..09488bef 100644 --- a/zephyr-build/src/devicetree/config.rs +++ b/zephyr-build/src/devicetree/config.rs @@ -177,10 +177,12 @@ impl RawInfo { pub unsafe fn get_instance_raw() -> *const crate::raw::device { &crate::raw::#rawdev } - pub fn get_instance() -> #device_id { + + static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + pub fn get_instance() -> Option<#device_id> { unsafe { let device = get_instance_raw(); - #device_id::new(device) + #device_id::new(&UNIQUE, device) } } } @@ -202,10 +204,11 @@ impl RawInfo { let target_route = target.route_to_rust(); quote! { - pub fn get_instance() -> #device_id { + static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + pub fn get_instance() -> Option<#device_id> { unsafe { let device = #target_route :: get_instance_raw(); - #device_id::new(device, #(#args),*) + #device_id::new(&UNIQUE, device, #(#args),*) } } } @@ -220,10 +223,11 @@ impl RawInfo { } quote! { - pub fn get_instance() -> #device_id { + static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + pub fn get_instance() -> Option<#device_id> { unsafe { let device = #path :: get_instance_raw(); - #device_id::new(device, #(#get_args),*) + #device_id::new(&UNIQUE, device, #(#get_args),*) } } } diff --git a/zephyr/src/device.rs b/zephyr/src/device.rs new file mode 100644 index 00000000..a4dff9e6 --- /dev/null +++ b/zephyr/src/device.rs @@ -0,0 +1,191 @@ +//! Device wrappers +//! +//! This module contains implementations of wrappers for various types of devices in zephyr. In +//! general, these wrap a `*const device` from Zephyr, and provide an API that is appropriate. +//! +//! Most of these instances come from the device tree. + +use crate::sync::atomic::{AtomicUsize, Ordering}; + +// Allow dead code, because it isn't required for a given build to have any devices. +/// Device uniqueness. +/// +/// As the zephyr devices are statically defined structures, this `Unique` value ensures that the +/// user is only able to get a single instance of any given device. +/// +/// Note that some devices in zephyr will require more than one instance of the actual device. For +/// example, a [`GpioPin`] will reference a single pin, but the underlying device for the gpio +/// driver will be shared among then. Generally, the constructor for the individual device will +/// call `get_instance_raw()` on the underlying device. +#[allow(dead_code)] +pub(crate) struct Unique(pub(crate) AtomicUsize); + +impl Unique { + /// Construct a new unique counter. + pub(crate) const fn new() -> Unique { + Unique(AtomicUsize::new(0)) + } + + /// Indicates if this particular entity can be used. This function, on a given `Unique` value + /// will return true exactly once. + #[allow(dead_code)] + pub(crate) fn once(&self) -> bool { + // `fetch_add` is likely to be faster than compare_exchage. This does have the limitation + // that `once` is not called more than `usize::MAX` times. + self.0.fetch_add(1, Ordering::AcqRel) == 0 + } +} + +pub mod gpio { + //! Most devices in Zephyr operate on a `struct device`. This provides untyped access to + //! devices. We want to have stronger typing in the Zephyr interfaces, so most of these types + //! will be wrapped in another structure. This wraps a Gpio device, and provides methods to + //! most of the operations on gpios. + + use crate::raw; + use super::Unique; + + /// A single instance of a zephyr device to manage a gpio controller. A gpio controller + /// represents a set of gpio pins, that are generally operated on by the same hardware block. + pub struct Gpio { + /// The underlying device itself. + #[allow(dead_code)] + pub(crate) device: *const raw::device, + } + + impl Gpio { + /// Constructor, used by the devicetree generated code. + /// + /// TODO: Guarantee single instancing. + pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { + if !unique.once() { + return None; + } + Some(Gpio { device }) + } + + /// Verify that the device is ready for use. At a minimum, this means the device has been + /// successfully initialized. + pub fn is_ready(&self) -> bool { + unsafe { + raw::device_is_ready(self.device) + } + } + } + + /// A GpioPin represents a single pin on a gpio device. This is a lightweight wrapper around + /// the Zephyr `gpio_dt_spec` structure. + #[allow(dead_code)] + pub struct GpioPin { + pub(crate) pin: raw::gpio_dt_spec, + } + + impl GpioPin { + /// Constructor, used by the devicetree generated code. + /// + /// TODO: Guarantee single instancing. + pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device, pin: u32, dt_flags: u32) -> Option { + if !unique.once() { + return None; + } + Some(GpioPin { + pin: raw::gpio_dt_spec { + port: device, + pin: pin as raw::gpio_pin_t, + dt_flags: dt_flags as raw::gpio_dt_flags_t, + } + }) + } + + /// Verify that the device is ready for use. At a minimum, this means the device has been + /// successfully initialized. + pub fn is_ready(&self) -> bool { + self.get_gpio().is_ready() + } + + /// Get the underlying Gpio device. + pub fn get_gpio(&self) -> Gpio { + Gpio { + device: self.pin.port, + } + } + + /// Configure a single pin. + pub fn configure(&mut self, extra_flags: raw::gpio_flags_t) { + // TODO: Error? + unsafe { + raw::gpio_pin_configure(self.pin.port, + self.pin.pin, + self.pin.dt_flags as raw::gpio_flags_t | extra_flags); + } + } + + /// Toggle pin level. + pub fn toggle_pin(&mut self) { + // TODO: Error? + unsafe { + raw::gpio_pin_toggle_dt(&self.pin); + } + } + } +} + +pub mod flash { + //! Device wrappers for flash controllers, and flash partitions. + + use crate::raw; + use super::Unique; + + /// A flash controller + /// + /// This is a wrapper around the `struct device` in Zephyr that represents a flash controller. + /// Using the flash controller allows flash operations on the entire device. See + /// [`FlashPartition`] for a wrapper that limits the operation to a partition as defined in the + /// DT. + #[allow(dead_code)] + pub struct FlashController { + pub(crate) device: *const raw::device, + } + + impl FlashController { + /// Constructor, intended to be called by devicetree generated code. + pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { + if !unique.once() { + return None; + } + + Some(FlashController { device }) + } + } + + /// A wrapper for flash partitions. There is no Zephyr struct that corresponds with this + /// information, which is typically used in a more direct underlying manner. + #[allow(dead_code)] + pub struct FlashPartition { + /// The underlying controller. + #[allow(dead_code)] + pub(crate) controller: FlashController, + #[allow(dead_code)] + pub(crate) offset: u32, + #[allow(dead_code)] + pub(crate) size: u32, + } + + impl FlashPartition { + /// Constructor, intended to be called by devicetree generated code. + pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device, offset: u32, size: u32) -> Option { + if !unique.once() { + return None; + } + + // The `get_instance` on the flash controller would try to guarantee a unique instance, + // but in this case, we need one for each device, so just construct it here. + // TODO: This is not actually safe. + let controller = FlashController { device }; + Some(FlashPartition { controller, offset, size }) + } + } + + // Note that currently, the flash partition shares the controller, so the underlying operations + // are not actually safe. Need to rethink how to manage this. +} diff --git a/zephyr/src/lib.rs b/zephyr/src/lib.rs index 9629488e..4acda6a4 100644 --- a/zephyr/src/lib.rs +++ b/zephyr/src/lib.rs @@ -11,6 +11,7 @@ #![deny(missing_docs)] pub mod align; +pub mod device; pub mod error; pub mod logging; pub mod object; diff --git a/zephyr/src/sys.rs b/zephyr/src/sys.rs index 86258b48..f85069a1 100644 --- a/zephyr/src/sys.rs +++ b/zephyr/src/sys.rs @@ -78,133 +78,3 @@ pub mod critical { } } } - -pub mod gpio { - //! Most devices in Zephyr operate on a `struct device`. This provides untyped access to - //! devices. We want to have stronger typing in the Zephyr interfaces, so most of these types - //! will be wrapped in another structure. This wraps a Gpio device, and provides methods to - //! most of the operations on gpios. - - use crate::raw; - - /// A single instance of a zephyr device to manage a gpio controller. A gpio controller - /// represents a set of gpio pins, that are generally operated on by the same hardware block. - pub struct Gpio { - /// The underlying device itself. - #[allow(dead_code)] - pub(crate) device: *const raw::device, - } - - impl Gpio { - /// Constructor, used by the devicetree generated code. - /// - /// TODO: Guarantee single instancing. - pub unsafe fn new(device: *const raw::device) -> Gpio { - Gpio { device } - } - - /// Verify that the device is ready for use. At a minimum, this means the device has been - /// successfully initialized. - pub fn is_ready(&self) -> bool { - unsafe { - raw::device_is_ready(self.device) - } - } - } - - /// A GpioPin represents a single pin on a gpio device. This is a lightweight wrapper around - /// the Zephyr `gpio_dt_spec` structure. - #[allow(dead_code)] - pub struct GpioPin { - pub(crate) pin: raw::gpio_dt_spec, - } - - impl GpioPin { - /// Constructor, used by the devicetree generated code. - /// - /// TODO: Guarantee single instancing. - pub unsafe fn new(device: *const raw::device, pin: u32, dt_flags: u32) -> GpioPin { - GpioPin { - pin: raw::gpio_dt_spec { - port: device, - pin: pin as raw::gpio_pin_t, - dt_flags: dt_flags as raw::gpio_dt_flags_t, - } - } - } - - /// Verify that the device is ready for use. At a minimum, this means the device has been - /// successfully initialized. - pub fn is_ready(&self) -> bool { - self.get_gpio().is_ready() - } - - /// Get the underlying Gpio device. - pub fn get_gpio(&self) -> Gpio { - Gpio { - device: self.pin.port, - } - } - - /// Configure a single pin. - pub fn configure(&mut self, extra_flags: raw::gpio_flags_t) { - // TODO: Error? - unsafe { - raw::gpio_pin_configure(self.pin.port, - self.pin.pin, - self.pin.dt_flags as raw::gpio_flags_t | extra_flags); - } - } - - /// Toggle pin level. - pub fn toggle_pin(&mut self) { - // TODO: Error? - unsafe { - raw::gpio_pin_toggle_dt(&self.pin); - } - } - } -} - -pub mod flash { - //! Device wrappers for flash controllers, and flash partitions. - - use crate::raw; - - #[allow(dead_code)] - pub struct FlashController { - pub(crate) device: *const raw::device, - } - - impl FlashController { - /// Constructor, intended to be called by devicetree generated code. - /// - /// TODO: Instance safety - pub unsafe fn new(device: *const raw::device) -> FlashController { - FlashController { device } - } - } - - /// A wrapper for flash partitions. There is no Zephyr struct that corresponds with this - /// information, which is typically used in a more direct underlying manner. - #[allow(dead_code)] - pub struct FlashPartition { - /// The underlying controller. - #[allow(dead_code)] - pub(crate) controller: FlashController, - #[allow(dead_code)] - pub(crate) offset: u32, - #[allow(dead_code)] - pub(crate) size: u32, - } - - impl FlashPartition { - /// Constructor, intended to be called by devicetree generated code. - pub unsafe fn new(device: *const raw::device, offset: u32, size: u32) -> FlashPartition { - // The `get_instance` on the flash controller would try to guarantee a unique instance, - // but in this case, we need one for each device, so just construct it here. - let controller = FlashController::new(device); - FlashPartition { controller, offset, size } - } - } -} From 70ed3b7289bfb5231fa9b04a963760c1334dc0fd Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 11:38:10 -0600 Subject: [PATCH 10/30] zephyr: Make gpio interface much more "unsafe" Gpios in Zephyr are inherently unsafe. There is a shared controller that is not just used by the pins defined here, but extensively across various drivers. As such, make all of the gpio pin operations themselves unsafe. We can help, a little bit, to at least enforce uniqueness with the Rust drivers that use gpios by requiring them to take a mutable instance of `GpioToken`, which has a singleton getter. Signed-off-by: David Brown --- zephyr/src/device.rs | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/zephyr/src/device.rs b/zephyr/src/device.rs index a4dff9e6..dca7d383 100644 --- a/zephyr/src/device.rs +++ b/zephyr/src/device.rs @@ -41,10 +41,36 @@ pub mod gpio { //! devices. We want to have stronger typing in the Zephyr interfaces, so most of these types //! will be wrapped in another structure. This wraps a Gpio device, and provides methods to //! most of the operations on gpios. + //! + //! Safey: In general, even just using gpio pins is unsafe in Zephyr. The gpio drivers are used + //! pervasively throughout Zephyr device drivers. As such, most of the calls in this module are + //! unsafe. use crate::raw; use super::Unique; + /// Global instance to help make gpio in Rust slightly safer. + /// + /// To help with safety, the rust types use a global instance of a gpio-token. Methods will + /// take a mutable reference to this, which will require either a single thread in the + /// application code, or something like a mutex or critical section to manage. The operation + /// methods are still unsafe, because we have no control over what happens with the gpio + /// operations outside of Rust code, but this will help make the Rust usage at least better. + pub struct GpioToken(()); + + static GPIO_TOKEN: Unique = Unique::new(); + + impl GpioToken { + /// Retrieves the gpio token. This is unsafe because lots of code in zephyr operates on the + /// gpio drivers. + pub unsafe fn get_instance() -> Option { + if !GPIO_TOKEN.once() { + return None; + } + Some(GpioToken(())) + } + } + /// A single instance of a zephyr device to manage a gpio controller. A gpio controller /// represents a set of gpio pins, that are generally operated on by the same hardware block. pub struct Gpio { @@ -73,8 +99,11 @@ pub mod gpio { } } - /// A GpioPin represents a single pin on a gpio device. This is a lightweight wrapper around - /// the Zephyr `gpio_dt_spec` structure. + /// A GpioPin represents a single pin on a gpio device. + /// + /// This is a lightweight wrapper around the Zephyr `gpio_dt_spec` structure. Note that + /// multiple pins may share a gpio controller, and as such, all methods on this are both unsafe, + /// and require a mutable reference to the [`GpioToken`]. #[allow(dead_code)] pub struct GpioPin { pub(crate) pin: raw::gpio_dt_spec, @@ -111,7 +140,7 @@ pub mod gpio { } /// Configure a single pin. - pub fn configure(&mut self, extra_flags: raw::gpio_flags_t) { + pub unsafe fn configure(&mut self, _token: &mut GpioToken, extra_flags: raw::gpio_flags_t) { // TODO: Error? unsafe { raw::gpio_pin_configure(self.pin.port, @@ -121,7 +150,7 @@ pub mod gpio { } /// Toggle pin level. - pub fn toggle_pin(&mut self) { + pub unsafe fn toggle_pin(&mut self, _token: &mut GpioToken) { // TODO: Error? unsafe { raw::gpio_pin_toggle_dt(&self.pin); From ed06d979548ec99ba3a4787c4228a7b65a91d834 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 11:49:43 -0600 Subject: [PATCH 11/30] zephyr-build: devicetree: Move augments into module The hard-coded augments in augment.rs are no long used, so remove them all, and move the config version into this file to avoid needing a separate module, just for the trait. Signed-off-by: David Brown --- zephyr-build/src/devicetree.rs | 3 +- zephyr-build/src/devicetree/augment.rs | 396 ++++++++++++++----------- zephyr-build/src/devicetree/config.rs | 313 ------------------- zephyr-build/src/lib.rs | 5 +- 4 files changed, 221 insertions(+), 496 deletions(-) delete mode 100644 zephyr-build/src/devicetree/config.rs diff --git a/zephyr-build/src/devicetree.rs b/zephyr-build/src/devicetree.rs index 2ec6b64b..6c7d0d08 100644 --- a/zephyr-build/src/devicetree.rs +++ b/zephyr-build/src/devicetree.rs @@ -24,12 +24,11 @@ use ordmap::OrdMap; use std::{cell::RefCell, collections::BTreeMap, path::Path, rc::Rc}; mod augment; -pub mod config; mod ordmap; mod output; mod parse; -pub use augment::Augment; +pub use augment::{Augment, load_augments}; pub struct DeviceTree { /// The root of the tree. diff --git a/zephyr-build/src/devicetree/augment.rs b/zephyr-build/src/devicetree/augment.rs index b39a7196..6b82d87f 100644 --- a/zephyr-build/src/devicetree/augment.rs +++ b/zephyr-build/src/devicetree/augment.rs @@ -1,11 +1,25 @@ //! Support for augmenting the device tree. - -use proc_macro2::TokenStream; +//! +//! There are various aspects of the device tree in Zephyr whose semantics are only indirectly +//! defined by the behavior of C code. Rather than trying to decipher this at build time, we will +//! use one or more yaml files that describe aspects of the device tree. +//! +//! This module is responsible for the format of this config file and the parsed contents will be +//! used to generate the [`Augment`] objects that will do the actual augmentation of the generated +//! device tree. +//! +//! Each augment is described by a top-level yaml element in an array. + +use std::{fs::File, path::Path}; + +use anyhow::Result; +use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; +use serde::{Deserialize, Serialize}; -use crate::devicetree::output::dt_to_lower_id; +use crate::devicetree::{output::dt_to_lower_id, Word}; -use super::{DeviceTree, Node, Word}; +use super::{DeviceTree, Node}; /// This action is given to each node in the device tree, and it is given a chance to return /// additional code to be included in the module associated with that entry. These are all @@ -29,228 +43,256 @@ pub trait Augment { fn generate(&self, node: &Node, tree: &DeviceTree) -> TokenStream; } -/// Return all of the Augments we wish to apply. -pub fn get_augments() -> Vec> { - vec![ - Box::new(GpioAugment), - Box::new(GpioLedsAugment), - Box::new(FlashControllerAugment), - Box::new(FlashPartitionAugment), - Box::new(SinglePartitionAugment), - Box::new(LabelAugment), - ] +/// A top level augmentation. +/// +/// This top level augmentation describes how to match a given node within the device tree, and then +/// what kind of action to describe upon that. +#[derive(Debug, Serialize, Deserialize)] +pub struct Augmentation { + /// A name for this augmentation. Used for diagnostic purposes. + name: String, + /// What to match. This is an array, and all must match for a given node to be considered. + /// This does mean that if this is an empty array, it will match on every node. + rules: Vec, + /// What to do when a given node matches. + actions: Vec, } -/// Augment gpio nodes. Gpio nodes are indicated by the 'gpio-controller' property, and not a -/// particular compatible. -struct GpioAugment; - -impl Augment for GpioAugment { +impl Augment for Augmentation { fn is_compatible(&self, node: &Node) -> bool { - node.has_prop("gpio-controller") + self.rules.iter().all(|n| n.is_compatible(node)) } - fn generate(&self, node: &Node, _tree: &DeviceTree) -> TokenStream { - let ord = node.ord; - let device = format_ident!("__device_dts_ord_{}", ord); + fn generate(&self, node: &Node, tree: &DeviceTree) -> TokenStream { + let name = format_ident!("{}", dt_to_lower_id(&self.name)); + let actions = self.actions.iter().map(|a| a.generate(&name, node, tree)); + quote! { - pub unsafe fn get_instance_raw() -> *const crate::raw::device { - &crate::raw::#device - } - pub fn get_instance() -> crate::sys::gpio::Gpio { - let device = unsafe { get_instance_raw() }; - crate::sys::gpio::Gpio { - device, - } - } + #(#actions)* } } } -/// Augment the individual led nodes. This provides a safe wrapper that can be used to directly use -/// these nodes. -struct GpioLedsAugment; +/// A matching rule. +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case", content = "value")] +pub enum Rule { + /// A set of "or" matches. + Or(Vec), + /// A set of "and" matches. Not needed at the top level, as the top level vec is an implicit + /// and. + And(Vec), + /// Matches if the node has the given property. + HasProp(String), + /// Matches if this node has one of the listed compatible strings. The the 'level' property + /// indicates how many levels up in the tree. Zero means match the current node, 1 means the + /// parent node, and so on. + Compatible { + names: Vec, + level: usize, + }, + /// Matches at the root of tree. + Root, +} -impl Augment for GpioLedsAugment { - // GPIO Leds are nodes whose parent is compatible with "gpio-leds". +impl Rule { fn is_compatible(&self, node: &Node) -> bool { - if let Some(parent) = node.parent.borrow().as_ref() { - parent.is_compatible("gpio-leds") - } else { - false + match self { + Rule::Or(rules) => rules.iter().any(|n| n.is_compatible(node)), + Rule::And(rules) => rules.iter().all(|n| n.is_compatible(node)), + Rule::HasProp(name) => node.has_prop(name), + Rule::Compatible { names, level } => parent_compatible(node, names, *level), + Rule::Root => node.parent.borrow().is_none(), } } +} - fn generate(&self, node: &Node, _tree: &DeviceTree) -> TokenStream { - // TODO: Generalize this. - let words = node.get_words("gpios").unwrap(); - let target = if let Word::Phandle(gpio) = &words[0] { - gpio.node_ref() +/// Determine if a node is compatible, looking `levels` levels up in the tree, where 0 means this +/// node. +fn parent_compatible(node: &Node, names: &[String], level: usize) -> bool { + // Writing this recursively simplifies the borrowing a lot. Otherwise, we'd have to clone the + // RCs. Our choice is the extra clone, or keeping the borrowed values on the stack. This code + // runs on the host, so the stack is easier. + if level == 0 { + names.iter().any(|n| node.is_compatible(n)) + } else { + if let Some(parent) = node.parent.borrow().as_ref() { + parent_compatible(parent, names, level - 1) } else { - panic!("gpios property in node is empty"); - }; - - // At this point, we support gpio-cells of 2. - if target.get_number("#gpio-cells") != Some(2) { - panic!("gpios only support with #gpio-cells of 2"); - } - - if words.len() != 3 { - panic!("gpio-leds, gpios property expected to have only one entry"); + false } + } +} - let pin = words[1].get_number().unwrap(); - let flags = words[2].get_number().unwrap(); +/// An action to perform +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case", content = "value")] +pub enum Action { + /// Generate an "instance" with a specific device name. + Instance { + /// Where to get the raw device information. + raw: RawInfo, + /// The name of the full path (within the zephyr-sys crate) for the wrapper node for this + /// device. + device: String, + }, + /// Generate all of the labels as its own node. + Labels, +} - let gpio_route = target.route_to_rust(); - quote! { - pub fn get_instance() -> crate::sys::gpio::GpioPin { - unsafe { - let port = #gpio_route :: get_instance_raw(); - crate::sys::gpio::GpioPin { - pin: crate::raw::gpio_dt_spec { - port, - pin: #pin as crate::raw::gpio_pin_t, - dt_flags: #flags as crate::raw::gpio_dt_flags_t, +impl Action { + fn generate(&self, _name: &Ident, node: &Node, tree: &DeviceTree) -> TokenStream { + match self { + Action::Instance { raw, device } => { + raw.generate(node, device) + } + Action::Labels => { + let nodes = tree.labels.iter().map(|(k, v)| { + let name = dt_to_lower_id(k); + let path = v.route_to_rust(); + quote! { + pub mod #name { + pub use #path::*; } } + }); + + quote! { + // This does assume the devicetree doesn't have a "labels" node at the root. + pub mod labels { + /// All of the labeles in the device tree. The device tree compiler + /// enforces that these are unique, allowing references such as + /// `zephyr::devicetree::labels::labelname::get_instance()`. + #(#nodes)* + } } } } } } -/// Augment flash controllers. -/// -/// Flash controllers are a little weird, because there is no unified compatible that says they -/// implement the flash interface. In fact, they seem to generally not be accessed through the -/// device tree at all. -struct FlashControllerAugment; +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case", content = "value")] +pub enum RawInfo { + /// Get the raw device directly from this node. + Myself, + /// Get the reference from a parent of this node, at a given level. + Parent { + /// How many levels to look up. 0 would refer to this node (but would also be an error). + level: usize, + args: Vec, + }, + /// Get the raw device from a phandle property. Additional parameters in the phandle will be + /// passed as additional arguments to the `new` constructor on the wrapper type. + Phandle(String), +} -impl Augment for FlashControllerAugment { - // For now, just look for specific ones we know. - fn is_compatible(&self, node: &Node) -> bool { - node.is_compatible("nordic,nrf52-flash-controller") || - node.is_compatible("raspberrypi,pico-flash-controller") - } +impl RawInfo { + fn generate(&self, node: &Node, device: &str) -> TokenStream { + let device_id = str_to_path(device); + match self { + RawInfo::Myself => { + let ord = node.ord; + let rawdev = format_ident!("__device_dts_ord_{}", ord); + quote! { + /// Get the raw `const struct device *` of the device tree generated node. + pub unsafe fn get_instance_raw() -> *const crate::raw::device { + &crate::raw::#rawdev + } - fn generate(&self, node: &Node, _tree: &DeviceTree) -> TokenStream { - // For now, just return the device node. - let ord = node.ord; - let device = format_ident!("__device_dts_ord_{}", ord); - quote! { - pub unsafe fn get_instance_raw() -> *const crate::raw::device { - &crate::raw::#device + static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + pub fn get_instance() -> Option<#device_id> { + unsafe { + let device = get_instance_raw(); + #device_id::new(&UNIQUE, device) + } + } + } } - - pub fn get_instance() -> crate::sys::flash::FlashController { - let device = unsafe { get_instance_raw() }; - crate::sys::flash::FlashController { - device, + RawInfo::Phandle(pname) => { + let words = node.get_words(pname).unwrap(); + // We assume that elt 0 is the phandle, and that the rest are numbers. + let target = if let Word::Phandle(handle) = &words[0] { + handle.node_ref() + } else { + panic!("phandle property {:?} in node is empty", pname); + }; + + // TODO: We would try to correlate with parent node's notion of number of cells, and + // try to handle cases where there is more than one reference. It is unclear when + // this will be needed. + let args: Vec = words[1..].iter().map(|n| n.get_number().unwrap()).collect(); + + let target_route = target.route_to_rust(); + + quote! { + static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + pub fn get_instance() -> Option<#device_id> { + unsafe { + let device = #target_route :: get_instance_raw(); + #device_id::new(&UNIQUE, device, #(#args),*) + } + } } } - } - } -} + RawInfo::Parent { level, args } => { + let get_args = args.iter().map(|arg| arg.args(node)); -/// Augment flash partitions. -/// -/// This provides access to the individual partitions via named modules under the flash device. -struct FlashPartitionAugment; - -impl Augment for FlashPartitionAugment { - fn is_compatible(&self, node: &Node) -> bool { - node.compatible_path(&[Some("fixed-partitions"), Some("soc-nv-flash")]) - } - - fn generate(&self, node: &Node, _tree: &DeviceTree) -> TokenStream { - let children = node.children.iter().map(|child| { - let label = child.get_single_string("label").unwrap(); - let label = dt_to_lower_id(label); - let reg = child.get_numbers("reg").unwrap(); - if reg.len() != 2 { - panic!("flash partition entry must have 2 reg values"); - } - let offset = reg[0]; - let size = reg[1]; + assert!(*level > 0); + let mut path = quote! {super}; + for _ in 1..*level { + path = quote! { #path :: super }; + } - quote! { - pub mod #label { - pub fn get_instance() -> crate::sys::flash::FlashPartition { - let controller = super::super::super::get_instance(); - crate::sys::flash::FlashPartition { - controller, - offset: #offset, - size: #size, + quote! { + static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + pub fn get_instance() -> Option<#device_id> { + unsafe { + let device = #path :: get_instance_raw(); + #device_id::new(&UNIQUE, device, #(#get_args),*) } } } } - }); - quote! { - #(#children)* } } } -/// Augment the partitions themselves rather than the whole partition table. -struct SinglePartitionAugment; - -impl Augment for SinglePartitionAugment { - fn is_compatible(&self, node: &Node) -> bool { - node.compatible_path(&[None, Some("fixed-partitions"), Some("soc-nv-flash")]) - } - - fn generate(&self, node: &Node, _tree: &DeviceTree) -> TokenStream { - let reg = node.get_numbers("reg").unwrap(); - if reg.len() != 2 { - panic!("flash partition entry must have 2 reg values"); - } - let offset = reg[0]; - let size = reg[1]; +/// Information about where to get constructor properties for arguments. +/// +/// At this point, we assume these all come from the current node. +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case", content = "value")] +pub enum ArgInfo { + /// The arguments come from a 'reg' property. + Reg, +} - quote! { - pub fn get_instance() -> crate::sys::flash::FlashPartition { - let controller = super::super::super::get_instance(); - crate::sys::flash::FlashPartition { - controller, - offset: #offset, - size: #size, +impl ArgInfo { + /// Extra properties for the argument, assembling the arguents that should be passed in. + fn args(&self, node: &Node) -> TokenStream { + match self { + ArgInfo::Reg => { + let reg = node.get_numbers("reg").unwrap(); + quote! { + #(#reg),* } } } } } -/// Add in all of the labels. -struct LabelAugment; - -impl Augment for LabelAugment { - fn is_compatible(&self, node: &Node) -> bool { - // Insert the labels at the root node. - println!("Augment check: {}", node.path); - if let Some(parent) = node.parent.borrow().as_ref() { - println!(" parent: {}", parent.path); - } - node.parent.borrow().is_none() +/// Split a path given by a user into a token stream. +fn str_to_path(path: &str) -> TokenStream { + let names = path.split("::").map(|n| format_ident!("{}", n)); + quote! { + #(#names)::* } +} - fn generate(&self, _node: &Node, tree: &DeviceTree) -> TokenStream { - let nodes = tree.labels.iter().map(|(k, v)| { - let name = dt_to_lower_id(k); - let path = v.route_to_rust(); - quote! { - pub mod #name { - pub use #path::*; - } - } - }); - - quote! { - // This does assume the devicetree doesn't have a "labels" node at the root. - pub mod labels { - #(#nodes)* - } - } - } +/// Load a file of the given name. +pub fn load_augments>(name: P) -> Result> { + let fd = File::open(name)?; + let augs: Vec = serde_yaml_ng::from_reader(fd)?; + Ok(augs) } diff --git a/zephyr-build/src/devicetree/config.rs b/zephyr-build/src/devicetree/config.rs deleted file mode 100644 index 09488bef..00000000 --- a/zephyr-build/src/devicetree/config.rs +++ /dev/null @@ -1,313 +0,0 @@ -//! Config handling. -//! -//! There are various aspects of the device tree in Zephyr whose semantics are only indirectly -//! defined by the behavior of C code. Rather than trying to decipher this at build time, we will -//! use one or more yaml files that describe aspects of the device tree. -//! -//! This module is responsible for the format of this config file and the parsed contents will be -//! used to generate the [`Augment`] objects that will do the actual augmentation of the generated -//! device tree. -//! -//! Each augment is described by a top-level yaml element in an array. - -use std::{fs::File, path::Path}; - -use anyhow::Result; -use proc_macro2::{Ident, TokenStream}; -use quote::{format_ident, quote}; -use serde::{Deserialize, Serialize}; - -use crate::devicetree::{output::dt_to_lower_id, Word}; - -use super::{augment::Augment, DeviceTree, Node}; - -/// A top level augmentation. -/// -/// This top level augmentation describes how to match a given node within the device tree, and then -/// what kind of action to describe upon that. -#[derive(Debug, Serialize, Deserialize)] -pub struct Augmentation { - /// A name for this augmentation. Used for diagnostic purposes. - name: String, - /// What to match. This is an array, and all must match for a given node to be considered. - /// This does mean that if this is an empty array, it will match on every node. - rules: Vec, - /// What to do when a given node matches. - actions: Vec, -} - -impl Augment for Augmentation { - fn is_compatible(&self, node: &Node) -> bool { - self.rules.iter().all(|n| n.is_compatible(node)) - } - - fn generate(&self, node: &Node, tree: &DeviceTree) -> TokenStream { - let name = format_ident!("{}", dt_to_lower_id(&self.name)); - let actions = self.actions.iter().map(|a| a.generate(&name, node, tree)); - - quote! { - #(#actions)* - } - } -} - -/// A matching rule. -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case", content = "value")] -pub enum Rule { - /// A set of "or" matches. - Or(Vec), - /// A set of "and" matches. Not needed at the top level, as the top level vec is an implicit - /// and. - And(Vec), - /// Matches if the node has the given property. - HasProp(String), - /// Matches if this node has one of the listed compatible strings. The the 'level' property - /// indicates how many levels up in the tree. Zero means match the current node, 1 means the - /// parent node, and so on. - Compatible { - names: Vec, - level: usize, - }, - /// Matches at the root of tree. - Root, -} - -impl Rule { - fn is_compatible(&self, node: &Node) -> bool { - match self { - Rule::Or(rules) => rules.iter().any(|n| n.is_compatible(node)), - Rule::And(rules) => rules.iter().all(|n| n.is_compatible(node)), - Rule::HasProp(name) => node.has_prop(name), - Rule::Compatible { names, level } => parent_compatible(node, names, *level), - Rule::Root => node.parent.borrow().is_none(), - } - } -} - -/// Determine if a node is compatible, looking `levels` levels up in the tree, where 0 means this -/// node. -fn parent_compatible(node: &Node, names: &[String], level: usize) -> bool { - // Writing this recursively simplifies the borrowing a lot. Otherwise, we'd have to clone the - // RCs. Our choice is the extra clone, or keeping the borrowed values on the stack. This code - // runs on the host, so the stack is easier. - if level == 0 { - names.iter().any(|n| node.is_compatible(n)) - } else { - if let Some(parent) = node.parent.borrow().as_ref() { - parent_compatible(parent, names, level - 1) - } else { - false - } - } -} - -/// An action to perform -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case", content = "value")] -pub enum Action { - /// Generate an "instance" with a specific device name. - Instance { - /// Where to get the raw device information. - raw: RawInfo, - /// The name of the full path (within the zephyr-sys crate) for the wrapper node for this - /// device. - device: String, - }, - /// Generate all of the labels as its own node. - Labels, -} - -impl Action { - fn generate(&self, _name: &Ident, node: &Node, tree: &DeviceTree) -> TokenStream { - match self { - Action::Instance { raw, device } => { - raw.generate(node, device) - } - Action::Labels => { - let nodes = tree.labels.iter().map(|(k, v)| { - let name = dt_to_lower_id(k); - let path = v.route_to_rust(); - quote! { - pub mod #name { - pub use #path::*; - } - } - }); - - quote! { - // This does assume the devicetree doesn't have a "labels" node at the root. - pub mod labels { - /// All of the labeles in the device tree. The device tree compiler - /// enforces that these are unique, allowing references such as - /// `zephyr::devicetree::labels::labelname::get_instance()`. - #(#nodes)* - } - } - } - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case", content = "value")] -pub enum RawInfo { - /// Get the raw device directly from this node. - Myself, - /// Get the reference from a parent of this node, at a given level. - Parent { - /// How many levels to look up. 0 would refer to this node (but would also be an error). - level: usize, - args: Vec, - }, - /// Get the raw device from a phandle property. Additional parameters in the phandle will be - /// passed as additional arguments to the `new` constructor on the wrapper type. - Phandle(String), -} - -impl RawInfo { - fn generate(&self, node: &Node, device: &str) -> TokenStream { - let device_id = str_to_path(device); - match self { - RawInfo::Myself => { - let ord = node.ord; - let rawdev = format_ident!("__device_dts_ord_{}", ord); - quote! { - /// Get the raw `const struct device *` of the device tree generated node. - pub unsafe fn get_instance_raw() -> *const crate::raw::device { - &crate::raw::#rawdev - } - - static UNIQUE: crate::device::Unique = crate::device::Unique::new(); - pub fn get_instance() -> Option<#device_id> { - unsafe { - let device = get_instance_raw(); - #device_id::new(&UNIQUE, device) - } - } - } - } - RawInfo::Phandle(pname) => { - let words = node.get_words(pname).unwrap(); - // We assume that elt 0 is the phandle, and that the rest are numbers. - let target = if let Word::Phandle(handle) = &words[0] { - handle.node_ref() - } else { - panic!("phandle property {:?} in node is empty", pname); - }; - - // TODO: We would try to correlate with parent node's notion of number of cells, and - // try to handle cases where there is more than one reference. It is unclear when - // this will be needed. - let args: Vec = words[1..].iter().map(|n| n.get_number().unwrap()).collect(); - - let target_route = target.route_to_rust(); - - quote! { - static UNIQUE: crate::device::Unique = crate::device::Unique::new(); - pub fn get_instance() -> Option<#device_id> { - unsafe { - let device = #target_route :: get_instance_raw(); - #device_id::new(&UNIQUE, device, #(#args),*) - } - } - } - } - RawInfo::Parent { level, args } => { - let get_args = args.iter().map(|arg| arg.args(node)); - - assert!(*level > 0); - let mut path = quote! {super}; - for _ in 1..*level { - path = quote! { #path :: super }; - } - - quote! { - static UNIQUE: crate::device::Unique = crate::device::Unique::new(); - pub fn get_instance() -> Option<#device_id> { - unsafe { - let device = #path :: get_instance_raw(); - #device_id::new(&UNIQUE, device, #(#get_args),*) - } - } - } - } - } - } -} - -/// Information about where to get constructor properties for arguments. -/// -/// At this point, we assume these all come from the current node. -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case", content = "value")] -pub enum ArgInfo { - /// The arguments come from a 'reg' property. - Reg, -} - -impl ArgInfo { - /// Extra properties for the argument, assembling the arguents that should be passed in. - fn args(&self, node: &Node) -> TokenStream { - match self { - ArgInfo::Reg => { - let reg = node.get_numbers("reg").unwrap(); - quote! { - #(#reg),* - } - } - } - } -} - -/// Split a path given by a user into a token stream. -fn str_to_path(path: &str) -> TokenStream { - let names = path.split("::").map(|n| format_ident!("{}", n)); - quote! { - #(#names)::* - } -} - -/// Load a file of the given name. -pub fn load>(name: P) -> Result> { - let fd = File::open(name)?; - let augs: Vec = serde_yaml_ng::from_reader(fd)?; - Ok(augs) -} - -/// Output a sample yaml file, so we can understand the format. -pub fn sample() { - use std::fs::File; - - let data = vec![ - Augmentation { - name: "gpio-controller".to_string(), - rules: vec![ - Rule::HasProp("gpio-controller".to_string()), - ], - actions: vec![ - Action::Instance { - raw: RawInfo::Myself, - device: "crate::sys::gpio::Gpio".to_string(), - }, - ], - }, - Augmentation { - name: "gpio-leds".to_string(), - rules: vec![ - Rule::Compatible { - names: vec!["gpio-leds".to_string()], - level: 1, - }, - ], - actions: vec![ - Action::Instance { - raw: RawInfo::Phandle("gpios".to_string()), - device: "crate::sys::gpio::GpioPin".to_string(), - } - ], - }, - ]; - let fd = File::create("dt-sample.yaml").unwrap(); - serde_yaml_ng::to_writer(fd, &data).unwrap(); -} diff --git a/zephyr-build/src/lib.rs b/zephyr-build/src/lib.rs index e9be1320..7763449d 100644 --- a/zephyr-build/src/lib.rs +++ b/zephyr-build/src/lib.rs @@ -24,9 +24,6 @@ use devicetree::{Augment, DeviceTree}; mod devicetree; -/// For debugging. -pub use devicetree::config::sample; - /// Export boolean Kconfig entries. This must happen in any crate that wishes to access the /// configuration settings. pub fn export_bool_kconfig() { @@ -102,7 +99,7 @@ pub fn build_dts() { let mut augs = Vec::new(); for aug in &augments { // println!("Load augment: {:?}", aug); - let mut aug = devicetree::config::load(aug).expect("Loading augment file"); + let mut aug = devicetree::load_augments(aug).expect("Loading augment file"); augs.append(&mut aug); } // For now, just print it out. From ba60ed22d5ca172c7d9edfa75ae5ed69d6e87b7a Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 13:42:05 -0600 Subject: [PATCH 12/30] zephyr-build: Include DT node presence configs Generate a full list of nodes that are present in the given device tree, and provide a tool that build.rs in the app can use to make these active. This will allow conditionals like: #[cfg(dt = "aliases::led")] to be used, which will make it possible to handle nodes being present or not in the DTS. See a subsequent patch to the blinky sample for an example of usage. Signed-off-by: David Brown --- zephyr-build/src/devicetree/output.rs | 51 +++++++++++++++++++++++++++ zephyr-build/src/lib.rs | 24 ++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/zephyr-build/src/devicetree/output.rs b/zephyr-build/src/devicetree/output.rs index 6fae6ec3..47d6556f 100644 --- a/zephyr-build/src/devicetree/output.rs +++ b/zephyr-build/src/devicetree/output.rs @@ -8,6 +8,9 @@ // Support for particular devices should also be added to the device tree here, so that the nodes // make sense for that device, and that there are general accessors that return wrapped node types. +use std::io::Write; + +use anyhow::Result; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; @@ -23,6 +26,20 @@ impl DeviceTree { self.node_walk(self.root.as_ref(), None, &augments) } + /// Write to the given file a list of the path names of all of the nodes present in this + /// devicetree. + pub fn output_node_paths(&self, write: &mut W) -> Result<()> { + self.root.as_ref().output_path_walk(write, None)?; + + // Also, output all of the labels. Technically, this depends on the labels augment being + // present. + writeln!(write, "labels")?; + for label in self.labels.keys() { + writeln!(write, "labels::{}", fix_id(label))?; + } + Ok(()) + } + fn node_walk(&self, node: &Node, name: Option<&str>, augments: &[Box]) -> TokenStream { let children = node.children.iter().map(|child| { self.node_walk(child.as_ref(), Some(&child.name), augments) @@ -118,6 +135,29 @@ impl Node { crate :: devicetree #(:: #route)* } } + + /// Walk this tree of nodes, writing out the path names of the nodes that are present. The name + /// of None, indicates the root node. + fn output_path_walk(&self, write: &mut W, name: Option<&str>) -> Result<()> { + for child in &self.children { + let fixed_name = fix_id(&child.name); + let child_name = if let Some(name) = name { + format!("{}::{}", name, fixed_name) + } else { + fixed_name + }; + + writeln!(write, "{}", child_name)?; + + for prop in &child.properties { + prop.output_path(write, &child_name)?; + } + + child.output_path_walk(write, Some(&child_name))?; + } + + Ok(()) + } } impl Property { @@ -129,6 +169,17 @@ impl Property { None } } + + // If this property is a single top-level phandle, output that a that path is valid. It isn't a + // real node, but acts like one. + fn output_path(&self, write: &mut W, name: &str) -> Result<()> { + if let Some(value) = self.get_single_value() { + if let Value::Phandle(_) = value { + writeln!(write, "{}::{}", name, self.name)?; + } + } + Ok(()) + } } fn general_property(prop: &Property) -> TokenStream { diff --git a/zephyr-build/src/lib.rs b/zephyr-build/src/lib.rs index 7763449d..8f016b5c 100644 --- a/zephyr-build/src/lib.rs +++ b/zephyr-build/src/lib.rs @@ -86,6 +86,7 @@ pub fn build_dts() { let outdir = env::var("OUT_DIR").expect("OUT_DIR must be set"); let gen_include = env::var("BINARY_DIR_INCLUDE_GENERATED") .expect("BINARY_DIR_INCLUDE_GENERATED"); + let builddir = env::var("BUILD_DIR").expect("BUILD_DIR"); let augments = env::var("DT_AUGMENTS").expect("DT_AUGMENTS must be set"); let augments: Vec = augments.split_whitespace().map(String::from).collect(); @@ -111,7 +112,6 @@ pub fn build_dts() { let generated = format!("{}/devicetree_generated.h", gen_include); let dt = DeviceTree::new(&zephyr_dts, generated); - let _ = dt; let out_path = Path::new(&outdir).join("devicetree.rs"); let mut out = File::create(&out_path).expect("Unable to create devicetree.rs"); @@ -122,6 +122,28 @@ pub fn build_dts() { } else { writeln!(out, "{}", tokens).unwrap(); }; + + // Output all of the node names in the discovered tree. + let all_nodes_path = Path::new(&builddir) + .join("rust") + .join("all-dt-nodes.txt"); + let mut out = File::create(&all_nodes_path).expect("Unable to create all-dt-nodex.txt"); + dt.output_node_paths(&mut out).expect("Unable to write to all-dt-nodes.txt"); +} + +/// Generate cfg directives for each of the nodes in the generated device tree. +/// +/// This assumes that build_dts was already run by the `zephyr` crate, which should happen if this +/// is called from a user application. +pub fn dt_cfgs() { + let builddir = env::var("BUILD_DIR").expect("BUILD_DIR"); + let path = Path::new(&builddir) + .join("rust") + .join("all-dt-nodes.txt"); + for line in BufReader::new(File::open(&path).expect("Unable to open all-dt-nodes")).lines() { + let line = line.expect("Error reading line from all-dt-nodes"); + println!("cargo:rustc-cfg=dt=\"{}\"", line); + } } /// Determine if `rustfmt` is in the path, and can be excecuted. Returns false on any kind of error. From f6b5e89e56ce9a6847fcad41c635152ac64cab23 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 13:43:49 -0600 Subject: [PATCH 13/30] samples: blinky: Domonstrate conditional DT compilation Show how an application can be conditional based on the presence of a node in the devicetree. Signed-off-by: David Brown --- samples/blinky/Cargo.toml | 3 +++ samples/blinky/build.rs | 3 +++ samples/blinky/src/lib.rs | 19 +++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 samples/blinky/build.rs diff --git a/samples/blinky/Cargo.toml b/samples/blinky/Cargo.toml index cc62c8b9..e812ba1a 100644 --- a/samples/blinky/Cargo.toml +++ b/samples/blinky/Cargo.toml @@ -15,3 +15,6 @@ crate-type = ["staticlib"] [dependencies] zephyr = "3.7.0" log = "0.4.22" + +[build-dependencies] +zephyr-build = "3.7.0" diff --git a/samples/blinky/build.rs b/samples/blinky/build.rs new file mode 100644 index 00000000..eea8aade --- /dev/null +++ b/samples/blinky/build.rs @@ -0,0 +1,3 @@ +fn main() { + zephyr_build::dt_cfgs(); +} diff --git a/samples/blinky/src/lib.rs b/samples/blinky/src/lib.rs index 50ce8de5..f94cf6b0 100644 --- a/samples/blinky/src/lib.rs +++ b/samples/blinky/src/lib.rs @@ -3,6 +3,12 @@ #![no_std] +// Sigh. The check config system requires that the compiler be told what possible config values +// there might be. This is completely impossible with both Kconfig and the DT configs, since the +// whole point is that we likely need to check for configs that aren't otherwise present in the +// build. So, this is just always necessary. +#![allow(unexpected_cfgs)] + use log::warn; use core::ffi::c_void; @@ -37,6 +43,12 @@ extern "C" fn rust_main() { // fn blink() { unsafe extern "C" fn blink(_p1: *mut c_void, _p2: *mut c_void, _p3: *mut c_void) { + // Just call a "safe" rust function. + do_blink(); +} + +#[cfg(dt = "aliases::led0")] +fn do_blink() { warn!("Inside of blinky"); let mut led0 = zephyr::devicetree::aliases::led0::get_instance().unwrap(); @@ -55,3 +67,10 @@ unsafe extern "C" fn blink(_p1: *mut c_void, _p2: *mut c_void, _p3: *mut c_void) sleep(duration); } } + +#[cfg(not(dt = "aliases::led0"))] +fn do_blink() { + warn!("No leds configured"); + loop { + } +} From b1fe3ea249b5e19e4cc138efe5950824901312cd Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 14:08:06 -0600 Subject: [PATCH 14/30] zephyr: device: Split gpio and flash to own files Move this code out of the device.rs file, and into separate files for each module. Signed-off-by: David Brown --- zephyr/src/device.rs | 186 +------------------------------------ zephyr/src/device/flash.rs | 57 ++++++++++++ zephyr/src/device/gpio.rs | 120 ++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 183 deletions(-) create mode 100644 zephyr/src/device/flash.rs create mode 100644 zephyr/src/device/gpio.rs diff --git a/zephyr/src/device.rs b/zephyr/src/device.rs index dca7d383..08bf91a9 100644 --- a/zephyr/src/device.rs +++ b/zephyr/src/device.rs @@ -7,6 +7,9 @@ use crate::sync::atomic::{AtomicUsize, Ordering}; +pub mod gpio; +pub mod flash; + // Allow dead code, because it isn't required for a given build to have any devices. /// Device uniqueness. /// @@ -35,186 +38,3 @@ impl Unique { self.0.fetch_add(1, Ordering::AcqRel) == 0 } } - -pub mod gpio { - //! Most devices in Zephyr operate on a `struct device`. This provides untyped access to - //! devices. We want to have stronger typing in the Zephyr interfaces, so most of these types - //! will be wrapped in another structure. This wraps a Gpio device, and provides methods to - //! most of the operations on gpios. - //! - //! Safey: In general, even just using gpio pins is unsafe in Zephyr. The gpio drivers are used - //! pervasively throughout Zephyr device drivers. As such, most of the calls in this module are - //! unsafe. - - use crate::raw; - use super::Unique; - - /// Global instance to help make gpio in Rust slightly safer. - /// - /// To help with safety, the rust types use a global instance of a gpio-token. Methods will - /// take a mutable reference to this, which will require either a single thread in the - /// application code, or something like a mutex or critical section to manage. The operation - /// methods are still unsafe, because we have no control over what happens with the gpio - /// operations outside of Rust code, but this will help make the Rust usage at least better. - pub struct GpioToken(()); - - static GPIO_TOKEN: Unique = Unique::new(); - - impl GpioToken { - /// Retrieves the gpio token. This is unsafe because lots of code in zephyr operates on the - /// gpio drivers. - pub unsafe fn get_instance() -> Option { - if !GPIO_TOKEN.once() { - return None; - } - Some(GpioToken(())) - } - } - - /// A single instance of a zephyr device to manage a gpio controller. A gpio controller - /// represents a set of gpio pins, that are generally operated on by the same hardware block. - pub struct Gpio { - /// The underlying device itself. - #[allow(dead_code)] - pub(crate) device: *const raw::device, - } - - impl Gpio { - /// Constructor, used by the devicetree generated code. - /// - /// TODO: Guarantee single instancing. - pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { - if !unique.once() { - return None; - } - Some(Gpio { device }) - } - - /// Verify that the device is ready for use. At a minimum, this means the device has been - /// successfully initialized. - pub fn is_ready(&self) -> bool { - unsafe { - raw::device_is_ready(self.device) - } - } - } - - /// A GpioPin represents a single pin on a gpio device. - /// - /// This is a lightweight wrapper around the Zephyr `gpio_dt_spec` structure. Note that - /// multiple pins may share a gpio controller, and as such, all methods on this are both unsafe, - /// and require a mutable reference to the [`GpioToken`]. - #[allow(dead_code)] - pub struct GpioPin { - pub(crate) pin: raw::gpio_dt_spec, - } - - impl GpioPin { - /// Constructor, used by the devicetree generated code. - /// - /// TODO: Guarantee single instancing. - pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device, pin: u32, dt_flags: u32) -> Option { - if !unique.once() { - return None; - } - Some(GpioPin { - pin: raw::gpio_dt_spec { - port: device, - pin: pin as raw::gpio_pin_t, - dt_flags: dt_flags as raw::gpio_dt_flags_t, - } - }) - } - - /// Verify that the device is ready for use. At a minimum, this means the device has been - /// successfully initialized. - pub fn is_ready(&self) -> bool { - self.get_gpio().is_ready() - } - - /// Get the underlying Gpio device. - pub fn get_gpio(&self) -> Gpio { - Gpio { - device: self.pin.port, - } - } - - /// Configure a single pin. - pub unsafe fn configure(&mut self, _token: &mut GpioToken, extra_flags: raw::gpio_flags_t) { - // TODO: Error? - unsafe { - raw::gpio_pin_configure(self.pin.port, - self.pin.pin, - self.pin.dt_flags as raw::gpio_flags_t | extra_flags); - } - } - - /// Toggle pin level. - pub unsafe fn toggle_pin(&mut self, _token: &mut GpioToken) { - // TODO: Error? - unsafe { - raw::gpio_pin_toggle_dt(&self.pin); - } - } - } -} - -pub mod flash { - //! Device wrappers for flash controllers, and flash partitions. - - use crate::raw; - use super::Unique; - - /// A flash controller - /// - /// This is a wrapper around the `struct device` in Zephyr that represents a flash controller. - /// Using the flash controller allows flash operations on the entire device. See - /// [`FlashPartition`] for a wrapper that limits the operation to a partition as defined in the - /// DT. - #[allow(dead_code)] - pub struct FlashController { - pub(crate) device: *const raw::device, - } - - impl FlashController { - /// Constructor, intended to be called by devicetree generated code. - pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { - if !unique.once() { - return None; - } - - Some(FlashController { device }) - } - } - - /// A wrapper for flash partitions. There is no Zephyr struct that corresponds with this - /// information, which is typically used in a more direct underlying manner. - #[allow(dead_code)] - pub struct FlashPartition { - /// The underlying controller. - #[allow(dead_code)] - pub(crate) controller: FlashController, - #[allow(dead_code)] - pub(crate) offset: u32, - #[allow(dead_code)] - pub(crate) size: u32, - } - - impl FlashPartition { - /// Constructor, intended to be called by devicetree generated code. - pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device, offset: u32, size: u32) -> Option { - if !unique.once() { - return None; - } - - // The `get_instance` on the flash controller would try to guarantee a unique instance, - // but in this case, we need one for each device, so just construct it here. - // TODO: This is not actually safe. - let controller = FlashController { device }; - Some(FlashPartition { controller, offset, size }) - } - } - - // Note that currently, the flash partition shares the controller, so the underlying operations - // are not actually safe. Need to rethink how to manage this. -} diff --git a/zephyr/src/device/flash.rs b/zephyr/src/device/flash.rs new file mode 100644 index 00000000..0fe0d214 --- /dev/null +++ b/zephyr/src/device/flash.rs @@ -0,0 +1,57 @@ +//! Device wrappers for flash controllers, and flash partitions. + +use crate::raw; +use super::Unique; + +/// A flash controller +/// +/// This is a wrapper around the `struct device` in Zephyr that represents a flash controller. +/// Using the flash controller allows flash operations on the entire device. See +/// [`FlashPartition`] for a wrapper that limits the operation to a partition as defined in the +/// DT. +#[allow(dead_code)] +pub struct FlashController { + pub(crate) device: *const raw::device, +} + +impl FlashController { + /// Constructor, intended to be called by devicetree generated code. + pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { + if !unique.once() { + return None; + } + + Some(FlashController { device }) + } +} + +/// A wrapper for flash partitions. There is no Zephyr struct that corresponds with this +/// information, which is typically used in a more direct underlying manner. +#[allow(dead_code)] +pub struct FlashPartition { + /// The underlying controller. + #[allow(dead_code)] + pub(crate) controller: FlashController, + #[allow(dead_code)] + pub(crate) offset: u32, + #[allow(dead_code)] + pub(crate) size: u32, +} + +impl FlashPartition { + /// Constructor, intended to be called by devicetree generated code. + pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device, offset: u32, size: u32) -> Option { + if !unique.once() { + return None; + } + + // The `get_instance` on the flash controller would try to guarantee a unique instance, + // but in this case, we need one for each device, so just construct it here. + // TODO: This is not actually safe. + let controller = FlashController { device }; + Some(FlashPartition { controller, offset, size }) + } +} + +// Note that currently, the flash partition shares the controller, so the underlying operations +// are not actually safe. Need to rethink how to manage this. diff --git a/zephyr/src/device/gpio.rs b/zephyr/src/device/gpio.rs new file mode 100644 index 00000000..be0ee2e9 --- /dev/null +++ b/zephyr/src/device/gpio.rs @@ -0,0 +1,120 @@ +//! Most devices in Zephyr operate on a `struct device`. This provides untyped access to +//! devices. We want to have stronger typing in the Zephyr interfaces, so most of these types +//! will be wrapped in another structure. This wraps a Gpio device, and provides methods to +//! most of the operations on gpios. +//! +//! Safey: In general, even just using gpio pins is unsafe in Zephyr. The gpio drivers are used +//! pervasively throughout Zephyr device drivers. As such, most of the calls in this module are +//! unsafe. + +use crate::raw; +use super::Unique; + +/// Global instance to help make gpio in Rust slightly safer. +/// +/// To help with safety, the rust types use a global instance of a gpio-token. Methods will +/// take a mutable reference to this, which will require either a single thread in the +/// application code, or something like a mutex or critical section to manage. The operation +/// methods are still unsafe, because we have no control over what happens with the gpio +/// operations outside of Rust code, but this will help make the Rust usage at least better. +pub struct GpioToken(()); + +static GPIO_TOKEN: Unique = Unique::new(); + +impl GpioToken { + /// Retrieves the gpio token. This is unsafe because lots of code in zephyr operates on the + /// gpio drivers. + pub unsafe fn get_instance() -> Option { + if !GPIO_TOKEN.once() { + return None; + } + Some(GpioToken(())) + } +} + +/// A single instance of a zephyr device to manage a gpio controller. A gpio controller +/// represents a set of gpio pins, that are generally operated on by the same hardware block. +pub struct Gpio { + /// The underlying device itself. + #[allow(dead_code)] + pub(crate) device: *const raw::device, +} + +impl Gpio { + /// Constructor, used by the devicetree generated code. + /// + /// TODO: Guarantee single instancing. + pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { + if !unique.once() { + return None; + } + Some(Gpio { device }) + } + + /// Verify that the device is ready for use. At a minimum, this means the device has been + /// successfully initialized. + pub fn is_ready(&self) -> bool { + unsafe { + raw::device_is_ready(self.device) + } + } +} + +/// A GpioPin represents a single pin on a gpio device. +/// +/// This is a lightweight wrapper around the Zephyr `gpio_dt_spec` structure. Note that +/// multiple pins may share a gpio controller, and as such, all methods on this are both unsafe, +/// and require a mutable reference to the [`GpioToken`]. +#[allow(dead_code)] +pub struct GpioPin { + pub(crate) pin: raw::gpio_dt_spec, +} + +impl GpioPin { + /// Constructor, used by the devicetree generated code. + /// + /// TODO: Guarantee single instancing. + pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device, pin: u32, dt_flags: u32) -> Option { + if !unique.once() { + return None; + } + Some(GpioPin { + pin: raw::gpio_dt_spec { + port: device, + pin: pin as raw::gpio_pin_t, + dt_flags: dt_flags as raw::gpio_dt_flags_t, + } + }) + } + + /// Verify that the device is ready for use. At a minimum, this means the device has been + /// successfully initialized. + pub fn is_ready(&self) -> bool { + self.get_gpio().is_ready() + } + + /// Get the underlying Gpio device. + pub fn get_gpio(&self) -> Gpio { + Gpio { + device: self.pin.port, + } + } + + /// Configure a single pin. + pub unsafe fn configure(&mut self, _token: &mut GpioToken, extra_flags: raw::gpio_flags_t) { + // TODO: Error? + unsafe { + raw::gpio_pin_configure(self.pin.port, + self.pin.pin, + self.pin.dt_flags as raw::gpio_flags_t | extra_flags); + } + } + + /// Toggle pin level. + pub unsafe fn toggle_pin(&mut self, _token: &mut GpioToken) { + // TODO: Error? + unsafe { + raw::gpio_pin_toggle_dt(&self.pin); + } + } +} From 2dce5d5ab379d9b64a2591b49363bdbdf85d787a Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 25 Oct 2024 12:59:00 -0600 Subject: [PATCH 15/30] platforms: Remove mps2 The gpio device tree entries for the mps2 are defined with a `#gpio-cells` value of 1, despite these values not being interpreted by the driver, but by the devicetree code. I'm not sure if these actually work with any of the demos, as it is unclear what the macros would do with this. It doesn't give us a value to use for dt_flags. If someone wants to put in some effort to fix this, feel free. But I don't think the problem is on the Rust side. Signed-off-by: David Brown --- etc/platforms.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/etc/platforms.txt b/etc/platforms.txt index e459795d..42b27bc6 100644 --- a/etc/platforms.txt +++ b/etc/platforms.txt @@ -1,5 +1,3 @@ --p mps2/an385 --p mps2/an521/cpu0 -p qemu_cortex_m0 -p qemu_cortex_m3 -p qemu_riscv32 From 08ee474faee59df9081fa9799fb50fec541023e9 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 29 Oct 2024 10:57:49 -0600 Subject: [PATCH 16/30] zephyr: gpio/flash: Allow constructor to be unused Prevents a warning on boards where there are no gpios or flash controllers are defined. Signed-off-by: David Brown --- zephyr/src/device/flash.rs | 2 ++ zephyr/src/device/gpio.rs | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/zephyr/src/device/flash.rs b/zephyr/src/device/flash.rs index 0fe0d214..69bdfaa1 100644 --- a/zephyr/src/device/flash.rs +++ b/zephyr/src/device/flash.rs @@ -16,6 +16,7 @@ pub struct FlashController { impl FlashController { /// Constructor, intended to be called by devicetree generated code. + #[allow(dead_code)] pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { if !unique.once() { return None; @@ -40,6 +41,7 @@ pub struct FlashPartition { impl FlashPartition { /// Constructor, intended to be called by devicetree generated code. + #[allow(dead_code)] pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device, offset: u32, size: u32) -> Option { if !unique.once() { return None; diff --git a/zephyr/src/device/gpio.rs b/zephyr/src/device/gpio.rs index be0ee2e9..c0cace33 100644 --- a/zephyr/src/device/gpio.rs +++ b/zephyr/src/device/gpio.rs @@ -72,8 +72,7 @@ pub struct GpioPin { impl GpioPin { /// Constructor, used by the devicetree generated code. - /// - /// TODO: Guarantee single instancing. + #[allow(dead_code)] pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device, pin: u32, dt_flags: u32) -> Option { if !unique.once() { return None; From bf03633396ccad7cc91cd411ce570c2826d13dd9 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 29 Oct 2024 10:58:28 -0600 Subject: [PATCH 17/30] dt-rust: Add the nrf51 flash controller This allows a build on the nrf51, preventing an error when the partitions are detected, but the controller wasn't. Signed-off-by: David Brown --- dt-rust.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/dt-rust.yaml b/dt-rust.yaml index 4f4fb085..878913db 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -42,6 +42,7 @@ value: names: - "nordic,nrf52-flash-controller" + - "nordic,nrf51-flash-controller" - "raspberrypi,pico-flash-controller" level: 0 actions: From ec45675169dac1a31a6b7e5d69ce4f83982eaf68 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 29 Oct 2024 14:09:19 -0600 Subject: [PATCH 18/30] zephyr-sys: Bump to newer bindgen This fixes a weird issue with bindgen missing the `__device_dts_ord_nn` declarations in some circumstances. It is unclear when this was occuring, and hopefully it doesn't return at some point. Signed-off-by: David Brown --- zephyr-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zephyr-sys/Cargo.toml b/zephyr-sys/Cargo.toml index 62e2256d..005695c7 100644 --- a/zephyr-sys/Cargo.toml +++ b/zephyr-sys/Cargo.toml @@ -14,5 +14,5 @@ Zephyr low-level API bindings. # used by the core Zephyr tree, but are needed by zephyr applications. [build-dependencies] anyhow = "1.0" -bindgen = { version = "0.69.4", features = ["experimental"] } +bindgen = { version = "0.70.1", features = ["experimental"] } # zephyr-build = { version = "0.1.0", path = "../zephyr-build" } From cf0d2b9ca7bdcae4e3730b42c3f2df8010299081 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 29 Oct 2024 15:50:09 -0600 Subject: [PATCH 19/30] zephyr-build: Use parsed DT for both uses Instead of trying to hand off data through a file between the build of different crates, which was causing build failures in applications that need to use cfgs based on the presence of DT nodes, instead, just parse the DT again, and recalculate the node tree from it. This should fix build issues with the all nodes txt file missing. Signed-off-by: David Brown --- zephyr-build/src/devicetree/output.rs | 11 ++++---- zephyr-build/src/lib.rs | 38 ++++++++++----------------- 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/zephyr-build/src/devicetree/output.rs b/zephyr-build/src/devicetree/output.rs index 47d6556f..4531da31 100644 --- a/zephyr-build/src/devicetree/output.rs +++ b/zephyr-build/src/devicetree/output.rs @@ -26,16 +26,15 @@ impl DeviceTree { self.node_walk(self.root.as_ref(), None, &augments) } - /// Write to the given file a list of the path names of all of the nodes present in this - /// devicetree. + // Write, to the given writer, CFG lines so that Rust code can conditionalize based on the DT. pub fn output_node_paths(&self, write: &mut W) -> Result<()> { self.root.as_ref().output_path_walk(write, None)?; // Also, output all of the labels. Technically, this depends on the labels augment being // present. - writeln!(write, "labels")?; + writeln!(write, "cargo:rustc-cfg=dt=\"labels\"")?; for label in self.labels.keys() { - writeln!(write, "labels::{}", fix_id(label))?; + writeln!(write, "cargo:rustc-cfg=dt=\"labels::{}\"", fix_id(label))?; } Ok(()) } @@ -147,7 +146,7 @@ impl Node { fixed_name }; - writeln!(write, "{}", child_name)?; + writeln!(write, "cargo:rustc-cfg=dt=\"{}\"", child_name)?; for prop in &child.properties { prop.output_path(write, &child_name)?; @@ -175,7 +174,7 @@ impl Property { fn output_path(&self, write: &mut W, name: &str) -> Result<()> { if let Some(value) = self.get_single_value() { if let Value::Phandle(_) = value { - writeln!(write, "{}::{}", name, self.name)?; + writeln!(write, "cargo:rustc-cfg=dt=\"{}::{}\"", name, fix_id(&self.name))?; } } Ok(()) diff --git a/zephyr-build/src/lib.rs b/zephyr-build/src/lib.rs index 8f016b5c..102f4d52 100644 --- a/zephyr-build/src/lib.rs +++ b/zephyr-build/src/lib.rs @@ -81,12 +81,21 @@ pub fn build_kconfig_mod() { } /// Parse the finalized DTS file, generating the Rust devicetree file. -pub fn build_dts() { +fn import_dt() -> DeviceTree { let zephyr_dts = env::var("ZEPHYR_DTS").expect("ZEPHYR_DTS must be set"); - let outdir = env::var("OUT_DIR").expect("OUT_DIR must be set"); let gen_include = env::var("BINARY_DIR_INCLUDE_GENERATED") .expect("BINARY_DIR_INCLUDE_GENERATED"); - let builddir = env::var("BUILD_DIR").expect("BUILD_DIR"); + + let generated = format!("{}/devicetree_generated.h", gen_include); + DeviceTree::new(&zephyr_dts, generated) +} + +pub fn build_dts() { + let dt = import_dt(); + + let outdir = env::var("OUT_DIR").expect("OUT_DIR must be set"); + let out_path = Path::new(&outdir).join("devicetree.rs"); + let mut out = File::create(&out_path).expect("Unable to create devicetree.rs"); let augments = env::var("DT_AUGMENTS").expect("DT_AUGMENTS must be set"); let augments: Vec = augments.split_whitespace().map(String::from).collect(); @@ -110,25 +119,12 @@ pub fn build_dts() { .map(|aug| Box::new(aug) as Box) .collect(); - let generated = format!("{}/devicetree_generated.h", gen_include); - let dt = DeviceTree::new(&zephyr_dts, generated); - - let out_path = Path::new(&outdir).join("devicetree.rs"); - let mut out = File::create(&out_path).expect("Unable to create devicetree.rs"); - let tokens = dt.to_tokens(&augs); if has_rustfmt() { write_formatted(out, tokens); } else { writeln!(out, "{}", tokens).unwrap(); }; - - // Output all of the node names in the discovered tree. - let all_nodes_path = Path::new(&builddir) - .join("rust") - .join("all-dt-nodes.txt"); - let mut out = File::create(&all_nodes_path).expect("Unable to create all-dt-nodex.txt"); - dt.output_node_paths(&mut out).expect("Unable to write to all-dt-nodes.txt"); } /// Generate cfg directives for each of the nodes in the generated device tree. @@ -136,14 +132,8 @@ pub fn build_dts() { /// This assumes that build_dts was already run by the `zephyr` crate, which should happen if this /// is called from a user application. pub fn dt_cfgs() { - let builddir = env::var("BUILD_DIR").expect("BUILD_DIR"); - let path = Path::new(&builddir) - .join("rust") - .join("all-dt-nodes.txt"); - for line in BufReader::new(File::open(&path).expect("Unable to open all-dt-nodes")).lines() { - let line = line.expect("Error reading line from all-dt-nodes"); - println!("cargo:rustc-cfg=dt=\"{}\"", line); - } + let dt = import_dt(); + dt.output_node_paths(&mut std::io::stdout()).unwrap(); } /// Determine if `rustfmt` is in the path, and can be excecuted. Returns false on any kind of error. From 64f0eb627a05003909839d4fbded2b53d445c889 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 22 Nov 2024 10:34:22 -0700 Subject: [PATCH 20/30] samples: blink: Fix for upstream API changes Several things have become unsafe, so use those in unsafe blocks. The GPIO driver now has a token that must be passed to each action, to enforce single threadded use. Signed-off-by: David Brown --- samples/blinky/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/samples/blinky/src/lib.rs b/samples/blinky/src/lib.rs index f94cf6b0..84d7bac6 100644 --- a/samples/blinky/src/lib.rs +++ b/samples/blinky/src/lib.rs @@ -18,7 +18,7 @@ use zephyr::time::{ Duration, sleep }; #[no_mangle] extern "C" fn rust_main() { - zephyr::set_logger(); + unsafe { zephyr::set_logger().unwrap(); } warn!("Starting blinky"); // println!("Blinky!"); @@ -52,6 +52,7 @@ fn do_blink() { warn!("Inside of blinky"); let mut led0 = zephyr::devicetree::aliases::led0::get_instance().unwrap(); + let mut gpio_token = unsafe { zephyr::device::gpio::GpioToken::get_instance().unwrap() }; if !led0.is_ready() { warn!("LED is not ready"); @@ -60,10 +61,10 @@ fn do_blink() { // return; } - led0.configure(GPIO_OUTPUT_ACTIVE); + unsafe { led0.configure(&mut gpio_token, GPIO_OUTPUT_ACTIVE); } let duration = Duration::millis_at_least(500); loop { - led0.toggle_pin(); + unsafe { led0.toggle_pin(&mut gpio_token); } sleep(duration); } } From d10c418ee685dd59f09e3da589e3b8e591e52e75 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 18 Dec 2024 10:21:38 -0700 Subject: [PATCH 21/30] zephyr: sys: device: gpio: Allow Gpio::new to be unused Add a `#[allow(dead_code)]` annotation to `Gpio::new`, as not all devices necessarily will have a Gpio device (or it could be disable). Signed-off-by: David Brown --- zephyr/src/device/gpio.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zephyr/src/device/gpio.rs b/zephyr/src/device/gpio.rs index c0cace33..b9ddaf77 100644 --- a/zephyr/src/device/gpio.rs +++ b/zephyr/src/device/gpio.rs @@ -44,6 +44,7 @@ impl Gpio { /// Constructor, used by the devicetree generated code. /// /// TODO: Guarantee single instancing. + #[allow(dead_code)] pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { if !unique.once() { return None; From 949b03e3a74a3a0606ac1a5988efe5d6171695bc Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 18 Dec 2024 10:39:23 -0700 Subject: [PATCH 22/30] Various cleanups from github comments Apply some clarifications and minor code changes based on github review comments. Signed-off-by: David Brown --- samples/blinky/Cargo.toml | 2 +- samples/blinky/build.rs | 6 ++++++ samples/blinky/sample.yaml | 1 + zephyr-build/src/devicetree.rs | 22 +++++++++------------- zephyr-build/src/lib.rs | 2 +- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/samples/blinky/Cargo.toml b/samples/blinky/Cargo.toml index e812ba1a..db4a5c43 100644 --- a/samples/blinky/Cargo.toml +++ b/samples/blinky/Cargo.toml @@ -6,7 +6,7 @@ name = "rustapp" version = "0.1.0" edition = "2021" -description = "A sample hello world application in Rust" +description = "Blink an LED forever using the GPIO API" license = "Apache-2.0 or MIT" [lib] diff --git a/samples/blinky/build.rs b/samples/blinky/build.rs index eea8aade..f3849d1e 100644 --- a/samples/blinky/build.rs +++ b/samples/blinky/build.rs @@ -1,3 +1,9 @@ fn main() { + // This call will make make config entries available in the code for every device tree node, to + // allow conditional compilation based on whether it is present in the device tree. + // For example, it will be possible to have: + // ```rust + // #[cfg(dt = "aliases::led0")] + // ``` zephyr_build::dt_cfgs(); } diff --git a/samples/blinky/sample.yaml b/samples/blinky/sample.yaml index de711910..2b37187d 100644 --- a/samples/blinky/sample.yaml +++ b/samples/blinky/sample.yaml @@ -1,3 +1,4 @@ +# See doc/develop/test/twister.rst for what is here. sample: name: Blinky Sample tests: diff --git a/zephyr-build/src/devicetree.rs b/zephyr-build/src/devicetree.rs index 6c7d0d08..981f2646 100644 --- a/zephyr-build/src/devicetree.rs +++ b/zephyr-build/src/devicetree.rs @@ -30,6 +30,7 @@ mod parse; pub use augment::{Augment, load_augments}; +/// Representation of a parsed device tree. pub struct DeviceTree { /// The root of the tree. root: Rc, @@ -37,7 +38,7 @@ pub struct DeviceTree { labels: BTreeMap>, } -// This is a single node in the devicetree. +// A single node in a [`DeviceTree`]. pub struct Node { // The name of the node itself. name: String, @@ -90,7 +91,7 @@ pub enum Word { } impl DeviceTree { - /// Decode the zephyr.dts and devicetree_generated.h files from the build and build an internal + /// Decode the `zephyr.dts` and `devicetree_generated.h` files from the build and build an internal /// representation of the devicetree itself. pub fn new, P2: AsRef>(dts_path: P1, dt_gen: P2) -> DeviceTree { let ords = OrdMap::new(dt_gen); @@ -98,19 +99,14 @@ impl DeviceTree { let dts = std::fs::read_to_string(dts_path) .expect("Reading zephyr.dts file"); let dt = parse::parse(&dts, &ords); - dt.resolve_phandles(); - dt.set_parents(); - dt - } - /// Walk the node tree, fixing any phandles to include their reference. - fn resolve_phandles(&self) { - self.root.phandle_walk(&self.labels); - } + // 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. - fn set_parents(&self) { - self.root.parent_walk(); + // Walk the node tree, setting each node's parent appropriately. + dt.root.parent_walk(); + + dt } } diff --git a/zephyr-build/src/lib.rs b/zephyr-build/src/lib.rs index 102f4d52..c4f02ef5 100644 --- a/zephyr-build/src/lib.rs +++ b/zephyr-build/src/lib.rs @@ -84,7 +84,7 @@ pub fn build_kconfig_mod() { fn import_dt() -> DeviceTree { let zephyr_dts = env::var("ZEPHYR_DTS").expect("ZEPHYR_DTS must be set"); let gen_include = env::var("BINARY_DIR_INCLUDE_GENERATED") - .expect("BINARY_DIR_INCLUDE_GENERATED"); + .expect("BINARY_DIR_INCLUDE_GENERATED must be set"); let generated = format!("{}/devicetree_generated.h", gen_include); DeviceTree::new(&zephyr_dts, generated) From 19ad114c47bba274ffb5f3e7bdf6592449b3bac4 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 18 Dec 2024 10:40:07 -0700 Subject: [PATCH 23/30] zephyr: device: Use AtomicBool instead of AtomicUsize Change the AtomicUsize to an AtomicBool to clarify the use. The flag is still stored inverted, so false is the correct initializer, and we will still initialize from zero-initted memory. Signed-off-by: David Brown --- zephyr/src/device.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/zephyr/src/device.rs b/zephyr/src/device.rs index 08bf91a9..db275e04 100644 --- a/zephyr/src/device.rs +++ b/zephyr/src/device.rs @@ -5,7 +5,7 @@ //! //! Most of these instances come from the device tree. -use crate::sync::atomic::{AtomicUsize, Ordering}; +use crate::sync::atomic::{AtomicBool, Ordering}; pub mod gpio; pub mod flash; @@ -21,12 +21,14 @@ pub mod flash; /// driver will be shared among then. Generally, the constructor for the individual device will /// call `get_instance_raw()` on the underlying device. #[allow(dead_code)] -pub(crate) struct Unique(pub(crate) AtomicUsize); +pub(crate) struct Unique(pub(crate) AtomicBool); impl Unique { + // Note that there are circumstances where these are in zero-initialized memory, so false must + // be used here, and the result of `once` inverted. /// Construct a new unique counter. pub(crate) const fn new() -> Unique { - Unique(AtomicUsize::new(0)) + Unique(AtomicBool::new(false)) } /// Indicates if this particular entity can be used. This function, on a given `Unique` value @@ -35,6 +37,6 @@ impl Unique { pub(crate) fn once(&self) -> bool { // `fetch_add` is likely to be faster than compare_exchage. This does have the limitation // that `once` is not called more than `usize::MAX` times. - self.0.fetch_add(1, Ordering::AcqRel) == 0 + !self.0.fetch_or(true, Ordering::AcqRel) } } From 37e5502d4e0f6718d14c08a9830f2fb50d493a59 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 18 Dec 2024 11:18:38 -0700 Subject: [PATCH 24/30] Numerous minor fixes from github reviews Apply various minor fixes. None of these should affect the code execution itself. Signed-off-by: David Brown --- samples/blinky/CMakeLists.txt | 2 +- samples/blinky/Cargo.toml | 2 +- samples/blinky/src/lib.rs | 3 +- samples/blinky/src/main.c | 48 ------------------ zephyr-build/src/devicetree.rs | 67 +++++++++++--------------- zephyr-build/src/devicetree/augment.rs | 8 +-- zephyr-build/src/devicetree/output.rs | 6 +-- zephyr-sys/wrapper.h | 2 +- zephyr/src/device.rs | 5 +- zephyr/src/device/flash.rs | 6 +-- zephyr/src/device/gpio.rs | 2 + 11 files changed, 46 insertions(+), 105 deletions(-) delete mode 100644 samples/blinky/src/main.c diff --git a/samples/blinky/CMakeLists.txt b/samples/blinky/CMakeLists.txt index fdbabbc8..9efa442c 100644 --- a/samples/blinky/CMakeLists.txt +++ b/samples/blinky/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 OR MIT cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) diff --git a/samples/blinky/Cargo.toml b/samples/blinky/Cargo.toml index db4a5c43..7c1d6bc7 100644 --- a/samples/blinky/Cargo.toml +++ b/samples/blinky/Cargo.toml @@ -7,7 +7,7 @@ name = "rustapp" version = "0.1.0" edition = "2021" description = "Blink an LED forever using the GPIO API" -license = "Apache-2.0 or MIT" +license = "Apache-2.0 OR MIT" [lib] crate-type = ["staticlib"] diff --git a/samples/blinky/src/lib.rs b/samples/blinky/src/lib.rs index 84d7bac6..431d0008 100644 --- a/samples/blinky/src/lib.rs +++ b/samples/blinky/src/lib.rs @@ -21,9 +21,8 @@ extern "C" fn rust_main() { unsafe { zephyr::set_logger().unwrap(); } warn!("Starting blinky"); - // println!("Blinky!"); + // Invoke "blink" as a user thread. - // blink(); if false { unsafe { zephyr::raw::k_thread_user_mode_enter diff --git a/samples/blinky/src/main.c b/samples/blinky/src/main.c deleted file mode 100644 index 4cab4969..00000000 --- a/samples/blinky/src/main.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2016 Intel Corporation - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include - -/* 1000 msec = 1 sec */ -#define SLEEP_TIME_MS 1000 - -/* The devicetree node identifier for the "led0" alias. */ -#define LED0_NODE DT_ALIAS(led0) - -/* - * A build error on this line means your board is unsupported. - * See the sample documentation for information on how to fix this. - */ -static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); - -int main(void) -{ - int ret; - bool led_state = true; - - if (!gpio_is_ready_dt(&led)) { - return 0; - } - - ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); - if (ret < 0) { - return 0; - } - - while (1) { - ret = gpio_pin_toggle_dt(&led); - if (ret < 0) { - return 0; - } - - led_state = !led_state; - printf("LED state: %s\n", led_state ? "ON" : "OFF"); - k_msleep(SLEEP_TIME_MS); - } - return 0; -} diff --git a/zephyr-build/src/devicetree.rs b/zephyr-build/src/devicetree.rs index 981f2646..18879823 100644 --- a/zephyr-build/src/devicetree.rs +++ b/zephyr-build/src/devicetree.rs @@ -35,6 +35,7 @@ pub struct DeviceTree { /// The root of the tree. root: Rc, /// All of the labels. + /// Note that this is a BTree so that the output will be deterministic. labels: BTreeMap>, } @@ -70,7 +71,7 @@ pub struct Property { pub enum Value { Words(Vec), Bytes(Vec), - Phandle(Phandle), // TODO + Phandle(Phandle), String(String), } @@ -123,8 +124,6 @@ impl Node { } fn parent_walk(self: &Rc) { - // *(self.parent.borrow_mut()) = Some(parent.clone()); - for child in &self.children { *(child.parent.borrow_mut()) = Some(self.clone()); child.parent_walk() @@ -132,25 +131,17 @@ impl Node { } fn is_compatible(&self, name: &str) -> bool { - if let Some(prop) = self.properties.iter().find(|p| p.name == "compatible") { - prop.value.iter().any(|v| { - match v { - Value::String(vn) if name == vn => true, - _ => false, - } - }) - } else { - // If there is no compatible field, we are clearly not compatible. - false - } + 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 { - let res = self.path_walk(path, 0); - // println!("compatible? {}: {} {:?}", res, self.path, path); - res + self.path_walk(path, 0) } /// Recursive path walk, to make borrowing simpler. @@ -161,10 +152,8 @@ impl Node { } // Check the failure condition, where this node isn't compatible with this section of the path. - if let Some(name) = path[pos] { - if !self.is_compatible(name) { - return false; - } + if matches!(path[pos], Some(name) if !self.is_compatible(name)) { + return false; } // Walk down the tree. We have to check for None here, as we can't recurse on the none @@ -177,19 +166,17 @@ impl Node { } } - /// Is the named property present? + /// 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) } - /// Get this property in its entirety. + /// 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]> { - for p in &self.properties { - if p.name == name { - return Some(&p.value); - } - } - return None; + 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. @@ -244,12 +231,11 @@ impl Node { impl Value { fn phandle_walk(&self, labels: &BTreeMap>) { match self { - Value::Phandle(ph) => ph.phandle_resolve(labels), - Value::Words(words) => { + Self::Phandle(ph) => ph.phandle_resolve(labels), + Self::Words(words) => { for w in words { - match w { - Word::Phandle(ph) => ph.phandle_resolve(labels), - _ => (), + if let Word::Phandle(ph) = w { + ph.phandle_resolve(labels); } } } @@ -260,8 +246,8 @@ impl Value { impl Phandle { /// Construct a phandle that is unresolved. - pub fn new(name: String) -> Phandle { - Phandle { + pub fn new(name: String) -> Self { + Self { name, node: RefCell::new(None), } @@ -286,9 +272,9 @@ impl Phandle { } impl Word { - pub fn get_number(&self) -> Option { + pub fn as_number(&self) -> Option { match self { - Word::Number(n) => Some(*n), + Self::Number(n) => Some(*n), _ => None, } } @@ -297,6 +283,9 @@ impl Word { // 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 { - write!(fmt, "Phandle({:?})", self.name) + fmt + .debug_struct("Phandle") + .field("name", &self.name) + .finish_non_exhaustive() } } diff --git a/zephyr-build/src/devicetree/augment.rs b/zephyr-build/src/devicetree/augment.rs index 6b82d87f..ec3bddb2 100644 --- a/zephyr-build/src/devicetree/augment.rs +++ b/zephyr-build/src/devicetree/augment.rs @@ -191,7 +191,7 @@ impl RawInfo { fn generate(&self, node: &Node, device: &str) -> TokenStream { let device_id = str_to_path(device); match self { - RawInfo::Myself => { + Self::Myself => { let ord = node.ord; let rawdev = format_ident!("__device_dts_ord_{}", ord); quote! { @@ -209,7 +209,7 @@ impl RawInfo { } } } - RawInfo::Phandle(pname) => { + Self::Phandle(pname) => { let words = node.get_words(pname).unwrap(); // We assume that elt 0 is the phandle, and that the rest are numbers. let target = if let Word::Phandle(handle) = &words[0] { @@ -221,7 +221,7 @@ impl RawInfo { // TODO: We would try to correlate with parent node's notion of number of cells, and // try to handle cases where there is more than one reference. It is unclear when // this will be needed. - let args: Vec = words[1..].iter().map(|n| n.get_number().unwrap()).collect(); + let args: Vec = words[1..].iter().map(|n| n.as_number().unwrap()).collect(); let target_route = target.route_to_rust(); @@ -235,7 +235,7 @@ impl RawInfo { } } } - RawInfo::Parent { level, args } => { + Self::Parent { level, args } => { let get_args = args.iter().map(|arg| arg.args(node)); assert!(*level > 0); diff --git a/zephyr-build/src/devicetree/output.rs b/zephyr-build/src/devicetree/output.rs index 4531da31..c5e6e455 100644 --- a/zephyr-build/src/devicetree/output.rs +++ b/zephyr-build/src/devicetree/output.rs @@ -102,10 +102,8 @@ impl DeviceTree { pub const #tag: u32 = #n; }; } - _ => return general_property(prop), + _ => (), } - } else { - return general_property(prop); } } Value::Phandle(ref ph) => { @@ -118,7 +116,7 @@ impl DeviceTree { } } } - _ => return general_property(prop), + _ => (), } } general_property(prop) diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 9300f37d..177bf507 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -12,7 +12,7 @@ */ /* - * This is getting build with KERNEL defined, which causes syscalls to not be implemented. Work + * This is getting built with KERNEL defined, which causes syscalls to not be implemented. Work * around this by just undefining this symbol. */ #undef KERNEL diff --git a/zephyr/src/device.rs b/zephyr/src/device.rs index db275e04..bf4afec6 100644 --- a/zephyr/src/device.rs +++ b/zephyr/src/device.rs @@ -5,6 +5,9 @@ //! //! Most of these instances come from the device tree. +// Allow for a Zephyr build that has no devices at all. +#![allow(dead_code)] + use crate::sync::atomic::{AtomicBool, Ordering}; pub mod gpio; @@ -20,7 +23,6 @@ pub mod flash; /// example, a [`GpioPin`] will reference a single pin, but the underlying device for the gpio /// driver will be shared among then. Generally, the constructor for the individual device will /// call `get_instance_raw()` on the underlying device. -#[allow(dead_code)] pub(crate) struct Unique(pub(crate) AtomicBool); impl Unique { @@ -33,7 +35,6 @@ impl Unique { /// Indicates if this particular entity can be used. This function, on a given `Unique` value /// will return true exactly once. - #[allow(dead_code)] pub(crate) fn once(&self) -> bool { // `fetch_add` is likely to be faster than compare_exchage. This does have the limitation // that `once` is not called more than `usize::MAX` times. diff --git a/zephyr/src/device/flash.rs b/zephyr/src/device/flash.rs index 69bdfaa1..83e277c3 100644 --- a/zephyr/src/device/flash.rs +++ b/zephyr/src/device/flash.rs @@ -1,5 +1,8 @@ //! Device wrappers for flash controllers, and flash partitions. +// Note that currently, the flash partition shares the controller, so the underlying operations +// are not actually safe. Need to rethink how to manage this. + use crate::raw; use super::Unique; @@ -54,6 +57,3 @@ impl FlashPartition { Some(FlashPartition { controller, offset, size }) } } - -// Note that currently, the flash partition shares the controller, so the underlying operations -// are not actually safe. Need to rethink how to manage this. diff --git a/zephyr/src/device/gpio.rs b/zephyr/src/device/gpio.rs index b9ddaf77..fda369bf 100644 --- a/zephyr/src/device/gpio.rs +++ b/zephyr/src/device/gpio.rs @@ -12,6 +12,8 @@ use super::Unique; /// Global instance to help make gpio in Rust slightly safer. /// +/// # Safety +/// /// To help with safety, the rust types use a global instance of a gpio-token. Methods will /// take a mutable reference to this, which will require either a single thread in the /// application code, or something like a mutex or critical section to manage. The operation From 64864c6d4e333f6b3909ee0762ff7d2dde7b6b08 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 18 Dec 2024 11:20:15 -0700 Subject: [PATCH 25/30] CMakeLists: Add missing decls added to doc generation With the doc generation basically duplicated, the added entries to build the DT are missing, causing doc generation to fail. Add those to the doc generation rule as well. Signed-off-by: David Brown --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 887a3265..bb0529f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -208,6 +208,8 @@ ${config_paths} INCLUDE_DIRS="${include_dirs}" INCLUDE_DEFINES="${include_defines}" WRAPPER_FILE="${WRAPPER_FILE}" + DT_AUGMENTS="${DT_AUGMENTS}" + BINARY_DIR_INCLUDE_GENERATED="${BINARY_DIR_INCLUDE_GENERATED}" cargo doc ${rust_build_type_arg} From 1b029461ce886b0a08aab13080e05ec4815741a4 Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 30 Dec 2024 12:08:44 -0700 Subject: [PATCH 26/30] zephyr: device: gpio: Add Send to Gpio and GpioPin Thread safety is managed by using a token on the operations, so these become Send safe. Signed-off-by: David Brown --- zephyr/src/device/gpio.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zephyr/src/device/gpio.rs b/zephyr/src/device/gpio.rs index fda369bf..b9a31d3e 100644 --- a/zephyr/src/device/gpio.rs +++ b/zephyr/src/device/gpio.rs @@ -42,6 +42,9 @@ pub struct Gpio { pub(crate) device: *const raw::device, } +// SAFETY: Gpio's can be shared with other threads. Safety is maintained by the Token. +unsafe impl Send for Gpio {} + impl Gpio { /// Constructor, used by the devicetree generated code. /// @@ -73,6 +76,9 @@ pub struct GpioPin { pub(crate) pin: raw::gpio_dt_spec, } +// SAFETY: GpioPin's can be shared with other threads. Safety is maintained by the Token. +unsafe impl Send for GpioPin {} + impl GpioPin { /// Constructor, used by the devicetree generated code. #[allow(dead_code)] From 89958c3c79abbf12b84236867b0aa07d1bdfbaa8 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 14 Jan 2025 10:00:43 -0700 Subject: [PATCH 27/30] zephyr-build: devicetree: Clean up compatable check Instead of a separate function which uses a `pos` argument, just make `compatible_path` itself recursive by using slice operations. Signed-off-by: David Brown --- zephyr-build/src/devicetree.rs | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/zephyr-build/src/devicetree.rs b/zephyr-build/src/devicetree.rs index 18879823..f2efa30c 100644 --- a/zephyr-build/src/devicetree.rs +++ b/zephyr-build/src/devicetree.rs @@ -141,28 +141,21 @@ impl Node { /// 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 { - self.path_walk(path, 0) - } - - /// Recursive path walk, to make borrowing simpler. - fn path_walk(&self, path: &[Option<&str>], pos: usize) -> bool { - if pos >= path.len() { - // Once past the end, we consider everything a match. - return true; - } - - // Check the failure condition, where this node isn't compatible with this section of the path. - if matches!(path[pos], Some(name) if !self.is_compatible(name)) { - return false; - } + if let Some(first) = path.first() { + if !matches!(first, Some(name) if !self.is_compatible(name)) { + return false; + } - // Walk down the tree. We have to check for None here, as we can't recurse on the none - // case. - if let Some(child) = self.parent.borrow().as_ref() { - child.path_walk(path, pos + 1) + // 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 { - // We've run out of nodes, so this is considered not matching. - false + // The empty path always matches. + true } } From f65528573fc5fd2b51a02ef12336f1c77e6eb863 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 14 Jan 2025 10:02:03 -0700 Subject: [PATCH 28/30] samples: blinky: Minor cleanups Remove a bogus commented line of code, and add a comment explaining why the `if false` code remains. Signed-off-by: David Brown --- samples/blinky/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/blinky/src/lib.rs b/samples/blinky/src/lib.rs index 431d0008..c4a6ed77 100644 --- a/samples/blinky/src/lib.rs +++ b/samples/blinky/src/lib.rs @@ -24,6 +24,9 @@ extern "C" fn rust_main() { // Invoke "blink" as a user thread. if false { + // Note that for now, this is just a 'false', but is an easy test to use to see if + // permissions are correct for usermode Rust. At this point, the GPIO won't be accessible, + // and neither will the memory needed for allocation. unsafe { zephyr::raw::k_thread_user_mode_enter (Some(blink), @@ -57,7 +60,6 @@ fn do_blink() { warn!("LED is not ready"); loop { } - // return; } unsafe { led0.configure(&mut gpio_token, GPIO_OUTPUT_ACTIVE); } From 6943226e479dbc1db90a4975184110c44e3c137f Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 14 Jan 2025 10:13:30 -0700 Subject: [PATCH 29/30] samples: blink: Update dependency version main has moved back to semantic versions, to 0.1.0. Fix our new sample to match. Signed-off-by: David Brown --- samples/blinky/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/blinky/Cargo.toml b/samples/blinky/Cargo.toml index 7c1d6bc7..9bf5f2ce 100644 --- a/samples/blinky/Cargo.toml +++ b/samples/blinky/Cargo.toml @@ -13,8 +13,8 @@ license = "Apache-2.0 OR MIT" crate-type = ["staticlib"] [dependencies] -zephyr = "3.7.0" +zephyr = "0.1.0" log = "0.4.22" [build-dependencies] -zephyr-build = "3.7.0" +zephyr-build = "0.1.0" From fe2cc401b2d45a380e008a81b1221a079ded0a89 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 14 Jan 2025 10:21:15 -0700 Subject: [PATCH 30/30] samples: blinky: Remove user thread test This doesn't belong in a blink sample, and can be re-introduced when support is implemented for it. Signed-off-by: David Brown --- samples/blinky/src/lib.rs | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/samples/blinky/src/lib.rs b/samples/blinky/src/lib.rs index c4a6ed77..5a22f03a 100644 --- a/samples/blinky/src/lib.rs +++ b/samples/blinky/src/lib.rs @@ -11,8 +11,6 @@ use log::warn; -use core::ffi::c_void; - use zephyr::raw::GPIO_OUTPUT_ACTIVE; use zephyr::time::{ Duration, sleep }; @@ -22,30 +20,6 @@ extern "C" fn rust_main() { warn!("Starting blinky"); - // Invoke "blink" as a user thread. - if false { - // Note that for now, this is just a 'false', but is an easy test to use to see if - // permissions are correct for usermode Rust. At this point, the GPIO won't be accessible, - // and neither will the memory needed for allocation. - unsafe { - zephyr::raw::k_thread_user_mode_enter - (Some(blink), - core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut()); - } - } else { - unsafe { - blink(core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut()); - } - } -} - -// fn blink() { -unsafe extern "C" fn blink(_p1: *mut c_void, _p2: *mut c_void, _p3: *mut c_void) { - // Just call a "safe" rust function. do_blink(); }