Skip to content

Commit e774695

Browse files
committed
zephyr: device: gpio: Fix races in waiting
Fix up the async wait for high and low to avoid various races. Instead of reading the gpio to determine if we are done, there is an atomic that holds the bits indicating the callback has been called. This does require the irq to always fire, but in the case where the gpio is already asserted, the irq will be called between enabling it, and checking, so it does avoid the task actually blocking. Unfortunately, the query API in Zephyr only returns a general IRQ status (some interrupt is ready) instead of per pin, so there is no way to read this other than to let the callback happen. Signed-off-by: David Brown <[email protected]>
1 parent b7774de commit e774695

File tree

1 file changed

+28
-13
lines changed

1 file changed

+28
-13
lines changed

zephyr/src/device/gpio.rs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,29 @@ mod async_io {
2222
2323
use core::{
2424
cell::UnsafeCell,
25-
ffi::c_int,
2625
future::Future,
2726
mem,
2827
sync::atomic::Ordering,
2928
task::{Poll, Waker},
3029
};
3130

3231
use embassy_sync::waitqueue::AtomicWaker;
33-
use portable_atomic::AtomicBool;
3432
use zephyr_sys::{
35-
device, gpio_add_callback, gpio_callback, gpio_init_callback, gpio_pin_get,
36-
gpio_pin_interrupt_configure, gpio_pin_interrupt_configure_dt, gpio_port_pins_t,
37-
GPIO_INT_LEVEL_HIGH, GPIO_INT_LEVEL_LOW, ZR_GPIO_INT_MODE_DISABLE_ONLY,
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,
3836
};
3937

40-
use crate::printkln;
38+
use crate::sync::atomic::{AtomicBool, AtomicU32};
4139

4240
use super::{GpioPin, GpioToken};
4341

4442
pub(crate) struct GpioStatic {
4543
/// The wakers for each of the gpios.
4644
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,
4748
/// Have we been initialized?
4849
installed: AtomicBool,
4950
/// The data for the callback itself.
@@ -56,6 +57,7 @@ mod async_io {
5657
pub(crate) const fn new() -> Self {
5758
Self {
5859
wakers: [const { AtomicWaker::new() }; 32],
60+
fired: AtomicU32::new(0),
5961
installed: AtomicBool::new(false),
6062
// SAFETY: `installed` will tell us this need to be installed.
6163
callback: unsafe { mem::zeroed() },
@@ -116,8 +118,6 @@ mod async_io {
116118
.cast::<Self>()
117119
};
118120

119-
// printkln!("CB called: pins: {pins:#x}");
120-
121121
// For each pin we are informed of.
122122
while pins > 0 {
123123
let pin = pins.trailing_zeros();
@@ -134,13 +134,23 @@ mod async_io {
134134
gpio_pin_interrupt_configure(port, pin as u8, ZR_GPIO_INT_MODE_DISABLE_ONLY);
135135

136136
// Remove the callback bit. Unclear if this is actually useful.
137-
// (*cb).pin_mask &= !(1 << pin);
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);
138142

139143
// After the interrupt is off, wake the handler.
140144
(*data).wakers[pin as usize].wake();
141145
}
142146
}
143147
}
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+
}
144154
}
145155

146156
impl GpioPin {
@@ -193,6 +203,12 @@ mod async_io {
193203
) -> core::task::Poll<Self::Output> {
194204
self.pin.data.fast_install(self.pin.pin.port);
195205

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+
196212
self.pin.data.register(self.pin.pin.pin, cx.waker());
197213

198214
let mode = match self.level {
@@ -204,10 +220,9 @@ mod async_io {
204220
unsafe {
205221
gpio_pin_interrupt_configure_dt(&self.pin.pin, mode);
206222

207-
if gpio_pin_get(self.pin.pin.port, self.pin.pin.pin) == self.level as c_int {
208-
// TODO: Need to match with level.
209-
// Zephyr doesn't have a way to determine if a given interrupt is pending, so the
210-
// best we can do is just read the pin. This doesn't work for edges though.
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) {
211226
return Poll::Ready(());
212227
}
213228
}

0 commit comments

Comments
 (0)