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}