Skip to main content

esp_radio/ble/
mod.rs

1//! Bluetooth Low Energy HCI interface
2
3#[cfg(bt_controller = "btdm")]
4pub(crate) mod btdm;
5
6#[cfg(bt_controller = "npl")]
7pub(crate) mod npl;
8#[cfg(bt_controller = "npl")]
9mod os_mempool;
10
11use alloc::{boxed::Box, collections::vec_deque::VecDeque};
12use core::mem::MaybeUninit;
13
14pub(crate) use ble::{ble_deinit, ble_init, send_hci, send_hci_async};
15use docsplay::Display;
16use esp_sync::NonReentrantMutex;
17
18/// An error that is returned when the configuration is invalid.
19#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, Hash)]
20#[cfg_attr(feature = "defmt", derive(defmt::Format))]
21#[non_exhaustive]
22pub struct InvalidConfigError;
23
24impl core::error::Error for InvalidConfigError {}
25
26// Expose chip-specific configuration types
27pub use ble::ble_os_adapter_chip_specific::*;
28
29#[cfg(bt_controller = "btdm")]
30use self::btdm as ble;
31#[cfg(bt_controller = "npl")]
32use self::npl as ble;
33
34unstable_module! {
35    pub mod controller;
36}
37
38pub(crate) unsafe extern "C" fn malloc(size: u32) -> *mut crate::sys::c_types::c_void {
39    unsafe { crate::compat::malloc::malloc(size as usize).cast() }
40}
41
42#[cfg(any(esp32, esp32c3, esp32s3))]
43pub(crate) unsafe extern "C" fn malloc_internal(size: u32) -> *mut crate::sys::c_types::c_void {
44    unsafe { crate::compat::malloc::malloc_internal(size as usize).cast() }
45}
46
47#[cfg(any(esp32c3, esp32s3))]
48pub(crate) unsafe extern "C" fn malloc_retention(size: u32) -> *mut crate::sys::c_types::c_void {
49    // IDF uses heap_caps_malloc(size, MALLOC_CAP_RETENTION). We have no retention
50    // heap, so fall back to the same internal allocator as malloc_internal.
51    unsafe { crate::compat::malloc::malloc_internal(size as usize).cast() }
52}
53
54pub(crate) unsafe extern "C" fn free(ptr: *mut crate::sys::c_types::c_void) {
55    unsafe { crate::compat::malloc::free(ptr.cast()) }
56}
57
58struct BleState {
59    pub rx_queue: VecDeque<ReceivedPacket>,
60    /// The packet that the byte-stream reader is part-way through, and the number of bytes the
61    /// host already took from it.
62    pub partial_read: Option<(Box<[u8]>, usize)>,
63}
64
65static BT_STATE: NonReentrantMutex<BleState> = NonReentrantMutex::new(BleState {
66    rx_queue: VecDeque::new(),
67    partial_read: None,
68});
69
70static mut HCI_OUT_COLLECTOR: MaybeUninit<HciOutCollector> = MaybeUninit::uninit();
71
72#[derive(PartialEq, Debug)]
73enum HciOutType {
74    Unknown,
75    Acl,
76    Command,
77}
78
79/// The largest HCI packet, including the packet type indicator byte.
80const MAX_HCI_PACKET_LEN: usize = 259;
81
82/// Reassembles whole HCI packets out of the byte stream that the host writes.
83///
84/// The byte-stream write APIs put no constraint on where the caller splits a packet, and one
85/// write can hold several packets. The collector takes only the bytes that the packet in progress
86/// still needs, so that it never runs past a packet boundary.
87struct HciOutCollector {
88    data: [u8; MAX_HCI_PACKET_LEN],
89    index: usize,
90    ready: bool,
91    kind: HciOutType,
92}
93
94impl HciOutCollector {
95    fn new() -> HciOutCollector {
96        HciOutCollector {
97            data: [0u8; MAX_HCI_PACKET_LEN],
98            index: 0,
99            ready: false,
100            kind: HciOutType::Unknown,
101        }
102    }
103
104    fn is_ready(&self) -> bool {
105        self.ready
106    }
107
108    /// The length of the header, including the packet type indicator byte.
109    ///
110    /// The kind is unknown until the indicator byte arrives, so ask for that byte on its own
111    /// first.
112    fn header_len(&self) -> usize {
113        match self.kind {
114            HciOutType::Unknown => 1,
115            HciOutType::Command => 4,
116            HciOutType::Acl => 5,
117        }
118    }
119
120    /// The length of the packet in progress, or `None` while its header is incomplete.
121    fn packet_len(&self) -> Option<usize> {
122        if self.index < self.header_len() {
123            return None;
124        }
125
126        match self.kind {
127            HciOutType::Unknown => None,
128            HciOutType::Command => Some(self.data[3] as usize + 4),
129            HciOutType::Acl => Some(u16::from_le_bytes([self.data[3], self.data[4]]) as usize + 5),
130        }
131    }
132
133    /// Copies bytes from `data` until the packet buffer holds `upto` bytes.
134    ///
135    /// Returns the number of bytes copied.
136    fn fill_to(&mut self, data: &[u8], upto: usize) -> usize {
137        let take = usize::min(data.len(), upto - self.index);
138        self.data[self.index..][..take].copy_from_slice(&data[..take]);
139        self.index += take;
140        take
141    }
142
143    /// Copies as much of `data` as the packet in progress needs, and returns how much it took.
144    ///
145    /// Bytes that belong to the next packet stay in `data`. The caller must send and reset the
146    /// collector once [`Self::is_ready`] holds, before it offers those bytes again.
147    fn push(&mut self, data: &[u8]) -> usize {
148        if data.is_empty() {
149            return 0;
150        }
151
152        if self.index == 0 {
153            self.kind = match data[0] {
154                1 => HciOutType::Command,
155                2 => HciOutType::Acl,
156                indicator => {
157                    warn!(
158                        "Dropping HCI byte with unknown packet type indicator {}",
159                        indicator
160                    );
161                    return 1;
162                }
163            };
164        }
165
166        // The packet length lives in the header, so complete the header before asking for the
167        // rest of the packet.
168        let mut taken = 0;
169        if self.packet_len().is_none() {
170            taken += self.fill_to(data, self.header_len());
171        }
172
173        if let Some(total) = self.packet_len() {
174            if total > self.data.len() {
175                warn!("Dropping HCI packet of {} bytes, which is too long", total);
176                self.reset();
177                return taken;
178            }
179
180            taken += self.fill_to(&data[taken..], total);
181            self.ready = self.index == total;
182        }
183
184        taken
185    }
186
187    fn reset(&mut self) {
188        self.index = 0;
189        self.ready = false;
190        self.kind = HciOutType::Unknown;
191    }
192
193    fn packet(&self) -> &[u8] {
194        &self.data[0..self.index]
195    }
196}
197
198/// Collects bytes of the host's stream, and passes the packet to `send` once it is complete.
199///
200/// This behaves like a byte-stream write: it handles at most one packet, and returns the number
201/// of bytes it took from `data`. Bytes that belong to the next packet stay in `data`, so the
202/// caller offers the rest in a later call. A non-empty `data` always yields a non-zero count.
203pub(crate) fn collect_and_send(data: &[u8], send: impl FnOnce(&[u8])) -> usize {
204    let hci_out = unsafe { (*core::ptr::addr_of_mut!(HCI_OUT_COLLECTOR)).assume_init_mut() };
205
206    let taken = hci_out.push(data);
207
208    if hci_out.is_ready() {
209        send(hci_out.packet());
210        hci_out.reset();
211    }
212
213    taken
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, Hash)]
217/// Represents a received BLE packet.
218#[instability::unstable]
219pub struct ReceivedPacket {
220    /// The data of the received packet.
221    pub data: Box<[u8]>,
222}
223
224#[cfg(feature = "defmt")]
225impl defmt::Format for ReceivedPacket {
226    fn format(&self, fmt: defmt::Formatter<'_>) {
227        defmt::write!(fmt, "ReceivedPacket {}", &self.data[..])
228    }
229}
230
231/// Drops packets the host never read, so they don't outlive the controller.
232pub(crate) fn clear_bt_state() {
233    BT_STATE.with(|state| {
234        state.rx_queue.clear();
235        state.partial_read = None;
236    });
237}
238
239/// Checks if there is any HCI data available to read.
240#[instability::unstable]
241pub fn have_hci_read_data() -> bool {
242    BT_STATE.with(|state| !state.rx_queue.is_empty() || state.partial_read.is_some())
243}
244
245/// Checks if the receive queue holds a complete packet.
246pub(crate) fn have_hci_packet() -> bool {
247    BT_STATE.with(|state| !state.rx_queue.is_empty())
248}
249
250/// Removes the next packet from the receive queue, without copying it.
251pub(crate) fn take_next() -> Option<Box<[u8]>> {
252    BT_STATE.with(|state| state.rx_queue.pop_front().map(|packet| packet.data))
253}
254
255pub(crate) fn read_next(data: &mut [u8]) -> usize {
256    if let Some(packet) = take_next() {
257        data[..packet.len()].copy_from_slice(&packet);
258        packet.len()
259    } else {
260        0
261    }
262}
263
264/// Reads the next HCI packet from the BLE controller.
265#[instability::unstable]
266pub fn read_hci(data: &mut [u8]) -> usize {
267    BT_STATE.with(|state| {
268        if state.partial_read.is_none()
269            && let Some(packet) = state.rx_queue.pop_front()
270        {
271            state.partial_read = Some((packet.data, 0));
272        }
273
274        let Some((packet, read)) = state.partial_read.as_mut() else {
275            return 0;
276        };
277
278        let remaining = &packet[*read..];
279        let l = usize::min(remaining.len(), data.len());
280        data[..l].copy_from_slice(&remaining[..l]);
281        *read += l;
282
283        let drained = *read == packet.len();
284        if drained {
285            state.partial_read = None;
286        }
287
288        l
289    })
290}
291
292fn dump_packet_info(_buffer: &[u8]) {
293    #[cfg(dump_packets)]
294    info!("@HCIFRAME {:?}", _buffer);
295}
296
297macro_rules! validate_range {
298    ($this:ident, $field:ident, $min:expr, $max:expr) => {
299        if !($min..=$max).contains(&$this.$field) {
300            error!(
301                "{} must be between {} and {}, current value is {}",
302                stringify!($field),
303                $min,
304                $max,
305                $this.$field
306            );
307            return Err(InvalidConfigError);
308        }
309    };
310}
311pub(crate) use validate_range;