Skip to content

Commit 0347eac

Browse files
committed
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 <[email protected]>
1 parent b60408c commit 0347eac

File tree

3 files changed

+257
-3
lines changed

3 files changed

+257
-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: 247 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,242 @@
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::{
24+
cell::UnsafeCell,
25+
future::Future,
26+
mem,
27+
sync::atomic::Ordering,
28+
task::{Poll, Waker},
29+
};
30+
31+
use embassy_sync::waitqueue::AtomicWaker;
32+
use zephyr_sys::{
33+
device, gpio_add_callback, gpio_callback, gpio_init_callback, gpio_pin_interrupt_configure,
34+
gpio_pin_interrupt_configure_dt, gpio_port_pins_t, GPIO_INT_LEVEL_HIGH, GPIO_INT_LEVEL_LOW,
35+
ZR_GPIO_INT_MODE_DISABLE_ONLY,
36+
};
37+
38+
use crate::sync::atomic::{AtomicBool, AtomicU32};
39+
40+
use super::{GpioPin, GpioToken};
41+
42+
pub(crate) struct GpioStatic {
43+
/// The wakers for each of the gpios.
44+
wakers: [AtomicWaker; 32],
45+
/// Indicates when an interrupt has fired. Used to definitively indicate the event has
46+
/// happened, so we can wake.
47+
fired: AtomicU32,
48+
/// Have we been initialized?
49+
installed: AtomicBool,
50+
/// The data for the callback itself.
51+
callback: UnsafeCell<gpio_callback>,
52+
}
53+
54+
unsafe impl Sync for GpioStatic {}
55+
56+
impl GpioStatic {
57+
pub(crate) const fn new() -> Self {
58+
Self {
59+
wakers: [const { AtomicWaker::new() }; 32],
60+
fired: AtomicU32::new(0),
61+
installed: AtomicBool::new(false),
62+
// SAFETY: `installed` will tell us this need to be installed.
63+
callback: unsafe { mem::zeroed() },
64+
}
65+
}
66+
67+
/// Ensure that the callback has been installed.
68+
pub(super) fn fast_install(&self, port: *const device) {
69+
if !self.installed.load(Ordering::Acquire) {
70+
self.install(port);
71+
}
72+
}
73+
74+
fn install(&self, port: *const device) {
75+
critical_section::with(|_| {
76+
if !self.installed.load(Ordering::Acquire) {
77+
let cb = self.callback.get();
78+
// SAFETY: We're in a critical section, so there should be no concurrent use,
79+
// and there should not be any calls from the driver.
80+
unsafe {
81+
gpio_init_callback(cb, Some(Self::callback_handler), 0);
82+
gpio_add_callback(port, cb);
83+
}
84+
85+
self.installed.store(true, Ordering::Release);
86+
}
87+
})
88+
}
89+
90+
/// Register (replacing) a given callback.
91+
pub(super) fn register(&self, pin: u8, waker: &Waker) {
92+
self.wakers[pin as usize].register(waker);
93+
94+
// SAFETY: Inherently unsafe, due to how the Zephyr API is defined.
95+
// The API appears to assume coherent memory, which although untrue, probably is "close
96+
// enough" on the supported targets.
97+
// The main issue is to ensure that any race is resolved in the direction of getting the
98+
// callback more than needed, rather than missing. In the context here, ensure the
99+
// waker is registered (which does use an atomic), before enabling the pin in the
100+
// callback structure.
101+
//
102+
// If it seems that wakes are getting missed, it might be the case that this needs some
103+
// kind of memory barrier.
104+
let cb = self.callback.get();
105+
unsafe {
106+
(*cb).pin_mask |= 1 << pin;
107+
}
108+
}
109+
110+
extern "C" fn callback_handler(
111+
port: *const device,
112+
cb: *mut gpio_callback,
113+
mut pins: gpio_port_pins_t,
114+
) {
115+
let data = unsafe {
116+
cb.cast::<u8>()
117+
.sub(mem::offset_of!(Self, callback))
118+
.cast::<Self>()
119+
};
120+
121+
// For each pin we are informed of.
122+
while pins > 0 {
123+
let pin = pins.trailing_zeros();
124+
125+
pins &= !(1 << pin);
126+
127+
// SAFETY: Handling this correctly is a bit tricky, especially with the
128+
// un-coordinated 'pin-mask' value.
129+
//
130+
// For level-triggered interrupts, not disabling this will result in an interrupt
131+
// storm.
132+
unsafe {
133+
// Disable the actual interrupt from the controller.
134+
gpio_pin_interrupt_configure(port, pin as u8, ZR_GPIO_INT_MODE_DISABLE_ONLY);
135+
136+
// Remove the callback bit. Unclear if this is actually useful.
137+
(*cb).pin_mask &= !(1 << pin);
138+
139+
// Indicate that we have fired.
140+
// AcqRel is sufficient for ordering across a single atomic.
141+
(*data).fired.fetch_or(1 << pin, Ordering::AcqRel);
142+
143+
// After the interrupt is off, wake the handler.
144+
(*data).wakers[pin as usize].wake();
145+
}
146+
}
147+
}
148+
149+
/// Check if we have fired for a given pin. Clears the status.
150+
pub(crate) fn has_fired(&self, pin: u8) -> bool {
151+
let value = self.fired.fetch_and(!(1 << pin), Ordering::AcqRel);
152+
value & (1 << pin) != 0
153+
}
154+
}
155+
156+
impl GpioPin {
157+
/// Asynchronously wait for a gpio pin to become high.
158+
///
159+
/// # Safety
160+
///
161+
/// The `_token` enforces single use of gpios. Note that this makes it impossible to wait for
162+
/// more than one GPIO.
163+
///
164+
pub unsafe fn wait_for_high(
165+
&mut self,
166+
_token: &mut GpioToken,
167+
) -> impl Future<Output = ()> + use<'_> {
168+
GpioWait::new(self, 1)
169+
}
170+
171+
/// Asynchronously wait for a gpio pin to become low.
172+
///
173+
/// # Safety
174+
///
175+
/// The `_token` enforces single use of gpios. Note that this makes it impossible to wait
176+
/// for more than one GPIO.
177+
pub unsafe fn wait_for_low(
178+
&mut self,
179+
_token: &mut GpioToken,
180+
) -> impl Future<Output = ()> + use<'_> {
181+
GpioWait::new(self, 0)
182+
}
183+
}
184+
185+
/// A future that waits for a gpio to become high.
186+
pub struct GpioWait<'a> {
187+
pin: &'a mut GpioPin,
188+
level: u8,
189+
}
190+
191+
impl<'a> GpioWait<'a> {
192+
fn new(pin: &'a mut GpioPin, level: u8) -> Self {
193+
Self { pin, level }
194+
}
195+
}
196+
197+
impl<'a> Future for GpioWait<'a> {
198+
type Output = ();
199+
200+
fn poll(
201+
self: core::pin::Pin<&mut Self>,
202+
cx: &mut core::task::Context<'_>,
203+
) -> core::task::Poll<Self::Output> {
204+
self.pin.data.fast_install(self.pin.pin.port);
205+
206+
// Early detection of the event. Also clears.
207+
// This should be non-racy as long as only one task at a time waits on the gpio.
208+
if self.pin.data.has_fired(self.pin.pin.pin) {
209+
return Poll::Ready(());
210+
}
211+
212+
self.pin.data.register(self.pin.pin.pin, cx.waker());
213+
214+
let mode = match self.level {
215+
0 => GPIO_INT_LEVEL_LOW,
216+
1 => GPIO_INT_LEVEL_HIGH,
217+
_ => unreachable!(),
218+
};
219+
220+
unsafe {
221+
gpio_pin_interrupt_configure_dt(&self.pin.pin, mode);
222+
223+
// Before sleeping, check if it fired, to avoid having to pend if it already
224+
// happened.
225+
if self.pin.data.has_fired(self.pin.pin.pin) {
226+
return Poll::Ready(());
227+
}
228+
}
229+
230+
Poll::Pending
231+
}
232+
}
233+
}
234+
235+
#[cfg(not(feature = "async-drivers"))]
236+
mod async_io {
237+
pub(crate) struct GpioStatic;
238+
239+
impl GpioStatic {
240+
pub(crate) const fn new() -> Self {
241+
Self
242+
}
243+
}
244+
}
245+
246+
pub(crate) use async_io::*;
247+
15248
/// Global instance to help make gpio in Rust slightly safer.
16249
///
17250
/// # Safety
@@ -47,6 +280,8 @@ pub struct Gpio {
47280
/// The underlying device itself.
48281
#[allow(dead_code)]
49282
pub(crate) device: *const raw::device,
283+
/// Our associated data, used for callbacks.
284+
pub(crate) data: &'static GpioStatic,
50285
}
51286

52287
// SAFETY: Gpio's can be shared with other threads. Safety is maintained by the Token.
@@ -57,11 +292,15 @@ impl Gpio {
57292
///
58293
/// TODO: Guarantee single instancing.
59294
#[allow(dead_code)]
60-
pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option<Gpio> {
295+
pub(crate) unsafe fn new(
296+
unique: &Unique,
297+
data: &'static GpioStatic,
298+
device: *const raw::device,
299+
) -> Option<Gpio> {
61300
if !unique.once() {
62301
return None;
63302
}
64-
Some(Gpio { device })
303+
Some(Gpio { device, data })
65304
}
66305

67306
/// Verify that the device is ready for use. At a minimum, this means the device has been
@@ -79,6 +318,7 @@ impl Gpio {
79318
#[allow(dead_code)]
80319
pub struct GpioPin {
81320
pub(crate) pin: raw::gpio_dt_spec,
321+
pub(crate) data: &'static GpioStatic,
82322
}
83323

84324
// SAFETY: GpioPin's can be shared with other threads. Safety is maintained by the Token.
@@ -89,7 +329,9 @@ impl GpioPin {
89329
#[allow(dead_code)]
90330
pub(crate) unsafe fn new(
91331
unique: &Unique,
332+
_static: &NoStatic,
92333
device: *const raw::device,
334+
device_static: &'static GpioStatic,
93335
pin: u32,
94336
dt_flags: u32,
95337
) -> Option<GpioPin> {
@@ -102,6 +344,7 @@ impl GpioPin {
102344
pin: pin as raw::gpio_pin_t,
103345
dt_flags: dt_flags as raw::gpio_dt_flags_t,
104346
},
347+
data: device_static,
105348
})
106349
}
107350

@@ -115,6 +358,7 @@ impl GpioPin {
115358
pub fn get_gpio(&self) -> Gpio {
116359
Gpio {
117360
device: self.pin.port,
361+
data: self.data,
118362
}
119363
}
120364

0 commit comments

Comments
 (0)