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}