capsules_extra/proximity.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 with access to proximity sensors.
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 proximity reading.
15//! The `subscribe`call return codes indicate the following:
16//!
17//! * `Ok(())`: the callback been successfully been configured.
18//! * `ENOSUPPORT`: Invalid allow_num.
19//!
20//!
21//! ### `command` System Call
22//!
23//! The `command` system call support one argument `cmd` which is used to specify the specific
24//! operation, currently the following cmd's are supported:
25//!
26//! * `0`: driver existence check
27//! * `1`: read proximity
28//! * `2`: read proximity on interrupt
29//!
30//!
31//! The possible return from the 'command' system call indicates the following:
32//!
33//! * `Ok(())`: The operation has been successful.
34//! * `BUSY`: The driver is busy.
35//! * `ENOSUPPORT`: Invalid `cmd`.
36//!
37//! Usage
38//! -----
39//!
40//! You need a device that provides the `hil::sensors::ProximityDriver` trait.
41//! Here is an example of how to set up a proximity sensor with the apds9960 IC
42//!
43//! ```rust,ignore
44//! # use kernel::static_init;
45//!
46//!let grant_cap = create_capability!(capabilities::MemoryAllocationCapability);
47//!
48//!let proximity = static_init!(
49//! capsules::proximity::ProximitySensor<'static>,
50//! capsules::proximity::ProximitySensor::new(apds9960 , board_kernel.create_grant(&grant_cap)));
51//!
52//!kernel::hil::sensors::ProximityDriver::set_client(apds9960, proximity);
53//! ```
54
55use core::cell::Cell;
56
57use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
58use kernel::hil;
59use kernel::syscall::{CommandReturn, SyscallDriver};
60use kernel::{ErrorCode, ProcessId};
61
62/// Syscall driver number.
63use capsules_core::driver;
64pub const DRIVER_NUM: usize = driver::NUM::Proximity as usize;
65
66#[derive(Default)]
67pub struct App {
68 subscribed: bool,
69 enqueued_command_type: ProximityCommand,
70 lower_proximity: u8,
71 upper_proximity: u8,
72}
73
74#[derive(Clone, Copy, PartialEq, Default)]
75pub enum ProximityCommand {
76 ReadProximity = 1,
77 ReadProximityOnInterrupt = 2,
78 #[default]
79 NoCommand = 3,
80}
81
82#[derive(Default)]
83pub struct Thresholds {
84 lower: u8,
85 upper: u8,
86}
87
88pub struct ProximitySensor<'a> {
89 driver: &'a dyn hil::sensors::ProximityDriver<'a>,
90 apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
91 command_running: Cell<ProximityCommand>,
92}
93
94impl<'a> ProximitySensor<'a> {
95 pub fn new(
96 driver: &'a dyn hil::sensors::ProximityDriver<'a>,
97 grant: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
98 ) -> ProximitySensor<'a> {
99 ProximitySensor {
100 driver,
101 apps: grant,
102 command_running: Cell::new(ProximityCommand::NoCommand),
103 }
104 }
105
106 fn enqueue_command(
107 &self,
108 command: ProximityCommand,
109 arg1: usize,
110 arg2: usize,
111 processid: ProcessId,
112 ) -> CommandReturn {
113 // Enqueue command by saving command type, args, processid within app struct in grant region
114 self.apps
115 .enter(processid, |app, _| {
116 // Return busy if same app attempts to enqueue second command before first one is "callbacked"
117 if app.subscribed {
118 return CommandReturn::failure(ErrorCode::BUSY);
119 }
120
121 if command == ProximityCommand::ReadProximityOnInterrupt {
122 app.lower_proximity = arg1 as u8;
123 app.upper_proximity = arg2 as u8;
124 }
125
126 app.subscribed = true; // enqueue
127 app.enqueued_command_type = command;
128
129 // If driver is currently processing a ReadProximityOnInterrupt command then we allow the current ReadProximityOnInterrupt command
130 // to interrupt it. With new thresholds set, we can account for all apps waiting on ReadProximityOnInterrupt with different thresholds set
131 // to all receive a callback when appropriate.
132 // Doing so ensures that the app issuing the current command can have it serviced without having to wait for the previous command to fire.
133 if (self.command_running.get() == ProximityCommand::ReadProximityOnInterrupt)
134 && (command == ProximityCommand::ReadProximityOnInterrupt)
135 {
136 let mut t: Thresholds = self.find_thresholds();
137 if t.lower < app.lower_proximity {
138 t.lower = app.lower_proximity;
139 }
140 if t.upper > app.upper_proximity {
141 t.upper = app.upper_proximity;
142 }
143 let _ = self.driver.read_proximity_on_interrupt(t.lower, t.upper);
144 self.command_running
145 .set(ProximityCommand::ReadProximityOnInterrupt);
146 return CommandReturn::success();
147 }
148
149 // If driver is currently processing a ReadProximityOnInterrupt command and current command is a ReadProximity then
150 // then command the driver to interrupt the former and replace with the latter. The former will still be in the queue as the app region in the
151 // grant will have the `subscribed` boolean field set
152 if (self.command_running.get() == ProximityCommand::ReadProximityOnInterrupt)
153 && (command == ProximityCommand::ReadProximity)
154 {
155 let _ = self.driver.read_proximity();
156 self.command_running.set(ProximityCommand::ReadProximity);
157 return CommandReturn::success();
158 }
159
160 if self.command_running.get() == ProximityCommand::NoCommand {
161 match app.enqueued_command_type {
162 ProximityCommand::ReadProximity => {
163 let _ = self.driver.read_proximity();
164 }
165 ProximityCommand::ReadProximityOnInterrupt => {
166 let mut t: Thresholds = self.find_thresholds();
167 if t.lower < app.lower_proximity {
168 t.lower = app.lower_proximity;
169 }
170 if t.upper > app.upper_proximity {
171 t.upper = app.upper_proximity;
172 }
173 let _ = self.driver.read_proximity_on_interrupt(t.lower, t.upper);
174 self.command_running
175 .set(ProximityCommand::ReadProximityOnInterrupt);
176 }
177 ProximityCommand::NoCommand => {}
178 }
179 }
180
181 CommandReturn::success()
182 })
183 .unwrap_or_else(|err| CommandReturn::failure(err.into()))
184 }
185
186 fn run_next_command(&self) -> Result<(), ErrorCode> {
187 // Find thresholds before entering any grant regions
188 let t: Thresholds = self.find_thresholds();
189 // Find and run another command
190 for cntr in self.apps.iter() {
191 let break_flag = cntr.enter(|app, _| {
192 if app.subscribed {
193 // run it
194 match app.enqueued_command_type {
195 ProximityCommand::ReadProximity => {
196 let _ = self.driver.read_proximity();
197 self.command_running.set(ProximityCommand::ReadProximity);
198 }
199 ProximityCommand::ReadProximityOnInterrupt => {
200 let _ = self.driver.read_proximity_on_interrupt(t.lower, t.upper);
201 self.command_running
202 .set(ProximityCommand::ReadProximityOnInterrupt);
203 }
204 ProximityCommand::NoCommand => {}
205 }
206 true
207 } else {
208 false
209 }
210 });
211
212 if break_flag {
213 break;
214 }
215 }
216
217 Ok(())
218 }
219
220 fn find_thresholds(&self) -> Thresholds {
221 // Get the lowest upper prox and highest lower prox of all enqueued apps waiting on a readproximityoninterrupt command
222 // With the IC thresholds set to these two values, we ensure to never miss an interrupt-causing proximity value for any of the
223 // apps waiting on a proximity interrupt
224 // Interrupts for thresholds t1,t2 where t1 < t2 are triggered when proximity > t2 or proximity < t1.
225 let mut highest_lower_proximity: u8 = 0;
226 let mut lowest_upper_proximity: u8 = 255;
227
228 for cntr in self.apps.iter() {
229 cntr.try_enter(|app, _| {
230 if (app.lower_proximity > highest_lower_proximity)
231 && app.subscribed
232 && app.enqueued_command_type == ProximityCommand::ReadProximityOnInterrupt
233 {
234 highest_lower_proximity = app.lower_proximity;
235 }
236 if (app.upper_proximity < lowest_upper_proximity)
237 && app.subscribed
238 && app.enqueued_command_type == ProximityCommand::ReadProximityOnInterrupt
239 {
240 lowest_upper_proximity = app.upper_proximity;
241 }
242 });
243 }
244
245 // return values
246 Thresholds {
247 lower: highest_lower_proximity,
248 upper: lowest_upper_proximity,
249 }
250 }
251}
252
253impl hil::sensors::ProximityClient for ProximitySensor<'_> {
254 fn callback(&self, temp_val: u8) {
255 // Here we callback the values only to the apps which are relevant for the callback
256 // We also dequeue any command for a callback so as to remove it from the wait list and add other commands to continue
257
258 // Schedule callbacks for appropriate apps (any apps waiting for a proximity command)
259 // For apps waiting on an interrupt, the reading is checked against the upper and lower thresholds of the app's enqueued command
260 // to notice if this reading will fulfill the app's command.
261 // The reading is also delivered to any apps waiting on an immediate reading.
262 for cntr in self.apps.iter() {
263 cntr.enter(|app, upcalls| {
264 if app.subscribed {
265 if app.enqueued_command_type == ProximityCommand::ReadProximityOnInterrupt {
266 // Case: ReadProximityOnInterrupt
267 // Only callback to those apps which we expect would want to know about this threshold reading.
268 if (temp_val > app.upper_proximity) || (temp_val < app.lower_proximity) {
269 upcalls.schedule_upcall(0, (temp_val as usize, 0, 0)).ok();
270 app.subscribed = false; // dequeue
271 }
272 } else {
273 // Case: ReadProximity
274 // Upcall to all apps waiting on read_proximity.
275 upcalls.schedule_upcall(0, (temp_val as usize, 0, 0)).ok();
276 app.subscribed = false; // dequeue
277 }
278 }
279 });
280 }
281
282 // No command is temporarily being run here as we have performed the callback for our last command
283 self.command_running.set(ProximityCommand::NoCommand);
284
285 // When we are done with callback (one command) then find another waiting command to run and run it
286 let _ = self.run_next_command();
287 }
288}
289
290impl SyscallDriver for ProximitySensor<'_> {
291 fn command(
292 &self,
293 command_num: usize,
294 arg1: usize,
295 arg2: usize,
296 processid: ProcessId,
297 ) -> CommandReturn {
298 match command_num {
299 // Driver existence check
300 0 => CommandReturn::success(),
301
302 // Instantaneous proximity measurement
303 1 => self.enqueue_command(ProximityCommand::ReadProximity, arg1, arg2, processid),
304
305 // Upcall occurs only after interrupt is fired
306 2 => self.enqueue_command(
307 ProximityCommand::ReadProximityOnInterrupt,
308 arg1,
309 arg2,
310 processid,
311 ),
312
313 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
314 }
315 }
316
317 fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
318 self.apps.enter(processid, |_, _| {})
319 }
320}