Skip to content

Commit c789893

Browse files
committed
Avoid crashing after shutting down WiFi
1 parent dee43ae commit c789893

File tree

6 files changed

+194
-67
lines changed

6 files changed

+194
-67
lines changed

esp-radio-rtos-driver/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
- Renamed `Scheduler` to `SchedulerImplementation` and `scheduler_impl!` to `register_scheduler_implementation!` (#4559)
2525
- `current_task`, `task_create` and `schedule_task_deletion` functions now work with `ThreadPtr` instead of `*mut c_void` (#4559)
2626
- `schedule_task_deletion` now takes an `Option` (#4559)
27+
- `SemaphoreHandle` is now `Send` (#4761)
2728

2829
### Fixed
2930

esp-radio-rtos-driver/src/semaphore.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,8 @@ impl Drop for SemaphoreHandle {
446446
}
447447
}
448448

449+
unsafe impl Send for SemaphoreHandle {}
450+
449451
#[cfg(feature = "ipc-implementations")]
450452
mod implementation {
451453
use alloc::boxed::Box;

esp-radio/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4040
- Fixed a linker error (about missing symbols) when the `wifi` feature is selected but the code doesn't use it (#4513)
4141
- `Controller::stop_async()` now returns `WifiError::NotStarted` when the `Controller` has not been started (#4504)
4242
- ESP32-C2: Disable BLE controller before deinitializing the stack (#4606)
43+
- Fix a crash after shutting down WiFi (#4761)
4344

4445
### Removed
4546

esp-radio/src/wifi/mod.rs

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,20 @@ impl WifiDeviceMode {
10251025
}
10261026

10271027
if self.can_send() {
1028+
// even checking for !Uninitialized would be enough to not crash
1029+
match self {
1030+
WifiDeviceMode::Station => {
1031+
if !matches!(station_state(), WifiStationState::Connected) {
1032+
return None;
1033+
}
1034+
}
1035+
WifiDeviceMode::AccessPoint => {
1036+
if !matches!(access_point_state(), WifiAccessPointState::Started) {
1037+
return None;
1038+
}
1039+
}
1040+
}
1041+
10281042
Some(WifiTxToken { mode: *self })
10291043
} else {
10301044
None
@@ -1436,19 +1450,34 @@ impl TxToken for WifiTxToken {
14361450
// though in reality `esp_wifi_internal_tx` copies the buffer into its own
14371451
// memory and does not modify
14381452
pub(crate) fn esp_wifi_send_data(interface: wifi_interface_t, data: &mut [u8]) {
1439-
trace!("sending... {} bytes", data.len());
1440-
dump_packet_info(data);
1453+
// `esp_wifi_internal_tx` will crash if wifi is uninitialized or de-inited
14411454

1442-
let len = data.len() as u16;
1443-
let ptr = data.as_mut_ptr().cast();
1455+
state::locked(|| {
1456+
// even checking for !Uninitialized would be enough to not crash
1457+
if interface == wifi_interface_t_WIFI_IF_STA
1458+
&& !matches!(station_state(), WifiStationState::Connected)
1459+
{
1460+
return;
1461+
} else if interface == wifi_interface_t_WIFI_IF_AP
1462+
&& !matches!(access_point_state(), WifiAccessPointState::Started)
1463+
{
1464+
return;
1465+
}
14441466

1445-
let res = unsafe { esp_wifi_result!(esp_wifi_internal_tx(interface, ptr, len)) };
1467+
trace!("sending... {} bytes", data.len());
1468+
dump_packet_info(data);
14461469

1447-
if res.is_err() {
1448-
decrement_inflight_counter();
1449-
} else {
1450-
trace!("esp_wifi_internal_tx ok");
1451-
}
1470+
let len = data.len() as u16;
1471+
let ptr = data.as_mut_ptr().cast();
1472+
1473+
let res = unsafe { esp_wifi_result!(esp_wifi_internal_tx(interface, ptr, len)) };
1474+
1475+
if res.is_err() {
1476+
decrement_inflight_counter();
1477+
} else {
1478+
trace!("esp_wifi_internal_tx ok");
1479+
}
1480+
})
14521481
}
14531482

14541483
fn dump_packet_info(_buffer: &mut [u8]) {
@@ -1960,16 +1989,18 @@ pub struct WifiController<'d> {
19601989

19611990
impl Drop for WifiController<'_> {
19621991
fn drop(&mut self) {
1963-
if let Err(e) = crate::wifi::wifi_deinit() {
1964-
warn!("Failed to cleanly deinit wifi: {:?}", e);
1965-
}
1992+
state::locked(|| {
1993+
if let Err(e) = crate::wifi::wifi_deinit() {
1994+
warn!("Failed to cleanly deinit wifi: {:?}", e);
1995+
}
19661996

1967-
set_access_point_state(WifiAccessPointState::Uninitialized);
1968-
set_station_state(WifiStationState::Uninitialized);
1997+
set_access_point_state(WifiAccessPointState::Uninitialized);
1998+
set_station_state(WifiStationState::Uninitialized);
19691999

1970-
esp_hal::rng::TrngSource::decrease_entropy_source_counter(unsafe {
1971-
esp_hal::Internal::conjure()
1972-
});
2000+
esp_hal::rng::TrngSource::decrease_entropy_source_counter(unsafe {
2001+
esp_hal::Internal::conjure()
2002+
});
2003+
})
19732004
}
19742005
}
19752006

esp-radio/src/wifi/state.rs

Lines changed: 64 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,53 @@
11
use core::sync::atomic::Ordering;
22

3-
use private::{AtomicWifiAccessPointState, AtomicWifiStationState};
4-
pub(crate) use private::{WifiAccessPointState, WifiStationState};
3+
use esp_radio_rtos_driver::semaphore::{SemaphoreHandle, SemaphoreKind};
4+
use esp_sync::NonReentrantMutex;
5+
use portable_atomic_enum::atomic_enum;
56

67
use super::WifiEvent;
78

8-
mod private {
9-
use portable_atomic_enum::atomic_enum;
10-
11-
/// Wi-Fi interface for station state.
12-
#[atomic_enum]
13-
#[derive(PartialEq, Debug, Clone, Copy, Hash)]
14-
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
15-
#[non_exhaustive]
16-
pub enum WifiStationState {
17-
/// Start initiated.
18-
Starting,
19-
/// Station started.
20-
Started,
21-
/// Connect initiated.
22-
Connecting,
23-
/// Station connected.
24-
Connected,
25-
/// Disconnect initiated.
26-
Disconnecting,
27-
/// Station disconnected.
28-
Disconnected,
29-
/// Stop initiated.
30-
Stopping,
31-
/// Station stopped
32-
Stopped,
33-
/// Uninitialized state.
34-
Uninitialized,
35-
}
9+
/// Wi-Fi interface for station state.
10+
#[atomic_enum]
11+
#[derive(PartialEq, Debug, Clone, Copy, Hash)]
12+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
13+
#[non_exhaustive]
14+
pub(crate) enum WifiStationState {
15+
/// Start initiated.
16+
Starting,
17+
/// Station started.
18+
Started,
19+
/// Connect initiated.
20+
Connecting,
21+
/// Station connected.
22+
Connected,
23+
/// Disconnect initiated.
24+
Disconnecting,
25+
/// Station disconnected.
26+
Disconnected,
27+
/// Stop initiated.
28+
Stopping,
29+
/// Station stopped
30+
Stopped,
31+
/// Uninitialized state.
32+
Uninitialized,
33+
}
3634

37-
/// Wi-Fi interface for access point state.
38-
#[atomic_enum]
39-
#[derive(PartialEq, Debug, Clone, Copy, Hash)]
40-
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
41-
#[non_exhaustive]
42-
pub enum WifiAccessPointState {
43-
/// Start initiated.
44-
Starting,
45-
/// Access point started.
46-
Started,
47-
/// Stop initiated.
48-
Stopping,
49-
/// Access point stopped.
50-
Stopped,
51-
/// Uninitialized state.
52-
Uninitialized,
53-
}
35+
/// Wi-Fi interface for access point state.
36+
#[atomic_enum]
37+
#[derive(PartialEq, Debug, Clone, Copy, Hash)]
38+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39+
#[non_exhaustive]
40+
pub(crate) enum WifiAccessPointState {
41+
/// Start initiated.
42+
Starting,
43+
/// Access point started.
44+
Started,
45+
/// Stop initiated.
46+
Stopping,
47+
/// Access point stopped.
48+
Stopped,
49+
/// Uninitialized state.
50+
Uninitialized,
5451
}
5552

5653
impl From<WifiEvent> for WifiStationState {
@@ -75,9 +72,9 @@ impl From<WifiEvent> for WifiAccessPointState {
7572
}
7673
}
7774

78-
pub(crate) static STATION_STATE: AtomicWifiStationState =
75+
static STATION_STATE: AtomicWifiStationState =
7976
AtomicWifiStationState::new(WifiStationState::Uninitialized);
80-
pub(crate) static ACCESS_POINT_STATE: AtomicWifiAccessPointState =
77+
static ACCESS_POINT_STATE: AtomicWifiAccessPointState =
8178
AtomicWifiAccessPointState::new(WifiAccessPointState::Uninitialized);
8279

8380
/// Get the current state of the access point.
@@ -118,3 +115,21 @@ pub(crate) fn set_access_point_state(state: WifiAccessPointState) {
118115
pub(crate) fn set_station_state(state: WifiStationState) {
119116
STATION_STATE.store(state, Ordering::Relaxed)
120117
}
118+
119+
pub(crate) fn locked<R>(f: impl FnOnce() -> R) -> R {
120+
static LOCK: NonReentrantMutex<Option<SemaphoreHandle>> = NonReentrantMutex::new(None);
121+
122+
LOCK.with(|sem| {
123+
sem.get_or_insert_with(|| SemaphoreHandle::new(SemaphoreKind::Mutex))
124+
.take(None)
125+
});
126+
127+
let res: R = f();
128+
129+
LOCK.with(|sem| {
130+
sem.get_or_insert_with(|| SemaphoreHandle::new(SemaphoreKind::Mutex))
131+
.give()
132+
});
133+
134+
res
135+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! Check that trying to send after drop doesn't crash
2+
3+
//% FEATURES: esp-radio esp-radio/wifi esp-radio/unstable esp-hal/unstable
4+
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32s2 esp32s3
5+
6+
#![no_std]
7+
#![no_main]
8+
9+
use embassy_executor::Spawner;
10+
use embassy_time::Duration;
11+
use esp_alloc as _;
12+
use esp_backtrace as _;
13+
use esp_hal::{
14+
clock::CpuClock,
15+
interrupt::software::SoftwareInterruptControl,
16+
ram,
17+
timer::timg::TimerGroup,
18+
};
19+
use esp_println::println;
20+
use esp_radio::wifi::{ModeConfig, sta::StationConfig};
21+
22+
esp_bootloader_esp_idf::esp_app_desc!();
23+
24+
const SSID: &str = env!("SSID");
25+
const PASSWORD: &str = env!("PASSWORD");
26+
27+
#[esp_rtos::main]
28+
async fn main(_spawner: Spawner) -> ! {
29+
esp_println::logger::init_logger_from_env();
30+
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
31+
let peripherals = esp_hal::init(config);
32+
33+
esp_alloc::heap_allocator!(size: 32 * 1024);
34+
// add some more RAM
35+
esp_alloc::heap_allocator!(#[ram(reclaimed)] size: 64 * 1024);
36+
37+
let timg0 = TimerGroup::new(peripherals.TIMG0);
38+
let sw_int = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
39+
esp_rtos::start(timg0.timer0, sw_int.software_interrupt0);
40+
41+
let mut wifi = peripherals.WIFI;
42+
let (mut controller, interfaces) =
43+
esp_radio::wifi::new(wifi.reborrow(), Default::default()).unwrap();
44+
45+
let station_config = ModeConfig::Station(
46+
StationConfig::default()
47+
.with_ssid(SSID.into())
48+
.with_password(PASSWORD.into()),
49+
);
50+
controller.set_config(&station_config).unwrap();
51+
controller.start_async().await.unwrap();
52+
controller.connect_async().await.unwrap();
53+
54+
let mut wifi_interface = interfaces.station;
55+
56+
use smoltcp::phy::Device;
57+
let token = wifi_interface.transmit(smoltcp::time::Instant::from_millis(
58+
esp_hal::time::Instant::now()
59+
.duration_since_epoch()
60+
.as_millis() as i64,
61+
));
62+
63+
println!("got token {}", token.is_some());
64+
65+
core::mem::drop(controller);
66+
67+
if let Some(token) = token {
68+
token.consume_token(10, |tx| tx.fill(0));
69+
println!("tx done");
70+
} else {
71+
println!("no token");
72+
}
73+
74+
loop {
75+
embassy_time::Timer::after(Duration::from_millis(10)).await;
76+
}
77+
}

0 commit comments

Comments
 (0)