diff --git a/CMakeLists.txt b/CMakeLists.txt index 582e14a..39a257b 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 @@ -77,8 +80,13 @@ function(rust_cargo_application) # TODO: Make sure RUSTFLAGS is not set. - # TODO: Let this be configurable, or based on Kconfig debug? - set(RUST_BUILD_TYPE debug) + if(CONFIG_DEBUG) + set(RUST_BUILD_TYPE "debug") + set(rust_build_type_arg "") + else() + set(RUST_BUILD_TYPE "release") + set(rust_build_type_arg "--release") + endif() set(BUILD_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE}") set(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/rust/target") @@ -133,6 +141,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,9 +161,10 @@ ${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 + ${rust_build_type_arg} # Override the features according to the shield given. For a general case, # this will need to come from a variable or argument. diff --git a/dt-rust.yaml b/dt-rust.yaml new file mode 100644 index 0000000..2837cf3 --- /dev/null +++ b/dt-rust.yaml @@ -0,0 +1,173 @@ +# 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 + value: + args: [] + 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 + 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 +# 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" + +# 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 + value: + args: [] + 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 + 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 + 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: + - 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..716cd2c --- /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() { + 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) { + // 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..457a43b --- /dev/null +++ b/zephyr-build/src/devicetree.rs @@ -0,0 +1,313 @@ +//! 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, + } + } + + 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. +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..38a0303 --- /dev/null +++ b/zephyr-build/src/devicetree/augment.rs @@ -0,0 +1,398 @@ +//! 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, Value, 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 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 { + 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, + /// A Kconfig option to allow the instances to only be present when said driver is compiled + /// 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, +} + +impl Action { + fn generate(&self, _name: &Ident, node: &Node, tree: &DeviceTree) -> TokenStream { + match self { + 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); + 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)* + } + } + } + } + } +} + +/// 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 { + /// Get the raw device directly from this node. + 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). + 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, 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 { + RawInfo::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! { + /// 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(); + #device_id::new(&UNIQUE, device, #(#get_args),*) + } + } + } + } + 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! { + #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(); + #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! { + #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(); + #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, + /// A count of the number of child nodes. + ChildCount, +} + +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),* + } + } + ArgInfo::ChildCount => { + let count = node.children.len(); + quote! { + #count + } + } + } + } +} + +/// 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..9a25568 --- /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, &fix_id(&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 7107369..e9de3cd 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -67,10 +67,27 @@ fn main() -> Result<()> { // one from the minimal libc. .clang_arg("-DRUST_BINDGEN") .clang_arg(format!("-I{}/lib/libc/minimal/include", zephyr_base)) - .derive_copy(false) + .derive_copy(true) .allowlist_function("k_.*") .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.*") + .allowlist_function("device_.*") + .allowlist_function("led_.*") .allowlist_function("sys_.*") + .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 b68b7fa..31b522a 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. @@ -33,11 +39,23 @@ extern int errno; #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_THREAD_ANALYZER +#include +#endif /* - * 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/Cargo.toml b/zephyr/Cargo.toml index 3775e27..5efb34c 100644 --- a/zephyr/Cargo.toml +++ b/zephyr/Cargo.toml @@ -11,6 +11,7 @@ Functionality for Rust-based applications that run on Zephyr. [dependencies] zephyr-sys = { version = "3.7.0", path = "../zephyr-sys" } +log = "0.4.22" # Although paste is brought in, it is a compile-time macro, and is not linked into the application. paste = "1.0" @@ -38,6 +39,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/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..0f253a8 --- /dev/null +++ b/zephyr/src/device.rs @@ -0,0 +1,43 @@ +//! 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; +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. +/// 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..2a26905 --- /dev/null +++ b/zephyr/src/device/gpio.rs @@ -0,0 +1,135 @@ +//! 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 core::ffi::c_int; + +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); + } + } + + /// 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"), + } + } +} diff --git a/zephyr/src/device/led.rs b/zephyr/src/device/led.rs new file mode 100644 index 0000000..f00fb6e --- /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) + }) + } +} diff --git a/zephyr/src/device/led_strip.rs b/zephyr/src/device/led_strip.rs new file mode 100644 index 0000000..2f6ace2 --- /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/device/uart.rs b/zephyr/src/device/uart.rs new file mode 100644 index 0000000..d718934 --- /dev/null +++ b/zephyr/src/device/uart.rs @@ -0,0 +1,163 @@ +//! Simple (and unsafe) wrappers around USB devices. + +// TODO! Remove this. +#![allow(dead_code)] +#![allow(unused_variables)] + +use crate::raw; +use crate::error::{Error, Result, to_result_void, to_result}; +use crate::printkln; + +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. + #[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) + } + + /// 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) + }) + } + + /// 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) + } + + /// 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) + } +} + +/// 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"); +} diff --git a/zephyr/src/device/uart/irq.rs b/zephyr/src/device/uart/irq.rs new file mode 100644 index 0000000..32af6f6 --- /dev/null +++ b/zephyr/src/device/uart/irq.rs @@ -0,0 +1,394 @@ +//! Simple (and unsafe) wrappers around USB devices. + +// TODO! Remove this. +#![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; +use crate::error::{Result, to_result_void}; +use crate::sys::sync::Semaphore; +use crate::sync::{Arc, SpinMutex}; +use crate::time::Timeout; + +use core::ffi::c_void; +use core::ops::Range; +use core::{fmt, result}; + +use super::Uart; + +/// 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 semaphore. This should **exactly** match the number of elements in `write_dones`. + write_sem: Semaphore, + inner: SpinMutex>, +} + +/// Data for communication with the UART IRQ. +struct IrqInnerData { + /// 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 +/// 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, +} + +/// 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 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, +} + +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); + +// 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 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, +/// 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. + 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, RS as u32)?, + write_sem: Semaphore::new(0, WS as u32)?, + inner: SpinMutex::new(IrqInnerData { + write_requests: ArrayDeque::new(), + write_dones: ArrayDeque::new(), + read_requests: ArrayDeque::new(), + read_dones: 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 { + 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 + } + + /// Enqueue a single write request. + /// + /// 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)), + } + } + + /// 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_requests.is_full() + } + + /// 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), + } + + 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 +// 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"); + } +} + +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(); + + // Handle any read requests. + loop { + 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; + } + } + } 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; + } + } + + // 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"); + + // If the count reaches 0, the fifo is full. + if count == 0 { + break; + } + } + } else { + // No work. Turn off the irq, and stop. + unsafe { raw::uart_irq_tx_disable(dev); } + break; + } + } + + unsafe { + raw::uart_irq_update(dev); + } +} diff --git a/zephyr/src/lib.rs b/zephyr/src/lib.rs index 85b6892..b64a501 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 object; pub mod sync; @@ -38,6 +39,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"); @@ -66,6 +83,12 @@ fn panic(info :&PanicInfo) -> ! { } } +/// Set the logger that the log crate will use to a printk-based logger within Zephyr. +#[cfg(CONFIG_PRINTK)] +pub fn set_logger() { + printk::set_printk_logger(); +} + /// Re-export of zephyr-sys as `zephyr::raw`. pub mod raw { pub use zephyr_sys::*; @@ -86,3 +109,19 @@ pub mod _export { // If allocation has been requested, provide the allocator. #[cfg(CONFIG_RUST_ALLOC)] pub mod alloc_impl; + +// If we have allocation, we can also support logging. +#[cfg(CONFIG_RUST_ALLOC)] +pub mod log { + //! A simple logging system using printk. + //! + //! As a stopgap for full logging, this allows Rust's logging to be transmitted via printk + //! messages. + + #[cfg(CONFIG_LOG)] + compile_error!("Rust with CONFIG_LOG is not yet supported"); + + mod log_printk; + + pub use log_printk::*; +} diff --git a/zephyr/src/log/log_printk.rs b/zephyr/src/log/log_printk.rs new file mode 100644 index 0000000..c71ffcf --- /dev/null +++ b/zephyr/src/log/log_printk.rs @@ -0,0 +1,29 @@ +//! A very simplistic logger that allocates and uses printk. +//! +//! This is just a stop gap so that users of Rust can have some simple messages. This provides a +//! `println` macro that works similar to the one in std. +//! +//! This module allocates at least twice as much space as the final message, for the duration of the +//! print call. +//! +//! An unfortunate caveat of this crate is that using it requires clients to have `extern crate +//! alloc;` in each module that uses it. + +extern crate alloc; + +use alloc::ffi::CString; + +pub use alloc::format; + +// Printk, with a fixed C-string formatting argument, and one we've built here. +extern "C" { + fn printk(fmt: *const i8, ...); +} + +#[doc(hidden)] +pub fn log_message(message: &str) { + let raw = CString::new(message).expect("CString::new failed"); + unsafe { + printk(c"%s\n".as_ptr(), raw.as_ptr()); + } +} diff --git a/zephyr/src/printk.rs b/zephyr/src/printk.rs index 8a281df..e8981cb 100644 --- a/zephyr/src/printk.rs +++ b/zephyr/src/printk.rs @@ -12,6 +12,8 @@ use core::fmt::{ write, }; +use log::{LevelFilter, Log, Metadata, Record}; + /// Print to Zephyr's console, without a newline. /// /// This macro uses the same syntax as std's @@ -46,6 +48,48 @@ macro_rules! printkln { }}; } +/// A simple log handler built around printk. +struct PrintkLogger; + +impl Log for PrintkLogger { + // Initially, everything is just available. + fn enabled(&self, _metadata: &Metadata<'_>) -> bool { + true + } + + // Just print out the information. + fn log(&self, record: &Record<'_>) { + printkln!("{}:{}: {}", + record.level(), + record.target(), + record.args()); + } + + // Nothing to do for flush. + fn flush(&self) { + } +} + +static PRINTK_LOGGER: PrintkLogger = PrintkLogger; + +// The cfg matches what is in the log crate, which doesn't use portable atomic, and assumes the +// racy init when not the case. +#[doc(hidden)] +#[cfg(target_has_atomic = "ptr")] +pub fn set_printk_logger() { + log::set_logger(&PRINTK_LOGGER).unwrap(); + log::set_max_level(LevelFilter::Info); +} + +#[doc(hidden)] +#[cfg(not(target_has_atomic = "ptr"))] +pub fn set_printk_logger() { + unsafe { + log::set_logger_racy(&PRINTK_LOGGER).unwrap(); + log::set_max_level_racy(LevelFilter::Info); + } +} + // This could readily be optimized for the configuration where we don't have userspace, as well as // when we do, and are not running in userspace. This initial implementation will always use a // string buffer, as it doesn't depend on static symbols in print.c. diff --git a/zephyr/src/sync.rs b/zephyr/src/sync.rs index 8003d9e..977433b 100644 --- a/zephyr/src/sync.rs +++ b/zephyr/src/sync.rs @@ -5,16 +5,6 @@ //! [`crossbeam-channel`](https://docs.rs/crossbeam-channel/latest/crossbeam_channel/), in as much //! as it makes sense. -use core::{ - cell::UnsafeCell, - fmt, - marker::PhantomData, - ops::{Deref, DerefMut}, -}; - -use crate::time::{Forever, NoWait}; -use crate::sys::sync as sys; - // Channels are currently only available with allocation. Bounded channels later might be // available. #[cfg(CONFIG_RUST_ALLOC)] @@ -38,239 +28,19 @@ pub mod atomic { #[cfg(CONFIG_RUST_ALLOC)] pub use portable_atomic_util::Arc; -// Channels are currently only available with allocation. Bounded channels later might be -// available. - -/// Until poisoning is implemented, mutexes never return an error, and we just get back the guard. -pub type LockResult = Result; - -/// The return type from [`Mutex::try_lock`]. -/// -/// The error indicates the reason for the failure. Until poisoning is -/// implemented, there is only a single type of failure. -pub type TryLockResult = Result; - -/// An enumeration of possible errors associated with a [`TryLockResult`]. -/// -/// Note that until Poisoning is implemented, there is only one value of this. -pub enum TryLockError { - /// The lock could not be acquired at this time because the operation would otherwise block. - WouldBlock, -} - -/// A mutual exclusion primitive useful for protecting shared data. -/// -/// This mutex will block threads waiting for the lock to become available. This is modeled after -/// [`std::sync::Mutex`](https://doc.rust-lang.org/stable/std/sync/struct.Mutex.html), and attempts -/// to implement that API as closely as makes sense on Zephyr. Currently, it has the following -/// differences: -/// - Poisoning: This does not yet implement poisoning, as there is no way to recover from panic at -/// this time on Zephyr. -/// - Allocation: `new` is not yet provided, and will be provided once kernel object pools are -/// implemented. Please use `new_from` which takes a reference to a statically allocated -/// `sys::Mutex`. -pub struct Mutex { - inner: sys::Mutex, - // poison: ... - data: UnsafeCell, -} - -// At least if correctly done, the Mutex provides for Send and Sync as long as the inner data -// supports Send. -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 "scoped lock" of a mutex. When this structure is dropped (faslls -/// out of scope), the lock will be unlocked. -/// -/// The data protected by the mutex can be accessed through this guard via its [`Deref`] and -/// [`DerefMut`] implementations. -/// -/// This structure is created by the [`lock`] and [`try_lock`] methods on [`Mutex`]. -/// -/// [`lock`]: Mutex::lock -/// [`try_lock`]: Mutex::try_lock -/// -/// Taken directly from -/// [`std::sync::MutexGuard`](https://doc.rust-lang.org/stable/std/sync/struct.MutexGuard.html). -pub struct MutexGuard<'a, T: ?Sized + 'a> { - lock: &'a Mutex, - // until is implemented, we have to mark unsend - // explicitly. This can be done by holding Phantom data with an unsafe cell in it. - _nosend: PhantomData>, -} - -// Make sure the guard doesn't get sent. -// Negative trait bounds are unstable, see marker above. -// impl !Send for MutexGuard<'_, T> {} -unsafe impl Sync for MutexGuard<'_, T> {} - -impl Mutex { - /// Construct a new wrapped Mutex, using the given underlying sys mutex. This is different that - /// `std::sync::Mutex` in that in Zephyr, objects are frequently allocated statically, and the - /// sys Mutex will be taken by this structure. It is safe to share the underlying Mutex between - /// different items, but without careful use, it is easy to deadlock, so it is not recommended. - pub const fn new_from(t: T, raw_mutex: sys::Mutex) -> Mutex { - Mutex { inner: raw_mutex, data: UnsafeCell::new(t) } - } - - /// Construct a new Mutex, dynamically allocating the underlying sys Mutex. - #[cfg(CONFIG_RUST_ALLOC)] - pub fn new(t: T) -> Mutex { - Mutex::new_from(t, sys::Mutex::new().unwrap()) - } -} - -impl Mutex { - /// Acquires a mutex, blocking the current thread until it is able to do so. - /// - /// This function will block the local thread until it is available to acquire the mutex. Upon - /// returning, the thread is the only thread with the lock held. An RAII guard is returned to - /// allow scoped unlock of the lock. When the guard goes out of scope, the mutex will be - /// unlocked. - /// - /// In `std`, an attempt to lock a mutex by a thread that already holds the mutex is - /// unspecified. Zephyr explicitly supports this behavior, by simply incrementing a lock - /// count. - pub fn lock(&self) -> LockResult> { - // With `Forever`, should never return an error. - self.inner.lock(Forever).unwrap(); - unsafe { - Ok(MutexGuard::new(self)) - } - } +mod mutex; - /// Attempts to acquire this lock. - /// - /// If the lock could not be acquired at this time, then [`Err`] is returned. Otherwise, an RAII - /// guard is returned. The lock will be unlocked when the guard is dropped. - /// - /// This function does not block. - pub fn try_lock(&self) -> TryLockResult> { - match self.inner.lock(NoWait) { - Ok(()) => { - unsafe { - Ok(MutexGuard::new(self)) - } - } - // TODO: It might be better to distinguish these errors, and only return the WouldBlock - // if that is the corresponding error. But, the lock shouldn't fail in Zephyr. - Err(_) => { - Err(TryLockError::WouldBlock) - } - } - } -} - -impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> { - unsafe fn new(lock: &'mutex Mutex) -> MutexGuard<'mutex, T> { - // poison todo - 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.unlock().unwrap(); - } -} - -/// Inspired by -/// [`std::sync::Condvar`](https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html), -/// implemented directly using `z_condvar` in Zephyr. -/// -/// Condition variables represent the ability to block a thread such that it consumes no CPU time -/// while waiting for an even to occur. Condition variables are typically associated with a -/// boolean predicate (a condition) and a mutex. The predicate is always verified inside of the -/// mutex before determining that a thread must block. -/// -/// Functions in this module will block the current **thread** of execution. Note that any attempt -/// to use multiple mutexces on the same condition variable may result in a runtime panic. -pub struct Condvar { - inner: sys::Condvar, -} - -impl Condvar { - /// Construct a new wrapped Condvar, using the given underlying `k_condvar`. - /// - /// This is different from `std::sync::Condvar` in that in Zephyr, objects are frequently - /// allocated statically, and the sys Condvar will be taken by this structure. - pub const fn new_from(raw_condvar: sys::Condvar) -> Condvar { - Condvar { inner: raw_condvar } - } - - /// Construct a new Condvar, dynamically allocating the underlying Zephyr `k_condvar`. - #[cfg(CONFIG_RUST_ALLOC)] - pub fn new() -> Condvar { - Condvar::new_from(sys::Condvar::new().unwrap()) - } - - /// Blocks the current thread until this conditional variable receives a notification. - /// - /// This function will automatically unlock the mutex specified (represented by `guard`) and - /// block the current thread. This means that any calls to `notify_one` or `notify_all` which - /// happen logically after the mutex is unlocked are candidates to wake this thread up. When - /// this function call returns, the lock specified will have been re-equired. - /// - /// Note that this function is susceptable to spurious wakeups. Condition variables normally - /// have a boolean predicate associated with them, and the predicate must always be checked - /// each time this function returns to protect against spurious wakeups. - pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> LockResult> { - self.inner.wait(&guard.lock.inner); - Ok(guard) - } - - // TODO: wait_while - // TODO: wait_timeout_ms - // TODO: wait_timeout - // TODO: wait_timeout_while - - /// Wakes up one blocked thread on this condvar. - /// - /// If there is a blocked thread on this condition variable, then it will be woken up from its - /// call to `wait` or `wait_timeout`. Calls to `notify_one` are not buffered in any way. - /// - /// To wakeup all threads, see `notify_all`. - pub fn notify_one(&self) { - self.inner.notify_one(); - } +pub use mutex::{ + Mutex, + MutexGuard, + Condvar, + LockResult, + TryLockResult, +}; - /// Wakes up all blocked threads on this condvar. - /// - /// This methods will ensure that any current waiters on the condition variable are awoken. - /// Calls to `notify_all()` are not buffered in any way. - /// - /// To wake up only one thread, see `notify_one`. - pub fn notify_all(&self) { - self.inner.notify_all(); - } -} +mod spinmutex; -impl fmt::Debug for Condvar { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Condvar {:?}", self.inner) - } -} +pub use spinmutex::{ + SpinMutex, + SpinMutexGuard, +}; diff --git a/zephyr/src/sync/channel.rs b/zephyr/src/sync/channel.rs index c541b75..5ecfa82 100644 --- a/zephyr/src/sync/channel.rs +++ b/zephyr/src/sync/channel.rs @@ -71,7 +71,10 @@ pub struct Message { } impl Message { - fn new(data: T) -> Message { + /// Construct a new message from the data. + /// + /// This is safe in itself, but sending them is unsafe. + pub fn new(data: T) -> Message { Message { _private: 0, data, @@ -99,6 +102,16 @@ impl Sender { } Ok(()) } + + /// Sends a message that has already been boxed. The box will be dropped upon receipt. This is + /// safe to call from interrupt context, and presumably the box will be allocate from a thread. + pub unsafe fn send_boxed(&self, msg: Box>) -> Result<(), SendError> { + let msg = Box::into_raw(msg); + unsafe { + self.queue.send(msg as *mut c_void); + } + Ok(()) + } } impl Drop for Sender { diff --git a/zephyr/src/sync/mutex.rs b/zephyr/src/sync/mutex.rs new file mode 100644 index 0000000..6cd21ac --- /dev/null +++ b/zephyr/src/sync/mutex.rs @@ -0,0 +1,249 @@ +//! Higher level Mutex type and friends. +//! +//! These are modeled after the synchronization primitives in +//! [`std::sync`](https://doc.rust-lang.org/stable/std/sync/index.html), notably `Mutex`, and +//! `Condvar`, and the associated types. + +use core::{ + cell::UnsafeCell, + fmt, + marker::PhantomData, + ops::{Deref, DerefMut}, +}; + +use crate::time::{Forever, NoWait}; +use crate::sys::sync as sys; + +/// Until poisoning is implemented, mutexes never return an error, and we just get back the guard. +pub type LockResult = Result; + +/// The return type from [`Mutex::try_lock`]. +/// +/// The error indicates the reason for the failure. Until poisoning is +/// implemented, there is only a single type of failure. +pub type TryLockResult = Result; + +/// An enumeration of possible errors associated with a [`TryLockResult`]. +/// +/// Note that until Poisoning is implemented, there is only one value of this. +pub enum TryLockError { + /// The lock could not be acquired at this time because the operation would otherwise block. + WouldBlock, +} + +/// A mutual exclusion primitive useful for protecting shared data. +/// +/// This mutex will block threads waiting for the lock to become available. This is modeled after +/// [`std::sync::Mutex`](https://doc.rust-lang.org/stable/std/sync/struct.Mutex.html), and attempts +/// to implement that API as closely as makes sense on Zephyr. Currently, it has the following +/// differences: +/// - Poisoning: This does not yet implement poisoning, as there is no way to recover from panic at +/// this time on Zephyr. +/// - Allocation: `new` is not yet provided, and will be provided once kernel object pools are +/// implemented. Please use `new_from` which takes a reference to a statically allocated +/// `sys::Mutex`. +pub struct Mutex { + inner: sys::Mutex, + // poison: ... + data: UnsafeCell, +} + +// At least if correctly done, the Mutex provides for Send and Sync as long as the inner data +// supports Send. +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 "scoped lock" of a mutex. When this structure is dropped (faslls +/// out of scope), the lock will be unlocked. +/// +/// The data protected by the mutex can be accessed through this guard via its [`Deref`] and +/// [`DerefMut`] implementations. +/// +/// This structure is created by the [`lock`] and [`try_lock`] methods on [`Mutex`]. +/// +/// [`lock`]: Mutex::lock +/// [`try_lock`]: Mutex::try_lock +/// +/// Taken directly from +/// [`std::sync::MutexGuard`](https://doc.rust-lang.org/stable/std/sync/struct.MutexGuard.html). +pub struct MutexGuard<'a, T: ?Sized + 'a> { + lock: &'a Mutex, + // until is implemented, we have to mark unsend + // explicitly. This can be done by holding Phantom data with an unsafe cell in it. + _nosend: PhantomData>, +} + +// Make sure the guard doesn't get sent. +// Negative trait bounds are unstable, see marker above. +// impl !Send for MutexGuard<'_, T> {} +unsafe impl Sync for MutexGuard<'_, T> {} + +impl Mutex { + /// Construct a new wrapped Mutex, using the given underlying sys mutex. This is different that + /// `std::sync::Mutex` in that in Zephyr, objects are frequently allocated statically, and the + /// sys Mutex will be taken by this structure. It is safe to share the underlying Mutex between + /// different items, but without careful use, it is easy to deadlock, so it is not recommended. + pub const fn new_from(t: T, raw_mutex: sys::Mutex) -> Mutex { + Mutex { inner: raw_mutex, data: UnsafeCell::new(t) } + } + + /// Construct a new Mutex, dynamically allocating the underlying sys Mutex. + #[cfg(CONFIG_RUST_ALLOC)] + pub fn new(t: T) -> Mutex { + Mutex::new_from(t, sys::Mutex::new().unwrap()) + } +} + +impl Mutex { + /// Acquires a mutex, blocking the current thread until it is able to do so. + /// + /// This function will block the local thread until it is available to acquire the mutex. Upon + /// returning, the thread is the only thread with the lock held. An RAII guard is returned to + /// allow scoped unlock of the lock. When the guard goes out of scope, the mutex will be + /// unlocked. + /// + /// In `std`, an attempt to lock a mutex by a thread that already holds the mutex is + /// unspecified. Zephyr explicitly supports this behavior, by simply incrementing a lock + /// count. + pub fn lock(&self) -> LockResult> { + // With `Forever`, should never return an error. + self.inner.lock(Forever).unwrap(); + unsafe { + Ok(MutexGuard::new(self)) + } + } + + /// Attempts to acquire this lock. + /// + /// If the lock could not be acquired at this time, then [`Err`] is returned. Otherwise, an RAII + /// guard is returned. The lock will be unlocked when the guard is dropped. + /// + /// This function does not block. + pub fn try_lock(&self) -> TryLockResult> { + match self.inner.lock(NoWait) { + Ok(()) => { + unsafe { + Ok(MutexGuard::new(self)) + } + } + // TODO: It might be better to distinguish these errors, and only return the WouldBlock + // if that is the corresponding error. But, the lock shouldn't fail in Zephyr. + Err(_) => { + Err(TryLockError::WouldBlock) + } + } + } +} + +impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> { + unsafe fn new(lock: &'mutex Mutex) -> MutexGuard<'mutex, T> { + // poison todo + 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.unlock().unwrap(); + } +} + +/// Inspired by +/// [`std::sync::Condvar`](https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html), +/// implemented directly using `z_condvar` in Zephyr. +/// +/// Condition variables represent the ability to block a thread such that it consumes no CPU time +/// while waiting for an even to occur. Condition variables are typically associated with a +/// boolean predicate (a condition) and a mutex. The predicate is always verified inside of the +/// mutex before determining that a thread must block. +/// +/// Functions in this module will block the current **thread** of execution. Note that any attempt +/// to use multiple mutexces on the same condition variable may result in a runtime panic. +pub struct Condvar { + inner: sys::Condvar, +} + +impl Condvar { + /// Construct a new wrapped Condvar, using the given underlying `k_condvar`. + /// + /// This is different from `std::sync::Condvar` in that in Zephyr, objects are frequently + /// allocated statically, and the sys Condvar will be taken by this structure. + pub const fn new_from(raw_condvar: sys::Condvar) -> Condvar { + Condvar { inner: raw_condvar } + } + + /// Construct a new Condvar, dynamically allocating the underlying Zephyr `k_condvar`. + #[cfg(CONFIG_RUST_ALLOC)] + pub fn new() -> Condvar { + Condvar::new_from(sys::Condvar::new().unwrap()) + } + + /// Blocks the current thread until this conditional variable receives a notification. + /// + /// This function will automatically unlock the mutex specified (represented by `guard`) and + /// block the current thread. This means that any calls to `notify_one` or `notify_all` which + /// happen logically after the mutex is unlocked are candidates to wake this thread up. When + /// this function call returns, the lock specified will have been re-equired. + /// + /// Note that this function is susceptable to spurious wakeups. Condition variables normally + /// have a boolean predicate associated with them, and the predicate must always be checked + /// each time this function returns to protect against spurious wakeups. + pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> LockResult> { + self.inner.wait(&guard.lock.inner); + Ok(guard) + } + + // TODO: wait_while + // TODO: wait_timeout_ms + // TODO: wait_timeout + // TODO: wait_timeout_while + + /// Wakes up one blocked thread on this condvar. + /// + /// If there is a blocked thread on this condition variable, then it will be woken up from its + /// call to `wait` or `wait_timeout`. Calls to `notify_one` are not buffered in any way. + /// + /// To wakeup all threads, see `notify_all`. + pub fn notify_one(&self) { + self.inner.notify_one(); + } + + /// Wakes up all blocked threads on this condvar. + /// + /// This methods will ensure that any current waiters on the condition variable are awoken. + /// Calls to `notify_all()` are not buffered in any way. + /// + /// To wake up only one thread, see `notify_one`. + pub fn notify_all(&self) { + self.inner.notify_all(); + } +} + +impl fmt::Debug for Condvar { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Condvar {:?}", self.inner) + } +} diff --git a/zephyr/src/sync/spinmutex.rs b/zephyr/src/sync/spinmutex.rs new file mode 100644 index 0000000..3d5f436 --- /dev/null +++ b/zephyr/src/sync/spinmutex.rs @@ -0,0 +1,156 @@ +//! Spinlock-based Mutexes +//! +//! The [`sync::Mutex`] type is quite handy in Rust for sharing data between multiple contexts. +//! However, it is not possible to aquire a mutex from interrupt context. +//! +//! This module provides a [`SpinMutex`] which has some similarities to the above, but with some +//! restrictions. In contrast, however, it is usable from interrupt context. +//! +//! It works by use a spinlock to protect the contents of the SpinMutex. This allows for use in +//! user threads as well as from irq context, the spinlock even protecting the data on SMP machines. +//! +//! In contract to [`critical_section::Mutex`], this has an API much closer to [`sync::Mutex`] (and +//! as such to [`std::sync::Mutex`]. In addition, it has slightly less overhead on Zephyr, and +//! since the mutex isn't shared, might allow for slightly better use on SMP systems, when the other +//! CPU(s) don't need access to the SyncMutex. +//! +//! Note that `SpinMutex` doesn't have anything comparable to `Condvar`. Generally, they can be +//! used with a `Semaphore` to allow clients to be waken, but this usage is racey, and if not done +//! carefully can result uses of the semaphore not waking. + +use core::{cell::UnsafeCell, convert::Infallible, fmt, marker::PhantomData, ops::{Deref, DerefMut}}; + +use crate::raw; + +/// Result from the lock call. We keep the result for consistency of the API, but these can never +/// fail. +pub type SpinLockResult = core::result::Result; + +/// Result from the `try_lock` call. There is only a single type of failure, indicating that this +/// would block. +pub type SpinTryLockResult = core::result::Result; + +/// The single error type that can be returned from `try_lock`. +pub enum SpinTryLockError { + /// The lock could not be acquired at this time because the operation would otherwise block. + WouldBlock, +} + +/// A lower-level mutual exclusion primitive for protecting data. +/// +/// This is modeled after [`sync::Mutex`] but instead of using `k_mutex` from Zephyr, it uses +/// `k_spinlock`. It's main advantage is that it is usable from IRQ context. However, it also is +/// uninterruptible, and prevents even IRQ handlers from running. +pub struct SpinMutex { + inner: UnsafeCell, + data: UnsafeCell, +} + +/// As the data is protected by spinlocks, with RAII ensuring the lock is always released, this +/// satisfies Rust's requirements for Send and Sync. The dependency of both on "Send" of the data +/// type is intentional, as it is the Mutex that is providing the Sync semantics. However, it only +/// makes sense for types that are usable from multiple thread contexts. +unsafe impl Send for SpinMutex {} +unsafe impl Sync for SpinMutex {} + +impl fmt::Debug for SpinMutex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Mutex {:?}", self.inner) + } +} + +/// An RAII implementation of a "scoped lock" of a SpinMutex. When this structure is dropped (falls +/// out of scope), the lock will be unlocked. +/// +/// The data protected by the SpinMutex can be accessed through this guard via it's [`Deref'] and +/// [`DerefMut`] implementations. +/// +/// This structure is created by the [`lock`] and [`try_lock`] methods on [`SpinMutex`]. +/// +/// [`lock`]: SpinMutex::lock +/// [`try_lock`]: SpinMutex::try_lock +/// +/// Borrowed largely from std's `MutexGuard`, but adapted to use spinlocks. +pub struct SpinMutexGuard<'a, T: ?Sized + 'a> { + lock: &'a SpinMutex, + key: raw::k_spinlock_key_t, + // Mark as not Send. + _nosend: PhantomData>, +} + +// Negative trait bounds are unstable, see the _nosend field above. +/// Mark as Sync if the contained data is sync. +unsafe impl Sync for SpinMutexGuard<'_, T> {} + +impl SpinMutex { + /// Construct a new wrapped Mutex. + pub const fn new(t: T) -> SpinMutex { + SpinMutex { + inner: UnsafeCell::new(unsafe { core::mem::zeroed() }), + data: UnsafeCell::new(t), + } + } +} + +impl SpinMutex { + /// Acquire a mutex, spinning as needed to aquire the controlling spinlock. + /// + /// This function will spin the current thread until it is able to acquire the spinlock. + /// Returns an RAII guard to allow scoped unlock of the lock. When the guard goes out of scope, + /// the SpinMutex will be unlocked. + pub fn lock(&self) -> SpinLockResult> { + let key = unsafe { raw::k_spin_lock(self.inner.get()) }; + unsafe { Ok(SpinMutexGuard::new(self, key)) } + } + + /// Attempts to aquire this lock. + /// + /// If the lock could not be aquired at this time, then [`Err`] is returned. Otherwise an RAII + /// guard is returned. The lock will be unlocked when the guard is dropped. + /// + /// This function does not block. + pub fn try_lock(&self) -> SpinTryLockResult> { + let mut key = raw::k_spinlock_key_t { key: 0 }; + match unsafe { raw::k_spin_trylock(self.inner.get(), &mut key) } { + 0 => { + unsafe { + Ok(SpinMutexGuard::new(self, key)) + } + } + _ => { + Err(SpinTryLockError::WouldBlock) + } + } + } +} + +impl<'mutex, T: ?Sized> SpinMutexGuard<'mutex, T> { + unsafe fn new(lock: &'mutex SpinMutex, key: raw::k_spinlock_key_t) -> SpinMutexGuard<'mutex, T> { + SpinMutexGuard { lock, key, _nosend: PhantomData } + } +} + +impl Deref for SpinMutexGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { + &*self.lock.data.get() + } + } +} + +impl DerefMut for SpinMutexGuard<'_, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.lock.data.get() } + } +} + +impl Drop for SpinMutexGuard<'_, T> { + #[inline] + fn drop(&mut self) { + unsafe { + raw::k_spin_unlock(self.lock.inner.get(), self.key); + } + } +} diff --git a/zephyr/src/sys.rs b/zephyr/src/sys.rs index 0783d3a..7b17f29 100644 --- a/zephyr/src/sys.rs +++ b/zephyr/src/sys.rs @@ -41,11 +41,22 @@ 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. //! //! Critical sections from Rust are handled with a single Zephyr spinlock. This doesn't allow //! any nesting, but neither does the `critical-section` crate. + //! + //! This provides the underlying critical section crate, which is useful for external crates + //! that want this interface. However, it isn't a particularly hygienic interface to use. For + //! something a bit nicer, please see [`sync::SpinMutex`]. use core::{ffi::c_int, ptr::addr_of_mut}; diff --git a/zephyr/src/sys/queue.rs b/zephyr/src/sys/queue.rs index f111c61..1bb61ee 100644 --- a/zephyr/src/sys/queue.rs +++ b/zephyr/src/sys/queue.rs @@ -6,6 +6,7 @@ use core::ffi::c_void; use core::fmt; +#[allow(unused_imports)] use core::mem; use zephyr_sys::{ @@ -15,6 +16,7 @@ use zephyr_sys::{ k_queue_get, }; +#[allow(unused_imports)] use crate::error::Result; use crate::sys::K_FOREVER; use crate::object::{Fixed, StaticKernelObject, Wrapped}; diff --git a/zephyr/src/sys/sync/mutex.rs b/zephyr/src/sys/sync/mutex.rs index 66bf2a8..22fbf23 100644 --- a/zephyr/src/sys/sync/mutex.rs +++ b/zephyr/src/sys/sync/mutex.rs @@ -9,6 +9,7 @@ //! [`object`]: crate::object use core::fmt; +#[allow(unused_imports)] use core::mem; use crate::{ error::{Result, to_result_void}, diff --git a/zephyr/src/sys/sync/semaphore.rs b/zephyr/src/sys/sync/semaphore.rs index ddb54d0..022296c 100644 --- a/zephyr/src/sys/sync/semaphore.rs +++ b/zephyr/src/sys/sync/semaphore.rs @@ -13,6 +13,7 @@ use core::ffi::c_uint; use core::fmt; +#[allow(unused_imports)] use core::mem; use crate::{