pub const GP_REG_COUNT: usize = 31;
const SPSR_M_EL1H: u64 = 0b0101;
const SPSR_M_EL1T: u64 = 0b0100;
const SPSR_M_MASK: u64 = 0xF;
const HYPERVISOR_ADDR_THRESHOLD: u64 = 0xFFFF_0000_0000_0000;
#[inline]
pub fn sanitize_spsr(raw: u64) -> u64 {
let mode = raw & SPSR_M_MASK;
if mode == SPSR_M_EL1H || mode == SPSR_M_EL1T {
raw
} else {
(0xF << 6) | SPSR_M_EL1H
}
}
#[inline]
pub fn validate_elr(elr: u64) -> bool {
elr < HYPERVISOR_ADDR_THRESHOLD
}
#[inline]
pub fn current_el() -> u8 {
let el: u64;
unsafe {
core::arch::asm!(
"mrs {reg}, CurrentEL",
reg = out(reg) el,
options(nomem, nostack, preserves_flags),
);
}
((el >> 2) & 0x3) as u8
}
pub fn configure_hcr_el2() {
assert_eq!(current_el(), 2, "configure_hcr_el2 must be called at EL2");
let hcr: u64 = (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 19) | (1 << 31);
unsafe {
core::arch::asm!(
"msr HCR_EL2, {val}",
"isb",
val = in(reg) hcr,
options(nomem, nostack, preserves_flags),
);
}
}
pub fn set_vttbr_el2(base: u64, vmid: u16) {
assert_eq!(base & 0xFFF, 0, "VTTBR_EL2 base must be 4KB-aligned");
let vttbr = ((vmid as u64) << 48) | (base & 0x0000_FFFF_FFFF_FFFE);
unsafe {
core::arch::asm!(
"msr VTTBR_EL2, {val}",
"isb",
val = in(reg) vttbr,
options(nomem, nostack, preserves_flags),
);
}
}
pub fn configure_vtcr_el2() {
let vtcr: u64 = (24 << 0) | (1 << 6) | (1 << 8) | (1 << 10) | (3 << 12) | (0 << 14) | (2 << 16);
unsafe {
core::arch::asm!(
"msr VTCR_EL2, {val}",
"isb",
val = in(reg) vtcr,
options(nomem, nostack, preserves_flags),
);
}
}
#[inline]
pub fn invalidate_tlb() {
unsafe {
core::arch::asm!(
"tlbi alle2",
"dsb ish",
"isb",
options(nomem, nostack, preserves_flags),
);
}
}
#[inline]
pub fn invalidate_stage2_tlb() {
unsafe {
core::arch::asm!(
"tlbi vmalls12e1",
"dsb ish",
"isb",
options(nomem, nostack, preserves_flags),
);
}
}
pub unsafe fn context_switch(from_regs: &mut [u64; 34], to_regs: &[u64; 34]) {
assert!(
validate_elr(to_regs[32]),
"ELR_EL2 ({:#x}) points into hypervisor address space",
to_regs[32]
);
let sanitized_spsr: u64 = sanitize_spsr(to_regs[33]);
unsafe {
let spsr_slot = to_regs.as_ptr().cast_mut().add(33);
core::ptr::write_volatile(spsr_slot, sanitized_spsr);
}
unsafe {
context_switch_inner(from_regs, to_regs);
}
}
#[inline(never)]
unsafe fn context_switch_inner(from_regs: &mut [u64; 34], to_regs: &[u64; 34]) {
let from_ptr = from_regs.as_mut_ptr();
let to_ptr = to_regs.as_ptr();
unsafe {
core::arch::asm!(
"stp x0, x1, [{from}, #0]",
"stp x2, x3, [{from}, #16]",
"stp x4, x5, [{from}, #32]",
"stp x6, x7, [{from}, #48]",
"stp x8, x9, [{from}, #64]",
"stp x10, x11, [{from}, #80]",
"stp x12, x13, [{from}, #96]",
"stp x14, x15, [{from}, #112]",
"stp x16, x17, [{from}, #128]",
"stp x18, x19, [{from}, #144]",
"stp x20, x21, [{from}, #160]",
"stp x22, x23, [{from}, #176]",
"stp x24, x25, [{from}, #192]",
"stp x26, x27, [{from}, #208]",
"stp x28, x29, [{from}, #224]",
"str x30, [{from}, #240]",
"mrs {tmp}, SP_EL1",
"str {tmp}, [{from}, #248]",
"mrs {tmp}, ELR_EL2",
"str {tmp}, [{from}, #256]",
"mrs {tmp}, SPSR_EL2",
"str {tmp}, [{from}, #264]",
"ldr {tmp}, [{to}, #256]",
"msr ELR_EL2, {tmp}",
"ldr {tmp}, [{to}, #264]",
"msr SPSR_EL2, {tmp}",
"ldr {tmp}, [{to}, #248]",
"msr SP_EL1, {tmp}",
"ldr x30, [{to}, #240]",
"ldp x28, x29, [{to}, #224]",
"ldp x26, x27, [{to}, #208]",
"ldp x24, x25, [{to}, #192]",
"ldp x22, x23, [{to}, #176]",
"ldp x20, x21, [{to}, #160]",
"ldp x18, x19, [{to}, #144]",
"ldp x16, x17, [{to}, #128]",
"ldp x14, x15, [{to}, #112]",
"ldp x12, x13, [{to}, #96]",
"ldp x10, x11, [{to}, #80]",
"ldp x8, x9, [{to}, #64]",
"ldp x6, x7, [{to}, #48]",
"ldp x4, x5, [{to}, #32]",
"ldp x2, x3, [{to}, #16]",
"ldp x0, x1, [{to}, #0]",
"isb",
from = in(reg) from_ptr,
to = in(reg) to_ptr,
tmp = out(reg) _,
out("x0") _, out("x1") _, out("x2") _, out("x3") _,
out("x4") _, out("x5") _, out("x6") _, out("x7") _,
out("x8") _, out("x9") _, out("x10") _, out("x11") _,
out("x12") _, out("x13") _, out("x14") _, out("x15") _,
out("x16") _, out("x17") _,
out("x20") _, out("x21") _, out("x22") _, out("x23") _,
out("x24") _, out("x25") _, out("x26") _, out("x27") _,
out("x28") _, out("x30") _,
options(nostack),
);
}
}
pub unsafe fn context_switch_vmid(
from_regs: &mut [u64; 34],
to_regs: &[u64; 34],
from_vmid: u16,
to_vmid: u16,
) {
if from_vmid != to_vmid {
invalidate_stage2_tlb();
}
unsafe {
context_switch(from_regs, to_regs);
}
}
pub unsafe fn clear_bss() {
extern "C" {
static mut __bss_start: u8;
static mut __bss_end: u8;
}
unsafe {
let start = core::ptr::addr_of_mut!(__bss_start);
let end = core::ptr::addr_of_mut!(__bss_end);
debug_assert!(
end as usize >= start as usize,
"BSS end ({:p}) < start ({:p}): linker script misconfigured",
end,
start,
);
let len = (end as usize).saturating_sub(start as usize);
core::ptr::write_bytes(start, 0, len);
}
}
#[inline]
pub fn wfi_loop() -> ! {
loop {
unsafe {
core::arch::asm!("wfi", options(nomem, nostack, preserves_flags));
}
}
}