|
| 1 | +//! Safe wrappers around a subset of the BLE system interface exposed by the |
| 2 | +//! STM32 WBA wireless stack. |
| 3 | +//! |
| 4 | +//! The goal of this module is to minimise the amount of ad-hoc `unsafe` code |
| 5 | +//! required by higher layers while still providing zero-cost access to the |
| 6 | +//! underlying C APIs. Only a carefully-curated portion of the enormous BLE |
| 7 | +//! surface is exposed here; additional helpers can be added incrementally as |
| 8 | +//! the need arises. |
| 9 | +
|
| 10 | +use core::marker::PhantomData; |
| 11 | +use core::ptr::{self, NonNull}; |
| 12 | +use core::slice; |
| 13 | + |
| 14 | +use crate::ffi; |
| 15 | + |
| 16 | +/// Result type returned by helpers that wrap `ble_stat_t`-style status values. |
| 17 | +pub type BleResult<T = ()> = Result<T, BleStatus>; |
| 18 | + |
| 19 | +/// Commonly observed BLE controller status codes. |
| 20 | +/// |
| 21 | +/// The values are derived from the constants defined in `ble_defs.h`. Any |
| 22 | +/// status that is not modelled explicitly will be surfaced through |
| 23 | +/// [`BleStatus::Other`]. |
| 24 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 25 | +pub enum BleStatus { |
| 26 | + Success, |
| 27 | + Busy, |
| 28 | + Pending, |
| 29 | + InvalidParameters, |
| 30 | + InsufficientResources, |
| 31 | + OutOfMemory, |
| 32 | + Timeout, |
| 33 | + Other(u8), |
| 34 | +} |
| 35 | + |
| 36 | +impl BleStatus { |
| 37 | + /// Convert a raw `ble_stat_t` (as returned by the vendor APIs) into the |
| 38 | + /// strongly-typed status representation. |
| 39 | + #[inline] |
| 40 | + pub fn from_raw(value: ffi::ble_stat_t) -> Self { |
| 41 | + match (value & 0xFF) as u8 { |
| 42 | + 0x00 => Self::Success, |
| 43 | + 0x93 => Self::Busy, |
| 44 | + 0x95 => Self::Pending, |
| 45 | + 0x92 => Self::InvalidParameters, |
| 46 | + 0x64 => Self::InsufficientResources, |
| 47 | + 0x98 => Self::OutOfMemory, |
| 48 | + 0xFF => Self::Timeout, |
| 49 | + other => Self::Other(other), |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + /// Returns the raw 8-bit status code. |
| 54 | + #[inline] |
| 55 | + pub const fn code(self) -> u8 { |
| 56 | + match self { |
| 57 | + Self::Success => 0x00, |
| 58 | + Self::Busy => 0x93, |
| 59 | + Self::Pending => 0x95, |
| 60 | + Self::InvalidParameters => 0x92, |
| 61 | + Self::InsufficientResources => 0x64, |
| 62 | + Self::OutOfMemory => 0x98, |
| 63 | + Self::Timeout => 0xFF, |
| 64 | + Self::Other(code) => code, |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + /// Converts the status into a `Result`, treating [`BleStatus::Success`] as |
| 69 | + /// the success case. |
| 70 | + #[inline] |
| 71 | + pub fn into_result(self) -> BleResult<()> { |
| 72 | + match self { |
| 73 | + Self::Success => Ok(()), |
| 74 | + other => Err(other), |
| 75 | + } |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +/// Callback used by the controller to hand HCI buffers back to the host |
| 80 | +/// transport. |
| 81 | +pub type HostCallback = unsafe extern "C" fn(*mut ffi::ble_buff_hdr_t) -> u8; |
| 82 | + |
| 83 | +/// Callback invoked when the controller fails to enqueue a buffer because the |
| 84 | +/// host queue is full. |
| 85 | +pub type HostQueueFullCallback = unsafe extern "C" fn(*mut ffi::ble_buff_hdr_t); |
| 86 | + |
| 87 | +/// Callback signature used for vendor-specific HCI command handling. |
| 88 | +pub type ExternalCustomCallback = unsafe extern "C" fn( |
| 89 | + ocf: u16, |
| 90 | + packet: *mut u8, |
| 91 | + event_packet: *mut u8, |
| 92 | + params_length: *mut u8, |
| 93 | + return_command_type: *mut ffi::hci_return_command_type, |
| 94 | +) -> ffi::ble_stat_t; |
| 95 | + |
| 96 | +pub type DispatchTable = ffi::hci_dispatch_tbl; |
| 97 | + |
| 98 | +/// RAII wrapper around the vendor-provided `ble_buff_hdr_t` buffers. |
| 99 | +/// |
| 100 | +/// Buffers are automatically returned to the controller when dropped. |
| 101 | +pub struct BleBuffer { |
| 102 | + ptr: NonNull<ffi::ble_buff_hdr_t>, |
| 103 | + _not_send_sync: PhantomData<core::cell::Cell<ffi::ble_buff_hdr_t>>, |
| 104 | +} |
| 105 | + |
| 106 | +impl BleBuffer { |
| 107 | + /// Attempts to allocate a fresh buffer from the controller. |
| 108 | + pub fn allocate() -> Option<Self> { |
| 109 | + let ptr = unsafe { ffi::hci_alloc_msg() }; |
| 110 | + NonNull::new(ptr).map(|ptr| Self { |
| 111 | + ptr, |
| 112 | + _not_send_sync: PhantomData, |
| 113 | + }) |
| 114 | + } |
| 115 | + |
| 116 | + /// Wrap an existing raw pointer returned by the vendor middleware. |
| 117 | + /// |
| 118 | + /// # Safety |
| 119 | + /// |
| 120 | + /// The caller must ensure that `ptr` is either null (in which case `None` |
| 121 | + /// is returned) or a valid buffer obtained from the controller and that it |
| 122 | + /// remains valid for the lifetime of the returned wrapper. |
| 123 | + pub unsafe fn from_raw(ptr: *mut ffi::ble_buff_hdr_t) -> Option<Self> { |
| 124 | + NonNull::new(ptr).map(|ptr| Self { |
| 125 | + ptr, |
| 126 | + _not_send_sync: PhantomData, |
| 127 | + }) |
| 128 | + } |
| 129 | + |
| 130 | + #[inline] |
| 131 | + fn header(&self) -> &ffi::ble_buff_hdr_t { |
| 132 | + unsafe { self.ptr.as_ref() } |
| 133 | + } |
| 134 | + |
| 135 | + #[inline] |
| 136 | + fn header_mut(&mut self) -> &mut ffi::ble_buff_hdr_t { |
| 137 | + unsafe { self.ptr.as_mut() } |
| 138 | + } |
| 139 | + |
| 140 | + #[inline] |
| 141 | + pub fn len(&self) -> usize { |
| 142 | + self.header().data_size as usize |
| 143 | + } |
| 144 | + |
| 145 | + #[inline] |
| 146 | + pub fn is_empty(&self) -> bool { |
| 147 | + self.len() == 0 |
| 148 | + } |
| 149 | + |
| 150 | + #[inline] |
| 151 | + pub fn capacity(&self) -> usize { |
| 152 | + self.header().total_len as usize |
| 153 | + } |
| 154 | + |
| 155 | + #[inline] |
| 156 | + pub fn offset(&self) -> usize { |
| 157 | + self.header().data_offset as usize |
| 158 | + } |
| 159 | + |
| 160 | + #[inline] |
| 161 | + pub fn set_len(&mut self, len: usize) { |
| 162 | + debug_assert!(len <= self.capacity()); |
| 163 | + debug_assert!(len <= u16::MAX as usize); |
| 164 | + self.header_mut().data_size = len as u16; |
| 165 | + } |
| 166 | + |
| 167 | + #[inline] |
| 168 | + pub fn set_offset(&mut self, offset: usize) { |
| 169 | + debug_assert!(offset <= self.capacity()); |
| 170 | + debug_assert!(offset <= u16::MAX as usize); |
| 171 | + self.header_mut().data_offset = offset as u16; |
| 172 | + } |
| 173 | + |
| 174 | + fn payload_parts(&self) -> Option<(NonNull<u8>, usize)> { |
| 175 | + let header = self.header(); |
| 176 | + let len = header.data_size as usize; |
| 177 | + if len == 0 { |
| 178 | + return None; |
| 179 | + } |
| 180 | + let base = NonNull::new(header.buff_start)?; |
| 181 | + let offset = header.data_offset as usize; |
| 182 | + let end = offset.checked_add(len)?; |
| 183 | + if header.total_len != 0 { |
| 184 | + debug_assert!(end <= header.total_len as usize); |
| 185 | + } |
| 186 | + let ptr = unsafe { base.as_ptr().add(offset) }; |
| 187 | + NonNull::new(ptr).map(|ptr| (ptr, len)) |
| 188 | + } |
| 189 | + |
| 190 | + /// Borrow the packet payload as an immutable slice. |
| 191 | + #[inline] |
| 192 | + pub fn payload(&self) -> Option<&[u8]> { |
| 193 | + self.payload_parts() |
| 194 | + .map(|(ptr, len)| unsafe { slice::from_raw_parts(ptr.as_ptr() as *const u8, len) }) |
| 195 | + } |
| 196 | + |
| 197 | + /// Borrow the packet payload as a mutable slice. |
| 198 | + #[inline] |
| 199 | + pub fn payload_mut(&mut self) -> Option<&mut [u8]> { |
| 200 | + self.payload_parts() |
| 201 | + .map(|(ptr, len)| unsafe { slice::from_raw_parts_mut(ptr.as_ptr(), len) }) |
| 202 | + } |
| 203 | + |
| 204 | + /// Overwrite the payload with `data`, adjusting length/offset as needed. |
| 205 | + /// |
| 206 | + /// Returns `Err(())` when the payload does not fit in the buffer. |
| 207 | + pub fn write_payload(&mut self, data: &[u8]) -> Result<(), ()> { |
| 208 | + if data.len() > self.capacity() { |
| 209 | + return Err(()); |
| 210 | + } |
| 211 | + |
| 212 | + self.set_offset(0); |
| 213 | + self.set_len(data.len()); |
| 214 | + |
| 215 | + match self.payload_mut() { |
| 216 | + Some(slot) => { |
| 217 | + slot.copy_from_slice(data); |
| 218 | + Ok(()) |
| 219 | + } |
| 220 | + None => { |
| 221 | + if data.is_empty() { |
| 222 | + Ok(()) |
| 223 | + } else { |
| 224 | + Err(()) |
| 225 | + } |
| 226 | + } |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + /// Returns the raw pointer without relinquishing ownership. |
| 231 | + #[inline] |
| 232 | + pub fn as_ptr(&self) -> *const ffi::ble_buff_hdr_t { |
| 233 | + self.ptr.as_ptr() |
| 234 | + } |
| 235 | + |
| 236 | + /// Returns the raw mutable pointer without relinquishing ownership. |
| 237 | + #[inline] |
| 238 | + pub fn as_mut_ptr(&mut self) -> *mut ffi::ble_buff_hdr_t { |
| 239 | + self.ptr.as_ptr() |
| 240 | + } |
| 241 | + |
| 242 | + /// Consumes the wrapper and returns the raw pointer, preventing the |
| 243 | + /// destructor from freeing it. |
| 244 | + #[inline] |
| 245 | + pub fn into_raw(self) -> *mut ffi::ble_buff_hdr_t { |
| 246 | + let ptr = self.ptr.as_ptr(); |
| 247 | + core::mem::forget(self); |
| 248 | + ptr |
| 249 | + } |
| 250 | +} |
| 251 | + |
| 252 | +impl Drop for BleBuffer { |
| 253 | + fn drop(&mut self) { |
| 254 | + unsafe { ffi::hci_free_msg(self.ptr.as_ptr()) }; |
| 255 | + } |
| 256 | +} |
| 257 | + |
| 258 | +/// Initialise the BLE controller glue logic. |
| 259 | +/// |
| 260 | +/// # Safety |
| 261 | +/// |
| 262 | +/// The supplied callback must adhere to the constraints expected by the |
| 263 | +/// controller (no unwinding across the FFI boundary, ISR safety where |
| 264 | +/// applicable, etc.). |
| 265 | +pub unsafe fn init_controller(callback: HostCallback) { |
| 266 | + unsafe { |
| 267 | + ffi::ll_sys_ble_cntrl_init(Some(callback)); |
| 268 | + } |
| 269 | +} |
| 270 | + |
| 271 | +/// Initialise the controller <-> host transport layer. |
| 272 | +pub fn init_transport(callback: HostCallback) -> BleResult { |
| 273 | + let status = unsafe { ffi::ll_hci_init(Some(callback)) }; |
| 274 | + BleStatus::from_raw(status).into_result() |
| 275 | +} |
| 276 | + |
| 277 | +pub fn init_with_dispatch_table(table: &DispatchTable) -> BleResult { |
| 278 | + let status = unsafe { ffi::ll_intf_init(table as *const _) }; |
| 279 | + BleStatus::from_raw(status).into_result() |
| 280 | +} |
| 281 | + |
| 282 | +pub fn init_default_interface() -> BleResult { |
| 283 | + match dispatch_table() { |
| 284 | + Some(table) => init_with_dispatch_table(table), |
| 285 | + None => Err(BleStatus::InsufficientResources), |
| 286 | + } |
| 287 | +} |
| 288 | + |
| 289 | +pub fn dispatch_table() -> Option<&'static DispatchTable> { |
| 290 | + let mut table: *const DispatchTable = ptr::null(); |
| 291 | + unsafe { |
| 292 | + ffi::hci_get_dis_tbl(&mut table); |
| 293 | + } |
| 294 | + if table.is_null() { |
| 295 | + None |
| 296 | + } else { |
| 297 | + Some(unsafe { &*table }) |
| 298 | + } |
| 299 | +} |
| 300 | + |
| 301 | +pub fn reset_interface() -> BleResult { |
| 302 | + let status = unsafe { ffi::ll_intf_reset() }; |
| 303 | + BleStatus::from_raw(status).into_result() |
| 304 | +} |
| 305 | + |
| 306 | +pub fn reset_system() { |
| 307 | + unsafe { ffi::ll_sys_reset() }; |
| 308 | +} |
| 309 | + |
| 310 | +/// Register an optional callback that is invoked when the host queue is full. |
| 311 | +pub unsafe fn register_queue_full_callback(callback: Option<HostQueueFullCallback>) { |
| 312 | + unsafe { |
| 313 | + ffi::hci_rgstr_hst_cbk_ll_queue_full(callback); |
| 314 | + } |
| 315 | +} |
| 316 | + |
| 317 | +/// Register the primary host callback that receives HCI buffers. |
| 318 | +pub unsafe fn register_host_callback(callback: Option<HostCallback>) { |
| 319 | + unsafe { |
| 320 | + ffi::hci_rgstr_hst_cbk(callback); |
| 321 | + } |
| 322 | +} |
| 323 | + |
| 324 | +/// Register or clear the vendor-specific HCI command handler. |
| 325 | +pub unsafe fn register_external_custom_callback(callback: Option<ExternalCustomCallback>) -> bool { |
| 326 | + unsafe { ffi::hci_rgstr_ble_external_custom_cbk(callback) != 0 } |
| 327 | +} |
| 328 | + |
| 329 | +/// Prepare the vendor event queues. This must be called once before invoking |
| 330 | +/// any of the queue-related helpers. |
| 331 | +pub fn init_event_queues() { |
| 332 | + unsafe { ffi::hci_init_events_queues() }; |
| 333 | +} |
| 334 | + |
| 335 | +/// Enqueue a packet for delivery to the host. |
| 336 | +pub fn queue_send_packet(buffer: BleBuffer) -> BleResult { |
| 337 | + let ptr = buffer.into_raw(); |
| 338 | + let status = BleStatus::from_raw(unsafe { ffi::hci_queue_send_pckt(ptr) as ffi::ble_stat_t }); |
| 339 | + if status == BleStatus::Success { |
| 340 | + Ok(()) |
| 341 | + } else { |
| 342 | + unsafe { ffi::hci_free_msg(ptr) }; |
| 343 | + Err(status) |
| 344 | + } |
| 345 | +} |
| 346 | + |
| 347 | +/// Helper that allocates a buffer and enqueues it after copying `payload` into it. |
| 348 | +/// |
| 349 | +/// Returns [`BleStatus::InsufficientResources`] if a buffer cannot be obtained or |
| 350 | +/// [`BleStatus::InvalidParameters`] when the payload exceeds the buffer capacity. |
| 351 | +pub fn queue_packet(payload: &[u8]) -> BleResult { |
| 352 | + let mut buffer = BleBuffer::allocate().ok_or(BleStatus::InsufficientResources)?; |
| 353 | + buffer |
| 354 | + .write_payload(payload) |
| 355 | + .map_err(|_| BleStatus::InvalidParameters)?; |
| 356 | + queue_send_packet(buffer) |
| 357 | +} |
| 358 | + |
| 359 | +/// Update the LE event mask used by the controller. |
| 360 | +pub fn set_le_event_mask(mask: &mut [u8; 8]) { |
| 361 | + unsafe { ffi::hci_ll_set_le_event_mask(mask.as_mut_ptr()) }; |
| 362 | +} |
| 363 | + |
| 364 | +/// Update the classic/Bluetooth event mask used by the controller. |
| 365 | +pub fn set_event_mask(mask: &mut [u8; 8]) { |
| 366 | + unsafe { ffi::hci_ll_set_event_mask(mask.as_mut_ptr()) }; |
| 367 | +} |
| 368 | + |
| 369 | +/// Update the page 2 event mask used by the controller. |
| 370 | +pub fn set_event_mask_page2(mask: &mut [u8; 8]) { |
| 371 | + unsafe { ffi::hci_ll_set_event_mask_page2(mask.as_mut_ptr()) }; |
| 372 | +} |
| 373 | + |
| 374 | +/// Update the custom event mask used by the controller. |
| 375 | +pub fn set_custom_event_mask(mask: u8) { |
| 376 | + unsafe { ffi::hci_ll_set_custom_event_mask(mask) }; |
| 377 | +} |
0 commit comments