use aarch64_cpu::registers::{ESR_EL2, HCR_EL2, Readable, SCTLR_EL1, VTCR_EL2, VTTBR_EL2};
use ax_errno::{AxError, AxResult};
use axaddrspace::{
GuestPhysAddr,
device::{AccessWidth, SysRegAddr},
};
use axvcpu::AxVCpuExitReason;
use log::error;
use crate::{
TrapFrame,
exception_utils::{
exception_class, exception_class_value, exception_data_abort_access_is_write,
exception_data_abort_access_reg, exception_data_abort_access_reg_width,
exception_data_abort_access_width, exception_data_abort_handleable,
exception_data_abort_is_permission_fault, exception_data_abort_is_translate_fault,
exception_esr, exception_fault_addr, exception_next_instruction_step,
exception_sysreg_addr, exception_sysreg_direction_write, exception_sysreg_gpr,
},
};
numeric_enum_macro::numeric_enum! {
#[repr(u8)]
#[derive(Debug)]
pub enum TrapKind {
Synchronous = 0,
Irq = 1,
Fiq = 2,
SError = 3,
}
}
const EXCEPTION_SYNC: usize = TrapKind::Synchronous as usize;
const EXCEPTION_IRQ: usize = TrapKind::Irq as usize;
#[repr(u8)]
#[derive(Debug)]
#[allow(unused)]
enum TrapSource {
CurrentSpEl0 = 0,
CurrentSpElx = 1,
LowerAArch64 = 2,
LowerAArch32 = 3,
}
core::arch::global_asm!(
include_str!("exception.S"),
exception_sync = const EXCEPTION_SYNC,
exception_irq = const EXCEPTION_IRQ,
);
pub fn handle_exception_sync(ctx: &mut TrapFrame) -> AxResult<AxVCpuExitReason> {
match exception_class() {
Some(ESR_EL2::EC::Value::DataAbortLowerEL) => {
let elr = ctx.exception_pc();
let val = elr + exception_next_instruction_step();
ctx.set_exception_pc(val);
handle_data_abort(ctx)
}
Some(ESR_EL2::EC::Value::HVC64) => {
let _hvc_arg_imm16 = ESR_EL2.read(ESR_EL2::ISS);
if let Some(result) = handle_psci_call(ctx) {
return result;
}
Ok(AxVCpuExitReason::Hypercall {
nr: ctx.gpr[0],
args: [
ctx.gpr[1], ctx.gpr[2], ctx.gpr[3], ctx.gpr[4], ctx.gpr[5], ctx.gpr[6],
],
})
}
Some(ESR_EL2::EC::Value::TrappedMsrMrs) => handle_system_register(ctx),
Some(ESR_EL2::EC::Value::SMC64) => {
let elr = ctx.exception_pc();
let val = elr + exception_next_instruction_step();
ctx.set_exception_pc(val);
handle_smc64_exception(ctx)
}
_ => {
panic!(
"handler not presents for EC_{} @ipa 0x{:x}, @pc 0x{:x}, @esr 0x{:x},
@sctlr_el1 0x{:x}, @vttbr_el2 0x{:x}, @vtcr_el2: {:#x} hcr: {:#x} ctx:{}",
exception_class_value(),
exception_fault_addr()?,
(*ctx).exception_pc(),
exception_esr(),
SCTLR_EL1.get() as usize,
VTTBR_EL2.get() as usize,
VTCR_EL2.get() as usize,
HCR_EL2.get() as usize,
ctx
);
}
}
}
fn handle_data_abort(context_frame: &mut TrapFrame) -> AxResult<AxVCpuExitReason> {
let addr = exception_fault_addr()?;
let access_width = exception_data_abort_access_width();
let is_write = exception_data_abort_access_is_write();
let reg = exception_data_abort_access_reg();
let reg_width = exception_data_abort_access_reg_width();
trace!(
"Data fault @{:?}, ELR {:#x}, esr: 0x{:x}",
addr,
context_frame.exception_pc(),
exception_esr(),
);
let width = match AccessWidth::try_from(access_width) {
Ok(access_width) => access_width,
Err(_) => return Err(AxError::InvalidInput),
};
let reg_width = match AccessWidth::try_from(reg_width) {
Ok(reg_width) => reg_width,
Err(_) => return Err(AxError::InvalidInput),
};
if !exception_data_abort_handleable() {
panic!(
"Core data abort not handleable {:#x}, esr {:#x}",
addr,
exception_esr()
);
}
if !exception_data_abort_is_translate_fault() {
if exception_data_abort_is_permission_fault() {
return Err(AxError::Unsupported);
} else {
panic!("Core data abort is not translate fault {:#x}", addr,);
}
}
if is_write {
return Ok(AxVCpuExitReason::MmioWrite {
addr,
width,
data: context_frame.gpr(reg) as u64,
});
}
Ok(AxVCpuExitReason::MmioRead {
addr,
width,
reg,
reg_width,
signed_ext: false,
})
}
fn handle_system_register(context_frame: &mut TrapFrame) -> AxResult<AxVCpuExitReason> {
let iss = ESR_EL2.read(ESR_EL2::ISS);
let addr = exception_sysreg_addr(iss.try_into().unwrap());
let elr = context_frame.exception_pc();
let val = elr + exception_next_instruction_step();
let write = exception_sysreg_direction_write(iss);
let reg = exception_sysreg_gpr(iss) as usize;
context_frame.set_exception_pc(val);
if write {
return Ok(AxVCpuExitReason::SysRegWrite {
addr: SysRegAddr::new(addr),
value: context_frame.gpr(reg as usize) as u64,
});
}
Ok(AxVCpuExitReason::SysRegRead {
addr: SysRegAddr::new(addr),
reg,
})
}
fn handle_psci_call(ctx: &mut TrapFrame) -> Option<AxResult<AxVCpuExitReason>> {
const PSCI_FN_RANGE_32: core::ops::RangeInclusive<u64> = 0x8400_0000..=0x8400_001F;
const PSCI_FN_RANGE_64: core::ops::RangeInclusive<u64> = 0xC400_0000..=0xC400_001F;
const PSCI_FN_VERSION: u64 = 0x0;
const _PSCI_FN_CPU_SUSPEND: u64 = 0x1;
const PSCI_FN_CPU_OFF: u64 = 0x2;
const PSCI_FN_CPU_ON: u64 = 0x3;
const _PSCI_FN_MIGRATE: u64 = 0x5;
const PSCI_FN_SYSTEM_OFF: u64 = 0x8;
const _PSCI_FN_SYSTEM_RESET: u64 = 0x9;
const PSCI_FN_END: u64 = 0x1f;
let fn_ = ctx.gpr[0];
let fn_offset = if PSCI_FN_RANGE_32.contains(&fn_) {
Some(fn_ - PSCI_FN_RANGE_32.start())
} else if PSCI_FN_RANGE_64.contains(&fn_) {
Some(fn_ - PSCI_FN_RANGE_64.start())
} else {
None
};
match fn_offset {
Some(PSCI_FN_CPU_OFF) => Some(Ok(AxVCpuExitReason::CpuDown { _state: ctx.gpr[1] })),
Some(PSCI_FN_CPU_ON) => Some(Ok(AxVCpuExitReason::CpuUp {
target_cpu: ctx.gpr[1],
entry_point: GuestPhysAddr::from(ctx.gpr[2] as usize),
arg: ctx.gpr[3],
})),
Some(PSCI_FN_SYSTEM_OFF) => Some(Ok(AxVCpuExitReason::SystemDown)),
Some(PSCI_FN_VERSION..PSCI_FN_END) => None,
_ => None,
}
}
fn handle_smc64_exception(ctx: &mut TrapFrame) -> AxResult<AxVCpuExitReason> {
if let Some(result) = handle_psci_call(ctx) {
result
} else {
(ctx.gpr[0], ctx.gpr[1], ctx.gpr[2], ctx.gpr[3]) =
unsafe { crate::smc::smc_call(ctx.gpr[0], ctx.gpr[1], ctx.gpr[2], ctx.gpr[3]) };
Ok(AxVCpuExitReason::Nothing)
}
}
#[unsafe(no_mangle)]
fn current_el_irq_handler(_tf: &mut TrapFrame) {
axvisor_api::arch::handle_irq()
}
#[unsafe(no_mangle)]
fn current_el_sync_handler(tf: &mut TrapFrame) {
let esr = ESR_EL2.extract();
let ec = ESR_EL2.read(ESR_EL2::EC);
let iss = ESR_EL2.read(ESR_EL2::ISS);
error!("ESR_EL2: {:#x}", esr.get());
error!("Exception Class: {ec:#x}");
error!("Instruction Specific Syndrome: {iss:#x}");
panic!(
"Unhandled synchronous exception from current EL: {:#x?}",
tf
);
}
#[unsafe(naked)]
#[unsafe(no_mangle)]
unsafe extern "C" fn vmexit_trampoline() -> ! {
core::arch::naked_asm!(
"add x9, sp, 34 * 8", "ldr x10, [x9]", "mov sp, x10", restore_regs_from_stack!(), "ret",
)
}
#[unsafe(no_mangle)]
fn invalid_exception_el2(tf: &mut TrapFrame, kind: TrapKind, source: TrapSource) {
panic!(
"Invalid exception {:?} from {:?}:\n{:#x?}",
kind, source, tf
);
}