#![no_std]
#![deny(clippy::undocumented_unsafe_blocks)]
#![deny(unsafe_op_in_unsafe_fn)]
#[cfg(any(
all(feature = "el1", feature = "el2"),
all(feature = "el1", feature = "el3"),
all(feature = "el2", feature = "el3"),
))]
compile_error!("Only one `el` feature may be enabled at once.");
mod entry;
#[cfg(feature = "exceptions")]
mod exceptions;
#[cfg(feature = "initial-pagetable")]
mod pagetable;
#[cfg(feature = "initial-pagetable")]
#[doc(hidden)]
pub mod __private {
pub use crate::pagetable::{__enable_mmu_el1, __enable_mmu_el2, __enable_mmu_el3};
}
#[cfg(any(feature = "exceptions", feature = "psci"))]
use core::arch::asm;
#[cfg(not(feature = "initial-pagetable"))]
use core::arch::naked_asm;
use core::mem::ManuallyDrop;
pub use entry::{SuspendContext, secondary_entry, warm_boot_entry};
#[cfg(feature = "exceptions")]
pub use exceptions::{ExceptionHandlers, RegisterState, RegisterStateRef};
#[cfg(all(feature = "initial-pagetable", feature = "el1"))]
pub use pagetable::DEFAULT_TCR_EL1 as DEFAULT_TCR;
#[cfg(all(feature = "initial-pagetable", feature = "el2"))]
pub use pagetable::DEFAULT_TCR_EL2 as DEFAULT_TCR;
#[cfg(all(feature = "initial-pagetable", feature = "el3"))]
pub use pagetable::DEFAULT_TCR_EL3 as DEFAULT_TCR;
#[cfg(feature = "initial-pagetable")]
pub use pagetable::{
DEFAULT_MAIR, DEFAULT_SCTLR, DEFAULT_TCR_EL1, DEFAULT_TCR_EL2, DEFAULT_TCR_EL3,
InitialPagetable,
};
#[cfg(not(feature = "initial-pagetable"))]
#[unsafe(naked)]
#[unsafe(link_section = ".init")]
#[unsafe(export_name = "enable_mmu")]
pub unsafe extern "C" fn enable_mmu() {
naked_asm!("ret")
}
#[cfg(feature = "initial-pagetable")]
unsafe extern "C" {
pub unsafe fn enable_mmu();
}
pub extern "C" fn set_exception_vector() {
#[cfg(all(feature = "el1", feature = "exceptions"))]
unsafe {
asm!(
"adr x9, vector_table_el1",
"msr vbar_el1, x9",
options(nomem, nostack),
out("x9") _,
);
}
#[cfg(all(feature = "el2", feature = "exceptions"))]
unsafe {
asm!(
"adr x9, vector_table_el2",
"msr vbar_el2, x9",
options(nomem, nostack),
out("x9") _,
);
}
#[cfg(all(feature = "el3", feature = "exceptions"))]
unsafe {
asm!(
"adr x9, vector_table_el3",
"msr vbar_el3, x9",
options(nomem, nostack),
out("x9") _,
);
}
#[cfg(all(
feature = "exceptions",
not(any(feature = "el1", feature = "el2", feature = "el3"))
))]
{
let current_el: u64;
unsafe {
asm!(
"mrs {current_el}, CurrentEL",
options(nomem, nostack, preserves_flags),
current_el = out(reg) current_el,
);
}
match (current_el >> 2) & 0b11 {
1 => unsafe {
asm!(
"adr x9, vector_table_el1",
"msr vbar_el1, x9",
options(nomem, nostack, preserves_flags),
out("x9") _,
);
},
2 => unsafe {
asm!(
"adr x9, vector_table_el2",
"msr vbar_el2, x9",
options(nomem, nostack, preserves_flags),
out("x9") _,
);
},
3 => unsafe {
asm!(
"adr x9, vector_table_el3",
"msr vbar_el3, x9",
options(nomem, nostack, preserves_flags),
out("x9") _,
);
},
_ => {
panic!("Unexpected EL");
}
}
}
}
extern "C" fn rust_entry(arg0: u64, arg1: u64, arg2: u64, arg3: u64) -> ! {
set_exception_vector();
__main(arg0, arg1, arg2, arg3)
}
unsafe extern "Rust" {
safe fn __main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) -> !;
}
#[macro_export]
macro_rules! entry {
($name:path) => {
entry!($name, 40);
};
($name:path, $boot_stack_pages:expr) => {
#[unsafe(export_name = "boot_stack")]
#[unsafe(link_section = ".stack.boot_stack")]
static mut __BOOT_STACK: $crate::Stack<$boot_stack_pages> = $crate::Stack::new();
#[unsafe(export_name = "__main")]
fn __main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) -> ! {
$name(arg0, arg1, arg2, arg3)
}
};
}
#[repr(C, align(4096))]
pub struct Stack<const NUM_PAGES: usize>([StackPage; NUM_PAGES]);
impl<const NUM_PAGES: usize> Stack<NUM_PAGES> {
pub const fn new() -> Self {
Self([const { StackPage::new() }; NUM_PAGES])
}
}
impl<const NUM_PAGES: usize> Default for Stack<NUM_PAGES> {
fn default() -> Self {
Self::new()
}
}
#[repr(C, align(4096))]
struct StackPage([u8; 4096]);
impl StackPage {
const fn new() -> Self {
Self([0; 4096])
}
}
#[repr(C)]
pub(crate) struct StartCoreStack<F> {
entry_ptr: *mut ManuallyDrop<F>,
trampoline_ptr: unsafe extern "C" fn(&mut ManuallyDrop<F>) -> !,
}
#[cfg(feature = "psci")]
pub unsafe fn start_core<C: smccc::Call, F: FnOnce() + Send + 'static, const N: usize>(
mpidr: u64,
stack: *mut Stack<N>,
rust_entry: F,
) -> Result<(), smccc::psci::Error> {
const {
assert!(
size_of::<StartCoreStack<F>>()
+ 2 * size_of::<F>()
+ 2 * align_of::<F>()
+ 1024 <= size_of::<Stack<N>>(),
"the `rust_entry` closure is too big to fit in the core stack"
);
}
let rust_entry = ManuallyDrop::new(rust_entry);
let stack_start = stack.cast::<u8>();
let align_offfset = stack_start.align_offset(align_of::<F>());
let entry_ptr = stack_start
.wrapping_add(align_offfset)
.cast::<ManuallyDrop<F>>();
assert!(stack.is_aligned());
let stack_end = stack.wrapping_add(1);
let params = stack_end.cast::<StartCoreStack<F>>().wrapping_sub(1);
unsafe {
entry_ptr.write(rust_entry);
*params = StartCoreStack {
entry_ptr,
trampoline_ptr: trampoline::<F>,
};
};
dsb_st();
smccc::psci::cpu_on::<C>(
mpidr,
secondary_entry as usize as _,
stack_end as usize as _,
)
}
#[cfg(feature = "psci")]
unsafe extern "C" fn trampoline<F: FnOnce() + Send + 'static>(entry: &mut ManuallyDrop<F>) -> ! {
let entry = unsafe { ManuallyDrop::take(entry) };
entry();
panic!("rust_entry function passed to start_core should never return");
}
#[cfg(feature = "psci")]
fn dsb_st() {
unsafe {
asm!("dsb st", options(nostack));
}
}
#[cfg(feature = "psci")]
pub unsafe fn suspend_core<C: smccc::Call>(
power_state: u32,
stack_ptr: *mut u64,
entry: extern "C" fn(u64) -> !,
arg: u64,
) -> Result<(), smccc::psci::Error> {
let suspend_context = SuspendContext {
stack_ptr,
entry,
arg,
};
smccc::psci::cpu_suspend::<C>(
power_state,
warm_boot_entry as u64,
(&raw const suspend_context) as u64,
)
}