From 145ee97bacbc5f0e8daf43cc8c89a6f0ade78ecf Mon Sep 17 00:00:00 2001 From: cshung <3410332+cshung@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:02:37 -0700 Subject: [PATCH 1/2] Implement MSHV aarch64 backend Add a full VirtualMachine trait implementation for MSHV on aarch64, replacing the previous stub that returned false/unimplemented. Key changes: - mshv/aarch64.rs: Complete implementation including VM creation, memory mapping, vcpu run loop with MMIO-based I/O dispatch, general/FPU/special register access via hv_register_assoc, and vcpu reset support. - hyperlight_vm/aarch64.rs: Wire up MshvVm::new() so MSHV is used when detected (previously returned NoHypervisorFound). - memory_region.rs: Implement TryFrom for MemoryRegionFlags using intercept_access_type field. The implementation mirrors the KVM aarch64 backend's I/O page pattern (MMIO writes to the I/O page GPA are interpreted as port I/O) and follows MSHV x86_64 patterns for partition creation and register access. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> --- .../src/hypervisor/hyperlight_vm/aarch64.rs | 8 +- .../virtual_machine/mshv/aarch64.rs | 334 +++++++++++++++++- src/hyperlight_host/src/mem/memory_region.rs | 12 +- 3 files changed, 340 insertions(+), 14 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/hyperlight_vm/aarch64.rs b/src/hyperlight_host/src/hypervisor/hyperlight_vm/aarch64.rs index 770959c14..eaf49ac74 100644 --- a/src/hyperlight_host/src/hypervisor/hyperlight_vm/aarch64.rs +++ b/src/hyperlight_host/src/hypervisor/hyperlight_vm/aarch64.rs @@ -29,7 +29,7 @@ use crate::hypervisor::hyperlight_vm::get_guest_log_filter; use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters}; #[cfg(kvm)] use crate::hypervisor::virtual_machine::kvm::KvmVm; -#[cfg(kvm)] +#[cfg(any(kvm, mshv3))] use crate::hypervisor::virtual_machine::{HypervisorType, VmError}; use crate::hypervisor::virtual_machine::{ RegisterError, ResetVcpuError, VirtualMachine, get_available_hypervisor, @@ -64,9 +64,11 @@ impl HyperlightVm { let vm: VmType = match get_available_hypervisor() { #[cfg(kvm)] Some(HypervisorType::Kvm) => Box::new(KvmVm::new().map_err(VmError::CreateVm)?), - // TODO: mshv support #[cfg(mshv3)] - Some(HypervisorType::Mshv) => return Err(CreateHyperlightVmError::NoHypervisorFound), + Some(HypervisorType::Mshv) => { + use crate::hypervisor::virtual_machine::mshv::MshvVm; + Box::new(MshvVm::new().map_err(VmError::CreateVm)?) + } None => return Err(CreateHyperlightVmError::NoHypervisorFound), }; vm.set_sregs(&CommonSpecialRegisters::defaults(root_pt_addr)) diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/aarch64.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/aarch64.rs index 3580adad1..b29115faf 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/aarch64.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/aarch64.rs @@ -14,27 +14,343 @@ See the License for the specific language governing permissions and limitations under the License. */ +use std::sync::LazyLock; + +use hyperlight_common::outb::VmAction; +use mshv_bindings::{ + hv_message_type, hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA, + hv_register_assoc, hv_register_name_HV_ARM64_REGISTER_CPACR_EL1, + hv_register_name_HV_ARM64_REGISTER_FPCR, hv_register_name_HV_ARM64_REGISTER_FPSR, + hv_register_name_HV_ARM64_REGISTER_MAIR_EL1, hv_register_name_HV_ARM64_REGISTER_PC, + hv_register_name_HV_ARM64_REGISTER_Q0, hv_register_name_HV_ARM64_REGISTER_SCTLR_EL1, + hv_register_name_HV_ARM64_REGISTER_SP_EL1, hv_register_name_HV_ARM64_REGISTER_TCR_EL1, + hv_register_name_HV_ARM64_REGISTER_TTBR0_EL1, hv_register_name_HV_ARM64_REGISTER_VBAR_EL1, + hv_register_value, hv_u128, mshv_create_partition_v2, mshv_user_mem_region, +}; +use mshv_ioctls::{Mshv, VcpuFd, VmFd}; use tracing::{Span, instrument}; -use crate::hypervisor::virtual_machine::CreateVmError; +use crate::hypervisor::regs::{ + CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters, +}; +use crate::hypervisor::virtual_machine::{ + CreateVmError, MapMemoryError, RegisterError, ResetVcpuError, RunVcpuError, UnmapMemoryError, + VirtualMachine, VmExit, +}; +use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; + +static MSHV: LazyLock> = + LazyLock::new(|| Mshv::new().map_err(|e| CreateVmError::HypervisorNotAvailable(e.into()))); -/// Return `true` if the MSHV API is available +/// Determine whether the MSHV hypervisor API is available on aarch64. #[instrument(skip_all, parent = Span::current(), level = "Trace")] pub(crate) fn is_hypervisor_present() -> bool { - // TODO(aarch64): implement MSHV detection - false + match Mshv::new() { + Ok(_) => true, + Err(_) => { + tracing::info!("MSHV is not available on this system"); + false + } + } } -/// An MSHV implementation of a single-vcpu VM +/// An MSHV implementation of a single-vcpu VM for aarch64. #[derive(Debug)] pub(crate) struct MshvVm { - _placeholder: (), + vm_fd: VmFd, + vcpu_fd: VcpuFd, } -#[allow(unused)] impl MshvVm { - #[allow(unused)] + /// Create a new MSHV VM instance for aarch64. + #[instrument(skip_all, parent = Span::current(), level = "Trace")] pub(crate) fn new() -> std::result::Result { - unimplemented!("MshvVm::new") + let mshv = MSHV.as_ref().map_err(|e| e.clone())?; + + let pr: mshv_create_partition_v2 = Default::default(); + let vm_fd = mshv + .create_vm_with_args(&pr) + .map_err(|e| CreateVmError::CreateVmFd(e.into()))?; + + vm_fd + .initialize() + .map_err(|e| CreateVmError::InitializeVm(e.into()))?; + + let vcpu_fd = vm_fd + .create_vcpu(0) + .map_err(|e| CreateVmError::CreateVcpuFd(e.into()))?; + + Ok(Self { vm_fd, vcpu_fd }) + } + + /// Helper to get a single 64-bit register by name. + fn get_reg64(&self, name: u32) -> Result { + let mut reg = [hv_register_assoc { + name, + ..Default::default() + }]; + self.vcpu_fd + .get_reg(&mut reg) + .map_err(|e| RegisterError::GetRegs(e.into()))?; + Ok(unsafe { reg[0].value.reg64 }) + } + + /// Helper to set a single 64-bit register by name. + fn set_reg64(&self, name: u32, value: u64) -> Result<(), RegisterError> { + self.vcpu_fd + .set_reg(&[hv_register_assoc { + name, + value: hv_register_value { reg64: value }, + ..Default::default() + }]) + .map_err(|e| RegisterError::SetRegs(e.into())) + } + + /// Helper to get a single 128-bit register (Q/SIMD) by name. + fn get_reg128(&self, name: u32) -> Result { + let mut reg = [hv_register_assoc { + name, + ..Default::default() + }]; + self.vcpu_fd + .get_reg(&mut reg) + .map_err(|e| RegisterError::GetFpu(e.into()))?; + let v = unsafe { reg[0].value.reg128 }; + Ok((v.high_part as u128) << 64 | v.low_part as u128) + } + + /// Helper to set a single 128-bit register (Q/SIMD) by name. + fn set_reg128(&self, name: u32, value: u128) -> Result<(), RegisterError> { + self.vcpu_fd + .set_reg(&[hv_register_assoc { + name, + value: hv_register_value { + reg128: hv_u128 { + low_part: value as u64, + high_part: (value >> 64) as u64, + }, + }, + ..Default::default() + }]) + .map_err(|e| RegisterError::SetFpu(e.into())) + } +} + +impl VirtualMachine for MshvVm { + unsafe fn map_memory( + &mut self, + (_slot, region): (u32, &MemoryRegion), + ) -> std::result::Result<(), MapMemoryError> { + let mshv_region: mshv_user_mem_region = region.into(); + self.vm_fd + .map_user_memory(mshv_region) + .map_err(|e| MapMemoryError::Hypervisor(e.into())) + } + + fn unmap_memory( + &mut self, + (_slot, region): (u32, &MemoryRegion), + ) -> std::result::Result<(), UnmapMemoryError> { + let mshv_region: mshv_user_mem_region = region.into(); + self.vm_fd + .unmap_user_memory(mshv_region) + .map_err(|e| UnmapMemoryError::Hypervisor(e.into())) + } + + fn run_vcpu( + &mut self, + #[cfg(feature = "trace_guest")] _tc: &mut crate::sandbox::trace::TraceContext, + ) -> std::result::Result { + const UNMAPPED_GPA_MESSAGE: hv_message_type = hv_message_type_HVMSG_UNMAPPED_GPA; + const INVALID_GPA_ACCESS_MESSAGE: hv_message_type = hv_message_type_HVMSG_GPA_INTERCEPT; + + loop { + let exit_reason = self.vcpu_fd.run(); + + match exit_reason { + Ok(m) => { + let msg_type = m.header.message_type; + match msg_type { + UNMAPPED_GPA_MESSAGE | INVALID_GPA_ACCESS_MESSAGE => { + let mem_msg = m + .to_memory_info() + .map_err(|_| RunVcpuError::DecodeIOMessage(msg_type))?; + let gpa = mem_msg.guest_physical_address; + + // On aarch64, I/O is performed via MMIO writes to + // the I/O page, similar to the KVM backend. + let io_page_gpa = + const { hyperlight_common::layout::io_page().unwrap().0 }; + let access_type = mem_msg.header.intercept_access_type; + let is_write = access_type != 0; + + if is_write + && gpa >= io_page_gpa + && (gpa - io_page_gpa) < hyperlight_common::vmem::PAGE_SIZE as u64 + { + let off = (gpa - io_page_gpa) as usize; + let port = off / core::mem::size_of::(); + + // Advance PC past the faulting instruction. + // MSHV does not auto-advance PC on intercepts. + let pc = mem_msg.header.pc; + let instruction_length = mem_msg.header.instruction_length as u64; + self.vcpu_fd + .set_reg(&[hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_PC, + value: hv_register_value { + reg64: pc + instruction_length, + }, + ..Default::default() + }]) + .map_err(|e| RunVcpuError::IncrementRip(e.into()))?; + + if port == VmAction::Halt as usize { + return Ok(VmExit::Halt()); + } else { + // The data value is read from the I/O page + // by the host; pass the offset as context. + return Ok(VmExit::IoOut( + port as u16, + (off as u64).to_le_bytes().to_vec(), + )); + } + } else { + // Non-I/O page memory access + return match MemoryRegionFlags::try_from(mem_msg) + .map_err(|_| RunVcpuError::ParseGpaAccessInfo) + { + Ok(MemoryRegionFlags::READ) => Ok(VmExit::MmioRead(gpa)), + Ok(MemoryRegionFlags::WRITE) => Ok(VmExit::MmioWrite(gpa)), + Ok(_) => Ok(VmExit::Unknown("Unknown MMIO access".to_string())), + Err(e) => Err(e), + }; + } + } + other => { + return Ok(VmExit::Unknown(format!( + "Unknown MSHV VCPU exit: {:?}", + other + ))); + } + } + } + Err(e) => match e.errno() { + libc::EINTR => { + return Ok(VmExit::Cancelled()); + } + libc::EAGAIN => { + return Ok(VmExit::Retry()); + } + _ => return Err(RunVcpuError::Unknown(e.into())), + }, + } + } + } + + fn regs(&self) -> std::result::Result { + let mshv_regs = self + .vcpu_fd + .get_regs() + .map_err(|e| RegisterError::GetRegs(e.into()))?; + Ok(CommonRegisters { + x: mshv_regs.regs, + sp: mshv_regs.sp, + pc: mshv_regs.pc, + pstate: mshv_regs.pstate, + }) + } + + fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError> { + use mshv_bindings::StandardRegisters; + let mshv_regs = StandardRegisters { + regs: regs.x, + sp: regs.sp, + pc: regs.pc, + pstate: regs.pstate, + // sp_el1 and elr_el1 are managed via special registers + sp_el1: 0, + elr_el1: 0, + fpsr: 0, + fpcr: 0, + }; + self.vcpu_fd + .set_regs(&mshv_regs) + .map_err(|e| RegisterError::SetRegs(e.into())) + } + + fn fpu(&self) -> std::result::Result { + let mut v: [u128; 32] = [0; 32]; + for i in 0..32u32 { + v[i as usize] = self.get_reg128(hv_register_name_HV_ARM64_REGISTER_Q0 + i)?; + } + let fpsr = self.get_reg64(hv_register_name_HV_ARM64_REGISTER_FPSR)? as u32; + let fpcr = self.get_reg64(hv_register_name_HV_ARM64_REGISTER_FPCR)? as u32; + Ok(CommonFpu { v, fpsr, fpcr }) + } + + fn set_fpu(&self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError> { + for i in 0..32u32 { + self.set_reg128(hv_register_name_HV_ARM64_REGISTER_Q0 + i, fpu.v[i as usize])?; + } + self.set_reg64(hv_register_name_HV_ARM64_REGISTER_FPSR, fpu.fpsr as u64)?; + self.set_reg64(hv_register_name_HV_ARM64_REGISTER_FPCR, fpu.fpcr as u64)?; + Ok(()) + } + + fn sregs(&self) -> std::result::Result { + Ok(CommonSpecialRegisters { + ttbr0_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_TTBR0_EL1)?, + tcr_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_TCR_EL1)?, + mair_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_MAIR_EL1)?, + sctlr_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_SCTLR_EL1)?, + cpacr_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_CPACR_EL1)?, + vbar_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_VBAR_EL1)?, + sp_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_SP_EL1)?, + }) + } + + fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> std::result::Result<(), RegisterError> { + self.set_reg64( + hv_register_name_HV_ARM64_REGISTER_TTBR0_EL1, + sregs.ttbr0_el1, + )?; + self.set_reg64(hv_register_name_HV_ARM64_REGISTER_TCR_EL1, sregs.tcr_el1)?; + self.set_reg64(hv_register_name_HV_ARM64_REGISTER_MAIR_EL1, sregs.mair_el1)?; + self.set_reg64( + hv_register_name_HV_ARM64_REGISTER_SCTLR_EL1, + sregs.sctlr_el1, + )?; + self.set_reg64( + hv_register_name_HV_ARM64_REGISTER_CPACR_EL1, + sregs.cpacr_el1, + )?; + self.set_reg64(hv_register_name_HV_ARM64_REGISTER_VBAR_EL1, sregs.vbar_el1)?; + self.set_reg64(hv_register_name_HV_ARM64_REGISTER_SP_EL1, sregs.sp_el1)?; + Ok(()) + } + + fn debug_regs(&self) -> std::result::Result { + // Debug register support on aarch64 MSHV is not yet implemented + Ok(CommonDebugRegs::default()) + } + + fn set_debug_regs(&self, _drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError> { + // Debug register support on aarch64 MSHV is not yet implemented + Ok(()) + } + + fn can_reset_vcpu(&self) -> bool { + true + } + + fn reset_vcpu(&mut self) -> Result<(), ResetVcpuError> { + // Reset the vCPU by zeroing all general-purpose registers + use mshv_bindings::StandardRegisters; + let regs = StandardRegisters::default(); + self.vcpu_fd + .set_regs(®s) + .map_err(|e| ResetVcpuError::Hypervisor(e.into()))?; + Ok(()) } } diff --git a/src/hyperlight_host/src/mem/memory_region.rs b/src/hyperlight_host/src/mem/memory_region.rs index 5d839647a..0122bbc6a 100644 --- a/src/hyperlight_host/src/mem/memory_region.rs +++ b/src/hyperlight_host/src/mem/memory_region.rs @@ -118,8 +118,16 @@ impl TryFrom for MemoryRegionFlags { impl TryFrom for MemoryRegionFlags { type Error = crate::HyperlightError; - fn try_from(_msg: hv_arm64_memory_intercept_message) -> crate::Result { - unimplemented!("try_from") + fn try_from(msg: hv_arm64_memory_intercept_message) -> crate::Result { + let access_type = msg.header.intercept_access_type; + match access_type { + 0 => Ok(MemoryRegionFlags::READ), + 1 => Ok(MemoryRegionFlags::WRITE), + 2 => Ok(MemoryRegionFlags::EXECUTE), + _ => Err(crate::HyperlightError::Error( + "unknown memory access type".to_string(), + )), + } } } From fb92ede47ff7c1bbbba15c84b6033585962660e4 Mon Sep 17 00:00:00 2001 From: cshung <3410332+cshung@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:13:14 +0000 Subject: [PATCH 2/2] fix: harden ARM64 MSHV backend Configure the GIC, decode MMIO writes, preserve register state, and rebuild the partition during restore. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> --- .../virtual_machine/mshv/aarch64.rs | 404 ++++++++++++------ .../src/hypervisor/virtual_machine/whp.rs | 7 + src/hyperlight_host/src/sandbox/config.rs | 4 +- typos.toml | 4 + 4 files changed, 277 insertions(+), 142 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/aarch64.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/aarch64.rs index b29115faf..be9b467af 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/aarch64.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/aarch64.rs @@ -19,12 +19,20 @@ use std::sync::LazyLock; use hyperlight_common::outb::VmAction; use mshv_bindings::{ hv_message_type, hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA, + hv_partition_property_code_HV_PARTITION_PROPERTY_GIC_LPI_INT_ID_BITS, + hv_partition_property_code_HV_PARTITION_PROPERTY_GIC_PPI_OVERFLOW_INTERRUPT_FROM_CNTV, + hv_partition_property_code_HV_PARTITION_PROPERTY_GIC_PPI_PERFORMANCE_MONITORS_INTERRUPT, + hv_partition_property_code_HV_PARTITION_PROPERTY_GICD_BASE_ADDRESS, + hv_partition_property_code_HV_PARTITION_PROPERTY_GITS_TRANSLATER_BASE_ADDRESS, hv_register_assoc, hv_register_name_HV_ARM64_REGISTER_CPACR_EL1, - hv_register_name_HV_ARM64_REGISTER_FPCR, hv_register_name_HV_ARM64_REGISTER_FPSR, - hv_register_name_HV_ARM64_REGISTER_MAIR_EL1, hv_register_name_HV_ARM64_REGISTER_PC, + hv_register_name_HV_ARM64_REGISTER_FP, hv_register_name_HV_ARM64_REGISTER_FPCR, + hv_register_name_HV_ARM64_REGISTER_FPSR, hv_register_name_HV_ARM64_REGISTER_GICR_BASE_GPA, + hv_register_name_HV_ARM64_REGISTER_LR, hv_register_name_HV_ARM64_REGISTER_MAIR_EL1, + hv_register_name_HV_ARM64_REGISTER_PC, hv_register_name_HV_ARM64_REGISTER_PSTATE, hv_register_name_HV_ARM64_REGISTER_Q0, hv_register_name_HV_ARM64_REGISTER_SCTLR_EL1, - hv_register_name_HV_ARM64_REGISTER_SP_EL1, hv_register_name_HV_ARM64_REGISTER_TCR_EL1, - hv_register_name_HV_ARM64_REGISTER_TTBR0_EL1, hv_register_name_HV_ARM64_REGISTER_VBAR_EL1, + hv_register_name_HV_ARM64_REGISTER_SP_EL0, hv_register_name_HV_ARM64_REGISTER_SP_EL1, + hv_register_name_HV_ARM64_REGISTER_TCR_EL1, hv_register_name_HV_ARM64_REGISTER_TTBR0_EL1, + hv_register_name_HV_ARM64_REGISTER_VBAR_EL1, hv_register_name_HV_ARM64_REGISTER_X0, hv_register_value, hv_u128, mshv_create_partition_v2, mshv_user_mem_region, }; use mshv_ioctls::{Mshv, VcpuFd, VmFd}; @@ -42,6 +50,19 @@ use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; static MSHV: LazyLock> = LazyLock::new(|| Mshv::new().map_err(|e| CreateVmError::HypervisorNotAvailable(e.into()))); +fn mmio_write_info(syndrome: u64) -> Option<(u32, usize)> { + const ISV: u64 = 1 << 24; + const WNR: u64 = 1 << 6; + + if syndrome & (ISV | WNR) != ISV | WNR { + return None; + } + + let source_register = ((syndrome >> 16) & 0x1f) as u32; + let access_size = 1usize << ((syndrome >> 22) & 0x3); + Some((source_register, access_size)) +} + /// Determine whether the MSHV hypervisor API is available on aarch64. #[instrument(skip_all, parent = Span::current(), level = "Trace")] pub(crate) fn is_hypervisor_present() -> bool { @@ -59,6 +80,7 @@ pub(crate) fn is_hypervisor_present() -> bool { pub(crate) struct MshvVm { vm_fd: VmFd, vcpu_fd: VcpuFd, + mapped_regions: Vec, } impl MshvVm { @@ -72,6 +94,33 @@ impl MshvVm { .create_vm_with_args(&pr) .map_err(|e| CreateVmError::CreateVmFd(e.into()))?; + for (property, value) in [ + ( + hv_partition_property_code_HV_PARTITION_PROPERTY_GICD_BASE_ADDRESS, + 0xffff0000, + ), + ( + hv_partition_property_code_HV_PARTITION_PROPERTY_GITS_TRANSLATER_BASE_ADDRESS, + 0xeff68000, + ), + ( + hv_partition_property_code_HV_PARTITION_PROPERTY_GIC_LPI_INT_ID_BITS, + 1, + ), + ( + hv_partition_property_code_HV_PARTITION_PROPERTY_GIC_PPI_OVERFLOW_INTERRUPT_FROM_CNTV, + 0x1b, + ), + ( + hv_partition_property_code_HV_PARTITION_PROPERTY_GIC_PPI_PERFORMANCE_MONITORS_INTERRUPT, + 0x17, + ), + ] { + vm_fd + .set_partition_property(property, value) + .map_err(|e| CreateVmError::SetPartitionProperty(e.into()))?; + } + vm_fd .initialize() .map_err(|e| CreateVmError::InitializeVm(e.into()))?; @@ -79,8 +128,19 @@ impl MshvVm { let vcpu_fd = vm_fd .create_vcpu(0) .map_err(|e| CreateVmError::CreateVcpuFd(e.into()))?; + vcpu_fd + .set_reg(&[hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_GICR_BASE_GPA, + value: hv_register_value { reg64: 0xeffee000 }, + ..Default::default() + }]) + .map_err(|e| CreateVmError::SetPartitionProperty(e.into()))?; - Ok(Self { vm_fd, vcpu_fd }) + Ok(Self { + vm_fd, + vcpu_fd, + mapped_regions: Vec::new(), + }) } /// Helper to get a single 64-bit register by name. @@ -105,35 +165,6 @@ impl MshvVm { }]) .map_err(|e| RegisterError::SetRegs(e.into())) } - - /// Helper to get a single 128-bit register (Q/SIMD) by name. - fn get_reg128(&self, name: u32) -> Result { - let mut reg = [hv_register_assoc { - name, - ..Default::default() - }]; - self.vcpu_fd - .get_reg(&mut reg) - .map_err(|e| RegisterError::GetFpu(e.into()))?; - let v = unsafe { reg[0].value.reg128 }; - Ok((v.high_part as u128) << 64 | v.low_part as u128) - } - - /// Helper to set a single 128-bit register (Q/SIMD) by name. - fn set_reg128(&self, name: u32, value: u128) -> Result<(), RegisterError> { - self.vcpu_fd - .set_reg(&[hv_register_assoc { - name, - value: hv_register_value { - reg128: hv_u128 { - low_part: value as u64, - high_part: (value >> 64) as u64, - }, - }, - ..Default::default() - }]) - .map_err(|e| RegisterError::SetFpu(e.into())) - } } impl VirtualMachine for MshvVm { @@ -144,7 +175,9 @@ impl VirtualMachine for MshvVm { let mshv_region: mshv_user_mem_region = region.into(); self.vm_fd .map_user_memory(mshv_region) - .map_err(|e| MapMemoryError::Hypervisor(e.into())) + .map_err(|e| MapMemoryError::Hypervisor(e.into()))?; + self.mapped_regions.push(mshv_region); + Ok(()) } fn unmap_memory( @@ -154,7 +187,13 @@ impl VirtualMachine for MshvVm { let mshv_region: mshv_user_mem_region = region.into(); self.vm_fd .unmap_user_memory(mshv_region) - .map_err(|e| UnmapMemoryError::Hypervisor(e.into())) + .map_err(|e| UnmapMemoryError::Hypervisor(e.into()))?; + self.mapped_regions.retain(|mapped| { + mapped.guest_pfn != mshv_region.guest_pfn + || mapped.size != mshv_region.size + || mapped.userspace_addr != mshv_region.userspace_addr + }); + Ok(()) } fn run_vcpu( @@ -164,87 +203,78 @@ impl VirtualMachine for MshvVm { const UNMAPPED_GPA_MESSAGE: hv_message_type = hv_message_type_HVMSG_UNMAPPED_GPA; const INVALID_GPA_ACCESS_MESSAGE: hv_message_type = hv_message_type_HVMSG_GPA_INTERCEPT; - loop { - let exit_reason = self.vcpu_fd.run(); - - match exit_reason { - Ok(m) => { - let msg_type = m.header.message_type; - match msg_type { - UNMAPPED_GPA_MESSAGE | INVALID_GPA_ACCESS_MESSAGE => { - let mem_msg = m - .to_memory_info() - .map_err(|_| RunVcpuError::DecodeIOMessage(msg_type))?; - let gpa = mem_msg.guest_physical_address; - - // On aarch64, I/O is performed via MMIO writes to - // the I/O page, similar to the KVM backend. - let io_page_gpa = - const { hyperlight_common::layout::io_page().unwrap().0 }; - let access_type = mem_msg.header.intercept_access_type; - let is_write = access_type != 0; - - if is_write - && gpa >= io_page_gpa - && (gpa - io_page_gpa) < hyperlight_common::vmem::PAGE_SIZE as u64 - { - let off = (gpa - io_page_gpa) as usize; - let port = off / core::mem::size_of::(); - - // Advance PC past the faulting instruction. - // MSHV does not auto-advance PC on intercepts. - let pc = mem_msg.header.pc; - let instruction_length = mem_msg.header.instruction_length as u64; - self.vcpu_fd - .set_reg(&[hv_register_assoc { - name: hv_register_name_HV_ARM64_REGISTER_PC, - value: hv_register_value { - reg64: pc + instruction_length, - }, - ..Default::default() - }]) - .map_err(|e| RunVcpuError::IncrementRip(e.into()))?; - - if port == VmAction::Halt as usize { - return Ok(VmExit::Halt()); - } else { - // The data value is read from the I/O page - // by the host; pass the offset as context. - return Ok(VmExit::IoOut( - port as u16, - (off as u64).to_le_bytes().to_vec(), - )); - } + match self.vcpu_fd.run() { + Ok(m) => { + let msg_type = m.header.message_type; + match msg_type { + UNMAPPED_GPA_MESSAGE | INVALID_GPA_ACCESS_MESSAGE => { + let mem_msg = m + .to_memory_info() + .map_err(|_| RunVcpuError::DecodeIOMessage(msg_type))?; + let gpa = mem_msg.guest_physical_address; + let io_page_gpa = const { hyperlight_common::layout::io_page().unwrap().0 }; + let is_write = mem_msg.header.intercept_access_type == 1; + + if is_write + && gpa >= io_page_gpa + && (gpa - io_page_gpa) < hyperlight_common::vmem::PAGE_SIZE as u64 + { + let off = (gpa - io_page_gpa) as usize; + let port = off / core::mem::size_of::(); + let pc = mem_msg.header.pc; + let instruction_length = mem_msg.header.instruction_length as u64; + self.vcpu_fd + .set_reg(&[hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_PC, + value: hv_register_value { + reg64: pc + instruction_length, + }, + ..Default::default() + }]) + .map_err(|e| RunVcpuError::IncrementRip(e.into()))?; + + if port == VmAction::Halt as usize { + return Ok(VmExit::Halt()); + } + + let (source_register, access_size) = mmio_write_info(mem_msg.syndrome) + .ok_or(RunVcpuError::ParseGpaAccessInfo)?; + let value = if source_register == 31 { + 0 } else { - // Non-I/O page memory access - return match MemoryRegionFlags::try_from(mem_msg) - .map_err(|_| RunVcpuError::ParseGpaAccessInfo) - { - Ok(MemoryRegionFlags::READ) => Ok(VmExit::MmioRead(gpa)), - Ok(MemoryRegionFlags::WRITE) => Ok(VmExit::MmioWrite(gpa)), - Ok(_) => Ok(VmExit::Unknown("Unknown MMIO access".to_string())), - Err(e) => Err(e), - }; + self.get_reg64( + hv_register_name_HV_ARM64_REGISTER_X0 + source_register, + ) + .map_err(|e| match e { + RegisterError::GetRegs(error) => RunVcpuError::Unknown(error), + _ => unreachable!("get_reg64 returned a non-get error"), + })? + }; + Ok(VmExit::IoOut( + port as u16, + value.to_le_bytes()[..access_size].to_vec(), + )) + } else { + match MemoryRegionFlags::try_from(mem_msg) + .map_err(|_| RunVcpuError::ParseGpaAccessInfo)? + { + MemoryRegionFlags::READ => Ok(VmExit::MmioRead(gpa)), + MemoryRegionFlags::WRITE => Ok(VmExit::MmioWrite(gpa)), + _ => Ok(VmExit::Unknown("Unknown MMIO access".to_string())), } } - other => { - return Ok(VmExit::Unknown(format!( - "Unknown MSHV VCPU exit: {:?}", - other - ))); - } } + other => Ok(VmExit::Unknown(format!( + "Unknown MSHV VCPU exit: {:?}", + other + ))), } - Err(e) => match e.errno() { - libc::EINTR => { - return Ok(VmExit::Cancelled()); - } - libc::EAGAIN => { - return Ok(VmExit::Retry()); - } - _ => return Err(RunVcpuError::Unknown(e.into())), - }, } + Err(e) => match e.errno() { + libc::EINTR => Ok(VmExit::Cancelled()), + libc::EAGAIN => Ok(VmExit::Retry()), + _ => Err(RunVcpuError::Unknown(e.into())), + }, } } @@ -255,47 +285,119 @@ impl VirtualMachine for MshvVm { .map_err(|e| RegisterError::GetRegs(e.into()))?; Ok(CommonRegisters { x: mshv_regs.regs, - sp: mshv_regs.sp, + sp: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_SP_EL0)?, pc: mshv_regs.pc, pstate: mshv_regs.pstate, }) } fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError> { - use mshv_bindings::StandardRegisters; - let mshv_regs = StandardRegisters { - regs: regs.x, - sp: regs.sp, - pc: regs.pc, - pstate: regs.pstate, - // sp_el1 and elr_el1 are managed via special registers - sp_el1: 0, - elr_el1: 0, - fpsr: 0, - fpcr: 0, - }; + let mut mshv_regs = Vec::with_capacity(34); + for (index, value) in regs.x[..29].iter().enumerate() { + mshv_regs.push(hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_X0 + index as u32, + value: hv_register_value { reg64: *value }, + ..Default::default() + }); + } + mshv_regs.extend([ + hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_FP, + value: hv_register_value { reg64: regs.x[29] }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_LR, + value: hv_register_value { reg64: regs.x[30] }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_SP_EL0, + value: hv_register_value { reg64: regs.sp }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_PC, + value: hv_register_value { reg64: regs.pc }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_PSTATE, + value: hv_register_value { reg64: regs.pstate }, + ..Default::default() + }, + ]); self.vcpu_fd - .set_regs(&mshv_regs) + .set_reg(&mshv_regs) .map_err(|e| RegisterError::SetRegs(e.into())) } fn fpu(&self) -> std::result::Result { + let mut regs: Vec<_> = (0..32) + .map(|index| hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_Q0 + index, + ..Default::default() + }) + .collect(); + regs.extend([ + hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_FPSR, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_FPCR, + ..Default::default() + }, + ]); + self.vcpu_fd + .get_reg(&mut regs) + .map_err(|e| RegisterError::GetFpu(e.into()))?; + let mut v: [u128; 32] = [0; 32]; - for i in 0..32u32 { - v[i as usize] = self.get_reg128(hv_register_name_HV_ARM64_REGISTER_Q0 + i)?; + for (value, reg) in v.iter_mut().zip(®s[..32]) { + let reg128 = unsafe { reg.value.reg128 }; + *value = (reg128.high_part as u128) << 64 | reg128.low_part as u128; } - let fpsr = self.get_reg64(hv_register_name_HV_ARM64_REGISTER_FPSR)? as u32; - let fpcr = self.get_reg64(hv_register_name_HV_ARM64_REGISTER_FPCR)? as u32; + let fpsr = unsafe { regs[32].value.reg64 } as u32; + let fpcr = unsafe { regs[33].value.reg64 } as u32; Ok(CommonFpu { v, fpsr, fpcr }) } fn set_fpu(&self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError> { - for i in 0..32u32 { - self.set_reg128(hv_register_name_HV_ARM64_REGISTER_Q0 + i, fpu.v[i as usize])?; - } - self.set_reg64(hv_register_name_HV_ARM64_REGISTER_FPSR, fpu.fpsr as u64)?; - self.set_reg64(hv_register_name_HV_ARM64_REGISTER_FPCR, fpu.fpcr as u64)?; - Ok(()) + let mut regs: Vec<_> = fpu + .v + .iter() + .enumerate() + .map(|(index, value)| hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_Q0 + index as u32, + value: hv_register_value { + reg128: hv_u128 { + low_part: *value as u64, + high_part: (*value >> 64) as u64, + }, + }, + ..Default::default() + }) + .collect(); + regs.extend([ + hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_FPSR, + value: hv_register_value { + reg64: fpu.fpsr as u64, + }, + ..Default::default() + }, + hv_register_assoc { + name: hv_register_name_HV_ARM64_REGISTER_FPCR, + value: hv_register_value { + reg64: fpu.fpcr as u64, + }, + ..Default::default() + }, + ]); + self.vcpu_fd + .set_reg(®s) + .map_err(|e| RegisterError::SetFpu(e.into())) } fn sregs(&self) -> std::result::Result { @@ -345,12 +447,32 @@ impl VirtualMachine for MshvVm { } fn reset_vcpu(&mut self) -> Result<(), ResetVcpuError> { - // Reset the vCPU by zeroing all general-purpose registers - use mshv_bindings::StandardRegisters; - let regs = StandardRegisters::default(); - self.vcpu_fd - .set_regs(®s) - .map_err(|e| ResetVcpuError::Hypervisor(e.into()))?; + let mut replacement = Self::new().map_err(|e| ResetVcpuError::Unknown(e.to_string()))?; + for region in &self.mapped_regions { + replacement + .vm_fd + .map_user_memory(*region) + .map_err(|e| ResetVcpuError::Hypervisor(e.into()))?; + } + replacement.mapped_regions.clone_from(&self.mapped_regions); + *self = replacement; Ok(()) } } + +#[cfg(test)] +mod tests { + use super::mmio_write_info; + + #[test] + fn decodes_mmio_write_syndrome() { + assert_eq!(mmio_write_info(0x93c8_8047), Some((8, 8))); + } + + #[test] + fn rejects_invalid_mmio_write_syndrome() { + assert_eq!(mmio_write_info(0), None); + assert_eq!(mmio_write_info(1 << 24), None); + assert_eq!(mmio_write_info(1 << 6), None); + } +} diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs index b60777fcc..4b16d609f 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs @@ -533,6 +533,13 @@ impl VirtualMachine for WhpVm { #[cfg(feature = "trace_guest")] tc.setup_guest_trace(Span::current().context()); + #[cfg_attr( + not(feature = "hw-interrupts"), + expect( + clippy::never_loop, + reason = "the loop continues when hw-interrupts is enabled" + ) + )] loop { unsafe { WHvRunVirtualProcessor( diff --git a/src/hyperlight_host/src/sandbox/config.rs b/src/hyperlight_host/src/sandbox/config.rs index e7216a8a7..442da8415 100644 --- a/src/hyperlight_host/src/sandbox/config.rs +++ b/src/hyperlight_host/src/sandbox/config.rs @@ -345,7 +345,9 @@ impl Default for SandboxConfiguration { #[cfg(test)] mod tests { - use super::{GuestMsrError, SandboxConfiguration}; + #[cfg(target_arch = "x86_64")] + use super::GuestMsrError; + use super::SandboxConfiguration; #[test] #[cfg(target_arch = "x86_64")] diff --git a/typos.toml b/typos.toml index e4cd5f295..8b1349b44 100644 --- a/typos.toml +++ b/typos.toml @@ -16,3 +16,7 @@ ist="ist" finitel="finitel" # writables as number of writable buffers writables="writables" + +[default.extend-identifiers] +# This spelling is part of the mshv-bindings API. +hv_partition_property_code_HV_PARTITION_PROPERTY_GITS_TRANSLATER_BASE_ADDRESS="hv_partition_property_code_HV_PARTITION_PROPERTY_GITS_TRANSLATER_BASE_ADDRESS"