capsules_extra/
temperature.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 temperature 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 temperature sensor 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//! * `NOMEM`: No sufficient memory available.
20//! * `INVAL`: Invalid address of the buffer or other error.
21//!
22//!
23//! ### `command` System Call
24//!
25//! The `command` system call support one argument `cmd` which is used to
26//! specify the specific operation, currently the following cmd's are supported:
27//!
28//! * `0`: check whether the driver exists
29//! * `1`: read the temperature
30//!
31//!
32//! The possible return from the 'command' system call indicates the following:
33//!
34//! * `Ok(())`:    The operation has been successful.
35//! * `NOSUPPORT`: Invalid `cmd`.
36//! * `NOMEM`:     Insufficient memory available.
37//! * `INVAL`:     Invalid address of the buffer or other error.
38//!
39//! Usage
40//! -----
41//!
42//! You need a device that provides the `hil::sensors::TemperatureDriver` trait.
43//!
44//! ```rust,ignore
45//! # use kernel::static_init;
46//!
47//! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability);
48//! let grant_temperature = board_kernel.create_grant(&grant_cap);
49//!
50//! let temp = static_init!(
51//!        capsules::temperature::TemperatureSensor<'static>,
52//!        capsules::temperature::TemperatureSensor::new(si7021,
53//!                                                 board_kernel.create_grant(&grant_cap)));
54//!
55//! kernel::hil::sensors::TemperatureDriver::set_client(si7021, temp);
56//! ```
57
58use core::cell::Cell;
59
60use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
61use kernel::hil;
62use kernel::syscall::{CommandReturn, SyscallDriver};
63use kernel::{ErrorCode, ProcessId};
64
65/// Syscall driver number.
66use capsules_core::driver;
67pub const DRIVER_NUM: usize = driver::NUM::Temperature as usize;
68
69#[derive(Default)]
70pub struct App {
71    subscribed: bool,
72}
73
74pub struct TemperatureSensor<'a, T: hil::sensors::TemperatureDriver<'a>> {
75    driver: &'a T,
76    apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
77    busy: Cell<bool>,
78}
79
80impl<'a, T: hil::sensors::TemperatureDriver<'a>> TemperatureSensor<'a, T> {
81    pub fn new(
82        driver: &'a T,
83        grant: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
84    ) -> TemperatureSensor<'a, T> {
85        TemperatureSensor {
86            driver,
87            apps: grant,
88            busy: Cell::new(false),
89        }
90    }
91
92    fn enqueue_command(&self, processid: ProcessId) -> CommandReturn {
93        self.apps
94            .enter(processid, |app, _| {
95                // Unconditionally mark this client as subscribed so it will get
96                // a callback when we get the temperature reading.
97                app.subscribed = true;
98
99                // If we do not already have an ongoing read, start one now.
100                if !self.busy.get() {
101                    self.busy.set(true);
102                    match self.driver.read_temperature() {
103                        Ok(()) => CommandReturn::success(),
104                        Err(e) => CommandReturn::failure(e),
105                    }
106                } else {
107                    // Just return success and we will get the upcall when the
108                    // temperature read is ready.
109                    CommandReturn::success()
110                }
111            })
112            .unwrap_or_else(|err| CommandReturn::failure(err.into()))
113    }
114}
115
116impl<'a, T: hil::sensors::TemperatureDriver<'a>> hil::sensors::TemperatureClient
117    for TemperatureSensor<'a, T>
118{
119    fn callback(&self, temp_val: Result<i32, ErrorCode>) {
120        // We completed the operation so we clear the busy flag in case we get
121        // another measurement request.
122        self.busy.set(false);
123
124        // Return the temperature reading to any waiting client.
125        if let Ok(temp_val) = temp_val {
126            // TODO: forward error conditions
127            for cntr in self.apps.iter() {
128                cntr.enter(|app, upcalls| {
129                    if app.subscribed {
130                        app.subscribed = false;
131                        upcalls.schedule_upcall(0, (temp_val as usize, 0, 0)).ok();
132                    }
133                });
134            }
135        }
136    }
137}
138
139impl<'a, T: hil::sensors::TemperatureDriver<'a>> SyscallDriver for TemperatureSensor<'a, T> {
140    fn command(
141        &self,
142        command_num: usize,
143        _: usize,
144        _: usize,
145        processid: ProcessId,
146    ) -> CommandReturn {
147        match command_num {
148            // driver existence check
149            0 => CommandReturn::success(),
150
151            // read temperature
152            1 => self.enqueue_command(processid),
153            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
154        }
155    }
156
157    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
158        self.apps.enter(processid, |_, _| {})
159    }
160}