Skip to content

Add async gpio support #64

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
1 change: 1 addition & 0 deletions docgen/prj.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions dt-rust.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions tests/drivers/gpio-async/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
45 changes: 45 additions & 0 deletions tests/drivers/gpio-async/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions tests/drivers/gpio-async/boards/pimoroni_tiny_2040.overlay
Original file line number Diff line number Diff line change
@@ -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";
};
};
};
13 changes: 13 additions & 0 deletions tests/drivers/gpio-async/boards/rpi_pico.overlay
Original file line number Diff line number Diff line change
@@ -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";
};
};
};
7 changes: 7 additions & 0 deletions tests/drivers/gpio-async/pimoroni_tiny_2040.conf
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions tests/drivers/gpio-async/prj.conf
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions tests/drivers/gpio-async/rpi_pico.conf
Original file line number Diff line number Diff line change
@@ -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
77 changes: 77 additions & 0 deletions tests/drivers/gpio-async/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Executor> = 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;
}
}
}
13 changes: 13 additions & 0 deletions tests/drivers/gpio-async/testcase.yaml
Original file line number Diff line number Diff line change
@@ -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"
25 changes: 20 additions & 5 deletions zephyr-build/src/devicetree/augment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
},
/// Generate all of the labels as its own node.
Labels,
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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)
}
}
}
Expand All @@ -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),*)
}
}
}
Expand All @@ -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),*)
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions zephyr-sys/wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions zephyr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,8 @@ time-driver = [
executor-zephyr = [
"dep:embassy-executor",
]

# Enables async support in various drivers.
async-drivers = [
"dep:embassy-sync",
]
10 changes: 10 additions & 0 deletions zephyr/src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
4 changes: 3 additions & 1 deletion zephyr/src/device/flash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,6 +22,7 @@ impl FlashController {
#[allow(dead_code)]
pub(crate) unsafe fn new(
unique: &Unique,
_static: &NoStatic,
device: *const raw::device,
) -> Option<FlashController> {
if !unique.once() {
Expand Down Expand Up @@ -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,
Expand Down
Loading