use super::*;
static TASK_SYSTEM: LazyInit<Pin<Box<TaskSystem>>> = LazyInit::new();
static PRIMARY_BOOTSTRAP_THREAD: LazyInit<PrimaryBootstrapThread> = LazyInit::new();
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct PrimaryBootstrapThread(ThreadId);
#[ax_percpu::def_percpu]
static CPU_LOCAL: LazyInit<Pin<Box<CpuLocal>>> = LazyInit::new();
#[ax_percpu::def_percpu]
static CPU_REMOTE_HANDLE: LazyInit<usize> = LazyInit::new();
#[ax_percpu::def_percpu]
static CPU_LOCAL_OWNER_HANDLE: usize = 0;
#[cfg(kernel_tls)]
#[ax_percpu::def_percpu]
static EARLY_BOOTSTRAP_TLS: usize = 0;
#[cfg(feature = "uspace")]
#[ax_percpu::def_percpu]
static OFFLINE_KERNEL_ROOT: usize = 0;
pub(super) unsafe fn with_current_cpu_pin<R>(
operation: impl for<'scope> FnOnce(&CpuPin<'scope>) -> R,
) -> R {
unsafe { ax_hal::percpu::with_cpu_pin(operation) }
.unwrap_or_else(|error| panic!("task runtime CPU-local state is invalid: {error}"))
}
fn with_irq_cpu_pin<R>(operation: impl for<'scope> FnOnce(&CpuPin<'scope>) -> R) -> R {
let _irq = crate::task::sync::IrqSaveGuard::new();
unsafe { with_current_cpu_pin(operation) }
}
pub(crate) fn initialize_primary(cpu_id: usize) -> Result<(), TaskError> {
let config = TaskSystemConfig::new(ax_hal::cpu_num())
.with_balance_interval_ns(crate::build_info::SCHEDULER_TICK_INTERVAL_NANOS);
let system = Box::pin(TaskSystem::new(config)?);
TASK_SYSTEM.init_once(system);
let bootstrap = initialize_current_cpu(cpu_id)?;
PRIMARY_BOOTSTRAP_THREAD.init_once(PrimaryBootstrapThread(bootstrap));
Ok(())
}
#[cfg(kernel_tls)]
pub(crate) fn initialize_early_bootstrap_tls() -> Result<(), TaskError> {
let existing = unsafe { with_current_cpu_pin(|pin| EARLY_BOOTSTRAP_TLS.read_current(pin)) };
assert_eq!(existing, 0, "bootstrap TLS initialized twice on one CPU");
let result = allocate_runtime_tls();
if result.status != RuntimeStatus::Success {
return Err(runtime_status_error(result.status));
}
if result.handle == 0 {
return Err(TaskError::InvalidRuntimeHandle);
}
let early_tls = unsafe { TlsHandle::from_raw(result.handle) };
unsafe {
with_current_cpu_pin(|pin| {
EARLY_BOOTSTRAP_TLS.write_current(pin, result.handle);
ax_hal::percpu::install_bootstrap_kernel_tls(
pin,
ax_hal::context::KernelTlsBase::new(runtime_tls_pointer(early_tls)),
);
})
};
Ok(())
}
#[cfg(feature = "smp")]
pub(crate) fn initialize_secondary(cpu_id: usize) -> Result<(), TaskError> {
initialize_current_cpu(cpu_id).map(|_| ())
}
#[must_use = "local IRQs may be enabled only after consuming this publication proof"]
pub(crate) struct PublishedCpuOnline(());
pub(crate) fn publish_current_cpu_online() -> Result<PublishedCpuOnline, TaskError> {
let system = task_system().ok_or(TaskError::NotInitialized)?;
with_current_cpu_local_mut_for_boot(|cpu| system.bring_cpu_online(cpu))?;
Ok(PublishedCpuOnline(()))
}
pub(crate) fn start_deferred_task_work_service() -> Result<(), TaskError> {
ax_task::runtime::service::start_deferred_task_work_service()
}
pub(crate) fn start_current_ktimer_service() -> Result<(), TaskError> {
ax_task::runtime::service::start_current_ktimer_service()
}
pub(crate) fn run_idle() -> ! {
let (current, idle) = with_irq_cpu_pin(|pin| {
let cpu = current_cpu_remote(pin)
.expect("idle entry requires the initialized current-CPU scheduler endpoint");
(cpu.current_thread(), cpu.idle_thread())
});
let entry_action = idle_entry_action(current, idle)
.unwrap_or_else(|error| panic!("idle loop entered without scheduler ownership: {error}"));
if entry_action == IdleEntryAction::RetireBootstrap {
match ax_task::thread::current::exit_current_thread() {
Err(error) => panic!("failed to retire secondary bootstrap thread: {error}"),
Ok(()) => panic!("retired secondary bootstrap thread unexpectedly resumed"),
}
}
loop {
ax_task::runtime::switch::schedule_current_cpu()
.unwrap_or_else(|error| panic!("idle scheduler safe point failed: {error}"));
#[cfg(feature = "fault-injection")]
super::creation_probe::service_idle_cpu_round_trip();
ax_task::runtime::cpu::idle_current_cpu_once()
.unwrap_or_else(|error| panic!("idle wait handshake failed: {error}"));
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum IdleEntryAction {
RetireBootstrap,
RunIdle,
}
pub(super) fn idle_entry_action(
current: Option<ThreadId>,
idle: Option<ThreadId>,
) -> Result<IdleEntryAction, TaskError> {
match (current, idle) {
(Some(current), Some(idle)) if current == idle => Ok(IdleEntryAction::RunIdle),
(Some(_), Some(_)) => Ok(IdleEntryAction::RetireBootstrap),
_ => Err(TaskError::InvalidConfiguration),
}
}
fn initialize_current_cpu(cpu_id: usize) -> Result<ThreadId, TaskError> {
let system = task_system().ok_or(TaskError::NotInitialized)?;
let cpu_id = u32::try_from(cpu_id).map_err(|_| TaskError::InvalidCpu(u32::MAX))?;
let owner = CpuId::new(cpu_id);
let remote_handle = system.runtime_cpu_remote_handle(owner).into_raw();
if remote_handle == 0 {
return Err(TaskError::InvalidCpu(cpu_id));
}
#[cfg(feature = "uspace")]
{
let kernel_root = if cfg!(any(target_arch = "x86_64", target_arch = "riscv64")) {
ax_cpu::mmu::read_kernel_page_table().as_usize()
} else {
0
};
unsafe { with_current_cpu_pin(|pin| OFFLINE_KERNEL_ROOT.write_current(pin, kernel_root)) };
}
let mut cpu = system.create_cpu_local(owner)?;
let mut owner_affinity = CpuSet::empty(ax_hal::cpu_num());
if !owner_affinity.insert(owner) {
return Err(TaskError::InvalidCpu(cpu_id));
}
let bootstrap_resources = create_bootstrap_resources()?;
let bootstrap_context = bootstrap_resources.context();
#[cfg(kernel_tls)]
let bootstrap_tls = bootstrap_resources.tls();
let bootstrap = system.install_bootstrap_thread(cpu.as_mut(), unsafe {
ThreadSpec::new(SchedulePolicy::default())
.with_affinity(owner_affinity.clone())
.with_resources(bootstrap_resources)
})?;
let bootstrap_thread = bootstrap.id();
drop(bootstrap);
#[cfg(kernel_tls)]
let bootstrap_kernel_tls = runtime_tls_pointer(bootstrap_tls);
#[cfg(not(kernel_tls))]
let bootstrap_kernel_tls = 0;
unsafe {
with_current_cpu_pin(|pin| {
bind_bootstrap_runtime_context(pin, bootstrap_context, bootstrap_kernel_tls)
})
}
.unwrap_or_else(|error| panic!("failed to publish bootstrap runtime context: {error}"));
#[cfg(kernel_tls)]
{
let early_tls = unsafe {
with_current_cpu_pin(|pin| {
let handle = EARLY_BOOTSTRAP_TLS.read_current(pin);
EARLY_BOOTSTRAP_TLS.write_current(pin, 0);
TlsHandle::from_raw(handle)
})
};
assert!(
!early_tls.is_none(),
"scheduler bootstrap requires early TLS ownership"
);
assert_eq!(
deallocate_runtime_tls(early_tls),
RuntimeStatus::Success,
"failed to release early bootstrap TLS"
);
}
let idle_resources = create_idle_resources();
system.register_idle_thread(cpu.as_mut(), unsafe {
ThreadSpec::new(SchedulePolicy::fair(Nice::ZERO, FairMode::Idle))
.with_affinity(owner_affinity)
.with_resources(idle_resources)
})?;
let owner_handle =
(unsafe { Pin::get_unchecked_mut(cpu.as_mut()) } as *mut CpuLocal).expose_provenance();
unsafe {
with_current_cpu_pin(|pin| {
ax_hal::percpu::with_exclusive_cpu(pin, |exclusive| {
CPU_REMOTE_HANDLE.with_current_mut(exclusive, |slot| {
slot.init_once(remote_handle);
});
CPU_LOCAL.with_current_mut(exclusive, |slot| {
slot.init_once(cpu);
});
});
CPU_LOCAL_OWNER_HANDLE.write_current(pin, owner_handle);
})
};
crate::guard::assert_boot_preemption_held();
Ok(bootstrap_thread)
}
pub(super) unsafe extern "C" fn idle_context_entry() -> ! {
finish_initial_scheduler_switch();
run_idle()
}
pub(super) fn task_system() -> Option<&'static TaskSystem> {
TASK_SYSTEM.get().map(|system| system.as_ref().get_ref())
}
fn with_current_cpu_local_mut_for_boot<R>(
operation: impl for<'cpu> FnOnce(Pin<&'cpu mut CpuLocal>) -> Result<R, TaskError>,
) -> Result<R, TaskError> {
if ax_cpu::interrupt::irqs_enabled() {
return Err(TaskError::InvalidConfiguration);
}
unsafe {
with_current_cpu_pin(|pin| {
ax_hal::percpu::with_exclusive_cpu(pin, |exclusive| {
CPU_LOCAL.with_current_mut(exclusive, |slot| {
let cpu = slot.get_mut().ok_or(TaskError::NotInitialized)?;
let actual = (cpu.as_ref().get_ref() as *const CpuLocal).expose_provenance();
let expected = CPU_LOCAL_OWNER_HANDLE.read_current(pin);
if expected == 0 || actual != expected {
return Err(TaskError::InvalidRuntimeHandle);
}
operation(cpu.as_mut())
})
})
})
}
}
pub(crate) fn current_cpu_remote(cpu_pin: &CpuPin) -> Option<&'static CpuRemote> {
let raw = current_cpu_remote_handle(cpu_pin).into_raw();
let remote = unsafe { &*ptr::with_exposed_provenance::<CpuRemote>(raw) };
remote.is_online().then_some(remote)
}
pub(super) fn cpu_remote(cpu: RuntimeCpuId) -> Option<&'static CpuRemote> {
task_system()?.cpu_remote(CpuId::new(cpu.as_u32()))
}
pub(super) fn current_cpu_owner_handles(cpu_pin: &CpuPin) -> CurrentCpuOwnerHandles {
let local = CPU_LOCAL_OWNER_HANDLE.read_current(cpu_pin);
assert_ne!(local, 0, "online scheduler CPU must own a CpuLocal handle");
let remote = current_cpu_remote_handle(cpu_pin);
unsafe { CurrentCpuOwnerHandles::new(CurrentCpuLocalHandle::from_raw(local), remote) }
}
fn current_cpu_remote_handle(cpu_pin: &CpuPin) -> CpuRemoteHandle {
CPU_REMOTE_HANDLE.with_current(cpu_pin, initialized_cpu_remote_handle)
}
fn initialized_cpu_remote_handle(slot: &LazyInit<usize>) -> CpuRemoteHandle {
let raw = *slot
.get()
.expect("online scheduler CPU must own a CpuRemote handle");
assert_ne!(raw, 0, "scheduler CpuRemote handle must not be null");
assert!(
raw.is_multiple_of(core::mem::align_of::<CpuRemote>()),
"scheduler CpuRemote handle must be aligned"
);
unsafe { CpuRemoteHandle::from_raw(raw) }
}
pub(super) unsafe fn scheduler_current_cpu_remote_handle() -> CpuRemoteHandle {
unsafe { CPU_REMOTE_HANDLE.with_current_cpu_area(initialized_cpu_remote_handle) }
.expect("scheduler current CPU area must be installed")
}
#[cfg(all(test, feature = "host-test"))]
mod tests {
use super::*;
#[test]
fn scheduler_remote_handle_uses_pre_pin_current_cpu_area() {
std::thread::spawn(|| {
const TEST_REMOTE_HANDLE: usize = 0x1000;
ax_hal::percpu::initialize_host_test_cpu();
unsafe {
with_current_cpu_pin(|pin| {
CPU_REMOTE_HANDLE.with_current(pin, |slot| {
slot.call_once(|| TEST_REMOTE_HANDLE);
});
})
};
cpu_local::host_test::reset_register_read_counts();
let handle = unsafe { scheduler_current_cpu_remote_handle() };
assert_eq!(handle.into_raw(), TEST_REMOTE_HANDLE);
assert_eq!(
cpu_local::host_test::register_read_counts(),
cpu_local::host_test::RegisterReadCounts {
cpu_base: 1,
current_context: 0,
binding_observations: 0,
initialized_area_validations: 0,
},
"scheduler endpoint lookup must use the pre-pin CPU-area boundary",
);
})
.join()
.expect("modeled scheduler CPU must complete endpoint lookup");
}
}
pub(super) fn primary_bootstrap_thread() -> Option<ThreadId> {
PRIMARY_BOOTSTRAP_THREAD.get().map(|thread| thread.0)
}
#[cfg(feature = "uspace")]
pub(super) fn offline_kernel_root(cpu_pin: &CpuPin) -> usize {
OFFLINE_KERNEL_ROOT.read_current(cpu_pin)
}