Skip to main content

ove/async_runtime/
gpio.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Async GPIO input wrapper using the existing `ove_gpio_irq_register`
8//! callback path — no new C-side API needed.
9//!
10//! The pattern differs slightly from stream / queue / sem / eventgroup
11//! because GPIO interrupts are edge-triggered events rather than
12//! "data became available". We pair an `AtomicWaker` with an
13//! `AtomicBool` latch: the IRQ trampoline sets the bool and wakes the
14//! waker; the consumer clears the bool on drain. This guards against
15//! the edge firing between user-side "register waker" and "go to
16//! sleep".
17
18use core::future::poll_fn;
19use core::sync::atomic::{AtomicBool, Ordering};
20use core::task::Poll;
21
22use embassy_sync::waitqueue::AtomicWaker;
23
24use crate::error::Result;
25use crate::gpio::{self, GpioIrqMode, GpioPin};
26
27/// Async wrapper around a GPIO input pin.
28///
29/// Methods take `&'static self` because the C-side `ove_gpio_irq_register`
30/// callback retains a pointer to the internal state.
31pub struct AsyncInput {
32    pin: GpioPin,
33    pending: AtomicBool,
34    waker: AtomicWaker,
35}
36
37// SAFETY: `AsyncInput` holds an `AtomicBool` + `AtomicWaker` (both already
38// `Sync`).  No interior thread-bound state; the waker dispatch path uses
39// `critical_section` for synchronisation.
40unsafe impl Send for AsyncInput {}
41unsafe impl Sync for AsyncInput {}
42
43impl AsyncInput {
44    /// Wrap a GPIO pin for async edge-event handling.
45    pub const fn new(pin: GpioPin) -> Self {
46        Self {
47            pin,
48            pending: AtomicBool::new(false),
49            waker: AtomicWaker::new(),
50        }
51    }
52
53    /// Register the IRQ trampoline and enable interrupts for `mode`
54    /// (rising / falling / both). Must be called exactly once.
55    pub fn arm(&'static self, mode: GpioIrqMode) -> Result<()> {
56        // SAFETY: 'static self; trampoline reinterprets user_data as
57        // &'static AsyncInput.
58        unsafe {
59            gpio::irq_register(
60                self.pin,
61                mode,
62                Some(gpio_irq_trampoline),
63                self as *const Self as *mut core::ffi::c_void,
64            )?;
65        }
66        gpio::irq_enable(self.pin)
67    }
68
69    /// Await the next edge event. Returns immediately if an edge has
70    /// fired since the last call to this function.
71    pub async fn wait_for_event(&'static self) {
72        poll_fn(|cx| {
73            // Fast path: drain any latched edge.
74            if self.pending.swap(false, Ordering::AcqRel) {
75                return Poll::Ready(());
76            }
77            self.waker.register(cx.waker());
78            // Re-check after register to close the race where the IRQ
79            // fires between the first swap and the waker registration.
80            if self.pending.swap(false, Ordering::AcqRel) {
81                return Poll::Ready(());
82            }
83            Poll::Pending
84        })
85        .await
86    }
87
88    /// Read the pin's current logic level.
89    pub fn is_high(&self) -> Result<bool> {
90        gpio::get(self.pin).map(|v| v != 0)
91    }
92
93    #[inline]
94    pub fn pin(&self) -> GpioPin {
95        self.pin
96    }
97}
98
99unsafe extern "C" fn gpio_irq_trampoline(
100    _port: core::ffi::c_uint,
101    _pin: core::ffi::c_uint,
102    user_data: *mut core::ffi::c_void,
103) {
104    // SAFETY: user_data was set by AsyncInput::arm to &'static AsyncInput.
105    let this = unsafe { &*(user_data as *const AsyncInput) };
106    this.pending.store(true, Ordering::Release);
107    this.waker.wake();
108}