From 0c7805b1fe524dbe535505b7140def037783ec75 Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 18 Jul 2024 14:34:34 -0600 Subject: [PATCH 01/65] 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 9475bfabf6b4ad10f589c7b6466bba570eab19c3 Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 16 Sep 2024 13:06:00 -0600 Subject: [PATCH 02/65] 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 77ecd5c1eca13f0c897189af028c9ed2f690fad0 Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 16 Sep 2024 12:51:15 -0600 Subject: [PATCH 03/65] 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 ca898a24f8580f8e8904beb1b5fd9d07072b1074 Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 16 Sep 2024 15:57:56 -0600 Subject: [PATCH 04/65] 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 fc2b384a2cdf44f0e3db8e4017db97f7547527b8 Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 16 Sep 2024 16:04:23 -0600 Subject: [PATCH 05/65] 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 4fc314a57859cd37f6be66ada57fb43acad8d0bd Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 16 Oct 2024 16:20:50 -0600 Subject: [PATCH 06/65] 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 0a8781507f2474ca93f0610a27ce7c8dbe3793ea Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 17 Oct 2024 23:28:56 -0600 Subject: [PATCH 07/65] 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 644378c5f8bc28953cabec0d858c9fbc9e9d105b Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 10:12:26 -0600 Subject: [PATCH 08/65] 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 6741961c7cb3504717d405c1b3ea2c8d0d3541a4 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 10:36:55 -0600 Subject: [PATCH 09/65] 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 09b99bfd0ccfcfc1bafb5f1b7a743698dc30fa6a Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 11:38:10 -0600 Subject: [PATCH 10/65] 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 d1ed7b6546354bb96d961c7c4996229820a56c8e Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 11:49:43 -0600 Subject: [PATCH 11/65] 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 e0719632e299937b66045e8b07b650eeeb31958d Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 13:42:05 -0600 Subject: [PATCH 12/65] 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 3bda0a6fa27f37cd8590337f48ab9ed2bef0f3ad Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 13:43:49 -0600 Subject: [PATCH 13/65] 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 3ded6777ed9347a2ca722fee38c3df5b66b10ea7 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 14:08:06 -0600 Subject: [PATCH 14/65] 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 ed49246fdc5b46ac3acfd2cf02008acd9a77c991 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 25 Oct 2024 12:59:00 -0600 Subject: [PATCH 15/65] 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 15dae9b655d08cbed042dee4d35e54d3726cbc6f Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 29 Oct 2024 10:57:49 -0600 Subject: [PATCH 16/65] 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 c250afb0d763c0b7d896d03e235dce97233f8175 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 29 Oct 2024 10:58:28 -0600 Subject: [PATCH 17/65] 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 c0bf963ab7f963518e665e71cf8e5fd5821af307 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 29 Oct 2024 14:09:19 -0600 Subject: [PATCH 18/65] 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 1a482f8b654c7686c37210642ef4ae08a73127fe Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 29 Oct 2024 15:50:09 -0600 Subject: [PATCH 19/65] 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 31b5e496f93ed0cf23f1e07b5696afd8a9e82bb7 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 22 Nov 2024 10:34:22 -0700 Subject: [PATCH 20/65] 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 97200c130b0148c006bf74ef89f5b112bc503eb2 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 18 Dec 2024 10:21:38 -0700 Subject: [PATCH 21/65] 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 69fe79edf7b2e24930c3f46bcb5bab293d5eb275 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 18 Dec 2024 10:39:23 -0700 Subject: [PATCH 22/65] 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 f59ecdd1ee767a1595fb3e3991cca3199609d572 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 18 Dec 2024 10:40:07 -0700 Subject: [PATCH 23/65] 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 4469875170a1c9411bb843bf73c76354e837b1de Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 18 Dec 2024 11:18:38 -0700 Subject: [PATCH 24/65] 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 0c6f9395e0b1bd58dd8fb155818aa616b7af7810 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 18 Dec 2024 11:20:15 -0700 Subject: [PATCH 25/65] 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 1289301f9a16292a080d7a568147649f9e4f6d2d Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 30 Dec 2024 12:08:44 -0700 Subject: [PATCH 26/65] 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 8173b0e89baaf5aff50b8ca738f4b4829483940f Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 14 Jan 2025 10:00:43 -0700 Subject: [PATCH 27/65] 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 2460e095830ea37cd576248a8d1f559bb7076be2 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 14 Jan 2025 10:02:03 -0700 Subject: [PATCH 28/65] 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 baffb4f4caee51067b499b05308898169c777886 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 14 Jan 2025 10:13:30 -0700 Subject: [PATCH 29/65] 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 1143ae9601f2854668756e815b16c23e9e63a18c Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 14 Jan 2025 10:21:15 -0700 Subject: [PATCH 30/65] 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(); } From 6382f57056314072264bf3b1be4a4c475b182471 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 27 Dec 2024 09:03:04 -0700 Subject: [PATCH 31/65] cmake: Consistent expansion of include path Accept both space and semicolon separated paths and defines from cmake. The add_custom_command really wants to replace the semicolons with spaces, whereas the 'file' command doesn't. To make this work, have the build.rs split on either space or semicolon. This fixes a problem where `cargo check` and `rust-analyzer` fail to run correctly when the Zephyr build is configured for release, and they are trying to do a debug build. With this fix, the debug build from cargo will work, allowing the analysis or verification. Signed-off-by: David Brown --- CMakeLists.txt | 2 +- zephyr-sys/build.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bb0529f8..21c88e57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,7 @@ endfunction() function(get_include_dirs target dirs) get_target_property(include_dirs ${target} INTERFACE_INCLUDE_DIRECTORIES) if(include_dirs) - set(${dirs} ${include_dirs} PARENT_SCOPE) + set(${dirs} "${include_dirs}" PARENT_SCOPE) else() set(${dirs} "" PARENT_SCOPE) endif() diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index efd8c8be..0f8a60e3 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -102,7 +102,9 @@ fn main() -> Result<()> { fn define_args(bindings: Builder, prefix: &str, var_name: &str) -> Builder { let text = env::var(var_name).unwrap(); let mut bindings = bindings; - for entry in text.split(" ") { + // Split on either spaces or semicolons, to allow some flexibility in what cmake might generate + // for us. + for entry in text.split(&[' ', ';']) { if entry.is_empty() { continue; } From 805156e7639357fdbc0e31ed8e8b37428bb5ca40 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 7 Jan 2025 09:47:28 -0700 Subject: [PATCH 32/65] zephyr: Add initial implementation of work queue and async support This provides a first pass an an implementation of management of Zephyr work queues, and an executor that schedules work using Zephyr's work queues. TODO: Clean up how Futures can use the Context to indicate scheduling. TODO: Move a few things to make the used modules a bit cleaner. Signed-off-by: David Brown --- zephyr-sys/build.rs | 1 + zephyr-sys/src/lib.rs | 2 + zephyr-sys/wrapper.h | 4 + zephyr/Cargo.toml | 1 + zephyr/src/kio.rs | 96 +++++ zephyr/src/kio/sync.rs | 126 +++++++ zephyr/src/lib.rs | 13 + zephyr/src/simpletls.rs | 123 +++++++ zephyr/src/sync.rs | 3 + zephyr/src/sync/channel.rs | 243 +++++++++++- zephyr/src/sys/queue.rs | 2 +- zephyr/src/sys/sync/semaphore.rs | 62 +++- zephyr/src/time.rs | 17 +- zephyr/src/work.rs | 613 +++++++++++++++++++++++++++++++ zephyr/src/work/futures.rs | 575 +++++++++++++++++++++++++++++ 15 files changed, 1875 insertions(+), 6 deletions(-) create mode 100644 zephyr/src/kio.rs create mode 100644 zephyr/src/kio/sync.rs create mode 100644 zephyr/src/simpletls.rs create mode 100644 zephyr/src/work.rs create mode 100644 zephyr/src/work/futures.rs diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index 0f8a60e3..842c21e3 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -86,6 +86,7 @@ fn main() -> Result<()> { .allowlist_item("K_.*") .allowlist_item("ZR_.*") .allowlist_item("LOG_LEVEL_.*") + .allowlist_item("k_poll_modes") // Deprecated .blocklist_function("sys_clock_timeout_end_calc") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) diff --git a/zephyr-sys/src/lib.rs b/zephyr-sys/src/lib.rs index 5f317d8c..aef51f04 100644 --- a/zephyr-sys/src/lib.rs +++ b/zephyr-sys/src/lib.rs @@ -48,3 +48,5 @@ macro_rules! derive_copy { derive_copy!(z_spinlock_key); derive_clone!(z_spinlock_key); +derive_copy!(k_timeout_t); +derive_clone!(k_timeout_t); diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 177bf507..7d964bb8 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -52,3 +52,7 @@ extern int errno; */ const uintptr_t ZR_STACK_ALIGN = Z_KERNEL_STACK_OBJ_ALIGN; const uintptr_t ZR_STACK_RESERVED = K_KERNEL_STACK_RESERVED; + +const uint32_t ZR_POLL_TYPE_SEM_AVAILABLE = K_POLL_TYPE_SEM_AVAILABLE; +const uint32_t ZR_POLL_TYPE_SIGNAL = K_POLL_TYPE_SIGNAL; +const uint32_t ZR_POLL_TYPE_DATA_AVAILABLE = K_POLL_TYPE_DATA_AVAILABLE; diff --git a/zephyr/Cargo.toml b/zephyr/Cargo.toml index 15d183c4..0b508568 100644 --- a/zephyr/Cargo.toml +++ b/zephyr/Cargo.toml @@ -20,6 +20,7 @@ cfg-if = "1.0" # The log create is used if the user desires logging, and calls `set_logger()`. log = "0.4.22" +arrayvec = { version = "0.7.6", default-features = false } [dependencies.fugit] version = "0.3.7" diff --git a/zephyr/src/kio.rs b/zephyr/src/kio.rs new file mode 100644 index 00000000..40f14b93 --- /dev/null +++ b/zephyr/src/kio.rs @@ -0,0 +1,96 @@ +//! Async IO for Zephyr +//! +//! This implements the basics of using Zephyr's work queues to implement async code on Zephyr. +//! +//! Most of the work happens in [`work`] and in [`futures`] +//! +//! [`work`]: crate::work +//! [`futures`]: crate::work::futures + +use core::ffi::CStr; +use core::task::Poll; +use core::{future::Future, pin::Pin}; + +use crate::time::NoWait; +use crate::work::futures::WakeInfo; +use crate::work::{futures::JoinHandle, futures::WorkBuilder, WorkQueue}; + +pub mod sync; + +/// Run an async future on the given worker thread. +/// +/// Arrange to have the given future run on the given worker thread. The resulting `JoinHandle` has +/// `join` and `join_async` methods that can be used to wait for the given thread. +pub fn spawn(future: F, worker: &WorkQueue, name: &'static CStr) -> JoinHandle +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + WorkBuilder::new() + .set_worker(worker) + .set_name(name) + .start(future) +} + +/// Run an async future on the current worker thread. +/// +/// Arrange to have the given future run on the current worker thread. The resulting `JoinHandle` +/// has `join` and `join_async` methods that can be used to wait for the given thread. +/// +/// The main use for this is to allow work threads to use `Rc` and `Rc>` within async +/// tasks. The main constraint is that references inside cannot be held across an `.await`. +/// +/// # Panics +/// If this is called other than from a worker task running on a work thread, it will panic. +pub fn spawn_local(future: F, name: &'static CStr) -> JoinHandle +where + F: Future + 'static, + F::Output: Send + 'static, +{ + WorkBuilder::new() + .set_name(name) + .start_local(future) +} + +/// Yield the current thread, returning it to the work queue to be run after other work on that +/// queue. (This has to be called `yield_now` in Rust, because `yield` is a keyword. +pub fn yield_now() -> impl Future { + YieldNow { waited: false } +} + +struct YieldNow { + waited: bool, +} + +impl Future for YieldNow { + type Output = (); + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + if self.waited { + Poll::Ready(()) + } else { + // Enqueue outselves with no wait and no events. + let info = unsafe { WakeInfo::from_context(cx) }; + + // Unsafely check if the work queue running us is empty. We only check explicitly + // specified workers (TODO access the system work queue). The check is racy, but should + // always fail indicating that the queue is not empty when it could be. Checking this + // avoids re-scheduling the only worker back into the queue. + // SAFETY: The check is racy, but will fail with us yielding when we didn't need to. + if let Some(wq) = info.queue { + let wq = unsafe { wq.as_ref() }; + if wq.pending.head == wq.pending.tail { + return Poll::Ready(()); + } + } + + info.timeout = NoWait.into(); + self.waited = true; + + Poll::Pending + } + } +} diff --git a/zephyr/src/kio/sync.rs b/zephyr/src/kio/sync.rs new file mode 100644 index 00000000..bb7cc848 --- /dev/null +++ b/zephyr/src/kio/sync.rs @@ -0,0 +1,126 @@ +//! Synchronization mechanisms that work with async. +//! +//! Notably, Zephyr's `k_mutex` type isn't supported as a type that can be waited for +//! asynchronously. +//! +//! The main problem with `k_mutex` (meaning [`crate::sync::Mutex`]) is that the `lock` operation +//! can block, and since multiple tasks may be scheduled for the same work queue, the system can +//! deadlock, as the scheduler may not run to allow the task that actually holds the mutex to run. +//! +//! As an initial stopgap. We provide a [`Mutex`] type that is usable within an async context. We +//! do not currently implement an associated `Condvar`. +//! +//! Note that using Semaphores for locking means that this mechanism doesn't handle priority +//! inversion issues. Be careful with workers that run at different priorities. + +// Use the same error types from the regular sync version. + +use core::{ + cell::UnsafeCell, + fmt, + marker::PhantomData, + ops::{Deref, DerefMut}, +}; + +use crate::{ + sync::{LockResult, TryLockError, TryLockResult}, + sys::sync::Semaphore, + time::{Forever, NoWait}, +}; + +/// A mutual exclusion primitive useful for protecting shared data. Async version. +/// +/// This mutex will block a task waiting for the lock to become available. +pub struct Mutex { + /// The semaphore indicating ownership of the data. When it is "0" the task that did the 'take' + /// on it owns the data, and will use `give` when it is unlocked. This mechanism works for + /// simple Mutex that protects the data without needing a condition variable. + inner: Semaphore, + data: UnsafeCell, +} + +// SAFETY: The semaphore, with the semantics provided here, provide Send and Sync. +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +impl fmt::Debug for Mutex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Mutex {:?}", self.inner) + } +} + +/// An RAII implementation of a held lock. +pub struct MutexGuard<'a, T: ?Sized + 'a> { + lock: &'a Mutex, + // Mark !Send explicitly until support is added to Rust for this. + _nosend: PhantomData>, +} + +// unsafe impl Sync for MutexGuard<'_, T> {} +unsafe impl Sync for MutexGuard<'_, T> {} + +impl Mutex { + /// Construct a new Mutex. + pub fn new(t: T) -> Mutex { + Mutex { + inner: Semaphore::new(1, 1).unwrap(), + data: UnsafeCell::new(t), + } + } +} + +impl Mutex { + /// Acquire the mutex, blocking the current thread until it is able to do so. + /// + /// This is a sync version, and calling it from an async task will possibly block the async work + /// thread, potentially causing deadlock. + pub fn lock(&self) -> LockResult> { + self.inner.take(Forever).unwrap(); + unsafe { Ok(MutexGuard::new(self)) } + } + + /// Aquire the mutex, async version. + pub async fn lock_async(&self) -> LockResult> { + self.inner.take_async(Forever).await.unwrap(); + unsafe { Ok(MutexGuard::new(self)) } + } + + /// Attempt to aquire the lock. + pub fn try_lock(&self) -> TryLockResult> { + match self.inner.take(NoWait) { + Ok(()) => unsafe { Ok(MutexGuard::new(self)) }, + // TODO: Distinguish timeout from other errors. + Err(_) => Err(TryLockError::WouldBlock), + } + } +} + +impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> { + unsafe fn new(lock: &'mutex Mutex) -> MutexGuard<'mutex, T> { + MutexGuard { + lock, + _nosend: PhantomData, + } + } +} + +impl Deref for MutexGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.lock.data.get() } + } +} + +impl DerefMut for MutexGuard<'_, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.lock.data.get() } + } +} + +impl Drop for MutexGuard<'_, T> { + #[inline] + fn drop(&mut self) { + self.lock.inner.give(); + } +} diff --git a/zephyr/src/lib.rs b/zephyr/src/lib.rs index 4acda6a4..4106aefe 100644 --- a/zephyr/src/lib.rs +++ b/zephyr/src/lib.rs @@ -15,11 +15,16 @@ pub mod device; pub mod error; pub mod logging; pub mod object; +pub mod simpletls; pub mod sync; pub mod sys; pub mod time; #[cfg(CONFIG_RUST_ALLOC)] pub mod timer; +#[cfg(CONFIG_RUST_ALLOC)] +pub mod work; +#[cfg(CONFIG_RUST_ALLOC)] +pub mod kio; pub use error::{Error, Result}; @@ -108,3 +113,11 @@ pub mod _export { // If allocation has been requested, provide the allocator. #[cfg(CONFIG_RUST_ALLOC)] pub mod alloc_impl; + +#[cfg(CONFIG_RUST_ALLOC)] +pub mod task { + //! Provides the portable-atomic version of `alloc::task::Wake`, which uses the compatible + //! versionm of Arc. + + pub use portable_atomic_util::task::Wake; +} diff --git a/zephyr/src/simpletls.rs b/zephyr/src/simpletls.rs new file mode 100644 index 00000000..ec892edf --- /dev/null +++ b/zephyr/src/simpletls.rs @@ -0,0 +1,123 @@ +//! A simple TLS helping tool. +//! +//! Until this crate implements general TLS support, similar to std, this simpletls module can +//! provide a simplified type of thread-local storage. + +extern crate alloc; + +use core::{ptr, sync::atomic::Ordering}; + +use alloc::boxed::Box; +use alloc::vec::Vec; +use zephyr_sys::{k_current_get, k_thread}; + +use crate::sync::{atomic::AtomicPtr, Mutex}; + +/// A container for simple thread local storage. +/// +/// This will maintain a mapping between Zephyr threads and a value of type T. Entries will have to +/// be added manually, generally when each thread is started. +/// +/// Note that T must implement Copy, as it is not safe to retain references to the inner data +/// outside of this api. +/// +/// T must also implement Send, since although 'get' always retrieves the current thread's data, +/// `insert` will typically need to move `T` across threads. +pub struct SimpleTls { + map: Vec<(usize, T)>, +} + +impl SimpleTls { + /// Create a new SimpleTls. + pub const fn new() -> Self { + Self { map: Vec::new() } + } + + /// Insert a new association into the SimpleTls. + /// + /// If this thread has already been added, the value will be replaced. + pub fn insert(&mut self, thread: *const k_thread, data: T) { + let thread = thread as usize; + + match self.map.binary_search_by(|(id, _)| id.cmp(&thread)) { + Ok(pos) => self.map[pos] = (thread, data), // Replace existing. + Err(pos) => self.map.insert(pos, (thread, data)), + } + } + + /// Lookup the data associated with a given thread. + pub fn get(&self) -> Option { + let thread = unsafe { k_current_get() } as usize; + + self.map + .binary_search_by(|(id, _)| id.cmp(&thread)) + .ok() + .map(|pos| self.map[pos].1) + } +} + +/// A helper to safely use these with static. +/// +/// The StaticTls type has a constant constructor, and the same insert and get methods as the +/// underlying SimpleTls, with support for initializing the Mutex as needed. +// TODO: This should eventually make it into a more general lazy mechanism. +pub struct StaticTls { + /// The container for the data. + /// + /// The AtomicPtr is either Null, or contains a raw pointer to the underlying Mutex holding the + /// data. + data: AtomicPtr>>, +} + +impl StaticTls { + /// Create a new StaticTls that is empty. + pub const fn new() -> Self { + Self { + data: AtomicPtr::new(ptr::null_mut()), + } + } + + /// Get the underlying Mutex out of the data, initializing it with an empty type if necessary. + fn get_inner(&self) -> &Mutex> { + let data = self.data.fetch_update( + // TODO: These orderings are likely stronger than necessary. + Ordering::SeqCst, + Ordering::SeqCst, + |ptr| { + if ptr.is_null() { + // For null, we need to allocate a new one. + let data = Box::new(Mutex::new(SimpleTls::new())); + Some(Box::into_raw(data)) + } else { + // If there was already a value, just use it. + None + } + }); + let data = match data { + Ok(_) => { + // If the update stored something, it unhelpfully returns the old value, which was + // the null pointer. Since the pointer will only ever be updated once, it is safe + // to use a relaxed load here. + self.data.load(Ordering::Relaxed) + } + // If there was already a pointer, that is what we want. + Err(ptr) => ptr, + }; + + // SAFETY: The stored data was updated at most once, by the above code, and we now have a + // pointer to a valid leaked box holding the data. + unsafe { &*data } + } + + /// Insert a new association into the StaticTls. + pub fn insert(&self, thread: *const k_thread, data: T) { + let inner = self.get_inner(); + inner.lock().unwrap().insert(thread, data); + } + + /// Lookup the data associated with a given thread. + pub fn get(&self) -> Option { + let inner = self.get_inner(); + inner.lock().unwrap().get() + } +} diff --git a/zephyr/src/sync.rs b/zephyr/src/sync.rs index 977433bb..e76179f7 100644 --- a/zephyr/src/sync.rs +++ b/zephyr/src/sync.rs @@ -27,6 +27,8 @@ pub mod atomic { #[cfg(CONFIG_RUST_ALLOC)] pub use portable_atomic_util::Arc; +#[cfg(CONFIG_RUST_ALLOC)] +pub use portable_atomic_util::Weak; mod mutex; @@ -36,6 +38,7 @@ pub use mutex::{ Condvar, LockResult, TryLockResult, + TryLockError, }; mod spinmutex; diff --git a/zephyr/src/sync/channel.rs b/zephyr/src/sync/channel.rs index 7dba4bb5..360171b5 100644 --- a/zephyr/src/sync/channel.rs +++ b/zephyr/src/sync/channel.rs @@ -45,12 +45,15 @@ use alloc::boxed::Box; use core::cell::UnsafeCell; use core::ffi::c_void; use core::fmt; +use core::future::Future; use core::marker::PhantomData; use core::mem::MaybeUninit; use core::pin::Pin; +use core::task::Poll; use crate::sys::queue::Queue; -use crate::time::{Forever, NoWait, Timeout}; +use crate::time::{Duration, Forever, NoWait, Timeout}; +use crate::work::futures::WakeInfo; mod counter; @@ -205,6 +208,87 @@ impl Sender { } } +// A little note about the Unpin constraint here. Because Futures are pinned in Rust Async code, +// and the future stores the messages, we can only send and receive messages that aren't pinned. +impl Sender { + /// Waits for a message to be sent into the channel, but only for a limited time. Async + /// version. + /// + /// This has the same behavior as [`send_timeout`], but as an Async function. + pub fn send_timeout_async<'a>(&'a self, msg: T, timeout: impl Into) + -> impl Future>> + 'a + { + SendFuture { + sender: self, + msg: Some(msg), + timeout: timeout.into(), + waited: false, + } + } + + /// Sends a message over the given channel, waiting if necessary. Async version. + pub async fn send_async(&self, msg: T) -> Result<(), SendError> { + self.send_timeout_async(msg, Forever).await + } + + // Note that there is no async version of `try_send`. +} + +/// The implementation of Future for Sender::send_timeout_async. +struct SendFuture<'a, T: Unpin> { + sender: &'a Sender, + msg: Option, + timeout: Timeout, + waited: bool, +} + +impl<'a, T: Unpin> Future for SendFuture<'a, T> { + type Output = Result<(), SendError>; + + fn poll(self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> core::task::Poll { + /* + let this = unsafe { + Pin::get_unchecked_mut(self) + }; + */ + let this = Pin::get_mut(self); + + // Take the message out in preparation to try sending it. It is a logic error if the unwrap + // fails. + let msg = this.msg.take().unwrap(); + + // Try sending the message, with no timeout. + let msg = match this.sender.try_send(msg) { + Ok(()) => return Poll::Ready(Ok(())), + Err(SendError(msg)) => msg, + }; + + if this.waited { + // We already waited, and no message, so give the messagre back, indiciating a timeout. + return Poll::Ready(Err(SendError(msg))); + } + + // Send didn't happen, put the message back to have for the next call. + this.msg = Some(msg); + + // Otherwise, schedule to wake up on receipt or timeout. + let info = unsafe { WakeInfo::from_context(cx) }; + match &this.sender.flavor { + SenderFlavor::Unbounded { .. } => { + panic!("Implementation error: unbounded queues should never fail"); + } + SenderFlavor::Bounded(chan) => { + unsafe { + info.add_queue(&chan.free); + } + } + } + info.timeout = this.timeout; + + Poll::Pending + } +} + impl Drop for Sender { fn drop(&mut self) { match &self.flavor { @@ -341,6 +425,42 @@ impl Receiver { } } +// Note that receive doesn't need the Unpin constraint, as we aren't storing any message. +impl Receiver { + /// Waits for a message to be received from the channel, but only for a limited time. + /// Async version. + /// + /// If the channel is empty and not disconnected, this call will block until the receive + /// operation can proceed or the operation times out. + /// wake up and return an error. + pub fn recv_timeout_async<'a>(&'a self, timeout: impl Into) + -> impl Future> + 'a + { + RecvFuture { + receiver: self, + timeout: timeout.into(), + waited: false, + } + } + + /// Blocks the current thread until a message is received or the channel is empty and + /// disconnected. Async version. + /// + /// If the channel is empty and not disconnected, this call will block until the receive + /// operation can proceed. + pub async fn recv_async(&self) -> Result { + self.recv_timeout_async(Forever).await + } + + /// Return a reference to the inner queue. + fn as_queue(&self) -> &Queue { + match &self.flavor { + ReceiverFlavor::Unbounded { queue, .. } => queue, + ReceiverFlavor::Bounded(chan) => &chan.chan, + } + } +} + impl Drop for Receiver { fn drop(&mut self) { match &self.flavor { @@ -390,6 +510,38 @@ impl fmt::Debug for Receiver { } } +struct RecvFuture<'a, T> { + receiver: &'a Receiver, + timeout: Timeout, + waited: bool, +} + +impl<'a, T> Future for RecvFuture<'a, T> { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll { + // Try to receive a message. + if let Ok(msg) = self.receiver.try_recv() { + return Poll::Ready(Ok(msg)); + } + + if self.waited { + // Wait already happened, so this is a timeout. + return Poll::Ready(Err(RecvError)); + } + + // Otherwise, schedule to wakeup on receipt or timeout. + let info = unsafe { WakeInfo::from_context(cx) }; + unsafe { + info.add_queue(self.receiver.as_queue()); + } + info.timeout = self.timeout; + self.waited = true; + + Poll::Pending + } +} + /// The "flavor" of a receiver. This maps to the type of the channel. enum ReceiverFlavor { /// An unbounded queue. Messages were allocated with Box, and will be freed upon receipt. @@ -481,3 +633,92 @@ impl fmt::Debug for SendError { /// [`recv`]: Receiver::recv #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub struct RecvError; + +/// Wait loop +/// +/// A common scenario for async work tasks is to wait for, and process messages off of a queue, but +/// to also wake periodically to perform some task. +/// +/// This performs this periodic loop. It has some support for handling the case where the +/// processing takes longer than the loop duration, but it merely re-schedules for the period past +/// the current time. This means the phase of the period will change upon dropped ticks. +/// +/// Each time an event is received, 'handle' is called with `Some(ev)`. In addition, periodically +/// (based on `period`) `handle` will be called with None. +/// +/// **Note**: It needs to be a single handler, because this closure will frequently be in a move +/// closure, and this would force shared data to be shared in Sync types of wrappers. The main +/// purpose of combining the event handling and the periodic is to avoid that. +/// +/// Note that also, if the timer is just barely able to run, it will still be scheduled "shortly" in +/// the future. +/// +/// T is the type of the messages expected to be received. +/// +/// TODO: This function, in general, is completely worthless without Rust support for [async +/// closures](https://rust-lang.github.io/rfcs/3668-async-closures.html). +pub async fn event_loop_useless ( + events: Receiver, + period: Duration, + mut handle: EF, +) -> ! +where + EF: FnMut(Option) -> EFF, + EFF: Future, +{ + // Start with a deadline 'period' out in the future. + let mut next = crate::time::now() + period; + loop { + if let Ok(ev) = events.recv_timeout_async(next).await { + handle(Some(ev)).await; + continue; + } + + // We either reached, or exceeded our timeout. + handle(None).await; + + // Calculate the next time. + next += period; + + // If this is passed, just reschedule after our Duration from "now". + let now = crate::time::now(); + if next <= now { + next = now + period; + } + } +} + +/// Wait loop, as a macro. +/// +/// This is the `event loop` above, implemented as a macro, which becomes more useful as the async +/// closures aren't needed. +#[macro_export] +macro_rules! event_loop { + ($events:expr, $period:expr, + Some($eventvar:ident) => $event_body:block, + None => $periodic_body: block $(,)?) => + { + let events = $events; + let period = $period; + let mut next = $crate::time::now() + period; + loop { + if let Ok($eventvar) = events.recv_timeout_async(next).await { + $event_body + } else { + // Note that ':block' above requires the braces, so this body can't introduce + // bindings that shadow our local variables. + $periodic_body + next += period; + + // If this is passed, just reschedule after our Duration from "now". + let now = $crate::time::now(); + if next <= now { + ::log::warn!("periodic overflow: {} ticks, {}:{}", + (now - next).ticks(), + core::file!(), core::line!()); + next = now + period; + } + } + } + }; +} diff --git a/zephyr/src/sys/queue.rs b/zephyr/src/sys/queue.rs index 943733f0..2360088d 100644 --- a/zephyr/src/sys/queue.rs +++ b/zephyr/src/sys/queue.rs @@ -23,7 +23,7 @@ use crate::time::Timeout; /// A wrapper around a Zephyr `k_queue` object. pub struct Queue { - item: Fixed, + pub(crate) item: Fixed, } unsafe impl Sync for StaticKernelObject { } diff --git a/zephyr/src/sys/sync/semaphore.rs b/zephyr/src/sys/sync/semaphore.rs index 1aafd8a6..16016ff6 100644 --- a/zephyr/src/sys/sync/semaphore.rs +++ b/zephyr/src/sys/sync/semaphore.rs @@ -11,16 +11,22 @@ //! operation, which in situation where counting is actually desired, will result in the count being //! incorrect. -use core::ffi::c_uint; +use core::pin::Pin; +use core::task::{Context, Poll}; +use core::{ffi::c_uint, future::Future}; use core::fmt; #[cfg(CONFIG_RUST_ALLOC)] use core::mem; +use zephyr_sys::ETIMEDOUT; + +use crate::time::NoWait; +use crate::work::futures::WakeInfo; use crate::{ error::{to_result_void, Result}, object::{Fixed, StaticKernelObject, Wrapped}, raw::{ - k_sem, k_sem_count_get, k_sem_give, k_sem_init, k_sem_reset, k_sem_take + k_sem, k_sem_count_get, k_sem_give, k_sem_init, k_sem_reset, k_sem_take, }, time::Timeout, }; @@ -30,7 +36,7 @@ pub use crate::raw::K_SEM_MAX_LIMIT; /// A zephyr `k_sem` usable from safe Rust code. pub struct Semaphore { /// The raw Zephyr `k_sem`. - item: Fixed, + pub(crate) item: Fixed, } /// By nature, Semaphores are both Sync and Send. Safety is handled by the underlying Zephyr @@ -68,6 +74,17 @@ impl Semaphore { to_result_void(ret) } + /// Take a semaphore, async version. + /// + /// Returns a future that either waits for the semaphore, or returns status. + pub fn take_async<'a>(&'a self, timeout: impl Into) -> impl Future> + 'a { + SemTake { + sem: self, + timeout: timeout.into(), + ran: false, + } + } + /// Give a semaphore. /// /// This routine gives to the semaphore, unless the semaphore is already at its maximum @@ -100,6 +117,45 @@ impl Semaphore { } } +/// The async 'take' Future +struct SemTake<'a> { + /// The semaphore we're waiting on. + sem: &'a Semaphore, + /// The timeout to use. + timeout: Timeout, + /// Set after we've waited once. + ran: bool, +} + +impl<'a> Future for SemTake<'a> { + type Output = Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Always check if data is available. + if let Ok(()) = self.sem.take(NoWait) { + return Poll::Ready(Ok(())); + } + + if self.ran { + // If we ran once, and still don't have any data, indicate this as a timeout. + return Poll::Ready(Err(crate::Error(ETIMEDOUT))); + + } + + // TODO: Clean this up. + let info = unsafe { WakeInfo::from_context(cx) }; + unsafe { + // SAFETY: The semaphore must outlive the queued event. The lifetime ensures that the + // Future won't outlive the semaphore. + info.add_semaphore(self.sem); + } + info.timeout = self.timeout; + self.ran = true; + + Poll::Pending + } +} + /// A static Zephyr `k_sem`. /// /// This is intended to be used from within the `kobj_define!` macro. It declares a static ksem diff --git a/zephyr/src/time.rs b/zephyr/src/time.rs index e1d6eafb..7fe1c0b5 100644 --- a/zephyr/src/time.rs +++ b/zephyr/src/time.rs @@ -24,7 +24,7 @@ //! by non-constant values). Similarly, the `fugit` crate offers constructors that aim to result //! in constants when possible, avoiding costly division operations. -use zephyr_sys::{k_timeout_t, k_ticks_t}; +use zephyr_sys::{k_ticks_t, k_timeout_t, k_uptime_ticks}; use core::fmt::Debug; @@ -54,6 +54,12 @@ pub type Duration = fugit::Duration; #[cfg(CONFIG_TIMEOUT_64BIT)] pub type Instant = fugit::Instant; +/// Retrieve the current scheduler time as an Instant. This can be used to schedule timeouts at +/// absolute points in time. +pub fn now() -> Instant { + Instant::from_ticks(unsafe { k_uptime_ticks() as u64 }) +} + // The Zephyr `k_timeout_t` represents several different types of intervals, based on the range of // the value. It is a signed number of the same size as the Tick here, which effectively means it // is one bit less. @@ -71,8 +77,17 @@ pub type Instant = fugit::Instant; /// This wrapper allows us to implement `From` and `Info` from the Fugit types into the Zephyr /// types. This allows various methods to accept either value, as well as the `Forever` and /// `NoWait` values defined here. +#[derive(Clone, Copy, Debug)] pub struct Timeout(pub k_timeout_t); +impl PartialEq for Timeout { + fn eq(&self, other: &Self) -> bool { + self.0.ticks == other.0.ticks + } +} + +impl Eq for Timeout {} + // `From` allows methods to take a time of various types and convert it into a Zephyr timeout. impl From for Timeout { fn from(value: Duration) -> Timeout { diff --git a/zephyr/src/work.rs b/zephyr/src/work.rs new file mode 100644 index 00000000..3322b396 --- /dev/null +++ b/zephyr/src/work.rs @@ -0,0 +1,613 @@ +//! Zephyr Work Queues +//! +//! # Zephyr Work Queues and Work +//! +//! Zephyr has a mechanism called a +//! [Workqueue](https://docs.zephyrproject.org/latest/kernel/services/threads/workqueue.html). +//! +//! Each workqueue is backed by a single Zephyr thread, and has its own stack. The work queue +//! consists of a FIFO queue of work items that will be run consecutively on that thread. The +//! underlying types are `k_work_q` for the work queue itself, and `k_work` for the worker. +//! +//! In addition to the simple schedulable work, Zephyr also has two additional types of work: +//! `k_work_delayable` which can be scheduled to run in the future, and `k_work_poll`, described as +//! triggered work in the docs. This can be scheduled to run when various items within Zephyr +//! become available. This triggered work also has a timeout. In this sense the triggered work is +//! a superset of the other types of work. Both delayable and triggered work are implemented by +//! having the `k_work` embedded in their structure, and Zephyr schedules the work when the given +//! reason happens. +//! +//! Zephyr's work queues can be used in different ways: +//! +//! - Work can be scheduled as needed. For example, an IRQ handler can queue a work item to process +//! data it has received from a device. +//! - Work can be scheduled periodically. +//! +//! As most C use of Zephyr statically allocates things like work, these are typically rescheduled +//! when the work is complete. The work queue scheduling functions are designed, and intended, for +//! a given work item to be able to reschedule itself, and such usage is common. +//! +//! ## Waitable events +//! +//! The triggerable work items can be triggered to wake on a set of any of the following: +//! +//! - A signal. `k_poll_signal` is a type used just for waking work items. This works similar to a +//! binary semaphore, but is lighter weight for use just by this mechanism. +//! - A semaphore. Work can be scheduled to run when a `k_sem` is available. Since +//! [`sys::sync::Semaphore`] is built on top of `k_sem`, the "take" operation for these semaphores +//! can be a trigger source. +//! - A queue/FIFO/LIFO. The queue is used to implement [`sync::channel`] and thus any blocking +//! operation on queues can be a trigger source. +//! - Message Queues, and Pipes. Although not yet provided in Rust, these can also be a source of +//! triggering. +//! +//! It is important to note that the trigger source may not necessarily still be available by the +//! time the work item is actually run. This depends on the design of the system. If there is only +//! a single waiter, then it will still be available (the mechanism does not have false triggers, +//! like CondVar). +//! +//! Also, note, specifically, that Zephyr Mutexes cannot be used as a trigger source. That means +//! that locking a [`sync::Mutex`] shouldn't be use within work items. There is another +//! [`kio::sync::Mutex`], which is a simplified Mutex that is implemented with a Semaphore that can +//! be used from work-queue based code. +//! +//! # Rust `Future` +//! +//! The rust language, also has built-in support for something rather similar to Zephyr work queues. +//! The main user-visible type behind this is [`Future`]. The rust compiler has support for +//! functions, as well as code blocks to be declared as `async`. For this code, instead of directly +//! returning the given data, returns a `Future` that has as its output type the data. What this +//! does is essentially capture what would be stored on the stack to maintain the state of that code +//! into the data of the `Future` itself. For rust code running on a typical OS, a crate such as +//! [Tokio](https://tokio.rs/) provides what is known as an executor, which implements the schedule +//! for these `Futures` as well as provides equivalent primitives for Mutex, Semaphores and channels +//! for this code to use for synchronization. +//! +//! It is notable that the Zephyr implementation of `Future` operations under a fairly simple +//! assumption of how this scheduling will work. Each future is invoked with a Context, which +//! contains a dynamic `Waker` that can be invoked to schedule this Future to run again. This means +//! that the primitives are typically implemented above OS primitives, where each manages wake +//! queues to determine the work that needs to be woken. +//! +//! # Bringing it together. +//! +//! There are a couple of issues that need to be addressed to bring work-queue support to Rust. +//! First is the question of how they will be used. On the one hand, there are users that will +//! definitely want to make use of `async` in rust, and it is important to implement a executor, +//! similar to Tokio, that will schedule this `async` code. On the other hand, it will likely be +//! common for others to want to make more direct use of the work queues themselves. As such, these +//! users will want more direct access to scheduling and triggering of work. +//! +//! ## Future erasure +//! +//! One challenge with using `Future` for work is that the `Future` type intentionally erases the +//! details of scheduling work, reducing it down to a single `Waker`, which similar to a trait, has +//! a `wake` method to cause the executor to schedule this work. Unfortunately, this simple +//! mechanism makes it challenging to take advantage of Zephyr's existing mechanisms to be able to +//! automatically trigger work based on primitives. +//! +//! As such, what we do is have a structure `Work` that contains both a `k_work_poll` as well as +//! `Context` from Rust. Our handler can use a mechanism similar to C's `CONTAINER_OF` macro to +//! recover this outer structure. +//! +//! There is some extra complexity to this process, as the `Future` we are storing associated with +//! the work is `?Sized`, since each particular Future will have a different size. As such, it is +//! not possible to recover the full work type. To work around this, we have a Sized struct at the +//! beginning of this structure, that along with judicious use of `#[repr(C)]` allows us to recover +//! this fixed data. This structure contains the information needed to re-schedule the work, based +//! on what is needed. +//! +//! ## Ownership +//! +//! The remaining challenge with implementing `k_work` for Rust is that of ownership. The model +//! taken here is that the work items are held in a `Box` that is effectively owned by the work +//! itself. When the work item is scheduled to Zephyr, ownership of that box is effectively handed +//! off to C, and then when the work item is called, the Box re-constructed. This repeats until the +//! work is no longer needed (e.g. when a [`Future::poll`] returns `Ready`), at which point the work +//! will be dropped. +//! +//! There are two common ways the lifecycle of work can be managed in an embedded system: +//! +//! - A set of `Future`'s are allocated once at the start, and these never return a value. Work +//! Futures inside of this (which correspond to `.await` in async code) can have lives and return +//! values, but the main loops will not return values, or be dropped. Embedded Futures will +//! typically not be boxed. +//! - Work will be dynamically created based on system need, with threads using [`kio::spawn`] to +//! create additional work (or creating the `Work` items directly). These can use [`join`] or +//! [`join_async`] to wait for the results. +//! +//! One consequence of the ownership being passed through to C code is that if the work cancellation +//! mechanism is used on a work queue, the work items themselves will be leaked. +//! +//! The Future mechanism in Rust relies on the use of [`Pin`] to ensure that work items are not +//! moved. We have the same requirements here, although currently, the pin is only applied while +//! the future is run, and we do not expose the `Box` that we use, thus preventing moves of the work +//! items. +//! +//! ## The work queues themselves +//! +//! Workqueues themselves are built using [`WorkQueueBuilder`]. This needs a statically defined +//! stack. Typical usage will be along the lines of: +//! ```rust +//! kobj_define! { +//! WORKER_STACK: ThreadStack<2048>; +//! } +//! // ... +//! let main_worker = Box::new(j +//! WorkQueueBuilder::new() +//! .set_priority(2). +//! .set_name(c"mainloop") +//! .set_no_yield(true) +//! .start(MAIN_LOOP_STACK.init_once(()).unwrap()) +//! ); +//! +//! let _ = zephyr::kio::spawn( +//! mainloop(), // Async or function returning Future. +//! &main_worker, +//! c"w:mainloop", +//! ); +//! +//! ... +//! +//! // Leak the Box so that the worker is never freed. +//! let _ = Box::leak(main_worker); +//! ``` +//! +//! It is important that WorkQueues never be dropped. It has a Drop implementation that invokes +//! panic. Zephyr provides no mechanism to stop work queue threads, so dropping would result in +//! undefined behavior. +//! +//! # Current Status +//! +//! Although Zephyr has 3 types of work queues, the `k_work_poll` is sufficient to implement all of +//! the behavior, and this implementation only implements this type. Non Future work could be built +//! around the other work types. +//! +//! As such, this means that manually constructed work is still built using `Future`. The `_async` +//! primitives throughout this crate can be used just as readily by hand-written Futures as by async +//! code. Notable, the use of [`Signal`] will likely be common, along with possible timeouts. + +extern crate alloc; + +use alloc::boxed::Box; +use core::{ + convert::Infallible, + ffi::{c_int, c_uint, CStr}, + future::Future, + mem, + pin::Pin, + ptr, + task::Poll, +}; +use futures::WakeInfo; + +use zephyr_sys::{ + k_poll_signal, k_poll_signal_check, k_poll_signal_init, k_poll_signal_raise, + k_poll_signal_reset, k_work, k_work_init, k_work_q, k_work_queue_config, k_work_queue_init, + k_work_queue_start, k_work_submit, k_work_submit_to_queue, ETIMEDOUT, +}; + +use crate::{error::to_result_void, object::Fixed, simpletls::StaticTls, sys::thread::ThreadStack, time::Timeout}; + +pub mod futures; + +/// A builder for work queues themselves. +/// +/// A work queue is a Zephyr thread that instead of directly running a piece of code, manages a work +/// queue. Various types of `Work` can be submitted to these queues, along with various types of +/// triggering conditions. +pub struct WorkQueueBuilder { + /// The "config" value passed in. + config: k_work_queue_config, + /// Priority for the work queue thread. + priority: c_int, +} + +impl WorkQueueBuilder { + /// Construct a new WorkQueueBuilder with default values. + pub fn new() -> Self { + Self { + config: k_work_queue_config { + name: ptr::null(), + no_yield: false, + essential: false, + }, + priority: 0, + } + } + + /// Set the name for the WorkQueue thread. + /// + /// This name shows up in debuggers and some analysis tools. + pub fn set_name(&mut self, name: &'static CStr) -> &mut Self { + self.config.name = name.as_ptr(); + self + } + + /// Set the "no yield" flag for the created worker. + /// + /// If this is not set, the work queue will call `k_yield` between each enqueued work item. For + /// non-preemptible threads, this will allow other threads to run. For preemptible threads, + /// this will allow other threads at the same priority to run. + /// + /// This method has a negative in the name, which goes against typical conventions. This is + /// done to match the field in the Zephyr config. + pub fn set_no_yield(&mut self, value: bool) -> &mut Self { + self.config.no_yield = value; + self + } + + /// Set the "essential" flag for the created worker. + /// + /// This sets the essential flag on the running thread. The system considers the termination of + /// an essential thread to be a fatal error. + pub fn set_essential(&mut self, value: bool) -> &mut Self { + self.config.essential = value; + self + } + + /// Set the priority for the worker thread. + /// + /// See the Zephyr docs for the meaning of priority. + pub fn set_priority(&mut self, value: c_int) -> &mut Self { + self.priority = value; + self + } + + /// Start the given work queue thread. + /// + /// TODO: Implement a 'start' that works from a static work queue. + pub fn start(&self, stack: ThreadStack) -> WorkQueue { + let item: Fixed = Fixed::new(unsafe { mem::zeroed() }); + unsafe { + // SAFETY: Initialize zeroed memory. + k_work_queue_init(item.get()); + + // SAFETY: This associates the workqueue with the thread ID that runs it. The thread is + // a pointer into this work item, which will not move, because of the Fixed. + let this = &mut *item.get(); + WORK_QUEUES.insert(&this.thread, WorkQueueRef(item.get())); + + // SAFETY: Start work queue thread. The main issue here is that the work queue cannot + // be deallocated once the thread has started. We enforce this by making Drop panic. + k_work_queue_start( + item.get(), + stack.base, + stack.size, + self.priority, + &self.config, + ); + } + + WorkQueue { item } + } +} + +/// A running work queue thread. +/// +/// # Panic +/// +/// Allowing a work queue to drop will result in a panic. There are two ways to handle this, +/// depending on whether the WorkQueue is in a Box, or an Arc: +/// ``` +/// // Leak a work queue in an Arc. +/// let wq = Arc::new(WorkQueueBuilder::new().start(...)); +/// // If the Arc is used after this: +/// let _ = Arc::into_raw(wq.clone()); +/// // If the Arc is no longer needed: +/// let _ = Arc::into_raw(wq); +/// +/// // Leak a work queue in a Box. +/// let wq = Box::new(WorkQueueBuilder::new().start(...)); +/// let _ = Box::leak(wq); +/// +pub struct WorkQueue { + #[allow(dead_code)] + item: Fixed, +} + +/// Work queues can be referenced from multiple threads, and thus are Send and Sync. +unsafe impl Send for WorkQueue {} +unsafe impl Sync for WorkQueue {} + +impl Drop for WorkQueue { + fn drop(&mut self) { + panic!("WorkQueues must not be dropped"); + } +} + +/// A simple mapping to get the current work_queue from the currently running thread. +/// +/// This assumes that Zephyr's works queues have a 1:1 mapping between the work queue and the +/// thread. +/// +/// # Safety +/// +/// The work queue is protected with a sync Mutex (which uses an underlying Zephyr mutex). It is, +/// in general, not a good idea to use a mutex in a work queue, as deadlock can happen. So it is +/// important to both never .await while holding the lock, as well as to make sure operations within +/// it are relatively fast. In this case, `jnsert` and `get` on the SimpleTls are reasonably fast. +/// `insert` is usually done just at startup as well. +/// +/// This is a little bit messy as we don't have a lazy mechanism, so we have to handle this a bit +/// manually right now. +static WORK_QUEUES: StaticTls = StaticTls::new(); + +/// For the queue mapping, we need a simple wrapper around the underlying pointer, one that doesn't +/// implement stop. +#[derive(Copy, Clone)] +struct WorkQueueRef(*mut k_work_q); + +// SAFETY: The work queue reference is also safe for both Send and Sync per Zephyr semantics. +unsafe impl Send for WorkQueueRef {} +unsafe impl Sync for WorkQueueRef {} + +/// Retrieve the current work queue, if we are running within one. +pub fn get_current_workq() -> Option<*mut k_work_q> { + WORK_QUEUES.get().map(|wq| wq.0) +} + +/// A Rust wrapper for `k_poll_signal`. +/// +/// A signal in Zephyr is an event mechanism that can be used to trigger actions in event queues to +/// run. The work somewhat like a kind of half boolean semaphore. The signaling is robust in the +/// direction of the event happening, as in a blocked task will definitely wake when the signal happens. However, the clearing of the signal is racy. Generally, there are two ways to do this: +/// +/// - A work action can clear the signal as soon as it wakes up, before it starts processing any +/// data the signal was meant to indicate. If the race happens, the processing will handle the +/// extra data. +/// - A work action can clear the signal after it does it's processing. This is useful for things +/// like periodic timers, where if it is still processing when an additional timer tick comes in, +/// that timer tick will be ignored. This is useful for periodic events where it is better to +/// just skip a tick rather than for them to "stack up" and get behind. +/// +/// Notably, as long as the `reset` method is only ever called by the worker that is waiting upon +/// it, there shouldn't ever be a race in the `wait_async` itself. +/// +/// Signals can pass a `c_int` from the signalling task to the task that is waiting for the signal. +/// It is not specified in the Zephyr documentation what value will be passed if `raise` is called +/// multiple times before a task waits upon a signal. The current implementation will return the +/// most recent raised `result` value. +/// +/// For most other use cases, channels or semaphores are likely to be better solutions. +pub struct Signal { + /// The raw Zephyr `k_poll_signal`. + pub(crate) item: Fixed, +} + +// SAFETY: Zephyr's API maintains thread safety. +unsafe impl Send for Signal {} +unsafe impl Sync for Signal {} + +impl Signal { + /// Create a new `Signal`. + /// + /// The Signal will be in the non-signaled state. + pub fn new() -> Result { + // SAFETY: The memory is zero initialized, and Fixed ensure that it never changes address. + let item: Fixed = Fixed::new(unsafe { mem::zeroed() }); + unsafe { + k_poll_signal_init(item.get()); + } + Ok(Signal { item }) + } + + /// Reset the Signal + /// + /// This resets the signal state to unsignaled. + /// + /// Please see the [`Signal`] documentation on how to handle the races that this implies. + pub fn reset(&self) { + // SAFETY: This is safe with a non-mut reference, as the purpose of the Zephyr API is to + // coordinate this information between threads. + unsafe { + k_poll_signal_reset(self.item.get()); + } + } + + /// Check the status of a signal. + /// + /// This reads the status of the signal. If the state is "signalled", this will return + /// `Some(result)` where the `result` is the result value given to [`raise`]. + pub fn check(&self) -> Option { + let mut signaled: c_uint = 0; + let mut result: c_int = 0; + unsafe { + // SAFETY: Zephyr's signal API coordinates access across threads. + k_poll_signal_check(self.item.get(), &mut signaled, &mut result); + } + + if signaled != 0 { + Some(result) + } else { + None + } + } + + /// Signal a signal object. + /// + /// This will signal to any worker that is waiting on this object that the event has happened. + /// The `result` will be returned from the worker's `wait` call. + /// + /// As per the Zephyr docs, this could return an EAGAIN error if the polling thread is in the + /// process of expiring. The implication is that the signal will not be raised in this case. + /// ... + pub fn raise(&self, result: c_int) -> crate::Result<()> { + to_result_void(unsafe { k_poll_signal_raise(self.item.get(), result) }) + } + + /// Asynchronously wait for a signal to be signaled. + /// + /// If the signal has not been raised, will wait until it has been. If the signal has been + /// raised, the Future will immediately return that value without waiting. + /// + /// **Note**: there is no sync wait, as Zephyr does not provide a convenient mechanmism for + /// this. It could be implemented with `k_poll` if needed. + pub fn wait_async<'a>( + &'a self, + timeout: impl Into, + ) -> impl Future> + 'a { + SignalWait { + signal: self, + timeout: timeout.into(), + ran: false, + } + } +} + +/// The Future for Signal::wait_async. +struct SignalWait<'a> { + /// The signal we are waiting on. + signal: &'a Signal, + /// The timeout to use. + timeout: Timeout, + /// Set after we've waited once, + ran: bool, +} + +impl<'a> Future for SignalWait<'a> { + type Output = crate::Result; + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + // We can check if the even happened immediately, and avoid blocking if we were already + // signaled. + if let Some(result) = self.signal.check() { + return Poll::Ready(Ok(result)); + } + + if self.ran { + // If it is not ready, assuming a timeout. Note that if a thread other than this work + // thread resets the signal, it is possible to see a timeout even if `Forever` was given + // as the timeout. + return Poll::Ready(Err(crate::Error(ETIMEDOUT))); + } + + let info = unsafe { WakeInfo::from_context(cx) }; + unsafe { + // SAFETY: The Signal must outlive the queued event. The lifetime ensure that the + // Future can't outlive the Signal. + info.add_signal(self.signal); + } + info.timeout = self.timeout; + self.ran = true; + + Poll::Pending + } +} + +/// Possible returns from work queue submission. +#[derive(Debug, Clone, Copy)] +pub enum SubmitResult { + /// This work was already in a queue. + AlreadySubmitted, + /// The work has been added to the specified queue. + Enqueued, + /// The queue was called from the worker itself, and has been queued to the queue that was + /// running it. + WasRunning, +} + +impl SubmitResult { + /// Does this result indicate that the work was enqueued? + pub fn enqueued(self) -> bool { + matches!(self, Self::Enqueued | Self::WasRunning) + } + + /// Convert an int result from a work submit function. + fn to_result(value: c_int) -> crate::Result { + crate::error::to_result(value).map(|code| match code { + 0 => Self::AlreadySubmitted, + 1 => Self::Enqueued, + 2 => Self::WasRunning, + _ => panic!("Unexpected result {} from Zephyr work submission", code), + }) + } +} + +/// A simple action that just does something with its data. +/// +/// This is similar to a Future, except there is no concept of it completing. It manages its +/// associated data however it wishes, and is responsible for re-queuing as needed. +pub trait SimpleAction { + /// Perform the action. + fn act(self: Pin<&mut Self>); +} + +/// A basic Zephyr work item. +/// +/// Holds a `k_work`, along with the data associated with that work. When the work is queued, the +/// `act` method will be called on the provided `SimpleAction`. +#[repr(C)] +pub struct Work { + work: k_work, + action: T, +} + +impl> Work { + /// Construct a new Work from the given action. + /// + /// Note that the data will be moved into the pinned Work. The data is internal, and only + /// accessible to the work thread (the `act` method). If shared data is needed, normal + /// inter-thread sharing mechanisms are needed. + /// + /// TODO: Can we come up with a way to allow sharing on the same worker using Rc instead of Arc? + pub fn new(action: T) -> Pin> { + let mut this = Box::pin(Self { + // SAFETY: will be initialized below, after this is pinned. + work: unsafe { mem::zeroed() }, + action, + }); + let ptr = this.as_mut().as_k_work(); + // SAFETY: Initializes the zero allocated struct. + unsafe { + k_work_init(ptr, Some(Self::handler)); + } + + this + } + + /// Submit this work to the system work queue. + /// + /// This can return several possible `Ok` results. See the docs on [`SubmitResult`] for an + /// explanation of them. + pub fn submit(self: Pin<&mut Self>) -> crate::Result { + // SAFETY: The Pin ensures this will not move. Our implementation of drop ensures that the + // work item is no longer queued when the data is dropped. + SubmitResult::to_result(unsafe { k_work_submit(self.as_k_work()) }) + } + + /// Submit this work to a specified work queue. + /// + /// TODO: Change when we have better wrappers for work queues. + pub fn submit_to_queue( + self: Pin<&mut Self>, + queue: *mut k_work_q, + ) -> crate::Result { + // SAFETY: The Pin ensures this will not move. Our implementation of drop ensures that the + // work item is no longer queued when the data is dropped. + SubmitResult::to_result(unsafe { k_work_submit_to_queue(queue, self.as_k_work()) }) + } + + /// Get the pointer to the underlying work queue. + fn as_k_work(self: Pin<&mut Self>) -> *mut k_work { + // SAFETY: This is private, and no code here will move the pinned item. + unsafe { self.map_unchecked_mut(|s| &mut s.work).get_unchecked_mut() } + } + + /// Get a pointer into our action. + fn as_action(self: Pin<&mut Self>) -> Pin<&mut T> { + // SAFETY: We rely on the worker itself not moving the data. + unsafe { self.map_unchecked_mut(|s| &mut s.action) } + } + + /// Callback, through C, but bound by a specific type. + extern "C" fn handler(work: *mut k_work) { + // SAFETY: We rely on repr(C) placing the first field of a struct at the same address as the + // struct. This avoids needing a Rust equivalent to `CONTAINER_OF`. + let this: Pin<&mut Self> = unsafe { Pin::new_unchecked(&mut *(work as *mut Self)) }; + this.as_action().act(); + } +} diff --git a/zephyr/src/work/futures.rs b/zephyr/src/work/futures.rs new file mode 100644 index 00000000..ed085f31 --- /dev/null +++ b/zephyr/src/work/futures.rs @@ -0,0 +1,575 @@ +//! Zephyr work wrappers targeted for the `Future` type. +//! +//! The future is similar to our [`SimpleAction`], with a few additional features: +//! - The poll function returns an enum indicating that either it can be suspended, or that it +//! is finished and has a result. +//! - The poll function takes a `Waker` which is used to "wake" the work item. +//! +//! However, there is a bit of a semantic mismatch between work queues and Futures. Futures are +//! effectively built with the assumption that the the waking will happen, by Rust code, at the +//! time the event is ready. However, work queues expect the work to be queued immediately, +//! with a "poll" indicating what kind of even the work. Work will be scheduled either based on +//! one of these events, or a timeout. + +extern crate alloc; + +use alloc::boxed::Box; + +use core::{ + cell::UnsafeCell, + ffi::{c_int, c_void, CStr}, + future::Future, + mem, + pin::Pin, + ptr::{self, NonNull}, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +}; + +use arrayvec::ArrayVec; +use zephyr_sys::{ + k_poll_event, k_poll_event_init, k_poll_modes_K_POLL_MODE_NOTIFY_ONLY, k_work, k_work_poll, + k_work_poll_init, k_work_poll_submit, k_work_poll_submit_to_queue, k_work_q, + ZR_POLL_TYPE_DATA_AVAILABLE, ZR_POLL_TYPE_SEM_AVAILABLE, ZR_POLL_TYPE_SIGNAL, +}; + +use crate::{ + printkln, + sync::{Arc, Mutex, Weak}, + sys::{queue::Queue, sync::Semaphore}, + time::{Duration, Forever, NoWait, Tick, Timeout}, +}; + +use super::{get_current_workq, Signal, SubmitResult, WorkQueue}; + +/// An answer to a completed Future. +/// +/// The are two times we need to wait on a future running to completion: the outer initial executor +/// invocation from the main thread, and running an async thread which will have a join method. +/// +/// For both cases, we will use a Semaphore to indicate when the data is available. +/// +/// The main issue is that this type is intended to be one shot. Trying to load a second value will +/// invalidate the data structure (the item will be replaced, but there is a race with the +/// semaphore). +/// +/// TODO: Currently, the data is stored inside of a Mutex. This isn't actually necessary (the +/// semaphore already manages the coordination), and only a memory barrier would be needed, which +/// would be provided by the semaphore. So, this should be changed to just unsafely share the data, +/// similar to how a mutex is implemented. +pub struct Answer { + item: Mutex>, + wake: Semaphore, +} + +impl Answer { + /// Construct a new Answer that does not have the result. + pub fn new() -> Self { + Self { + item: Mutex::new(None), + wake: Semaphore::new(0, 1).expect("Initialize semaphore"), + } + } + + /// Place the item into the Answer. + /// + /// # Panic + /// + /// If the answer already contains an item, this will panic. + /// + /// # TODO + /// + /// We could check that the Answer has ever been used, not just that it has an answer in it. + pub fn place(&self, item: T) { + let mut inner = self.item.lock().expect("Get Mutex"); + if inner.is_some() { + panic!("Answer already contains a value"); + } + *inner = Some(item); + self.wake.give(); + } + + /// Synchronously wait for an Answer. + /// + /// Blocks the current thread until an answer is available, returning it. + pub fn take(&self) -> T { + self.wake.take(Forever).expect("Forever returned early"); + self.item + .lock() + .expect("Get Mutex") + .take() + .expect("Answer should contain value") + } + + /// Asynchronously wait for an answer. + pub async fn take_async(&self) -> T { + self.wake + .take_async(Forever) + .await + .expect("Forever returnd early"); + self.item + .lock() + .expect("Get Mutex") + .take() + .expect("Answer should contain value") + } +} + +/// Build a combiner for Future and a Zephyr work queue. This encapsulates the idea of starting +/// a new thread of work, and is the basis of both the main `run` for work queues, as well as +/// any calls to spawn that happen within the Future world. +pub struct WorkBuilder { + queue: Option>, + // A name for this task, used by debugging and such. + name: Option<&'static CStr>, +} + +impl WorkBuilder { + /// Construct a new builder for work. + /// + /// The builder will default to running on the system workqueue. + pub fn new() -> Self { + Self { + queue: None, + name: None, + } + } + + /// Set the work queue for this worker to run on. + /// + /// By default, A Worker will run on the system work-queue. + pub fn set_worker(&mut self, worker: &WorkQueue) -> &mut Self { + self.queue = Some(NonNull::new(worker.item.get()).expect("work must not be null")); + self + } + + /// Set a name for this worker, for debugging. + pub fn set_name(&mut self, name: &'static CStr) -> &mut Self { + self.name = Some(name); + self + } + + /// Start this working, consuming the given Future to do the work. + /// + /// The work queue is in a pinned Arc to meet requirements of how Futures are used. The Arc + /// maintains lifetime while the worker is running. See notes below for issues of lifetimes + /// and canceled work. + pub fn start(&self, future: F) -> JoinHandle { + JoinHandle::new(self, future) + } + + /// Start this work, locally running on the current worker thread. + /// + /// This is the same as `start`, but the work will always be started on the current work queue + /// thread. This relaxes the `Send` requirement, as the data will always be contained in a + /// single thread. + /// + /// # Panics + /// + /// If called from other than a Future running on a work queue, will panic. The System work + /// queue is not yet supported. + pub fn start_local(&self, future: F) -> JoinHandle { + JoinHandle::new_local(self, future) + } +} + +/// A potentially running Work. +/// +/// This encapsulates a Future that is potentially running in the Zephyr work queue system. +/// +/// # Safety +/// +/// Once the worker has been started (meaning once WorkBuilder::start returns this `Work`), all +/// but one field here is owned by the worker itself (it runs on the worker thread, hence the +/// Send constraint). The exception is the 'answer' field which can be used by the caller to +/// wait for the Work to finish. +pub struct JoinHandle { + /// The answer will be placed here. This Arc holds a strong reference, and if the spawning + /// thread doesn't hold the `Work`, it will be dropped. + answer: Arc>, +} + +impl JoinHandle { + /// Construct new [`JoinHandle`] that runs on a specified [`WorkQueue`]. + fn new(builder: &WorkBuilder, future: F) -> Self { + // Answer holds the result when the work finishes. + let answer = Arc::new(Answer::new()); + + let work = WorkData::new( + future, + Arc::downgrade(&answer), + builder.queue, + builder.name, + ); + WorkData::submit(work).expect("Unable to enqueue worker"); + + Self { answer } + } +} + +impl JoinHandle { + /// Construct a new [`JoinHandle`] that runs on the current [`WorkQueue`]. + /// + /// # Panics + /// + /// If `new_local` is called from a context other than running within a worker defined in this + /// crate, it will panic. + /// + /// Note that currently, the system workq is not considered a worked defined in this crate. + fn new_local(builder: &WorkBuilder, future: F) -> Self { + let workq = get_current_workq().expect("Called new_local not from worker"); + let answer = Arc::new(Answer::new()); + + let work = WorkData::new( + future, + Arc::downgrade(&answer), + Some(NonNull::new(workq).unwrap()), + builder.name, + ); + WorkData::submit(work).expect("Unable to enqueue worker"); + + Self { answer } + } +} + +impl JoinHandle { + /// Synchronously wait for this future to have an answer. + pub fn join(&self) -> F::Output { + self.answer.take() + } + + /// Asynchronously wait for this future to have an answer. + pub async fn join_async(&self) -> F::Output { + self.answer.take_async().await + } +} + +/// Futures will need to be able to set the events and timeout of this waker. Because the Waker is +/// parameterized, they will not have access to the whole WorkWaker, but only this WakeInfo. +pub struct WakeInfo { + /// The work queue to submit this work to. None indicates the system workq. + pub(crate) queue: Option>, + /// Events to use for our next wakeup. Currently cleared before calling the future (although + /// this discards the wakeup reason, so needs to be fixed). + pub events: EventArray, + /// Timeout to use for the next wakeup. Will be set to Forever before calling the Future's + /// poll. + pub timeout: Timeout, + /// A Context to use for invoking workers. This `WakeInfo` can be recovered from this context. + /// Note that our contexts are `'static` as they are maintained inside of the worker. + pub context: Context<'static>, +} + +impl WakeInfo { + /// Recover the WakeInfo from a given context. + /// + /// # Safety + /// + /// Although the lifetime of Context is `'static`, the generic type passed to `Future` does not + /// specify a lifetime. As such, it is not possible for the future to store the Context, and + /// rescheduling must be specified before this Future invocation returns. + /// + /// This does assume we are only using the Zephyr scheduler. The Context does have an any-based + /// data pointer mechanism, but it is nightly. This recovery would be easier using that + /// mechanism. + pub unsafe fn from_context<'b>(context: &'b mut Context) -> &'b mut Self { + // SAFETY: We're doing pointer arithmetic to recover Self from a reference to the embedded + // context. The 'mut' is preserved to keep the rules of mut in Rust. + unsafe { + let this: *mut Context = context; + let this = this + .cast::() + .sub(mem::offset_of!(Self, context)) + .cast::(); + &mut *this + } + } + + /// Add an event that represents waiting for a semaphore to be available for "take". + pub unsafe fn add_semaphore<'a>(&'a mut self, sem: &'a Semaphore) { + // SAFETY: Fill with zeroed memory, initializatuon happens in the init function next. + self.events.push(unsafe { mem::zeroed() }); + let ev = self.events.last().unwrap(); + + unsafe { + k_poll_event_init( + ev.get(), + ZR_POLL_TYPE_SEM_AVAILABLE, + k_poll_modes_K_POLL_MODE_NOTIFY_ONLY as i32, + sem.item.get() as *mut c_void, + ); + } + } + + /// Add an event that represents waiting for a signal. + pub unsafe fn add_signal<'a>(&'a mut self, signal: &'a Signal) { + // SAFETY: Fill with zeroed memory, initializatuon happens in the init function next. + self.events.push(unsafe { mem::zeroed() }); + let ev = self.events.last().unwrap(); + + unsafe { + k_poll_event_init( + ev.get(), + ZR_POLL_TYPE_SIGNAL, + k_poll_modes_K_POLL_MODE_NOTIFY_ONLY as i32, + signal.item.get() as *mut c_void, + ); + } + } + + /// Add an event that represents waiting for a queue to have a message. + pub unsafe fn add_queue<'a>(&'a mut self, queue: &'a Queue) { + // SAFETY: Fill with zeroed memory, initializatuon happens in the init function next. + self.events.push(unsafe { mem::zeroed() }); + let ev = self.events.last().unwrap(); + + unsafe { + k_poll_event_init( + ev.get(), + ZR_POLL_TYPE_DATA_AVAILABLE, + k_poll_modes_K_POLL_MODE_NOTIFY_ONLY as i32, + queue.item.get() as *mut c_void, + ); + } + } +} + +/// The worker-owned information about that worker. +/// +/// This holds a single worker, and will be owned by that worker itself. +struct WorkData { + /// Info needed to reschedule the work. + info: WakeInfo, + /// The Zephyr worker. This struct is allocated in a Box, and only used by the worker thread, + /// so it is easy to recover. The UnsafeCell is to indicate that Zephyr is free to mutate the + /// work. + work: UnsafeCell, + /// Where the answer is placed. This is weak because the spawning thread may not be interested + /// in the result, which will drop the only reference to the Arc, breaking the weak reference. + answer: Weak>, + /// The future that is running this work. + future: F, +} + +// SAFETY: The worker struct is explicitly safe to send by the Zephyr docs. +// unsafe impl Send for WorkData {} + +impl WorkData { + /// Build a new WorkWaker around the given future. The weak reference to the answer is where + /// the answer is stored if the task spawner is still interested in the answer. + fn new( + future: F, + answer: Weak>, + queue: Option>, + name: Option<&'static CStr>, + ) -> Pin> { + // name is only used for SystemView debugging, so prevent a warning when that is not + // enabled. + let _ = name; + + let this = Box::pin(Self { + // SAFETY: This will be initialized below, once the Box allocates and the memory won't + // move. + work: unsafe { mem::zeroed() }, + future, + answer, + info: WakeInfo { + queue, + events: EventArray::new(), + // Initial timeout is NoWait so work starts as soon as submitted. + timeout: NoWait.into(), + context: Context::from_waker(&VOID_WAKER), + }, + }); + + unsafe { + // SAFETY: The above Arc allocates the worker. The code here is careful to not move it. + k_work_poll_init(this.work.get(), Some(Self::handler)); + } + + this + } + + /// Submit this work to the Zephyr work queue. This consumes the Box, with the primary owner + /// being the work thread itself. Not that canceling work will leak the worker. + fn submit(mut this: Pin>) -> crate::Result { + // SAFETY: This is unsafe because the pointer lose the Pin guarantee, but C code will not + // move it. + let this_ref = unsafe { + Pin::get_unchecked_mut(this.as_mut()) + }; + + let result = if let Some(queue) = this_ref.info.queue { + unsafe { + // SAFETY: We're transferring ownership of the box to the enqueued work. For + // regular re-submission as the worker runs, the worker won't be run until this + // method exits. For initial creation, there is a possible period where our + // reference here survives while the worker is schedule (when the work queue is + // higher priority than this. I'm not sure if this fully followes the rules, as + // there is still a reference to this here, but as long as we only use it to leak + // the box, I believe we are safe. If this is deemed unsafe, these values could be + // copied to variables and the box leaked before we enqueue. + k_work_poll_submit_to_queue( + queue.as_ptr(), + this_ref.work.get(), + this_ref.info.events.as_mut_ptr() as *mut k_poll_event, + this.info.events.len() as c_int, + this.info.timeout.0, + ) + } + } else { + unsafe { + // SAFETY: See above, safety here is the same. + k_work_poll_submit( + this_ref.work.get(), + this_ref.info.events.as_mut_ptr() as *mut k_poll_event, + this_ref.info.events.len() as c_int, + this_ref.info.timeout.0, + ) + } + }; + + // The Box has been handed to C. Consume the box, leaking the value. We use `into_raw` as + // it is the raw pointer we will be recovering the Box with when the worker runs. + let _ = Self::into_raw(this); + + match result { + 0 => Ok(SubmitResult::Enqueued), + code => panic!("Unexpected result from work poll submit: {}", code), + } + } + + /// The work callback, coming from the Zephyr C world. The box was into_raw(), We recover the + /// WorkWaker by using container_of and recovering it back into a box, which we will leak when + /// we re-submit it. + extern "C" fn handler(work: *mut k_work) { + // Note that we want to avoid needing a `repr(C)` on our struct, so the k_work pointer is + // not necessarily at the beginning of the struct. + let mut this = unsafe { Self::from_raw(work) }; + + let this_ref = unsafe { + Pin::get_unchecked_mut(this.as_mut()) + }; + + // Set the next work to Forever, with no events. TODO: This prevents the next poll from + // being able to determine the reason for the wakeup. + this_ref.info.events.clear(); + this_ref.info.timeout = Forever.into(); + + // SAFETY: poll requires the pointer to be pinned, in case that is needed. We rely on the + // Boxing of the pointer, and that our code does not move the future. + let future = unsafe { Pin::new_unchecked(&mut this_ref.future) }; + match future.poll(&mut this_ref.info.context) { + Poll::Pending => { + // With pending, use the timeout and events to schedule ourselves to do more work. + // TODO: If we want to support a real Waker, this would need to detect that, and + // schedule a possible wake on this no wake case. + // Currently, this check is only testing that something is missed, and is really + // more of a debug assertion. + if this.info.events.is_empty() && this.info.timeout == Forever.into() { + printkln!("Warning: worker scheduled to never wake up"); + } + + // The re-submission will give ownership of the box back to the scheduled work. + Self::submit(this).expect("Unable to schedule work"); + } + Poll::Ready(answer) => { + // If the spawning task is still interested in the answer, provide it. + if let Some(store) = this.answer.upgrade() { + store.place(answer); + } + + // Work is finished, so allow the Box to be dropped. + } + } + } + + /// Consume the pinned box containing Self, and return the internal pointer. + fn into_raw(this: Pin>) -> *mut Self { + // SAFETY: This removes the Pin guarantee, but is given as a raw pointer to C, which doesn't + // generally use move. + let this = unsafe { Pin::into_inner_unchecked(this) }; + Box::into_raw(this) + } + + /// Given a pointer to the work_q burried within, recover the Pinned Box containing our data. + unsafe fn from_raw(ptr: *mut k_work) -> Pin> { + // SAFETY: This fixes the pointer back to the beginning of Self. This also assumes the + // pointer is valid. + let ptr = ptr + .cast::() + .sub(mem::offset_of!(k_work_poll, work)) + .sub(mem::offset_of!(Self, work)) + .cast::(); + let this = Box::from_raw(ptr); + Pin::new_unchecked(this) + } +} + +/// A VoidWaker is used when we don't use the Waker mechanism. There is no data associated with +/// this waker, and it panics if anyone tries to clone it or use it to wake a task. +/// This is static to simplify lifetimes. +static VOID_WAKER: Waker = unsafe { + Waker::from_raw(RawWaker::new( + ptr::null(), + &RawWakerVTable::new(void_clone, void_wake, void_wake_by_ref, void_drop), + )) +}; + +/// Void clone operation. Panics for now. If we want to implement a real waker, this will need +/// to be managed. +unsafe fn void_clone(_: *const ()) -> RawWaker { + panic!("Zephyr Wakers not yet supported for general 'Waker' use"); +} + +/// Void wake operation. Panics for now. If we want to implement a real waker, this will need +/// to be managed. +unsafe fn void_wake(_: *const ()) { + panic!("Zephyr Wakers not yet supported for general 'Waker' use"); +} + +/// Void wake_by_ref operation. Panics for now. If we want to implement a real waker, this will need +/// to be managed. +unsafe fn void_wake_by_ref(_: *const ()) { + panic!("Zephyr Wakers not yet supported for general 'Waker' use"); +} + +/// The void drop will be called when the Context is dropped after the first invocation. Because +/// clone above panics, we know there aren't references hanging around. So, it is safe to just +/// do nothing. +unsafe fn void_drop(_: *const ()) {} +/// To avoid having to parameterize everything, we limit the size of the ArrayVec of events to +/// this amount. The amount needed her depends on overall use, but so far, 1 is sufficient. +type EventArray = ArrayVec, 1>; + +/// Async sleep. +pub fn sleep(duration: Duration) -> Sleep { + Sleep { + ticks_left: duration.ticks(), + } +} + +/// A future that sleeps for a while. +pub struct Sleep { + // How much time is left. TODO: Change this into an absolute sleep once we have the ability to + // determine why were were scheduled. + ticks_left: Tick, +} + +impl Future for Sleep { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // If the sleep is done, so are we. + if self.ticks_left == 0 { + return Poll::Ready(()); + } + + // Otherwise, queue outselves back. + let this = unsafe { WakeInfo::from_context(cx) }; + + this.timeout = Duration::from_ticks(self.ticks_left).into(); + self.ticks_left = 0; + + Poll::Pending + } +} From c89b2b1a68ab4c9085408bcc2b348ffe66478609 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 7 Jan 2025 09:50:40 -0700 Subject: [PATCH 33/65] zephyr: work: Support showing work in Segger SystemView If `CONFIG_SEGGER_SYSVIEW` is enabled, add a few calls to the workqueue scheduling to record these as task switches. There is still a problem with the task names showing up, but this does generally show scheduled work as separate tasks in System View. The main issues where this lacks is that if the Zephyr scheduler schedules something else, and then returns to the work queue, that will be shown as being done on the work queue thread instead of the particular work. Signed-off-by: David Brown --- zephyr-sys/build.rs | 1 + zephyr/src/work/futures.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index 842c21e3..091d773c 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -82,6 +82,7 @@ fn main() -> Result<()> { .allowlist_function("sys_.*") .allowlist_function("z_log.*") .allowlist_function("bt_.*") + .allowlist_function("SEGGER.*") .allowlist_item("E.*") .allowlist_item("K_.*") .allowlist_item("ZR_.*") diff --git a/zephyr/src/work/futures.rs b/zephyr/src/work/futures.rs index ed085f31..b8ff3a4a 100644 --- a/zephyr/src/work/futures.rs +++ b/zephyr/src/work/futures.rs @@ -384,6 +384,23 @@ impl WorkData { unsafe { // SAFETY: The above Arc allocates the worker. The code here is careful to not move it. k_work_poll_init(this.work.get(), Some(Self::handler)); + + // If we have a name, send it to Segger. + #[cfg(CONFIG_SEGGER_SYSTEMVIEW)] + { + let ww = &(&*this.work.get()).work; + if let Some(name) = name { + let info = crate::raw::SEGGER_SYSVIEW_TASKINFO { + TaskID: this.work.get() as ::core::ffi::c_ulong, + sName: name.as_ptr(), + Prio: 1, + StackBase: 0, + StackSize: 32, + }; + crate::raw::SEGGER_SYSVIEW_OnTaskCreate(this.work.get() as ::core::ffi::c_ulong); + crate::raw::SEGGER_SYSVIEW_SendTaskInfo(&info); + } + } } this @@ -458,8 +475,16 @@ impl WorkData { // SAFETY: poll requires the pointer to be pinned, in case that is needed. We rely on the // Boxing of the pointer, and that our code does not move the future. let future = unsafe { Pin::new_unchecked(&mut this_ref.future) }; + #[cfg(CONFIG_SEGGER_SYSTEMVIEW)] + unsafe { + crate::raw::SEGGER_SYSVIEW_OnTaskStartExec(work as u32); + } match future.poll(&mut this_ref.info.context) { Poll::Pending => { + #[cfg(CONFIG_SEGGER_SYSTEMVIEW)] + unsafe { + crate::raw::SEGGER_SYSVIEW_OnTaskStopExec(); + } // With pending, use the timeout and events to schedule ourselves to do more work. // TODO: If we want to support a real Waker, this would need to detect that, and // schedule a possible wake on this no wake case. @@ -473,6 +498,11 @@ impl WorkData { Self::submit(this).expect("Unable to schedule work"); } Poll::Ready(answer) => { + #[cfg(CONFIG_SEGGER_SYSTEMVIEW)] + unsafe { + crate::raw::SEGGER_SYSVIEW_OnTaskStopExec(); + } + // TODO: Delete the task as well. // If the spawning task is still interested in the answer, provide it. if let Some(store) = this.answer.upgrade() { store.place(answer); From 2a9c50a2a46667072430e377e00912a3b03273c6 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 7 Jan 2025 09:53:25 -0700 Subject: [PATCH 34/65] samples: work-philosophers This is intended to be a demonstration of implementing the dining philosopher's problem using work queues. At this point, it just serves as a test of the workqueue and async mechanism. Signed-off-by: David Brown --- samples/work-philosophers/CMakeLists.txt | 8 + samples/work-philosophers/Cargo.toml | 25 ++ samples/work-philosophers/Kconfig | 56 ++++ .../work-philosophers/boards/rpi_pico.conf | 7 + samples/work-philosophers/build.rs | 9 + samples/work-philosophers/prj.conf | 20 ++ samples/work-philosophers/sample.yaml | 15 ++ samples/work-philosophers/src/lib.rs | 254 ++++++++++++++++++ 8 files changed, 394 insertions(+) create mode 100644 samples/work-philosophers/CMakeLists.txt create mode 100644 samples/work-philosophers/Cargo.toml create mode 100644 samples/work-philosophers/Kconfig create mode 100644 samples/work-philosophers/boards/rpi_pico.conf create mode 100644 samples/work-philosophers/build.rs create mode 100644 samples/work-philosophers/prj.conf create mode 100644 samples/work-philosophers/sample.yaml create mode 100644 samples/work-philosophers/src/lib.rs diff --git a/samples/work-philosophers/CMakeLists.txt b/samples/work-philosophers/CMakeLists.txt new file mode 100644 index 00000000..e118b2c3 --- /dev/null +++ b/samples/work-philosophers/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(work_philosophers) + +rust_cargo_application() diff --git a/samples/work-philosophers/Cargo.toml b/samples/work-philosophers/Cargo.toml new file mode 100644 index 00000000..af2dcd45 --- /dev/null +++ b/samples/work-philosophers/Cargo.toml @@ -0,0 +1,25 @@ +# 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 = "0.1.0" + +# Dependencies that are used by build.rs. +[build-dependencies] +zephyr-build = "0.1.0" + +[profile.release] +debug-assertions = true +overflow-checks = true +debug = true diff --git a/samples/work-philosophers/Kconfig b/samples/work-philosophers/Kconfig new file mode 100644 index 00000000..d8ffa99d --- /dev/null +++ b/samples/work-philosophers/Kconfig @@ -0,0 +1,56 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "Rust Dining Philosphers" + +source "Kconfig.zephyr" + +choice + prompt "Select Synchronization implementation" + default SYNC_CHANNEL + + config SYNC_SYS_SEMAPHORE + bool "Use sys::Semaphore to synchronize forks" + help + Use to have the dining philosophers sample use sys::Semaphore, with one per fork, to + synchronize. + + config SYNC_SYS_DYNAMIC_SEMAPHORE + bool "Use a dynamic sys::Semaphore to synchronize forks" + help + Use to have the dining philosophers sample use sys::Semaphore, with one per fork, to + synchronize. The Semaphores will be dynamically allocated. + + config SYNC_SYS_MUTEX + bool "Use sys::Semaphore to synchronize forks" + help + Use to have the dining philosophers sample use sys::Mutex, with one per fork, to + synchronize. + + config SYNC_CONDVAR + bool "Use sync::Condvar and sync::Mutex to synchronize forks" + help + Use to have the dining philosophers sample use a single data structure, protected + by a sync::Mutex and coordinated with a sync::Condvar, to synchronize. + + config SYNC_CHANNEL + bool "Use sync::channel to synchronize forks" + help + Use to have the dining philosophers sample use a worker thread, communicating via + channels to synchronize. + + config SYNC_WORKQUEUE + bool "Use workqueues to simulate the philosophers" + help + Use workqueues to simulate the philosophers. + +endchoice + +if SYNC_CHANNEL + config USE_BOUNDED_CHANNELS + bool "Should channel sync use bounded channels?" + default y + help + If set, the channel-based communication will use bounded channels with bounds calculated + to not ever block. +endif diff --git a/samples/work-philosophers/boards/rpi_pico.conf b/samples/work-philosophers/boards/rpi_pico.conf new file mode 100644 index 00000000..94c0843a --- /dev/null +++ b/samples/work-philosophers/boards/rpi_pico.conf @@ -0,0 +1,7 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +# This board doesn't have a serial console, so use RTT. +CONFIG_UART_CONSOLE=n +CONFIG_RTT_CONSOLE=y +CONFIG_USE_SEGGER_RTT=y diff --git a/samples/work-philosophers/build.rs b/samples/work-philosophers/build.rs new file mode 100644 index 00000000..22233f15 --- /dev/null +++ b/samples/work-philosophers/build.rs @@ -0,0 +1,9 @@ +// Copyright (c) 2023 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +// This crate needs access to kconfig variables. This is an example of how to do that. The +// zephyr-build must be a build dependency. + +fn main() { + zephyr_build::export_bool_kconfig(); +} diff --git a/samples/work-philosophers/prj.conf b/samples/work-philosophers/prj.conf new file mode 100644 index 00000000..c53536c4 --- /dev/null +++ b/samples/work-philosophers/prj.conf @@ -0,0 +1,20 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_RUST=y +CONFIG_RUST_ALLOC=y +CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 + +CONFIG_POLL=y + +# CONFIG_DEBUG=y + +# CONFIG_USERSPACE=y + +# Some debugging +CONFIG_THREAD_MONITOR=y +CONFIG_THREAD_ANALYZER=y +CONFIG_THREAD_ANALYZER_USE_PRINTK=y +CONFIG_THREAD_ANALYZER_AUTO=n +# CONFIG_THREAD_ANALYZER_AUTO_INTERVAL=15 diff --git a/samples/work-philosophers/sample.yaml b/samples/work-philosophers/sample.yaml new file mode 100644 index 00000000..a4235fe7 --- /dev/null +++ b/samples/work-philosophers/sample.yaml @@ -0,0 +1,15 @@ +sample: + description: Philosphers, in Rust + name: workq philosophers rust +common: + harness: console + harness_config: + type: one_line + regex: + - "All threads done" + tags: rust + filter: CONFIG_RUST_SUPPORTED +tests: + sample.rust.philosopher: + tags: introduction + min_ram: 32 diff --git a/samples/work-philosophers/src/lib.rs b/samples/work-philosophers/src/lib.rs new file mode 100644 index 00000000..095109f6 --- /dev/null +++ b/samples/work-philosophers/src/lib.rs @@ -0,0 +1,254 @@ +// Copyright (c) 2023 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +// Cargo tries to detect configs that have typos in them. Unfortunately, the Zephyr Kconfig system +// uses a large number of Kconfigs and there is no easy way to know which ones might conceivably be +// valid. This prevents a warning about each cfg that is used. +#![allow(unexpected_cfgs)] + +extern crate alloc; + +use alloc::{boxed::Box, ffi::CString, format}; +use zephyr::{kobj_define, printkln, sync::Arc, sys::sync::Semaphore, time::{Duration, Forever}, work::{futures::sleep, Signal, WorkQueue, WorkQueueBuilder}}; + +/// How many philosophers. There will be the same number of forks. +const _NUM_PHIL: usize = 6; + +/// Size of the stack for the work queue. +const WORK_STACK_SIZE: usize = 2048; + +// The dining philosophers problem is a simple example of cooperation between multiple threads. +// This implementation use one of several different underlying mechanism to support this cooperation. + +// This example uses dynamic dispatch to allow multiple implementations. The intent is to be able +// to periodically shut down all of the philosphers and start them up with a differernt sync +// mechanism. This isn't implemented yet. + +/// The philosophers use a fork synchronization mechanism. Essentially, this is 6 locks, and will be +/// implemented in a few different ways to demonstrate/test different mechanmism in Rust. All of +/// them implement The ForkSync trait which provides this mechanism. +/* +trait ForkSync: core::fmt::Debug + Sync + Send { + /// Take the given fork. The are indexed the same as the philosopher index number. This will + /// block until the fork is released. + fn take(&self, index: usize); + + /// Release the given fork. Index is the same as take. + fn release(&self, index: usize); +} +*/ + +#[no_mangle] +extern "C" fn rust_main() { + printkln!("Hello world from Rust on {}", + zephyr::kconfig::CONFIG_BOARD); + printkln!("Time tick: {}", zephyr::time::SYS_FREQUENCY); + + // Create the work queue to run this. + let worker = Box::new(WorkQueueBuilder::new() + .set_priority(1) + .start(WORK_STACK.init_once(()).unwrap())); + + // In addition, create a lower priority worker. + let lower_worker = Arc::new(WorkQueueBuilder::new() + .set_priority(5) + .start(LOWER_WORK_STACK.init_once(()).unwrap())); + + // It is important that work queues are not dropped, as they are persistent objects in the + // Zephyr world. + let _ = Arc::into_raw(lower_worker.clone()); + + // Create a semaphore the phil thread can wait for, part way through its work. + let sem = Arc::new(Semaphore::new(0, 1).unwrap()); + + let th = phil_thread(0, sem.clone(), lower_worker.clone()); + printkln!("Size of phil thread: {}", core::mem::size_of_val(&th)); + // TODO: How to do as much on the stack as we can, for now, don't worry too much. + // let mut th = pin!(th); + + let th = zephyr::kio::spawn(th, &worker, c"phil-worker"); + + // As we no longer use the worker, it is important that it never be dropped. Leaking the box + // ensures that it will not be deallocated. + Box::leak(worker); + + // Sleep a bit to allow the worker to run to where it is waiting. + zephyr::time::sleep(Duration::millis_at_least(1_000)); + printkln!("Giving the semaphore"); + sem.give(); + + let result = th.join(); + printkln!("th result: {:?}", result); + + /* + // TODO: The allocated Arc doesn't work on all Zephyr platforms, but need to make our own copy + // of alloc::task + let waker = Arc::new(PWaker).into(); + let mut cx = Context::from_waker(&waker); + + // Run the future to completion. + loop { + match th.as_mut().poll(&mut cx) { + Poll::Pending => todo!(), + Poll::Ready(_) => break, + } + } + */ + printkln!("All threads done"); + + /* + let stats = Arc::new(Mutex::new_from(Stats::default(), STAT_MUTEX.init_once(()).unwrap())); + + let syncers = get_syncer(); + + printkln!("Pre fork"); + + for (i, syncer) in (0..NUM_PHIL).zip(syncers.into_iter()) { + let child_stat = stats.clone(); + let thread = PHIL_THREADS[i].init_once(PHIL_STACKS[i].init_once(()).unwrap()).unwrap(); + thread.spawn(move || { + phil_thread(i, syncer, child_stat); + }); + } + + let delay = Duration::secs_at_least(10); + loop { + // Periodically, printout the stats. + zephyr::time::sleep(delay); + stats.lock().unwrap().show(); + } + */ +} + +async fn phil_thread(n: usize, sem: Arc, lower_thread: Arc) -> usize { + printkln!("Child {} started", n); + show_it(&sem).await; + sleep(Duration::millis_at_least(1000)).await; + printkln!("Child {} done sleeping", n); + + // Lastly fire off something on the other worker thread. + let sig = Arc::new(Signal::new().unwrap()); + let sig2 = sig.clone(); + + // To help with debugging, we can name these, but it must be a `&'static Cstr`. We'll build a + // CString (on the heap), and then leak it to be owned by the work. The leaking while keeping + // the `&'static CStr` is a bit messy. + let name = CString::new(format!("phil-{}", n)).unwrap(); + let name = Box::leak(name.into_boxed_c_str()); + + // It _should_ be safe to drop the child, as it uses an internal clone of the Arc to keep it + // around as long as there is a worker. + let _child = Box::new(zephyr::kio::spawn(async move { + sleep(Duration::millis_at_least(800)).await; + sig2.raise(12345).unwrap(); + }, &lower_thread, name)); + + // Wait for the signal. + let num = sig.wait_async(Forever).await.unwrap(); + printkln!("Signaled value: {}", num); + + 42 +} + +async fn show_it(sem: &Semaphore) { + for i in 0..10 { + sleep(Duration::millis_at_least(1)).await; + printkln!("Tick: {i}"); + + // Wait after 5 for the semaphore to be signaled. + if i == 4 { + printkln!("Waiting for semaphore"); + sem.take_async(Forever).await.unwrap(); + printkln!("Done waiting"); + } + } +} + +kobj_define! { + static WORK_STACK: ThreadStack; + static LOWER_WORK_STACK: ThreadStack; +} + +/* +fn phil_thread(n: usize, syncer: Arc, stats: Arc>) { + printkln!("Child {} started: {:?}", n, syncer); + + // Determine our two forks. + let forks = if n == NUM_PHIL - 1 { + // Per Dijkstra, the last phyilosopher needs to reverse forks, or we deadlock. + (0, n) + } else { + (n, n+1) + }; + + loop { + { + // printkln!("Child {} hungry", n); + // printkln!("Child {} take left fork", n); + syncer.take(forks.0); + // printkln!("Child {} take right fork", n); + syncer.take(forks.1); + + let delay = get_random_delay(n, 25); + // printkln!("Child {} eating ({} ms)", n, delay); + sleep(delay); + stats.lock().unwrap().record_eat(n, delay); + + // Release the forks. + // printkln!("Child {} giving up forks", n); + syncer.release(forks.1); + syncer.release(forks.0); + + let delay = get_random_delay(n, 25); + // printkln!("Child {} thinking ({} ms)", n, delay); + sleep(delay); + stats.lock().unwrap().record_think(n, delay); + } + } +} + +/// Get a random delay, based on the ID of this user, and the current uptime. +fn get_random_delay(id: usize, period: usize) -> Duration { + let tick = (uptime_get() & (usize::MAX as i64)) as usize; + let delay = (tick / 100 * (id + 1)) & 0x1f; + + // Use one greater to be sure to never get a delay of zero. + Duration::millis_at_least(((delay + 1) * period) as Tick) +} + +/// Instead of just printint out so much information that the data just scolls by, gather +/// statistics. +#[derive(Default)] +struct Stats { + /// How many times each philosopher has gone through the loop. + count: [u64; NUM_PHIL], + /// How much time each philosopher has spent eating. + eating: [u64; NUM_PHIL], + /// How much time each philosopher has spent thinking. + thinking: [u64; NUM_PHIL], +} + +impl Stats { + fn record_eat(&mut self, index: usize, time: Duration) { + self.eating[index] += time.to_millis(); + } + + fn record_think(&mut self, index: usize, time: Duration) { + self.thinking[index] += time.to_millis(); + self.count[index] += 1; + } + + fn show(&self) { + printkln!("c:{:?}, e:{:?}, t:{:?}", self.count, self.eating, self.thinking); + } +} + +kobj_define! { + static PHIL_THREADS: [StaticThread; NUM_PHIL]; + static PHIL_STACKS: [ThreadStack; NUM_PHIL]; + + static STAT_MUTEX: StaticMutex; +} +*/ From 3745c1236fbef0d77510f63858a3f38d814eff81 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 7 Jan 2025 13:52:06 -0700 Subject: [PATCH 35/65] zephyr: Add numerous missing conditionals around alloc These are starting to get messy, but for now, add to at least allow code to compile with alloc off. Signed-off-by: David Brown --- zephyr/src/lib.rs | 1 + zephyr/src/sys/sync/semaphore.rs | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/zephyr/src/lib.rs b/zephyr/src/lib.rs index 4106aefe..05c60fd0 100644 --- a/zephyr/src/lib.rs +++ b/zephyr/src/lib.rs @@ -15,6 +15,7 @@ pub mod device; pub mod error; pub mod logging; pub mod object; +#[cfg(CONFIG_RUST_ALLOC)] pub mod simpletls; pub mod sync; pub mod sys; diff --git a/zephyr/src/sys/sync/semaphore.rs b/zephyr/src/sys/sync/semaphore.rs index 16016ff6..55edc9bd 100644 --- a/zephyr/src/sys/sync/semaphore.rs +++ b/zephyr/src/sys/sync/semaphore.rs @@ -11,16 +11,23 @@ //! operation, which in situation where counting is actually desired, will result in the count being //! incorrect. +#[cfg(CONFIG_RUST_ALLOC)] use core::pin::Pin; +#[cfg(CONFIG_RUST_ALLOC)] use core::task::{Context, Poll}; -use core::{ffi::c_uint, future::Future}; +#[cfg(CONFIG_RUST_ALLOC)] +use core::future::Future; +use core::ffi::c_uint; use core::fmt; #[cfg(CONFIG_RUST_ALLOC)] use core::mem; +#[cfg(CONFIG_RUST_ALLOC)] use zephyr_sys::ETIMEDOUT; +#[cfg(CONFIG_RUST_ALLOC)] use crate::time::NoWait; +#[cfg(CONFIG_RUST_ALLOC)] use crate::work::futures::WakeInfo; use crate::{ error::{to_result_void, Result}, @@ -77,6 +84,7 @@ impl Semaphore { /// Take a semaphore, async version. /// /// Returns a future that either waits for the semaphore, or returns status. + #[cfg(CONFIG_RUST_ALLOC)] pub fn take_async<'a>(&'a self, timeout: impl Into) -> impl Future> + 'a { SemTake { sem: self, @@ -118,6 +126,7 @@ impl Semaphore { } /// The async 'take' Future +#[cfg(CONFIG_RUST_ALLOC)] struct SemTake<'a> { /// The semaphore we're waiting on. sem: &'a Semaphore, @@ -127,6 +136,7 @@ struct SemTake<'a> { ran: bool, } +#[cfg(CONFIG_RUST_ALLOC)] impl<'a> Future for SemTake<'a> { type Output = Result<()>; From a148a8c6013454a65cc5fe882c9752c96fc9c459 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 7 Jan 2025 14:57:06 -0700 Subject: [PATCH 36/65] zephyr: work: Create ContextExt Because Zephyr's workqueue re-scheduling is based on the Context struct, not the waker, we need a way to get back to our `WorkInfo` struct that this Context is embedded in. Create a `ContextExt` that adds some methods to the `Context` to be able to indicate when the scheduler should reschedule this work. Signed-off-by: David Brown --- zephyr/src/kio.rs | 73 +++++++++++++++++++++++++++++++- zephyr/src/sync/channel.rs | 14 ++---- zephyr/src/sys/sync/semaphore.rs | 11 +---- zephyr/src/work.rs | 11 +---- zephyr/src/work/futures.rs | 6 +-- 5 files changed, 81 insertions(+), 34 deletions(-) diff --git a/zephyr/src/kio.rs b/zephyr/src/kio.rs index 40f14b93..3bd823f2 100644 --- a/zephyr/src/kio.rs +++ b/zephyr/src/kio.rs @@ -8,15 +8,20 @@ //! [`futures`]: crate::work::futures use core::ffi::CStr; -use core::task::Poll; +use core::task::{Context, Poll}; use core::{future::Future, pin::Pin}; -use crate::time::NoWait; +use crate::sys::queue::Queue; +use crate::sys::sync::Semaphore; +use crate::time::{NoWait, Timeout}; use crate::work::futures::WakeInfo; +use crate::work::Signal; use crate::work::{futures::JoinHandle, futures::WorkBuilder, WorkQueue}; pub mod sync; +pub use crate::work::futures::sleep; + /// Run an async future on the given worker thread. /// /// Arrange to have the given future run on the given worker thread. The resulting `JoinHandle` has @@ -94,3 +99,67 @@ impl Future for YieldNow { } } } + +/// Extensions on [`Context`] to support scheduling via Zephyr's workqueue system. +/// +/// All of these are called from within the context of running work, and indicate what _next_ +/// should cause this work to be run again. If none of these methods are called before the work +/// exits, the work will be scheduled to run after `Forever`, which is not useful. There may be +/// later support for having a `Waker` that can schedule work from another context. +/// +/// Note that the events to wait on, such as Semaphores or channels, if there are multiple threads +/// that can wait for them, might cause this worker to run, but not actually be available. As such, +/// to maintain the non-blocking requirements of Work, [`Semaphore::take`], and the blocking `send` +/// and `recv` operations on channels should not be used, even after being woken. +/// +/// For the timeout [`Forever`] is useful to indicate there is no timeout. If called with +/// [`NoWait`], the work will be immediately scheduled. In general, it is better to query the +/// underlying object directly rather than have the overhead of being rescheduled. +/// +/// # Safety +/// +/// The lifetime bounds on the items waited for ensure that these items live at least as long as the +/// work queue. Practically, this can only be satisfied by using something with 'static' lifetime, +/// or embedding the value in the Future itself. +/// +/// With the Zephyr executor, the `Context` is embedded within a `WakeInfo` struct, which this makes +/// use of. If a different executor were to be used, these calls would result in undefined +/// behavior. +/// +/// This could be checked at runtime, but it would have runtime cost. +pub trait ContextExt { + /// Indicate the work should next be scheduled based on a semaphore being available for "take". + /// + /// The work will be scheduled either when the given semaphore becomes available to 'take', or + /// after the timeout. + fn add_semaphore<'a>(&'a mut self, sem: &'a Semaphore, timeout: impl Into); + + /// Indicate that the work should be scheduled after receiving the given [`Signal`], or the + /// timeout occurs. + fn add_signal<'a>(&'a mut self, signal: &'a Signal, timeout: impl Into); + + /// Indicate that the work should be scheduled when the given [`Queue`] has data available to + /// recv, or the timeout occurs. + fn add_queue<'a>(&'a mut self, queue: &'a Queue, timeout: impl Into); +} + +/// Implementation of ContextExt for the Rust [`Context`] type. +impl<'b> ContextExt for Context<'b> { + fn add_semaphore<'a>(&'a mut self, sem: &'a Semaphore, timeout: impl Into) { + let info = unsafe { WakeInfo::from_context(self) }; + info.add_semaphore(sem); + info.timeout = timeout.into(); + } + + fn add_signal<'a>(&'a mut self, signal: &'a Signal, timeout: impl Into) { + let info = unsafe { WakeInfo::from_context(self) }; + info.add_signal(signal); + info.timeout = timeout.into(); + } + + fn add_queue<'a>(&'a mut self, queue: &'a Queue, timeout: impl Into) { + let info = unsafe { WakeInfo::from_context(self) }; + info.add_queue(queue); + info.timeout = timeout.into(); + } +} diff --git a/zephyr/src/sync/channel.rs b/zephyr/src/sync/channel.rs index 360171b5..da49be86 100644 --- a/zephyr/src/sync/channel.rs +++ b/zephyr/src/sync/channel.rs @@ -51,9 +51,9 @@ use core::mem::MaybeUninit; use core::pin::Pin; use core::task::Poll; +use crate::kio::ContextExt; use crate::sys::queue::Queue; use crate::time::{Duration, Forever, NoWait, Timeout}; -use crate::work::futures::WakeInfo; mod counter; @@ -272,18 +272,14 @@ impl<'a, T: Unpin> Future for SendFuture<'a, T> { this.msg = Some(msg); // Otherwise, schedule to wake up on receipt or timeout. - let info = unsafe { WakeInfo::from_context(cx) }; match &this.sender.flavor { SenderFlavor::Unbounded { .. } => { panic!("Implementation error: unbounded queues should never fail"); } SenderFlavor::Bounded(chan) => { - unsafe { - info.add_queue(&chan.free); - } + cx.add_queue(&chan.free, this.timeout); } } - info.timeout = this.timeout; Poll::Pending } @@ -531,11 +527,7 @@ impl<'a, T> Future for RecvFuture<'a, T> { } // Otherwise, schedule to wakeup on receipt or timeout. - let info = unsafe { WakeInfo::from_context(cx) }; - unsafe { - info.add_queue(self.receiver.as_queue()); - } - info.timeout = self.timeout; + cx.add_queue(self.receiver.as_queue(), self.timeout); self.waited = true; Poll::Pending diff --git a/zephyr/src/sys/sync/semaphore.rs b/zephyr/src/sys/sync/semaphore.rs index 55edc9bd..85e7bdb4 100644 --- a/zephyr/src/sys/sync/semaphore.rs +++ b/zephyr/src/sys/sync/semaphore.rs @@ -25,10 +25,9 @@ use core::mem; #[cfg(CONFIG_RUST_ALLOC)] use zephyr_sys::ETIMEDOUT; +use crate::kio::ContextExt; #[cfg(CONFIG_RUST_ALLOC)] use crate::time::NoWait; -#[cfg(CONFIG_RUST_ALLOC)] -use crate::work::futures::WakeInfo; use crate::{ error::{to_result_void, Result}, object::{Fixed, StaticKernelObject, Wrapped}, @@ -153,13 +152,7 @@ impl<'a> Future for SemTake<'a> { } // TODO: Clean this up. - let info = unsafe { WakeInfo::from_context(cx) }; - unsafe { - // SAFETY: The semaphore must outlive the queued event. The lifetime ensures that the - // Future won't outlive the semaphore. - info.add_semaphore(self.sem); - } - info.timeout = self.timeout; + cx.add_semaphore(self.sem, self.timeout); self.ran = true; Poll::Pending diff --git a/zephyr/src/work.rs b/zephyr/src/work.rs index 3322b396..ae5c306e 100644 --- a/zephyr/src/work.rs +++ b/zephyr/src/work.rs @@ -179,7 +179,6 @@ use core::{ ptr, task::Poll, }; -use futures::WakeInfo; use zephyr_sys::{ k_poll_signal, k_poll_signal_check, k_poll_signal_init, k_poll_signal_raise, @@ -187,7 +186,7 @@ use zephyr_sys::{ k_work_queue_start, k_work_submit, k_work_submit_to_queue, ETIMEDOUT, }; -use crate::{error::to_result_void, object::Fixed, simpletls::StaticTls, sys::thread::ThreadStack, time::Timeout}; +use crate::{error::to_result_void, kio::ContextExt, object::Fixed, simpletls::StaticTls, sys::thread::ThreadStack, time::Timeout}; pub mod futures; @@ -485,13 +484,7 @@ impl<'a> Future for SignalWait<'a> { return Poll::Ready(Err(crate::Error(ETIMEDOUT))); } - let info = unsafe { WakeInfo::from_context(cx) }; - unsafe { - // SAFETY: The Signal must outlive the queued event. The lifetime ensure that the - // Future can't outlive the Signal. - info.add_signal(self.signal); - } - info.timeout = self.timeout; + cx.add_signal(self.signal, self.timeout); self.ran = true; Poll::Pending diff --git a/zephyr/src/work/futures.rs b/zephyr/src/work/futures.rs index b8ff3a4a..11bf8ad0 100644 --- a/zephyr/src/work/futures.rs +++ b/zephyr/src/work/futures.rs @@ -285,7 +285,7 @@ impl WakeInfo { } /// Add an event that represents waiting for a semaphore to be available for "take". - pub unsafe fn add_semaphore<'a>(&'a mut self, sem: &'a Semaphore) { + pub fn add_semaphore<'a>(&'a mut self, sem: &'a Semaphore) { // SAFETY: Fill with zeroed memory, initializatuon happens in the init function next. self.events.push(unsafe { mem::zeroed() }); let ev = self.events.last().unwrap(); @@ -301,7 +301,7 @@ impl WakeInfo { } /// Add an event that represents waiting for a signal. - pub unsafe fn add_signal<'a>(&'a mut self, signal: &'a Signal) { + pub fn add_signal<'a>(&'a mut self, signal: &'a Signal) { // SAFETY: Fill with zeroed memory, initializatuon happens in the init function next. self.events.push(unsafe { mem::zeroed() }); let ev = self.events.last().unwrap(); @@ -317,7 +317,7 @@ impl WakeInfo { } /// Add an event that represents waiting for a queue to have a message. - pub unsafe fn add_queue<'a>(&'a mut self, queue: &'a Queue) { + pub fn add_queue<'a>(&'a mut self, queue: &'a Queue) { // SAFETY: Fill with zeroed memory, initializatuon happens in the init function next. self.events.push(unsafe { mem::zeroed() }); let ev = self.events.last().unwrap(); From efe18d57c93cb1bf49d1a922ad1feefb0633fbf0 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 8 Jan 2025 16:15:59 -0700 Subject: [PATCH 37/65] zephyr: work: Fix broken doc links Fix various broken documentation links. Signed-off-by: David Brown --- zephyr/src/kio.rs | 2 ++ zephyr/src/sync/channel.rs | 2 ++ zephyr/src/work.rs | 10 ++++++++++ zephyr/src/work/futures.rs | 2 ++ 4 files changed, 16 insertions(+) diff --git a/zephyr/src/kio.rs b/zephyr/src/kio.rs index 3bd823f2..981ad3a4 100644 --- a/zephyr/src/kio.rs +++ b/zephyr/src/kio.rs @@ -127,6 +127,8 @@ impl Future for YieldNow { /// behavior. /// /// This could be checked at runtime, but it would have runtime cost. +/// +/// [`Forever`]: crate::time::Forever pub trait ContextExt { /// Indicate the work should next be scheduled based on a semaphore being available for "take". /// diff --git a/zephyr/src/sync/channel.rs b/zephyr/src/sync/channel.rs index da49be86..67cfc652 100644 --- a/zephyr/src/sync/channel.rs +++ b/zephyr/src/sync/channel.rs @@ -215,6 +215,8 @@ impl Sender { /// version. /// /// This has the same behavior as [`send_timeout`], but as an Async function. + /// + /// [`send_timeout`]: Sender::send_timeout pub fn send_timeout_async<'a>(&'a self, msg: T, timeout: impl Into) -> impl Future>> + 'a { diff --git a/zephyr/src/work.rs b/zephyr/src/work.rs index ae5c306e..4f78d39f 100644 --- a/zephyr/src/work.rs +++ b/zephyr/src/work.rs @@ -166,6 +166,14 @@ //! As such, this means that manually constructed work is still built using `Future`. The `_async` //! primitives throughout this crate can be used just as readily by hand-written Futures as by async //! code. Notable, the use of [`Signal`] will likely be common, along with possible timeouts. +//! +//! [`sys::sync::Semaphore`]: crate::sys::sync::Semaphore +//! [`sync::channel`]: crate::sync::channel +//! [`sync::Mutex`]: crate::sync::Mutex +//! [`kio::sync::Mutex`]: crate::kio::sync::Mutex +//! [`kio::spawn`]: crate::kio::spawn +//! [`join`]: futures::JoinHandle::join +//! [`join_async`]: futures::JoinHandle::join_async extern crate alloc; @@ -408,6 +416,8 @@ impl Signal { /// /// This reads the status of the signal. If the state is "signalled", this will return /// `Some(result)` where the `result` is the result value given to [`raise`]. + /// + /// [`raise`]: Self::raise pub fn check(&self) -> Option { let mut signaled: c_uint = 0; let mut result: c_int = 0; diff --git a/zephyr/src/work/futures.rs b/zephyr/src/work/futures.rs index 11bf8ad0..d2549be2 100644 --- a/zephyr/src/work/futures.rs +++ b/zephyr/src/work/futures.rs @@ -10,6 +10,8 @@ //! time the event is ready. However, work queues expect the work to be queued immediately, //! with a "poll" indicating what kind of even the work. Work will be scheduled either based on //! one of these events, or a timeout. +//! +//! [`SimpleAction`]: super::SimpleAction extern crate alloc; From d8295cf9a4859ab01c417c7660d77ef3a4294865 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 10 Jan 2025 10:13:27 -0700 Subject: [PATCH 38/65] zephyr: work: futures: Ensure JoinHandle is Send when it can be The join handle can bew used across multiple threads, as long as the output of the Future is Send. Signed-off-by: David Brown --- zephyr/src/work/futures.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/zephyr/src/work/futures.rs b/zephyr/src/work/futures.rs index d2549be2..7d556a5f 100644 --- a/zephyr/src/work/futures.rs +++ b/zephyr/src/work/futures.rs @@ -190,6 +190,15 @@ pub struct JoinHandle { answer: Arc>, } +// SAFETY: The join handle can be Send as long as the Output is send. It does not depend on the +// Future being send. +unsafe impl Send for JoinHandle +where + F: Future, + F::Output: Send +{ +} + impl JoinHandle { /// Construct new [`JoinHandle`] that runs on a specified [`WorkQueue`]. fn new(builder: &WorkBuilder, future: F) -> Self { From 82b5e12f7ff9a61f8a623c169b87229572b81939 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 10 Jan 2025 10:14:19 -0700 Subject: [PATCH 39/65] samples: work-philosphers: Async Semaphore based solution Implement the async Semaphore based solution to the philospher problem. This demonstrates a few things: - The use of multiple async tasks created with `spawn` and `spawn_local`. - How to use `spawn_local` to allow a group of tasks all running on the same worker to use Rc instead of Arc for sharing. - A check that completed work (from a spawn, after calling join or join_async) drops all of its data properly. Signed-off-by: David Brown --- samples/work-philosophers/src/async_sem.rs | 113 +++++++++++ samples/work-philosophers/src/lib.rs | 220 ++++----------------- 2 files changed, 153 insertions(+), 180 deletions(-) create mode 100644 samples/work-philosophers/src/async_sem.rs diff --git a/samples/work-philosophers/src/async_sem.rs b/samples/work-philosophers/src/async_sem.rs new file mode 100644 index 00000000..b4528a8d --- /dev/null +++ b/samples/work-philosophers/src/async_sem.rs @@ -0,0 +1,113 @@ +//! Async Semaphore based demo +//! +//! This implementation on the dining philosopher problem uses Zephyr semaphores to represent the +//! forks. Each philosopher dines as per the algorithm a number of times, and when the are all +//! finished, the test is considered successful. Deadlock will result in the primary thread not +//! completing. +//! +//! Notably, this uses Rc and RefCell along with spawn_local to demonstrate that multiple async +//! tasks run on the same worker do not need Send. It is just important that write operations on +//! the RefCell do not `.await` or a panic is likely. + +use core::cell::RefCell; + +use alloc::{rc::Rc, vec::Vec}; +use zephyr::{ + kio::{sleep, spawn_local}, + printkln, + sys::sync::Semaphore, + time::Forever, +}; + +use crate::{get_random_delay, Stats, NUM_PHIL}; + +/// Number of iterations of each philospher. +/// +/// Should be long enough to exercise the test, but too +/// long and the test will timeout. The delay calculated will randomly be between 25 and 775, and +/// there are two waits, so typically, each "eat" will take about a second. +const EAT_COUNT: usize = 10; + +pub async fn phil() -> Stats { + // It is a little tricky to be able to use local workers. We have to have this nested thread + // that waits. This is because the Future from `local_phil()` does not implement Send, since it + // waits for the philosophers, which are not Send. However, this outer async function does not + // hold onto any data that is not send, and therefore will be Send. Fortunately, this extra + // Future is very lightweight. + spawn_local(local_phil(), c"phil_wrap").join_async().await +} + +async fn local_phil() -> Stats { + // Our overall stats. + let stats = Rc::new(RefCell::new(Stats::default())); + + // One fork for each philospher. + let forks: Vec<_> = (0..NUM_PHIL) + .map(|_| Rc::new(Semaphore::new(1, 1).unwrap())) + .collect(); + + // Create all of the philosphers + let phils: Vec<_> = (0..NUM_PHIL) + .map(|i| { + // Determine the two forks. The forks are paired with each philosopher taking the fork of + // their number, and the next on, module the size of the ring. However, for the last case, + // we need to swap the forks used, it is necessary to obey a strict ordering of the locks to + // avoid deadlocks. + let forks = if i == NUM_PHIL - 1 { + [forks[0].clone(), forks[i].clone()] + } else { + [forks[i].clone(), forks[i + 1].clone()] + }; + + spawn_local(one_phil(forks, i, stats.clone()), c"phil") + }) + .collect(); + + // Wait for them all to finish. + for p in phils { + p.join_async().await; + } + + // Leak the stats as a test. + // Uncomment this to test that the expect below does truly detect a missed drop. + // let _ = Rc::into_raw(stats.clone()); + + // At this point, all of the philosphers should have dropped their stats ref, and we should be + // able to turn stats back into it's value. + // This tests that completed work does drop the future. + Rc::into_inner(stats) + .expect("Failure: a philospher didn't drop it's future") + .into_inner() +} + +/// Simulate a single philospher. +/// +/// The forks must be ordered with the first fork having th lowest number, otherwise this will +/// likely deadlock. +/// +/// This will run for EAT_COUNT times, and then return. +async fn one_phil(forks: [Rc; 2], n: usize, stats: Rc>) { + for i in 0..EAT_COUNT { + // Acquire the forks. + // printkln!("Child {n} take left fork"); + forks[0].take_async(Forever).await.unwrap(); + // printkln!("Child {n} take right fork"); + forks[1].take_async(Forever).await.unwrap(); + + // printkln!("Child {n} eating"); + let delay = get_random_delay(n, 25); + sleep(delay).await; + stats.borrow_mut().record_eat(n, delay); + + // Release the forks. + // printkln!("Child {n} giving up forks"); + forks[1].give(); + forks[0].give(); + + let delay = get_random_delay(n, 25); + sleep(delay).await; + stats.borrow_mut().record_think(n, delay); + + printkln!("Philospher {n} finished eating time {i}"); + } +} diff --git a/samples/work-philosophers/src/lib.rs b/samples/work-philosophers/src/lib.rs index 095109f6..8037c89e 100644 --- a/samples/work-philosophers/src/lib.rs +++ b/samples/work-philosophers/src/lib.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 #![no_std] - // Cargo tries to detect configs that have typos in them. Unfortunately, the Zephyr Kconfig system // uses a large number of Kconfigs and there is no easy way to know which ones might conceivably be // valid. This prevents a warning about each cfg that is used. @@ -10,160 +9,62 @@ extern crate alloc; -use alloc::{boxed::Box, ffi::CString, format}; -use zephyr::{kobj_define, printkln, sync::Arc, sys::sync::Semaphore, time::{Duration, Forever}, work::{futures::sleep, Signal, WorkQueue, WorkQueueBuilder}}; +use zephyr::{ + kio::spawn, + kobj_define, printkln, + sync::Arc, + sys::uptime_get, + time::{Duration, Tick}, + work::WorkQueueBuilder, +}; + +mod async_sem; /// How many philosophers. There will be the same number of forks. -const _NUM_PHIL: usize = 6; +const NUM_PHIL: usize = 6; /// Size of the stack for the work queue. const WORK_STACK_SIZE: usize = 2048; // The dining philosophers problem is a simple example of cooperation between multiple threads. -// This implementation use one of several different underlying mechanism to support this cooperation. - -// This example uses dynamic dispatch to allow multiple implementations. The intent is to be able -// to periodically shut down all of the philosphers and start them up with a differernt sync -// mechanism. This isn't implemented yet. - -/// The philosophers use a fork synchronization mechanism. Essentially, this is 6 locks, and will be -/// implemented in a few different ways to demonstrate/test different mechanmism in Rust. All of -/// them implement The ForkSync trait which provides this mechanism. -/* -trait ForkSync: core::fmt::Debug + Sync + Send { - /// Take the given fork. The are indexed the same as the philosopher index number. This will - /// block until the fork is released. - fn take(&self, index: usize); - - /// Release the given fork. Index is the same as take. - fn release(&self, index: usize); -} -*/ +// This implementation demonstrates a few ways that Zephyr's work-queues can be used to simulate +// this problem. #[no_mangle] extern "C" fn rust_main() { - printkln!("Hello world from Rust on {}", - zephyr::kconfig::CONFIG_BOARD); + printkln!( + "Async/work-queue dining philosophers{}", + zephyr::kconfig::CONFIG_BOARD + ); printkln!("Time tick: {}", zephyr::time::SYS_FREQUENCY); // Create the work queue to run this. - let worker = Box::new(WorkQueueBuilder::new() - .set_priority(1) - .start(WORK_STACK.init_once(()).unwrap())); + let worker = Arc::new( + WorkQueueBuilder::new() + .set_priority(1) + .start(WORK_STACK.init_once(()).unwrap()), + ); // In addition, create a lower priority worker. - let lower_worker = Arc::new(WorkQueueBuilder::new() - .set_priority(5) - .start(LOWER_WORK_STACK.init_once(()).unwrap())); + let lower_worker = Arc::new( + WorkQueueBuilder::new() + .set_priority(5) + .start(LOWER_WORK_STACK.init_once(()).unwrap()), + ); // It is important that work queues are not dropped, as they are persistent objects in the // Zephyr world. let _ = Arc::into_raw(lower_worker.clone()); + let _ = Arc::into_raw(worker.clone()); - // Create a semaphore the phil thread can wait for, part way through its work. - let sem = Arc::new(Semaphore::new(0, 1).unwrap()); - - let th = phil_thread(0, sem.clone(), lower_worker.clone()); - printkln!("Size of phil thread: {}", core::mem::size_of_val(&th)); - // TODO: How to do as much on the stack as we can, for now, don't worry too much. - // let mut th = pin!(th); - - let th = zephyr::kio::spawn(th, &worker, c"phil-worker"); - - // As we no longer use the worker, it is important that it never be dropped. Leaking the box - // ensures that it will not be deallocated. - Box::leak(worker); - - // Sleep a bit to allow the worker to run to where it is waiting. - zephyr::time::sleep(Duration::millis_at_least(1_000)); - printkln!("Giving the semaphore"); - sem.give(); - - let result = th.join(); - printkln!("th result: {:?}", result); + // First run the async semaphore based one. + printkln!("Running 'async-sem' test"); + let handle = spawn(async_sem::phil(), &worker, c"async-sem"); + let stats = handle.join(); + printkln!("Done with 'async-sem' test"); + stats.show(); - /* - // TODO: The allocated Arc doesn't work on all Zephyr platforms, but need to make our own copy - // of alloc::task - let waker = Arc::new(PWaker).into(); - let mut cx = Context::from_waker(&waker); - - // Run the future to completion. - loop { - match th.as_mut().poll(&mut cx) { - Poll::Pending => todo!(), - Poll::Ready(_) => break, - } - } - */ printkln!("All threads done"); - - /* - let stats = Arc::new(Mutex::new_from(Stats::default(), STAT_MUTEX.init_once(()).unwrap())); - - let syncers = get_syncer(); - - printkln!("Pre fork"); - - for (i, syncer) in (0..NUM_PHIL).zip(syncers.into_iter()) { - let child_stat = stats.clone(); - let thread = PHIL_THREADS[i].init_once(PHIL_STACKS[i].init_once(()).unwrap()).unwrap(); - thread.spawn(move || { - phil_thread(i, syncer, child_stat); - }); - } - - let delay = Duration::secs_at_least(10); - loop { - // Periodically, printout the stats. - zephyr::time::sleep(delay); - stats.lock().unwrap().show(); - } - */ -} - -async fn phil_thread(n: usize, sem: Arc, lower_thread: Arc) -> usize { - printkln!("Child {} started", n); - show_it(&sem).await; - sleep(Duration::millis_at_least(1000)).await; - printkln!("Child {} done sleeping", n); - - // Lastly fire off something on the other worker thread. - let sig = Arc::new(Signal::new().unwrap()); - let sig2 = sig.clone(); - - // To help with debugging, we can name these, but it must be a `&'static Cstr`. We'll build a - // CString (on the heap), and then leak it to be owned by the work. The leaking while keeping - // the `&'static CStr` is a bit messy. - let name = CString::new(format!("phil-{}", n)).unwrap(); - let name = Box::leak(name.into_boxed_c_str()); - - // It _should_ be safe to drop the child, as it uses an internal clone of the Arc to keep it - // around as long as there is a worker. - let _child = Box::new(zephyr::kio::spawn(async move { - sleep(Duration::millis_at_least(800)).await; - sig2.raise(12345).unwrap(); - }, &lower_thread, name)); - - // Wait for the signal. - let num = sig.wait_async(Forever).await.unwrap(); - printkln!("Signaled value: {}", num); - - 42 -} - -async fn show_it(sem: &Semaphore) { - for i in 0..10 { - sleep(Duration::millis_at_least(1)).await; - printkln!("Tick: {i}"); - - // Wait after 5 for the semaphore to be signaled. - if i == 4 { - printkln!("Waiting for semaphore"); - sem.take_async(Forever).await.unwrap(); - printkln!("Done waiting"); - } - } } kobj_define! { @@ -171,44 +72,6 @@ kobj_define! { static LOWER_WORK_STACK: ThreadStack; } -/* -fn phil_thread(n: usize, syncer: Arc, stats: Arc>) { - printkln!("Child {} started: {:?}", n, syncer); - - // Determine our two forks. - let forks = if n == NUM_PHIL - 1 { - // Per Dijkstra, the last phyilosopher needs to reverse forks, or we deadlock. - (0, n) - } else { - (n, n+1) - }; - - loop { - { - // printkln!("Child {} hungry", n); - // printkln!("Child {} take left fork", n); - syncer.take(forks.0); - // printkln!("Child {} take right fork", n); - syncer.take(forks.1); - - let delay = get_random_delay(n, 25); - // printkln!("Child {} eating ({} ms)", n, delay); - sleep(delay); - stats.lock().unwrap().record_eat(n, delay); - - // Release the forks. - // printkln!("Child {} giving up forks", n); - syncer.release(forks.1); - syncer.release(forks.0); - - let delay = get_random_delay(n, 25); - // printkln!("Child {} thinking ({} ms)", n, delay); - sleep(delay); - stats.lock().unwrap().record_think(n, delay); - } - } -} - /// Get a random delay, based on the ID of this user, and the current uptime. fn get_random_delay(id: usize, period: usize) -> Duration { let tick = (uptime_get() & (usize::MAX as i64)) as usize; @@ -241,14 +104,11 @@ impl Stats { } fn show(&self) { - printkln!("c:{:?}, e:{:?}, t:{:?}", self.count, self.eating, self.thinking); + printkln!( + "c:{:?}, e:{:?}, t:{:?}", + self.count, + self.eating, + self.thinking + ); } } - -kobj_define! { - static PHIL_THREADS: [StaticThread; NUM_PHIL]; - static PHIL_STACKS: [ThreadStack; NUM_PHIL]; - - static STAT_MUTEX: StaticMutex; -} -*/ From 28b346519a4bba7c9a70946f38c42b82cff54023 Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 11 Jan 2025 09:03:27 -0700 Subject: [PATCH 40/65] TMP: Non working hand philosopher demo Needs to be fixed if done at all. Signed-off-by: David Brown --- samples/work-philosophers/prj.conf | 3 + samples/work-philosophers/src/async_sem.rs | 4 +- samples/work-philosophers/src/hand_worker.rs | 285 +++++++++++++++++++ samples/work-philosophers/src/lib.rs | 46 ++- 4 files changed, 328 insertions(+), 10 deletions(-) create mode 100644 samples/work-philosophers/src/hand_worker.rs diff --git a/samples/work-philosophers/prj.conf b/samples/work-philosophers/prj.conf index c53536c4..108393cb 100644 --- a/samples/work-philosophers/prj.conf +++ b/samples/work-philosophers/prj.conf @@ -9,6 +9,9 @@ CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 CONFIG_POLL=y # CONFIG_DEBUG=y +CONFIG_DEBUG=n +# CONFIG_ASSERT=y +CONFIG_MPU_STACK_GUARD=y # CONFIG_USERSPACE=y diff --git a/samples/work-philosophers/src/async_sem.rs b/samples/work-philosophers/src/async_sem.rs index b4528a8d..ee72cc86 100644 --- a/samples/work-philosophers/src/async_sem.rs +++ b/samples/work-philosophers/src/async_sem.rs @@ -59,7 +59,9 @@ async fn local_phil() -> Stats { [forks[i].clone(), forks[i + 1].clone()] }; - spawn_local(one_phil(forks, i, stats.clone()), c"phil") + let phil = one_phil(forks, i, stats.clone()); + printkln!("Size of child {i}: {}", size_of_val(&phil)); + spawn_local(phil, c"phil") }) .collect(); diff --git a/samples/work-philosophers/src/hand_worker.rs b/samples/work-philosophers/src/hand_worker.rs new file mode 100644 index 00000000..84f7e95a --- /dev/null +++ b/samples/work-philosophers/src/hand_worker.rs @@ -0,0 +1,285 @@ +//! Work-queue work, with hand-constructed work. +//! +//! This module tries to demonstrate what a hand-crafted system based on work-queues might be like. +//! +//! As such, this isn't built around any synchronization mechanimsms other than signal. In some +//! sense, this is structured more like various workers that are coordinating with devices, except +//! that we will use timeouts for the pauses. + +use core::{future::Future, pin::Pin, task::{Context, Poll}}; + +use alloc::vec; +use alloc::vec::Vec; +use zephyr::{kio::{spawn, ContextExt}, printkln, sync::{Arc, SpinMutex}, time::Forever, work::{futures::JoinHandle, Signal, WorkQueue}}; + +use crate::{get_random_delay, NUM_PHIL}; +pub use crate::Stats; + +pub fn phil(workq: &WorkQueue) -> Manager { + let wake_manager = Arc::new(Signal::new().unwrap()); + + let actions: Vec<_> = (0..NUM_PHIL).map(|_| Arc::new(Action::new(wake_manager.clone()))).collect(); + + let phils: Vec<_> = (0..NUM_PHIL) + .map(|i| Phil::new(actions[i].clone(), i)) + .map(|act| spawn(act, workq, c"phil")) + .collect(); + + Manager { + request: wake_manager, + actions, + phils, + forks: vec![ForkState::Idle; NUM_PHIL], + } +} + +#[derive(Copy, Clone, Debug)] +enum ForkState { + /// Nobody is using the fork. + Idle, + /// A single philospher is eating with this fork. + Eating, + /// Someone is eating, and the numbered philospher is also waiting to use it. + Waiting(usize), +} + +/// Outer Phil is the main event handler for the work queue system. +pub struct Manager { + actions: Vec>, + phils: Vec>, + /// The signal to wake the manager up. + request: Arc, + + // The state of each fork. + forks: Vec, +} + +impl Future for Manager { + type Output = Stats; + + fn poll(mut self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> core::task::Poll { + // Run through the actions, and see what they have to do. + printkln!("Manager running"); + + // Clear out signal before any processing, so it can be set. + self.request.reset(); + + // Loop through all of the actions. + for i in 0..self.actions.len() { + // for (i, act) in self.actions.iter().enumerate() { + let act = &self.actions[i]; + let mut change = None; + let mut lock = act.fork.lock().unwrap(); + match *lock { + ForkRequest::Idle => (), + ForkRequest::Waiting => (), + ForkRequest::Take(f) => { + printkln!("phil {i} wants fork {f}: state {:?}", self.forks[f]); + match self.forks[f] { + ForkState::Idle => { + assert!(change.is_none()); + + // This philospher can have this fork. + change = Some((f, ForkState::Eating)); + + // And let them know they got it. + *lock = ForkRequest::Idle; + act.wake_phil.raise(-1).unwrap(); + } + ForkState::Eating => { + // The fork is busy, but remember who is waiting for it. + assert!(change.is_none()); + change = Some((f, ForkState::Waiting(i))); + *lock = ForkRequest::Waiting; + } + ForkState::Waiting(i2) => { + // This indicates the forks were not assigned to the philosphers + // correctly. + panic!("Too many philosphers requesting same fork {i} {i2}"); + } + } + } + ForkRequest::Give(f) => { + printkln!("phil {i} releases fork {f}: state {:?}", self.forks[f]); + match self.forks[f] { + ForkState::Idle => { + panic!("Philospher returned a fork it did not have"); + } + ForkState::Eating => { + // This philospher was the only one using this fork. + assert!(change.is_none()); + change = Some((f, ForkState::Idle)); + + // And let them now that was fine. + *lock = ForkRequest::Idle; + // TODO: Move this raise to after the lock to shorten the time spent + // holding the lock. + act.wake_phil.raise(-2).unwrap(); + } + ForkState::Waiting(i2) => { + // We (i) are done with the fork, and can now give it to i2. + // The state changes to Eating to indicate one waiter. + assert!(change.is_none()); + change = Some((f, ForkState::Eating)); + + // We inform current philospher that we have handled them being done + // with the fork. + *lock = ForkRequest::Idle; + act.wake_phil.raise(-1).unwrap(); + + // And inform the waiter that they can continue. + *self.actions[i2].fork.lock().unwrap() = ForkRequest::Idle; + self.actions[i2].wake_phil.raise(-2).unwrap(); + } + } + } + } + drop(lock); + if let Some((f, state)) = change { + self.forks[f] = state; + } + } + + // Unless we're completely done, set to wake on our own signal. + cx.add_signal(&self.request, Forever); + printkln!("Manager pending"); + Poll::Pending + } +} + +/// Captures requests from a philospher for exclusive use of some forks. +/// +/// This works by having the philosopher set the request, and the Manager sets this to Idle when it +/// has been satisfied. Each will signal the other after setting this value. +struct Action { + fork: SpinMutex, + wake_manager: Arc, + wake_phil: Signal, +} + +impl Action { + fn new(wake_manager: Arc) -> Self { + Self { + fork: SpinMutex::new(ForkRequest::Idle), + wake_manager, + wake_phil: Signal::new().unwrap(), + } + } +} + +/// A single request concerning a fork. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum ForkRequest { + /// There is no request pending. + Idle, + /// Take the given numbered fork. + Take(usize), + /// Give back the give numbered fork. + Give(usize), + /// The philospher has requested a fork, but it is not available. + Waiting, +} + +/// The state of a philospher. +#[derive(Debug, Copy, Clone)] +enum PhilState { + /// The initial state. + Init, + /// Wait upon getting the given fork. + Take(usize), + /// Eating is happening, should wake from a sleep. + Eating, + /// Waiting on returning the given fork. + Give(usize), + /// Resting between bytes. + Resting, + /// Done with the whole thing. poll will always return Ready after setting this. + Done, +} + +/// Phil represents a single dining philospher. +/// +/// Each Phil runs on its own, making its requests to 'req' for taking and returning the forks. +struct Phil { + /// Which philospher are we? + index: usize, + /// Our view of our actions. + action: Arc, + /// Current state of this philosopher. + state: PhilState, + /// How many times have we finished. + count: usize, + /// The forks we should be using. + forks: [usize; 2], +} + +impl Phil { + fn new(action: Arc, index: usize) -> Self { + let forks = if index == NUM_PHIL - 1 { + [0, index] + } else { + [index, index + 1] + }; + + Self { + index, + action, + state: PhilState::Init, + count: 0, + forks, + } + } +} + +impl Future for Phil { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + printkln!("Phil run: {}, {:?}", self.index, self.state); + + match self.state { + PhilState::Init => { + // Initially, request the first fork to be taken. + *self.action.fork.lock().unwrap() = ForkRequest::Take(self.forks[0]); + self.action.wake_manager.raise(self.index as i32).unwrap(); + + // Next wake event is the signal. + cx.add_signal(&self.action.wake_phil, Forever); + + self.state = PhilState::Take(0); + } + PhilState::Take(f) => { + // Check that we were actually supposed to wake up. There shouldn't be spurious + // wakeups in this direction. + let cur_state = *self.action.fork.lock().unwrap(); + if cur_state != ForkRequest::Idle { + panic!("State error, taken fork should be idle: {:?}", cur_state); + } + + if f == 1 { + // Both are taken, We're eating, and we wait by timeout. + printkln!("Phil {} eating", self.index); + + self.state = PhilState::Eating; + let delay = get_random_delay(self.index, 25); + cx.add_timeout(delay); + } else { + // First fork taken, wait for second. + printkln!("Setting state to {:?}", ForkRequest::Take(self.forks[1])); + *self.action.fork.lock().unwrap() = ForkRequest::Take(self.forks[1]); + self.action.wake_manager.raise(self.index as i32).unwrap(); + + cx.add_signal(&self.action.wake_phil, Forever); + + self.state = PhilState::Take(1); + } + } + PhilState::Eating => todo!(), + PhilState::Give(_) => todo!(), + PhilState::Resting => todo!(), + PhilState::Done => todo!(), + } + + Poll::Pending + } +} diff --git a/samples/work-philosophers/src/lib.rs b/samples/work-philosophers/src/lib.rs index 8037c89e..a3022431 100644 --- a/samples/work-philosophers/src/lib.rs +++ b/samples/work-philosophers/src/lib.rs @@ -9,6 +9,8 @@ extern crate alloc; +use alloc::vec; +use alloc::vec::Vec; use zephyr::{ kio::spawn, kobj_define, printkln, @@ -19,9 +21,13 @@ use zephyr::{ }; mod async_sem; +mod hand_worker; /// How many philosophers. There will be the same number of forks. +/// +/// For async, this can typically be quite a bit larger than the number of threads possible. const NUM_PHIL: usize = 6; +//const NUM_PHIL: usize = 16; /// Size of the stack for the work queue. const WORK_STACK_SIZE: usize = 2048; @@ -57,9 +63,20 @@ extern "C" fn rust_main() { let _ = Arc::into_raw(lower_worker.clone()); let _ = Arc::into_raw(worker.clone()); - // First run the async semaphore based one. + // Run the by-hand worker. + printkln!("Running hand-worker test"); + let work = hand_worker::phil(&lower_worker); + let handle = spawn(work, &worker, c"hand-work"); + let stats = handle.join(); + printkln!("Done with hand-worker"); + stats.show(); + + // Run the async semaphore based worker. printkln!("Running 'async-sem' test"); - let handle = spawn(async_sem::phil(), &worker, c"async-sem"); + // let handle = spawn(async_sem::phil(), &worker, c"async-sem"); + let work = async_sem::phil(); + printkln!("size of async-sem worker: {}", size_of_val(&work)); + let handle = spawn(work, &worker, c"async-sem"); let stats = handle.join(); printkln!("Done with 'async-sem' test"); stats.show(); @@ -83,23 +100,34 @@ fn get_random_delay(id: usize, period: usize) -> Duration { /// Instead of just printint out so much information that the data just scolls by, gather /// statistics. -#[derive(Default)] -struct Stats { +// #[derive(Default)] +pub struct Stats { /// How many times each philosopher has gone through the loop. - count: [u64; NUM_PHIL], + count: Vec, /// How much time each philosopher has spent eating. - eating: [u64; NUM_PHIL], + eating: Vec, /// How much time each philosopher has spent thinking. - thinking: [u64; NUM_PHIL], + thinking: Vec, +} + +// Implement default manually, as the fixed arrays only implement initialization up to 64 elements. +impl Default for Stats { + fn default() -> Self { + Self { + count: vec![0; NUM_PHIL], + eating: vec![0; NUM_PHIL], + thinking: vec![0; NUM_PHIL], + } + } } impl Stats { fn record_eat(&mut self, index: usize, time: Duration) { - self.eating[index] += time.to_millis(); + self.eating[index] += time.to_millis() as u16; } fn record_think(&mut self, index: usize, time: Duration) { - self.thinking[index] += time.to_millis(); + self.thinking[index] += time.to_millis() as u16; self.count[index] += 1; } From 85fe22dd861477286153140cc7e4836f466a14d4 Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 11 Jan 2025 11:11:46 -0700 Subject: [PATCH 41/65] zephyr: work/kio: Timeout support for work All hand-written work to directly schedule timeouts. Signed-off-by: David Brown --- zephyr/src/kio.rs | 11 +++++++++++ zephyr/src/work.rs | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/zephyr/src/kio.rs b/zephyr/src/kio.rs index 981ad3a4..89e8936a 100644 --- a/zephyr/src/kio.rs +++ b/zephyr/src/kio.rs @@ -143,6 +143,12 @@ pub trait ContextExt { /// Indicate that the work should be scheduled when the given [`Queue`] has data available to /// recv, or the timeout occurs. fn add_queue<'a>(&'a mut self, queue: &'a Queue, timeout: impl Into); + + /// Indicate that the work should just be scheduled after the given timeout. + /// + /// Note that this only works if none of the other wake methods are called, as those also set + /// the timeout. + fn add_timeout(&mut self, timeout: impl Into); } /// Implementation of ContextExt for the Rust [`Context`] type. @@ -164,4 +170,9 @@ impl<'b> ContextExt for Context<'b> { info.add_queue(queue); info.timeout = timeout.into(); } + + fn add_timeout(&mut self, timeout: impl Into) { + let info = unsafe { WakeInfo::from_context(self) }; + info.timeout = timeout.into(); + } } diff --git a/zephyr/src/work.rs b/zephyr/src/work.rs index 4f78d39f..6632a8ce 100644 --- a/zephyr/src/work.rs +++ b/zephyr/src/work.rs @@ -464,6 +464,12 @@ impl Signal { } } +impl Default for Signal { + fn default() -> Self { + Signal::new().unwrap() + } +} + /// The Future for Signal::wait_async. struct SignalWait<'a> { /// The signal we are waiting on. From 76ca3522a76d870f19c8c68e990f62efa71c5613 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 14:51:23 -0600 Subject: [PATCH 42/65] zephyr: device: Add uart Add a simplistic uart wrapper, and drivers around it. All methods are unsafe. Signed-off-by: David Brown --- dt-rust.yaml | 20 ++++++ zephyr-build/src/devicetree/augment.rs | 31 ++++++++- zephyr-sys/build.rs | 4 ++ zephyr-sys/wrapper.h | 1 + zephyr/src/device.rs | 1 + zephyr/src/device/uart.rs | 94 ++++++++++++++++++++++++++ 6 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 zephyr/src/device/uart.rs diff --git a/dt-rust.yaml b/dt-rust.yaml index 878913db..a5367f6d 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -78,6 +78,26 @@ - type: reg device: "crate::device::flash::FlashPartition" +# Uart devices. This just has to be a list of devices that implement this interface. +- name: uart + rules: + - type: compatible + value: + names: + - "arm,pl011" + # The nordic driver needs to be separate because they have a separate Kconfig for each uart + # block. + # - "nordic,nrf-uarte" + - "zephyr,cdc-acm-uart" + level: 0 + actions: + - type: instance + value: + raw: + type: myself + device: "crate::device::uart::Uart" + kconfig: CONFIG_SERIAL + # Generate a pseudo node that matches all of the labels across the tree with their nodes. - name: labels rules: diff --git a/zephyr-build/src/devicetree/augment.rs b/zephyr-build/src/devicetree/augment.rs index ec3bddb2..50d5abfa 100644 --- a/zephyr-build/src/devicetree/augment.rs +++ b/zephyr-build/src/devicetree/augment.rs @@ -28,6 +28,12 @@ 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 there is a status field present, and it is not set to "okay", don't augment this node. + if let Some(status) = node.get_single_string("status") { + if status != "okay" { + return TokenStream::new(); + } + } if self.is_compatible(node) { self.generate(node, tree) } else { @@ -135,6 +141,9 @@ pub enum Action { /// The name of the full path (within the zephyr-sys crate) for the wrapper node for this /// device. device: String, + /// A Kconfig option to allow the instances to only be present when said driver is compiled + /// in. + kconfig: Option, }, /// Generate all of the labels as its own node. Labels, @@ -143,8 +152,8 @@ pub enum Action { impl Action { fn generate(&self, _name: &Ident, node: &Node, tree: &DeviceTree) -> TokenStream { match self { - Action::Instance { raw, device } => { - raw.generate(node, device) + Action::Instance { raw, device, kconfig } => { + raw.generate(node, device, kconfig.as_deref()) } Action::Labels => { let nodes = tree.labels.iter().map(|(k, v)| { @@ -188,19 +197,31 @@ pub enum RawInfo { } impl RawInfo { - fn generate(&self, node: &Node, device: &str) -> TokenStream { + fn generate(&self, node: &Node, device: &str, kconfig: Option<&str>) -> TokenStream { let device_id = str_to_path(device); + let kconfig = if let Some(name) = kconfig { + let name = format_ident!("{}", name); + quote! { + #[cfg(#name)] + } + } else { + quote! {} + }; + match self { Self::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. + #kconfig pub unsafe fn get_instance_raw() -> *const crate::raw::device { &crate::raw::#rawdev } + #kconfig static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + #kconfig pub fn get_instance() -> Option<#device_id> { unsafe { let device = get_instance_raw(); @@ -226,7 +247,9 @@ impl RawInfo { let target_route = target.route_to_rust(); quote! { + #kconfig static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + #kconfig pub fn get_instance() -> Option<#device_id> { unsafe { let device = #target_route :: get_instance_raw(); @@ -245,7 +268,9 @@ impl RawInfo { } quote! { + #kconfig static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + #kconfig pub fn get_instance() -> Option<#device_id> { unsafe { let device = #path :: get_instance_raw(); diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index 091d773c..b424e3cc 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -71,11 +71,14 @@ fn main() -> Result<()> { .allowlist_function("k_.*") .allowlist_function("gpio_.*") .allowlist_function("flash_.*") + .allowlist_function("usb_.*") .allowlist_item("GPIO_.*") + .allowlist_item("USB_.*") .allowlist_item("FLASH_.*") .allowlist_item("Z_.*") .allowlist_item("ZR_.*") .allowlist_item("K_.*") + .allowlist_item("uart_line_ctrl") // Each DT node has a device entry that is a static. .allowlist_item("__device_dts_ord.*") .allowlist_function("device_.*") @@ -83,6 +86,7 @@ fn main() -> Result<()> { .allowlist_function("z_log.*") .allowlist_function("bt_.*") .allowlist_function("SEGGER.*") + .allowlist_function("uart_.*") .allowlist_item("E.*") .allowlist_item("K_.*") .allowlist_item("ZR_.*") diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 7d964bb8..3094bfb2 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -42,6 +42,7 @@ extern int errno; #include #include #include +#include /* * bindgen will only output #defined constants that resolve to simple numbers. These are some diff --git a/zephyr/src/device.rs b/zephyr/src/device.rs index bf4afec6..a1320293 100644 --- a/zephyr/src/device.rs +++ b/zephyr/src/device.rs @@ -12,6 +12,7 @@ use crate::sync::atomic::{AtomicBool, Ordering}; pub mod gpio; pub mod flash; +pub mod uart; // Allow dead code, because it isn't required for a given build to have any devices. /// Device uniqueness. diff --git a/zephyr/src/device/uart.rs b/zephyr/src/device/uart.rs new file mode 100644 index 00000000..356b92a8 --- /dev/null +++ b/zephyr/src/device/uart.rs @@ -0,0 +1,94 @@ +//! Simple (and unsafe) wrappers around USB devices. + +use crate::raw; +use crate::error::{Error, Result, to_result_void, to_result}; + +use core::ffi::{c_uchar, c_int}; + +use super::Unique; + +/// A wrapper around a UART device on Zephyr. +pub struct Uart { + /// The underlying device itself. + #[allow(dead_code)] + pub(crate) device: *const raw::device, +} + +/// Uart control values. +/// +/// This mirrors these definitions from C, but as an enum. +#[repr(u32)] +pub enum LineControl { + /// Baud rate + BaudRate = raw::uart_line_ctrl_UART_LINE_CTRL_BAUD_RATE, + /// Request To Send (RTS) + RTS = raw::uart_line_ctrl_UART_LINE_CTRL_RTS, + /// Data Terminal Ready (DTR) + DTR = raw::uart_line_ctrl_UART_LINE_CTRL_DTR, + /// Data Carrier Detect (DCD) + DCD = raw::uart_line_ctrl_UART_LINE_CTRL_DCD, + /// Data Set Ready (DSR) + DSR = raw::uart_line_ctrl_UART_LINE_CTRL_DSR, +} + +impl Uart { + // Note that the `poll_in` and `poll_out` are terrible. + + /// Constructor, used by the devicetree generated code. + #[allow(dead_code)] + pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { + if !unique.once() { + return None; + } + + Some(Uart { device }) + } + + /// Attempt to read a character from the UART fifo. + /// + /// Will return Ok(Some(ch)) if there is a character available, `Ok(None)` if no character + /// is available, or `Err(e)` if there was an error. + pub unsafe fn poll_in(&mut self) -> Result> { + let mut ch: c_uchar = 0; + + match to_result_void(unsafe { raw::uart_poll_in(self.device, &mut ch) }) { + Ok(()) => Ok(Some(ch as u8)), + Err(Error(1)) => Ok(None), + Err(e) => Err(e), + } + } + + /// Attempt to write to the outgoing FIFO. + /// + /// This writes to the outgoing UART fifo. This will block if the outgoing fifo is full. + pub unsafe fn poll_out(&mut self, out_char: u8) { + unsafe { raw::uart_poll_out(self.device, out_char as c_uchar) } + } + + /// Fill FIFO with data. + /// + /// This is unspecified what happens if this is not called from IRQ context. + /// Returns Ok(n) for the number of bytes sent. + pub unsafe fn fifo_fill(&mut self, data: &[u8]) -> Result { + to_result(unsafe { + raw::uart_fifo_fill(self.device, data.as_ptr(), data.len() as c_int) + }).map(|count| count as usize) + } + + /// Drain FIFO. + /// + /// This is unspecified as to what happens if not called from IRQ context. + pub unsafe fn fifo_read(&mut self, data: &mut [u8]) -> Result { + to_result(unsafe { + raw::uart_fifo_read(self.device, data.as_mut_ptr(), data.len() as c_int) + }).map(|count| count as usize) + } + + /// Read one of the UART line control values. + pub unsafe fn line_ctrl_get(&self, item: LineControl) -> Result { + let mut result: u32 = 0; + to_result_void(unsafe { + raw::uart_line_ctrl_get(self.device, item as u32, &mut result) + }).map(|()| result) + } +} From 7a209c88ffdc99ad072e4dc49124fcf5d457163c Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 15:15:09 -0600 Subject: [PATCH 43/65] zephyr: device: Add led-strip wrapper Add a simple wrapper for the led strip driver. Signed-off-by: David Brown --- dt-rust.yaml | 22 +++++++++++++++++ zephyr-sys/build.rs | 1 + zephyr-sys/src/lib.rs | 4 +-- zephyr-sys/wrapper.h | 1 + zephyr/src/device.rs | 1 + zephyr/src/device/led_strip.rs | 45 ++++++++++++++++++++++++++++++++++ zephyr/src/sys.rs | 7 ++++++ 7 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 zephyr/src/device/led_strip.rs diff --git a/dt-rust.yaml b/dt-rust.yaml index a5367f6d..f841fc2f 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -98,6 +98,28 @@ device: "crate::device::uart::Uart" kconfig: CONFIG_SERIAL +- name: led-strip + rules: + - type: or + value: + - type: compatible + value: + names: + - "worldsemi,wd2812-spi" + level: 0 + - type: compatible + value: + names: + - "worldsemi,ws2812-rpi_pico-pio" + level: 1 + actions: + - type: instance + value: + raw: + type: myself + device: "crate::device::led_strip::LedStrip" + kconfig: CONFIG_LED_STRIP + # Generate a pseudo node that matches all of the labels across the tree with their nodes. - name: labels rules: diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index b424e3cc..3819fde1 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -82,6 +82,7 @@ fn main() -> Result<()> { // Each DT node has a device entry that is a static. .allowlist_item("__device_dts_ord.*") .allowlist_function("device_.*") + .allowlist_function("led_strip.*") .allowlist_function("sys_.*") .allowlist_function("z_log.*") .allowlist_function("bt_.*") diff --git a/zephyr-sys/src/lib.rs b/zephyr-sys/src/lib.rs index aef51f04..4cd040a6 100644 --- a/zephyr-sys/src/lib.rs +++ b/zephyr-sys/src/lib.rs @@ -46,7 +46,7 @@ macro_rules! derive_copy { } } -derive_copy!(z_spinlock_key); -derive_clone!(z_spinlock_key); +derive_copy!(z_spinlock_key, led_rgb); +derive_clone!(z_spinlock_key, led_rgb); derive_copy!(k_timeout_t); derive_clone!(k_timeout_t); diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 3094bfb2..7473eab6 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -43,6 +43,7 @@ extern int errno; #include #include #include +#include /* * bindgen will only output #defined constants that resolve to simple numbers. These are some diff --git a/zephyr/src/device.rs b/zephyr/src/device.rs index a1320293..75316a5c 100644 --- a/zephyr/src/device.rs +++ b/zephyr/src/device.rs @@ -13,6 +13,7 @@ use crate::sync::atomic::{AtomicBool, Ordering}; pub mod gpio; pub mod flash; pub mod uart; +pub mod led_strip; // Allow dead code, because it isn't required for a given build to have any devices. /// Device uniqueness. diff --git a/zephyr/src/device/led_strip.rs b/zephyr/src/device/led_strip.rs new file mode 100644 index 00000000..2f6ace23 --- /dev/null +++ b/zephyr/src/device/led_strip.rs @@ -0,0 +1,45 @@ +//! Simple led strip driver + +use crate::raw; +use crate::error::{Result, to_result_void}; + +use super::Unique; + +/// A simple led strip wrapper. +pub struct LedStrip { + /// The underlying device itself. + #[allow(dead_code)] + pub(crate) device: *const raw::device, +} + +// This is send, safe with Zephyr. +unsafe impl Send for LedStrip { } + +impl LedStrip { + /// Constructor, used by the devicetree generated code. + #[allow(dead_code)] + pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { + if !unique.once() { + return None; + } + + Some(LedStrip { device }) + } + + /// Return the number of LEDS in the chain. + pub fn chain_len(&self) -> usize { + unsafe { raw::led_strip_length(self.device) as usize} + } + + /// Update the state of the LEDs. + /// + /// It is unclear from the API docs whether this is supposed to be an array of rgb_led + /// (which is what the samples assume), or packed rgb data. + pub unsafe fn update(&mut self, channels: &[raw::led_rgb]) -> Result<()> { + to_result_void(unsafe { + raw::led_strip_update_rgb(self.device, + channels.as_ptr() as *mut _, + channels.len()) + }) + } +} diff --git a/zephyr/src/sys.rs b/zephyr/src/sys.rs index f85069a1..2c6a854f 100644 --- a/zephyr/src/sys.rs +++ b/zephyr/src/sys.rs @@ -41,6 +41,13 @@ pub fn uptime_get() -> i64 { } } +/// Busy wait. +/// +/// Busy wait for a give number of microseconds. This directly calls `zephyr_sys::k_busy_wait`. +/// +/// Zephyr has numerous caveats on configurations where this function doesn't work. +pub use zephyr_sys::k_busy_wait as busy_wait; + pub mod critical { //! Zephyr implementation of critical sections. //! From d93ab3087589e984fe197e0ddfa4682ad01ad1ad Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 15:26:08 -0600 Subject: [PATCH 44/65] zephyr: device: gpio: Add a few more methods Add methods for get and set of pin logical values. Signed-off-by: David Brown --- zephyr/src/device/gpio.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/zephyr/src/device/gpio.rs b/zephyr/src/device/gpio.rs index b9a31d3e..2c9b50c4 100644 --- a/zephyr/src/device/gpio.rs +++ b/zephyr/src/device/gpio.rs @@ -7,6 +7,8 @@ //! pervasively throughout Zephyr device drivers. As such, most of the calls in this module are //! unsafe. +use core::ffi::c_int; + use crate::raw; use super::Unique; @@ -125,4 +127,18 @@ impl GpioPin { raw::gpio_pin_toggle_dt(&self.pin); } } + + /// Set the logical level of the pin. + pub unsafe fn set(&mut self, _token: &mut GpioToken, value: bool) { + raw::gpio_pin_set_dt(&self.pin, value as c_int); + } + + /// Read the logical level of the pin. + pub unsafe fn get(&mut self, _token: &mut GpioToken) -> bool { + match raw::gpio_pin_get_dt(&self.pin) { + 0 => false, + 1 => true, + _ => panic!("TODO: Handle gpio get error"), + } + } } From 32573c23dd171197dd1858abfe4e1f192185ce93 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 16:14:55 -0600 Subject: [PATCH 45/65] hack: Add DT augment for a bbq-kbd-matrix This is a special keyboard matrix type used by the bbq-keyboard firmware. It doesn't belong here, and can be moved out, once we support additional augment files. Signed-off-by: David Brown --- dt-rust.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/dt-rust.yaml b/dt-rust.yaml index f841fc2f..d4020de6 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -120,6 +120,25 @@ device: "crate::device::led_strip::LedStrip" kconfig: CONFIG_LED_STRIP +# This doesn't really belong here, and can be moved once we support modules having their own augment +# files. +- name: bbq-kbd-matrix + rules: + - type: compatible + value: + names: + - "bbq-kbd-matrix" + level: 0 + actions: + - type: gpio_pins + value: + property: "row-gpios" + getter: "get_rows" + - type: gpio_pins + value: + property: "col-gpios" + getter: "get_cols" + # Generate a pseudo node that matches all of the labels across the tree with their nodes. - name: labels rules: From 13ed0e66fc5b026384d0441568eb781a49943c34 Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 19 Oct 2024 11:14:45 -0600 Subject: [PATCH 46/65] zephyr: Add Leds driver Add support for Leds, and a simple augment for the pwm drivers. --- dt-rust.yaml | 25 +++++++++++++++ zephyr-build/src/devicetree/augment.rs | 18 +++++++++-- zephyr-sys/build.rs | 2 +- zephyr-sys/wrapper.h | 1 + zephyr/src/device.rs | 1 + zephyr/src/device/led.rs | 44 ++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 zephyr/src/device/led.rs diff --git a/dt-rust.yaml b/dt-rust.yaml index d4020de6..2837cf3f 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -14,6 +14,8 @@ value: raw: type: myself + value: + args: [] device: crate::device::gpio::Gpio # The gpio-leds node will have #children nodes describing each led. We'll match on the parent @@ -50,6 +52,8 @@ value: raw: type: myself + value: + args: [] device: crate::device::flash::FlashController # Flash partitions exist as children of a node compatible with "soc-nv-flash" that itself is a child @@ -95,6 +99,8 @@ value: raw: type: myself + value: + args: [] device: "crate::device::uart::Uart" kconfig: CONFIG_SERIAL @@ -117,9 +123,28 @@ value: raw: type: myself + value: + args: [] device: "crate::device::led_strip::LedStrip" kconfig: CONFIG_LED_STRIP +- name: pwm-leds + rules: + - type: compatible + value: + names: + - "pwm-leds" + level: 0 + actions: + - type: instance + value: + raw: + type: myself + value: + args: + - type: child_count + device: "crate::device::led::Leds" + # This doesn't really belong here, and can be moved once we support modules having their own augment # files. - name: bbq-kbd-matrix diff --git a/zephyr-build/src/devicetree/augment.rs b/zephyr-build/src/devicetree/augment.rs index 50d5abfa..8a2046a5 100644 --- a/zephyr-build/src/devicetree/augment.rs +++ b/zephyr-build/src/devicetree/augment.rs @@ -184,7 +184,9 @@ impl Action { #[serde(tag = "type", rename_all = "snake_case", content = "value")] pub enum RawInfo { /// Get the raw device directly from this node. - Myself, + Myself { + args: Vec, + }, /// 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). @@ -209,7 +211,9 @@ impl RawInfo { }; match self { - Self::Myself => { + Self::Myself { args } => { + let get_args = args.iter().map(|arg| arg.args(node)); + let ord = node.ord; let rawdev = format_ident!("__device_dts_ord_{}", ord); quote! { @@ -225,7 +229,7 @@ impl RawInfo { pub fn get_instance() -> Option<#device_id> { unsafe { let device = get_instance_raw(); - #device_id::new(&UNIQUE, device) + #device_id::new(&UNIQUE, device, #(#get_args),*) } } } @@ -291,6 +295,8 @@ impl RawInfo { pub enum ArgInfo { /// The arguments come from a 'reg' property. Reg, + /// A count of the number of child nodes. + ChildCount, } impl ArgInfo { @@ -303,6 +309,12 @@ impl ArgInfo { #(#reg),* } } + ArgInfo::ChildCount => { + let count = node.children.len(); + quote! { + #count + } + } } } } diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index 3819fde1..c2ea8c38 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -82,7 +82,7 @@ fn main() -> Result<()> { // Each DT node has a device entry that is a static. .allowlist_item("__device_dts_ord.*") .allowlist_function("device_.*") - .allowlist_function("led_strip.*") + .allowlist_function("led_.*") .allowlist_function("sys_.*") .allowlist_function("z_log.*") .allowlist_function("bt_.*") diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 7473eab6..3f8189b0 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -44,6 +44,7 @@ extern int errno; #include #include #include +#include /* * bindgen will only output #defined constants that resolve to simple numbers. These are some diff --git a/zephyr/src/device.rs b/zephyr/src/device.rs index 75316a5c..da7fb12a 100644 --- a/zephyr/src/device.rs +++ b/zephyr/src/device.rs @@ -13,6 +13,7 @@ use crate::sync::atomic::{AtomicBool, Ordering}; pub mod gpio; pub mod flash; pub mod uart; +pub mod led; pub mod led_strip; // Allow dead code, because it isn't required for a given build to have any devices. diff --git a/zephyr/src/device/led.rs b/zephyr/src/device/led.rs new file mode 100644 index 00000000..f00fb6eb --- /dev/null +++ b/zephyr/src/device/led.rs @@ -0,0 +1,44 @@ +//! LED driver, with support for PWMLED driver, with support for PWM. + +use crate::raw; +use crate::error::{Result, to_result_void}; + +use super::Unique; + +/// A simple led strip wrapper. +pub struct Leds { + /// The underlying device itself. + #[allow(dead_code)] + pub(crate) device: *const raw::device, + /// How many are configured in the DT. + pub(crate) count: usize, +} + +// This is send, safe with Zephyr. +unsafe impl Send for Leds { } + +impl Leds { + /// Constructor, used by the devicetree generated code. + #[allow(dead_code)] + pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device, count: usize) -> Option { + if !unique.once() { + return None; + } + + Some(Leds { device, count }) + } + + /// Return the number of LEDS. + pub fn len(&self) -> usize { + self.count + } + + /// Set the brightness of one of the described LEDs + pub unsafe fn set_brightness(&mut self, index: usize, value: u8) -> Result<()> { + to_result_void(unsafe { + raw::led_set_brightness(self.device, + index as u32, + value) + }) + } +} From f67bc90d9c4fcdb7b137648783f4b8510fe6e929 Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 19 Oct 2024 11:18:53 -0600 Subject: [PATCH 47/65] zephyr-build: Fix dt node names Fix the DT node names in the generated `dt_cfgs` entries to match the names of the nodes. Need to apply `fix_id` in a few places to make sure non-identifier characters are converted to underscores. Signed-off-by: David Brown --- zephyr-build/src/devicetree/output.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zephyr-build/src/devicetree/output.rs b/zephyr-build/src/devicetree/output.rs index c5e6e455..7eb9239a 100644 --- a/zephyr-build/src/devicetree/output.rs +++ b/zephyr-build/src/devicetree/output.rs @@ -147,7 +147,7 @@ impl Node { writeln!(write, "cargo:rustc-cfg=dt=\"{}\"", child_name)?; for prop in &child.properties { - prop.output_path(write, &child_name)?; + prop.output_path(write, &fix_id(&child_name))?; } child.output_path_walk(write, Some(&child_name))?; From 9dd2530fbc00d0faeeaf466062e737d21e8ef62e Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 22 Oct 2024 10:31:31 -0600 Subject: [PATCH 48/65] zephyr-sys: Bring thread_analyzer symbols in if selected Enable use of the thread analyzer, if it is enabled. Signed-off-by: David Brown --- zephyr-sys/build.rs | 1 + zephyr-sys/wrapper.h | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index c2ea8c38..ec985bf1 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -88,6 +88,7 @@ fn main() -> Result<()> { .allowlist_function("bt_.*") .allowlist_function("SEGGER.*") .allowlist_function("uart_.*") + .allowlist_function("thread_analyzer.*") .allowlist_item("E.*") .allowlist_item("K_.*") .allowlist_item("ZR_.*") diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 3f8189b0..658b766f 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -46,6 +46,10 @@ extern int errno; #include #include +#ifdef CONFIG_THREAD_ANALYZER +#include +#endif + /* * 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 From 0adff738035769e98b60703f9186f8b1bb875aac Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 29 Oct 2024 10:46:58 -0600 Subject: [PATCH 49/65] zephyr-sys: Add bindings for usb/hid Add these bindings so that app code can directly use these. Signed-off-by: David Brown --- zephyr-sys/build.rs | 2 ++ zephyr-sys/wrapper.h | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index ec985bf1..df5a9519 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -72,12 +72,14 @@ fn main() -> Result<()> { .allowlist_function("gpio_.*") .allowlist_function("flash_.*") .allowlist_function("usb_.*") + .allowlist_function("hid_.*") .allowlist_item("GPIO_.*") .allowlist_item("USB_.*") .allowlist_item("FLASH_.*") .allowlist_item("Z_.*") .allowlist_item("ZR_.*") .allowlist_item("K_.*") + .allowlist_item("hid_ops*") .allowlist_item("uart_line_ctrl") // Each DT node has a device entry that is a static. .allowlist_item("__device_dts_ord.*") diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 658b766f..509e2b84 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -45,6 +45,10 @@ extern int errno; #include #include #include +#include +#include +#include +#include #ifdef CONFIG_THREAD_ANALYZER #include From 6df0d15068ddb42d70396b847552a1ceb4af5a74 Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 31 Oct 2024 16:31:27 -0600 Subject: [PATCH 50/65] zephyr: device: uart: Add IRQ mode Add an `.into_irq()` to the Uart device that turns it into an interrupt driven interface. Currently, read is implemented, with a `.try_read()` method that will try, with a timeout, to read data from the interface. The methods are all marked as 'unsafe' currently, until a more thorough safety analysis can be made. Signed-off-by: David Brown --- zephyr/Cargo.toml | 5 + zephyr/src/device/uart.rs | 203 +++++++++++++++++++++++++++++++++++++- 2 files changed, 207 insertions(+), 1 deletion(-) diff --git a/zephyr/Cargo.toml b/zephyr/Cargo.toml index 0b508568..3a4b7f36 100644 --- a/zephyr/Cargo.toml +++ b/zephyr/Cargo.toml @@ -45,6 +45,11 @@ version = "0.2.2" # should be safe to build the crate even if the Rust code doesn't use it because of configs. features = ["alloc"] +# Gives us an ArrayDeque type to implement a basic ring buffer. +[dependencies.arraydeque] +version = "0.5.1" +default-features = false + # These are needed at build time. # Whether these need to be vendored is an open question. They are not # used by the core Zephyr tree, but are needed by zephyr applications. diff --git a/zephyr/src/device/uart.rs b/zephyr/src/device/uart.rs index 356b92a8..72ae2058 100644 --- a/zephyr/src/device/uart.rs +++ b/zephyr/src/device/uart.rs @@ -1,9 +1,20 @@ //! Simple (and unsafe) wrappers around USB devices. +// TODO! Remove this. +#![allow(dead_code)] +#![allow(unused_variables)] + +use arraydeque::ArrayDeque; + use crate::raw; use crate::error::{Error, Result, to_result_void, to_result}; +use crate::printkln; +use crate::sys::sync::Semaphore; +use crate::sync::{Arc, SpinMutex}; +use crate::time::{NoWait, Timeout}; -use core::ffi::{c_uchar, c_int}; +use core::ffi::{c_int, c_uchar, c_void}; +use core::ptr; use super::Unique; @@ -91,4 +102,194 @@ impl Uart { raw::uart_line_ctrl_get(self.device, item as u32, &mut result) }).map(|()| result) } + + /// Set one of the UART line control values. + pub unsafe fn line_ctrl_set(&mut self, item: LineControl, value: u32) -> Result<()> { + to_result_void(unsafe { + raw::uart_line_ctrl_set(self.device, item as u32, value) + }) + } + + /// Convert this UART into an async one. + pub unsafe fn into_async(self) -> Result { + UartAsync::new(self) + } + + /// Convert into an IRQ one. + pub unsafe fn into_irq(self) -> Result { + UartIrq::new(self) + } +} + +/// The uart is safe to Send, as long as it is only used from one thread at a time. As such, it is +/// not Sync. +unsafe impl Send for Uart {} + +/// This is the async interface to the uart. +/// +/// Until we can analyze this for safety, it will just be declared as unsafe. +/// +/// It is unclear from the docs what context this callback api is called from, so we will assume +/// that it might be called from an irq. As such, we'll need to use a critical-section and it's +/// mutex to protect the data. +pub struct UartAsync(); + +impl UartAsync { + /// Take a Uart device and turn it into an async interface. + /// + /// TODO: Return the uart back if this fails. + pub unsafe fn new(uart: Uart) -> Result { + let ret = unsafe { + raw::uart_callback_set(uart.device, Some(async_callback), ptr::null_mut()) + }; + to_result_void(ret)?; + Ok(UartAsync()) + } +} + +extern "C" fn async_callback( + _dev: *const raw::device, + _evt: *mut raw::uart_event, + _user_data: *mut c_void, +) { + printkln!("Async"); +} + +/// Size of the irq buffer used for UartIrq. +/// +/// TODO: Make this a parameter of the type. +const BUFFER_SIZE: usize = 256; + +/// The "outer" struct holds the semaphore, and the mutex. The semaphore has to live outside of the +/// mutex because it can only be waited on when the Mutex is not locked. +struct IrqOuterData { + read_sem: Semaphore, + inner: SpinMutex, +} + +/// Data for communication with the UART IRQ. +struct IrqInnerData { + /// The Ring buffer holding incoming and read data. + buffer: ArrayDeque, +} + +/// This is the irq-driven interface. +pub struct UartIrq { + /// The raw device. + device: *const raw::device, + /// Critical section protected data. + data: Arc, +} + +// UartIrq is also Send, !Sync, for the same reasons as for Uart. +unsafe impl Send for UartIrq {} + +impl UartIrq { + /// Convert uart into irq driven one. + pub unsafe fn new(uart: Uart) -> Result { + let data = Arc::new(IrqOuterData { + read_sem: Semaphore::new(0, 1)?, + inner: SpinMutex::new(IrqInnerData { + buffer: ArrayDeque::new(), + }), + }); + + // Clone the arc, and convert to a raw pointer, to give to the callback. + // This will leak the Arc (which prevents deallocation). + let data_raw = Arc::into_raw(data.clone()); + let data_raw = data_raw as *mut c_void; + + let ret = unsafe { + raw::uart_irq_callback_user_data_set(uart.device, Some(irq_callback), data_raw) + }; + to_result_void(ret)?; + // Should this be settable? + unsafe { + raw::uart_irq_tx_enable(uart.device); + raw::uart_irq_rx_enable(uart.device); + } + Ok(UartIrq { + device: uart.device, + data, + }) + } + + /// Get the underlying UART to be able to change line control and such. + pub unsafe fn inner(&mut self) -> Uart { + Uart { + device: self.device + } + } + + /// Attempt to read data from the UART into the buffer. If no data is available, it will + /// attempt, once, to wait using the given timeout. + /// + /// Returns the number of bytes that were read, with zero indicating that a timeout occurred. + pub unsafe fn try_read(&mut self, buf: &mut [u8], timeout: T) -> usize + where T: Into, + { + // Start with a read, before any blocking. + let count = self.data.try_read(buf); + if count > 0 { + return count; + } + + // Otherwise, wait for the semaphore. Ignore the result, as we will try to read again, in + // case there was a race. + let _ = self.data.read_sem.take(timeout); + + self.data.try_read(buf) + } +} + +impl IrqOuterData { + /// Try reading from the inner data, filling the buffer with as much data as makes sense. + /// Returns the number of bytes actually read, or Zero if none. + fn try_read(&self, buf: &mut [u8]) -> usize { + let mut inner = self.inner.lock().unwrap(); + let mut pos = 0; + while pos < buf.len() { + if let Some(elt) = inner.buffer.pop_front() { + buf[pos] = elt; + pos += 1; + } else { + break; + } + } + + if pos > 0 { + // Any time we do a read, clear the semaphore. + let _ = self.read_sem.take(NoWait); + } + pos + } +} + +extern "C" fn irq_callback( + dev: *const raw::device, + user_data: *mut c_void, +) { + // Convert our user data, back to the CS Mutex. + let outer = unsafe { &*(user_data as *const IrqOuterData) }; + let mut inner = outer.inner.lock().unwrap(); + + // TODO: Make this more efficient. + let mut byte = 0u8; + let mut did_read = false; + loop { + match unsafe { raw::uart_fifo_read(dev, &mut byte, 1) } { + 0 => break, + 1 => { + // TODO: should we warn about overflow here? + let _ = inner.buffer.push_back(byte); + did_read = true; + } + e => panic!("Uart fifo read not implemented: {}", e), + } + } + + // This is safe (and important) to do while the mutex is held. + if did_read { + outer.read_sem.give(); + } } From 0134ba31a4f540845a76871294a663a735e2778b Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 31 Oct 2024 17:20:51 -0600 Subject: [PATCH 51/65] zephyr: device: uart: Implement a blocking transmit A blocking write allows us to do the write without needing an intermediate copy of the data. The thread's buffer is directly copied into the uart's fifo. To make this safe, the write call must block until all of the data has been placed in the fifo. The uart.h irq interface is a little quirky. Namely, the uart fifo can only be accessed from the irq handler. To make this possible, the drivers will implement usually a soft interrupt to the irq handler, for the initial transmission. There is also an async interface in uart.h, but numerous devices don't support it. Signed-off-by: David Brown --- zephyr/src/device/uart.rs | 85 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/zephyr/src/device/uart.rs b/zephyr/src/device/uart.rs index 72ae2058..22ebd7ec 100644 --- a/zephyr/src/device/uart.rs +++ b/zephyr/src/device/uart.rs @@ -11,7 +11,7 @@ use crate::error::{Error, Result, to_result_void, to_result}; use crate::printkln; use crate::sys::sync::Semaphore; use crate::sync::{Arc, SpinMutex}; -use crate::time::{NoWait, Timeout}; +use crate::time::{Forever, NoWait, Timeout}; use core::ffi::{c_int, c_uchar, c_void}; use core::ptr; @@ -164,6 +164,7 @@ const BUFFER_SIZE: usize = 256; /// mutex because it can only be waited on when the Mutex is not locked. struct IrqOuterData { read_sem: Semaphore, + write_sem: Semaphore, inner: SpinMutex, } @@ -171,6 +172,27 @@ struct IrqOuterData { struct IrqInnerData { /// The Ring buffer holding incoming and read data. buffer: ArrayDeque, + /// Data to be written, if that is the case. + /// + /// If this is Some, then the irq should be enabled. + write: Option, +} + +/// Represents a slice of data that the irq is going to write. +struct WriteSlice { + data: *const u8, + len: usize, +} + +impl WriteSlice { + /// Add an offset to the beginning of this slice, returning a new slice. This is equivalent to + /// &item[count..] with a slice. + pub unsafe fn add(&self, count: usize) -> WriteSlice { + WriteSlice { + data: unsafe { self.data.add(count) }, + len: self.len - count, + } + } } /// This is the irq-driven interface. @@ -189,8 +211,10 @@ impl UartIrq { pub unsafe fn new(uart: Uart) -> Result { let data = Arc::new(IrqOuterData { read_sem: Semaphore::new(0, 1)?, + write_sem: Semaphore::new(0, 1)?, inner: SpinMutex::new(IrqInnerData { buffer: ArrayDeque::new(), + write: None, }), }); @@ -205,7 +229,7 @@ impl UartIrq { to_result_void(ret)?; // Should this be settable? unsafe { - raw::uart_irq_tx_enable(uart.device); + // raw::uart_irq_tx_enable(uart.device); raw::uart_irq_rx_enable(uart.device); } Ok(UartIrq { @@ -240,6 +264,36 @@ impl UartIrq { self.data.try_read(buf) } + + /// A blocking write to the UART. + /// + /// By making this blocking, we don't need to make an extra copy of the data. + /// + /// TODO: Async write. + pub unsafe fn write(&mut self, buf: &[u8]) { + if buf.len() == 0 { + return; + } + + // Make the data to be written available to the irq handler, and get it going. + { + let mut inner = self.data.inner.lock().unwrap(); + assert!(inner.write.is_none()); + + inner.write = Some(WriteSlice { + data: buf.as_ptr(), + len: buf.len(), + }); + + unsafe { raw::uart_irq_tx_enable(self.device) }; + } + + // Wait for the transmission to complete. This shouldn't be racy, as the irq shouldn't be + // giving the semaphore until there is 'write' data, and it has been consumed. + let _ = self.data.write_sem.take(Forever); + + // TODO: Should we check that the write actually finished? + } } impl IrqOuterData { @@ -292,4 +346,31 @@ extern "C" fn irq_callback( if did_read { outer.read_sem.give(); } + + // If there is data to write, ensure the fifo is full, and when we run out of data, disable the + // interrupt and signal the waiting thread. + if let Some(write) = inner.write.take() { + let count = unsafe { + raw::uart_fifo_fill(dev, write.data, write.len as i32) + }; + if count < 0 { + panic!("Incorrect use of device fifo"); + } + let count = count as usize; + + if count == write.len { + // The write finished, leave 'write' empty, and let the thread know we're done. + outer.write_sem.give(); + + // Disable the tx fifo, as we don't need it any more. + unsafe { raw::uart_irq_tx_disable(dev) }; + } else { + // We're not finished, so remember how much is left. + inner.write = Some(unsafe { write.add(count) }); + } + } + + unsafe { + raw::uart_irq_update(dev); + } } From 7b048a36147cea2d7d20f3a4b9bb1e85e67df9e3 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 1 Nov 2024 14:57:36 -0600 Subject: [PATCH 52/65] zephyr: device: uart: Add `is_dtr_set` convenience Signed-off-by: David Brown --- zephyr/src/device/uart.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zephyr/src/device/uart.rs b/zephyr/src/device/uart.rs index 22ebd7ec..c0d1f6df 100644 --- a/zephyr/src/device/uart.rs +++ b/zephyr/src/device/uart.rs @@ -110,6 +110,14 @@ impl Uart { }) } + /// Convenience, return if DTR is asserted. + pub unsafe fn is_dtr_set(&self) -> Result { + let ret = unsafe { + self.line_ctrl_get(LineControl::DTR)? + }; + Ok(ret == 1) + } + /// Convert this UART into an async one. pub unsafe fn into_async(self) -> Result { UartAsync::new(self) From 1a4ef5ad520c5c7054418659b87c7ef4c8844299 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 1 Nov 2024 14:58:17 -0600 Subject: [PATCH 53/65] zephyr: device: uart: Add timeout to 'write' entry Add a timeout parameter to the 'write' method on the UartIrq interface. The method now returns the number of bytes that were actually written. Signed-off-by: David Brown --- zephyr/src/device/uart.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/zephyr/src/device/uart.rs b/zephyr/src/device/uart.rs index c0d1f6df..133c83a3 100644 --- a/zephyr/src/device/uart.rs +++ b/zephyr/src/device/uart.rs @@ -11,7 +11,7 @@ use crate::error::{Error, Result, to_result_void, to_result}; use crate::printkln; use crate::sys::sync::Semaphore; use crate::sync::{Arc, SpinMutex}; -use crate::time::{Forever, NoWait, Timeout}; +use crate::time::{NoWait, Timeout}; use core::ffi::{c_int, c_uchar, c_void}; use core::ptr; @@ -278,9 +278,11 @@ impl UartIrq { /// By making this blocking, we don't need to make an extra copy of the data. /// /// TODO: Async write. - pub unsafe fn write(&mut self, buf: &[u8]) { + pub unsafe fn write(&mut self, buf: &[u8], timeout: T) -> usize + where T: Into + { if buf.len() == 0 { - return; + return 0; } // Make the data to be written available to the irq handler, and get it going. @@ -298,9 +300,26 @@ impl UartIrq { // Wait for the transmission to complete. This shouldn't be racy, as the irq shouldn't be // giving the semaphore until there is 'write' data, and it has been consumed. - let _ = self.data.write_sem.take(Forever); + let _ = self.data.write_sem.take(timeout); + + // Depending on the driver, there might be a race here. This would result in the above + // 'take' returning early, and no actual data being written. + + { + let mut inner = self.data.inner.lock().unwrap(); - // TODO: Should we check that the write actually finished? + if let Some(write) = inner.write.take() { + // First, make sure that no more interrupts will come in. + unsafe { raw::uart_irq_tx_disable(self.device) }; + + // The write did not complete, and this represents remaining data to write. + buf.len() - write.len + } else { + // The write completed, the rx irq should be disabled. Just return the whole + // buffer. + buf.len() + } + } } } From f889d601ecee40fad764ccf3ca6a50440abe2a6d Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 1 Nov 2024 15:00:21 -0600 Subject: [PATCH 54/65] zephyr: device: uart: Fix lifetimes of 'inner' method on UartIrq This method had a lifetime error, and allowed the inner uart to be leaked beyond its lifetime. This should prevent the returned device from outliving the UartIrq, or even being used after mutable methods on the UartIrq have been used. For now, don't even return a mutable method until we evaluate what should be permissible. This at least allows reading the line status. In addition, since the return Uart is not owned by the caller, it is not permissible to call any of the `into_*` methods. Signed-off-by: David Brown --- zephyr/src/device/uart.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/zephyr/src/device/uart.rs b/zephyr/src/device/uart.rs index 133c83a3..8d60b9c8 100644 --- a/zephyr/src/device/uart.rs +++ b/zephyr/src/device/uart.rs @@ -209,6 +209,8 @@ pub struct UartIrq { device: *const raw::device, /// Critical section protected data. data: Arc, + /// Interior wrapped device, to be able to hand out lifetime managed references to it. + uart: Uart, } // UartIrq is also Send, !Sync, for the same reasons as for Uart. @@ -243,14 +245,16 @@ impl UartIrq { Ok(UartIrq { device: uart.device, data, + uart, }) } /// Get the underlying UART to be able to change line control and such. - pub unsafe fn inner(&mut self) -> Uart { - Uart { - device: self.device - } + /// + /// TODO: This really should return something like `&Uart` to bind the lifetime. Otherwise the + /// user can continue to use the uart handle beyond the lifetime of the driver. + pub unsafe fn inner(&mut self) -> &Uart { + &self.uart } /// Attempt to read data from the UART into the buffer. If no data is available, it will From 6462a0b6d4efefc394e53add316309d917e8c10e Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 2 Nov 2024 08:33:35 -0600 Subject: [PATCH 55/65] zephyr: device: uart: Remove redundant 'device' field With the 'Uart' encapsulated within the `UartIrq`, having a separate field for device is redundant with the one inside of `Uart`. Remove this redundant field, and change the references to it to use the one inside the Uart. Signed-off-by: David Brown --- zephyr/src/device/uart.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/zephyr/src/device/uart.rs b/zephyr/src/device/uart.rs index 8d60b9c8..59ad487d 100644 --- a/zephyr/src/device/uart.rs +++ b/zephyr/src/device/uart.rs @@ -205,12 +205,10 @@ impl WriteSlice { /// This is the irq-driven interface. pub struct UartIrq { - /// The raw device. - device: *const raw::device, - /// Critical section protected data. - data: Arc, /// Interior wrapped device, to be able to hand out lifetime managed references to it. uart: Uart, + /// Critical section protected data. + data: Arc, } // UartIrq is also Send, !Sync, for the same reasons as for Uart. @@ -243,7 +241,6 @@ impl UartIrq { raw::uart_irq_rx_enable(uart.device); } Ok(UartIrq { - device: uart.device, data, uart, }) @@ -299,7 +296,7 @@ impl UartIrq { len: buf.len(), }); - unsafe { raw::uart_irq_tx_enable(self.device) }; + unsafe { raw::uart_irq_tx_enable(self.uart.device) }; } // Wait for the transmission to complete. This shouldn't be racy, as the irq shouldn't be @@ -314,7 +311,7 @@ impl UartIrq { if let Some(write) = inner.write.take() { // First, make sure that no more interrupts will come in. - unsafe { raw::uart_irq_tx_disable(self.device) }; + unsafe { raw::uart_irq_tx_disable(self.uart.device) }; // The write did not complete, and this represents remaining data to write. buf.len() - write.len From 0899f67bd6ad1c5ec88aaab8ad8040fcff2b00a7 Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 2 Nov 2024 08:38:25 -0600 Subject: [PATCH 56/65] zephyr: device: uart: Move UartIrq to own crate Move this specific type into its own crate. Signed-off-by: David Brown --- zephyr/src/device/uart.rs | 248 +------------------------------- zephyr/src/device/uart/irq.rs | 257 ++++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+), 245 deletions(-) create mode 100644 zephyr/src/device/uart/irq.rs diff --git a/zephyr/src/device/uart.rs b/zephyr/src/device/uart.rs index 59ad487d..2c30bc8e 100644 --- a/zephyr/src/device/uart.rs +++ b/zephyr/src/device/uart.rs @@ -4,20 +4,18 @@ #![allow(dead_code)] #![allow(unused_variables)] -use arraydeque::ArrayDeque; - use crate::raw; use crate::error::{Error, Result, to_result_void, to_result}; use crate::printkln; -use crate::sys::sync::Semaphore; -use crate::sync::{Arc, SpinMutex}; -use crate::time::{NoWait, Timeout}; use core::ffi::{c_int, c_uchar, c_void}; use core::ptr; use super::Unique; +mod irq; +pub use irq::UartIrq; + /// A wrapper around a UART device on Zephyr. pub struct Uart { /// The underlying device itself. @@ -162,243 +160,3 @@ extern "C" fn async_callback( ) { printkln!("Async"); } - -/// Size of the irq buffer used for UartIrq. -/// -/// TODO: Make this a parameter of the type. -const BUFFER_SIZE: usize = 256; - -/// The "outer" struct holds the semaphore, and the mutex. The semaphore has to live outside of the -/// mutex because it can only be waited on when the Mutex is not locked. -struct IrqOuterData { - read_sem: Semaphore, - write_sem: Semaphore, - inner: SpinMutex, -} - -/// Data for communication with the UART IRQ. -struct IrqInnerData { - /// The Ring buffer holding incoming and read data. - buffer: ArrayDeque, - /// Data to be written, if that is the case. - /// - /// If this is Some, then the irq should be enabled. - write: Option, -} - -/// Represents a slice of data that the irq is going to write. -struct WriteSlice { - data: *const u8, - len: usize, -} - -impl WriteSlice { - /// Add an offset to the beginning of this slice, returning a new slice. This is equivalent to - /// &item[count..] with a slice. - pub unsafe fn add(&self, count: usize) -> WriteSlice { - WriteSlice { - data: unsafe { self.data.add(count) }, - len: self.len - count, - } - } -} - -/// This is the irq-driven interface. -pub struct UartIrq { - /// Interior wrapped device, to be able to hand out lifetime managed references to it. - uart: Uart, - /// Critical section protected data. - data: Arc, -} - -// UartIrq is also Send, !Sync, for the same reasons as for Uart. -unsafe impl Send for UartIrq {} - -impl UartIrq { - /// Convert uart into irq driven one. - pub unsafe fn new(uart: Uart) -> Result { - let data = Arc::new(IrqOuterData { - read_sem: Semaphore::new(0, 1)?, - write_sem: Semaphore::new(0, 1)?, - inner: SpinMutex::new(IrqInnerData { - buffer: ArrayDeque::new(), - write: None, - }), - }); - - // Clone the arc, and convert to a raw pointer, to give to the callback. - // This will leak the Arc (which prevents deallocation). - let data_raw = Arc::into_raw(data.clone()); - let data_raw = data_raw as *mut c_void; - - let ret = unsafe { - raw::uart_irq_callback_user_data_set(uart.device, Some(irq_callback), data_raw) - }; - to_result_void(ret)?; - // Should this be settable? - unsafe { - // raw::uart_irq_tx_enable(uart.device); - raw::uart_irq_rx_enable(uart.device); - } - Ok(UartIrq { - data, - uart, - }) - } - - /// Get the underlying UART to be able to change line control and such. - /// - /// TODO: This really should return something like `&Uart` to bind the lifetime. Otherwise the - /// user can continue to use the uart handle beyond the lifetime of the driver. - pub unsafe fn inner(&mut self) -> &Uart { - &self.uart - } - - /// Attempt to read data from the UART into the buffer. If no data is available, it will - /// attempt, once, to wait using the given timeout. - /// - /// Returns the number of bytes that were read, with zero indicating that a timeout occurred. - pub unsafe fn try_read(&mut self, buf: &mut [u8], timeout: T) -> usize - where T: Into, - { - // Start with a read, before any blocking. - let count = self.data.try_read(buf); - if count > 0 { - return count; - } - - // Otherwise, wait for the semaphore. Ignore the result, as we will try to read again, in - // case there was a race. - let _ = self.data.read_sem.take(timeout); - - self.data.try_read(buf) - } - - /// A blocking write to the UART. - /// - /// By making this blocking, we don't need to make an extra copy of the data. - /// - /// TODO: Async write. - pub unsafe fn write(&mut self, buf: &[u8], timeout: T) -> usize - where T: Into - { - if buf.len() == 0 { - return 0; - } - - // Make the data to be written available to the irq handler, and get it going. - { - let mut inner = self.data.inner.lock().unwrap(); - assert!(inner.write.is_none()); - - inner.write = Some(WriteSlice { - data: buf.as_ptr(), - len: buf.len(), - }); - - unsafe { raw::uart_irq_tx_enable(self.uart.device) }; - } - - // Wait for the transmission to complete. This shouldn't be racy, as the irq shouldn't be - // giving the semaphore until there is 'write' data, and it has been consumed. - let _ = self.data.write_sem.take(timeout); - - // Depending on the driver, there might be a race here. This would result in the above - // 'take' returning early, and no actual data being written. - - { - let mut inner = self.data.inner.lock().unwrap(); - - if let Some(write) = inner.write.take() { - // First, make sure that no more interrupts will come in. - unsafe { raw::uart_irq_tx_disable(self.uart.device) }; - - // The write did not complete, and this represents remaining data to write. - buf.len() - write.len - } else { - // The write completed, the rx irq should be disabled. Just return the whole - // buffer. - buf.len() - } - } - } -} - -impl IrqOuterData { - /// Try reading from the inner data, filling the buffer with as much data as makes sense. - /// Returns the number of bytes actually read, or Zero if none. - fn try_read(&self, buf: &mut [u8]) -> usize { - let mut inner = self.inner.lock().unwrap(); - let mut pos = 0; - while pos < buf.len() { - if let Some(elt) = inner.buffer.pop_front() { - buf[pos] = elt; - pos += 1; - } else { - break; - } - } - - if pos > 0 { - // Any time we do a read, clear the semaphore. - let _ = self.read_sem.take(NoWait); - } - pos - } -} - -extern "C" fn irq_callback( - dev: *const raw::device, - user_data: *mut c_void, -) { - // Convert our user data, back to the CS Mutex. - let outer = unsafe { &*(user_data as *const IrqOuterData) }; - let mut inner = outer.inner.lock().unwrap(); - - // TODO: Make this more efficient. - let mut byte = 0u8; - let mut did_read = false; - loop { - match unsafe { raw::uart_fifo_read(dev, &mut byte, 1) } { - 0 => break, - 1 => { - // TODO: should we warn about overflow here? - let _ = inner.buffer.push_back(byte); - did_read = true; - } - e => panic!("Uart fifo read not implemented: {}", e), - } - } - - // This is safe (and important) to do while the mutex is held. - if did_read { - outer.read_sem.give(); - } - - // If there is data to write, ensure the fifo is full, and when we run out of data, disable the - // interrupt and signal the waiting thread. - if let Some(write) = inner.write.take() { - let count = unsafe { - raw::uart_fifo_fill(dev, write.data, write.len as i32) - }; - if count < 0 { - panic!("Incorrect use of device fifo"); - } - let count = count as usize; - - if count == write.len { - // The write finished, leave 'write' empty, and let the thread know we're done. - outer.write_sem.give(); - - // Disable the tx fifo, as we don't need it any more. - unsafe { raw::uart_irq_tx_disable(dev) }; - } else { - // We're not finished, so remember how much is left. - inner.write = Some(unsafe { write.add(count) }); - } - } - - unsafe { - raw::uart_irq_update(dev); - } -} diff --git a/zephyr/src/device/uart/irq.rs b/zephyr/src/device/uart/irq.rs new file mode 100644 index 00000000..2b0069c7 --- /dev/null +++ b/zephyr/src/device/uart/irq.rs @@ -0,0 +1,257 @@ +//! Simple (and unsafe) wrappers around USB devices. + +// TODO! Remove this. +#![allow(dead_code)] +#![allow(unused_variables)] + +use arraydeque::ArrayDeque; + +use crate::raw; +use crate::error::{Result, to_result_void}; +use crate::sys::sync::Semaphore; +use crate::sync::{Arc, SpinMutex}; +use crate::time::{NoWait, Timeout}; + +use core::ffi::c_void; + +use super::Uart; + +/// Size of the irq buffer used for UartIrq. +/// +/// TODO: Make this a parameter of the type. +const BUFFER_SIZE: usize = 256; + +/// The "outer" struct holds the semaphore, and the mutex. The semaphore has to live outside of the +/// mutex because it can only be waited on when the Mutex is not locked. +struct IrqOuterData { + read_sem: Semaphore, + write_sem: Semaphore, + inner: SpinMutex, +} + +/// Data for communication with the UART IRQ. +struct IrqInnerData { + /// The Ring buffer holding incoming and read data. + buffer: ArrayDeque, + /// Data to be written, if that is the case. + /// + /// If this is Some, then the irq should be enabled. + write: Option, +} + +/// Represents a slice of data that the irq is going to write. +struct WriteSlice { + data: *const u8, + len: usize, +} + +impl WriteSlice { + /// Add an offset to the beginning of this slice, returning a new slice. This is equivalent to + /// &item[count..] with a slice. + pub unsafe fn add(&self, count: usize) -> WriteSlice { + WriteSlice { + data: unsafe { self.data.add(count) }, + len: self.len - count, + } + } +} + +/// This is the irq-driven interface. +pub struct UartIrq { + /// Interior wrapped device, to be able to hand out lifetime managed references to it. + uart: Uart, + /// Critical section protected data. + data: Arc, +} + +// UartIrq is also Send, !Sync, for the same reasons as for Uart. +unsafe impl Send for UartIrq {} + +impl UartIrq { + /// Convert uart into irq driven one. + pub unsafe fn new(uart: Uart) -> Result { + let data = Arc::new(IrqOuterData { + read_sem: Semaphore::new(0, 1)?, + write_sem: Semaphore::new(0, 1)?, + inner: SpinMutex::new(IrqInnerData { + buffer: ArrayDeque::new(), + write: None, + }), + }); + + // Clone the arc, and convert to a raw pointer, to give to the callback. + // This will leak the Arc (which prevents deallocation). + let data_raw = Arc::into_raw(data.clone()); + let data_raw = data_raw as *mut c_void; + + let ret = unsafe { + raw::uart_irq_callback_user_data_set(uart.device, Some(irq_callback), data_raw) + }; + to_result_void(ret)?; + // Should this be settable? + unsafe { + // raw::uart_irq_tx_enable(uart.device); + raw::uart_irq_rx_enable(uart.device); + } + Ok(UartIrq { + data, + uart, + }) + } + + /// Get the underlying UART to be able to change line control and such. + /// + /// TODO: This really should return something like `&Uart` to bind the lifetime. Otherwise the + /// user can continue to use the uart handle beyond the lifetime of the driver. + pub unsafe fn inner(&mut self) -> &Uart { + &self.uart + } + + /// Attempt to read data from the UART into the buffer. If no data is available, it will + /// attempt, once, to wait using the given timeout. + /// + /// Returns the number of bytes that were read, with zero indicating that a timeout occurred. + pub unsafe fn try_read(&mut self, buf: &mut [u8], timeout: T) -> usize + where T: Into, + { + // Start with a read, before any blocking. + let count = self.data.try_read(buf); + if count > 0 { + return count; + } + + // Otherwise, wait for the semaphore. Ignore the result, as we will try to read again, in + // case there was a race. + let _ = self.data.read_sem.take(timeout); + + self.data.try_read(buf) + } + + /// A blocking write to the UART. + /// + /// By making this blocking, we don't need to make an extra copy of the data. + /// + /// TODO: Async write. + pub unsafe fn write(&mut self, buf: &[u8], timeout: T) -> usize + where T: Into + { + if buf.len() == 0 { + return 0; + } + + // Make the data to be written available to the irq handler, and get it going. + { + let mut inner = self.data.inner.lock().unwrap(); + assert!(inner.write.is_none()); + + inner.write = Some(WriteSlice { + data: buf.as_ptr(), + len: buf.len(), + }); + + unsafe { raw::uart_irq_tx_enable(self.uart.device) }; + } + + // Wait for the transmission to complete. This shouldn't be racy, as the irq shouldn't be + // giving the semaphore until there is 'write' data, and it has been consumed. + let _ = self.data.write_sem.take(timeout); + + // Depending on the driver, there might be a race here. This would result in the above + // 'take' returning early, and no actual data being written. + + { + let mut inner = self.data.inner.lock().unwrap(); + + if let Some(write) = inner.write.take() { + // First, make sure that no more interrupts will come in. + unsafe { raw::uart_irq_tx_disable(self.uart.device) }; + + // The write did not complete, and this represents remaining data to write. + buf.len() - write.len + } else { + // The write completed, the rx irq should be disabled. Just return the whole + // buffer. + buf.len() + } + } + } +} + +impl IrqOuterData { + /// Try reading from the inner data, filling the buffer with as much data as makes sense. + /// Returns the number of bytes actually read, or Zero if none. + fn try_read(&self, buf: &mut [u8]) -> usize { + let mut inner = self.inner.lock().unwrap(); + let mut pos = 0; + while pos < buf.len() { + if let Some(elt) = inner.buffer.pop_front() { + buf[pos] = elt; + pos += 1; + } else { + break; + } + } + + if pos > 0 { + // Any time we do a read, clear the semaphore. + let _ = self.read_sem.take(NoWait); + } + pos + } +} + +extern "C" fn irq_callback( + dev: *const raw::device, + user_data: *mut c_void, +) { + // Convert our user data, back to the CS Mutex. + let outer = unsafe { &*(user_data as *const IrqOuterData) }; + let mut inner = outer.inner.lock().unwrap(); + + // TODO: Make this more efficient. + let mut byte = 0u8; + let mut did_read = false; + loop { + match unsafe { raw::uart_fifo_read(dev, &mut byte, 1) } { + 0 => break, + 1 => { + // TODO: should we warn about overflow here? + let _ = inner.buffer.push_back(byte); + did_read = true; + } + e => panic!("Uart fifo read not implemented: {}", e), + } + } + + // This is safe (and important) to do while the mutex is held. + if did_read { + outer.read_sem.give(); + } + + // If there is data to write, ensure the fifo is full, and when we run out of data, disable the + // interrupt and signal the waiting thread. + if let Some(write) = inner.write.take() { + let count = unsafe { + raw::uart_fifo_fill(dev, write.data, write.len as i32) + }; + if count < 0 { + panic!("Incorrect use of device fifo"); + } + let count = count as usize; + + if count == write.len { + // The write finished, leave 'write' empty, and let the thread know we're done. + outer.write_sem.give(); + + // Disable the tx fifo, as we don't need it any more. + unsafe { raw::uart_irq_tx_disable(dev) }; + } else { + // We're not finished, so remember how much is left. + inner.write = Some(unsafe { write.add(count) }); + } + } + + unsafe { + raw::uart_irq_update(dev); + } +} From 673c5c5a745cd757747b1ff58accaf0e448cf12c Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 2 Nov 2024 08:46:47 -0600 Subject: [PATCH 57/65] zephyr: device: uart: wip add ring size parameters Add ring size parameters to the UartIrq type. These will later be used to migrate the interface to an async interface (not rust asyc). Signed-off-by: David Brown --- zephyr/src/device/uart.rs | 5 +++-- zephyr/src/device/uart/irq.rs | 16 +++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/zephyr/src/device/uart.rs b/zephyr/src/device/uart.rs index 2c30bc8e..d718934f 100644 --- a/zephyr/src/device/uart.rs +++ b/zephyr/src/device/uart.rs @@ -121,8 +121,9 @@ impl Uart { UartAsync::new(self) } - /// Convert into an IRQ one. - pub unsafe fn into_irq(self) -> Result { + /// Convert into an IRQ one. The parameters `WS` and `RS` set the size of the rings for write + /// and read respectively. + pub unsafe fn into_irq(self) -> Result> { UartIrq::new(self) } } diff --git a/zephyr/src/device/uart/irq.rs b/zephyr/src/device/uart/irq.rs index 2b0069c7..8703ea97 100644 --- a/zephyr/src/device/uart/irq.rs +++ b/zephyr/src/device/uart/irq.rs @@ -56,8 +56,14 @@ impl WriteSlice { } } -/// This is the irq-driven interface. -pub struct UartIrq { +/// An interface to the UART, that uses the "legacy" IRQ API. +/// +/// The interface is parameterized by two value, `WS` is the number of elements in the write ring, +/// and `RS` is the number of elements in the read ring. Each direction will have two rings, one +/// for pending operations, and the other for completed operations. Setting these to 2 is +/// sufficient to avoid stalling writes or dropping reads, but requires the application attend to +/// the buffers. +pub struct UartIrq { /// Interior wrapped device, to be able to hand out lifetime managed references to it. uart: Uart, /// Critical section protected data. @@ -65,11 +71,11 @@ pub struct UartIrq { } // UartIrq is also Send, !Sync, for the same reasons as for Uart. -unsafe impl Send for UartIrq {} +unsafe impl Send for UartIrq {} -impl UartIrq { +impl UartIrq { /// Convert uart into irq driven one. - pub unsafe fn new(uart: Uart) -> Result { + pub unsafe fn new(uart: Uart) -> Result { let data = Arc::new(IrqOuterData { read_sem: Semaphore::new(0, 1)?, write_sem: Semaphore::new(0, 1)?, From 65c77e8e570c8ff77b3bcbfc74bda2c28cc1dab3 Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 2 Nov 2024 10:46:45 -0600 Subject: [PATCH 58/65] zephyr: device: uart: irq: Replace write with async API Update the writing to be an async interface. Instead of a blocking write, there is a `write_enqueue` to add the message, and a `write_wait` which is a blocking wait for a write to handle. For this to work, the UartIrq needs to own the buffer for the duration of the write. At this point, the buffers are just `Vec`, but a future change will make this more general, so that, for example, Pooled buffers can be used. The buffer should never be dropped within this code. Signed-off-by: David Brown --- zephyr/src/device/uart/irq.rs | 193 +++++++++++++++++++++------------- 1 file changed, 119 insertions(+), 74 deletions(-) diff --git a/zephyr/src/device/uart/irq.rs b/zephyr/src/device/uart/irq.rs index 8703ea97..0d8e3316 100644 --- a/zephyr/src/device/uart/irq.rs +++ b/zephyr/src/device/uart/irq.rs @@ -4,6 +4,12 @@ #![allow(dead_code)] #![allow(unused_variables)] +extern crate alloc; + +// TODO: This should be a generic Buffer type indicating some type of owned buffer, with Vec as one +// possible implementation. +use alloc::vec::Vec; + use arraydeque::ArrayDeque; use crate::raw; @@ -13,6 +19,8 @@ use crate::sync::{Arc, SpinMutex}; use crate::time::{NoWait, Timeout}; use core::ffi::c_void; +use core::ops::Range; +use core::{fmt, result}; use super::Uart; @@ -23,20 +31,38 @@ const BUFFER_SIZE: usize = 256; /// The "outer" struct holds the semaphore, and the mutex. The semaphore has to live outside of the /// mutex because it can only be waited on when the Mutex is not locked. -struct IrqOuterData { +struct IrqOuterData { read_sem: Semaphore, + /// Write semaphore. This should **exactly** match the number of elements in `write_dones`. write_sem: Semaphore, - inner: SpinMutex, + inner: SpinMutex>, } /// Data for communication with the UART IRQ. -struct IrqInnerData { +struct IrqInnerData { /// The Ring buffer holding incoming and read data. buffer: ArrayDeque, - /// Data to be written, if that is the case. - /// - /// If this is Some, then the irq should be enabled. - write: Option, + /// Write request. The 'head' is the one being worked on. Once completed, they will move into + /// the completion queue. + write_requests: ArrayDeque, + /// Completed writes. + write_dones: ArrayDeque, +} + +/// A single requested write. This is a managed buffer, and a range of the buffer to actually +/// write. The write is completed when `pos` == `len`. +struct WriteRequest { + /// The data to write. + data: Vec, + /// What part to write. + part: Range, +} + +/// A completed write. All the requested data will have been written, and this returns the buffer +/// to the user. +struct WriteDone { + /// The returned buffer. + data: Vec, } /// Represents a slice of data that the irq is going to write. @@ -56,6 +82,19 @@ impl WriteSlice { } } +/// The error type from write requests. Used to return the buffer. +pub struct WriteError(pub Vec); + +// The default Debug for Write error will print the whole buffer, which isn't particularly useful. +impl fmt::Debug for WriteError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "WriteError(...)") + } +} + +/// The wait for write completion timed out. +pub struct WriteWaitTimedOut; + /// An interface to the UART, that uses the "legacy" IRQ API. /// /// The interface is parameterized by two value, `WS` is the number of elements in the write ring, @@ -67,7 +106,7 @@ pub struct UartIrq { /// Interior wrapped device, to be able to hand out lifetime managed references to it. uart: Uart, /// Critical section protected data. - data: Arc, + data: Arc>, } // UartIrq is also Send, !Sync, for the same reasons as for Uart. @@ -77,11 +116,12 @@ impl UartIrq { /// Convert uart into irq driven one. pub unsafe fn new(uart: Uart) -> Result { let data = Arc::new(IrqOuterData { - read_sem: Semaphore::new(0, 1)?, - write_sem: Semaphore::new(0, 1)?, + read_sem: Semaphore::new(0, RS as u32)?, + write_sem: Semaphore::new(0, WS as u32)?, inner: SpinMutex::new(IrqInnerData { buffer: ArrayDeque::new(), - write: None, + write_requests: ArrayDeque::new(), + write_dones: ArrayDeque::new(), }), }); @@ -91,7 +131,7 @@ impl UartIrq { let data_raw = data_raw as *mut c_void; let ret = unsafe { - raw::uart_irq_callback_user_data_set(uart.device, Some(irq_callback), data_raw) + raw::uart_irq_callback_user_data_set(uart.device, Some(irq_callback::), data_raw) }; to_result_void(ret)?; // Should this be settable? @@ -133,57 +173,54 @@ impl UartIrq { self.data.try_read(buf) } - /// A blocking write to the UART. + /// Enqueue a single write request. /// - /// By making this blocking, we don't need to make an extra copy of the data. - /// - /// TODO: Async write. - pub unsafe fn write(&mut self, buf: &[u8], timeout: T) -> usize - where T: Into - { - if buf.len() == 0 { - return 0; + /// If the queue is full, the `WriteError` returned will return the buffer. + pub fn write_enqueue(&mut self, data: Vec, part: Range) -> result::Result<(), WriteError> { + let mut inner = self.data.inner.lock().unwrap(); + + let req = WriteRequest { data, part }; + match inner.write_requests.push_back(req) { + Ok(()) => { + // Make sure the write actually happens. This needs to happen for the first message + // queued, if some were already queued, it should already be enabled. + if inner.write_requests.len() == 1 { + unsafe { raw::uart_irq_tx_enable(self.uart.device); } + } + Ok(()) + } + Err(e) => Err(WriteError(e.element.data)), } + } - // Make the data to be written available to the irq handler, and get it going. - { - let mut inner = self.data.inner.lock().unwrap(); - assert!(inner.write.is_none()); + /// Return true if the write queue is full. + /// + /// There is a race between this and write_enqueue, but only in the safe direction (this may + /// return false, but a write may complete before being able to call write_enqueue). + pub fn write_is_full(&self) -> bool { + let inner = self.data.inner.lock().unwrap(); - inner.write = Some(WriteSlice { - data: buf.as_ptr(), - len: buf.len(), - }); + inner.write_requests.is_full() + } - unsafe { raw::uart_irq_tx_enable(self.uart.device) }; + /// Retrieve a write completion. + /// + /// Waits up to `timeout` for a write to complete, and returns the buffer. + pub fn write_wait(&mut self, timeout: T) -> result::Result, WriteWaitTimedOut> + where T: Into, + { + match self.data.write_sem.take(timeout) { + Ok(()) => (), + // TODO: Handle other errors? + Err(_) => return Err(WriteWaitTimedOut), } - // Wait for the transmission to complete. This shouldn't be racy, as the irq shouldn't be - // giving the semaphore until there is 'write' data, and it has been consumed. - let _ = self.data.write_sem.take(timeout); - - // Depending on the driver, there might be a race here. This would result in the above - // 'take' returning early, and no actual data being written. - - { - let mut inner = self.data.inner.lock().unwrap(); - - if let Some(write) = inner.write.take() { - // First, make sure that no more interrupts will come in. - unsafe { raw::uart_irq_tx_disable(self.uart.device) }; - - // The write did not complete, and this represents remaining data to write. - buf.len() - write.len - } else { - // The write completed, the rx irq should be disabled. Just return the whole - // buffer. - buf.len() - } - } + let mut inner = self.data.inner.lock().unwrap(); + Ok(inner.write_dones.pop_front().expect("Write done empty, despite semaphore").data) } } -impl IrqOuterData { +impl IrqOuterData { /// Try reading from the inner data, filling the buffer with as much data as makes sense. /// Returns the number of bytes actually read, or Zero if none. fn try_read(&self, buf: &mut [u8]) -> usize { @@ -206,12 +243,12 @@ impl IrqOuterData { } } -extern "C" fn irq_callback( +extern "C" fn irq_callback( dev: *const raw::device, user_data: *mut c_void, ) { // Convert our user data, back to the CS Mutex. - let outer = unsafe { &*(user_data as *const IrqOuterData) }; + let outer = unsafe { &*(user_data as *const IrqOuterData) }; let mut inner = outer.inner.lock().unwrap(); // TODO: Make this more efficient. @@ -234,26 +271,34 @@ extern "C" fn irq_callback( outer.read_sem.give(); } - // If there is data to write, ensure the fifo is full, and when we run out of data, disable the - // interrupt and signal the waiting thread. - if let Some(write) = inner.write.take() { - let count = unsafe { - raw::uart_fifo_fill(dev, write.data, write.len as i32) - }; - if count < 0 { - panic!("Incorrect use of device fifo"); - } - let count = count as usize; - - if count == write.len { - // The write finished, leave 'write' empty, and let the thread know we're done. - outer.write_sem.give(); - - // Disable the tx fifo, as we don't need it any more. - unsafe { raw::uart_irq_tx_disable(dev) }; + // Handle any write requests. + loop { + if let Some(mut req) = inner.write_requests.pop_front() { + if req.part.is_empty() { + // This request is empty. Move to completion. + inner.write_dones.push_back(WriteDone { data: req.data }) + .expect("write done queue is full"); + outer.write_sem.give(); + } else { + // Try to write this part of the data. + let piece = &req.data[req.part.clone()]; + let count = unsafe { + raw::uart_fifo_fill(dev, piece.as_ptr(), piece.len() as i32) + }; + if count < 0 { + panic!("Incorrect use of device fifo"); + } + let count = count as usize; + + // Adjust the part. The next through the loop will notice the write being done. + req.part.start += count; + inner.write_requests.push_front(req) + .expect("Unexpected write_dones overflow"); + } } else { - // We're not finished, so remember how much is left. - inner.write = Some(unsafe { write.add(count) }); + // No work. Turn off the irq, and stop. + unsafe { raw::uart_irq_tx_disable(dev); } + break; } } From e8c2a2fcff4db2b7f2196e71867b939fae18c483 Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 2 Nov 2024 11:22:00 -0600 Subject: [PATCH 59/65] zephyr: device: uart: irq: panic on drop Because this interface has registered IRQs, prevent drops. They are probably safe due to the use of the leaked Arc, but the data is in fact leaked. For now, just cause Drop to panic, as normal use won't ever drop this. Signed-off-by: David Brown --- zephyr/src/device/uart/irq.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/zephyr/src/device/uart/irq.rs b/zephyr/src/device/uart/irq.rs index 0d8e3316..4f5e753d 100644 --- a/zephyr/src/device/uart/irq.rs +++ b/zephyr/src/device/uart/irq.rs @@ -220,6 +220,15 @@ impl UartIrq { } } +// TODO: It could actually be possible to implement drop, but we would need to make sure the irq +// handlers are deregistered. These is also the issue of the buffers being dropped. For now, just +// panic, as this isn't normal. +impl Drop for UartIrq { + fn drop(&mut self) { + panic!("UartIrq dropped"); + } +} + impl IrqOuterData { /// Try reading from the inner data, filling the buffer with as much data as makes sense. /// Returns the number of bytes actually read, or Zero if none. From d745eb86dc99706fe35f7080265fa0a94eec8acb Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 2 Nov 2024 13:34:31 -0600 Subject: [PATCH 60/65] zephyr: device: uart: irq: Implement async read Change the read interface to also be async. The user must enqueue one or more buffers for the driver to place data into. Signed-off-by: David Brown --- zephyr/src/device/uart/irq.rs | 223 ++++++++++++++++++++++------------ 1 file changed, 145 insertions(+), 78 deletions(-) diff --git a/zephyr/src/device/uart/irq.rs b/zephyr/src/device/uart/irq.rs index 4f5e753d..2874809e 100644 --- a/zephyr/src/device/uart/irq.rs +++ b/zephyr/src/device/uart/irq.rs @@ -16,7 +16,7 @@ use crate::raw; use crate::error::{Result, to_result_void}; use crate::sys::sync::Semaphore; use crate::sync::{Arc, SpinMutex}; -use crate::time::{NoWait, Timeout}; +use crate::time::Timeout; use core::ffi::c_void; use core::ops::Range; @@ -24,11 +24,6 @@ use core::{fmt, result}; use super::Uart; -/// Size of the irq buffer used for UartIrq. -/// -/// TODO: Make this a parameter of the type. -const BUFFER_SIZE: usize = 256; - /// The "outer" struct holds the semaphore, and the mutex. The semaphore has to live outside of the /// mutex because it can only be waited on when the Mutex is not locked. struct IrqOuterData { @@ -40,13 +35,15 @@ struct IrqOuterData { /// Data for communication with the UART IRQ. struct IrqInnerData { - /// The Ring buffer holding incoming and read data. - buffer: ArrayDeque, /// Write request. The 'head' is the one being worked on. Once completed, they will move into /// the completion queue. write_requests: ArrayDeque, /// Completed writes. write_dones: ArrayDeque, + /// Read requests. The 'head' is the one data will come into. + read_requests: ArrayDeque, + /// Completed writes. Generally, these will be full, but a read might move an early one here. + read_dones: ArrayDeque, } /// A single requested write. This is a managed buffer, and a range of the buffer to actually @@ -65,23 +62,41 @@ struct WriteDone { data: Vec, } -/// Represents a slice of data that the irq is going to write. -struct WriteSlice { - data: *const u8, +/// A single read request. This is a buffer to hold data being read, along with the part still +/// valid to hold data. +struct ReadRequest { + /// The data to read. + data: Vec, + /// How much of the data has been read so far. len: usize, } -impl WriteSlice { - /// Add an offset to the beginning of this slice, returning a new slice. This is equivalent to - /// &item[count..] with a slice. - pub unsafe fn add(&self, count: usize) -> WriteSlice { - WriteSlice { - data: unsafe { self.data.add(count) }, - len: self.len - count, - } +impl ReadRequest { + fn into_done(self) -> ReadDone { + ReadDone { data: self.data, len: self.len } + } +} + +/// A completed read. +struct ReadDone { + /// The buffer holding the data. + data: Vec, + /// How much of `data` contains read data. Should always be > 0. + len: usize, +} + +impl ReadDone { + fn into_result(self) -> ReadResult { + ReadResult { data: self.data, len: self.len } } } +/// The result of a read. +pub struct ReadResult { + data: Vec, + len: usize, +} + /// The error type from write requests. Used to return the buffer. pub struct WriteError(pub Vec); @@ -92,9 +107,22 @@ impl fmt::Debug for WriteError { } } +/// The error type from read requests. Used to return the buffer. +pub struct ReadError(pub Vec); + +// The default Debug for Write error will print the whole buffer, which isn't particularly useful. +impl fmt::Debug for ReadError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ReadError(...)") + } +} + /// The wait for write completion timed out. pub struct WriteWaitTimedOut; +/// The wait for read completion timed out. +pub struct ReadWaitTimedOut; + /// An interface to the UART, that uses the "legacy" IRQ API. /// /// The interface is parameterized by two value, `WS` is the number of elements in the write ring, @@ -119,9 +147,10 @@ impl UartIrq { read_sem: Semaphore::new(0, RS as u32)?, write_sem: Semaphore::new(0, WS as u32)?, inner: SpinMutex::new(IrqInnerData { - buffer: ArrayDeque::new(), write_requests: ArrayDeque::new(), write_dones: ArrayDeque::new(), + read_requests: ArrayDeque::new(), + read_dones: ArrayDeque::new(), }), }); @@ -153,26 +182,6 @@ impl UartIrq { &self.uart } - /// Attempt to read data from the UART into the buffer. If no data is available, it will - /// attempt, once, to wait using the given timeout. - /// - /// Returns the number of bytes that were read, with zero indicating that a timeout occurred. - pub unsafe fn try_read(&mut self, buf: &mut [u8], timeout: T) -> usize - where T: Into, - { - // Start with a read, before any blocking. - let count = self.data.try_read(buf); - if count > 0 { - return count; - } - - // Otherwise, wait for the semaphore. Ignore the result, as we will try to read again, in - // case there was a race. - let _ = self.data.read_sem.take(timeout); - - self.data.try_read(buf) - } - /// Enqueue a single write request. /// /// If the queue is full, the `WriteError` returned will return the buffer. @@ -218,6 +227,63 @@ impl UartIrq { let mut inner = self.data.inner.lock().unwrap(); Ok(inner.write_dones.pop_front().expect("Write done empty, despite semaphore").data) } + + /// Enqueue a buffer for reading data. + /// + /// Enqueues a buffer to hold read data. Can enqueue up to RS of these. + pub fn read_enqueue(&mut self, data: Vec) -> result::Result<(), ReadError> { + let mut inner = self.data.inner.lock().unwrap(); + + let req = ReadRequest { data, len: 0 }; + match inner.read_requests.push_back(req) { + Ok(()) => { + // Enable the rx fifo so incoming data will be placed. + if inner.read_requests.len() == 1 { + unsafe { raw::uart_irq_rx_enable(self.uart.device); } + } + Ok(()) + } + Err(e) => Err(ReadError(e.element.data)) + } + } + + /// Wait up to 'timeout' for a read to complete, and returns the data. + /// + /// Note that if there is a buffer that has been partially filled, this will return that buffer, + /// so that there isn't a delay with read data. + pub fn read_wait(&mut self, timeout: T) -> result::Result + where T: Into, + { + // If there is no read data available, see if we have a partial block we can consider a + // completion. + let mut inner = self.data.inner.lock().unwrap(); + if inner.read_dones.is_empty() { + if let Some(req) = inner.read_requests.pop_front() { + // TODO: User defined threshold? + if req.len > 0 { + // Queue this up as a completion. + inner.read_dones.push_back(req.into_done()).unwrap(); + + // Signal the sem, as we've pushed. + self.data.read_sem.give(); + } else { + // Stick it back on the queue. + inner.read_requests.push_front(req).unwrap(); + } + } + } + drop(inner); + + match self.data.read_sem.take(timeout) { + Ok(()) => (), + // TODO: Handle other errors? + Err(_) => return Err(ReadWaitTimedOut), + } + + let mut inner = self.data.inner.lock().unwrap(); + let done = inner.read_dones.pop_front().expect("Semaphore mismatched with read done queue"); + Ok(done.into_result()) + } } // TODO: It could actually be possible to implement drop, but we would need to make sure the irq @@ -229,29 +295,6 @@ impl Drop for UartIrq { } } -impl IrqOuterData { - /// Try reading from the inner data, filling the buffer with as much data as makes sense. - /// Returns the number of bytes actually read, or Zero if none. - fn try_read(&self, buf: &mut [u8]) -> usize { - let mut inner = self.inner.lock().unwrap(); - let mut pos = 0; - while pos < buf.len() { - if let Some(elt) = inner.buffer.pop_front() { - buf[pos] = elt; - pos += 1; - } else { - break; - } - } - - if pos > 0 { - // Any time we do a read, clear the semaphore. - let _ = self.read_sem.take(NoWait); - } - pos - } -} - extern "C" fn irq_callback( dev: *const raw::device, user_data: *mut c_void, @@ -260,26 +303,45 @@ extern "C" fn irq_callback( let outer = unsafe { &*(user_data as *const IrqOuterData) }; let mut inner = outer.inner.lock().unwrap(); - // TODO: Make this more efficient. - let mut byte = 0u8; - let mut did_read = false; + // Handle any read requests. loop { - match unsafe { raw::uart_fifo_read(dev, &mut byte, 1) } { - 0 => break, - 1 => { - // TODO: should we warn about overflow here? - let _ = inner.buffer.push_back(byte); - did_read = true; + if let Some(mut req) = inner.read_requests.pop_front() { + if req.len == req.data.len() { + // This buffer is full, make it a completion. + inner.read_dones.push_back(req.into_done()) + .expect("Completion queue not large enough"); + outer.read_sem.give(); + } else { + // Read as much as we can. + let piece = &mut req.data[req.len..]; + let count = unsafe { + raw::uart_fifo_read(dev, piece.as_mut_ptr(), piece.len() as i32) + }; + if count < 0 { + panic!("Incorrect use of read"); + } + let count = count as usize; + + // Adjust the piece. The next time through the loop will notice if the write is + // full. + req.len += count; + inner.read_requests.push_front(req) + .expect("Unexpected read request overflow"); + + if count == 0 { + // There is no more data in the fifo. + break; + } } - e => panic!("Uart fifo read not implemented: {}", e), + } else { + // No place to store results. Turn off the irq and stop. + // The doc's don't describe this as being possible, but hopefully the implementations + // are sane. + unsafe { raw::uart_irq_rx_disable(dev); } + break; } } - // This is safe (and important) to do while the mutex is held. - if did_read { - outer.read_sem.give(); - } - // Handle any write requests. loop { if let Some(mut req) = inner.write_requests.pop_front() { @@ -303,6 +365,11 @@ extern "C" fn irq_callback( req.part.start += count; inner.write_requests.push_front(req) .expect("Unexpected write_dones overflow"); + + // If the count reaches 0, the fifo is full. + if count == 0 { + break; + } } } else { // No work. Turn off the irq, and stop. From 4eea74ceebba4034eb46dab69cc908c65935eb49 Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 2 Nov 2024 13:46:00 -0600 Subject: [PATCH 61/65] zephyr: device: uart: irq: Add accessors for async read The result from the async_write holds the buffer, but the fields are private. Provide two accessors to make this result useful: - as_slice: returns a slice of the useful part of the buffer - into_inner: Transforms into the buffer, so it can be reused. Signed-off-by: David Brown --- zephyr/src/device/uart/irq.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/zephyr/src/device/uart/irq.rs b/zephyr/src/device/uart/irq.rs index 2874809e..32af6f6e 100644 --- a/zephyr/src/device/uart/irq.rs +++ b/zephyr/src/device/uart/irq.rs @@ -97,6 +97,16 @@ pub struct ReadResult { len: usize, } +impl ReadResult { + pub fn as_slice(&self) -> &[u8] { + &self.data[..self.len] + } + + pub fn into_inner(self) -> Vec { + self.data + } +} + /// The error type from write requests. Used to return the buffer. pub struct WriteError(pub Vec); From 389948d92a39ab4bcf8eea5d2bd5c29aceb28c2d Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 19 Nov 2024 14:17:58 -0700 Subject: [PATCH 62/65] zephyr: uart: Conditionalize irq handling for alloc Currently, the IRQ handling code requires a SpinMutex which is only available with Arc, which requires allocation. Disable this use of the driver in this situation. Signed-off-by: David Brown --- zephyr/src/device/uart.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zephyr/src/device/uart.rs b/zephyr/src/device/uart.rs index d718934f..d98b0b09 100644 --- a/zephyr/src/device/uart.rs +++ b/zephyr/src/device/uart.rs @@ -13,7 +13,9 @@ use core::ptr; use super::Unique; +#[cfg(CONFIG_RUST_ALLOC)] mod irq; +#[cfg(CONFIG_RUST_ALLOC)] pub use irq::UartIrq; /// A wrapper around a UART device on Zephyr. @@ -123,6 +125,7 @@ impl Uart { /// Convert into an IRQ one. The parameters `WS` and `RS` set the size of the rings for write /// and read respectively. + #[cfg(CONFIG_RUST_ALLOC)] pub unsafe fn into_irq(self) -> Result> { UartIrq::new(self) } From 0480107e870d12a507bc08d1c924ba8f79ed3401 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 16:12:53 -0600 Subject: [PATCH 63/65] zephyr-build: All augments for gpios This isn't really quite right, as the "unique" values here are per pin, and if the pin were to be used across the system, nothing would enforce these to be unique. Signed-off-by: David Brown --- zephyr-build/src/devicetree.rs | 7 +++ zephyr-build/src/devicetree/augment.rs | 65 +++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/zephyr-build/src/devicetree.rs b/zephyr-build/src/devicetree.rs index f2efa30c..dda4bc11 100644 --- a/zephyr-build/src/devicetree.rs +++ b/zephyr-build/src/devicetree.rs @@ -271,6 +271,13 @@ impl Word { _ => None, } } + + pub fn get_phandle(&self) -> Option<&Phandle> { + match self { + Word::Phandle(ph) => Some(ph), + _ => None, + } + } } // To avoid recursion, the debug printer for Phandle just prints the name. diff --git a/zephyr-build/src/devicetree/augment.rs b/zephyr-build/src/devicetree/augment.rs index 8a2046a5..131b34b5 100644 --- a/zephyr-build/src/devicetree/augment.rs +++ b/zephyr-build/src/devicetree/augment.rs @@ -17,7 +17,7 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use serde::{Deserialize, Serialize}; -use crate::devicetree::{output::dt_to_lower_id, Word}; +use crate::devicetree::{output::dt_to_lower_id, Value, Word}; use super::{DeviceTree, Node}; @@ -145,6 +145,13 @@ pub enum Action { /// in. kconfig: Option, }, + /// Generate a getter for a gpio assignment property. + GpioPins { + /// The name of the property holding the pins. + property: String, + /// The name of the getter function. + getter: String, + }, /// Generate all of the labels as its own node. Labels, } @@ -155,6 +162,38 @@ impl Action { Action::Instance { raw, device, kconfig } => { raw.generate(node, device, kconfig.as_deref()) } + Action::GpioPins { property, getter } => { + let upper_getter = getter.to_uppercase(); + let getter = format_ident!("{}", getter); + // TODO: This isn't actually right, these unique values should be based on the pin + // definition so that we'll get a compile error if two parts of the DT reference the + // same pin. + + let pins = node.get_property(property).unwrap(); + let npins = pins.len(); + + let uniques: Vec<_> = (0..npins).map(|n| { + format_ident!("{}_UNIQUE_{}", upper_getter, n) + }).collect(); + + let pins = pins + .iter() + .zip(uniques.iter()) + .map(|(pin, unique)| decode_gpios_gpio(unique, pin)); + + let unique_defs = uniques.iter().map(|u| { + quote! { + static #u: crate::device::Unique = crate::device::Unique::new(); + } + }); + + quote! { + #(#unique_defs)* + pub fn #getter() -> [Option; #npins] { + [#(#pins),*] + } + } + } Action::Labels => { let nodes = tree.labels.iter().map(|(k, v)| { let name = dt_to_lower_id(k); @@ -180,6 +219,30 @@ impl Action { } } +/// Decode a single gpio entry. +fn decode_gpios_gpio(unique: &Ident, entry: &Value) -> TokenStream { + let entry = if let Value::Words(w) = entry { + w + } else { + panic!("gpios list is not list of <&gpionnn aa bbb>"); + }; + if entry.len() != 3 { + panic!("gpios currently must be three items"); + } + let gpio_route = entry[0].get_phandle().unwrap().node_ref().route_to_rust(); + let args: Vec = entry[1..].iter().map(|n| n.get_number().unwrap()).collect(); + + quote! { + // TODO: Don't hard code this but put in yaml file. + unsafe { + crate::device::gpio::GpioPin::new( + &#unique, + #gpio_route :: get_instance_raw(), + #(#args),*) + } + } +} + #[derive(Debug, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case", content = "value")] pub enum RawInfo { From 415ffedd657cc6c386ceee709c73b0bccb2b2d9a Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 16 Jan 2025 15:25:47 -0700 Subject: [PATCH 64/65] build: devicetree: augment: Fix method rename from 'main' This method was renamed as part of the review process. Adjust the new code to match the newly available name. Signed-off-by: David Brown --- zephyr-build/src/devicetree/augment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zephyr-build/src/devicetree/augment.rs b/zephyr-build/src/devicetree/augment.rs index 131b34b5..a38b0b23 100644 --- a/zephyr-build/src/devicetree/augment.rs +++ b/zephyr-build/src/devicetree/augment.rs @@ -230,7 +230,7 @@ fn decode_gpios_gpio(unique: &Ident, entry: &Value) -> TokenStream { panic!("gpios currently must be three items"); } let gpio_route = entry[0].get_phandle().unwrap().node_ref().route_to_rust(); - let args: Vec = entry[1..].iter().map(|n| n.get_number().unwrap()).collect(); + let args: Vec = entry[1..].iter().map(|n| n.as_number().unwrap()).collect(); quote! { // TODO: Don't hard code this but put in yaml file. From 4c85315a3fc4fb9457d0fc8774cbded5b862d240 Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 16 Jan 2025 15:26:22 -0700 Subject: [PATCH 65/65] zephyr: SEGGER: Remove extraneous line This line causes a warning about an unused variable. The print message with it has been removed, so remove the variable creation as well. Signed-off-by: David Brown --- zephyr/src/work/futures.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zephyr/src/work/futures.rs b/zephyr/src/work/futures.rs index 7d556a5f..d70cf2fb 100644 --- a/zephyr/src/work/futures.rs +++ b/zephyr/src/work/futures.rs @@ -399,7 +399,6 @@ impl WorkData { // If we have a name, send it to Segger. #[cfg(CONFIG_SEGGER_SYSTEMVIEW)] { - let ww = &(&*this.work.get()).work; if let Some(name) = name { let info = crate::raw::SEGGER_SYSVIEW_TASKINFO { TaskID: this.work.get() as ::core::ffi::c_ulong,