use core::marker::PhantomData;
use aarch64_cpu::registers::*;
use aarch64_sysreg::SystemRegType;
use super::{
TrapFrame,
context_frame::GuestSystemRegisters,
exception::{TrapKind, handle_exception_sync},
exception_utils::exception_class_value,
host::{ArmHostIrqConfig, ArmHostIrqGuard, ArmHostOps},
};
use crate::{
ArmGuestPhysAddr, ArmNestedPagingConfig, ArmSysRegAddr, ArmTimerKind, ArmTimerSnapshot,
ArmTimerVmConfig, ArmVcpuResult, ArmVcpuTimer, ArmVmExit,
};
#[repr(C)]
#[derive(Clone, Debug, Copy, Default)]
#[allow(dead_code)]
pub struct VmCpuRegisters {
pub trap_context_regs: TrapFrame,
pub vm_system_regs: GuestSystemRegisters,
}
#[repr(C)]
#[derive(Debug, Default)]
struct HostRuntimeContext {
stack_top: u64,
sp_el0: u64,
tpidr_el0: u64,
irq_interface: u64,
irq_cpu_interface_base: usize,
pending_irq_ack: u32,
_reserved: u32,
}
#[repr(C)]
#[derive(Debug)]
pub struct ArmVcpu<H: ArmHostOps> {
ctx: TrapFrame,
host: HostRuntimeContext,
guest_system_regs: GuestSystemRegisters,
timer: ArmVcpuTimer,
mpidr: u64,
_host: PhantomData<fn() -> H>,
}
struct AssemblyLayoutHost;
impl ArmHostOps for AssemblyLayoutHost {
fn inject_virtual_interrupt(_vector: u32) -> ArmVcpuResult {
Err(crate::ArmVcpuError::BadState)
}
fn finish_pending_host_irq(_raw_ack: u32) -> Option<usize> {
None
}
fn handle_current_host_irq() {}
}
type AssemblyArmVcpu = ArmVcpu<AssemblyLayoutHost>;
pub const ARM_VCPU_TRAP_FRAME_SIZE: usize = core::mem::size_of::<TrapFrame>();
pub const ARM_VCPU_HOST_STACK_TOP_OFFSET: usize = core::mem::offset_of!(AssemblyArmVcpu, host)
+ core::mem::offset_of!(HostRuntimeContext, stack_top);
pub const ARM_VCPU_HOST_SP_EL0_OFFSET: usize = core::mem::offset_of!(AssemblyArmVcpu, host)
+ core::mem::offset_of!(HostRuntimeContext, sp_el0);
pub(crate) const ARM_VCPU_HOST_TPIDR_EL0_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, host)
+ core::mem::offset_of!(HostRuntimeContext, tpidr_el0);
pub(crate) const ARM_VCPU_HOST_IRQ_INTERFACE_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, host)
+ core::mem::offset_of!(HostRuntimeContext, irq_interface);
pub(crate) const ARM_VCPU_HOST_IRQ_CPU_INTERFACE_BASE_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, host)
+ core::mem::offset_of!(HostRuntimeContext, irq_cpu_interface_base);
pub(crate) const ARM_VCPU_HOST_PENDING_IRQ_ACK_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, host)
+ core::mem::offset_of!(HostRuntimeContext, pending_irq_ack);
pub(crate) const ARM_VCPU_GUEST_TPIDR_EL0_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, guest_system_regs)
+ super::context_frame::GUEST_TPIDR_EL0_OFFSET;
pub(crate) const ARM_VCPU_TIMER_VIRTUAL_OFFSET_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, timer) + crate::timer::TIMER_VIRTUAL_OFFSET_OFFSET;
pub(crate) const ARM_VCPU_TIMER_VIRTUAL_COMPARE_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, timer) + crate::timer::TIMER_VIRTUAL_COMPARE_OFFSET;
pub(crate) const ARM_VCPU_TIMER_VIRTUAL_CONTROL_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, timer) + crate::timer::TIMER_VIRTUAL_CONTROL_OFFSET;
pub(crate) const ARM_VCPU_TIMER_GUEST_HYPERVISOR_CONTROL_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, timer)
+ crate::timer::TIMER_GUEST_HYPERVISOR_CONTROL_OFFSET;
pub(crate) const ARM_VCPU_TIMER_GUEST_KERNEL_CONTROL_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, timer) + crate::timer::TIMER_GUEST_KERNEL_CONTROL_OFFSET;
pub(crate) const ARM_VCPU_TIMER_HOST_HYPERVISOR_CONTROL_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, timer)
+ crate::timer::TIMER_HOST_HYPERVISOR_CONTROL_OFFSET;
pub(crate) const ARM_VCPU_TIMER_HOST_KERNEL_CONTROL_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, timer) + crate::timer::TIMER_HOST_KERNEL_CONTROL_OFFSET;
pub(crate) const ARM_VCPU_TIMER_LOADED_OFFSET: usize =
core::mem::offset_of!(AssemblyArmVcpu, timer) + crate::timer::TIMER_LOADED_OFFSET;
const _: () = {
assert!(core::mem::offset_of!(AssemblyArmVcpu, ctx) == 0);
assert!(ARM_VCPU_HOST_STACK_TOP_OFFSET == ARM_VCPU_TRAP_FRAME_SIZE);
assert!(
ARM_VCPU_HOST_SP_EL0_OFFSET == ARM_VCPU_HOST_STACK_TOP_OFFSET + core::mem::size_of::<u64>()
);
assert!(
ARM_VCPU_HOST_TPIDR_EL0_OFFSET == ARM_VCPU_HOST_SP_EL0_OFFSET + core::mem::size_of::<u64>()
);
assert!(ARM_VCPU_HOST_IRQ_INTERFACE_OFFSET.is_multiple_of(core::mem::align_of::<u64>()));
assert!(
ARM_VCPU_HOST_IRQ_CPU_INTERFACE_BASE_OFFSET.is_multiple_of(core::mem::align_of::<usize>())
);
assert!(ARM_VCPU_HOST_PENDING_IRQ_ACK_OFFSET.is_multiple_of(core::mem::align_of::<u32>()));
assert!(
ARM_VCPU_GUEST_TPIDR_EL0_OFFSET
>= ARM_VCPU_HOST_TPIDR_EL0_OFFSET + core::mem::size_of::<u64>()
);
assert!(ARM_VCPU_TIMER_VIRTUAL_OFFSET_OFFSET.is_multiple_of(core::mem::align_of::<u64>()));
assert!(ARM_VCPU_TIMER_VIRTUAL_COMPARE_OFFSET.is_multiple_of(core::mem::align_of::<u64>()));
assert!(ARM_VCPU_TIMER_VIRTUAL_CONTROL_OFFSET.is_multiple_of(core::mem::align_of::<u32>()));
};
#[derive(Clone, Debug, Default)]
pub struct ArmVcpuCreateConfig {
pub mpidr_el1: u64,
pub dtb_addr: usize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ArmVcpuSetupConfig {
timer: ArmTimerVmConfig,
host_irq: ArmHostIrqConfig,
}
impl ArmVcpuSetupConfig {
pub const fn new(timer: ArmTimerVmConfig, host_irq: ArmHostIrqConfig) -> Self {
Self { timer, host_irq }
}
pub const fn timer(self) -> ArmTimerVmConfig {
self.timer
}
pub const fn host_irq(self) -> ArmHostIrqConfig {
self.host_irq
}
}
impl<H: ArmHostOps> ArmVcpu<H> {
pub fn new(_vm_id: usize, _vcpu_id: usize, config: ArmVcpuCreateConfig) -> ArmVcpuResult<Self> {
let mut ctx = TrapFrame::default();
ctx.set_argument(config.dtb_addr);
Ok(Self {
ctx,
host: HostRuntimeContext::default(),
guest_system_regs: GuestSystemRegisters::default(),
timer: ArmVcpuTimer::unconfigured(),
mpidr: config.mpidr_el1,
_host: PhantomData,
})
}
pub fn setup(&mut self, config: ArmVcpuSetupConfig) -> ArmVcpuResult {
self.init_hv(config);
Ok(())
}
pub fn set_entry(&mut self, entry: ArmGuestPhysAddr) -> ArmVcpuResult {
debug!("set vcpu entry:{entry:?}");
self.set_elr(entry.as_usize());
Ok(())
}
pub fn set_nested_page_table(&mut self, config: ArmNestedPagingConfig) -> ArmVcpuResult {
debug!("set vcpu stage-2 root:{:#x}", config.root_paddr);
self.guest_system_regs.vttbr_el2 = config.root_paddr as u64;
let pa_bits = if config.mode == 0 {
pa_bits()
} else {
config.mode
};
self.guest_system_regs.vtcr_el2 = vtcr_for_config(config.levels, config.gpa_bits, pa_bits);
Ok(())
}
pub fn timer_snapshot(&self) -> ArmVcpuResult<ArmTimerSnapshot> {
self.timer.snapshot()
}
pub fn run(&mut self, _host_irq_guard: &ArmHostIrqGuard) -> ArmVcpuResult<ArmVmExit> {
let exit_reason = unsafe {
if !self.timer.is_configured() || self.timer.is_loaded() {
return Err(crate::ArmVcpuError::BadState);
}
self.restore_vm_system_regs();
self.run_guest()
};
if self.timer.is_loaded() {
return Err(crate::ArmVcpuError::BadState);
}
let trap_kind = TrapKind::try_from(exit_reason as u8).expect("Invalid TrapKind");
self.vmexit_handler(trap_kind)
}
pub fn bind(&mut self) -> ArmVcpuResult {
Ok(())
}
pub fn unbind(&mut self) -> ArmVcpuResult {
Ok(())
}
pub fn set_gpr(&mut self, idx: usize, val: usize) {
self.ctx.set_gpr(idx, val);
}
pub fn inject_interrupt(&mut self, vector: usize) -> ArmVcpuResult {
let vector = u32::try_from(vector).map_err(|_| crate::ArmVcpuError::InvalidInput)?;
H::inject_virtual_interrupt(vector)
}
pub fn set_return_value(&mut self, val: usize) {
self.ctx.set_argument(val);
}
}
impl<H: ArmHostOps> ArmVcpu<H> {
fn init_hv(&mut self, config: ArmVcpuSetupConfig) {
self.ctx.spsr = (SPSR_EL1::M::EL1h
+ SPSR_EL1::I::Masked
+ SPSR_EL1::F::Masked
+ SPSR_EL1::A::Masked
+ SPSR_EL1::D::Masked)
.value;
self.init_vm_context(config);
}
fn init_vm_context(&mut self, config: ArmVcpuSetupConfig) {
let guest_hypervisor_control =
(CNTHCTL_EL2::EL1PCEN::CLEAR + CNTHCTL_EL2::EL1PCTEN::CLEAR).into();
self.timer = ArmVcpuTimer::new(config.timer(), guest_hypervisor_control);
self.host.irq_interface = config.host_irq().interface();
self.host.irq_cpu_interface_base = config.host_irq().cpu_interface_base();
self.host.pending_irq_ack = u32::MAX;
self.guest_system_regs.sctlr_el1 = 0x30C50830;
self.guest_system_regs.pmcr_el0 = 0;
if self.guest_system_regs.vtcr_el2 == 0 {
let pa_bits = pa_bits();
let levels = max_gpt_level(pa_bits);
let gpa_bits = if levels == 3 { 39 } else { 48 };
self.guest_system_regs.vtcr_el2 = vtcr_for_config(levels, gpa_bits, pa_bits);
}
let hcr_el2 = HCR_EL2::VM::Enable
+ HCR_EL2::TSC::EnableTrapEl1SmcToEl2
+ HCR_EL2::TWI::SET
+ HCR_EL2::RW::EL1IsAarch64
+ HCR_EL2::IMO::EnableVirtualIRQ
+ HCR_EL2::FMO::EnableVirtualFIQ;
self.guest_system_regs.hcr_el2 = hcr_el2.into();
let mut vmpidr = 1 << 31;
vmpidr |= self.mpidr;
self.guest_system_regs.vmpidr_el2 = vmpidr;
}
fn set_elr(&mut self, elr: usize) {
self.ctx.set_exception_pc(elr);
}
#[allow(unused)]
fn get_gpr(&self, idx: usize) {
self.ctx.gpr(idx);
}
}
impl<H: ArmHostOps> ArmVcpu<H> {
#[unsafe(naked)]
unsafe extern "C" fn run_guest(&mut self) -> usize {
core::arch::naked_asm!(
save_regs_to_stack!(),
"mov x9, sp",
"add x10, x0, {host_stack_top_offset}",
"str x9, [x10]",
"mrs x9, sp_el0",
"str x9, [x10, #8]",
"mrs x9, tpidr_el0",
"str x9, [x10, {host_tpidr_el0_delta}]",
"mov x0, x10",
"b context_vm_entry",
"b {run_guest_panic}",
host_stack_top_offset = const ARM_VCPU_HOST_STACK_TOP_OFFSET,
host_tpidr_el0_delta = const ARM_VCPU_HOST_TPIDR_EL0_OFFSET
- ARM_VCPU_HOST_STACK_TOP_OFFSET,
run_guest_panic = sym Self::run_guest_panic,
);
}
unsafe fn run_guest_panic() -> ! {
panic!("run_guest_panic");
}
unsafe fn restore_vm_system_regs(&mut self) {
unsafe {
core::arch::asm!(
"
mov x3, xzr // Trap nothing from EL1 to El2.
msr cptr_el2, x3"
);
self.guest_system_regs.restore();
core::arch::asm!(
"
ic iallu
tlbi alle2
tlbi alle1 // Flush tlb
dsb nsh
isb"
);
}
}
fn vmexit_handler(&mut self, exit_reason: TrapKind) -> ArmVcpuResult<ArmVmExit> {
trace!(
"ArmVcpu vmexit_handler() esr:{:#x} ctx:{:#x?}",
exception_class_value(),
self.ctx
);
unsafe {
self.guest_system_regs.store();
}
let result = match exit_reason {
TrapKind::Synchronous => handle_exception_sync(&mut self.ctx),
TrapKind::Irq => {
let raw_ack = core::mem::replace(&mut self.host.pending_irq_ack, u32::MAX);
Ok(ArmVmExit::ExternalInterrupt {
token: (raw_ack != u32::MAX)
.then(|| H::finish_pending_host_irq(raw_ack))
.flatten(),
})
}
_ => panic!("Unhandled exception {:?}", exit_reason),
};
match result {
Ok(ArmVmExit::SysRegRead { addr, reg }) => {
if let Some(exit_reason) =
self.builtin_sysreg_access_handler(addr, false, 0, reg)?
{
return Ok(exit_reason);
}
result
}
Ok(ArmVmExit::SysRegWrite { addr, value }) => {
if let Some(exit_reason) =
self.builtin_sysreg_access_handler(addr, true, value, 0)?
{
return Ok(exit_reason);
}
result
}
r => r,
}
}
fn builtin_sysreg_access_handler(
&mut self,
addr: ArmSysRegAddr,
write: bool,
value: u64,
reg: usize,
) -> ArmVcpuResult<Option<ArmVmExit>> {
const SYSREG_ICC_PMR_EL1: ArmSysRegAddr = ArmSysRegAddr::new(0x30_100c);
const SYSREG_ICC_SGI1R_EL1: ArmSysRegAddr = ArmSysRegAddr::new(0x3a_3016);
const SYSREG_ICC_DIR_EL1: ArmSysRegAddr = ArmSysRegAddr::new(0x32_3016);
const SYSREG_ICC_RPR_EL1: ArmSysRegAddr = ArmSysRegAddr::new(0x36_3016);
const SYSREG_ICC_CTLR_EL1: ArmSysRegAddr = ArmSysRegAddr::new(0x38_3018);
const SYSREG_CNTFRQ_EL0: ArmSysRegAddr =
ArmSysRegAddr::new(SystemRegType::CNTFRQ_EL0 as usize);
const SYSREG_CNTPCT_EL0: ArmSysRegAddr =
ArmSysRegAddr::new(SystemRegType::CNTPCT_EL0 as usize);
const SYSREG_CNTP_TVAL_EL0: ArmSysRegAddr =
ArmSysRegAddr::new(SystemRegType::CNTP_TVAL_EL0 as usize);
const SYSREG_CNTP_CTL_EL0: ArmSysRegAddr =
ArmSysRegAddr::new(SystemRegType::CNTP_CTL_EL0 as usize);
const SYSREG_CNTP_CVAL_EL0: ArmSysRegAddr =
ArmSysRegAddr::new(SystemRegType::CNTP_CVAL_EL0 as usize);
match (addr, write) {
(SYSREG_CNTFRQ_EL0, false) => {
self.set_gpr(reg, self.timer.config().frequency() as usize);
Ok(Some(ArmVmExit::Nothing))
}
(SYSREG_CNTPCT_EL0, false) => {
let counter = self
.timer
.guest_counter(ArmTimerKind::Physical, physical_counter())?;
self.set_gpr(reg, counter as usize);
Ok(Some(ArmVmExit::Nothing))
}
(SYSREG_CNTP_TVAL_EL0, false) => {
let value = self
.timer
.read_tval(ArmTimerKind::Physical, physical_counter())?;
self.set_gpr(reg, value as usize);
Ok(Some(ArmVmExit::Nothing))
}
(SYSREG_CNTP_CTL_EL0, false) => {
let value = self
.timer
.read_control(ArmTimerKind::Physical, physical_counter())?;
self.set_gpr(reg, value as usize);
Ok(Some(ArmVmExit::Nothing))
}
(SYSREG_CNTP_CVAL_EL0, false) => {
let value = self.timer.read_compare(ArmTimerKind::Physical)?;
self.set_gpr(reg, value as usize);
Ok(Some(ArmVmExit::Nothing))
}
(SYSREG_CNTP_TVAL_EL0, true) => {
self.timer
.write_tval(ArmTimerKind::Physical, physical_counter(), value as u32)?;
Ok(Some(ArmVmExit::Nothing))
}
(SYSREG_CNTP_CTL_EL0, true) => {
self.timer
.write_control(ArmTimerKind::Physical, value as u32)?;
Ok(Some(ArmVmExit::Nothing))
}
(SYSREG_CNTP_CVAL_EL0, true) => {
self.timer.write_compare(ArmTimerKind::Physical, value)?;
Ok(Some(ArmVmExit::Nothing))
}
(SYSREG_CNTFRQ_EL0 | SYSREG_CNTPCT_EL0, true) => Err(crate::ArmVcpuError::InvalidInput),
(SYSREG_ICC_SGI1R_EL1, true) => {
debug!("arm_vcpu ICC_SGI1R_EL1 write: {value:#x}");
Ok(Some(ArmVmExit::SendIPI { value }))
}
(SYSREG_ICC_SGI1R_EL1, false) => {
self.set_gpr(reg, 0);
Ok(Some(ArmVmExit::Nothing))
}
(SYSREG_ICC_DIR_EL1, true) => Ok(Some(ArmVmExit::DeactivateInterrupt {
intid: value as u32 & 0x00ff_ffff,
})),
(SYSREG_ICC_DIR_EL1, false) => {
self.set_gpr(reg, 0);
Ok(Some(ArmVmExit::Nothing))
}
(SYSREG_ICC_CTLR_EL1, false) => Ok(Some(ArmVmExit::GicCpuInterfaceRead {
register: crate::ArmGicCpuInterfaceRegister::Control,
destination: reg,
})),
(SYSREG_ICC_CTLR_EL1, true) => Ok(Some(ArmVmExit::GicCpuInterfaceWrite {
register: crate::ArmGicCpuInterfaceRegister::Control,
value,
})),
(SYSREG_ICC_PMR_EL1, false) => Ok(Some(ArmVmExit::GicCpuInterfaceRead {
register: crate::ArmGicCpuInterfaceRegister::PriorityMask,
destination: reg,
})),
(SYSREG_ICC_PMR_EL1, true) => Ok(Some(ArmVmExit::GicCpuInterfaceWrite {
register: crate::ArmGicCpuInterfaceRegister::PriorityMask,
value,
})),
(SYSREG_ICC_RPR_EL1, false) => Ok(Some(ArmVmExit::GicCpuInterfaceRead {
register: crate::ArmGicCpuInterfaceRegister::RunningPriority,
destination: reg,
})),
(SYSREG_ICC_RPR_EL1, true) => Ok(Some(ArmVmExit::GicCpuInterfaceWrite {
register: crate::ArmGicCpuInterfaceRegister::RunningPriority,
value,
})),
_ => {
Ok(None)
}
}
}
}
fn physical_counter() -> u64 {
let counter: u64;
unsafe {
core::arch::asm!("mrs {counter}, CNTPCT_EL0", counter = out(reg) counter);
}
counter
}
pub(crate) fn pa_bits() -> usize {
match ID_AA64MMFR0_EL1.read_as_enum(ID_AA64MMFR0_EL1::PARange) {
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_32) => 32,
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_36) => 36,
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_40) => 40,
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_42) => 42,
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_44) => 44,
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_48) => 48,
Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_52) => 52,
_ => 32,
}
}
#[allow(dead_code)]
pub(crate) fn current_gpt_level() -> usize {
let t0sz = VTCR_EL2.read(VTCR_EL2::T0SZ) as usize;
match t0sz {
16..=25 => 4,
26..=35 => 3,
_ => 2,
}
}
pub(crate) fn max_gpt_level(pa_bits: usize) -> usize {
match pa_bits {
44.. => 4,
_ => 3,
}
}
fn vtcr_for_config(levels: usize, gpa_bits: usize, pa_bits: usize) -> u64 {
let mut val = match levels {
4 => VTCR_EL2::SL0::Granule4KBLevel0 + VTCR_EL2::T0SZ.val((64 - gpa_bits) as u64),
_ => VTCR_EL2::SL0::Granule4KBLevel1 + VTCR_EL2::T0SZ.val((64 - gpa_bits) as u64),
};
match pa_bits {
52..=64 => val += VTCR_EL2::PS::PA_52B_4PB,
48..=51 => val += VTCR_EL2::PS::PA_48B_256TB,
44..=47 => val += VTCR_EL2::PS::PA_44B_16TB,
42..=43 => val += VTCR_EL2::PS::PA_42B_4TB,
40..=41 => val += VTCR_EL2::PS::PA_40B_1TB,
36..=39 => val += VTCR_EL2::PS::PA_36B_64GB,
_ => val += VTCR_EL2::PS::PA_32B_4GB,
}
val += VTCR_EL2::TG0::Granule4KB
+ VTCR_EL2::SH0::Inner
+ VTCR_EL2::ORGN0::NormalWBRAWA
+ VTCR_EL2::IRGN0::NormalWBRAWA;
val.value
}