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}