Process timer queue in timer interrupt handler (#4147)

This commit is contained in:
Dániel Buga 2025-09-19 12:45:21 +02:00 committed by GitHub
parent 37f048f20b
commit e57530c9b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 36 additions and 47 deletions

View File

@ -35,9 +35,6 @@ impl SchedulerEvent {
// If this is NOT set, the event is a cooperative yield of some sort (time slicing, self-yield).
const TASK_BLOCKED: usize = 1 << 0;
// If this is set, the timer queue should be processed.
const TIMER_EVENT: usize = 1 << 1;
fn contains(self, bit: usize) -> bool {
self.0 & bit != 0
}
@ -49,17 +46,9 @@ impl SchedulerEvent {
self.set(Self::TASK_BLOCKED)
}
pub(crate) fn set_timer_event(&mut self) {
self.set(Self::TIMER_EVENT)
}
pub(crate) fn is_cooperative_yield(self) -> bool {
!self.contains(Self::TASK_BLOCKED)
}
pub(crate) fn is_timer_event(self) -> bool {
self.contains(Self::TIMER_EVENT)
}
}
pub(crate) struct SchedulerState {
@ -154,22 +143,6 @@ impl SchedulerState {
self.delete_marked_tasks();
let event = core::mem::take(&mut self.event);
let time_driver = unwrap!(self.time_driver.as_mut());
if event.is_timer_event() {
time_driver.handle_alarm(|ready_task| {
debug_assert_eq!(
ready_task.state(),
task::TaskState::Sleeping,
"task: {:?}",
ready_task
);
debug!("Task {:?} is ready", ready_task);
self.run_queue.mark_task_ready(ready_task);
});
}
if event.is_cooperative_yield()
&& let Some(current_task) = self.current_task
@ -232,6 +205,7 @@ impl SchedulerState {
let arm_next_timeslice_tick = priority
.map(|p| !self.run_queue.is_level_empty(p))
.unwrap_or(false);
let time_driver = unwrap!(self.time_driver.as_mut());
time_driver.arm_next_wakeup(arm_next_timeslice_tick);
}

View File

@ -169,28 +169,43 @@ impl TimeDriver {
#[esp_hal::ram]
extern "C" fn timer_tick_handler(#[cfg(xtensa)] _context: &mut esp_hal::trapframe::TrapFrame) {
SCHEDULER.with(|scheduler| {
unwrap!(scheduler.time_driver.as_mut())
.timer
.clear_interrupt();
let time_driver = unwrap!(scheduler.time_driver.as_mut());
scheduler.event.set_timer_event();
time_driver.timer.clear_interrupt();
// `Scheduler::switch_task` must be called on a single interrupt priority level only.
// To ensure this, we call yield_task to pend the software interrupt.
//
// RISC-V: esp-hal's interrupt handler can process multiple interrupts before handing
// control back to the interrupted context. This can result in two task switches
// before the first one's context save could run. To prevent this, here we only
// trigger the software interrupt which will then run the scheduler.
//
// ESP32: Because on ESP32 the software interrupt is triggered at priority 3 but
// the timer interrupt is triggered at priority 1, we need to trigger the
// software interrupt manually.
cfg_if::cfg_if! {
if #[cfg(any(riscv, esp32))] {
crate::task::yield_task();
} else {
scheduler.switch_task(_context)
let mut switch_tasks = false;
time_driver.handle_alarm(|ready_task| {
debug_assert_eq!(
ready_task.state(),
crate::task::TaskState::Sleeping,
"task: {:?}",
ready_task
);
debug!("Task {:?} is ready", ready_task);
scheduler.run_queue.mark_task_ready(ready_task);
switch_tasks = true;
});
if switch_tasks {
// `Scheduler::switch_task` must be called on a single interrupt priority level only.
// To ensure this, we call yield_task to pend the software interrupt.
//
// RISC-V: esp-hal's interrupt handler can process multiple interrupts before handing
// control back to the interrupted context. This can result in two task switches
// before the first one's context save could run. To prevent this, here we only
// trigger the software interrupt which will then run the scheduler.
//
// ESP32: Because on ESP32 the software interrupt is triggered at priority 3 but
// the timer interrupt is triggered at priority 1, we need to trigger the
// software interrupt manually.
cfg_if::cfg_if! {
if #[cfg(any(riscv, esp32))] {
crate::task::yield_task();
} else {
scheduler.switch_task(_context)
}
}
}
});