Skip to main content

osiris/drivers/
key.rs

1//! gpio-keys kernel driver. Each DT child of a `gpio-keys` node maps to
2//! one [`Key`]; consumers `open` by alias, label, or code and block on
3//! [`Key::wait`] until an edge arrives.
4
5use core::sync::atomic::{AtomicBool, Ordering};
6
7use crate::error::Result;
8use crate::hal;
9use crate::sync::atomic::AtomicU64;
10use crate::sync::once::OnceCell;
11use crate::sync::waiter::ParkedWaiter;
12
13use hal::Machinelike;
14use hal::device_tree::KeyRegistryEntry;
15use hal::gpio::{Edges, Level, Pin, Pull};
16
17pub struct KeyEvent {
18    pub code: u32,
19    pub pressed: bool,
20}
21
22struct KeyState {
23    entry: &'static KeyRegistryEntry,
24    waiter: ParkedWaiter,
25    /// `0` means "no edge yet" — see `on_edge`.
26    last_event_mono: AtomicU64,
27    /// Pre-converted from `entry.debounce_ms` so the edge callback avoids a divide.
28    debounce_ticks: u64,
29    /// State at edge time, so `wait()` survives presses that settle back before the consumer wakes.
30    latched_pressed: AtomicBool,
31    /// `Key::open_*` rejects handles whose init failed partway.
32    initialized: AtomicBool,
33}
34
35impl KeyState {
36    fn pin(&self) -> Pin {
37        Pin {
38            port: self.entry.port,
39            line: self.entry.line,
40        }
41    }
42}
43
44// One slot per DT entry; the `&'static KeyState` handed to ISRs lives
45// for the program.
46static SLOTS: [OnceCell<KeyState>; hal::device_tree::KEY_REGISTRY.len()] =
47    [const { OnceCell::new() }; hal::device_tree::KEY_REGISTRY.len()];
48
49const _: () = {
50    let entries = hal::device_tree::KEY_REGISTRY;
51    let mut i = 0;
52    while i < entries.len() {
53        let slot_i = match hal::gpio::irq_slot_for_line(entries[i].line) {
54            Some(v) => v,
55            None => panic!("gpio-key DT entry references an unmapped GPIO line"),
56        };
57        assert!(
58            slot_i < 64,
59            "gpio-key IRQ slot exceeds u64 dedup-mask width"
60        );
61
62        let mut j = 0;
63        while j < i {
64            if let Some(slot_j) = hal::gpio::irq_slot_for_line(entries[j].line) {
65                if slot_i == slot_j {
66                    assert!(
67                        entries[i].irq_priority == entries[j].irq_priority,
68                        "gpio-keys sharing an IRQ slot must declare the same `osiris,irq-priority`"
69                    );
70                }
71            }
72            j += 1;
73        }
74        i += 1;
75    }
76};
77
78pub struct Key {
79    state: &'static KeyState,
80}
81
82impl Key {
83    pub fn open_by_alias(name: &str) -> Result<Self> {
84        let entry = hal::device_tree::key_by_alias(name)
85            .ok_or_else(|| kerr!(ENODEV, "key alias not found: {name}"))?;
86        Self::open_for_node(entry.node)
87    }
88
89    pub fn open_by_label(label: &str) -> Result<Self> {
90        let entry = hal::device_tree::key_by_label(label)
91            .ok_or_else(|| kerr!(ENODEV, "key label not found: {label}"))?;
92        Self::open_for_node(entry.node)
93    }
94
95    pub fn open_by_code(code: u32) -> Result<Self> {
96        let entry = hal::device_tree::key_by_code(code)
97            .ok_or_else(|| kerr!(ENODEV, "key code not found: {code}"))?;
98        Self::open_for_node(entry.node)
99    }
100
101    fn open_for_node(node: usize) -> Result<Self> {
102        for cell in SLOTS.iter() {
103            if let Some(state) = cell.get() {
104                if state.entry.node == node {
105                    if !state.initialized.load(Ordering::Acquire) {
106                        return Err(kerr!(EIO, "key node {node} init failed"));
107                    }
108                    return Ok(Self { state });
109                }
110            }
111        }
112        Err(kerr!(ENODEV, "key node {node} not registered"))
113    }
114
115    pub fn code(&self) -> u32 {
116        self.state.entry.code
117    }
118
119    pub fn label(&self) -> &'static str {
120        self.state.entry.label
121    }
122
123    /// Snapshot the line without blocking.
124    pub fn poll(&self) -> Result<KeyEvent> {
125        let level = hal::gpio::read(self.state.pin())?;
126        Ok(KeyEvent {
127            code: self.state.entry.code,
128            pressed: pressed_from_level(self.state.entry, level),
129        })
130    }
131
132    /// Block until the next edge. `EBUSY` if another thread is already
133    /// waiting; edges arriving before wake-up coalesce into one event.
134    pub fn wait(&self) -> Result<KeyEvent> {
135        self.state.waiter.park_current()?;
136        Ok(KeyEvent {
137            code: self.state.entry.code,
138            pressed: self.state.latched_pressed.load(Ordering::Acquire),
139        })
140    }
141}
142
143fn pressed_from_level(entry: &KeyRegistryEntry, level: Level) -> bool {
144    let active_low = entry.active_low != 0;
145    (level == Level::High) ^ active_low
146}
147
148extern "C" fn on_edge(_line: u8, ctx: *mut ()) {
149    // Guard against a stray fire during teardown.
150    if ctx.is_null() {
151        return;
152    }
153    // SAFETY: ctx points into SLOTS (a static OnceCell array).
154    let state = unsafe { &*(ctx as *const KeyState) };
155
156    if state.debounce_ticks > 0 {
157        let now = hal::Machine::monotonic_now();
158        let prev = state.last_event_mono.load(Ordering::Acquire);
159        // Skip debounce on the first edge — `monotonic_now()` may still
160        // be smaller than `debounce_ticks`.
161        if prev != 0 && now.saturating_sub(prev) < state.debounce_ticks {
162            return;
163        }
164        let stored = if now == 0 { 1 } else { now };
165        state.last_event_mono.store(stored, Ordering::Release);
166    }
167
168    if let Ok(level) = hal::gpio::read(state.pin()) {
169        state
170            .latched_pressed
171            .store(pressed_from_level(state.entry, level), Ordering::Release);
172    }
173
174    state.waiter.wake();
175}
176
177fn debounce_to_ticks(debounce_ms: u32) -> u64 {
178    if debounce_ms == 0 {
179        return 0;
180    }
181    let freq = hal::Machine::monotonic_freq();
182    (debounce_ms as u64).saturating_mul(freq) / 1000
183}
184
185pub fn init() {
186    let entries = hal::device_tree::KEY_REGISTRY;
187    kprintln!("Found {} gpio-key entries", entries.len());
188
189    // Install the shared dispatcher once per slot.
190    let mut seen_slots: u64 = 0;
191
192    for (i, entry) in entries.iter().enumerate() {
193        if let Err(e) = init_entry(i, entry, &mut seen_slots) {
194            kprintln!("    Key {}: init failed: {:?}", entry.label, e);
195        }
196    }
197}
198
199fn init_entry(
200    slot_idx: usize,
201    entry: &'static KeyRegistryEntry,
202    seen_slots: &mut u64,
203) -> Result<()> {
204    let slot =
205        hal::gpio::irq_slot_for_line(entry.line).ok_or_else(|| kerr!(EINVAL, "invalid line"))?;
206
207    let state = KeyState {
208        entry,
209        waiter: ParkedWaiter::new(),
210        last_event_mono: AtomicU64::new(0),
211        debounce_ticks: debounce_to_ticks(entry.debounce_ms),
212        latched_pressed: AtomicBool::new(false),
213        initialized: AtomicBool::new(false),
214    };
215    let state_ref: &'static KeyState = SLOTS[slot_idx].set_or_get(state);
216    let pin = state_ref.pin();
217
218    let bit = 1u64 << slot;
219    if *seen_slots & bit == 0 {
220        unsafe { crate::irq::register_irq(slot, hal::gpio::dispatch, None) }?;
221        *seen_slots |= bit;
222    }
223
224    let pull = if entry.active_low != 0 {
225        Pull::Up
226    } else {
227        Pull::Down
228    };
229    hal::gpio::configure_input(pin, pull)?;
230
231    if let Ok(level) = hal::gpio::read(pin) {
232        state_ref
233            .latched_pressed
234            .store(pressed_from_level(entry, level), Ordering::Release);
235    }
236
237    let ctx = state_ref as *const KeyState as *mut ();
238    hal::gpio::register_edge_handler(pin, Edges::BOTH, on_edge, ctx, entry.irq_priority)?;
239
240    state_ref.initialized.store(true, Ordering::Release);
241    kprintln!(
242        "    Initialized key {} on port 0x{:x} line {}",
243        entry.label,
244        entry.port,
245        entry.line
246    );
247    Ok(())
248}