capsules_extra/gpio_async.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 applications with a driver interface to asynchronous GPIO
6//! pins.
7//!
8//! Async GPIO pins are pins that exist on something like a GPIO extender or a
9//! radio that has controllable GPIOs.
10//!
11//! Usage
12//! -----
13//!
14//! ```rust,ignore
15//! # use kernel::static_init;
16//!
17//! // Generate a list of ports to group into one userspace driver.
18//! let async_gpio_ports = static_init!(
19//! [&'static capsules::mcp230xx::MCP230xx; 1],
20//! [mcp23008]);
21//!
22//! let gpio_async = static_init!(
23//! capsules::gpio_async::GPIOAsync<'static, capsules::mcp230xx::MCP230xx<'static>>,
24//! capsules::gpio_async::GPIOAsync::new(async_gpio_ports));
25//!
26//! // Setup the clients correctly.
27//! for port in async_gpio_ports.iter() {
28//! port.set_client(gpio_async);
29//! }
30//! ```
31
32use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
33use kernel::hil;
34use kernel::syscall::{CommandReturn, SyscallDriver};
35use kernel::utilities::cells::OptionalCell;
36use kernel::{ErrorCode, ProcessId};
37
38/// Syscall driver number.
39use capsules_core::driver;
40pub const DRIVER_NUM: usize = driver::NUM::GpioAsync as usize;
41
42pub struct GPIOAsync<'a, Port: hil::gpio_async::Port> {
43 ports: &'a [&'a Port],
44 grants: Grant<App, UpcallCount<2>, AllowRoCount<0>, AllowRwCount<0>>,
45 /// **Transient** ownership of the partially virtualized peripheral.
46 ///
47 /// Current GPIO HIL semantics notify *all* processes of interrupts
48 /// to any pin with interrupts configured (and it's left to higher
49 /// layers to filter activity based on which pin generated their
50 /// interrupt). For Async GPIO, this is awkward to virtualize, as
51 /// there is no owning process of a pin for activity interrupts, but
52 /// there is for configuration result interrupts. Also, the underlying
53 /// hardware likely can't handle multiple concurrent configurations
54 /// from multiple apps. Hence, this variable, which tracks a configuration
55 /// while it is in flight and notifies the correct process that their
56 /// configuration has succeeded. In the rare case where two apps attempt
57 /// concurrent configuration requests, the later app will receive `EBUSY`.
58 /// A retry loop should be sufficient for most apps to handle this rare
59 /// case.
60 configuring_process: OptionalCell<ProcessId>,
61}
62
63#[derive(Default)]
64pub struct App {}
65
66impl<'a, Port: hil::gpio_async::Port> GPIOAsync<'a, Port> {
67 pub fn new(
68 ports: &'a [&'a Port],
69 grants: Grant<App, UpcallCount<2>, AllowRoCount<0>, AllowRwCount<0>>,
70 ) -> GPIOAsync<'a, Port> {
71 GPIOAsync {
72 ports,
73 grants,
74 configuring_process: OptionalCell::empty(),
75 }
76 }
77
78 fn configure_input_pin(&self, port: usize, pin: usize, config: usize) -> Result<(), ErrorCode> {
79 let ports = self.ports;
80 let mode = match config {
81 0 => hil::gpio::FloatingState::PullNone,
82 1 => hil::gpio::FloatingState::PullUp,
83 2 => hil::gpio::FloatingState::PullDown,
84 _ => return Err(ErrorCode::INVAL),
85 };
86 ports[port].make_input(pin, mode)
87 }
88
89 fn configure_interrupt(&self, port: usize, pin: usize, config: usize) -> Result<(), ErrorCode> {
90 let ports = self.ports;
91 let mode = match config {
92 0 => hil::gpio::InterruptEdge::EitherEdge,
93 1 => hil::gpio::InterruptEdge::RisingEdge,
94 2 => hil::gpio::InterruptEdge::FallingEdge,
95 _ => return Err(ErrorCode::INVAL),
96 };
97 ports[port].enable_interrupt(pin, mode)
98 }
99}
100
101impl<Port: hil::gpio_async::Port> hil::gpio_async::Client for GPIOAsync<'_, Port> {
102 fn fired(&self, pin: usize, identifier: usize) {
103 // schedule callback with the pin number and value for all apps
104 self.grants.each(|_, _app, upcalls| {
105 upcalls.schedule_upcall(1, (identifier, pin, 0)).ok();
106 });
107 }
108
109 fn done(&self, value: usize) {
110 // alert currently configuring app
111 self.configuring_process.map(|pid| {
112 let _ = self.grants.enter(pid, |_app, upcalls| {
113 upcalls.schedule_upcall(0, (0, value, 0)).ok();
114 });
115 });
116 // then clear currently configuring app
117 self.configuring_process.clear();
118 }
119}
120
121impl<Port: hil::gpio_async::Port> SyscallDriver for GPIOAsync<'_, Port> {
122 // Setup callbacks for gpio_async events.
123 //
124 // ### `subscribe_num`
125 //
126 // - `0`: Setup a callback for when **split-phase operations complete**.
127 // This callback gets called from the gpio_async `done()` event and
128 // signals the end of operations like asserting a GPIO pin or configuring
129 // an interrupt pin. The callback will be called with two valid
130 // arguments. The first is the callback type, which is currently 0 for
131 // all `done()` events. The second is a value, which is only useful for
132 // operations which should return something, like a GPIO read.
133 // - `1`: Setup a callback for when a **GPIO interrupt** occurs. This
134 // callback will be called with two arguments, the first being the port
135 // number of the interrupting pin, and the second being the pin number.
136
137 /// Configure and read GPIO pins.
138 ///
139 /// `pin` is the index of the pin.
140 ///
141 /// `data` is a 32 bit value packed with the lowest 16 bits as the port
142 /// number, and the remaining upper bits as a command-specific value.
143 ///
144 /// ### `command_num`
145 ///
146 /// - `0`: Driver existence check.
147 /// - `1`: Set a pin as an output.
148 /// - `2`: Set a pin high by setting it to 1.
149 /// - `3`: Clear a pin by setting it to 0.
150 /// - `4`: Toggle a pin.
151 /// - `5`: Set a pin as an input and configure its pull-up or pull-down
152 /// state. The command-specific field should be set to 0 for a pull-up, 1
153 /// for a pull-down, or 2 for neither.
154 /// - `6`: Read a GPIO pin state, and have its value returned in the done()
155 /// callback.
156 /// - `7`: Enable an interrupt on a GPIO pin. The command-specific data
157 /// should be 0 for an either-edge interrupt, 1 for a rising edge
158 /// interrupt, and 2 for a falling edge interrupt.
159 /// - `8`: Disable an interrupt on a pin.
160 /// - `9`: Disable a GPIO pin.
161 /// - `10`: Get number of GPIO ports supported.
162 fn command(
163 &self,
164 command_number: usize,
165 pin: usize,
166 data: usize,
167 process_id: ProcessId,
168 ) -> CommandReturn {
169 let port = data & 0xFFFF;
170 let other = (data >> 16) & 0xFFFF;
171 let ports = self.ports;
172
173 // Driver existence check.
174 if command_number == 0 {
175 CommandReturn::success();
176 }
177
178 // Special case command 10; everything else results in a process-owned,
179 // split-phase call.
180 if command_number == 10 {
181 // How many ports
182 return CommandReturn::success_u32(ports.len() as u32);
183 }
184
185 // On any command other than 0, we check for ports length.
186 if port >= ports.len() {
187 return CommandReturn::failure(ErrorCode::INVAL);
188 }
189
190 // On any command other than 0, we check if another command is in flight
191 if self.configuring_process.is_some() {
192 return CommandReturn::failure(ErrorCode::BUSY);
193 }
194
195 let res = match command_number {
196 // enable output
197 1 => ports[port].make_output(pin),
198
199 // set pin
200 2 => ports[port].set(pin),
201
202 // clear pin
203 3 => ports[port].clear(pin),
204
205 // toggle pin
206 4 => ports[port].toggle(pin),
207
208 // enable and configure input
209 5 => self.configure_input_pin(port, pin, other & 0xFF),
210
211 // read input
212 6 => ports[port].read(pin),
213
214 // enable interrupt on pin
215 7 => self.configure_interrupt(port, pin, other & 0xFF),
216
217 // disable interrupt on pin
218 8 => ports[port].disable_interrupt(pin),
219
220 // disable pin
221 9 => ports[port].disable(pin),
222
223 // default
224 _ => return CommandReturn::failure(ErrorCode::NOSUPPORT),
225 };
226
227 // If any async command kicked off, note that the peripheral is busy
228 // and which process to return the command result to
229 if res.is_ok() {
230 self.configuring_process.set(process_id);
231 }
232
233 res.into()
234 }
235
236 fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
237 self.grants.enter(processid, |_, _| {})
238 }
239}