use core::fmt::Write;
use hyperlight_common::vmem::{
BasicMapping, CowMapping, MappingKind, PAGE_SIZE, PhysAddr, VirtAddr,
};
use hyperlight_guest::error::ErrorCode;
use hyperlight_guest::exit::write_abort;
use hyperlight_guest::layout::{MAIN_STACK_LIMIT_GVA, MAIN_STACK_TOP_GVA};
use super::super::mrs;
use super::types::*;
use crate::HyperlightAbortWriter;
#[inline(always)]
fn bits<const HIGH_BIT: u8, const LOW_BIT: u8>(x: u64) -> u64 {
(x & ((1 << (HIGH_BIT + 1)) - 1)) >> LOW_BIT
}
const ESR_EC_DATA_ABORT_LOWER_EL: u64 = 0b100100;
const ESR_EC_DATA_ABORT_SAME_EL: u64 = 0b100101;
#[allow(dead_code)]
#[derive(Debug, Copy, Clone)]
enum DataFault {
TranslationFault(i64),
PermissionFault(i64),
Other(u64),
}
fn decode_data_fault(dfsc: u64) -> DataFault {
if bits::<5, 2>(dfsc) == 0b0011 {
DataFault::PermissionFault(bits::<1, 0>(dfsc) as i64)
} else if bits::<5, 2>(dfsc) == 0b0001 {
DataFault::TranslationFault(bits::<1, 0>(dfsc) as i64)
} else if bits::<5, 2>(dfsc) == 0b1010 {
if bits::<1, 0>(dfsc) >= 2 {
DataFault::TranslationFault(bits::<1, 0>(dfsc) as i64 - 4)
} else {
DataFault::Other(dfsc)
}
} else {
DataFault::Other(dfsc)
}
}
#[allow(dead_code)]
#[derive(Debug, Copy, Clone)]
enum Exception {
DataFault(bool, u64, DataFault),
Other(u64),
}
fn decode_syndrome(esr: u64) -> Exception {
let ec = bits::<31, 26>(esr);
match ec {
ESR_EC_DATA_ABORT_LOWER_EL => Exception::DataFault(
true,
unsafe { mrs!(FAR_EL1) },
decode_data_fault(bits::<5, 0>(esr)),
),
ESR_EC_DATA_ABORT_SAME_EL => Exception::DataFault(
false,
unsafe { mrs!(FAR_EL1) },
decode_data_fault(bits::<5, 0>(esr)),
),
_ => Exception::Other(esr),
}
}
fn handle_stack_fault(far: u64) {
unsafe {
let new_page = hyperlight_guest::prim_alloc::alloc_phys_pages(1);
crate::paging::map_region(
new_page,
(far & !((PAGE_SIZE - 1) as u64)) as *mut u8,
PAGE_SIZE as u64,
MappingKind::Basic(BasicMapping {
readable: true,
writable: true,
executable: false,
}),
);
core::arch::asm!("dsb sy");
}
}
fn handle_cow_fault(_orig_phys: PhysAddr, virt: VirtAddr, perms: CowMapping) {
unsafe {
let new_page = hyperlight_guest::prim_alloc::alloc_phys_pages(1);
let target_virt = virt as *mut u8;
let Some(scratch_mapping_access) = crate::paging::phys_to_virt(new_page) else {
write_abort(&[ErrorCode::GuestError as u8, 0xfeu8]);
write_abort("impossible: phys_to_virt failed on alloc_phys_pages return".as_bytes());
write_abort(&[0xFF]);
unreachable!();
};
core::ptr::copy(target_virt, scratch_mapping_access, PAGE_SIZE);
crate::paging::map_region(
new_page,
target_virt,
PAGE_SIZE as u64,
MappingKind::Basic(BasicMapping {
readable: perms.readable,
writable: true,
executable: perms.executable,
}),
);
core::arch::asm!("
dsb ish
tlbi vae1is, {}
dsb ish
isb
",
in(reg) (virt >> 12),
options(readonly, nostack, preserves_flags)
);
}
}
#[unsafe(no_mangle)]
pub extern "Rust" fn _debug_print(x: &str) {
hyperlight_guest::exit::debug_print(x);
}
fn handle_internal_fault(exn: Exception) -> bool {
match exn {
Exception::DataFault(false, far, DataFault::TranslationFault(_)) => {
if (MAIN_STACK_LIMIT_GVA..MAIN_STACK_TOP_GVA).contains(&far) {
handle_stack_fault(far);
true
} else {
false
}
}
Exception::DataFault(false, far, DataFault::PermissionFault(_)) => {
let mut orig_mappings = crate::paging::virt_to_phys(far);
if let Some(mapping) = orig_mappings.next()
&& let None = orig_mappings.next()
&& let MappingKind::Cow(cm) = mapping.kind
{
handle_cow_fault(mapping.phys_base, mapping.virt_base, cm);
true
} else {
false
}
}
_ => false,
}
}
pub(super) extern "C" fn handle_exception(
typ: ExceptionType,
from: ExceptionFrom,
_regs: *mut ExceptionContext,
) {
let esr = unsafe { mrs!(ESR_EL1) };
if typ == ExceptionType::Synchronous && from == ExceptionFrom::CurrentSP0 {
let exn = decode_syndrome(esr);
if handle_internal_fault(exn) {
return;
}
}
let elr = unsafe { mrs!(ELR_EL1) };
let far = unsafe { mrs!(FAR_EL1) };
let insn_bytes = unsafe { (elr as *const [u8; 8]).read_volatile() };
let mut w = HyperlightAbortWriter;
write_abort(&[ErrorCode::GuestError as u8, 0xfeu8]);
let write_res = write!(
w,
"Exception vector: {:?} {:?}\n\
Faulting Instruction: {:#x}\n\
Bytes At Faulting Instruction: {:?}\n\
Faulting Address: {:#x}\n\
Exception Syndrome: {:#x}",
from, typ, elr, insn_bytes, far, esr
);
if write_res.is_err() {
write_abort("exception message format failed".as_bytes());
}
write_abort(&[0xFF]);
unreachable!();
}