use core::ptr::addr_of;
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use crate::InitError;
use percpu_macros::percpu_symbol_vma;
static IS_INIT: AtomicBool = AtomicBool::new(false);
const SIZE_64BIT: usize = 64;
const PERCPU_AREA_ALIGN: usize = SIZE_64BIT;
const fn align_up_64(val: usize) -> usize {
(val + SIZE_64BIT - 1) & !(SIZE_64BIT - 1)
}
static PERCPU_AREA_BASE: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
extern "C" {
static _percpu_start: u8;
static _percpu_end: u8;
static _percpu_load_start: u8;
static _percpu_load_end: u8;
}
pub fn percpu_area_num() -> usize {
(addr_of!(_percpu_end) as usize - addr_of!(_percpu_start) as usize)
/ align_up_64(percpu_area_size())
}
pub fn percpu_area_size() -> usize {
percpu_symbol_vma!(_percpu_load_end) - percpu_symbol_vma!(_percpu_load_start)
}
pub fn percpu_area_layout_expected(cpu_count: usize) -> core::alloc::Layout {
let size = cpu_count * align_up_64(percpu_area_size());
core::alloc::Layout::from_size_align(size, PERCPU_AREA_ALIGN).unwrap()
}
fn percpu_area_base_nolock(cpu_id: usize) -> usize {
let base = PERCPU_AREA_BASE.load(Ordering::Relaxed);
if base.is_null() {
panic!("PerCPU area base address not set");
}
base as usize + cpu_id * align_up_64(percpu_area_size())
}
pub fn percpu_area_base(cpu_id: usize) -> usize {
while IS_INIT.load(Ordering::Acquire) {
core::hint::spin_loop();
}
percpu_area_base_nolock(cpu_id)
}
fn validate_percpu_vma() {
#[cfg(not(feature = "non-zero-vma"))]
{
assert_eq!(
percpu_symbol_vma!(_percpu_load_start), 0,
"The `.percpu` section must be loaded at VMA address 0 when feature \"non-zero-vma\" is disabled"
)
}
}
fn copy_percpu_region<T: Iterator<Item = usize>>(source: *const u8, dest_ids: T) {
let size = percpu_area_size();
for dest_id in dest_ids {
let dest_base = percpu_area_base_nolock(dest_id);
unsafe {
core::ptr::copy_nonoverlapping(source, dest_base as *mut u8, size);
}
}
}
fn validate_percpu_area_base(base: *mut u8) -> Result<(), InitError> {
if base.is_null() {
return Err(InitError::InvalidBase);
}
if (base as usize) % PERCPU_AREA_ALIGN != 0 {
return Err(InitError::UnalignedBase);
}
Ok(())
}
fn init_inner(
base: *mut u8,
cpu_count: usize,
do_not_copy_to_primary: bool,
) -> Result<usize, InitError> {
validate_percpu_area_base(base)?;
if IS_INIT
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_err()
{
return Ok(0);
}
validate_percpu_vma();
PERCPU_AREA_BASE.store(base as _, Ordering::Relaxed);
copy_percpu_region(
addr_of!(_percpu_start),
if do_not_copy_to_primary {
1..cpu_count
} else {
0..cpu_count
},
);
IS_INIT.store(false, Ordering::Release);
Ok(cpu_count)
}
pub fn init_in_place() -> Result<usize, InitError> {
let base = addr_of!(_percpu_start) as *mut u8;
init_inner(base, percpu_area_num(), true)
}
pub fn init(base: *mut u8, cpu_count: usize) -> Result<usize, InitError> {
init_inner(base, cpu_count, false)
}
pub fn read_percpu_reg() -> usize {
let tp: usize;
unsafe {
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
tp = if cfg!(target_os = "linux") {
SELF_PTR.read_current_raw()
} else if cfg!(target_os = "none") {
x86::msr::rdmsr(x86::msr::IA32_GS_BASE) as usize
} else {
unimplemented!()
};
} else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] {
core::arch::asm!("mv {}, gp", out(reg) tp)
} else if #[cfg(all(target_arch = "aarch64", not(feature = "arm-el2")))] {
core::arch::asm!("mrs {}, TPIDR_EL1", out(reg) tp)
} else if #[cfg(all(target_arch = "aarch64", feature = "arm-el2"))] {
core::arch::asm!("mrs {}, TPIDR_EL2", out(reg) tp)
} else if #[cfg(target_arch = "loongarch64")] {
core::arch::asm!("move {}, $r21", out(reg) tp)
} else if #[cfg(target_arch = "arm")] {
core::arch::asm!("mrc p15, 0, {}, c13, c0, 4", out(reg) tp)
}
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "non-zero-vma")] {
tp + percpu_symbol_vma!(_percpu_load_start)
} else {
tp
}
}
}
pub unsafe fn write_percpu_reg(tp: usize) {
cfg_if::cfg_if! {
if #[cfg(feature = "non-zero-vma")] {
let tp = tp - percpu_symbol_vma!(_percpu_load_start);
}
};
unsafe {
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
if cfg!(target_os = "linux") {
const ARCH_SET_GS: u32 = 0x1001;
const SYS_ARCH_PRCTL: u32 = 158;
core::arch::asm!(
"syscall",
in("eax") SYS_ARCH_PRCTL,
in("edi") ARCH_SET_GS,
in("rsi") tp,
);
} else if cfg!(target_os = "none") {
x86::msr::wrmsr(x86::msr::IA32_GS_BASE, tp as u64);
} else {
unimplemented!()
}
SELF_PTR.write_current_raw(tp);
} else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] {
core::arch::asm!("mv gp, {}", in(reg) tp)
} else if #[cfg(all(target_arch = "aarch64", not(feature = "arm-el2")))] {
core::arch::asm!("msr TPIDR_EL1, {}", in(reg) tp)
} else if #[cfg(all(target_arch = "aarch64", feature = "arm-el2"))] {
core::arch::asm!("msr TPIDR_EL2, {}", in(reg) tp)
} else if #[cfg(target_arch = "loongarch64")] {
core::arch::asm!("move $r21, {}", in(reg) tp)
} else if #[cfg(target_arch = "arm")] {
core::arch::asm!("mcr p15, 0, {}, c13, c0, 4", in(reg) tp)
}
}
}
}
pub fn init_percpu_reg(cpu_id: usize) {
let tp = percpu_area_base(cpu_id);
unsafe { write_percpu_reg(tp) }
}
#[allow(unused_imports)]
use crate as percpu;
#[cfg(target_arch = "x86_64")]
#[no_mangle]
#[percpu_macros::def_percpu]
static SELF_PTR: usize = 0;