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}