Skip to content
File

Blob: firmware/crates/esp32-radio/src/platform/mod.rs

rust109 lines
1//! Safe application-facing boundary around ESP-IDF and its Rust HAL.
2//! Native handles never escape.
3//! This module exposes no application callbacks to C and adds no unsafe
4//! Send/Sync implementations.
5mod audio;
6mod certificate;
7mod crypto;
8#[cfg(feature = "crypto-profile")]
9mod crypto_profile;
10mod ffi;
11mod http;
12mod metrics;
13mod music;
14mod task;
15 
16use crate::error::{Error, Result, check};
17pub(crate) use audio::{Dsp, History};
18pub(crate) use certificate::{certificate, ipv4};
19pub(crate) use crypto::provider as crypto_provider;
20#[cfg(feature = "crypto-profile")]
21pub(crate) use crypto_profile::CryptoProfile;
22pub(crate) use http::Http;
23pub(crate) use metrics::Metrics;
24pub(crate) use music::MusicStorage;
25use radio_core::protocol::Color;
26use std::{
27 cell::Cell,
28 ffi::CString,
29 marker::PhantomData,
30 sync::atomic::{AtomicBool, Ordering},
31};
32pub(crate) use task::SpawnConfig;
33 
34static BOARD_TAKEN: AtomicBool = AtomicBool::new(false);
35 
36/// Unique LED owner; moved to the radio thread after initialization, never shared.
37#[derive(Debug)]
38pub(crate) struct Board {
39 _not_sync: PhantomData<Cell<()>>,
40}
41 
42impl Board {
43 pub(crate) fn init() -> Result<Self> {
44 if BOARD_TAKEN.swap(true, Ordering::AcqRel) {
45 return Err(Error::new("board already initialized"));
46 }
47 #[cfg(target_os = "espidf")]
48 esp_idf_hal::sys::link_patches();
49 // SAFETY: the atomic guard permits exactly one initialization; C owns all
50 // driver state. It registers only C callbacks and borrows no Rust memory.
51 let code = unsafe { ffi::radio_board_init() };
52 check(code, "board initialization failed")?;
53 loop {
54 // SAFETY: Board initialization is the sole SNTP initializer, before
55 // tasks needing certificate timestamps or verified HTTPS are spawned.
56 match unsafe { ffi::radio_clock_sync() } {
57 1 => break,
58 -1 => return Err(Error::new("clock initialization failed")),
59 _ => log("Waiting for time synchronization"),
60 }
61 }
62 Ok(Self {
63 _not_sync: PhantomData,
64 })
65 }
66 pub(crate) fn set_led(&mut self, Color([r, g, b]): Color) -> Result<()> {
67 // SAFETY: a Board exists only after initialization; &mut self serializes
68 // LED calls and this sole owner is not Sync. Components fit the C ABI.
69 check(unsafe { ffi::radio_led(r, g, b) }, "LED write failed")
70 }
71}
72 
73pub(crate) fn now_us() -> u64 {
74 // SAFETY: thread-safe IDF monotonic timer; no borrowed state or pointers.
75 unsafe { ffi::radio_now_us() }
76}
77pub(crate) fn random() -> u32 {
78 // SAFETY: thread-safe IDF random API; no borrowed state or retained pointers.
79 unsafe { ffi::radio_random() }
80}
81pub(crate) fn heap_free() -> u32 {
82 // SAFETY: IDF serializes heap inspection internally; scalar result only.
83 unsafe { ffi::radio_heap_free() }
84}
85pub(crate) fn stack_free() -> u32 {
86 // SAFETY: query of the calling FreeRTOS task, with no external pointers.
87 unsafe { ffi::radio_stack_free() }
88}
89pub(crate) fn console_byte() -> i32 {
90 // SAFETY: called only by app_main's console loop; C stdio owns its buffer.
91 unsafe { ffi::radio_console_byte() }
92}
93pub(crate) fn log(message: &str) {
94 if let Ok(message) = CString::new(message) {
95 // SAFETY: valid terminated string remains live until synchronous logging
96 // returns. C uses "%s", so text cannot become a format string.
97 unsafe { ffi::radio_log(message.as_ptr()) };
98 }
99}
100pub(crate) fn recovery_attempt(reset: bool) -> u32 {
101 // SAFETY: called only by signaling (or fatal recovery which never returns).
102 // C retains the bounded scalar counter across software resets.
103 unsafe { ffi::radio_recovery_attempt(i32::from(reset)) }
104}
105pub(crate) fn restart() -> ! {
106 // SAFETY: IDF reset does not return or unwind and takes no borrowed memory.
107 unsafe { ffi::radio_restart() }
108}