diff --git a/CMakeLists.txt b/CMakeLists.txt index 582e14a..1c14325 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 @@ -133,6 +136,8 @@ ZEPHYR_DTS = \"${ZEPHYR_DTS}\" 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} @@ -151,6 +156,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 build # TODO: release flag if release build # --release diff --git a/dt-rust.yaml b/dt-rust.yaml new file mode 100644 index 0000000..878913d --- /dev/null +++ b/dt-rust.yaml @@ -0,0 +1,87 @@ +# 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::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 +# 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::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. +- name: flash-controller + rules: + - type: compatible + value: + names: + - "nordic,nrf52-flash-controller" + - "nordic,nrf51-flash-controller" + - "raspberrypi,pico-flash-controller" + level: 0 + actions: + - type: instance + value: + raw: + type: myself + 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. +# 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::device::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/etc/platforms.txt b/etc/platforms.txt index e459795..42b27bc 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 diff --git a/samples/blinky/CMakeLists.txt b/samples/blinky/CMakeLists.txt new file mode 100644 index 0000000..fdbabbc --- /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 0000000..e812ba1 --- /dev/null +++ b/samples/blinky/Cargo.toml @@ -0,0 +1,20 @@ +# 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" + +[build-dependencies] +zephyr-build = "3.7.0" diff --git a/samples/blinky/README.rst b/samples/blinky/README.rst new file mode 100644 index 0000000..ec23fe5 --- /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/build.rs b/samples/blinky/build.rs new file mode 100644 index 0000000..eea8aad --- /dev/null +++ b/samples/blinky/build.rs @@ -0,0 +1,3 @@ +fn main() { + zephyr_build::dt_cfgs(); +} diff --git a/samples/blinky/prj.conf b/samples/blinky/prj.conf new file mode 100644 index 0000000..1ff6fb7 --- /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 0000000..de71191 --- /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 0000000..84d7bac --- /dev/null +++ b/samples/blinky/src/lib.rs @@ -0,0 +1,77 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +#![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; + +use zephyr::raw::GPIO_OUTPUT_ACTIVE; +use zephyr::time::{ Duration, sleep }; + +#[no_mangle] +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 + (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(); +} + +#[cfg(dt = "aliases::led0")] +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"); + loop { + } + // return; + } + + unsafe { led0.configure(&mut gpio_token, GPIO_OUTPUT_ACTIVE); } + let duration = Duration::millis_at_least(500); + loop { + unsafe { led0.toggle_pin(&mut gpio_token); } + sleep(duration); + } +} + +#[cfg(not(dt = "aliases::led0"))] +fn do_blink() { + warn!("No leds configured"); + loop { + } +} diff --git a/samples/blinky/src/main.c b/samples/blinky/src/main.c new file mode 100644 index 0000000..4cab496 --- /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; +} diff --git a/zephyr-build/Cargo.toml b/zephyr-build/Cargo.toml index a31eaa8..16182cb 100644 --- a/zephyr-build/Cargo.toml +++ b/zephyr-build/Cargo.toml @@ -15,3 +15,10 @@ 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" +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 new file mode 100644 index 0000000..6c7d0d0 --- /dev/null +++ b/zephyr-build/src/devicetree.rs @@ -0,0 +1,306 @@ +//! Incorporating Zephyr's devicetree into Rust. +//! +//! Zephyr depends fairly heavily on the devicetree for configuration. The build system reads +//! multiple DTS files, and coalesces this into a single devicetree. This tree is output in a few +//! different ways: +//! +//! - Canonical DTS. There is a single DTS file (`build/zephyr/zephyr.dts`) that contains the final +//! tree, but still in DTS format (the DTB file would have information discarded). +//! +//! - Generated. The C header `devicetree_generated.h` contains all of the definitions. This isn't +//! a particularly friendly file to read or parse, but it does have one piece of information that is +//! not represented anywhere else: the mapping between devicetree nodes and their "ORD" index. The +//! device nodes in the system are indexed by this number, and we need this in order to be able to +//! reference the nodes from Rust. +//! +//! Beyond the ORD field, it seems easier to deal with the DTS file itself. Parsing is fairly +//! straightforward, as it is a subset of the DTS format, and we only have to be able to deal with +//! the files that are generated by the Zephyr build process. + +// TODO: Turn this off. +#![allow(dead_code)] + +use ordmap::OrdMap; +use std::{cell::RefCell, collections::BTreeMap, path::Path, rc::Rc}; + +mod augment; +mod ordmap; +mod output; +mod parse; + +pub use augment::{Augment, load_augments}; + +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 0000000..6b82d87 --- /dev/null +++ b/zephyr-build/src/devicetree/augment.rs @@ -0,0 +1,298 @@ +//! Support for augmenting the device tree. +//! +//! 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::{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 +/// 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; +} + +/// 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_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/dts.pest b/zephyr-build/src/devicetree/dts.pest new file mode 100644 index 0000000..8a52158 --- /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 0000000..02bc0ec --- /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 0000000..4531da3 --- /dev/null +++ b/zephyr-build/src/devicetree/output.rs @@ -0,0 +1,215 @@ +//! 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 std::io::Write; + +use anyhow::Result; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; + +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, 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 + // the name. + self.node_walk(self.root.as_ref(), None, &augments) + } + + // 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, "cargo:rustc-cfg=dt=\"labels\"")?; + for label in self.labels.keys() { + writeln!(write, "cargo:rustc-cfg=dt=\"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) + }); + // 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)); + + 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)* + #(#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)* + } + } + + /// 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, "cargo:rustc-cfg=dt=\"{}\"", child_name)?; + + for prop in &child.properties { + prop.output_path(write, &child_name)?; + } + + child.output_path_walk(write, Some(&child_name))?; + } + + Ok(()) + } +} + +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 + } + } + + // 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, "cargo:rustc-cfg=dt=\"{}::{}\"", name, fix_id(&self.name))?; + } + } + Ok(()) + } +} + +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 0000000..44a4289 --- /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 bec7134..102f4d5 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::{Augment, 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,91 @@ pub fn build_kconfig_mod() { } } } + +/// Parse the finalized DTS file, generating the Rust devicetree file. +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"); + + 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(); + + // 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::load_augments(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 tokens = dt.to_tokens(&augs); + if has_rustfmt() { + write_formatted(out, tokens); + } else { + writeln!(out, "{}", tokens).unwrap(); + }; +} + +/// 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 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. +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/Cargo.toml b/zephyr-sys/Cargo.toml index 385dcd5..a37c17d 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 = "3.7.0", path = "../zephyr-build" } diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index a393a50..78344ef 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_item("E.*") diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 53550dc..ffe70e9 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. @@ -34,11 +40,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 f4345e9..84f3a78 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/device.rs b/zephyr/src/device.rs new file mode 100644 index 0000000..08bf91a --- /dev/null +++ b/zephyr/src/device.rs @@ -0,0 +1,40 @@ +//! 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}; + +pub mod gpio; +pub mod flash; + +// 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 + } +} diff --git a/zephyr/src/device/flash.rs b/zephyr/src/device/flash.rs new file mode 100644 index 0000000..69bdfaa --- /dev/null +++ b/zephyr/src/device/flash.rs @@ -0,0 +1,59 @@ +//! 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. + #[allow(dead_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. + #[allow(dead_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 0000000..c0cace3 --- /dev/null +++ b/zephyr/src/device/gpio.rs @@ -0,0 +1,119 @@ +//! 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. + #[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; + } + 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); + } + } +} diff --git a/zephyr/src/lib.rs b/zephyr/src/lib.rs index aca2bd6..82ed392 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; @@ -41,6 +42,22 @@ pub mod kconfig { include!(concat!(env!("OUT_DIR"), "/kconfig.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))] compile_error!("CONFIG_RUST must be set to build Rust in Zephyr");