File
Blob: archive/fft-benchmark/firmware/crates/esp32-radio/src/platform/mod.rs
| 1 | //! Safe application-facing boundary around ESP-IDF. Native handles never escape. |
| 2 | //! There are no Rust callbacks from C and no unsafe Send/Sync implementations. |
| 3 | mod ffi; |
| 4 | mod http; |
| 5 | mod music; |
| 6 | mod peer; |
| 7 | |
| 8 | use crate::error::{Error, Result, check}; |
| 9 | pub(crate) use http::{Http, RESPONSE_LIMIT}; |
| 10 | pub(crate) use music::MusicBytes; |
| 11 | pub(crate) use peer::{Peer, PeerState}; |
| 12 | use radio_core::protocol::Color; |
| 13 | use std::{ |
| 14 | cell::Cell, |
| 15 | ffi::CString, |
| 16 | marker::PhantomData, |
| 17 | sync::atomic::{AtomicBool, Ordering}, |
| 18 | }; |
| 19 | |
| 20 | static BOARD_TAKEN: AtomicBool = AtomicBool::new(false); |
| 21 | |
| 22 | /// Unique LED owner; moved to the radio thread after initialization, never shared. |
| 23 | #[derive(Debug)] |
| 24 | pub(crate) struct Board { |
| 25 | _not_sync: PhantomData<Cell<()>>, |
| 26 | } |
| 27 | |
| 28 | impl Board { |
| 29 | pub(crate) fn init() -> Result<Self> { |
| 30 | if BOARD_TAKEN.swap(true, Ordering::AcqRel) { |
| 31 | return Err(Error::new("board already initialized")); |
| 32 | } |
| 33 | // SAFETY: the atomic guard permits exactly one initialization; C owns all |
| 34 | // driver state. It registers only C callbacks and borrows no Rust memory. |
| 35 | let code = unsafe { ffi::radio_board_init() }; |
| 36 | check(code, "board initialization failed")?; |
| 37 | Ok(Self { |
| 38 | _not_sync: PhantomData, |
| 39 | }) |
| 40 | } |
| 41 | pub(crate) fn set_led(&mut self, Color([r, g, b]): Color) -> Result<()> { |
| 42 | // SAFETY: a Board exists only after initialization; &mut self serializes |
| 43 | // LED calls and this sole owner is not Sync. Components fit the C ABI. |
| 44 | check(unsafe { ffi::radio_led(r, g, b) }, "LED write failed") |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | pub(crate) fn now_us() -> u64 { |
| 49 | // SAFETY: thread-safe IDF monotonic timer; no borrowed state or pointers. |
| 50 | unsafe { ffi::radio_now_us() } |
| 51 | } |
| 52 | pub(crate) fn random() -> u32 { |
| 53 | // SAFETY: thread-safe IDF entropy API; Wi-Fi is active for the application. |
| 54 | unsafe { ffi::radio_random() } |
| 55 | } |
| 56 | pub(crate) fn heap_free() -> u32 { |
| 57 | // SAFETY: IDF serializes heap inspection internally; scalar result only. |
| 58 | unsafe { ffi::radio_heap_free() } |
| 59 | } |
| 60 | pub(crate) fn stack_free() -> u32 { |
| 61 | // SAFETY: query of the calling FreeRTOS task, with no external pointers. |
| 62 | unsafe { ffi::radio_stack_free() } |
| 63 | } |
| 64 | pub(crate) fn rssi() -> i32 { |
| 65 | // SAFETY: IDF Wi-Fi API synchronizes access and C initializes its output. |
| 66 | unsafe { ffi::radio_rssi() } |
| 67 | } |
| 68 | pub(crate) fn console_byte() -> i32 { |
| 69 | // SAFETY: called only by app_main's console loop; C stdio owns its buffer. |
| 70 | unsafe { ffi::radio_console_byte() } |
| 71 | } |
| 72 | pub(crate) fn log(message: &str) { |
| 73 | if let Ok(message) = CString::new(message) { |
| 74 | // SAFETY: valid terminated string remains live until synchronous logging |
| 75 | // returns. C uses "%s", so text cannot become a format string. |
| 76 | unsafe { ffi::radio_log(message.as_ptr()) }; |
| 77 | } |
| 78 | } |
| 79 | pub(crate) fn recovery_attempt(reset: bool) -> u32 { |
| 80 | // SAFETY: called only by signaling (or fatal recovery which never returns). |
| 81 | // C retains the bounded scalar counter across software resets. |
| 82 | unsafe { ffi::radio_recovery_attempt(i32::from(reset)) } |
| 83 | } |
| 84 | pub(crate) fn restart() -> ! { |
| 85 | // SAFETY: IDF reset does not return or unwind and takes no borrowed memory. |
| 86 | unsafe { ffi::radio_restart() } |
| 87 | } |