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). // If this is NOT set, the event is a cooperative yield of some sort (time slicing, self-yield).
const TASK_BLOCKED: usize = 1 << 0; 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 { fn contains(self, bit: usize) -> bool {
self.0 & bit != 0 self.0 & bit != 0
} }
@ -49,17 +46,9 @@ impl SchedulerEvent {
self.set(Self::TASK_BLOCKED) 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 { pub(crate) fn is_cooperative_yield(self) -> bool {
!self.contains(Self::TASK_BLOCKED) !self.contains(Self::TASK_BLOCKED)
} }
pub(crate) fn is_timer_event(self) -> bool {
self.contains(Self::TIMER_EVENT)
}
} }
pub(crate) struct SchedulerState { pub(crate) struct SchedulerState {
@ -154,22 +143,6 @@ impl SchedulerState {
self.delete_marked_tasks(); self.delete_marked_tasks();
let event = core::mem::take(&mut self.event); 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() if event.is_cooperative_yield()
&& let Some(current_task) = self.current_task && let Some(current_task) = self.current_task
@ -232,6 +205,7 @@ impl SchedulerState {
let arm_next_timeslice_tick = priority let arm_next_timeslice_tick = priority
.map(|p| !self.run_queue.is_level_empty(p)) .map(|p| !self.run_queue.is_level_empty(p))
.unwrap_or(false); .unwrap_or(false);
let time_driver = unwrap!(self.time_driver.as_mut());
time_driver.arm_next_wakeup(arm_next_timeslice_tick); time_driver.arm_next_wakeup(arm_next_timeslice_tick);
} }

View File

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