capsules_extra/
distance.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 2024.
4
5//! Provides userspace with access to distance sensor.
6//!
7//! Userspace Interface
8//! -------------------
9//!
10//! ### `subscribe` System Call
11//!
12//! The `subscribe` system call supports the single `subscribe_number` zero,
13//! which is used to provide a callback that will return back the result of
14//! a distance sensor reading.
15//! The `subscribe` call return codes indicate the following:
16//!
17//! * `Ok(())`: the callback has been successfully been configured.
18//! * `ENOSUPPORT`: Invalid `subscribe_number`.
19//! * `NOMEM`: No sufficient memory available.
20//! * `INVAL`: Invalid address of the buffer or other error.
21//!
22//! ### `command` System Call
23//!
24//! The `command` system call supports one argument `cmd` which is used to
25//! specify the specific operation. Currently, the following commands are supported:
26//!
27//! * `0`: check whether the driver exists.
28//! * `1`: read the distance.
29//! * `2`: get the minimum distance that the sensor can measure based on the datasheet, in millimeters.
30//! * `3`: get the maximum distance that the sensor can measure based on the datasheet, in millimeters.
31//!
32//! The possible returns from the `command` system call indicate the following:
33//!
34//! * `Ok(())`: The operation has been successful.
35//! * `NOACK`: No acknowledgment was received from the sensor during distance measurement.
36//! * `INVAL`: Invalid measurement, such as when the object is out of range or no valid echo is received.
37//! * `ENOSUPPORT`: Invalid `cmd`.
38//! * `NOMEM`: Insufficient memory available.
39//! * `INVAL`: Invalid address of the buffer or other error.
40//!
41//! The upcall has the following parameters:
42//!
43//! * `0`: Indicates a successful distance measurement, with the second parameter containing the distance, in millimeters.
44//! * Non-zero: Indicates an error, with the first parameter containing the error code, and the second parameter being `0`.
45//!
46//! Components for the distance sensor.
47//!
48//! Usage
49//! -----
50//!
51//! You need a device that provides the `hil::sensors::Distance` trait.
52//! Here is an example of how to set up a distance sensor with the HC-SR04.
53//!
54//! ```rust,ignore
55//! use components::hcsr04::HcSr04Component;
56
57//! let trig_pin = peripherals.pins.get_pin(RPGpio::GPIO4);
58//! let echo_pin = peripherals.pins.get_pin(RPGpio::GPIO5);
59//!
60//! let distance_sensor = components::hcsr04::HcSr04Component::new(
61//!     mux_alarm,
62//!     trig_pin,
63//!     echo_pin
64//! ).finalize(components::hcsr04_component_static!());
65//!
66//! distance_sensor.set_client(distance_sensor_client);
67//! ```
68
69use core::cell::Cell;
70
71use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
72use kernel::hil;
73use kernel::syscall::{CommandReturn, SyscallDriver};
74use kernel::{ErrorCode, ProcessId};
75
76/// Syscall driver number.
77use capsules_core::driver;
78pub const DRIVER_NUM: usize = driver::NUM::Distance as usize;
79
80#[derive(Default)]
81pub struct App {
82    subscribed: bool,
83}
84
85pub struct DistanceSensor<'a, T: hil::sensors::Distance<'a>> {
86    driver: &'a T,
87    apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
88    busy: Cell<bool>,
89}
90
91impl<'a, T: hil::sensors::Distance<'a>> DistanceSensor<'a, T> {
92    pub fn new(
93        driver: &'a T,
94        grant: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
95    ) -> DistanceSensor<'a, T> {
96        DistanceSensor {
97            driver,
98            apps: grant,
99            busy: Cell::new(false),
100        }
101    }
102
103    fn enqueue_command(&self, processid: ProcessId) -> CommandReturn {
104        self.apps
105            .enter(processid, |app, _| {
106                // Unconditionally mark this client as subscribed so it will get
107                // a callback when we get the distance reading.
108                app.subscribed = true;
109
110                // If we do not already have an ongoing read, start one now.
111                if !self.busy.get() {
112                    self.busy.set(true);
113                    match self.driver.read_distance() {
114                        Ok(()) => CommandReturn::success(),
115                        Err(e) => {
116                            self.busy.set(false);
117                            app.subscribed = false;
118                            CommandReturn::failure(e)
119                        }
120                    }
121                } else {
122                    // Just return success and we will get the upcall when the
123                    // distance read is ready.
124                    CommandReturn::success()
125                }
126            })
127            .unwrap_or_else(|err| CommandReturn::failure(err.into()))
128    }
129}
130
131impl<'a, T: hil::sensors::Distance<'a>> hil::sensors::DistanceClient for DistanceSensor<'a, T> {
132    fn callback(&self, distance_val: Result<u32, ErrorCode>) {
133        // We completed the operation so we clear the busy flag in case we get
134        // another measurement request.
135        self.busy.set(false);
136
137        // Return the distance reading or an error to any waiting client.
138        for cntr in self.apps.iter() {
139            cntr.enter(|app, upcalls| {
140                if app.subscribed {
141                    app.subscribed = false; // Clear the subscribed flag.
142                    match distance_val {
143                        Ok(distance) => {
144                            upcalls.schedule_upcall(0, (0, distance as usize, 0)).ok();
145                        }
146                        Err(e) => {
147                            upcalls.schedule_upcall(0, (e as usize, 0, 0)).ok();
148                        }
149                    }
150                }
151            });
152        }
153    }
154}
155
156impl<'a, T: hil::sensors::Distance<'a>> SyscallDriver for DistanceSensor<'a, T> {
157    fn command(
158        &self,
159        command_num: usize,
160        _: usize,
161        _: usize,
162        processid: ProcessId,
163    ) -> CommandReturn {
164        match command_num {
165            0 => {
166                // Driver existence check.
167                CommandReturn::success()
168            }
169            1 => {
170                // Read distance.
171                self.enqueue_command(processid)
172            }
173            2 => {
174                // Get minimum distance.
175                CommandReturn::success_u32(self.driver.get_minimum_distance())
176            }
177            3 => {
178                // Get maximum distance.
179                CommandReturn::success_u32(self.driver.get_maximum_distance())
180            }
181            _ => {
182                // Command not supported.
183                CommandReturn::failure(ErrorCode::NOSUPPORT)
184            }
185        }
186    }
187
188    fn allocate_grant(&self, process_id: ProcessId) -> Result<(), kernel::process::Error> {
189        self.apps.enter(process_id, |_, _| {})
190    }
191}