capsules_extra/
ble_advertising_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//! Bluetooth Low Energy Advertising Driver
6//!
7//! A system call driver that exposes the Bluetooth Low Energy advertising
8//! channel. The driver generates a unique static address for each process,
9//! allowing each process to act as its own device and send or scan for
10//! advertisements. Timing of advertising or scanning events is handled by the
11//! driver but processes can request an advertising or scanning interval.
12//! Processes can also control the TX power used for their advertisements.
13//!
14//! Data payloads are limited to 31 bytes since the maximum advertising channel
15//! protocol data unit (PDU) is 37 bytes and includes a 6-byte header.
16//!
17//! ### Allow system calls
18//!
19//! There is one ReadWrite and one ReadOnly allow buffers, both at index `0`.
20//!
21//! * ReadOnly: Advertising data, containing the full _payload_ (i.e. excluding
22//!   the header) the process wishes to advertise.
23//! * ReadWrite: Passive scanning buffer, which is populated during BLE scans
24//!   with complete (i.e. including headers) advertising packets received on
25//!   channels 37, 38 and 39.
26//!
27//! The possible return codes from the 'allow' system call indicate the following:
28//!
29//! * Ok(()): The buffer has successfully been filled
30//! * NOMEM: No sufficient memory available
31//! * INVAL: Invalid address of the buffer or other error
32//! * BUSY: The driver is currently busy with other tasks
33//! * ENOSUPPORT: The operation is not supported
34//! * ERROR: Operation `map` on Option failed
35//!
36//! ### Subscribe system call
37//!
38//!  The `subscribe` system call supports two arguments `subscribe number' and `callback`.
39//!  The `subscribe` is used to specify the specific operation, currently:
40//!
41//! * 0: provides a callback user-space when a device scanning for
42//!   advertisements and the callback is used to invoke user-space processes.
43//!
44//! The possible return codes from the `allow` system call indicate the following:
45//!
46//! * NOMEM:    Not sufficient amount memory
47//! * INVAL:    Invalid operation
48//!
49//! ### Command system call
50//!
51//! The `command` system call supports two arguments `command number` and `subcommand number`.
52//! `command number` is used to specify the specific operation, currently
53//! the following commands are supported:
54//!
55//! * 0: start advertisement
56//! * 1: stop advertisement or scanning
57//! * 5: start scanning
58//!
59//! The possible return codes from the `command` system call indicate the following:
60//!
61//! * Ok(()):      The command was successful
62//! * BUSY:        The driver is currently busy with other tasks
63//! * ENOSUPPORT:   The operation is not supported
64//!
65//! Usage
66//! -----
67//!
68//! You need a device that provides the `kernel::BleAdvertisementDriver` trait along with a virtual
69//! timer to perform events and not block the entire kernel
70//!
71//! ```rust,ignore
72//! # use kernel::static_init;
73//! # use capsules::virtual_alarm::VirtualMuxAlarm;
74//!
75//! let ble_radio = static_init!(
76//! nrf5x::ble_advertising_driver::BLE<
77//!     'static,
78//!     nrf52::radio::Radio, VirtualMuxAlarm<'static, Rtc>
79//! >,
80//! nrf5x::ble_advertising_driver::BLE::new(
81//!     &mut nrf52::radio::RADIO,
82//!     board_kernel.create_grant(&grant_cap),
83//!     &mut nrf5x::ble_advertising_driver::BUF,
84//!     ble_radio_virtual_alarm));
85//! nrf5x::ble_advertising_hil::BleAdvertisementDriver::set_rx_client(&nrf52::radio::RADIO,
86//!                                                                   ble_radio);
87//! nrf5x::ble_advertising_hil::BleAdvertisementDriver::set_tx_client(&nrf52::radio::RADIO,
88//!                                                                   ble_radio);
89//! ble_radio_virtual_alarm.set_client(ble_radio);
90//! ```
91//!
92//! ### Authors
93//! * Niklas Adolfsson <niklasadolfsson1@gmail.com>
94//! * Fredrik Nilsson <frednils@student.chalmers.se>
95//! * Date: June 22, 2017
96
97// # Implementation
98//
99// Advertising virtualization works by implementing a virtual periodic timer for each process. The
100// timer is configured to fire at each advertising interval, as specified by the process. When a
101// timer fires, we serialize the advertising packet for that process (using the provided AdvData
102// payload, generated address and PDU type) and perform one advertising event (on each of three
103// channels).
104//
105// This means that advertising events can collide. In this case, we just defer one of the
106// advertisements. Because we add a pseudo random pad to the timer interval each time (as required
107// by the Bluetooth specification) multiple collisions of the same processes are highly unlikely.
108
109use core::cell::Cell;
110use core::cmp;
111
112use kernel::debug;
113use kernel::grant::{AllowRoCount, AllowRwCount, Grant, GrantKernelData, UpcallCount};
114use kernel::hil::ble_advertising;
115use kernel::hil::ble_advertising::RadioChannel;
116use kernel::hil::time::{Frequency, Ticks};
117use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer};
118use kernel::syscall::{CommandReturn, SyscallDriver};
119use kernel::utilities::cells::OptionalCell;
120use kernel::utilities::copy_slice::CopyOrErr;
121use kernel::{ErrorCode, ProcessId};
122
123/// Syscall driver number.
124use capsules_core::driver;
125pub const DRIVER_NUM: usize = driver::NUM::BleAdvertising as usize;
126
127/// Ids for read-only allow buffers
128mod ro_allow {
129    pub const ADV_DATA: usize = 0;
130    /// The number of allow buffers the kernel stores for this grant
131    pub const COUNT: u8 = 1;
132}
133
134/// Ids for read-write allow buffers
135mod rw_allow {
136    pub const SCAN_BUFFER: usize = 0;
137    /// The number of allow buffers the kernel stores for this grant
138    pub const COUNT: u8 = 1;
139}
140
141const PACKET_ADDR_LEN: usize = 6;
142pub const PACKET_LENGTH: usize = 39;
143const ADV_HEADER_TXADD_OFFSET: usize = 6;
144
145#[derive(PartialEq, Debug)]
146enum BLEState {
147    Idle,
148    ScanningIdle,
149    Scanning(RadioChannel),
150    AdvertisingIdle,
151    Advertising(RadioChannel),
152}
153
154#[derive(Copy, Clone)]
155enum Expiration {
156    Disabled,
157    Enabled(u32, u32),
158}
159
160#[derive(Copy, Clone)]
161struct AlarmData {
162    expiration: Expiration,
163}
164
165impl AlarmData {
166    fn new() -> AlarmData {
167        AlarmData {
168            expiration: Expiration::Disabled,
169        }
170    }
171}
172
173type AdvPduType = u8;
174
175// BLUETOOTH SPECIFICATION Version 4.2 [Vol 6, Part B], section 2.3.3
176const ADV_IND: AdvPduType = 0b0000;
177#[allow(dead_code)]
178const ADV_DIRECTED_IND: AdvPduType = 0b0001;
179const ADV_NONCONN_IND: AdvPduType = 0b0010;
180#[allow(dead_code)]
181const SCAN_REQ: AdvPduType = 0b0011;
182#[allow(dead_code)]
183const SCAN_RESP: AdvPduType = 0b0100;
184#[allow(dead_code)]
185const CONNECT_IND: AdvPduType = 0b0101;
186const ADV_SCAN_IND: AdvPduType = 0b0110;
187
188/// Process specific memory
189pub struct App {
190    process_status: Option<BLEState>,
191    alarm_data: AlarmData,
192
193    // Advertising meta-data
194    address: [u8; PACKET_ADDR_LEN],
195    pdu_type: AdvPduType,
196    advertisement_interval_ms: u32,
197    tx_power: u8,
198    /// The state of an app-specific pseudo random number.
199    ///
200    /// For example, it can be used for the pseudo-random `advDelay` parameter.
201    /// It should be read using the `random_number` method, which updates it as
202    /// well.
203    random_nonce: u32,
204}
205
206impl Default for App {
207    fn default() -> App {
208        App {
209            alarm_data: AlarmData::new(),
210            address: [0; PACKET_ADDR_LEN],
211            pdu_type: ADV_NONCONN_IND,
212            process_status: Some(BLEState::Idle),
213            tx_power: 0,
214            advertisement_interval_ms: 200,
215            // Just use any non-zero starting value by default
216            random_nonce: 0xdeadbeef,
217        }
218    }
219}
220
221impl App {
222    // Bluetooth Core Specification:Vol. 6, Part B, section 1.3.2.1 Static Device Address
223    //
224    // A static address is a 48-bit randomly generated address and shall meet the following
225    // requirements:
226    // • The two most significant bits of the address shall be equal to 1
227    // • At least one bit of the random part of the address shall be 0
228    // • At least one bit of the random part of the address shall be 1
229    //
230    // Note that endianness is a potential problem here as this is suppose to be platform
231    // independent therefore use 0xf0 as both byte 1 and byte 6 i.e., the two most significant bits
232    // are equal to one regardless of endianness
233    //
234    // Byte 1            0xf0
235    // Byte 2-5          random
236    // Byte 6            0xf0
237    // FIXME: For now use ProcessId as "randomness"
238    fn generate_random_address(&mut self, processid: kernel::ProcessId) -> Result<(), ErrorCode> {
239        self.address = [
240            0xf0,
241            (processid.id() & 0xff) as u8,
242            ((processid.id() << 8) & 0xff) as u8,
243            ((processid.id() << 16) & 0xff) as u8,
244            ((processid.id() << 24) & 0xff) as u8,
245            0xf0,
246        ];
247        Ok(())
248    }
249
250    fn send_advertisement<'a, B, A>(
251        &mut self,
252        processid: kernel::ProcessId,
253        kernel_data: &GrantKernelData,
254        ble: &BLE<'a, B, A>,
255        channel: RadioChannel,
256    ) -> Result<(), ErrorCode>
257    where
258        B: ble_advertising::BleAdvertisementDriver<'a> + ble_advertising::BleConfig,
259        A: kernel::hil::time::Alarm<'a>,
260    {
261        // Ensure we have an address set before advertisement
262        self.generate_random_address(processid)?;
263        kernel_data
264            .get_readonly_processbuffer(ro_allow::ADV_DATA)
265            .and_then(|adv_data| {
266                adv_data.enter(|adv_data| {
267                    ble.kernel_tx
268                        .take()
269                        .map_or(Err(ErrorCode::FAIL), |kernel_tx| {
270                            let adv_data_len =
271                                cmp::min(kernel_tx.len() - PACKET_ADDR_LEN - 2, adv_data.len());
272                            let adv_data_corrected =
273                                adv_data.get(..adv_data_len).ok_or(ErrorCode::SIZE)?;
274                            let payload_len = adv_data_corrected.len() + PACKET_ADDR_LEN;
275                            {
276                                let (header, payload) = kernel_tx.split_at_mut(2);
277                                header[0] = self.pdu_type;
278                                match self.pdu_type {
279                                    ADV_IND | ADV_NONCONN_IND | ADV_SCAN_IND => {
280                                        // Set TxAdd because AdvA field is going to be a "random"
281                                        // address
282                                        header[0] |= 1 << ADV_HEADER_TXADD_OFFSET;
283                                    }
284                                    _ => {}
285                                }
286                                // The LENGTH field is 6-bits wide, so make sure to truncate it
287                                header[1] = (payload_len & 0x3f) as u8;
288
289                                let (adva, data) = payload.split_at_mut(6);
290                                adva.copy_from_slice_or_err(&self.address)?;
291                                adv_data_corrected.copy_to_slice(&mut data[..adv_data_len]);
292                            }
293                            let total_len = cmp::min(PACKET_LENGTH, payload_len + 2);
294                            ble.radio
295                                .transmit_advertisement(kernel_tx, total_len, channel);
296                            Ok(())
297                        })
298                })
299            })
300            .unwrap_or(Err(ErrorCode::FAIL))
301    }
302
303    // Returns a new pseudo-random number and updates the randomness state.
304    //
305    // Uses the [Xorshift](https://en.wikipedia.org/wiki/Xorshift) algorithm to
306    // produce pseudo-random numbers. Uses the `random_nonce` field to keep
307    // state.
308    fn random_nonce(&mut self) -> u32 {
309        let mut next_nonce = ::core::num::Wrapping(self.random_nonce);
310        next_nonce ^= next_nonce << 13;
311        next_nonce ^= next_nonce >> 17;
312        next_nonce ^= next_nonce << 5;
313        self.random_nonce = next_nonce.0;
314        self.random_nonce
315    }
316
317    // Set the next alarm for this app using the period and provided start time.
318    fn set_next_alarm<F: Frequency>(&mut self, now: u32) {
319        let nonce = self.random_nonce() % 10;
320
321        let period_ms = (self.advertisement_interval_ms + nonce) * F::frequency() / 1000;
322        self.alarm_data.expiration = Expiration::Enabled(now, period_ms);
323    }
324}
325
326pub struct BLE<'a, B, A>
327where
328    B: ble_advertising::BleAdvertisementDriver<'a> + ble_advertising::BleConfig,
329    A: kernel::hil::time::Alarm<'a>,
330{
331    radio: &'a B,
332    busy: Cell<bool>,
333    app: Grant<
334        App,
335        UpcallCount<1>,
336        AllowRoCount<{ ro_allow::COUNT }>,
337        AllowRwCount<{ rw_allow::COUNT }>,
338    >,
339    kernel_tx: kernel::utilities::cells::TakeCell<'static, [u8]>,
340    alarm: &'a A,
341    sending_app: OptionalCell<kernel::ProcessId>,
342    receiving_app: OptionalCell<kernel::ProcessId>,
343}
344
345impl<'a, B, A> BLE<'a, B, A>
346where
347    B: ble_advertising::BleAdvertisementDriver<'a> + ble_advertising::BleConfig,
348    A: kernel::hil::time::Alarm<'a>,
349{
350    pub fn new(
351        radio: &'a B,
352        container: Grant<
353            App,
354            UpcallCount<1>,
355            AllowRoCount<{ ro_allow::COUNT }>,
356            AllowRwCount<{ rw_allow::COUNT }>,
357        >,
358        tx_buf: &'static mut [u8],
359        alarm: &'a A,
360    ) -> BLE<'a, B, A> {
361        BLE {
362            radio,
363            busy: Cell::new(false),
364            app: container,
365            kernel_tx: kernel::utilities::cells::TakeCell::new(tx_buf),
366            alarm,
367            sending_app: OptionalCell::empty(),
368            receiving_app: OptionalCell::empty(),
369        }
370    }
371
372    // Determines which app timer will expire next and sets the underlying alarm
373    // to it.
374    //
375    // This method iterates through all grants so it should be used somewhat
376    // sparingly. Moreover, it should _not_ be called from within a grant,
377    // since any open grant will not be iterated over and the wrong timer will
378    // likely be chosen.
379    fn reset_active_alarm(&self) {
380        let now = self.alarm.now();
381        let mut next_ref = u32::MAX;
382        let mut next_dt = u32::MAX;
383        let mut next_dist = u32::MAX;
384        for app in self.app.iter() {
385            app.enter(|app, _| match app.alarm_data.expiration {
386                Expiration::Enabled(reference, dt) => {
387                    let exp = reference.wrapping_add(dt);
388                    let t_dist = exp.wrapping_sub(now.into_u32());
389                    if next_dist > t_dist {
390                        next_ref = reference;
391                        next_dt = dt;
392                        next_dist = t_dist;
393                    }
394                }
395                Expiration::Disabled => {}
396            });
397        }
398        if next_ref != u32::MAX {
399            self.alarm
400                .set_alarm(A::Ticks::from(next_ref), A::Ticks::from(next_dt));
401        }
402    }
403}
404
405// Timer alarm
406impl<'a, B, A> kernel::hil::time::AlarmClient for BLE<'a, B, A>
407where
408    B: ble_advertising::BleAdvertisementDriver<'a> + ble_advertising::BleConfig,
409    A: kernel::hil::time::Alarm<'a>,
410{
411    // When an alarm is fired, we find which apps have expired timers. Expired
412    // timers indicate a desire to perform some operation (e.g. start an
413    // advertising or scanning event). We know which operation based on the
414    // current app's state.
415    //
416    // In case of collision---if there is already an event happening---we'll
417    // just delay the operation for next time and hope for the best. Since some
418    // randomness is added for each period in an app's timer, collisions should
419    // be rare in practice.
420    //
421    // TODO: perhaps break ties more fairly by prioritizing apps that have least
422    // recently performed an operation.
423    fn alarm(&self) {
424        let now = self.alarm.now();
425
426        self.app.each(|processid, app, kernel_data| {
427            if let Expiration::Enabled(reference, dt) = app.alarm_data.expiration {
428                let exp = A::Ticks::from(reference.wrapping_add(dt));
429                let t0 = A::Ticks::from(reference);
430                let expired = !now.within_range(t0, exp);
431                if expired {
432                    if self.busy.get() {
433                        // The radio is currently busy, so we won't be able to start the
434                        // operation at the appropriate time. Instead, reschedule the
435                        // operation for later. This is _kind_ of simulating actual
436                        // on-air interference. 3 seems like a small number of ticks.
437                        debug!("BLE: operation delayed for app {:?}", processid);
438                        app.set_next_alarm::<A::Frequency>(self.alarm.now().into_u32());
439                        return;
440                    }
441
442                    app.alarm_data.expiration = Expiration::Disabled;
443
444                    match app.process_status {
445                        Some(BLEState::AdvertisingIdle) => {
446                            self.busy.set(true);
447                            app.process_status =
448                                Some(BLEState::Advertising(RadioChannel::AdvertisingChannel37));
449                            self.sending_app.set(processid);
450                            let _ = self.radio.set_tx_power(app.tx_power);
451                            let _ = app.send_advertisement(
452                                processid,
453                                kernel_data,
454                                self,
455                                RadioChannel::AdvertisingChannel37,
456                            );
457                        }
458                        Some(BLEState::ScanningIdle) => {
459                            self.busy.set(true);
460                            app.process_status =
461                                Some(BLEState::Scanning(RadioChannel::AdvertisingChannel37));
462                            self.receiving_app.set(processid);
463                            let _ = self.radio.set_tx_power(app.tx_power);
464                            self.radio
465                                .receive_advertisement(RadioChannel::AdvertisingChannel37);
466                        }
467                        _ => debug!(
468                            "app: {:?} \t invalid state {:?}",
469                            processid, app.process_status
470                        ),
471                    }
472                }
473            }
474        });
475        self.reset_active_alarm();
476    }
477}
478
479// Callback from the radio once a RX event occur
480impl<'a, B, A> ble_advertising::RxClient for BLE<'a, B, A>
481where
482    B: ble_advertising::BleAdvertisementDriver<'a> + ble_advertising::BleConfig,
483    A: kernel::hil::time::Alarm<'a>,
484{
485    fn receive_event(&self, buf: &'static mut [u8], len: u8, result: Result<(), ErrorCode>) {
486        self.receiving_app.map(|processid| {
487            let _ = self.app.enter(processid, |app, kernel_data| {
488                // Validate the received data, because ordinary BLE packets can be bigger than 39
489                // bytes. Thus, we need to check for that!
490                // Moreover, we use the packet header to find size but the radio reads maximum
491                // 39 bytes.
492                // Therefore, we ignore payloads with a header size bigger than 39 because the
493                // channels 37, 38 and 39 should only be used for advertisements!
494                // Packets that are bigger than 39 bytes are likely `Channel PDUs` which should
495                // only be sent on the other 37 RadioChannel channels.
496
497                if len <= PACKET_LENGTH as u8 && result == Ok(()) {
498                    // write to buffer in userland
499
500                    let success = kernel_data
501                        .get_readwrite_processbuffer(rw_allow::SCAN_BUFFER)
502                        .and_then(|scan_buffer| {
503                            scan_buffer.mut_enter(|userland| {
504                                userland[0..len as usize]
505                                    .copy_from_slice_or_err(&buf[0..len as usize])
506                                    .is_ok()
507                            })
508                        })
509                        .unwrap_or(false);
510
511                    if success {
512                        let _ = kernel_data.schedule_upcall(
513                            0,
514                            (kernel::errorcode::into_statuscode(result), len as usize, 0),
515                        );
516                    }
517                }
518
519                match app.process_status {
520                    Some(BLEState::Scanning(RadioChannel::AdvertisingChannel37)) => {
521                        app.process_status =
522                            Some(BLEState::Scanning(RadioChannel::AdvertisingChannel38));
523                        self.receiving_app.set(processid);
524                        let _ = self.radio.set_tx_power(app.tx_power);
525                        self.radio
526                            .receive_advertisement(RadioChannel::AdvertisingChannel38);
527                    }
528                    Some(BLEState::Scanning(RadioChannel::AdvertisingChannel38)) => {
529                        app.process_status =
530                            Some(BLEState::Scanning(RadioChannel::AdvertisingChannel39));
531                        self.receiving_app.set(processid);
532                        self.radio
533                            .receive_advertisement(RadioChannel::AdvertisingChannel39);
534                    }
535                    Some(BLEState::Scanning(RadioChannel::AdvertisingChannel39)) => {
536                        self.busy.set(false);
537                        app.process_status = Some(BLEState::ScanningIdle);
538                        app.set_next_alarm::<A::Frequency>(self.alarm.now().into_u32());
539                    }
540                    // Invalid state => don't care
541                    _ => (),
542                }
543            });
544            self.reset_active_alarm();
545        });
546    }
547}
548
549// Callback from the radio once a TX event occur
550impl<'a, B, A> ble_advertising::TxClient for BLE<'a, B, A>
551where
552    B: ble_advertising::BleAdvertisementDriver<'a> + ble_advertising::BleConfig,
553    A: kernel::hil::time::Alarm<'a>,
554{
555    // The Result<(), ErrorCode> indicates valid CRC or not, not used yet but could be used for
556    // re-transmissions for invalid CRCs
557    fn transmit_event(&self, buf: &'static mut [u8], _crc_ok: Result<(), ErrorCode>) {
558        self.kernel_tx.replace(buf);
559        self.sending_app.map(|processid| {
560            let _ = self.app.enter(processid, |app, kernel_data| {
561                match app.process_status {
562                    Some(BLEState::Advertising(RadioChannel::AdvertisingChannel37)) => {
563                        app.process_status =
564                            Some(BLEState::Advertising(RadioChannel::AdvertisingChannel38));
565                        self.sending_app.set(processid);
566                        let _ = self.radio.set_tx_power(app.tx_power);
567                        let _ = app.send_advertisement(
568                            processid,
569                            kernel_data,
570                            self,
571                            RadioChannel::AdvertisingChannel38,
572                        );
573                    }
574
575                    Some(BLEState::Advertising(RadioChannel::AdvertisingChannel38)) => {
576                        app.process_status =
577                            Some(BLEState::Advertising(RadioChannel::AdvertisingChannel39));
578                        self.sending_app.set(processid);
579                        let _ = app.send_advertisement(
580                            processid,
581                            kernel_data,
582                            self,
583                            RadioChannel::AdvertisingChannel39,
584                        );
585                    }
586
587                    Some(BLEState::Advertising(RadioChannel::AdvertisingChannel39)) => {
588                        self.busy.set(false);
589                        app.process_status = Some(BLEState::AdvertisingIdle);
590                        app.set_next_alarm::<A::Frequency>(self.alarm.now().into_u32());
591                    }
592                    // Invalid state => don't care
593                    _ => (),
594                }
595            });
596            self.reset_active_alarm();
597        });
598    }
599}
600
601// System Call implementation
602impl<'a, B, A> SyscallDriver for BLE<'a, B, A>
603where
604    B: ble_advertising::BleAdvertisementDriver<'a> + ble_advertising::BleConfig,
605    A: kernel::hil::time::Alarm<'a>,
606{
607    fn command(
608        &self,
609        command_num: usize,
610        data: usize,
611        interval: usize,
612        processid: kernel::ProcessId,
613    ) -> CommandReturn {
614        match command_num {
615            // Start periodic advertisements
616            0 => {
617                self.app
618                    .enter(processid, |app, _| {
619                        if let Some(BLEState::Idle) = app.process_status {
620                            let pdu_type = data as AdvPduType;
621                            match pdu_type {
622                                ADV_IND | ADV_NONCONN_IND | ADV_SCAN_IND => {
623                                    app.pdu_type = pdu_type;
624                                    app.process_status = Some(BLEState::AdvertisingIdle);
625                                    app.random_nonce = self.alarm.now().into_u32();
626                                    app.advertisement_interval_ms = cmp::max(20, interval as u32);
627                                    app.set_next_alarm::<A::Frequency>(self.alarm.now().into_u32());
628                                    Ok(())
629                                }
630                                _ => Err(ErrorCode::INVAL),
631                            }
632                        } else {
633                            Err(ErrorCode::BUSY)
634                        }
635                    })
636                    .map_or_else(
637                        |err| CommandReturn::failure(err.into()),
638                        |res| match res {
639                            Ok(()) => {
640                                // must be called outside closure passed to grant region!
641                                self.reset_active_alarm();
642                                CommandReturn::success()
643                            }
644                            Err(e) => CommandReturn::failure(e),
645                        },
646                    )
647            }
648
649            // Stop periodic advertisements or passive scanning
650            1 => self
651                .app
652                .enter(processid, |app, _| match app.process_status {
653                    Some(BLEState::AdvertisingIdle) | Some(BLEState::ScanningIdle) => {
654                        app.process_status = Some(BLEState::Idle);
655                        CommandReturn::success()
656                    }
657                    _ => CommandReturn::failure(ErrorCode::BUSY),
658                })
659                .unwrap_or_else(|err| err.into()),
660
661            // Configure transmitted power
662            // BLUETOOTH SPECIFICATION Version 4.2 [Vol 6, Part A], section 3
663            //
664            // Minimum Output Power:    0.01 mW (-20 dBm)
665            // Maximum Output Power:    10 mW (+10 dBm)
666            //
667            // data - Transmitting power in dBm
668            2 => {
669                self.app
670                    .enter(processid, |app, _| {
671                        if app.process_status != Some(BLEState::ScanningIdle)
672                            && app.process_status != Some(BLEState::AdvertisingIdle)
673                        {
674                            match data as u8 {
675                                tx_power @ 0..=10 | tx_power @ 0xec..=0xff => {
676                                    // query the underlying chip if the power level is supported
677                                    let status = self.radio.set_tx_power(tx_power);
678                                    if let Ok(()) = status {
679                                        app.tx_power = tx_power;
680                                    }
681                                    status.into()
682                                }
683                                _ => CommandReturn::failure(ErrorCode::INVAL),
684                            }
685                        } else {
686                            CommandReturn::failure(ErrorCode::BUSY)
687                        }
688                    })
689                    .unwrap_or_else(|err| err.into())
690            }
691
692            // Passive scanning mode
693            5 => {
694                self.app
695                    .enter(processid, |app, _| {
696                        if let Some(BLEState::Idle) = app.process_status {
697                            app.process_status = Some(BLEState::ScanningIdle);
698                            app.set_next_alarm::<A::Frequency>(self.alarm.now().into_u32());
699                            Ok(())
700                        } else {
701                            Err(ErrorCode::BUSY)
702                        }
703                    })
704                    .map_or_else(
705                        |err| err.into(),
706                        |res| match res {
707                            Ok(()) => {
708                                // must be called outside closure passed to grant region!
709                                self.reset_active_alarm();
710                                CommandReturn::success()
711                            }
712                            Err(e) => CommandReturn::failure(e),
713                        },
714                    )
715            }
716
717            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
718        }
719    }
720
721    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
722        self.app.enter(processid, |_, _| {})
723    }
724}