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}