capsules_extra/
ninedof.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 virtualized access to 9DOF sensors.
6//!
7//! Usage
8//! -----
9//!
10//! You need a device that provides the `hil::sensors::NineDof` trait.
11//!
12//! ```rust,ignore
13//! # use kernel::{hil, static_init};
14//!
15//! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability);
16//! let grant_ninedof = board_kernel.create_grant(&grant_cap);
17//!
18//! let ninedof = static_init!(
19//!     capsules::ninedof::NineDof<'static>,
20//!     capsules::ninedof::NineDof::new(grant_ninedof));
21//! ninedof.add_driver(fxos8700);
22//! hil::sensors::NineDof::set_client(fxos8700, ninedof);
23//! ```
24
25use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
26use kernel::hil;
27use kernel::syscall::{CommandReturn, SyscallDriver};
28use kernel::utilities::cells::OptionalCell;
29use kernel::{ErrorCode, ProcessId};
30
31/// Syscall driver number.
32use capsules_core::driver;
33pub const DRIVER_NUM: usize = driver::NUM::NINEDOF as usize;
34
35#[derive(Clone, Copy, PartialEq)]
36pub enum NineDofCommand {
37    Exists,
38    ReadAccelerometer,
39    ReadMagnetometer,
40    ReadGyroscope,
41}
42
43pub struct App {
44    pending_command: bool,
45    command: NineDofCommand,
46    arg1: usize,
47}
48
49impl Default for App {
50    fn default() -> App {
51        App {
52            pending_command: false,
53            command: NineDofCommand::Exists,
54            arg1: 0,
55        }
56    }
57}
58
59pub struct NineDof<'a> {
60    drivers: &'a [&'a dyn hil::sensors::NineDof<'a>],
61    apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
62    current_app: OptionalCell<ProcessId>,
63}
64
65impl<'a> NineDof<'a> {
66    pub fn new(
67        drivers: &'a [&'a dyn hil::sensors::NineDof<'a>],
68        grant: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
69    ) -> NineDof<'a> {
70        NineDof {
71            drivers,
72            apps: grant,
73            current_app: OptionalCell::empty(),
74        }
75    }
76
77    // Check so see if we are doing something. If not,
78    // go ahead and do this command. If so, this is queued
79    // and will be run when the pending command completes.
80    fn enqueue_command(
81        &self,
82        command: NineDofCommand,
83        arg1: usize,
84        processid: ProcessId,
85    ) -> CommandReturn {
86        self.apps
87            .enter(processid, |app, _| {
88                if self.current_app.is_none() {
89                    self.current_app.set(processid);
90                    let value = self.call_driver(command, arg1);
91                    if value != Ok(()) {
92                        self.current_app.clear();
93                    }
94                    CommandReturn::from(value)
95                } else {
96                    if app.pending_command {
97                        CommandReturn::failure(ErrorCode::BUSY)
98                    } else {
99                        app.pending_command = true;
100                        app.command = command;
101                        app.arg1 = arg1;
102                        CommandReturn::success()
103                    }
104                }
105            })
106            .unwrap_or_else(|err| {
107                let rcode: Result<(), ErrorCode> = err.into();
108                CommandReturn::from(rcode)
109            })
110    }
111
112    fn call_driver(&self, command: NineDofCommand, _: usize) -> Result<(), ErrorCode> {
113        match command {
114            NineDofCommand::ReadAccelerometer => {
115                let mut data = Err(ErrorCode::NODEVICE);
116                for driver in self.drivers.iter() {
117                    data = driver.read_accelerometer();
118                    if data == Ok(()) {
119                        break;
120                    }
121                }
122                data
123            }
124            NineDofCommand::ReadMagnetometer => {
125                let mut data = Err(ErrorCode::NODEVICE);
126                for driver in self.drivers.iter() {
127                    data = driver.read_magnetometer();
128                    if data == Ok(()) {
129                        break;
130                    }
131                }
132                data
133            }
134            NineDofCommand::ReadGyroscope => {
135                let mut data = Err(ErrorCode::NODEVICE);
136                for driver in self.drivers.iter() {
137                    data = driver.read_gyroscope();
138                    if data == Ok(()) {
139                        break;
140                    }
141                }
142                data
143            }
144            _ => Err(ErrorCode::NOSUPPORT),
145        }
146    }
147}
148
149impl hil::sensors::NineDofClient for NineDof<'_> {
150    fn callback(&self, arg1: usize, arg2: usize, arg3: usize) {
151        // Notify the current application that the command finished.
152        // Also keep track of what just finished to see if we can re-use
153        // the result.
154        let mut finished_command = NineDofCommand::Exists;
155        let mut finished_command_arg = 0;
156        self.current_app.take().map(|processid| {
157            let _ = self.apps.enter(processid, |app, upcalls| {
158                app.pending_command = false;
159                finished_command = app.command;
160                finished_command_arg = app.arg1;
161                upcalls.schedule_upcall(0, (arg1, arg2, arg3)).ok();
162            });
163        });
164
165        // Check if there are any pending events.
166        for cntr in self.apps.iter() {
167            let processid = cntr.processid();
168            let started_command = cntr.enter(|app, upcalls| {
169                if app.pending_command
170                    && app.command == finished_command
171                    && app.arg1 == finished_command_arg
172                {
173                    // Don't bother re-issuing this command, just use
174                    // the existing result.
175                    app.pending_command = false;
176                    upcalls.schedule_upcall(0, (arg1, arg2, arg3)).ok();
177                    false
178                } else if app.pending_command {
179                    app.pending_command = false;
180                    self.current_app.set(processid);
181                    self.call_driver(app.command, app.arg1) == Ok(())
182                } else {
183                    false
184                }
185            });
186            if started_command {
187                break;
188            }
189        }
190    }
191}
192
193impl SyscallDriver for NineDof<'_> {
194    fn command(
195        &self,
196        command_num: usize,
197        arg1: usize,
198        _: usize,
199        processid: ProcessId,
200    ) -> CommandReturn {
201        match command_num {
202            0 => CommandReturn::success(),
203            // Single acceleration reading.
204            1 => self.enqueue_command(NineDofCommand::ReadAccelerometer, arg1, processid),
205
206            // Single magnetometer reading.
207            100 => self.enqueue_command(NineDofCommand::ReadMagnetometer, arg1, processid),
208
209            // Single gyroscope reading.
210            200 => self.enqueue_command(NineDofCommand::ReadGyroscope, arg1, processid),
211
212            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
213        }
214    }
215
216    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
217        self.apps.enter(processid, |_, _| {})
218    }
219}