Fix panic on long waits (#3433)

Co-authored-by: Scott Mabin <scott@mabez.dev>
This commit is contained in:
Dániel Buga 2025-04-29 10:00:27 +02:00 committed by GitHub
parent 9a04b258bf
commit 5c97eaf8ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 38 additions and 9 deletions

View File

@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Fixed a panic on very long wakeup times (#3433)
### Removed ### Removed
## [0.7.0] - 2025-02-24 ## [0.7.0] - 2025-02-24

View File

@ -12,7 +12,7 @@ use esp_hal::{
interrupt::{InterruptHandler, Priority}, interrupt::{InterruptHandler, Priority},
sync::Locked, sync::Locked,
time::{Duration, Instant}, time::{Duration, Instant},
timer::OneShotTimer, timer::{Error, OneShotTimer},
}; };
pub type Timer = OneShotTimer<'static, Blocking>; pub type Timer = OneShotTimer<'static, Blocking>;
@ -206,8 +206,20 @@ impl EmbassyTimer {
let now = Instant::now().duration_since_epoch().as_micros(); let now = Instant::now().duration_since_epoch().as_micros();
if timestamp > now { if timestamp > now {
let timeout = Duration::from_micros(timestamp - now); let mut timeout = Duration::from_micros(timestamp - now);
unwrap!(timer.schedule(timeout)); loop {
// The timer API doesn't let us query a maximum timeout, so let's try backing
// off on failure.
match timer.schedule(timeout) {
Ok(()) => break,
Err(Error::InvalidTimeout) => {
// It's okay to wake up earlier than scheduled.
timeout = timeout / 2;
assert_ne!(timeout, Duration::ZERO);
}
other => unwrap!(other),
}
}
true true
} else { } else {
// If the timestamp is past, we return `false` to ask embassy to poll again // If the timestamp is past, we return `false` to ask embassy to poll again

View File

@ -77,6 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Async I2C is doesn't do blocking reads anymore (#3344) - Async I2C is doesn't do blocking reads anymore (#3344)
- Passing an invalid seven bit I2C address is now rejected (#3343) - Passing an invalid seven bit I2C address is now rejected (#3343)
- PARL_IO: Use correct max transfer size (#3346) - PARL_IO: Use correct max transfer size (#3346)
- `OneShot` timer now returns an InvalidTimeout from `schedule` instead of panicking (#3433)
### Removed ### Removed

View File

@ -472,7 +472,9 @@ impl Timer<'_> {
let clk_src = Clocks::get().apb_clock; let clk_src = Clocks::get().apb_clock;
} }
} }
let ticks = timeout_to_ticks(value, clk_src, self.divider()); let Some(ticks) = timeout_to_ticks(value, clk_src, self.divider()) else {
return Err(Error::InvalidTimeout);
};
// The counter is 54-bits wide, so we must ensure that the provided // The counter is 54-bits wide, so we must ensure that the provided
// value is not too wide: // value is not too wide:
@ -580,13 +582,11 @@ fn ticks_to_timeout(ticks: u64, clock: Rate, divider: u32) -> u64 {
ticks * period / 1_000_000 ticks * period / 1_000_000
} }
fn timeout_to_ticks(timeout: Duration, clock: Rate, divider: u32) -> u64 { fn timeout_to_ticks(timeout: Duration, clock: Rate, divider: u32) -> Option<u64> {
let micros = timeout.as_micros(); let micros = timeout.as_micros();
let ticks_per_sec = (clock.as_hz() / divider) as u64;
// 1_000_000 is used to get rid of `float` calculations micros.checked_mul(ticks_per_sec).map(|n| n / 1_000_000)
let period: u64 = 1_000_000 * 1_000_000 / ((clock.as_hz() / divider) as u64);
(1_000_000 * micros) / period
} }
/// Behavior of the MWDT stage if it times out. /// Behavior of the MWDT stage if it times out.

View File

@ -6,6 +6,7 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use embassy_futures::select::select;
use embassy_time::{Duration, Ticker, Timer}; use embassy_time::{Duration, Ticker, Timer};
#[cfg(not(feature = "esp32"))] #[cfg(not(feature = "esp32"))]
use esp_hal::{ use esp_hal::{
@ -122,6 +123,7 @@ fn set_up_embassy_with_systimer(peripherals: Peripherals) {
#[cfg(test)] #[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())] #[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod test { mod test {
use super::*; use super::*;
use crate::test_cases::*; use crate::test_cases::*;
#[cfg(not(feature = "esp32"))] #[cfg(not(feature = "esp32"))]
@ -271,4 +273,16 @@ mod test {
assert!(false, "Test failed after 5 retries"); assert!(false, "Test failed after 5 retries");
} }
/// Test that timg0 and systimer don't have vastly different tick rates.
#[test]
async fn test_that_a_very_long_wakeup_does_not_panic(peripherals: Peripherals) {
set_up_embassy_with_timg0(peripherals);
select(
Timer::after(Duration::from_micros(u64::MAX / 2)),
embassy_futures::yield_now(), // we don't actually want to wait forever
)
.await;
}
} }