capsules_extra/buzzer_driver.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//! This provides virtualized userspace access to a buzzer.
6//!
7//! Each app can have one outstanding buzz request, and buzz requests will queue
8//! with each app getting exclusive access to the buzzer during its turn. Apps
9//! can specify the frequency and duration of the square wave buzz, but the
10//! duration is capped to prevent this from being annoying.
11//!
12//! Apps can subscribe to an optional callback if they care about getting
13//! buzz done events.
14//!
15//! Usage
16//! -----
17//!
18//! ```rust,ignore
19//! # use kernel::static_init;
20//!
21//! let virtual_pwm_buzzer = static_init!(
22//! capsules::virtual_pwm::PwmPinUser<'static, nrf52::pwm::Pwm>,
23//! capsules::virtual_pwm::PwmPinUser::new(mux_pwm, nrf5x::pinmux::Pinmux::new(31))
24//! );
25//! virtual_pwm_buzzer.add_to_mux();
26//!
27//! let virtual_alarm_buzzer = static_init!(
28//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf5x::rtc::Rtc>,
29//! capsules::virtual_alarm::VirtualMuxAlarm::new(mux_alarm)
30//! );
31//! virtual_alarm_buzzer.setup();
32//!
33//! let pwm_buzzer = static_init!(
34//! capsules::buzzer_pwm::PwmBuzzer<
35//! 'static,
36//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>,
37//! capsules::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>,
38//! >,
39//! capsules::buzzer_pwm::PwmBuzzer::new(
40//! virtual_pwm_buzzer,
41//! virtual_alarm_buzzer,
42//! capsules::buzzer_pwm::DEFAULT_MAX_BUZZ_TIME_MS,
43//! )
44//! );
45//!
46//! let buzzer_driver = static_init!(
47//! capsules::buzzer_driver::Buzzer<
48//! 'static,
49//! capsules::buzzer_pwm::PwmBuzzer<
50//! 'static,
51//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>,
52//! capsules::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>,
53//! >,
54//! >,
55//! capsules::buzzer_driver::Buzzer::new(
56//! pwm_buzzer,
57//! capsules::buzzer_driver::DEFAULT_MAX_BUZZ_TIME_MS,
58//! board_kernel.create_grant(capsules::buzzer_driver::DRIVER_NUM, &memory_allocation_capability)
59//! )
60//! );
61//!
62//! pwm_buzzer.set_client(buzzer_driver);
63//!
64//! virtual_alarm_buzzer.set_client(pwm_buzzer);
65//! ```
66
67use core::cmp;
68
69use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
70use kernel::hil;
71use kernel::syscall::{CommandReturn, SyscallDriver};
72use kernel::utilities::cells::OptionalCell;
73use kernel::{ErrorCode, ProcessId};
74
75/// Syscall driver number.
76use capsules_core::driver;
77pub const DRIVER_NUM: usize = driver::NUM::Buzzer as usize;
78
79/// Standard max buzz time.
80pub const DEFAULT_MAX_BUZZ_TIME_MS: usize = 5000;
81
82#[derive(Clone, Copy, PartialEq)]
83pub enum BuzzerCommand {
84 Buzz {
85 frequency_hz: usize,
86 duration_ms: usize,
87 },
88}
89
90#[derive(Default)]
91pub struct App {
92 pending_command: Option<BuzzerCommand>, // What command to run when the buzzer is free.
93}
94
95pub struct Buzzer<'a, B: hil::buzzer::Buzzer<'a>> {
96 /// The service capsule buzzer.
97 buzzer: &'a B,
98 /// Per-app state.
99 apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
100 /// Which app is currently using the buzzer.
101 active_app: OptionalCell<ProcessId>,
102 /// Max buzz time.
103 max_duration_ms: usize,
104}
105
106impl<'a, B: hil::buzzer::Buzzer<'a>> Buzzer<'a, B> {
107 pub fn new(
108 buzzer: &'a B,
109 max_duration_ms: usize,
110 grant: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
111 ) -> Buzzer<'a, B> {
112 Buzzer {
113 buzzer,
114 apps: grant,
115 active_app: OptionalCell::empty(),
116 max_duration_ms,
117 }
118 }
119
120 // Check so see if we are doing something. If not, go ahead and do this
121 // command. If so, this is queued and will be run when the pending
122 // command completes.
123 fn enqueue_command(
124 &self,
125 command: BuzzerCommand,
126 processid: ProcessId,
127 ) -> Result<(), ErrorCode> {
128 if self.active_app.is_none() {
129 // No app is currently using the buzzer, so we just use this app.
130 self.active_app.set(processid);
131 match command {
132 BuzzerCommand::Buzz {
133 frequency_hz,
134 duration_ms,
135 } => self.buzzer.buzz(frequency_hz, duration_ms),
136 }
137 } else {
138 // There is an active app, so queue this request (if possible).
139 self.apps
140 .enter(processid, |app, _| {
141 // Some app is using the storage, we must wait.
142 if app.pending_command.is_some() {
143 // No more room in the queue, nowhere to store this
144 // request.
145 Err(ErrorCode::NOMEM)
146 } else {
147 // We can store this, so lets do it.
148 app.pending_command = Some(command);
149 Ok(())
150 }
151 })
152 .unwrap_or_else(|err| err.into())
153 }
154 }
155
156 fn check_queue(&self) {
157 for appiter in self.apps.iter() {
158 let processid = appiter.processid();
159 let started_command = appiter.enter(|app, _| {
160 // If this app has a pending command let's use it.
161 app.pending_command.take().is_some_and(|command| {
162 // Mark this driver as being in use.
163 self.active_app.set(processid);
164 // Actually make the buzz happen.
165 match command {
166 BuzzerCommand::Buzz {
167 frequency_hz,
168 duration_ms,
169 } => self.buzzer.buzz(frequency_hz, duration_ms) == Ok(()),
170 }
171 })
172 });
173 if started_command {
174 break;
175 }
176 }
177 }
178
179 /// For buzzing immediatelly
180 /// Checks whether an app is valid or not. The app is valid if
181 /// there is no current active_app using the driver, or if the app corresponds
182 /// to the current active_app. Otherwise, a different app is trying to
183 /// use the driver while it is already in use, therefore it is not valid.
184 pub fn is_valid_app(&self, processid: ProcessId) -> bool {
185 self.active_app
186 .map_or(true, |owning_app| owning_app == processid)
187 }
188}
189
190impl<'a, B: hil::buzzer::Buzzer<'a>> hil::buzzer::BuzzerClient for Buzzer<'a, B> {
191 fn buzzer_done(&self, status: Result<(), ErrorCode>) {
192 // Mark the active app as None and see if there is a callback.
193 self.active_app.take().map(|processid| {
194 let _ = self.apps.enter(processid, |_app, upcalls| {
195 upcalls
196 .schedule_upcall(0, (kernel::errorcode::into_statuscode(status), 0, 0))
197 .ok();
198 });
199 });
200
201 // Remove the current app.
202 self.active_app.clear();
203
204 // Check if there is anything else to do.
205 self.check_queue();
206 }
207}
208
209/// Provide an interface for userland.
210impl<'a, B: hil::buzzer::Buzzer<'a>> SyscallDriver for Buzzer<'a, B> {
211 // Setup callbacks.
212 //
213 // ### `subscribe_num`
214 //
215 // - `0`: Setup a buzz done callback.
216
217 /// Command interface.
218 ///
219 /// ### `command_num`
220 ///
221 /// - `0`: Return Ok(()) if this driver is included on the platform.
222 /// - `1`: Buzz the buzzer when available. `data1` is used for the frequency in hertz, and
223 /// `data2` is the duration in ms. Note the duration is capped at 5000
224 /// milliseconds.
225 /// - `2`: Buzz the buzzer immediatelly. `data1` is used for the frequency in hertz, and
226 /// `data2` is the duration in ms. Note the duration is capped at 5000
227 /// milliseconds.
228 /// - `3`: Stop the buzzer.
229 fn command(
230 &self,
231 command_num: usize,
232 data1: usize,
233 data2: usize,
234 processid: ProcessId,
235 ) -> CommandReturn {
236 match command_num {
237 // Check whether the driver exists.
238 0 => CommandReturn::success(),
239
240 // Play a sound when available.
241 1 => {
242 let frequency_hz = data1;
243 let duration_ms = cmp::min(data2, self.max_duration_ms);
244 self.enqueue_command(
245 BuzzerCommand::Buzz {
246 frequency_hz,
247 duration_ms,
248 },
249 processid,
250 )
251 .into()
252 }
253
254 // Play a sound immediately.
255 2 => {
256 if !self.is_valid_app(processid) {
257 // A different app is trying to use the buzzer, so we return RESERVE.
258 CommandReturn::failure(ErrorCode::RESERVE)
259 } else {
260 // If there is no active app or the same app is trying to use the buzzer,
261 // we set/replace the frequency and duration.
262 self.active_app.set(processid);
263 self.buzzer.buzz(data1, data2).into()
264 }
265 }
266
267 // Stop the current sound.
268 3 => {
269 if !self.is_valid_app(processid) {
270 CommandReturn::failure(ErrorCode::RESERVE)
271 } else if self.active_app.is_none() {
272 // If there is no active app, the buzzer isn't playing, so we return OFF.
273 CommandReturn::failure(ErrorCode::OFF)
274 } else {
275 self.active_app.set(processid);
276 self.buzzer.stop().into()
277 }
278 }
279
280 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
281 }
282 }
283
284 fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
285 self.apps.enter(processid, |_, _| {})
286 }
287}