Skip to content

Commit b34cc5d

Browse files
cshungCopilot
andcommitted
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<hv_arm64_memory_intercept_message> 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>
1 parent a214b54 commit b34cc5d

3 files changed

Lines changed: 340 additions & 14 deletions

File tree

src/hyperlight_host/src/hypervisor/hyperlight_vm/aarch64.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::hypervisor::hyperlight_vm::get_guest_log_filter;
2929
use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters};
3030
#[cfg(kvm)]
3131
use crate::hypervisor::virtual_machine::kvm::KvmVm;
32-
#[cfg(kvm)]
32+
#[cfg(any(kvm, mshv3))]
3333
use crate::hypervisor::virtual_machine::{HypervisorType, VmError};
3434
use crate::hypervisor::virtual_machine::{
3535
RegisterError, ResetVcpuError, VirtualMachine, get_available_hypervisor,
@@ -64,9 +64,11 @@ impl HyperlightVm {
6464
let vm: VmType = match get_available_hypervisor() {
6565
#[cfg(kvm)]
6666
Some(HypervisorType::Kvm) => Box::new(KvmVm::new().map_err(VmError::CreateVm)?),
67-
// TODO: mshv support
6867
#[cfg(mshv3)]
69-
Some(HypervisorType::Mshv) => return Err(CreateHyperlightVmError::NoHypervisorFound),
68+
Some(HypervisorType::Mshv) => {
69+
use crate::hypervisor::virtual_machine::mshv::MshvVm;
70+
Box::new(MshvVm::new().map_err(VmError::CreateVm)?)
71+
}
7072
None => return Err(CreateHyperlightVmError::NoHypervisorFound),
7173
};
7274
vm.set_sregs(&CommonSpecialRegisters::defaults(root_pt_addr))

src/hyperlight_host/src/hypervisor/virtual_machine/mshv/aarch64.rs

Lines changed: 325 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,343 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
use std::sync::LazyLock;
18+
19+
use hyperlight_common::outb::VmAction;
20+
use mshv_bindings::{
21+
hv_message_type, hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA,
22+
hv_register_assoc, hv_register_name_HV_ARM64_REGISTER_CPACR_EL1,
23+
hv_register_name_HV_ARM64_REGISTER_FPCR, hv_register_name_HV_ARM64_REGISTER_FPSR,
24+
hv_register_name_HV_ARM64_REGISTER_MAIR_EL1, hv_register_name_HV_ARM64_REGISTER_PC,
25+
hv_register_name_HV_ARM64_REGISTER_Q0, hv_register_name_HV_ARM64_REGISTER_SCTLR_EL1,
26+
hv_register_name_HV_ARM64_REGISTER_SP_EL1, hv_register_name_HV_ARM64_REGISTER_TCR_EL1,
27+
hv_register_name_HV_ARM64_REGISTER_TTBR0_EL1, hv_register_name_HV_ARM64_REGISTER_VBAR_EL1,
28+
hv_register_value, hv_u128, mshv_create_partition_v2, mshv_user_mem_region,
29+
};
30+
use mshv_ioctls::{Mshv, VcpuFd, VmFd};
1731
use tracing::{Span, instrument};
1832

19-
use crate::hypervisor::virtual_machine::CreateVmError;
33+
use crate::hypervisor::regs::{
34+
CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
35+
};
36+
use crate::hypervisor::virtual_machine::{
37+
CreateVmError, MapMemoryError, RegisterError, ResetVcpuError, RunVcpuError, UnmapMemoryError,
38+
VirtualMachine, VmExit,
39+
};
40+
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
41+
42+
static MSHV: LazyLock<std::result::Result<Mshv, CreateVmError>> =
43+
LazyLock::new(|| Mshv::new().map_err(|e| CreateVmError::HypervisorNotAvailable(e.into())));
2044

21-
/// Return `true` if the MSHV API is available
45+
/// Determine whether the MSHV hypervisor API is available on aarch64.
2246
#[instrument(skip_all, parent = Span::current(), level = "Trace")]
2347
pub(crate) fn is_hypervisor_present() -> bool {
24-
// TODO(aarch64): implement MSHV detection
25-
false
48+
match Mshv::new() {
49+
Ok(_) => true,
50+
Err(_) => {
51+
tracing::info!("MSHV is not available on this system");
52+
false
53+
}
54+
}
2655
}
2756

28-
/// An MSHV implementation of a single-vcpu VM
57+
/// An MSHV implementation of a single-vcpu VM for aarch64.
2958
#[derive(Debug)]
3059
pub(crate) struct MshvVm {
31-
_placeholder: (),
60+
vm_fd: VmFd,
61+
vcpu_fd: VcpuFd,
3262
}
3363

34-
#[allow(unused)]
3564
impl MshvVm {
36-
#[allow(unused)]
65+
/// Create a new MSHV VM instance for aarch64.
66+
#[instrument(skip_all, parent = Span::current(), level = "Trace")]
3767
pub(crate) fn new() -> std::result::Result<Self, CreateVmError> {
38-
unimplemented!("MshvVm::new")
68+
let mshv = MSHV.as_ref().map_err(|e| e.clone())?;
69+
70+
let pr: mshv_create_partition_v2 = Default::default();
71+
let vm_fd = mshv
72+
.create_vm_with_args(&pr)
73+
.map_err(|e| CreateVmError::CreateVmFd(e.into()))?;
74+
75+
vm_fd
76+
.initialize()
77+
.map_err(|e| CreateVmError::InitializeVm(e.into()))?;
78+
79+
let vcpu_fd = vm_fd
80+
.create_vcpu(0)
81+
.map_err(|e| CreateVmError::CreateVcpuFd(e.into()))?;
82+
83+
Ok(Self { vm_fd, vcpu_fd })
84+
}
85+
86+
/// Helper to get a single 64-bit register by name.
87+
fn get_reg64(&self, name: u32) -> Result<u64, RegisterError> {
88+
let mut reg = [hv_register_assoc {
89+
name,
90+
..Default::default()
91+
}];
92+
self.vcpu_fd
93+
.get_reg(&mut reg)
94+
.map_err(|e| RegisterError::GetRegs(e.into()))?;
95+
Ok(unsafe { reg[0].value.reg64 })
96+
}
97+
98+
/// Helper to set a single 64-bit register by name.
99+
fn set_reg64(&self, name: u32, value: u64) -> Result<(), RegisterError> {
100+
self.vcpu_fd
101+
.set_reg(&[hv_register_assoc {
102+
name,
103+
value: hv_register_value { reg64: value },
104+
..Default::default()
105+
}])
106+
.map_err(|e| RegisterError::SetRegs(e.into()))
107+
}
108+
109+
/// Helper to get a single 128-bit register (Q/SIMD) by name.
110+
fn get_reg128(&self, name: u32) -> Result<u128, RegisterError> {
111+
let mut reg = [hv_register_assoc {
112+
name,
113+
..Default::default()
114+
}];
115+
self.vcpu_fd
116+
.get_reg(&mut reg)
117+
.map_err(|e| RegisterError::GetFpu(e.into()))?;
118+
let v = unsafe { reg[0].value.reg128 };
119+
Ok((v.high_part as u128) << 64 | v.low_part as u128)
120+
}
121+
122+
/// Helper to set a single 128-bit register (Q/SIMD) by name.
123+
fn set_reg128(&self, name: u32, value: u128) -> Result<(), RegisterError> {
124+
self.vcpu_fd
125+
.set_reg(&[hv_register_assoc {
126+
name,
127+
value: hv_register_value {
128+
reg128: hv_u128 {
129+
low_part: value as u64,
130+
high_part: (value >> 64) as u64,
131+
},
132+
},
133+
..Default::default()
134+
}])
135+
.map_err(|e| RegisterError::SetFpu(e.into()))
136+
}
137+
}
138+
139+
impl VirtualMachine for MshvVm {
140+
unsafe fn map_memory(
141+
&mut self,
142+
(_slot, region): (u32, &MemoryRegion),
143+
) -> std::result::Result<(), MapMemoryError> {
144+
let mshv_region: mshv_user_mem_region = region.into();
145+
self.vm_fd
146+
.map_user_memory(mshv_region)
147+
.map_err(|e| MapMemoryError::Hypervisor(e.into()))
148+
}
149+
150+
fn unmap_memory(
151+
&mut self,
152+
(_slot, region): (u32, &MemoryRegion),
153+
) -> std::result::Result<(), UnmapMemoryError> {
154+
let mshv_region: mshv_user_mem_region = region.into();
155+
self.vm_fd
156+
.unmap_user_memory(mshv_region)
157+
.map_err(|e| UnmapMemoryError::Hypervisor(e.into()))
158+
}
159+
160+
fn run_vcpu(
161+
&mut self,
162+
#[cfg(feature = "trace_guest")] _tc: &mut crate::sandbox::trace::TraceContext,
163+
) -> std::result::Result<VmExit, RunVcpuError> {
164+
const UNMAPPED_GPA_MESSAGE: hv_message_type = hv_message_type_HVMSG_UNMAPPED_GPA;
165+
const INVALID_GPA_ACCESS_MESSAGE: hv_message_type = hv_message_type_HVMSG_GPA_INTERCEPT;
166+
167+
loop {
168+
let exit_reason = self.vcpu_fd.run();
169+
170+
match exit_reason {
171+
Ok(m) => {
172+
let msg_type = m.header.message_type;
173+
match msg_type {
174+
UNMAPPED_GPA_MESSAGE | INVALID_GPA_ACCESS_MESSAGE => {
175+
let mem_msg = m
176+
.to_memory_info()
177+
.map_err(|_| RunVcpuError::DecodeIOMessage(msg_type))?;
178+
let gpa = mem_msg.guest_physical_address;
179+
180+
// On aarch64, I/O is performed via MMIO writes to
181+
// the I/O page, similar to the KVM backend.
182+
let io_page_gpa =
183+
const { hyperlight_common::layout::io_page().unwrap().0 };
184+
let access_type = mem_msg.header.intercept_access_type;
185+
let is_write = access_type != 0;
186+
187+
if is_write
188+
&& gpa >= io_page_gpa
189+
&& (gpa - io_page_gpa) < hyperlight_common::vmem::PAGE_SIZE as u64
190+
{
191+
let off = (gpa - io_page_gpa) as usize;
192+
let port = off / core::mem::size_of::<u64>();
193+
194+
// Advance PC past the faulting instruction.
195+
// MSHV does not auto-advance PC on intercepts.
196+
let pc = mem_msg.header.pc;
197+
let instruction_length = mem_msg.header.instruction_length as u64;
198+
self.vcpu_fd
199+
.set_reg(&[hv_register_assoc {
200+
name: hv_register_name_HV_ARM64_REGISTER_PC,
201+
value: hv_register_value {
202+
reg64: pc + instruction_length,
203+
},
204+
..Default::default()
205+
}])
206+
.map_err(|e| RunVcpuError::IncrementRip(e.into()))?;
207+
208+
if port == VmAction::Halt as usize {
209+
return Ok(VmExit::Halt());
210+
} else {
211+
// The data value is read from the I/O page
212+
// by the host; pass the offset as context.
213+
return Ok(VmExit::IoOut(
214+
port as u16,
215+
(off as u64).to_le_bytes().to_vec(),
216+
));
217+
}
218+
} else {
219+
// Non-I/O page memory access
220+
return match MemoryRegionFlags::try_from(mem_msg)
221+
.map_err(|_| RunVcpuError::ParseGpaAccessInfo)
222+
{
223+
Ok(MemoryRegionFlags::READ) => Ok(VmExit::MmioRead(gpa)),
224+
Ok(MemoryRegionFlags::WRITE) => Ok(VmExit::MmioWrite(gpa)),
225+
Ok(_) => Ok(VmExit::Unknown("Unknown MMIO access".to_string())),
226+
Err(e) => Err(e),
227+
};
228+
}
229+
}
230+
other => {
231+
return Ok(VmExit::Unknown(format!(
232+
"Unknown MSHV VCPU exit: {:?}",
233+
other
234+
)));
235+
}
236+
}
237+
}
238+
Err(e) => match e.errno() {
239+
libc::EINTR => {
240+
return Ok(VmExit::Cancelled());
241+
}
242+
libc::EAGAIN => {
243+
return Ok(VmExit::Retry());
244+
}
245+
_ => return Err(RunVcpuError::Unknown(e.into())),
246+
},
247+
}
248+
}
249+
}
250+
251+
fn regs(&self) -> std::result::Result<CommonRegisters, RegisterError> {
252+
let mshv_regs = self
253+
.vcpu_fd
254+
.get_regs()
255+
.map_err(|e| RegisterError::GetRegs(e.into()))?;
256+
Ok(CommonRegisters {
257+
x: mshv_regs.regs,
258+
sp: mshv_regs.sp,
259+
pc: mshv_regs.pc,
260+
pstate: mshv_regs.pstate,
261+
})
262+
}
263+
264+
fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError> {
265+
use mshv_bindings::StandardRegisters;
266+
let mshv_regs = StandardRegisters {
267+
regs: regs.x,
268+
sp: regs.sp,
269+
pc: regs.pc,
270+
pstate: regs.pstate,
271+
// sp_el1 and elr_el1 are managed via special registers
272+
sp_el1: 0,
273+
elr_el1: 0,
274+
fpsr: 0,
275+
fpcr: 0,
276+
};
277+
self.vcpu_fd
278+
.set_regs(&mshv_regs)
279+
.map_err(|e| RegisterError::SetRegs(e.into()))
280+
}
281+
282+
fn fpu(&self) -> std::result::Result<CommonFpu, RegisterError> {
283+
let mut v: [u128; 32] = [0; 32];
284+
for i in 0..32u32 {
285+
v[i as usize] = self.get_reg128(hv_register_name_HV_ARM64_REGISTER_Q0 + i)?;
286+
}
287+
let fpsr = self.get_reg64(hv_register_name_HV_ARM64_REGISTER_FPSR)? as u32;
288+
let fpcr = self.get_reg64(hv_register_name_HV_ARM64_REGISTER_FPCR)? as u32;
289+
Ok(CommonFpu { v, fpsr, fpcr })
290+
}
291+
292+
fn set_fpu(&self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError> {
293+
for i in 0..32u32 {
294+
self.set_reg128(hv_register_name_HV_ARM64_REGISTER_Q0 + i, fpu.v[i as usize])?;
295+
}
296+
self.set_reg64(hv_register_name_HV_ARM64_REGISTER_FPSR, fpu.fpsr as u64)?;
297+
self.set_reg64(hv_register_name_HV_ARM64_REGISTER_FPCR, fpu.fpcr as u64)?;
298+
Ok(())
299+
}
300+
301+
fn sregs(&self) -> std::result::Result<CommonSpecialRegisters, RegisterError> {
302+
Ok(CommonSpecialRegisters {
303+
ttbr0_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_TTBR0_EL1)?,
304+
tcr_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_TCR_EL1)?,
305+
mair_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_MAIR_EL1)?,
306+
sctlr_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_SCTLR_EL1)?,
307+
cpacr_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_CPACR_EL1)?,
308+
vbar_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_VBAR_EL1)?,
309+
sp_el1: self.get_reg64(hv_register_name_HV_ARM64_REGISTER_SP_EL1)?,
310+
})
311+
}
312+
313+
fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> std::result::Result<(), RegisterError> {
314+
self.set_reg64(
315+
hv_register_name_HV_ARM64_REGISTER_TTBR0_EL1,
316+
sregs.ttbr0_el1,
317+
)?;
318+
self.set_reg64(hv_register_name_HV_ARM64_REGISTER_TCR_EL1, sregs.tcr_el1)?;
319+
self.set_reg64(hv_register_name_HV_ARM64_REGISTER_MAIR_EL1, sregs.mair_el1)?;
320+
self.set_reg64(
321+
hv_register_name_HV_ARM64_REGISTER_SCTLR_EL1,
322+
sregs.sctlr_el1,
323+
)?;
324+
self.set_reg64(
325+
hv_register_name_HV_ARM64_REGISTER_CPACR_EL1,
326+
sregs.cpacr_el1,
327+
)?;
328+
self.set_reg64(hv_register_name_HV_ARM64_REGISTER_VBAR_EL1, sregs.vbar_el1)?;
329+
self.set_reg64(hv_register_name_HV_ARM64_REGISTER_SP_EL1, sregs.sp_el1)?;
330+
Ok(())
331+
}
332+
333+
fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError> {
334+
// Debug register support on aarch64 MSHV is not yet implemented
335+
Ok(CommonDebugRegs::default())
336+
}
337+
338+
fn set_debug_regs(&self, _drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError> {
339+
// Debug register support on aarch64 MSHV is not yet implemented
340+
Ok(())
341+
}
342+
343+
fn can_reset_vcpu(&self) -> bool {
344+
true
345+
}
346+
347+
fn reset_vcpu(&mut self) -> Result<(), ResetVcpuError> {
348+
// Reset the vCPU by zeroing all general-purpose registers
349+
use mshv_bindings::StandardRegisters;
350+
let regs = StandardRegisters::default();
351+
self.vcpu_fd
352+
.set_regs(&regs)
353+
.map_err(|e| ResetVcpuError::Hypervisor(e.into()))?;
354+
Ok(())
39355
}
40356
}

src/hyperlight_host/src/mem/memory_region.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,16 @@ impl TryFrom<hv_x64_memory_intercept_message> for MemoryRegionFlags {
118118
impl TryFrom<hv_arm64_memory_intercept_message> for MemoryRegionFlags {
119119
type Error = crate::HyperlightError;
120120

121-
fn try_from(_msg: hv_arm64_memory_intercept_message) -> crate::Result<Self> {
122-
unimplemented!("try_from")
121+
fn try_from(msg: hv_arm64_memory_intercept_message) -> crate::Result<Self> {
122+
let access_type = msg.header.intercept_access_type;
123+
match access_type {
124+
0 => Ok(MemoryRegionFlags::READ),
125+
1 => Ok(MemoryRegionFlags::WRITE),
126+
2 => Ok(MemoryRegionFlags::EXECUTE),
127+
_ => Err(crate::HyperlightError::Error(
128+
"unknown memory access type".to_string(),
129+
)),
130+
}
123131
}
124132
}
125133

0 commit comments

Comments
 (0)