From 4bb335243f006cb7d3df5d441b50672af5d0ef90 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 18 Oct 2024 15:26:08 -0600 Subject: [PATCH 1/5] zephyr: device: gpio: Add a few more methods Add methods for get and set of pin logical values. Signed-off-by: David Brown --- zephyr/src/device/gpio.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/zephyr/src/device/gpio.rs b/zephyr/src/device/gpio.rs index 14a562e0..bf96b09c 100644 --- a/zephyr/src/device/gpio.rs +++ b/zephyr/src/device/gpio.rs @@ -7,6 +7,8 @@ //! pervasively throughout Zephyr device drivers. As such, most of the calls in this module are //! unsafe. +use core::ffi::c_int; + use super::Unique; use crate::raw; @@ -147,4 +149,18 @@ impl GpioPin { raw::gpio_pin_toggle_dt(&self.pin); } } + + /// Set the logical level of the pin. + pub unsafe fn set(&mut self, _token: &mut GpioToken, value: bool) { + raw::gpio_pin_set_dt(&self.pin, value as c_int); + } + + /// Read the logical level of the pin. + pub unsafe fn get(&mut self, _token: &mut GpioToken) -> bool { + match raw::gpio_pin_get_dt(&self.pin) { + 0 => false, + 1 => true, + _ => panic!("TODO: Handle gpio get error"), + } + } } From 494a4dd5cd76ba73f3dcb2fd8ad39d7e1232b1d5 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 4 Mar 2025 16:03:22 -0700 Subject: [PATCH 2/5] zephyr: device: Add a possible static element with each device Allow the dt-rust.yaml file to specify a `static_type` value for device nodes. The generated code for the nodes will reserve a static value of that type, and pass it into the constructor. There is a NoStatic which is the default. As it is zero size, it will not cause any code to be generated for the types that do not need static. Fix the flash device to accept this argument, but the fix for gpio will come in a subsequent patch. Signed-off-by: David Brown --- dt-rust.yaml | 1 + zephyr-build/src/devicetree/augment.rs | 25 ++++++++++++++++++++----- zephyr/src/device.rs | 10 ++++++++++ zephyr/src/device/flash.rs | 4 +++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/dt-rust.yaml b/dt-rust.yaml index 878913db..8eb8db92 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -15,6 +15,7 @@ raw: type: myself device: crate::device::gpio::Gpio + static_type: crate::device::gpio::GpioStatic # 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 diff --git a/zephyr-build/src/devicetree/augment.rs b/zephyr-build/src/devicetree/augment.rs index 55f150db..3b781da8 100644 --- a/zephyr-build/src/devicetree/augment.rs +++ b/zephyr-build/src/devicetree/augment.rs @@ -130,6 +130,8 @@ pub enum Action { /// The name of the full path (within the zephyr-sys crate) for the wrapper node for this /// device. device: String, + /// Full path to a type if this node needs a static associated with each instance. + static_type: Option, }, /// Generate all of the labels as its own node. Labels, @@ -138,7 +140,11 @@ pub enum Action { impl Action { fn generate(&self, _name: &Ident, node: &Node, tree: &DeviceTree) -> TokenStream { match self { - Action::Instance { raw, device } => raw.generate(node, device), + Action::Instance { + raw, + device, + static_type, + } => raw.generate(node, device, static_type.as_deref()), Action::Labels => { let nodes = tree.labels.iter().map(|(k, v)| { let name = dt_to_lower_id(k); @@ -181,8 +187,9 @@ pub enum RawInfo { } impl RawInfo { - fn generate(&self, node: &Node, device: &str) -> TokenStream { + fn generate(&self, node: &Node, device: &str, static_type: Option<&str>) -> TokenStream { let device_id = str_to_path(device); + let static_type = str_to_path(static_type.unwrap_or("crate::device::NoStatic")); match self { Self::Myself => { let ord = node.ord; @@ -192,12 +199,17 @@ impl RawInfo { pub unsafe fn get_instance_raw() -> *const crate::raw::device { &crate::raw::#rawdev } + #[allow(dead_code)] + pub(crate) unsafe fn get_static_raw() -> &'static #static_type { + &STATIC + } static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + static STATIC: #static_type = #static_type::new(); pub fn get_instance() -> Option<#device_id> { unsafe { let device = get_instance_raw(); - #device_id::new(&UNIQUE, device) + #device_id::new(&UNIQUE, &STATIC, device) } } } @@ -220,10 +232,12 @@ impl RawInfo { quote! { static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + static STATIC: #static_type = #static_type::new(); pub fn get_instance() -> Option<#device_id> { unsafe { let device = #target_route :: get_instance_raw(); - #device_id::new(&UNIQUE, device, #(#args),*) + let device_static = #target_route :: get_static_raw(); + #device_id::new(&UNIQUE, &STATIC, device, device_static, #(#args),*) } } } @@ -239,10 +253,11 @@ impl RawInfo { quote! { static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + static STATIC: #static_type = #static_type::new(); pub fn get_instance() -> Option<#device_id> { unsafe { let device = #path :: get_instance_raw(); - #device_id::new(&UNIQUE, device, #(#get_args),*) + #device_id::new(&UNIQUE, &STATIC, device, #(#get_args),*) } } } diff --git a/zephyr/src/device.rs b/zephyr/src/device.rs index 5206d720..0bd80ac3 100644 --- a/zephyr/src/device.rs +++ b/zephyr/src/device.rs @@ -41,3 +41,13 @@ impl Unique { !self.0.fetch_or(true, Ordering::AcqRel) } } + +/// For devices that don't need any associated static data, This NoStatic type will take no space +/// and generate no code, and has the const constructor needed for the type. +pub(crate) struct NoStatic; + +impl NoStatic { + pub(crate) const fn new() -> Self { + Self + } +} diff --git a/zephyr/src/device/flash.rs b/zephyr/src/device/flash.rs index 5d76b422..3372c5a1 100644 --- a/zephyr/src/device/flash.rs +++ b/zephyr/src/device/flash.rs @@ -3,7 +3,7 @@ // Note that currently, the flash partition shares the controller, so the underlying operations // are not actually safe. Need to rethink how to manage this. -use super::Unique; +use super::{NoStatic, Unique}; use crate::raw; /// A flash controller @@ -22,6 +22,7 @@ impl FlashController { #[allow(dead_code)] pub(crate) unsafe fn new( unique: &Unique, + _static: &NoStatic, device: *const raw::device, ) -> Option { if !unique.once() { @@ -50,6 +51,7 @@ impl FlashPartition { #[allow(dead_code)] pub(crate) unsafe fn new( unique: &Unique, + _static: &NoStatic, device: *const raw::device, offset: u32, size: u32, From f4106619564cd5ef3960e1acc1ed487dc43dc913 Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 6 Mar 2025 09:28:03 -0700 Subject: [PATCH 3/5] zephyr: Add async 'wait_for_high' and 'wait_for_low' to gpio Implement a general async `wait_for_high` and `wait_for_low` to the GpioPin interface. The waiting is supported at the level of the gpio driver. Each Gpio instance has an array of `AtomiWaker` to hold the wakers that are waiting on the pins, as well as the callback handler from using the pins in interrupt mode in Zephyr. Although most of the examples of gpios in Zephyr use irq-gpios instead of gpios in the custom properties, there isn't anything that happens when this is done, and regular 'gpios' properties work just fine with interrupts. Signed-off-by: David Brown --- zephyr-sys/wrapper.h | 5 + zephyr/Cargo.toml | 5 + zephyr/src/device/gpio.rs | 250 +++++++++++++++++++++++++++++++++++++- 3 files changed, 257 insertions(+), 3 deletions(-) diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 7d964bb8..98bb9571 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -56,3 +56,8 @@ const uintptr_t ZR_STACK_RESERVED = K_KERNEL_STACK_RESERVED; const uint32_t ZR_POLL_TYPE_SEM_AVAILABLE = K_POLL_TYPE_SEM_AVAILABLE; const uint32_t ZR_POLL_TYPE_SIGNAL = K_POLL_TYPE_SIGNAL; const uint32_t ZR_POLL_TYPE_DATA_AVAILABLE = K_POLL_TYPE_DATA_AVAILABLE; + +#ifdef CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT +const uint32_t ZR_GPIO_INT_MODE_DISABLE_ONLY = GPIO_INT_MODE_DISABLE_ONLY; +const uint32_t ZR_GPIO_INT_MODE_ENABLE_ONLY = GPIO_INT_MODE_ENABLE_ONLY; +#endif diff --git a/zephyr/Cargo.toml b/zephyr/Cargo.toml index 2e340907..bb70549c 100644 --- a/zephyr/Cargo.toml +++ b/zephyr/Cargo.toml @@ -81,3 +81,8 @@ time-driver = [ executor-zephyr = [ "dep:embassy-executor", ] + +# Enables async support in various drivers. +async-drivers = [ + "dep:embassy-sync", +] diff --git a/zephyr/src/device/gpio.rs b/zephyr/src/device/gpio.rs index bf96b09c..f0e52c8a 100644 --- a/zephyr/src/device/gpio.rs +++ b/zephyr/src/device/gpio.rs @@ -9,9 +9,242 @@ use core::ffi::c_int; -use super::Unique; +use super::{NoStatic, Unique}; use crate::raw; +#[cfg(feature = "async-drivers")] +mod async_io { + //! Async operations for gpio drivers. + //! + //! For now, we make an assumption that a gpio controller can contain up to 32 gpios, which is + //! the largest number currently used, although this might change with 64-bit targest in the + //! future. + + use core::{ + cell::UnsafeCell, + future::Future, + mem, + sync::atomic::Ordering, + task::{Poll, Waker}, + }; + + use embassy_sync::waitqueue::AtomicWaker; + use zephyr_sys::{ + device, gpio_add_callback, gpio_callback, gpio_init_callback, gpio_pin_interrupt_configure, + gpio_pin_interrupt_configure_dt, gpio_port_pins_t, GPIO_INT_LEVEL_HIGH, GPIO_INT_LEVEL_LOW, + ZR_GPIO_INT_MODE_DISABLE_ONLY, + }; + + use crate::sync::atomic::{AtomicBool, AtomicU32}; + + use super::{GpioPin, GpioToken}; + + pub(crate) struct GpioStatic { + /// The wakers for each of the gpios. + wakers: [AtomicWaker; 32], + /// Indicates when an interrupt has fired. Used to definitively indicate the event has + /// happened, so we can wake. + fired: AtomicU32, + /// Have we been initialized? + installed: AtomicBool, + /// The data for the callback itself. + callback: UnsafeCell, + } + + unsafe impl Sync for GpioStatic {} + + impl GpioStatic { + pub(crate) const fn new() -> Self { + Self { + wakers: [const { AtomicWaker::new() }; 32], + fired: AtomicU32::new(0), + installed: AtomicBool::new(false), + // SAFETY: `installed` will tell us this need to be installed. + callback: unsafe { mem::zeroed() }, + } + } + + /// Ensure that the callback has been installed. + pub(super) fn fast_install(&self, port: *const device) { + if !self.installed.load(Ordering::Acquire) { + self.install(port); + } + } + + fn install(&self, port: *const device) { + critical_section::with(|_| { + if !self.installed.load(Ordering::Acquire) { + let cb = self.callback.get(); + // SAFETY: We're in a critical section, so there should be no concurrent use, + // and there should not be any calls from the driver. + unsafe { + gpio_init_callback(cb, Some(Self::callback_handler), 0); + gpio_add_callback(port, cb); + } + + self.installed.store(true, Ordering::Release); + } + }) + } + + /// Register (replacing) a given callback. + pub(super) fn register(&self, pin: u8, waker: &Waker) { + self.wakers[pin as usize].register(waker); + + // SAFETY: Inherently unsafe, due to how the Zephyr API is defined. + // The API appears to assume coherent memory, which although untrue, probably is "close + // enough" on the supported targets. + // The main issue is to ensure that any race is resolved in the direction of getting the + // callback more than needed, rather than missing. In the context here, ensure the + // waker is registered (which does use an atomic), before enabling the pin in the + // callback structure. + // + // If it seems that wakes are getting missed, it might be the case that this needs some + // kind of memory barrier. + let cb = self.callback.get(); + unsafe { + (*cb).pin_mask |= 1 << pin; + } + } + + extern "C" fn callback_handler( + port: *const device, + cb: *mut gpio_callback, + mut pins: gpio_port_pins_t, + ) { + let data = unsafe { + cb.cast::() + .sub(mem::offset_of!(Self, callback)) + .cast::() + }; + + // For each pin we are informed of. + while pins > 0 { + let pin = pins.trailing_zeros(); + + pins &= !(1 << pin); + + // SAFETY: Handling this correctly is a bit tricky, especially with the + // un-coordinated 'pin-mask' value. + // + // For level-triggered interrupts, not disabling this will result in an interrupt + // storm. + unsafe { + // Disable the actual interrupt from the controller. + gpio_pin_interrupt_configure(port, pin as u8, ZR_GPIO_INT_MODE_DISABLE_ONLY); + + // Remove the callback bit. Unclear if this is actually useful. + (*cb).pin_mask &= !(1 << pin); + + // Indicate that we have fired. + // AcqRel is sufficient for ordering across a single atomic. + (*data).fired.fetch_or(1 << pin, Ordering::AcqRel); + + // After the interrupt is off, wake the handler. + (*data).wakers[pin as usize].wake(); + } + } + } + + /// Check if we have fired for a given pin. Clears the status. + pub(crate) fn has_fired(&self, pin: u8) -> bool { + let value = self.fired.fetch_and(!(1 << pin), Ordering::AcqRel); + value & (1 << pin) != 0 + } + } + + impl GpioPin { + /// Asynchronously wait for a gpio pin to become high. + /// + /// # Safety + /// + /// The `_token` enforces single use of gpios. Note that this makes it impossible to wait for + /// more than one GPIO. + /// + pub unsafe fn wait_for_high( + &mut self, + _token: &mut GpioToken, + ) -> impl Future + use<'_> { + GpioWait::new(self, 1) + } + + /// Asynchronously wait for a gpio pin to become low. + /// + /// # Safety + /// + /// The `_token` enforces single use of gpios. Note that this makes it impossible to wait + /// for more than one GPIO. + pub unsafe fn wait_for_low( + &mut self, + _token: &mut GpioToken, + ) -> impl Future + use<'_> { + GpioWait::new(self, 0) + } + } + + /// A future that waits for a gpio to become high. + pub struct GpioWait<'a> { + pin: &'a mut GpioPin, + level: u8, + } + + impl<'a> GpioWait<'a> { + fn new(pin: &'a mut GpioPin, level: u8) -> Self { + Self { pin, level } + } + } + + impl<'a> Future for GpioWait<'a> { + type Output = (); + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + self.pin.data.fast_install(self.pin.pin.port); + + // Early detection of the event. Also clears. + // This should be non-racy as long as only one task at a time waits on the gpio. + if self.pin.data.has_fired(self.pin.pin.pin) { + return Poll::Ready(()); + } + + self.pin.data.register(self.pin.pin.pin, cx.waker()); + + let mode = match self.level { + 0 => GPIO_INT_LEVEL_LOW, + 1 => GPIO_INT_LEVEL_HIGH, + _ => unreachable!(), + }; + + unsafe { + gpio_pin_interrupt_configure_dt(&self.pin.pin, mode); + + // Before sleeping, check if it fired, to avoid having to pend if it already + // happened. + if self.pin.data.has_fired(self.pin.pin.pin) { + return Poll::Ready(()); + } + } + + Poll::Pending + } + } +} + +#[cfg(not(feature = "async-drivers"))] +mod async_io { + pub(crate) struct GpioStatic; + + impl GpioStatic { + pub(crate) const fn new() -> Self { + Self + } + } +} + +pub(crate) use async_io::*; + /// Global instance to help make gpio in Rust slightly safer. /// /// # Safety @@ -47,6 +280,8 @@ pub struct Gpio { /// The underlying device itself. #[allow(dead_code)] pub(crate) device: *const raw::device, + /// Our associated data, used for callbacks. + pub(crate) data: &'static GpioStatic, } // SAFETY: Gpio's can be shared with other threads. Safety is maintained by the Token. @@ -57,11 +292,15 @@ impl Gpio { /// /// TODO: Guarantee single instancing. #[allow(dead_code)] - pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { + pub(crate) unsafe fn new( + unique: &Unique, + data: &'static GpioStatic, + device: *const raw::device, + ) -> Option { if !unique.once() { return None; } - Some(Gpio { device }) + Some(Gpio { device, data }) } /// Verify that the device is ready for use. At a minimum, this means the device has been @@ -79,6 +318,7 @@ impl Gpio { #[allow(dead_code)] pub struct GpioPin { pub(crate) pin: raw::gpio_dt_spec, + pub(crate) data: &'static GpioStatic, } // SAFETY: GpioPin's can be shared with other threads. Safety is maintained by the Token. @@ -89,7 +329,9 @@ impl GpioPin { #[allow(dead_code)] pub(crate) unsafe fn new( unique: &Unique, + _static: &NoStatic, device: *const raw::device, + device_static: &'static GpioStatic, pin: u32, dt_flags: u32, ) -> Option { @@ -102,6 +344,7 @@ impl GpioPin { pin: pin as raw::gpio_pin_t, dt_flags: dt_flags as raw::gpio_dt_flags_t, }, + data: device_static, }) } @@ -115,6 +358,7 @@ impl GpioPin { pub fn get_gpio(&self) -> Gpio { Gpio { device: self.pin.port, + data: self.data, } } From 159ddd611066548202c79e9ada13a8d39ad69d49 Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 3 Mar 2025 16:38:04 -0700 Subject: [PATCH 4/5] tests: drivers: gpio-async: Test the async gpio operations This is a basic test to test for the waiting gpios. Currently, this test requires a specific hardware setup. To test on the rpi-pico, it is necessary to connect GPIO-0 to GPIO-6 with a button or switch. Running should then print Pressed/Released on a debounced version of that switch, with the code not polling the gpio while waiting. Signed-off-by: David Brown --- tests/drivers/gpio-async/CMakeLists.txt | 8 ++ tests/drivers/gpio-async/Cargo.toml | 45 +++++++++++ .../boards/pimoroni_tiny_2040.overlay | 13 ++++ .../gpio-async/boards/rpi_pico.overlay | 13 ++++ .../gpio-async/pimoroni_tiny_2040.conf | 7 ++ tests/drivers/gpio-async/prj.conf | 16 ++++ tests/drivers/gpio-async/rpi_pico.conf | 7 ++ tests/drivers/gpio-async/src/lib.rs | 77 +++++++++++++++++++ tests/drivers/gpio-async/testcase.yaml | 13 ++++ 9 files changed, 199 insertions(+) create mode 100644 tests/drivers/gpio-async/CMakeLists.txt create mode 100644 tests/drivers/gpio-async/Cargo.toml create mode 100644 tests/drivers/gpio-async/boards/pimoroni_tiny_2040.overlay create mode 100644 tests/drivers/gpio-async/boards/rpi_pico.overlay create mode 100644 tests/drivers/gpio-async/pimoroni_tiny_2040.conf create mode 100644 tests/drivers/gpio-async/prj.conf create mode 100644 tests/drivers/gpio-async/rpi_pico.conf create mode 100644 tests/drivers/gpio-async/src/lib.rs create mode 100644 tests/drivers/gpio-async/testcase.yaml diff --git a/tests/drivers/gpio-async/CMakeLists.txt b/tests/drivers/gpio-async/CMakeLists.txt new file mode 100644 index 00000000..3a7f7ab5 --- /dev/null +++ b/tests/drivers/gpio-async/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(gpio_async) +rust_cargo_application() diff --git a/tests/drivers/gpio-async/Cargo.toml b/tests/drivers/gpio-async/Cargo.toml new file mode 100644 index 00000000..ed59653b --- /dev/null +++ b/tests/drivers/gpio-async/Cargo.toml @@ -0,0 +1,45 @@ +# 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 = "Test async gpios" +license = "Apache-2.0 or MIT" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +zephyr = { version = "0.1.0", features = ["time-driver", "executor-zephyr", "async-drivers"] } +log = "0.4.22" +static_cell = "2.1" +heapless = "0.8" + +[dependencies.embassy-executor] +version = "0.7.0" +features = [ + "log", + "task-arena-size-2048", +] + +[dependencies.embassy-futures] +version = "0.1.1" + +[dependencies.embassy-sync] +version = "0.6.2" + +[dependencies.embassy-time] +version = "0.4.0" +features = ["tick-hz-10_000"] + +[dependencies.critical-section] +version = "1.2" + +[profile.dev] +opt-level = 1 + +[profile.release] +debug = true diff --git a/tests/drivers/gpio-async/boards/pimoroni_tiny_2040.overlay b/tests/drivers/gpio-async/boards/pimoroni_tiny_2040.overlay new file mode 100644 index 00000000..6b33d86d --- /dev/null +++ b/tests/drivers/gpio-async/boards/pimoroni_tiny_2040.overlay @@ -0,0 +1,13 @@ +/ { + gpio-leds { + compatible = "gpio-leds"; + col0: col0 { + gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>; + label = "Column 0"; + }; + row0: row0 { + gpios = <&gpio0 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; + label = "Row 0"; + }; + }; +}; diff --git a/tests/drivers/gpio-async/boards/rpi_pico.overlay b/tests/drivers/gpio-async/boards/rpi_pico.overlay new file mode 100644 index 00000000..6b33d86d --- /dev/null +++ b/tests/drivers/gpio-async/boards/rpi_pico.overlay @@ -0,0 +1,13 @@ +/ { + gpio-leds { + compatible = "gpio-leds"; + col0: col0 { + gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>; + label = "Column 0"; + }; + row0: row0 { + gpios = <&gpio0 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; + label = "Row 0"; + }; + }; +}; diff --git a/tests/drivers/gpio-async/pimoroni_tiny_2040.conf b/tests/drivers/gpio-async/pimoroni_tiny_2040.conf new file mode 100644 index 00000000..94c0843a --- /dev/null +++ b/tests/drivers/gpio-async/pimoroni_tiny_2040.conf @@ -0,0 +1,7 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +# This board doesn't have a serial console, so use RTT. +CONFIG_UART_CONSOLE=n +CONFIG_RTT_CONSOLE=y +CONFIG_USE_SEGGER_RTT=y diff --git a/tests/drivers/gpio-async/prj.conf b/tests/drivers/gpio-async/prj.conf new file mode 100644 index 00000000..f9a269ba --- /dev/null +++ b/tests/drivers/gpio-async/prj.conf @@ -0,0 +1,16 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_DEBUG=y + +# The default 1k stack isn't large enough for rust string formatting with logging. +CONFIG_MAIN_STACK_SIZE=4096 + +CONFIG_RUST=y + +CONFIG_RUST_ALLOC=y +# CONFIG_LOG=y +CONFIG_GPIO=y +CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT=y + +CONFIG_LOG_BACKEND_RTT=n diff --git a/tests/drivers/gpio-async/rpi_pico.conf b/tests/drivers/gpio-async/rpi_pico.conf new file mode 100644 index 00000000..94c0843a --- /dev/null +++ b/tests/drivers/gpio-async/rpi_pico.conf @@ -0,0 +1,7 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +# This board doesn't have a serial console, so use RTT. +CONFIG_UART_CONSOLE=n +CONFIG_RTT_CONSOLE=y +CONFIG_USE_SEGGER_RTT=y diff --git a/tests/drivers/gpio-async/src/lib.rs b/tests/drivers/gpio-async/src/lib.rs new file mode 100644 index 00000000..ebccc5ff --- /dev/null +++ b/tests/drivers/gpio-async/src/lib.rs @@ -0,0 +1,77 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +extern crate alloc; + +use embassy_time::{Duration, Ticker}; +use zephyr::{ + device::gpio::{GpioPin, GpioToken}, + embassy::Executor, + raw::{GPIO_INPUT, GPIO_OUTPUT_ACTIVE, GPIO_PULL_DOWN}, +}; + +use embassy_executor::Spawner; +use log::info; +use static_cell::StaticCell; + +static EXECUTOR_MAIN: StaticCell = StaticCell::new(); + +#[no_mangle] +extern "C" fn rust_main() { + unsafe { + zephyr::set_logger().unwrap(); + } + + let executor = EXECUTOR_MAIN.init(Executor::new()); + executor.run(|spawner| { + spawner.spawn(main(spawner)).unwrap(); + }) +} + +#[embassy_executor::task] +async fn main(spawner: Spawner) { + info!("Hello world"); + let _ = spawner; + + let mut col0 = zephyr::devicetree::labels::col0::get_instance().unwrap(); + let mut row0 = zephyr::devicetree::labels::row0::get_instance().unwrap(); + let mut gpio_token = unsafe { zephyr::device::gpio::GpioToken::get_instance().unwrap() }; + + unsafe { + col0.configure(&mut gpio_token, GPIO_OUTPUT_ACTIVE); + col0.set(&mut gpio_token, true); + row0.configure(&mut gpio_token, GPIO_INPUT | GPIO_PULL_DOWN); + } + + loop { + unsafe { row0.wait_for_high(&mut gpio_token).await }; + // Simple debounce, Wait for 20 consecutive high samples. + debounce(&mut row0, &mut gpio_token, true).await; + info!("Pressed"); + unsafe { row0.wait_for_low(&mut gpio_token).await }; + debounce(&mut row0, &mut gpio_token, false).await; + info!("Released"); + } +} + +/// Simple debounce. Scan the gpio periodically, and return when we have 20 consecutive samples of +/// the intended value. +async fn debounce(pin: &mut GpioPin, gpio_token: &mut GpioToken, level: bool) { + let mut count = 0; + let mut ticker = Ticker::every(Duration::from_millis(1)); + loop { + ticker.next().await; + + if unsafe { pin.get(gpio_token) } == level { + count += 1; + + if count >= 20 { + return; + } + } else { + count = 0; + } + } +} diff --git a/tests/drivers/gpio-async/testcase.yaml b/tests/drivers/gpio-async/testcase.yaml new file mode 100644 index 00000000..cd915ba0 --- /dev/null +++ b/tests/drivers/gpio-async/testcase.yaml @@ -0,0 +1,13 @@ +common: + filter: CONFIG_RUST_SUPPORTED + platform_allow: + - rpi_pico +tests: + test.gpio-async: + harness: console + harness_config: + type: one_line + regex: + # This doesn't actually happen, as this test requires specific hardware and buttons to be + # pressed. + - "All tests passed" From 58652d4e738268dd0e305a6c0f6b12ee0bbdc36f Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 5 Mar 2025 13:33:13 -0700 Subject: [PATCH 5/5] docgen: Enable async features for doc generation Enable the async functionality so it will be included in the generated documentation. This also requires enabling an additional KConfig needed to be able to configure the state of interrupts on gpios. Signed-off-by: David Brown --- docgen/Cargo.toml | 2 +- docgen/prj.conf | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docgen/Cargo.toml b/docgen/Cargo.toml index 3e384c26..69ade174 100644 --- a/docgen/Cargo.toml +++ b/docgen/Cargo.toml @@ -13,4 +13,4 @@ license = "Apache-2.0 or MIT" crate-type = ["staticlib"] [dependencies] -zephyr = "0.1.0" +zephyr = { version = "0.1.0", features = ["executor-zephyr", "async-drivers"] } diff --git a/docgen/prj.conf b/docgen/prj.conf index 6ffe361e..4c55eb98 100644 --- a/docgen/prj.conf +++ b/docgen/prj.conf @@ -4,5 +4,6 @@ CONFIG_RUST=y CONFIG_RUST_ALLOC=y CONFIG_GPIO=y +CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT=y CONFIG_PRINTK=y CONFIG_POLL=y