Skip to content

kernel::scheduler

Preemptive priority scheduler.

48 application priority levels plus a reserved idle level (lower number = higher priority), round-robin within a level via a 10 ms quantum. Supports priority inheritance, sleep, queue/mutex blocking, and a pending-switch flag consumed by the trap handler.

Switching model (plan W1.2): all context switches happen in the trap handler (crate::switch::flint_trap) and resume via rfe. Cooperative operations (sleep/yield/block) mutate scheduler state then raise the software interrupt so the switch occurs through the same path. Nothing switches by returning up a call stack anymore.

Concurrency (plan W2.2): the global scheduler is mutated either from task context inside a cs_with critical section, or from the trap handler with interrupts already masked. Both exclude each other.

struct Scheduler {
pub tasks: [Option<TaskControlBlock>; 32],
pub current_per_core: [u32; 2],
pub ready_mask: u64,
// some private fields omitted
}

Implements: Default

References: TaskControlBlock

struct TaskControlBlock {
pub id: u32,
pub name: &'static str,
pub entry: Option<fn(..)>,
pub affinity: Affinity,
pub base_prio: u8,
pub priority: u8,
pub state: TaskState,
pub stack_base: u32,
pub stack_size: u32,
pub stack_hwm: u32,
pub context: crate::arch::Context,
pub quantum: u32,
pub sleep_until: u64,
pub boosted_from: Option<u8>,
pub blocked_on_mutex: Option<usize>,
pub heap_stack: bool,
}

Per-task control block.

References: Affinity, Context, TaskState

enum Affinity {
Any,
Core(hal::smp::CoreId),
}

Which core(s) a task may run on.

The default is [Affinity::Any], because most tasks genuinely do not care and pinning what does not need pinning throws away the second core.

Pinning exists for the ones that do: a driver whose peripheral interrupt is routed to one core’s matrix cannot service it from the other, and anything with a hard timing budget does not want to be moved.

Implements: Clone, Copy, Debug, Default, Eq, PartialEq, StructuralPartialEq

References: CoreId

enum TaskState {
Init,
Ready,
Running,
BlockedSend,
BlockedRecv,
BlockedMutex,
BlockedSleep,
Suspended,
Deleting,
Faulted,
}

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq

fn join_current_core(idle: u32)

Attach the calling secondary core to an already-created pinned idle task.

The joined bit is deliberately the last write. Until the task is both Running and installed as this core’s current, affinity requests must continue to reject the core.

fn request_switch()

Request a context switch: set the flag and raise the software interrupt so the switch happens in the trap handler.

fn request_switch_on(core: hal::smp::CoreId)

Request a switch on another joined core.

The pending flag is published before the inter-core notification. If the architecture has no remote interrupt hook, the next periodic tick still observes the request.

References: CoreId

fn set_pending_switch()

Record that a switch is owed.

fn set_pending_switch_on(core: hal::smp::CoreId)

Record scheduling work for a particular joined core.

References: CoreId

fn switch_pending() -> bool

Whether this core has scheduling work pending, without consuming it.

fn take_pending_switch() -> bool

Consume the pending-switch flag, returning whether one was owed.

fn try_with<R, impl FnOnce(&mut Scheduler) -> R>(f: impl FnOnce) -> Option<R>

Run f only if the scheduler is free, rather than waiting.

For diagnostics that must not hang — a fault handler reporting task state cannot afford to block on a lock whose holder may be the thing that faulted.

fn with<R, impl FnOnce(&mut Scheduler) -> R>(f: impl FnOnce) -> R

Run f with the scheduler locked. Safe from task or interrupt context, on either core.

Keep f short: interrupts are masked on this core throughout, and the other core spins if it wants the scheduler meanwhile.

const DEFAULT_QUANTUM_MS: u32;
const IDLE_PRIORITY: u8;

The lowest possible priority value (idle). Higher number = lower priority.

Deliberately one below anything application code can request. When this was 47 it collided with Background(15), so a task spawned at the lowest public priority became a round-robin peer of idle and shared time-slices with it, rather than idle being a guaranteed last resort.

const MAX_PUBLIC_PRIORITY: u8;

Highest priority value any public Priority can encode to (Background(15) == 0x2F).

const MAX_TASKS: usize;
const NUM_PRIORITIES: usize;

Effective priority levels.

The public Priority encoding spans 0x00..=0x2F (3 bands × 16 levels, see Priority::numeric), so 48 values are reachable from application code. One further level exists above them, reserved for idle – hence 49.