mod fifo_scheduler;
pub mod info;
use core::sync::atomic::{AtomicBool, Ordering};
use spin::Once;
use super::{preempt::cpu_local, processor, Task};
use crate::{cpu::PinCurrentCpu, prelude::*, task::disable_preempt, timer};
pub fn inject_scheduler(scheduler: &'static dyn Scheduler<Task>) {
SCHEDULER.call_once(|| scheduler);
timer::register_callback(|| {
SCHEDULER.get().unwrap().local_mut_rq_with(&mut |local_rq| {
if local_rq.update_current(UpdateFlags::Tick) {
cpu_local::set_need_preempt();
}
})
});
}
static SCHEDULER: Once<&'static dyn Scheduler<Task>> = Once::new();
pub trait Scheduler<T = Task>: Sync + Send {
fn enqueue(&self, runnable: Arc<T>, flags: EnqueueFlags) -> Option<u32>;
fn local_rq_with(&self, f: &mut dyn FnMut(&dyn LocalRunQueue<T>));
fn local_mut_rq_with(&self, f: &mut dyn FnMut(&mut dyn LocalRunQueue<T>));
}
pub trait LocalRunQueue<T = Task> {
fn current(&self) -> Option<&Arc<T>>;
fn update_current(&mut self, flags: UpdateFlags) -> bool;
fn pick_next_current(&mut self) -> Option<&Arc<T>>;
fn dequeue_current(&mut self) -> Option<Arc<T>>;
}
#[derive(PartialEq, Copy, Clone)]
pub enum EnqueueFlags {
Spawn,
Wake,
}
#[derive(PartialEq, Copy, Clone)]
pub enum UpdateFlags {
Tick,
Wait,
Yield,
}
pub(crate) fn might_preempt() {
if !cpu_local::should_preempt() {
return;
}
yield_now();
}
pub(crate) fn park_current(has_woken: &AtomicBool) {
let mut current = None;
let mut is_first_try = true;
reschedule(&mut |local_rq: &mut dyn LocalRunQueue| {
if is_first_try {
if has_woken.load(Ordering::Acquire) {
return ReschedAction::DoNothing;
}
current = local_rq.dequeue_current();
local_rq.update_current(UpdateFlags::Wait);
}
if let Some(next_task) = local_rq.pick_next_current() {
if Arc::ptr_eq(current.as_ref().unwrap(), next_task) {
return ReschedAction::DoNothing;
}
ReschedAction::SwitchTo(next_task.clone())
} else {
is_first_try = false;
ReschedAction::Retry
}
});
}
pub(crate) fn unpark_target(runnable: Arc<Task>) {
let need_preempt_info = SCHEDULER
.get()
.unwrap()
.enqueue(runnable, EnqueueFlags::Wake);
if need_preempt_info.is_some() {
let cpu_id = need_preempt_info.unwrap();
let preempt_guard = disable_preempt();
if cpu_id == preempt_guard.current_cpu() {
cpu_local::set_need_preempt();
}
}
}
pub(super) fn run_new_task(runnable: Arc<Task>) {
if !SCHEDULER.is_completed() {
fifo_scheduler::init();
}
let need_preempt_info = SCHEDULER
.get()
.unwrap()
.enqueue(runnable, EnqueueFlags::Spawn);
if need_preempt_info.is_some() {
let cpu_id = need_preempt_info.unwrap();
let preempt_guard = disable_preempt();
if cpu_id == preempt_guard.current_cpu() {
cpu_local::set_need_preempt();
}
}
might_preempt();
}
pub(super) fn exit_current() {
reschedule(&mut |local_rq: &mut dyn LocalRunQueue| {
let _ = local_rq.dequeue_current();
if let Some(next_task) = local_rq.pick_next_current() {
ReschedAction::SwitchTo(next_task.clone())
} else {
ReschedAction::Retry
}
})
}
pub(super) fn yield_now() {
reschedule(&mut |local_rq| {
local_rq.update_current(UpdateFlags::Yield);
if let Some(next_task) = local_rq.pick_next_current() {
ReschedAction::SwitchTo(next_task.clone())
} else {
ReschedAction::DoNothing
}
})
}
fn reschedule<F>(f: &mut F)
where
F: FnMut(&mut dyn LocalRunQueue) -> ReschedAction,
{
let next_task = loop {
let mut action = ReschedAction::DoNothing;
SCHEDULER.get().unwrap().local_mut_rq_with(&mut |rq| {
action = f(rq);
});
match action {
ReschedAction::DoNothing => {
return;
}
ReschedAction::Retry => {
continue;
}
ReschedAction::SwitchTo(next_task) => {
break next_task;
}
};
};
cpu_local::clear_need_preempt();
processor::switch_to_task(next_task);
}
enum ReschedAction {
DoNothing,
Retry,
SwitchTo(Arc<Task>),
}