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                        kernel_data
513                            .schedule_upcall(
514                                0,
515                                (kernel::errorcode::into_statuscode(result), len as usize, 0),
516                            )
517                            .ok();
518                    }
519                }
520
521                match app.process_status {
522                    Some(BLEState::Scanning(RadioChannel::AdvertisingChannel37)) => {
523                        app.process_status =
524                            Some(BLEState::Scanning(RadioChannel::AdvertisingChannel38));
525                        self.receiving_app.set(processid);
526                        let _ = self.radio.set_tx_power(app.tx_power);
527                        self.radio
528                            .receive_advertisement(RadioChannel::AdvertisingChannel38);
529                    }
530                    Some(BLEState::Scanning(RadioChannel::AdvertisingChannel38)) => {
531                        app.process_status =
532                            Some(BLEState::Scanning(RadioChannel::AdvertisingChannel39));
533                        self.receiving_app.set(processid);
534                        self.radio
535                            .receive_advertisement(RadioChannel::AdvertisingChannel39);
536                    }
537                    Some(BLEState::Scanning(RadioChannel::AdvertisingChannel39)) => {
538                        self.busy.set(false);
539                        app.process_status = Some(BLEState::ScanningIdle);
540                        app.set_next_alarm::<A::Frequency>(self.alarm.now().into_u32());
541                    }
542                    // Invalid state => don't care
543                    _ => (),
544                }
545            });
546            self.reset_active_alarm();
547        });
548    }
549}
550
551// Callback from the radio once a TX event occur
552impl<'a, B, A> ble_advertising::TxClient for BLE<'a, B, A>
553where
554    B: ble_advertising::BleAdvertisementDriver<'a> + ble_advertising::BleConfig,
555    A: kernel::hil::time::Alarm<'a>,
556{
557    // The Result<(), ErrorCode> indicates valid CRC or not, not used yet but could be used for
558    // re-transmissions for invalid CRCs
559    fn transmit_event(&self, buf: &'static mut [u8], _crc_ok: Result<(), ErrorCode>) {
560        self.kernel_tx.replace(buf);
561        self.sending_app.map(|processid| {
562            let _ = self.app.enter(processid, |app, kernel_data| {
563                match app.process_status {
564                    Some(BLEState::Advertising(RadioChannel::AdvertisingChannel37)) => {
565                        app.process_status =
566                            Some(BLEState::Advertising(RadioChannel::AdvertisingChannel38));
567                        self.sending_app.set(processid);
568                        let _ = self.radio.set_tx_power(app.tx_power);
569                        let _ = app.send_advertisement(
570                            processid,
571                            kernel_data,
572                            self,
573                            RadioChannel::AdvertisingChannel38,
574                        );
575                    }
576
577                    Some(BLEState::Advertising(RadioChannel::AdvertisingChannel38)) => {
578                        app.process_status =
579                            Some(BLEState::Advertising(RadioChannel::AdvertisingChannel39));
580                        self.sending_app.set(processid);
581                        let _ = app.send_advertisement(
582                            processid,
583                            kernel_data,
584                            self,
585                            RadioChannel::AdvertisingChannel39,
586                        );
587                    }
588
589                    Some(BLEState::Advertising(RadioChannel::AdvertisingChannel39)) => {
590                        self.busy.set(false);
591                        app.process_status = Some(BLEState::AdvertisingIdle);
592                        app.set_next_alarm::<A::Frequency>(self.alarm.now().into_u32());
593                    }
594                    // Invalid state => don't care
595                    _ => (),
596                }
597            });
598            self.reset_active_alarm();
599        });
600    }
601}
602
603// System Call implementation
604impl<'a, B, A> SyscallDriver for BLE<'a, B, A>
605where
606    B: ble_advertising::BleAdvertisementDriver<'a> + ble_advertising::BleConfig,
607    A: kernel::hil::time::Alarm<'a>,
608{
609    fn command(
610        &self,
611        command_num: usize,
612        data: usize,
613        interval: usize,
614        processid: kernel::ProcessId,
615    ) -> CommandReturn {
616        match command_num {
617            // Start periodic advertisements
618            0 => {
619                self.app
620                    .enter(processid, |app, _| {
621                        if let Some(BLEState::Idle) = app.process_status {
622                            let pdu_type = data as AdvPduType;
623                            match pdu_type {
624                                ADV_IND | ADV_NONCONN_IND | ADV_SCAN_IND => {
625                                    app.pdu_type = pdu_type;
626                                    app.process_status = Some(BLEState::AdvertisingIdle);
627                                    app.random_nonce = self.alarm.now().into_u32();
628                                    app.advertisement_interval_ms = cmp::max(20, interval as u32);
629                                    app.set_next_alarm::<A::Frequency>(self.alarm.now().into_u32());
630                                    Ok(())
631                                }
632                                _ => Err(ErrorCode::INVAL),
633                            }
634                        } else {
635                            Err(ErrorCode::BUSY)
636                        }
637                    })
638                    .map_or_else(
639                        |err| CommandReturn::failure(err.into()),
640                        |res| match res {
641                            Ok(()) => {
642                                // must be called outside closure passed to grant region!
643                                self.reset_active_alarm();
644                                CommandReturn::success()
645                            }
646                            Err(e) => CommandReturn::failure(e),
647                        },
648                    )
649            }
650
651            // Stop periodic advertisements or passive scanning
652            1 => self
653                .app
654                .enter(processid, |app, _| match app.process_status {
655                    Some(BLEState::AdvertisingIdle) | Some(BLEState::ScanningIdle) => {
656                        app.process_status = Some(BLEState::Idle);
657                        CommandReturn::success()
658                    }
659                    _ => CommandReturn::failure(ErrorCode::BUSY),
660                })
661                .unwrap_or_else(|err| err.into()),
662
663            // Configure transmitted power
664            // BLUETOOTH SPECIFICATION Version 4.2 [Vol 6, Part A], section 3
665            //
666            // Minimum Output Power:    0.01 mW (-20 dBm)
667            // Maximum Output Power:    10 mW (+10 dBm)
668            //
669            // data - Transmitting power in dBm
670            2 => {
671                self.app
672                    .enter(processid, |app, _| {
673                        if app.process_status != Some(BLEState::ScanningIdle)
674                            && app.process_status != Some(BLEState::AdvertisingIdle)
675                        {
676                            match data as u8 {
677                                tx_power @ 0..=10 | tx_power @ 0xec..=0xff => {
678                                    // query the underlying chip if the power level is supported
679                                    let status = self.radio.set_tx_power(tx_power);
680                                    if let Ok(()) = status {
681                                        app.tx_power = tx_power;
682                                    }
683                                    status.into()
684                                }
685                                _ => CommandReturn::failure(ErrorCode::INVAL),
686                            }
687                        } else {
688                            CommandReturn::failure(ErrorCode::BUSY)
689                        }
690                    })
691                    .unwrap_or_else(|err| err.into())
692            }
693
694            // Passive scanning mode
695            5 => {
696                self.app
697                    .enter(processid, |app, _| {
698                        if let Some(BLEState::Idle) = app.process_status {
699                            app.process_status = Some(BLEState::ScanningIdle);
700                            app.set_next_alarm::<A::Frequency>(self.alarm.now().into_u32());
701                            Ok(())
702                        } else {
703                            Err(ErrorCode::BUSY)
704                        }
705                    })
706                    .map_or_else(
707                        |err| err.into(),
708                        |res| match res {
709                            Ok(()) => {
710                                // must be called outside closure passed to grant region!
711                                self.reset_active_alarm();
712                                CommandReturn::success()
713                            }
714                            Err(e) => CommandReturn::failure(e),
715                        },
716                    )
717            }
718
719            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
720        }
721    }
722
723    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
724        self.app.enter(processid, |_, _| {})
725    }
726}