1#![cfg_attr(
2 all(docsrs, not(not_really_docsrs)),
3 doc = "<div style='padding:30px;background:#810;color:#fff;text-align:center;'><p>You might want to <a href='https://docs.espressif.com/projects/rust/'>browse the <code>esp-wifi</code> documentation on the esp-rs website</a> instead.</p><p>The documentation here on <a href='https://docs.rs'>docs.rs</a> is built for a single chip only (ESP32-C3, in particular), while on the esp-rs website you can select your exact chip from the list of supported devices. Available peripherals and their APIs might change depending on the chip.</p></div>\n\n<br/>\n\n"
4)]
5#![cfg_attr(esp32, doc = "**ESP32**")]
7#![cfg_attr(esp32s2, doc = "**ESP32-S2**")]
8#![cfg_attr(esp32s3, doc = "**ESP32-S3**")]
9#![cfg_attr(esp32c2, doc = "**ESP32-C2**")]
10#![cfg_attr(esp32c3, doc = "**ESP32-C3**")]
11#![cfg_attr(esp32c6, doc = "**ESP32-C6**")]
12#![cfg_attr(esp32h2, doc = "**ESP32-H2**")]
13#![doc = concat!(r#"features = [""#, esp_hal::chip!(), r#"", "wifi", "esp-now"]"#)]
29#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
74#![doc = ""]
81#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_wifi_config_table.md"))]
82#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
83#![no_std]
84#![cfg_attr(xtensa, feature(asm_experimental_arch))]
85#![cfg_attr(feature = "sys-logs", feature(c_variadic))]
86#![deny(rust_2018_idioms, rustdoc::all)]
87#![allow(rustdoc::bare_urls)]
88#![allow(unknown_lints)]
91#![allow(non_local_definitions)]
92#![cfg_attr(
93 not(any(feature = "wifi", feature = "ble")),
94 allow(
95 unused,
96 reason = "There are a number of places where code is needed for either wifi or ble,
97 and cfg-ing them out would make the code less readable just to avoid warnings in the
98 less common case. Truly unused code will be flagged by the check that enables either
99 ble or wifi."
100 )
101)]
102
103extern crate alloc;
104
105mod fmt;
107
108use core::marker::PhantomData;
109
110use common_adapter::chip_specific::phy_mem_init;
111use esp_config::*;
112use esp_hal::{self as hal, clock::RadioClockController, peripherals::RADIO_CLK};
113use hal::{
114 Blocking,
115 clock::Clocks,
116 rng::{Rng, Trng},
117 time::Rate,
118 timer::{AnyTimer, PeriodicTimer, timg::Timer as TimgTimer},
119};
120use portable_atomic::Ordering;
121
122#[cfg(feature = "wifi")]
123use crate::wifi::WifiError;
124use crate::{
125 preempt::yield_task,
126 radio::{setup_radio_isr, shutdown_radio_isr},
127 tasks::init_tasks,
128};
129
130mod binary {
131 pub use esp_wifi_sys::*;
132}
133mod compat;
134
135#[cfg(feature = "builtin-scheduler")]
136mod preempt_builtin;
137
138pub mod preempt;
139
140mod radio;
141mod time;
142
143#[cfg(feature = "wifi")]
144pub mod wifi;
145
146#[cfg(feature = "ble")]
147pub mod ble;
148
149#[cfg(feature = "esp-now")]
150pub mod esp_now;
151
152pub mod config;
153
154pub(crate) mod common_adapter;
155
156#[doc(hidden)]
157pub mod tasks;
158
159pub(crate) mod memory_fence;
160
161#[allow(clippy::assertions_on_constants)] const _: () = {
164 cfg_if::cfg_if! {
165 if #[cfg(not(esp32h2))] {
166 core::assert!(binary::include::CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM == 10);
167 core::assert!(binary::include::CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM == 32);
168 core::assert!(binary::include::WIFI_STATIC_TX_BUFFER_NUM == 0);
169 core::assert!(binary::include::CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM == 32);
170 core::assert!(binary::include::CONFIG_ESP_WIFI_AMPDU_RX_ENABLED == 1);
171 core::assert!(binary::include::CONFIG_ESP_WIFI_AMPDU_TX_ENABLED == 1);
172 core::assert!(binary::include::WIFI_AMSDU_TX_ENABLED == 0);
173 core::assert!(binary::include::CONFIG_ESP32_WIFI_RX_BA_WIN == 6);
174 }
175 };
176};
177
178#[derive(Debug)]
179#[cfg_attr(feature = "defmt", derive(defmt::Format))]
180#[allow(unused)] struct Config {
183 rx_queue_size: usize,
184 tx_queue_size: usize,
185 static_rx_buf_num: usize,
186 dynamic_rx_buf_num: usize,
187 static_tx_buf_num: usize,
188 dynamic_tx_buf_num: usize,
189 ampdu_rx_enable: bool,
190 ampdu_tx_enable: bool,
191 amsdu_tx_enable: bool,
192 rx_ba_win: usize,
193 max_burst_size: usize,
194 country_code: &'static str,
195 country_code_operating_class: u8,
196 mtu: usize,
197 tick_rate_hz: u32,
198 listen_interval: u16,
199 beacon_timeout: u16,
200 ap_beacon_timeout: u16,
201 failure_retry_cnt: u8,
202 scan_method: u32,
203}
204
205pub(crate) const CONFIG: config::EspWifiConfig = config::EspWifiConfig {
206 rx_queue_size: esp_config_int!(usize, "ESP_WIFI_CONFIG_RX_QUEUE_SIZE"),
207 tx_queue_size: esp_config_int!(usize, "ESP_WIFI_CONFIG_TX_QUEUE_SIZE"),
208 static_rx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_STATIC_RX_BUF_NUM"),
209 dynamic_rx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_DYNAMIC_RX_BUF_NUM"),
210 static_tx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_STATIC_TX_BUF_NUM"),
211 dynamic_tx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_DYNAMIC_TX_BUF_NUM"),
212 ampdu_rx_enable: esp_config_bool!("ESP_WIFI_CONFIG_AMPDU_RX_ENABLE"),
213 ampdu_tx_enable: esp_config_bool!("ESP_WIFI_CONFIG_AMPDU_TX_ENABLE"),
214 amsdu_tx_enable: esp_config_bool!("ESP_WIFI_CONFIG_AMSDU_TX_ENABLE"),
215 rx_ba_win: esp_config_int!(usize, "ESP_WIFI_CONFIG_RX_BA_WIN"),
216 max_burst_size: esp_config_int!(usize, "ESP_WIFI_CONFIG_MAX_BURST_SIZE"),
217 country_code: esp_config_str!("ESP_WIFI_CONFIG_COUNTRY_CODE"),
218 country_code_operating_class: esp_config_int!(
219 u8,
220 "ESP_WIFI_CONFIG_COUNTRY_CODE_OPERATING_CLASS"
221 ),
222 mtu: esp_config_int!(usize, "ESP_WIFI_CONFIG_MTU"),
223 tick_rate_hz: esp_config_int!(u32, "ESP_WIFI_CONFIG_TICK_RATE_HZ"),
224 listen_interval: esp_config_int!(u16, "ESP_WIFI_CONFIG_LISTEN_INTERVAL"),
225 beacon_timeout: esp_config_int!(u16, "ESP_WIFI_CONFIG_BEACON_TIMEOUT"),
226 ap_beacon_timeout: esp_config_int!(u16, "ESP_WIFI_CONFIG_AP_BEACON_TIMEOUT"),
227 failure_retry_cnt: esp_config_int!(u8, "ESP_WIFI_CONFIG_FAILURE_RETRY_CNT"),
228 scan_method: esp_config_int!(u32, "ESP_WIFI_CONFIG_SCAN_METHOD"),
229};
230
231#[allow(clippy::assertions_on_constants)]
233const _: () = {
234 core::assert!(
237 CONFIG.rx_ba_win < CONFIG.dynamic_rx_buf_num,
238 "WiFi configuration check: rx_ba_win should not be larger than dynamic_rx_buf_num!"
239 );
240 core::assert!(
241 CONFIG.rx_ba_win < (CONFIG.static_rx_buf_num * 2),
242 "WiFi configuration check: rx_ba_win should not be larger than double of the static_rx_buf_num!"
243 );
244};
245
246type TimeBase = PeriodicTimer<'static, Blocking>;
247
248pub(crate) mod flags {
249 use portable_atomic::AtomicBool;
250
251 pub(crate) static ESP_WIFI_INITIALIZED: AtomicBool = AtomicBool::new(false);
252 pub(crate) static WIFI: AtomicBool = AtomicBool::new(false);
253 pub(crate) static BLE: AtomicBool = AtomicBool::new(false);
254}
255
256#[derive(Debug, PartialEq, PartialOrd)]
257#[cfg_attr(feature = "defmt", derive(defmt::Format))]
258pub struct EspWifiController<'d> {
259 _inner: PhantomData<&'d ()>,
260}
261
262impl EspWifiController<'_> {
263 pub fn wifi(&self) -> bool {
265 crate::flags::WIFI.load(Ordering::Acquire)
266 }
267
268 pub fn ble(&self) -> bool {
270 crate::flags::BLE.load(Ordering::Acquire)
271 }
272
273 pub fn deinit(self) -> Result<(), InitializationError> {
275 if crate::flags::ESP_WIFI_INITIALIZED.load(Ordering::Acquire) {
276 unsafe { deinit_unchecked() }
278 } else {
279 Ok(())
280 }
281 }
282
283 pub(crate) unsafe fn conjure() -> Self {
284 Self {
285 _inner: PhantomData,
286 }
287 }
288}
289
290impl Drop for EspWifiController<'_> {
291 fn drop(&mut self) {
292 if crate::flags::ESP_WIFI_INITIALIZED.load(Ordering::Acquire) {
293 unsafe { deinit_unchecked().ok() };
295 }
296 }
297}
298
299pub trait EspWifiTimerSource: private::Sealed {
303 unsafe fn timer(self) -> TimeBase;
309}
310
311impl private::Sealed for TimeBase {}
312impl private::Sealed for AnyTimer<'_> {}
313impl private::Sealed for TimgTimer<'_> {}
314#[cfg(systimer)]
315impl private::Sealed for esp_hal::timer::systimer::Alarm<'_> {}
316
317impl<T> EspWifiTimerSource for T
318where
319 T: esp_hal::timer::IntoAnyTimer + private::Sealed,
320{
321 unsafe fn timer(self) -> TimeBase {
322 let any_timer: AnyTimer<'_> = self.degrade();
323 let any_timer: AnyTimer<'static> = unsafe {
324 core::mem::transmute(any_timer)
330 };
331 TimeBase::new(any_timer)
332 }
333}
334
335pub trait EspWifiRngSource: rand_core::RngCore + private::Sealed {}
337
338impl EspWifiRngSource for Rng {}
339impl private::Sealed for Rng {}
340impl EspWifiRngSource for Trng<'_> {}
341impl private::Sealed for Trng<'_> {}
342
343#[doc = esp_hal::before_snippet!()]
360pub fn init<'d>(
372 timer: impl EspWifiTimerSource + 'd,
373 _rng: impl EspWifiRngSource + 'd,
374 _radio_clocks: RADIO_CLK<'d>,
375) -> Result<EspWifiController<'d>, InitializationError> {
376 if crate::is_interrupts_disabled() {
377 return Err(InitializationError::InterruptsDisabled);
378 }
379
380 const MIN_CLOCK: Rate = Rate::from_mhz(80);
382 let clocks = Clocks::get();
383 if clocks.cpu_clock < MIN_CLOCK {
384 return Err(InitializationError::WrongClockConfig);
385 }
386
387 info!("esp-wifi configuration {:?}", crate::CONFIG);
388 crate::common_adapter::chip_specific::enable_wifi_power_domain();
389 phy_mem_init();
390
391 setup_radio_isr();
392
393 #[cfg(feature = "builtin-scheduler")]
395 preempt_builtin::setup_timer(unsafe { timer.timer() });
396
397 #[cfg(not(feature = "builtin-scheduler"))]
398 let _ = timer; preempt::enable();
402
403 init_tasks();
404 yield_task();
405
406 wifi_set_log_verbose();
407 init_clocks();
408
409 #[cfg(coex)]
410 match crate::wifi::coex_initialize() {
411 0 => {}
412 error => return Err(InitializationError::General(error)),
413 }
414
415 crate::flags::ESP_WIFI_INITIALIZED.store(true, Ordering::Release);
416
417 Ok(EspWifiController {
418 _inner: PhantomData,
419 })
420}
421
422pub unsafe fn deinit_unchecked() -> Result<(), InitializationError> {
432 #[cfg(coex)]
434 {
435 unsafe { crate::wifi::os_adapter::coex_disable() };
436 unsafe { crate::wifi::os_adapter::coex_deinit() };
437 }
438
439 let controller = unsafe { EspWifiController::conjure() };
440
441 if controller.wifi() {
445 #[cfg(feature = "wifi")]
446 crate::wifi::wifi_deinit()?;
447 crate::flags::WIFI.store(false, Ordering::Release);
448 }
449
450 if controller.ble() {
451 #[cfg(feature = "ble")]
452 crate::ble::ble_deinit();
453 crate::flags::BLE.store(false, Ordering::Release);
454 }
455
456 shutdown_radio_isr();
457
458 preempt::disable();
460
461 crate::flags::ESP_WIFI_INITIALIZED.store(false, Ordering::Release);
462
463 Ok(())
464}
465
466fn is_interrupts_disabled() -> bool {
468 #[cfg(target_arch = "xtensa")]
469 return hal::xtensa_lx::interrupt::get_level() != 0
470 || hal::xtensa_lx::interrupt::get_mask() == 0;
471
472 #[cfg(target_arch = "riscv32")]
473 return !hal::riscv::register::mstatus::read().mie()
474 || hal::interrupt::current_runlevel() >= hal::interrupt::Priority::Priority1;
475}
476
477pub(crate) mod private {
478 pub trait Sealed {}
479}
480
481#[derive(Debug, Clone, Copy)]
482#[cfg_attr(feature = "defmt", derive(defmt::Format))]
483#[non_exhaustive]
485pub enum InitializationError {
486 General(i32),
489 #[cfg(feature = "wifi")]
491 WifiError(WifiError),
492 WrongClockConfig,
494 InterruptsDisabled,
497}
498
499#[cfg(feature = "wifi")]
500impl From<WifiError> for InitializationError {
501 fn from(value: WifiError) -> Self {
502 InitializationError::WifiError(value)
503 }
504}
505
506pub fn wifi_set_log_verbose() {
509 #[cfg(all(feature = "sys-logs", not(esp32h2)))]
510 unsafe {
511 use crate::binary::include::{
512 esp_wifi_internal_set_log_level,
513 wifi_log_level_t_WIFI_LOG_VERBOSE,
514 };
515
516 esp_wifi_internal_set_log_level(wifi_log_level_t_WIFI_LOG_VERBOSE);
517 }
518}
519
520fn init_clocks() {
521 let radio_clocks = unsafe { RADIO_CLK::steal() };
522 RadioClockController::new(radio_clocks).init_clocks();
523}