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 = [""#, 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
103#[macro_use]
104extern crate esp_metadata_generated;
105
106extern crate alloc;
107
108// MUST be the first module
109mod fmt;
110
111use core::marker::PhantomData;
112
113use common_adapter::chip_specific::phy_mem_init;
114use esp_config::*;
115use esp_hal as hal;
116use hal::{
117    Blocking,
118    clock::{Clocks, init_radio_clocks},
119    rng::{Rng, Trng},
120    time::Rate,
121    timer::{AnyTimer, PeriodicTimer, timg::Timer as TimgTimer},
122};
123
124#[cfg(feature = "wifi")]
125use crate::wifi::WifiError;
126use crate::{
127    preempt::yield_task,
128    radio::{setup_radio_isr, shutdown_radio_isr},
129    tasks::init_tasks,
130};
131
132mod binary {
133    pub use esp_wifi_sys::*;
134}
135mod compat;
136
137#[cfg(feature = "builtin-scheduler")]
138mod preempt_builtin;
139
140pub mod preempt;
141
142mod radio;
143mod time;
144
145#[cfg(feature = "wifi")]
146pub mod wifi;
147
148#[cfg(feature = "ble")]
149pub mod ble;
150
151#[cfg(feature = "esp-now")]
152pub mod esp_now;
153
154pub mod config;
155
156pub(crate) mod common_adapter;
157
158#[doc(hidden)]
159pub mod tasks;
160
161pub(crate) mod memory_fence;
162
163// this is just to verify that we use the correct defaults in `build.rs`
164#[allow(clippy::assertions_on_constants)] // TODO: try assert_eq once it's usable in const context
165const _: () = {
166    cfg_if::cfg_if! {
167        if #[cfg(not(esp32h2))] {
168            core::assert!(binary::include::CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM == 10);
169            core::assert!(binary::include::CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM == 32);
170            core::assert!(binary::include::WIFI_STATIC_TX_BUFFER_NUM == 0);
171            core::assert!(binary::include::CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM == 32);
172            core::assert!(binary::include::CONFIG_ESP_WIFI_AMPDU_RX_ENABLED == 1);
173            core::assert!(binary::include::CONFIG_ESP_WIFI_AMPDU_TX_ENABLED == 1);
174            core::assert!(binary::include::WIFI_AMSDU_TX_ENABLED == 0);
175            core::assert!(binary::include::CONFIG_ESP32_WIFI_RX_BA_WIN == 6);
176        }
177    };
178};
179
180#[derive(Debug)]
181#[cfg_attr(feature = "defmt", derive(defmt::Format))]
182/// Tunable parameters for the WiFi driver
183#[allow(unused)] // currently there are no ble tunables
184struct Config {
185    rx_queue_size: usize,
186    tx_queue_size: usize,
187    static_rx_buf_num: usize,
188    dynamic_rx_buf_num: usize,
189    static_tx_buf_num: usize,
190    dynamic_tx_buf_num: usize,
191    ampdu_rx_enable: bool,
192    ampdu_tx_enable: bool,
193    amsdu_tx_enable: bool,
194    rx_ba_win: usize,
195    max_burst_size: usize,
196    country_code: &'static str,
197    country_code_operating_class: u8,
198    mtu: usize,
199    tick_rate_hz: u32,
200    listen_interval: u16,
201    beacon_timeout: u16,
202    ap_beacon_timeout: u16,
203    failure_retry_cnt: u8,
204    scan_method: u32,
205}
206
207pub(crate) const CONFIG: config::EspWifiConfig = config::EspWifiConfig {
208    rx_queue_size: esp_config_int!(usize, "ESP_WIFI_CONFIG_RX_QUEUE_SIZE"),
209    tx_queue_size: esp_config_int!(usize, "ESP_WIFI_CONFIG_TX_QUEUE_SIZE"),
210    static_rx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_STATIC_RX_BUF_NUM"),
211    dynamic_rx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_DYNAMIC_RX_BUF_NUM"),
212    static_tx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_STATIC_TX_BUF_NUM"),
213    dynamic_tx_buf_num: esp_config_int!(usize, "ESP_WIFI_CONFIG_DYNAMIC_TX_BUF_NUM"),
214    ampdu_rx_enable: esp_config_bool!("ESP_WIFI_CONFIG_AMPDU_RX_ENABLE"),
215    ampdu_tx_enable: esp_config_bool!("ESP_WIFI_CONFIG_AMPDU_TX_ENABLE"),
216    amsdu_tx_enable: esp_config_bool!("ESP_WIFI_CONFIG_AMSDU_TX_ENABLE"),
217    rx_ba_win: esp_config_int!(usize, "ESP_WIFI_CONFIG_RX_BA_WIN"),
218    max_burst_size: esp_config_int!(usize, "ESP_WIFI_CONFIG_MAX_BURST_SIZE"),
219    country_code: esp_config_str!("ESP_WIFI_CONFIG_COUNTRY_CODE"),
220    country_code_operating_class: esp_config_int!(
221        u8,
222        "ESP_WIFI_CONFIG_COUNTRY_CODE_OPERATING_CLASS"
223    ),
224    mtu: esp_config_int!(usize, "ESP_WIFI_CONFIG_MTU"),
225    tick_rate_hz: esp_config_int!(u32, "ESP_WIFI_CONFIG_TICK_RATE_HZ"),
226    listen_interval: esp_config_int!(u16, "ESP_WIFI_CONFIG_LISTEN_INTERVAL"),
227    beacon_timeout: esp_config_int!(u16, "ESP_WIFI_CONFIG_BEACON_TIMEOUT"),
228    ap_beacon_timeout: esp_config_int!(u16, "ESP_WIFI_CONFIG_AP_BEACON_TIMEOUT"),
229    failure_retry_cnt: esp_config_int!(u8, "ESP_WIFI_CONFIG_FAILURE_RETRY_CNT"),
230    scan_method: esp_config_int!(u32, "ESP_WIFI_CONFIG_SCAN_METHOD"),
231};
232
233type TimeBase = PeriodicTimer<'static, Blocking>;
234
235#[derive(Debug, PartialEq, PartialOrd)]
236#[cfg_attr(feature = "defmt", derive(defmt::Format))]
237pub struct EspWifiController<'d> {
238    _inner: PhantomData<&'d ()>,
239}
240
241impl Drop for EspWifiController<'_> {
242    fn drop(&mut self) {
243        // Disable coexistence
244        #[cfg(coex)]
245        {
246            unsafe { crate::wifi::os_adapter::coex_disable() };
247            unsafe { crate::wifi::os_adapter::coex_deinit() };
248        }
249
250        shutdown_radio_isr();
251
252        // This shuts down the task switcher and timer tick interrupt.
253        preempt::disable();
254    }
255}
256
257/// A trait to allow better UX for initializing esp-wifi.
258///
259/// This trait is meant to be used only for the `init` function.
260pub trait EspWifiTimerSource: private::Sealed {
261    /// Returns the timer source.
262    ///
263    /// # Safety
264    ///
265    /// It is UB to call this method outside of [`init`].
266    unsafe fn timer(self) -> TimeBase;
267}
268
269impl private::Sealed for TimeBase {}
270impl private::Sealed for AnyTimer<'_> {}
271impl private::Sealed for TimgTimer<'_> {}
272#[cfg(systimer)]
273impl private::Sealed for esp_hal::timer::systimer::Alarm<'_> {}
274
275impl<T> EspWifiTimerSource for T
276where
277    T: esp_hal::timer::any::Degrade + private::Sealed,
278{
279    unsafe fn timer(self) -> TimeBase {
280        let any_timer: AnyTimer<'_> = self.degrade();
281        let any_timer: AnyTimer<'static> = unsafe {
282            // Safety: this method is only safe to be called from within `init`.
283            // This 'static lifetime is a fake one, the timer is only used for the lifetime
284            // of the `EspWifiController` instance. The lifetime bounds on `init` and
285            // `EspWifiTimerSource` ensure that the timer is not used after the
286            // `EspWifiController` is dropped.
287            core::mem::transmute(any_timer)
288        };
289        TimeBase::new(any_timer)
290    }
291}
292
293/// A marker trait for suitable Rng sources for esp-wifi
294pub trait EspWifiRngSource: rand_core::RngCore + private::Sealed {}
295
296impl EspWifiRngSource for Rng {}
297impl private::Sealed for Rng {}
298impl EspWifiRngSource for Trng<'_> {}
299impl private::Sealed for Trng<'_> {}
300
301/// Initialize for using WiFi and or BLE.
302///
303/// Make sure to **not** call this function while interrupts are disabled.
304///
305/// # The `timer` argument
306///
307/// The `timer` argument is a timer source that is used by the WiFi driver to
308/// schedule internal tasks. The timer source can be any of the following:
309///
310/// - A timg `Timer` instance
311/// - A systimer `Alarm` instance
312/// - An `AnyTimer` instance
313///
314/// # Examples
315///
316/// ```rust, no_run
317#[doc = esp_hal::before_snippet!()]
318/// use esp_hal::{rng::Rng, timer::timg::TimerGroup};
319///
320/// let timg0 = TimerGroup::new(peripherals.TIMG0);
321/// let init = esp_wifi::init(
322///     timg0.timer0,
323///     Rng::new(peripherals.RNG),
324///     peripherals.RADIO_CLK,
325/// )
326/// .unwrap();
327/// # }
328/// ```
329pub fn init<'d>(
330    timer: impl EspWifiTimerSource + 'd,
331    _rng: impl EspWifiRngSource + 'd,
332) -> Result<EspWifiController<'d>, InitializationError> {
333    if crate::is_interrupts_disabled() {
334        return Err(InitializationError::InterruptsDisabled);
335    }
336
337    // A minimum clock of 80MHz is required to operate WiFi module.
338    const MIN_CLOCK: Rate = Rate::from_mhz(80);
339    let clocks = Clocks::get();
340    if clocks.cpu_clock < MIN_CLOCK {
341        return Err(InitializationError::WrongClockConfig);
342    }
343
344    info!("esp-wifi configuration {:?}", crate::CONFIG);
345    crate::common_adapter::chip_specific::enable_wifi_power_domain();
346    phy_mem_init();
347
348    setup_radio_isr();
349
350    // Enable timer tick interrupt
351    #[cfg(feature = "builtin-scheduler")]
352    preempt_builtin::setup_timer(unsafe { timer.timer() });
353
354    #[cfg(not(feature = "builtin-scheduler"))]
355    let _ = timer; // mark used to suppress warning
356
357    // This initializes the task switcher
358    preempt::enable();
359
360    init_tasks();
361    yield_task();
362
363    wifi_set_log_verbose();
364    init_radio_clocks();
365
366    #[cfg(coex)]
367    match crate::wifi::coex_initialize() {
368        0 => {}
369        error => return Err(InitializationError::General(error)),
370    }
371
372    Ok(EspWifiController {
373        _inner: PhantomData,
374    })
375}
376
377/// Returns true if at least some interrupt levels are disabled.
378fn is_interrupts_disabled() -> bool {
379    #[cfg(target_arch = "xtensa")]
380    return hal::xtensa_lx::interrupt::get_level() != 0
381        || hal::xtensa_lx::interrupt::get_mask() == 0;
382
383    #[cfg(target_arch = "riscv32")]
384    return !hal::riscv::register::mstatus::read().mie()
385        || hal::interrupt::current_runlevel() >= hal::interrupt::Priority::Priority1;
386}
387
388pub(crate) mod private {
389    pub trait Sealed {}
390}
391
392#[derive(Debug, Clone, Copy)]
393#[cfg_attr(feature = "defmt", derive(defmt::Format))]
394/// Error which can be returned during [`init`].
395#[non_exhaustive]
396pub enum InitializationError {
397    /// A general error occurred.
398    /// The internal error code is reported.
399    General(i32),
400    /// An error from the WiFi driver.
401    #[cfg(feature = "wifi")]
402    WifiError(WifiError),
403    /// The current CPU clock frequency is too low.
404    WrongClockConfig,
405    /// Tried to initialize while interrupts are disabled.
406    /// This is not supported.
407    InterruptsDisabled,
408}
409
410#[cfg(feature = "wifi")]
411impl From<WifiError> for InitializationError {
412    fn from(value: WifiError) -> Self {
413        InitializationError::WifiError(value)
414    }
415}
416
417/// Enable verbose logging within the WiFi driver
418/// Does nothing unless the `sys-logs` feature is enabled.
419pub fn wifi_set_log_verbose() {
420    #[cfg(all(feature = "sys-logs", not(esp32h2)))]
421    unsafe {
422        use crate::binary::include::{
423            esp_wifi_internal_set_log_level,
424            wifi_log_level_t_WIFI_LOG_VERBOSE,
425        };
426
427        esp_wifi_internal_set_log_level(wifi_log_level_t_WIFI_LOG_VERBOSE);
428    }
429}