Skip to content

Commit eb962b1

Browse files
committed
zephyr: device: gpio: Add async wait_for_high (WIP)
If the new feature `async-drivers` is enabled, add a method to the GpioPin driver to 'wait for high'. This will configure the underlying driver to callback when the gpio goes high, and use this to wake the task. Signed-off-by: David Brown <[email protected]>
1 parent 84663ed commit eb962b1

File tree

3 files changed

+197
-3
lines changed

3 files changed

+197
-3
lines changed

zephyr-sys/wrapper.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,8 @@ const uintptr_t ZR_STACK_RESERVED = K_KERNEL_STACK_RESERVED;
5656
const uint32_t ZR_POLL_TYPE_SEM_AVAILABLE = K_POLL_TYPE_SEM_AVAILABLE;
5757
const uint32_t ZR_POLL_TYPE_SIGNAL = K_POLL_TYPE_SIGNAL;
5858
const uint32_t ZR_POLL_TYPE_DATA_AVAILABLE = K_POLL_TYPE_DATA_AVAILABLE;
59+
60+
#ifdef CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT
61+
const uint32_t ZR_GPIO_INT_MODE_DISABLE_ONLY = GPIO_INT_MODE_DISABLE_ONLY;
62+
const uint32_t ZR_GPIO_INT_MODE_ENABLE_ONLY = GPIO_INT_MODE_ENABLE_ONLY;
63+
#endif

zephyr/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,8 @@ time-driver = [
8181
executor-zephyr = [
8282
"dep:embassy-executor",
8383
]
84+
85+
# Enables async support in various drivers.
86+
async-drivers = [
87+
"dep:embassy-sync",
88+
]

zephyr/src/device/gpio.rs

Lines changed: 187 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,186 @@
99
1010
use core::ffi::c_int;
1111

12-
use super::Unique;
12+
use super::{NoStatic, Unique};
1313
use crate::raw;
1414

15+
#[cfg(feature = "async-drivers")]
16+
mod async_io {
17+
//! Async operations for gpio drivers.
18+
//!
19+
//! For now, we make an assumption that a gpio controller can contain up to 32 gpios, which is
20+
//! the largest number currently used, although this might change with 64-bit targest in the
21+
//! future.
22+
23+
use core::{cell::UnsafeCell, future::Future, mem, sync::atomic::Ordering, task::{Poll, Waker}};
24+
25+
use embassy_sync::waitqueue::AtomicWaker;
26+
use portable_atomic::AtomicBool;
27+
use zephyr_sys::{device, gpio_add_callback, gpio_callback, gpio_init_callback, gpio_pin_get, gpio_pin_interrupt_configure, gpio_pin_interrupt_configure_dt, gpio_port_pins_t, GPIO_INT_LEVEL_HIGH, ZR_GPIO_INT_MODE_DISABLE_ONLY};
28+
29+
use super::{GpioPin, GpioToken};
30+
31+
pub(crate) struct GpioStatic {
32+
/// The wakers for each of the gpios.
33+
wakers: [AtomicWaker; 32],
34+
/// Have we been initialized?
35+
installed: AtomicBool,
36+
/// The data for the callback itself.
37+
callback: UnsafeCell<gpio_callback>,
38+
}
39+
40+
unsafe impl Sync for GpioStatic {}
41+
42+
impl GpioStatic {
43+
pub(crate) const fn new() -> Self {
44+
Self {
45+
wakers: [const { AtomicWaker::new() }; 32],
46+
installed: AtomicBool::new(false),
47+
// SAFETY: `installed` will tell us this need to be installed.
48+
callback: unsafe { mem::zeroed() },
49+
}
50+
}
51+
52+
/// Ensure that the callback has been installed.
53+
pub(super) fn fast_install(&self, port: *const device) {
54+
if !self.installed.load(Ordering::Acquire) {
55+
self.install(port);
56+
}
57+
}
58+
59+
fn install(&self, port: *const device) {
60+
critical_section::with(|_| {
61+
if !self.installed.load(Ordering::Acquire) {
62+
let cb = self.callback.get();
63+
// SAFETY: We're in a critical section, so there should be no concurrent use,
64+
// and there should not be any calls from the driver.
65+
unsafe {
66+
gpio_init_callback(cb, Some(Self::callback_handler), 0);
67+
gpio_add_callback(port, cb);
68+
}
69+
70+
self.installed.store(true, Ordering::Release);
71+
}
72+
})
73+
}
74+
75+
/// Register (replacing) a given callback.
76+
pub(super) fn register(&self, pin: u8, waker: &Waker) {
77+
self.wakers[pin as usize].register(waker);
78+
79+
// SAFETY: Inherently unsafe, due to how the Zephyr API is defined.
80+
// The API appears to assume coherent memory, which although untrue, probably is "close
81+
// enough" on the supported targets.
82+
// The main issue is to ensure that any race is resolved in the direction of getting the
83+
// callback more than needed, rather than missing. In the context here, ensure the
84+
// waker is registered (which does use an atomic), before enabling the pin in the
85+
// callback structure.
86+
//
87+
// If it seems that wakes are getting missed, it might be the case that this needs some
88+
// kind of memory barrier.
89+
let cb = self.callback.get();
90+
unsafe {
91+
(*cb).pin_mask |= 1 << pin;
92+
}
93+
}
94+
95+
extern "C" fn callback_handler(port: *const device, cb: *mut gpio_callback, mut pins: gpio_port_pins_t) {
96+
let data = unsafe { cb
97+
.cast::<u8>()
98+
.sub(mem::offset_of!(Self, callback))
99+
.cast::<Self>()
100+
};
101+
102+
// printkln!("CB called: pins: {pins:#x}");
103+
104+
// For each pin we are informed of.
105+
while pins > 0 {
106+
let pin = pins.trailing_zeros();
107+
108+
pins &= !(1 << pin);
109+
110+
// SAFETY: Handling this correctly is a bit tricky, especially with the
111+
// un-coordinated 'pin-mask' value.
112+
//
113+
// For level-triggered interrupts, not disabling this will result in an interrupt
114+
// storm.
115+
unsafe {
116+
// Disable the actual interrupt from the controller.
117+
gpio_pin_interrupt_configure(port, pin as u8, ZR_GPIO_INT_MODE_DISABLE_ONLY);
118+
119+
// Remove the callback bit. Unclear if this is actually useful.
120+
// (*cb).pin_mask &= !(1 << pin);
121+
122+
// After the interrupt is off, wake the handler.
123+
(*data).wakers[pin as usize].wake();
124+
}
125+
}
126+
}
127+
}
128+
129+
impl GpioPin {
130+
/// Asynchronously wait for a gpio pin to become high.
131+
///
132+
/// # Safety
133+
///
134+
/// The `_token` enforces single use of gpios. Note that this makes it impossible to wait for
135+
/// more than one GPIO.
136+
///
137+
pub unsafe fn wait_for_high(&mut self, _token: &mut GpioToken) -> impl Future<Output = ()> + use<'_> {
138+
GpioWait::new(self)
139+
}
140+
}
141+
142+
/// A future that waits for a gpio to become high.
143+
pub struct GpioWait<'a> {
144+
pin: &'a mut GpioPin,
145+
}
146+
147+
impl<'a> GpioWait<'a> {
148+
fn new(pin: &'a mut GpioPin) -> Self {
149+
Self {
150+
pin,
151+
}
152+
}
153+
}
154+
155+
impl<'a> Future for GpioWait<'a> {
156+
type Output = ();
157+
158+
fn poll(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> core::task::Poll<Self::Output> {
159+
self.pin.data.fast_install(self.pin.pin.port);
160+
161+
self.pin.data.register(self.pin.pin.pin, cx.waker());
162+
163+
unsafe {
164+
gpio_pin_interrupt_configure_dt(&self.pin.pin, GPIO_INT_LEVEL_HIGH);
165+
166+
if gpio_pin_get(self.pin.pin.port, self.pin.pin.pin) == 1 {
167+
// TODO: Need to match with level.
168+
// Zephyr doesn't have a way to determine if a given interrupt is pending, so the
169+
// best we can do is just read the pin. This doesn't work for edges though.
170+
return Poll::Ready(());
171+
}
172+
}
173+
174+
Poll::Pending
175+
}
176+
}
177+
}
178+
179+
#[cfg(not(feature = "async-drivers"))]
180+
mod async_io {
181+
pub(crate) struct GpioStatic;
182+
183+
impl GpioStatic {
184+
pub(crate) const fn new() -> Self {
185+
Self
186+
}
187+
}
188+
}
189+
190+
pub(crate) use async_io::*;
191+
15192
/// Global instance to help make gpio in Rust slightly safer.
16193
///
17194
/// # Safety
@@ -47,6 +224,8 @@ pub struct Gpio {
47224
/// The underlying device itself.
48225
#[allow(dead_code)]
49226
pub(crate) device: *const raw::device,
227+
/// Our associated data, used for callbacks.
228+
pub(crate) data: &'static GpioStatic,
50229
}
51230

52231
// SAFETY: Gpio's can be shared with other threads. Safety is maintained by the Token.
@@ -57,11 +236,11 @@ impl Gpio {
57236
///
58237
/// TODO: Guarantee single instancing.
59238
#[allow(dead_code)]
60-
pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option<Gpio> {
239+
pub(crate) unsafe fn new(unique: &Unique, data: &'static GpioStatic, device: *const raw::device) -> Option<Gpio> {
61240
if !unique.once() {
62241
return None;
63242
}
64-
Some(Gpio { device })
243+
Some(Gpio { device, data })
65244
}
66245

67246
/// Verify that the device is ready for use. At a minimum, this means the device has been
@@ -79,6 +258,7 @@ impl Gpio {
79258
#[allow(dead_code)]
80259
pub struct GpioPin {
81260
pub(crate) pin: raw::gpio_dt_spec,
261+
pub(crate) data: &'static GpioStatic,
82262
}
83263

84264
// SAFETY: GpioPin's can be shared with other threads. Safety is maintained by the Token.
@@ -89,7 +269,9 @@ impl GpioPin {
89269
#[allow(dead_code)]
90270
pub(crate) unsafe fn new(
91271
unique: &Unique,
272+
_static: &NoStatic,
92273
device: *const raw::device,
274+
device_static: &'static GpioStatic,
93275
pin: u32,
94276
dt_flags: u32,
95277
) -> Option<GpioPin> {
@@ -102,6 +284,7 @@ impl GpioPin {
102284
pin: pin as raw::gpio_pin_t,
103285
dt_flags: dt_flags as raw::gpio_dt_flags_t,
104286
},
287+
data: device_static,
105288
})
106289
}
107290

@@ -115,6 +298,7 @@ impl GpioPin {
115298
pub fn get_gpio(&self) -> Gpio {
116299
Gpio {
117300
device: self.pin.port,
301+
data: self.data,
118302
}
119303
}
120304

0 commit comments

Comments
 (0)