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                let _ =
196                    upcalls.schedule_upcall(0, (kernel::errorcode::into_statuscode(status), 0, 0));
197            });
198        });
199
200        // Remove the current app.
201        self.active_app.clear();
202
203        // Check if there is anything else to do.
204        self.check_queue();
205    }
206}
207
208/// Provide an interface for userland.
209impl<'a, B: hil::buzzer::Buzzer<'a>> SyscallDriver for Buzzer<'a, B> {
210    // Setup callbacks.
211    //
212    // ### `subscribe_num`
213    //
214    // - `0`: Setup a buzz done callback.
215
216    /// Command interface.
217    ///
218    /// ### `command_num`
219    ///
220    /// - `0`: Return Ok(()) if this driver is included on the platform.
221    /// - `1`: Buzz the buzzer when available. `data1` is used for the frequency in hertz, and
222    ///   `data2` is the duration in ms. Note the duration is capped at 5000
223    ///   milliseconds.
224    /// - `2`: Buzz the buzzer immediatelly. `data1` is used for the frequency in hertz, and
225    ///   `data2` is the duration in ms. Note the duration is capped at 5000
226    ///   milliseconds.
227    /// - `3`: Stop the buzzer.
228    fn command(
229        &self,
230        command_num: usize,
231        data1: usize,
232        data2: usize,
233        processid: ProcessId,
234    ) -> CommandReturn {
235        match command_num {
236            // Check whether the driver exists.
237            0 => CommandReturn::success(),
238
239            // Play a sound when available.
240            1 => {
241                let frequency_hz = data1;
242                let duration_ms = cmp::min(data2, self.max_duration_ms);
243                self.enqueue_command(
244                    BuzzerCommand::Buzz {
245                        frequency_hz,
246                        duration_ms,
247                    },
248                    processid,
249                )
250                .into()
251            }
252
253            // Play a sound immediately.
254            2 => {
255                if !self.is_valid_app(processid) {
256                    // A different app is trying to use the buzzer, so we return RESERVE.
257                    CommandReturn::failure(ErrorCode::RESERVE)
258                } else {
259                    // If there is no active app or the same app is trying to use the buzzer,
260                    // we set/replace the frequency and duration.
261                    self.active_app.set(processid);
262                    self.buzzer.buzz(data1, data2).into()
263                }
264            }
265
266            // Stop the current sound.
267            3 => {
268                if !self.is_valid_app(processid) {
269                    CommandReturn::failure(ErrorCode::RESERVE)
270                } else if self.active_app.is_none() {
271                    // If there is no active app, the buzzer isn't playing, so we return OFF.
272                    CommandReturn::failure(ErrorCode::OFF)
273                } else {
274                    self.active_app.set(processid);
275                    self.buzzer.stop().into()
276                }
277            }
278
279            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
280        }
281    }
282
283    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
284        self.apps.enter(processid, |_, _| {})
285    }
286}