use core::{pin::Pin, ptr::NonNull, sync::atomic::Ordering};
use crate::{CpuAreaRef, CpuLocalError, CpuPin, CurrentThreadHeader, ThreadSwitchError};
#[cfg(all(not(feature = "host-test"), target_arch = "aarch64"))]
mod aarch64;
#[cfg(feature = "host-test")]
mod host;
#[cfg(all(not(feature = "host-test"), target_arch = "loongarch64"))]
mod loongarch64;
#[cfg(all(
not(feature = "host-test"),
any(target_arch = "riscv32", target_arch = "riscv64")
))]
mod riscv;
#[cfg(all(not(feature = "host-test"), target_arch = "x86_64"))]
mod x86_64;
#[cfg(all(not(feature = "host-test"), target_arch = "aarch64"))]
use aarch64 as imp;
#[cfg(feature = "host-test")]
use host as imp;
#[cfg(all(not(feature = "host-test"), target_arch = "loongarch64"))]
use loongarch64 as imp;
#[cfg(all(
not(feature = "host-test"),
any(target_arch = "riscv32", target_arch = "riscv64")
))]
use riscv as imp;
#[cfg(all(not(feature = "host-test"), target_arch = "x86_64"))]
use x86_64 as imp;
#[cfg(all(
not(feature = "host-test"),
not(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch64"
))
))]
compile_error!("cpu-local supports x86_64, AArch64, RISC-V, and LoongArch64 only");
#[doc(hidden)]
pub unsafe fn install_cpu_area(area: CpuAreaRef) -> Result<(), CpuLocalError> {
imp::validate_environment()?;
let boot_thread = area.prefix().boot_thread().header();
let boot_pointer = boot_thread as *const CurrentThreadHeader as usize;
unsafe { imp::install_cpu_base(area.base(), boot_pointer) };
if unsafe { imp::read_cpu_base()? } != area.base() {
fatal_register_invariant();
}
Ok(())
}
pub(crate) fn current_area() -> Result<CpuAreaRef, CpuLocalError> {
let area_base = unsafe { imp::read_cpu_base()? };
if area_base == 0 {
return Err(CpuLocalError::AreaNotInstalled);
}
unsafe { CpuAreaRef::from_initialized_base(area_base) }
}
pub(crate) unsafe fn commit_current_thread(area: CpuAreaRef, value: usize) {
area.runtime_anchor()
.current_thread_slot()
.store(value, Ordering::Release);
}
pub fn current_thread(pin: &CpuPin<'_>) -> Result<NonNull<CurrentThreadHeader>, CpuLocalError> {
let area = pin.area();
let slot = area.runtime_anchor().current_thread_raw();
let register = unsafe { imp::read_current_thread(area.base()) };
if slot == 0
|| slot != register
|| !slot.is_multiple_of(core::mem::align_of::<CurrentThreadHeader>())
{
return Err(CpuLocalError::CurrentThreadMismatch);
}
let pointer = NonNull::new(slot as *mut CurrentThreadHeader)
.ok_or(CpuLocalError::CurrentThreadMismatch)?;
let thread_area = unsafe { pointer.as_ref() }
.cpu_area()
.ok_or(CpuLocalError::CurrentThreadMismatch)?;
if thread_area != area {
return Err(CpuLocalError::CurrentThreadMismatch);
}
Ok(pointer)
}
#[doc(hidden)]
pub unsafe fn scheduler_current_thread() -> Result<NonNull<CurrentThreadHeader>, CpuLocalError> {
#[cfg(not(feature = "tls"))]
{
let register = unsafe { imp::read_current_thread(0) };
NonNull::new(register as *mut CurrentThreadHeader)
.ok_or(CpuLocalError::CurrentThreadMismatch)
}
#[cfg(feature = "tls")]
loop {
let area = current_area()?;
let register = unsafe { imp::read_current_thread(area.base()) };
if unsafe { imp::read_cpu_base()? } != area.base() {
continue;
}
return NonNull::new(register as *mut CurrentThreadHeader)
.ok_or(CpuLocalError::CurrentThreadMismatch);
}
}
#[cfg(all(test, feature = "host-test"))]
mod tests {
use core::mem::MaybeUninit;
use super::*;
use crate::{CpuAreaPrefix, CpuIndex};
fn modeled_area(cpu_index: usize) -> CpuAreaRef {
let storage = Box::leak(Box::new(MaybeUninit::<CpuAreaPrefix>::uninit()));
let base = storage.as_mut_ptr() as usize;
storage.write(
CpuAreaPrefix::initialize(CpuIndex::try_from(cpu_index).unwrap(), base).unwrap(),
);
unsafe { CpuAreaRef::from_initialized_base(base) }.unwrap()
}
#[test]
fn scheduler_current_thread_survives_migration_during_bootstrap_read() {
let first = modeled_area(0);
let second = modeled_area(1);
let first_boot = first.prefix().boot_thread().header();
let second_boot = second.prefix().boot_thread().header();
unsafe { imp::install_cpu_base(first.base(), first_boot as *const _ as usize) };
imp::migrate_on_next_current_read(second.base());
assert_eq!(
unsafe { scheduler_current_thread() },
Ok(NonNull::from(second_boot)),
);
}
}
#[doc(hidden)]
pub unsafe fn install_bootstrap_thread(
pin: &CpuPin<'_>,
header: Pin<&CurrentThreadHeader>,
) -> Result<(), ThreadSwitchError> {
let epoch = unsafe { header.bind_cpu(pin.area()) }?;
let pointer = header.as_non_null().as_ptr() as usize;
unsafe { commit_current_thread(pin.area(), pointer) };
unsafe { imp::write_current_thread(pointer) };
if current_thread(pin) != Ok(header.as_non_null()) {
let _ = epoch;
fatal_register_invariant();
}
Ok(())
}
#[cfg(feature = "tls")]
pub fn kernel_tls(_pin: &CpuPin<'_>) -> usize {
unsafe { imp::read_kernel_tls() }
}
#[cfg(feature = "tls")]
#[doc(hidden)]
pub unsafe fn install_kernel_tls(_pin: &CpuPin<'_>, value: usize) {
unsafe { imp::write_kernel_tls(value) };
}
#[cold]
#[inline(never)]
fn fatal_register_invariant() -> ! {
panic!("CPU-local register commit did not retain the validated state")
}