capsules_core/button.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 2022.
4
5//! Provides userspace control of buttons on a board.
6//!
7//! This allows for much more cross platform controlling of buttons without
8//! having to know which of the GPIO pins exposed across the syscall interface
9//! are buttons.
10//!
11//! Usage
12//! -----
13//!
14//! ```rust,ignore
15//! # use kernel::static_init;
16//!
17//! let button_pins = static_init!(
18//! [&'static sam4l::gpio::GPIOPin; 1],
19//! [&sam4l::gpio::PA[16]]);
20//! let button = static_init!(
21//! capsules_core::button::Button<'static>,
22//! capsules_core::button::Button::new(button_pins, board_kernel.create_grant(&grant_cap)));
23//! for btn in button_pins.iter() {
24//! btn.set_client(button);
25//! }
26//! ```
27//!
28//! Syscall Interface
29//! -----------------
30//!
31//! - Stability: 2 - Stable
32//!
33//! ### Command
34//!
35//! Enable or disable button interrupts and read the current button state.
36//!
37//! #### `command_num`
38//!
39//! - `0`: Driver existence check and get number of buttons on the board.
40//! - `1`: Enable interrupts for a given button. This will enable both press
41//! and depress events.
42//! - `2`: Disable interrupts for a button. No affect or reliance on
43//! registered callback.
44//! - `3`: Read the current state of the button.
45//!
46//! ### Subscribe
47//!
48//! Setup a callback for button presses.
49//!
50//! #### `subscribe_num`
51//!
52//! - `0`: Set callback for pin interrupts. Note setting this callback has
53//! no reliance on individual pins being configured as interrupts. The
54//! interrupt will be called with two parameters: the index of the button
55//! that triggered the interrupt and the pressed (1) or not pressed (0) state
56//! of the button.
57
58use core::cell::Cell;
59
60use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
61use kernel::hil::gpio;
62use kernel::hil::gpio::{Configure, Input, InterruptWithValue};
63use kernel::syscall::{CommandReturn, SyscallDriver};
64use kernel::{ErrorCode, ProcessId};
65
66/// Syscall driver number.
67use crate::driver;
68pub const DRIVER_NUM: usize = driver::NUM::Button as usize;
69
70/// Keeps track which buttons each app has a registered interrupt for.
71///
72/// `SubscribeMap` is a bit array where bits are set to one if
73/// that app has an interrupt registered for that button.
74pub type SubscribeMap = u32;
75
76/// Keeps track for each app of which buttons it has a registered
77/// interrupt for.
78///
79/// `SubscribeMap` is a bit array where bits are set to one if that
80/// app has an interrupt registered for that button.
81#[derive(Default)]
82pub struct App {
83 subscribe_map: u32,
84}
85
86/// Manages the list of GPIO pins that are connected to buttons and which apps
87/// are listening for interrupts from which buttons.
88pub struct Button<'a, P: gpio::InterruptPin<'a>> {
89 pins: &'a [(
90 &'a gpio::InterruptValueWrapper<'a, P>,
91 gpio::ActivationMode,
92 gpio::FloatingState,
93 )],
94 apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
95}
96
97impl<'a, P: gpio::InterruptPin<'a>> Button<'a, P> {
98 pub fn new(
99 pins: &'a [(
100 &'a gpio::InterruptValueWrapper<'a, P>,
101 gpio::ActivationMode,
102 gpio::FloatingState,
103 )],
104 grant: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
105 ) -> Self {
106 for (i, &(pin, _, floating_state)) in pins.iter().enumerate() {
107 pin.make_input();
108 pin.set_value(i as u32);
109 pin.set_floating_state(floating_state);
110 }
111
112 Self { pins, apps: grant }
113 }
114
115 fn get_button_state(&self, pin_num: u32) -> gpio::ActivationState {
116 let pin = &self.pins[pin_num as usize];
117 pin.0.read_activation(pin.1)
118 }
119}
120
121/// ### `subscribe_num`
122///
123/// - `0`: Set callback for pin interrupts. Note setting this callback has
124/// no reliance on individual pins being configured as interrupts. The
125/// interrupt will be called with two parameters: the index of the button
126/// that triggered the interrupt and the pressed/not pressed state of the
127/// button.
128const UPCALL_NUM: usize = 0;
129
130impl<'a, P: gpio::InterruptPin<'a>> SyscallDriver for Button<'a, P> {
131 /// Configure interrupts and read state for buttons.
132 ///
133 /// `data` is the index of the button in the button array as passed to
134 /// `Button::new()`.
135 ///
136 /// All commands greater than zero return `INVAL` if an invalid button
137 /// number is passed in.
138 ///
139 /// ### `command_num`
140 ///
141 /// - `0`: Driver existence check and get number of buttons on the board.
142 /// - `1`: Enable interrupts for a given button. This will enable both press
143 /// and depress events.
144 /// - `2`: Disable interrupts for a button. No affect or reliance on
145 /// registered callback.
146 /// - `3`: Read the current state of the button.
147 fn command(
148 &self,
149 command_num: usize,
150 data: usize,
151 _: usize,
152 processid: ProcessId,
153 ) -> CommandReturn {
154 let pins = self.pins;
155 match command_num {
156 // return button count
157 // TODO(Tock 3.0): TRD104 specifies that Command 0 should return Success, not SuccessU32,
158 // but this driver is unchanged since it has been stabilized. It will be brought into
159 // compliance as part of the next major release of Tock. See #3375.
160 0 => CommandReturn::success_u32(pins.len() as u32),
161
162 // enable interrupts for a button
163 1 => {
164 if data < pins.len() {
165 self.apps
166 .enter(processid, |cntr, _| {
167 cntr.subscribe_map |= 1 << data;
168 let _ = pins[data]
169 .0
170 .enable_interrupts(gpio::InterruptEdge::EitherEdge);
171 CommandReturn::success()
172 })
173 .unwrap_or_else(|err| CommandReturn::failure(err.into()))
174 } else {
175 CommandReturn::failure(ErrorCode::INVAL) /* impossible button */
176 }
177 }
178
179 // disable interrupts for a button
180 2 => {
181 if data >= pins.len() {
182 CommandReturn::failure(ErrorCode::INVAL) /* impossible button */
183 } else {
184 let res = self
185 .apps
186 .enter(processid, |cntr, _| {
187 cntr.subscribe_map &= !(1 << data);
188 CommandReturn::success()
189 })
190 .unwrap_or_else(|err| CommandReturn::failure(err.into()));
191
192 // are any processes waiting for this button?
193 let interrupt_count = Cell::new(0);
194 self.apps.each(|_, cntr, _| {
195 if cntr.subscribe_map & (1 << data) != 0 {
196 interrupt_count.set(interrupt_count.get() + 1);
197 }
198 });
199
200 // if not, disable the interrupt
201 if interrupt_count.get() == 0 {
202 self.pins[data].0.disable_interrupts();
203 }
204
205 res
206 }
207 }
208
209 // read input
210 3 => {
211 if data >= pins.len() {
212 CommandReturn::failure(ErrorCode::INVAL) /* impossible button */
213 } else {
214 let button_state = self.get_button_state(data as u32);
215 CommandReturn::success_u32(button_state as u32)
216 }
217 }
218
219 // default
220 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
221 }
222 }
223
224 fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
225 self.apps.enter(processid, |_, _| {})
226 }
227}
228
229impl<'a, P: gpio::InterruptPin<'a>> gpio::ClientWithValue for Button<'a, P> {
230 fn fired(&self, pin_num: u32) {
231 // Read the value of the pin and get the button state.
232 let button_state = self.get_button_state(pin_num);
233 let interrupt_count = Cell::new(0);
234
235 // schedule callback with the pin number and value
236 self.apps.each(|_, cntr, upcalls| {
237 if cntr.subscribe_map & (1 << pin_num) != 0 {
238 interrupt_count.set(interrupt_count.get() + 1);
239 upcalls
240 .schedule_upcall(UPCALL_NUM, (pin_num as usize, button_state as usize, 0))
241 .ok();
242 }
243 });
244
245 // It's possible we got an interrupt for a process that has since died
246 // (and didn't unregister the interrupt). Lazily disable interrupts for
247 // this button if so.
248 if interrupt_count.get() == 0 {
249 self.pins[pin_num as usize].0.disable_interrupts();
250 }
251 }
252}