mod state;
use state::{RuntimeGuardState, RuntimeIrqState, RuntimePreemptState, SchedulerBatonState};
#[ax_percpu::def_percpu]
static RUNTIME_GUARD_STATE: RuntimeGuardState = RuntimeGuardState::new();
pub(crate) fn assert_boot_preemption_held() {
let state = read_state();
assert_eq!(
state.irq,
RuntimeIrqState::new(),
"IRQ guard crossed a runtime boot phase"
);
assert_eq!(
state.preempt,
RuntimePreemptState::new(),
"preemption guard crossed a runtime boot phase"
);
assert_eq!(
current_preempt_depth(),
1,
"boot current must retain PREEMPT_DISABLED until scheduler publication"
);
}
pub(crate) fn release_bootstrap_preemption() {
let state = read_state();
assert!(state.irq.is_clear() && state.preempt.is_clear());
assert_eq!(
current_preempt_depth(),
1,
"bootstrap release requires the exact Linux boot preemption depth"
);
with_current_cpu_pin(cpu_local::release_bootstrap_preemption)
.unwrap_or_else(|error| panic!("bootstrap preemption owner is invalid: {error}"));
assert_eq!(
current_preempt_depth(),
0,
"bootstrap preemption depth must be released exactly once"
);
assert!(
ax_cpu::interrupt::irqs_enabled(),
"multitask bootstrap must publish IRQ delivery before releasing PREEMPT_DISABLED"
);
ax_task::runtime::switch::schedule_current_cpu()
.unwrap_or_else(|error| panic!("bootstrap scheduler entry failed: {error}"));
}
#[cfg(feature = "uspace")]
pub(crate) fn prepare_user_return() -> Result<(), ax_task::thread::TaskError> {
loop {
if !ax_cpu::interrupt::irqs_enabled() {
return Err(ax_task::thread::TaskError::UnsafeContext);
}
ax_cpu::interrupt::disable_irqs();
let pending = with_current_cpu_pin(|pin| {
let state = RUNTIME_GUARD_STATE.with_current(pin, |state| *state);
if !state.irq.is_clear()
|| !state.preempt.is_clear()
|| current_preempt_depth_pinned(pin) != 0
|| in_hard_irq_on(pin)
{
return Err(ax_task::thread::TaskError::UnsafeContext);
}
crate::thread::current_cpu_needs_reschedule_pinned(pin)
});
let pending = match pending {
Ok(pending) => pending,
Err(error) => {
ax_cpu::interrupt::enable_irqs();
return Err(error);
}
};
if !pending {
return Ok(());
}
ax_cpu::interrupt::enable_irqs();
ax_task::runtime::switch::schedule_current_cpu()?;
}
}
pub(crate) fn validate_schedule_context(
_origin: ax_task::runtime::switch::RuntimeScheduleOrigin,
) -> ax_task::runtime::RuntimeStatus {
use ax_task::runtime::RuntimeStatus;
if !ax_cpu::interrupt::irqs_enabled() {
return RuntimeStatus::UnsafeContext;
}
ax_cpu::interrupt::disable_irqs();
let valid = with_current_cpu_pin(|pin| {
let state = RUNTIME_GUARD_STATE.with_current(pin, |state| *state);
state.irq.is_clear()
&& state.preempt.is_clear()
&& current_preempt_depth_pinned(pin) == 0
&& !in_hard_irq_on(pin)
});
ax_cpu::interrupt::enable_irqs();
if valid {
RuntimeStatus::Success
} else {
RuntimeStatus::UnsafeContext
}
}
pub(crate) fn validate_owner_cpu_context() -> ax_task::runtime::RuntimeStatus {
use ax_task::runtime::RuntimeStatus;
if ax_cpu::interrupt::irqs_enabled() {
return RuntimeStatus::UnsafeContext;
}
with_current_cpu_pin(|pin| {
let state = RUNTIME_GUARD_STATE.with_current(pin, |state| *state);
if in_hard_irq_on(pin) && state.irq.is_clear() {
if state.preempt.is_clear() && current_preempt_depth_pinned(pin) != 0 {
return RuntimeStatus::Success;
}
return RuntimeStatus::UnsafeContext;
}
if state.owns_cpu_context() {
RuntimeStatus::Success
} else {
RuntimeStatus::UnsafeContext
}
})
}
#[cfg(not(any(test, feature = "host-test")))]
pub(crate) fn inherits_hardirq_cpu_owner() -> bool {
if ax_cpu::interrupt::irqs_enabled() {
return false;
}
with_current_cpu_pin(|pin| {
let state = RUNTIME_GUARD_STATE.with_current(pin, |state| *state);
in_hard_irq_on(pin) && state.irq.is_clear() && state.preempt.is_clear()
})
}
#[cfg(feature = "fs")]
pub(crate) fn in_atomic_context() -> bool {
if !ax_cpu::interrupt::irqs_enabled() {
return true;
}
if ax_hal::irq::in_irq_context() {
return true;
}
ax_cpu::interrupt::disable_irqs();
let guarded = read_state().has_context_guard(current_preempt_depth());
ax_cpu::interrupt::enable_irqs();
guarded
}
#[cfg(not(any(test, feature = "host-test")))]
pub(crate) fn enter_irq() {
let outer_irqs_enabled = ax_cpu::interrupt::irqs_enabled();
ax_cpu::interrupt::disable_irqs();
with_guard_state_mut(|state| state.enter_irq(outer_irqs_enabled));
}
#[cfg(not(any(test, feature = "host-test")))]
pub(crate) fn exit_irq(owner: &'static str) {
let (must_schedule, restore_irqs) = with_current_cpu_pin(|pin| {
let preempt_depth = current_preempt_depth_pinned(pin);
with_guard_state_mut_pinned(pin, |state| {
if irq_guard_exit_needs_schedule(state, preempt_depth, || {
let needs_reschedule = unsafe {
ax_task::runtime::cpu::current_needs_immediate_scheduler_work_pinned()
}
.unwrap_or_else(|error| {
panic!("IRQ guard exit lost the current scheduler owner: {error:?}")
});
if needs_reschedule {
publish_preemption_pending_pinned(pin, true);
}
needs_reschedule
}) {
(true, false)
} else {
(false, state.exit_irq(owner))
}
})
});
if must_schedule {
if let Err(error) =
unsafe { ax_task::runtime::switch::schedule_current_cpu_from_irq_guard_exit() }
{
panic!("IRQ-guard-exit scheduler entry failed: {error}");
}
return;
}
if restore_irqs {
ax_cpu::interrupt::enable_irqs();
}
}
#[cfg(not(any(test, feature = "host-test")))]
pub(crate) fn publish_local_scheduler_work() -> bool {
assert!(
!ax_cpu::interrupt::irqs_enabled(),
"local scheduler-work query requires an IRQ publication guard"
);
with_current_cpu_pin(|pin| {
publish_preemption_pending_pinned(pin, true);
in_hard_irq_on(pin)
|| RUNTIME_GUARD_STATE.with_current(pin, |state| {
state.local_scheduler_work_is_self_serviced(current_preempt_depth_pinned(pin))
})
})
}
#[cfg(all(
any(test, feature = "host-test"),
any(feature = "ipi", feature = "wake-ipi")
))]
pub(crate) const fn publish_local_scheduler_work() -> bool {
false
}
pub(crate) fn finish_initial_context_switch() {
assert_eq!(
current_preempt_depth(),
0,
"initial scheduler frame must own only the transferred scheduler baton"
);
let needs_reschedule = {
unsafe { ax_task::runtime::cpu::current_needs_immediate_scheduler_work_pinned() }
.unwrap_or_else(|error| panic!("initial scheduler tail lost its owner: {error:?}"))
};
let _task_context_safe = exit_scheduler_frame_guard_inner(
ax_task::runtime::switch::RuntimeSchedulerReturn::Task,
needs_reschedule,
"initial scheduler frame",
);
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum PreemptExitOrigin {
Task,
IrqReturn,
}
impl PreemptExitOrigin {
const fn is_irq_return(self) -> bool {
matches!(self, Self::IrqReturn)
}
}
#[cfg(not(test))]
fn exit_lock_preempt(origin: PreemptExitOrigin, token: cpu_local::PreemptionToken) {
let irq_return = origin.is_irq_return();
assert!(
!irq_return || !ax_cpu::interrupt::irqs_enabled(),
"IRQ-return preemption exit requires hardware IRQs disabled"
);
let cpu_local::PreemptionExit::Pending(pending) = cpu_local::finish_preemption(token) else {
return;
};
let irqs_were_enabled = ax_cpu::interrupt::irqs_enabled();
if irqs_were_enabled {
ax_cpu::interrupt::disable_irqs();
}
let must_schedule = claim_preempt_exit_scheduler(origin, irqs_were_enabled);
pending.release();
if must_schedule {
use ax_task::runtime::switch::RuntimeSchedulerEntry;
let entry = match origin {
PreemptExitOrigin::Task => RuntimeSchedulerEntry::PreemptExit,
PreemptExitOrigin::IrqReturn => RuntimeSchedulerEntry::IrqReturn,
};
if let Err(error) =
unsafe { ax_task::runtime::switch::schedule_current_cpu_from_preempt_exit(entry) }
{
panic!("preemption-exit scheduler entry failed: {error}");
}
assert_eq!(
ax_cpu::interrupt::irqs_enabled(),
!irq_return,
"scheduler continuation restored the wrong hardware IRQ state"
);
return;
}
if !irq_return && irqs_were_enabled {
ax_cpu::interrupt::enable_irqs();
}
}
fn claim_preempt_exit_scheduler(origin: PreemptExitOrigin, irqs_were_enabled: bool) -> bool {
with_current_cpu_pin(|pin| {
let preempt_depth = current_preempt_depth_pinned(pin);
with_guard_state_mut_pinned(pin, |state| {
let must_schedule = preempt_exit_needs_schedule(
state,
preempt_depth,
origin,
irqs_were_enabled,
|| in_hard_irq_on(pin),
);
if must_schedule {
assert!(
state.claim_preempt_exit_scheduler(preempt_depth),
"final preemption depth could not become the scheduler baton"
);
}
must_schedule
})
})
}
#[cfg(not(any(test, feature = "host-test")))]
#[inline(always)]
pub(crate) fn enter_lock_preempt() -> Option<cpu_local::PreemptionToken> {
if !ax_cpu::interrupt::irqs_enabled() {
let state = read_state();
if state.owns_cpu_context()
|| (state.irq.is_clear()
&& state.preempt.is_clear()
&& with_current_cpu_pin(in_hard_irq_on))
{
return None;
}
}
let token = cpu_local::enter_preemption();
Some(token)
}
#[cfg(any(test, feature = "host-test"))]
pub(crate) const fn enter_lock_preempt() -> Option<cpu_local::PreemptionToken> {
None
}
#[cfg(not(test))]
pub(crate) fn exit_preempt(token: cpu_local::PreemptionToken) {
exit_lock_preempt(PreemptExitOrigin::Task, token);
}
#[cfg(test)]
pub(crate) fn exit_preempt(_token: cpu_local::PreemptionToken) {
panic!("unit-test runtime cannot exit an unowned preemption guard")
}
#[cfg(not(test))]
pub(crate) fn exit_preempt_from_irq_return(token: cpu_local::PreemptionToken) {
exit_lock_preempt(PreemptExitOrigin::IrqReturn, token);
}
#[cfg(test)]
pub(crate) fn exit_preempt_from_irq_return(_token: cpu_local::PreemptionToken) {
panic!("unit-test runtime cannot exit an unowned IRQ-return guard")
}
fn preempt_exit_needs_schedule(
state: &RuntimeGuardState,
preempt_depth: u32,
origin: PreemptExitOrigin,
irqs_were_enabled: bool,
in_hard_irq: impl FnOnce() -> bool,
) -> bool {
state.irq.is_clear()
&& preempt_depth == 1
&& matches!(state.preempt.scheduler_baton, SchedulerBatonState::Finished)
&& (origin.is_irq_return() || irqs_were_enabled)
&& !in_hard_irq()
}
#[cfg(any(test, not(feature = "host-test")))]
fn irq_guard_exit_needs_schedule(
state: &RuntimeGuardState,
preempt_depth: u32,
needs_reschedule: impl FnOnce() -> bool,
) -> bool {
state.irq.depth == 1
&& state.irq.outer_irqs_enabled
&& preempt_depth == 0
&& state.preempt.is_clear()
&& needs_reschedule()
}
pub(crate) fn enter_scheduler_frame_guard(
_origin: ax_task::runtime::switch::RuntimeScheduleOrigin,
entry: ax_task::runtime::switch::RuntimeSchedulerEntry,
) -> ax_task::runtime::switch::RuntimeSchedulerFrameEnterResult {
use ax_task::runtime::switch::{RuntimeSchedulerEntry, RuntimeSchedulerFrameEnterResult};
let irqs_enabled = ax_cpu::interrupt::irqs_enabled();
if entry == RuntimeSchedulerEntry::IrqReturnContinuation {
if irqs_enabled || in_hard_irq() {
return RuntimeSchedulerFrameEnterResult::failure();
}
#[cfg(feature = "qperf-metrics")]
crate::thread::record_irq_return_scheduler_continuation();
if !enter_irq_return_continuation_scheduler() {
return RuntimeSchedulerFrameEnterResult::failure();
}
return with_current_cpu_pin(crate::thread::scheduler_frame_capabilities);
}
let raw_state_valid = match entry {
RuntimeSchedulerEntry::Task => irqs_enabled,
RuntimeSchedulerEntry::PreemptExit
| RuntimeSchedulerEntry::IrqReturn
| RuntimeSchedulerEntry::IrqGuardExit => !irqs_enabled,
RuntimeSchedulerEntry::IrqReturnContinuation => unreachable!(),
};
if !raw_state_valid {
return RuntimeSchedulerFrameEnterResult::failure();
}
ax_cpu::interrupt::disable_irqs();
let capabilities = claim_scheduler_cpu_state(entry);
let Some(capabilities) = capabilities else {
if irqs_enabled {
ax_cpu::interrupt::enable_irqs();
}
return RuntimeSchedulerFrameEnterResult::failure();
};
capabilities
}
fn enter_irq_return_continuation_scheduler() -> bool {
assert!(
!ax_cpu::interrupt::irqs_enabled(),
"IRQ-return continuation must enter with hardware IRQs disabled"
);
let Some(token) = enter_lock_preempt() else {
return false;
};
ax_cpu::interrupt::enable_irqs();
core::hint::spin_loop();
ax_cpu::interrupt::disable_irqs();
#[cfg(feature = "qperf-metrics")]
crate::thread::record_irq_return_scheduler_window();
let cpu_local::PreemptionExit::Pending(pending) = cpu_local::finish_preemption(token) else {
panic!("IRQ-return continuation lost its pending scheduler request");
};
let preclaimed =
with_guard_state_mut(|state| state.claim_preempt_exit_scheduler(current_preempt_depth()));
pending.release();
preclaimed
&& with_guard_state_mut(|state| state.enter_preclaimed_scheduler(current_preempt_depth()))
}
fn claim_scheduler_cpu_state(
entry: ax_task::runtime::switch::RuntimeSchedulerEntry,
) -> Option<ax_task::runtime::switch::RuntimeSchedulerFrameEnterResult> {
use ax_task::runtime::switch::RuntimeSchedulerEntry;
with_current_cpu_pin(|pin| {
if in_hard_irq_on(pin) {
return None;
}
let preempt_depth = current_preempt_depth_pinned(pin);
let claimed = with_guard_state_mut_pinned(pin, |state| match entry {
RuntimeSchedulerEntry::Task => state.claim_task_scheduler(preempt_depth),
RuntimeSchedulerEntry::PreemptExit | RuntimeSchedulerEntry::IrqReturn => {
state.enter_preclaimed_scheduler(preempt_depth)
}
RuntimeSchedulerEntry::IrqReturnContinuation => unreachable!(),
RuntimeSchedulerEntry::IrqGuardExit => state.claim_irq_exit_scheduler(preempt_depth),
});
claimed.then(|| crate::thread::scheduler_frame_capabilities(pin))
})
}
pub(crate) fn exit_scheduler_frame_guard(
return_to: ax_task::runtime::switch::RuntimeSchedulerReturn,
needs_reschedule: bool,
) -> bool {
exit_scheduler_frame_guard_inner(return_to, needs_reschedule, "resumed scheduler frame")
}
fn exit_scheduler_frame_guard_inner(
return_to: ax_task::runtime::switch::RuntimeSchedulerReturn,
needs_reschedule: bool,
owner: &'static str,
) -> bool {
use ax_task::runtime::switch::RuntimeSchedulerReturn;
assert!(
!ax_cpu::interrupt::irqs_enabled(),
"scheduler baton must keep hardware IRQs disabled until switch tail"
);
finish_scheduler_cpu_transaction(needs_reschedule, owner);
match return_to {
RuntimeSchedulerReturn::Task => {
ax_cpu::interrupt::enable_irqs();
true
}
RuntimeSchedulerReturn::IrqReturn => false,
}
}
fn finish_scheduler_cpu_transaction(needs_reschedule: bool, owner: &'static str) {
with_current_cpu_pin(|pin| {
publish_preemption_pending_pinned(pin, needs_reschedule);
with_guard_state_mut_pinned(pin, |state| state.exit_scheduler_preempt(owner));
crate::clock_event_runtime::finish_deferred_rearm_pinned(pin);
});
}
#[must_use = "the prepared scheduler baton must be transferred to the switch tail"]
pub(crate) struct PreparedSchedulerSwitchBaton<'pin, 'cpu> {
pin: &'pin cpu_local::CpuPin<'cpu>,
}
impl PreparedSchedulerSwitchBaton<'_, '_> {
#[inline(always)]
pub(crate) fn transfer(self) {
unsafe {
cpu_local::with_exclusive_cpu(self.pin, |exclusive| {
RUNTIME_GUARD_STATE.with_current_mut(exclusive, |state| {
state.commit_prepared_scheduler_preempt();
});
});
}
}
}
pub(crate) fn prepare_scheduler_switch_baton<'pin, 'cpu>(
pin: &'pin cpu_local::CpuPin<'cpu>,
) -> PreparedSchedulerSwitchBaton<'pin, 'cpu> {
assert!(
!ax_cpu::interrupt::irqs_enabled(),
"scheduler switch requires local IRQs disabled"
);
let state = RUNTIME_GUARD_STATE.with_current(pin, |state| *state);
assert!(
state.irq.is_clear() && state.preempt.has_active_scheduler_baton(),
"scheduler switch requires the active CPU-local scheduler baton"
);
PreparedSchedulerSwitchBaton { pin }
}
fn in_hard_irq() -> bool {
ax_hal::irq::in_irq_context()
}
fn in_hard_irq_on(pin: &cpu_local::CpuPin<'_>) -> bool {
ax_hal::irq::in_irq_context_pinned(pin)
}
#[inline(always)]
fn read_state() -> RuntimeGuardState {
if !ax_cpu::interrupt::irqs_enabled() {
return unsafe { RUNTIME_GUARD_STATE.with_current_cpu_area(|state| *state) }
.unwrap_or_else(|error| panic!("runtime CPU-owner state is invalid: {error}"));
}
with_guard_state(|state| *state)
}
#[inline(always)]
fn current_preempt_depth() -> u32 {
with_current_cpu_pin(current_preempt_depth_pinned)
}
#[inline(always)]
fn current_preempt_depth_pinned(pin: &cpu_local::CpuPin<'_>) -> u32 {
cpu_local::preemption_snapshot(pin)
.unwrap_or_else(|error| panic!("architecture preemption state is invalid: {error}"))
.depth()
}
fn publish_preemption_pending_pinned(pin: &cpu_local::CpuPin<'_>, pending: bool) {
if pending {
cpu_local::set_preemption_pending(pin)
} else {
cpu_local::clear_preemption_pending(pin)
}
.unwrap_or_else(|error| panic!("architecture preemption publication failed: {error}"));
}
fn with_current_cpu_pin<R>(
operation: impl for<'scope> FnOnce(&cpu_local::CpuPin<'scope>) -> R,
) -> R {
let restore_irqs = ax_cpu::interrupt::irqs_enabled();
if restore_irqs {
ax_cpu::interrupt::disable_irqs();
}
let result = unsafe { cpu_local::with_cpu_pin(operation) }
.unwrap_or_else(|error| panic!("runtime CPU-local state is invalid: {error}"));
if restore_irqs {
ax_cpu::interrupt::enable_irqs();
}
result
}
fn with_guard_state<R>(operation: impl for<'value> FnOnce(&'value RuntimeGuardState) -> R) -> R {
with_current_cpu_pin(|pin| RUNTIME_GUARD_STATE.with_current(pin, operation))
}
fn with_guard_state_mut<R>(
operation: impl for<'value> FnOnce(&'value mut RuntimeGuardState) -> R,
) -> R {
with_current_cpu_pin(|pin| with_guard_state_mut_pinned(pin, operation))
}
fn with_guard_state_mut_pinned<R>(
pin: &cpu_local::CpuPin<'_>,
operation: impl for<'value> FnOnce(&'value mut RuntimeGuardState) -> R,
) -> R {
assert!(
!ax_cpu::interrupt::irqs_enabled(),
"mutable runtime guard state requires local IRQ exclusion"
);
unsafe {
cpu_local::with_exclusive_cpu(pin, |exclusive| {
RUNTIME_GUARD_STATE.with_current_mut(exclusive, operation)
})
}
}
#[cfg(test)]
mod tests;