esp_wifi/
lib.rs

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//! This documentation is built for the
6#![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//! . Please ensure you are reading the correct documentation for your target
14//! device.
15//!
16//! ## Usage
17//!
18//! ### Importing
19//!
20//! Note that this crate currently requires you to enable the `unstable` feature
21//! on `esp-hal`.
22//!
23//! Ensure that the right features are enabled for your chip. See [Examples](https://github.com/esp-rs/esp-hal/tree/main/examples#examples) for more examples.
24//!
25//! ```toml
26//! [dependencies.esp-wifi]
27//! # A supported chip needs to be specified, as well as specific use-case features
28#![doc = concat!(r#"features = [""#, esp_hal::chip!(), r#"", "wifi", "esp-now"]"#)]
29//! ```
30//! 
31//! ### Optimization Level
32//!
33//! It is necessary to build with optimization level 2 or 3 since otherwise, it
34//! might not even be able to connect or advertise.
35//!
36//! To make it work also for your debug builds add this to your `Cargo.toml`
37//! ```toml
38//! [profile.dev.package.esp-wifi]
39//! opt-level = 3
40//! ```
41//! ## Globally disable logging
42//!
43//! `esp-wifi` contains a lot of trace-level logging statements.
44//! For maximum performance you might want to disable logging via
45//! a feature flag of the `log` crate. See [documentation](https://docs.rs/log/0.4.19/log/#compile-time-filters).
46//! You should set it to `release_max_level_off`.
47//!
48//! ### WiFi performance considerations
49//!
50//! The default configuration is quite conservative to reduce power and memory consumption.
51//!
52//! There are a number of settings which influence the general performance. Optimal settings are chip and applications specific.
53//! You can get inspiration from the [ESP-IDF examples](https://github.com/espressif/esp-idf/tree/release/v5.3/examples/wifi/iperf)
54//!
55//! Please note that the configuration keys are usually named slightly different and not all configuration keys apply.
56//!
57//! By default the power-saving mode is [PowerSaveMode::None](crate::config::PowerSaveMode::None) and `ESP_WIFI_PHY_ENABLE_USB` is enabled by default.
58//!
59//! In addition pay attention to these configuration keys:
60//! - `ESP_WIFI_RX_QUEUE_SIZE`
61//! - `ESP_WIFI_TX_QUEUE_SIZE`
62//! - `ESP_WIFI_MAX_BURST_SIZE`
63//!
64//! # Features flags
65//!
66//! Note that not all features are available on every MCU. For example, `ble`
67//! (and thus, `coex`) is not available on ESP32-S2.
68//!
69//! When using the `dump_packets` config you can use the extcap in
70//! `extras/esp-wifishark` to analyze the frames in Wireshark.
71//! For more information see
72//! [extras/esp-wifishark/README.md](../extras/esp-wifishark/README.md)
73#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
74//! ## Additional configuration
75//!
76//! We've exposed some configuration options that don't fit into cargo
77//! features. These can be set via environment variables, or via cargo's `[env]`
78//! section inside `.cargo/config.toml`. Below is a table of tunable parameters
79//! for this crate:
80#![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 until num-derive doesn't generate this warning anymore (unknown_lints because Xtensa
89// toolchain doesn't know about that lint, yet)
90#![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
105// MUST be the first module
106mod 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// this is just to verify that we use the correct defaults in `build.rs`
162#[allow(clippy::assertions_on_constants)] // TODO: try assert_eq once it's usable in const context
163const _: () = {
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/// Tunable parameters for the WiFi driver
181#[allow(unused)] // currently there are no ble tunables
182struct 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// Validate the configuration at compile time
232#[allow(clippy::assertions_on_constants)]
233const _: () = {
234    // We explicitely use `core` assert here because this evaluation happens at
235    // compile time and won't bloat the binary
236    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    /// Is the WiFi part of the radio running
264    pub fn wifi(&self) -> bool {
265        crate::flags::WIFI.load(Ordering::Acquire)
266    }
267
268    /// Is the BLE part of the radio running
269    pub fn ble(&self) -> bool {
270        crate::flags::BLE.load(Ordering::Acquire)
271    }
272
273    /// De-initialize the radio
274    pub fn deinit(self) -> Result<(), InitializationError> {
275        if crate::flags::ESP_WIFI_INITIALIZED.load(Ordering::Acquire) {
276            // safety: no other driver can be using this if this is callable
277            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            // safety: no other driver can be using this if this is callable
294            unsafe { deinit_unchecked().ok() };
295        }
296    }
297}
298
299/// A trait to allow better UX for initializing esp-wifi.
300///
301/// This trait is meant to be used only for the `init` function.
302pub trait EspWifiTimerSource: private::Sealed {
303    /// Returns the timer source.
304    ///
305    /// # Safety
306    ///
307    /// It is UB to call this method outside of [`init`].
308    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            // Safety: this method is only safe to be called from within `init`.
325            // This 'static lifetime is a fake one, the timer is only used for the lifetime
326            // of the `EspWifiController` instance. The lifetime bounds on `init` and
327            // `EspWifiTimerSource` ensure that the timer is not used after the
328            // `EspWifiController` is dropped.
329            core::mem::transmute(any_timer)
330        };
331        TimeBase::new(any_timer)
332    }
333}
334
335/// A marker trait for suitable Rng sources for esp-wifi
336pub 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/// Initialize for using WiFi and or BLE.
344///
345/// Make sure to **not** call this function while interrupts are disabled.
346///
347/// # The `timer` argument
348///
349/// The `timer` argument is a timer source that is used by the WiFi driver to
350/// schedule internal tasks. The timer source can be any of the following:
351///
352/// - A timg `Timer` instance
353/// - A systimer `Alarm` instance
354/// - An `AnyTimer` instance
355///
356/// # Examples
357///
358/// ```rust, no_run
359#[doc = esp_hal::before_snippet!()]
360/// use esp_hal::{rng::Rng, timer::timg::TimerGroup};
361///
362/// let timg0 = TimerGroup::new(peripherals.TIMG0);
363/// let init = esp_wifi::init(
364///     timg0.timer0,
365///     Rng::new(peripherals.RNG),
366///     peripherals.RADIO_CLK,
367/// )
368/// .unwrap();
369/// # }
370/// ```
371pub 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    // A minimum clock of 80MHz is required to operate WiFi module.
381    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    // Enable timer tick interrupt
394    #[cfg(feature = "builtin-scheduler")]
395    preempt_builtin::setup_timer(unsafe { timer.timer() });
396
397    #[cfg(not(feature = "builtin-scheduler"))]
398    let _ = timer; // mark used to suppress warning
399
400    // This initializes the task switcher
401    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
422/// Deinitializes the entire radio stack
423///
424/// This can be useful to shutdown the stack before going to sleep for example.
425///
426/// # Safety
427///
428/// The user must ensure that any use of the radio via the WIFI/BLE/ESP-NOW
429/// drivers are complete, else undefined behaviour may occur within those
430/// drivers.
431pub unsafe fn deinit_unchecked() -> Result<(), InitializationError> {
432    // Disable coexistence
433    #[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    // Peripheral drivers should already take care of shutting these down
442    // we have to check this in the case where a user calls `deinit_unchecked`
443    // directly.
444    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    // This shuts down the task switcher and timer tick interrupt.
459    preempt::disable();
460
461    crate::flags::ESP_WIFI_INITIALIZED.store(false, Ordering::Release);
462
463    Ok(())
464}
465
466/// Returns true if at least some interrupt levels are disabled.
467fn 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/// Error which can be returned during [`init`].
484#[non_exhaustive]
485pub enum InitializationError {
486    /// A general error occurred.
487    /// The internal error code is reported.
488    General(i32),
489    /// An error from the WiFi driver.
490    #[cfg(feature = "wifi")]
491    WifiError(WifiError),
492    /// The current CPU clock frequency is too low.
493    WrongClockConfig,
494    /// Tried to initialize while interrupts are disabled.
495    /// This is not supported.
496    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
506/// Enable verbose logging within the WiFi driver
507/// Does nothing unless the `sys-logs` feature is enabled.
508pub 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}