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}