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}