capsules_extra/
button_keyboard.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2025.
4
5//! Mimic buttons using keyboard presses.
6//!
7//! This implements the same `Driver` interface as the normal button capsule,
8//! but instead of using GPIO pins as the underlying source for the buttons
9//! it uses keyboard key presses.
10
11use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
12use kernel::syscall::{CommandReturn, SyscallDriver};
13use kernel::{ErrorCode, ProcessId};
14
15/// Syscall driver number.
16use capsules_core::driver;
17pub const DRIVER_NUM: usize = driver::NUM::Button as usize;
18
19/// Keeps track for each app of which buttons it has a registered interrupt
20/// for.
21///
22/// `subscribe_map` is a bit array where bits are set to one if that app has an
23/// interrupt registered for that button.
24#[derive(Default)]
25pub struct App {
26    subscribe_map: u32,
27}
28
29/// Manages the list of GPIO pins that are connected to buttons and which apps
30/// are listening for interrupts from which buttons.
31pub struct ButtonKeyboard<'a> {
32    /// The key codes we are looking for to map to buttons. These are in order,
33    /// e.g., the second key code in the array maps to button with index 1.
34    key_codes: &'a [u16],
35    apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
36}
37
38impl<'a> ButtonKeyboard<'a> {
39    pub fn new(
40        key_codes: &'a [u16],
41        grant: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
42    ) -> Self {
43        if key_codes.len() >= 32 {
44            panic!("ButtonKeyboard capsule only supports up to 32 buttons.");
45        }
46        Self {
47            key_codes,
48            apps: grant,
49        }
50    }
51}
52
53/// ### `subscribe_num`
54///
55/// - `0`: Set callback for pin interrupts.
56const UPCALL_NUM: usize = 0;
57
58impl SyscallDriver for ButtonKeyboard<'_> {
59    fn command(
60        &self,
61        command_num: usize,
62        data: usize,
63        _: usize,
64        processid: ProcessId,
65    ) -> CommandReturn {
66        match command_num {
67            // return button count
68            // TODO(Tock 3.0): TRD104 specifies that Command 0 should return Success, not SuccessU32,
69            // but this driver is unchanged since it has been stabilized. It will be brought into
70            // compliance as part of the next major release of Tock. See #3375.
71            0 => CommandReturn::success_u32(self.key_codes.len() as u32),
72
73            // enable interrupts for a button
74            1 => {
75                if data >= self.key_codes.len() {
76                    CommandReturn::failure(ErrorCode::INVAL)
77                } else {
78                    self.apps
79                        .enter(processid, |app, _| {
80                            app.subscribe_map |= 1 << data;
81                            CommandReturn::success()
82                        })
83                        .unwrap_or_else(|err| CommandReturn::failure(err.into()))
84                }
85            }
86
87            // disable interrupts for a button
88            2 => {
89                if data >= self.key_codes.len() {
90                    CommandReturn::failure(ErrorCode::INVAL)
91                } else {
92                    self.apps
93                        .enter(processid, |app, _| {
94                            app.subscribe_map &= !(1 << data);
95                            CommandReturn::success()
96                        })
97                        .unwrap_or_else(|err| CommandReturn::failure(err.into()))
98                }
99            }
100
101            // read input
102            3 => {
103                if data >= self.key_codes.len() {
104                    CommandReturn::failure(ErrorCode::INVAL)
105                } else {
106                    // Always return not pressed
107                    CommandReturn::success_u32(0)
108                }
109            }
110
111            // default
112            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
113        }
114    }
115
116    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
117        self.apps.enter(processid, |_, _| {})
118    }
119}
120
121impl kernel::hil::keyboard::KeyboardClient for ButtonKeyboard<'_> {
122    fn keys_pressed(&self, keys: &[(u16, bool)], result: Result<(), ErrorCode>) {
123        if result.is_ok() {
124            // Iterate all key presses we received.
125            for (key, is_pressed) in keys.iter() {
126                // Iterate through all of the keys we are looking for.
127                for (active_key_index, active_key) in self.key_codes.iter().enumerate() {
128                    // If there is a match then we may want to handle this key.
129                    if key == active_key {
130                        kernel::debug!(
131                            "[ButtonKeyboard] Notify button {} (key {})",
132                            active_key_index,
133                            key
134                        );
135
136                        // Schedule callback for apps waiting on that key.
137                        self.apps.each(|_, app, upcalls| {
138                            if app.subscribe_map & (1 << active_key_index) != 0 {
139                                let button_state = usize::from(*is_pressed);
140                                let _ = upcalls.schedule_upcall(
141                                    UPCALL_NUM,
142                                    (active_key_index, button_state, 0),
143                                );
144                            }
145                        });
146                    }
147                }
148            }
149        }
150    }
151}