Skip to content

Commit 4d1a570

Browse files
committed
Use WHvResetPartition on windows
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent c171901 commit 4d1a570

File tree

6 files changed

+116
-113
lines changed

6 files changed

+116
-113
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,15 @@ impl HyperlightVm {
8989
unimplemented!("get_snapshot_sregs")
9090
}
9191

92-
pub(crate) fn reset_vcpu(
92+
pub(crate) fn reset_vm_state(&mut self) -> std::result::Result<(), RegisterError> {
93+
unimplemented!("reset_vm_state")
94+
}
95+
96+
pub(crate) fn restore_sregs(
9397
&mut self,
9498
_cr3: u64,
9599
_sregs: &CommonSpecialRegisters,
96100
) -> std::result::Result<(), RegisterError> {
97-
unimplemented!("reset_vcpu")
101+
unimplemented!("restore_sregs")
98102
}
99103
}

src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ use crate::hypervisor::gdb::{
4040
};
4141
#[cfg(gdb)]
4242
use crate::hypervisor::gdb::{DebugError, DebugMemoryAccessError};
43-
use crate::hypervisor::regs::{
44-
CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
45-
};
43+
#[cfg(not(target_os = "windows"))]
44+
use crate::hypervisor::regs::CommonDebugRegs;
45+
use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters};
4646
#[cfg(not(gdb))]
4747
use crate::hypervisor::virtual_machine::VirtualMachine;
4848
#[cfg(kvm)]
@@ -335,23 +335,37 @@ impl HyperlightVm {
335335
}
336336

337337
/// Resets the following vCPU state:
338-
/// - General purpose registers
339-
/// - Debug registers
340-
/// - XSAVE (includes FPU/SSE state with proper FCW and MXCSR defaults)
341-
/// - Special registers (restored from snapshot, with CR3 updated to new page table location)
338+
/// - On Windows: calls WHvResetPartition (resets all per-VP state including
339+
/// GP registers, debug registers, XSAVE, MSRs, APIC, etc.)
340+
/// - On Linux: explicitly resets GP registers, debug registers, and XSAVE
341+
///
342+
/// This does NOT restore special registers (except on windows). Call `restore_sregs` separately
343+
/// after memory mappings are established.
342344
// TODO: check if other state needs to be reset
343-
pub(crate) fn reset_vcpu(
345+
pub(crate) fn reset_vm_state(&mut self) -> std::result::Result<(), RegisterError> {
346+
#[cfg(target_os = "windows")]
347+
self.vm.reset_partition()?;
348+
349+
#[cfg(not(target_os = "windows"))]
350+
{
351+
self.vm.set_regs(&CommonRegisters {
352+
rflags: 1 << 1, // Reserved bit always set
353+
..Default::default()
354+
})?;
355+
self.vm.set_debug_regs(&CommonDebugRegs::default())?;
356+
self.vm.reset_xsave()?;
357+
}
358+
359+
Ok(())
360+
}
361+
362+
/// Restores special registers from snapshot with CR3 updated to the
363+
/// new page table location.
364+
pub(crate) fn restore_sregs(
344365
&mut self,
345366
cr3: u64,
346367
sregs: &CommonSpecialRegisters,
347368
) -> std::result::Result<(), RegisterError> {
348-
self.vm.set_regs(&CommonRegisters {
349-
rflags: 1 << 1, // Reserved bit always set
350-
..Default::default()
351-
})?;
352-
self.vm.set_debug_regs(&CommonDebugRegs::default())?;
353-
self.vm.reset_xsave()?;
354-
355369
#[cfg(not(feature = "nanvix-unstable"))]
356370
{
357371
// Restore the full special registers from snapshot, but update CR3
@@ -885,7 +899,9 @@ mod tests {
885899
use super::*;
886900
#[cfg(kvm)]
887901
use crate::hypervisor::regs::FP_CONTROL_WORD_DEFAULT;
888-
use crate::hypervisor::regs::{CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT};
902+
use crate::hypervisor::regs::{
903+
CommonDebugRegs, CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT,
904+
};
889905
use crate::hypervisor::virtual_machine::VirtualMachine;
890906
use crate::mem::layout::SandboxMemoryLayout;
891907
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegionFlags};
@@ -1629,7 +1645,8 @@ mod tests {
16291645
assert_eq!(got_sregs, expected_sregs);
16301646

16311647
// Reset the vCPU
1632-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1648+
hyperlight_vm.reset_vm_state().unwrap();
1649+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
16331650

16341651
// Verify registers are reset to defaults
16351652
assert_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1758,7 +1775,8 @@ mod tests {
17581775
assert_eq!(regs, expected_dirty);
17591776

17601777
// Reset vcpu
1761-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1778+
hyperlight_vm.reset_vm_state().unwrap();
1779+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
17621780

17631781
// Check registers are reset to defaults
17641782
assert_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1882,7 +1900,8 @@ mod tests {
18821900
}
18831901

18841902
// Reset vcpu
1885-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1903+
hyperlight_vm.reset_vm_state().unwrap();
1904+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
18861905

18871906
// Check FPU is reset to defaults
18881907
assert_fpu_reset(hyperlight_vm.vm.as_ref());
@@ -1933,7 +1952,8 @@ mod tests {
19331952
assert_eq!(debug_regs, expected_dirty);
19341953

19351954
// Reset vcpu
1936-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
1955+
hyperlight_vm.reset_vm_state().unwrap();
1956+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
19371957

19381958
// Check debug registers are reset to default values
19391959
assert_debug_regs_reset(hyperlight_vm.vm.as_ref());
@@ -1982,9 +2002,10 @@ mod tests {
19822002
assert_eq!(sregs, expected_dirty);
19832003

19842004
// Reset vcpu
1985-
hyperlight_vm.reset_vcpu(0, &default_sregs()).unwrap();
2005+
hyperlight_vm.reset_vm_state().unwrap();
2006+
hyperlight_vm.restore_sregs(0, &default_sregs()).unwrap();
19862007

1987-
// Check registers are reset to defaults (CR3 is 0 as passed to reset_vcpu)
2008+
// Check registers are reset to defaults
19882009
let sregs = hyperlight_vm.vm.sregs().unwrap();
19892010
let mut expected_reset = CommonSpecialRegisters::standard_64bit_defaults(0);
19902011
normalize_sregs_for_run_tests(&mut expected_reset, &sregs);
@@ -2020,7 +2041,11 @@ mod tests {
20202041
let root_pt_addr = ctx.ctx.vm.get_root_pt().unwrap();
20212042
let segment_state = ctx.ctx.vm.get_snapshot_sregs().unwrap();
20222043

2023-
ctx.ctx.vm.reset_vcpu(root_pt_addr, &segment_state).unwrap();
2044+
ctx.ctx.vm.reset_vm_state().unwrap();
2045+
ctx.ctx
2046+
.vm
2047+
.restore_sregs(root_pt_addr, &segment_state)
2048+
.unwrap();
20242049

20252050
// Re-run from entrypoint (flag=1 means guest skips dirty phase, just does FXSAVE)
20262051
// Use stack_top - 8 to match initialise()'s behavior (simulates call pushing return addr)

src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ pub(crate) enum HypervisorType {
107107
/// Minimum XSAVE buffer size: 512 bytes legacy region + 64 bytes header.
108108
/// Only used by MSHV and WHP which use compacted XSAVE format and need to
109109
/// validate buffer size before accessing XCOMP_BV.
110-
#[cfg(any(mshv3, target_os = "windows"))]
110+
#[cfg(mshv3)]
111111
pub(crate) const XSAVE_MIN_SIZE: usize = 576;
112112

113113
/// Standard XSAVE buffer size (4KB) used by KVM and MSHV.
@@ -244,6 +244,9 @@ pub enum RegisterError {
244244
#[cfg(target_os = "windows")]
245245
#[error("Failed to convert WHP registers: {0}")]
246246
ConversionFailed(String),
247+
#[cfg(target_os = "windows")]
248+
#[error("Failed to reset partition: {0}")]
249+
ResetPartition(HypervisorError),
247250
}
248251

249252
/// Map memory error
@@ -341,18 +344,32 @@ pub(crate) trait VirtualMachine: Debug + Send {
341344
#[allow(dead_code)]
342345
fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError>;
343346
/// Set the debug registers of the vCPU
347+
#[allow(dead_code)] // Called on Linux (reset_vm_state) and via gdb, but dead on Windows release builds without gdb
344348
fn set_debug_regs(&self, drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError>;
345349

346350
/// Get xsave
347351
#[allow(dead_code)]
348352
fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError>;
349353
/// Reset xsave to default state
354+
#[cfg(any(kvm, mshv3))]
350355
fn reset_xsave(&self) -> std::result::Result<(), RegisterError>;
351356
/// Set xsave - only used for tests
352357
#[cfg(test)]
353358
#[cfg(not(feature = "nanvix-unstable"))]
354359
fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>;
355360

361+
/// Reset the partition using WHvResetPartition.
362+
///
363+
/// Resets all per-VP state to architectural defaults: GP registers, segment
364+
/// registers, control registers, EFER, MSRs, debug registers, full XSAVE
365+
/// state (x87/SSE/AVX/AVX-512/AMX), XCR0, APIC/LAPIC, pending interrupts,
366+
/// and VMCS/VMCB internals.
367+
///
368+
/// Does NOT reset: GPA memory mappings or contents, partition configuration,
369+
/// notification ports, or VPCI device state.
370+
#[cfg(target_os = "windows")]
371+
fn reset_partition(&mut self) -> std::result::Result<(), RegisterError>;
372+
356373
/// Get partition handle
357374
#[cfg(target_os = "windows")]
358375
fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;

src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs

Lines changed: 34 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,16 @@ use windows_result::HRESULT;
3232
use crate::hypervisor::gdb::{DebugError, DebuggableVm};
3333
use crate::hypervisor::regs::{
3434
Align16, CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
35-
FP_CONTROL_WORD_DEFAULT, MXCSR_DEFAULT, WHP_DEBUG_REGS_NAMES, WHP_DEBUG_REGS_NAMES_LEN,
36-
WHP_FPU_NAMES, WHP_FPU_NAMES_LEN, WHP_REGS_NAMES, WHP_REGS_NAMES_LEN, WHP_SREGS_NAMES,
37-
WHP_SREGS_NAMES_LEN,
35+
WHP_DEBUG_REGS_NAMES, WHP_DEBUG_REGS_NAMES_LEN, WHP_FPU_NAMES, WHP_FPU_NAMES_LEN,
36+
WHP_REGS_NAMES, WHP_REGS_NAMES_LEN, WHP_SREGS_NAMES, WHP_SREGS_NAMES_LEN,
3837
};
3938
use crate::hypervisor::surrogate_process::SurrogateProcess;
4039
use crate::hypervisor::surrogate_process_manager::get_surrogate_process_manager;
4140
#[cfg(feature = "hw-interrupts")]
4241
use crate::hypervisor::virtual_machine::x86_64::hw_interrupts::TimerThread;
4342
use crate::hypervisor::virtual_machine::{
4443
CreateVmError, HypervisorError, MapMemoryError, RegisterError, RunVcpuError, UnmapMemoryError,
45-
VirtualMachine, VmExit, XSAVE_MIN_SIZE,
44+
VirtualMachine, VmExit,
4645
};
4746
use crate::hypervisor::wrappers::HandleWrapper;
4847
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
@@ -607,6 +606,7 @@ impl VirtualMachine for WhpVm {
607606
})
608607
}
609608

609+
#[allow(dead_code)]
610610
fn set_debug_regs(&self, drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError> {
611611
let whp_regs: [(WHV_REGISTER_NAME, Align16<WHV_REGISTER_VALUE>); WHP_DEBUG_REGS_NAMES_LEN] =
612612
drs.into();
@@ -666,85 +666,6 @@ impl VirtualMachine for WhpVm {
666666
Ok(xsave_buffer)
667667
}
668668

669-
fn reset_xsave(&self) -> std::result::Result<(), RegisterError> {
670-
// WHP uses compacted XSAVE format (bit 63 of XCOMP_BV set).
671-
// We cannot just zero out the xsave area, we need to preserve the XCOMP_BV.
672-
673-
// Get the required buffer size by calling with NULL buffer.
674-
let mut buffer_size_needed: u32 = 0;
675-
676-
let result = unsafe {
677-
WHvGetVirtualProcessorXsaveState(
678-
self.partition,
679-
0,
680-
std::ptr::null_mut(),
681-
0,
682-
&mut buffer_size_needed,
683-
)
684-
};
685-
686-
// Expect insufficient buffer error; any other error is unexpected
687-
if let Err(e) = result
688-
&& e.code() != windows::Win32::Foundation::WHV_E_INSUFFICIENT_BUFFER
689-
{
690-
return Err(RegisterError::GetXsaveSize(e.into()));
691-
}
692-
693-
if buffer_size_needed < XSAVE_MIN_SIZE as u32 {
694-
return Err(RegisterError::XsaveSizeMismatch {
695-
expected: XSAVE_MIN_SIZE as u32,
696-
actual: buffer_size_needed,
697-
});
698-
}
699-
700-
// Create a buffer to hold the current state (to get the correct XCOMP_BV)
701-
let mut current_state = vec![0u8; buffer_size_needed as usize];
702-
let mut written_bytes = 0;
703-
unsafe {
704-
WHvGetVirtualProcessorXsaveState(
705-
self.partition,
706-
0,
707-
current_state.as_mut_ptr() as *mut std::ffi::c_void,
708-
buffer_size_needed,
709-
&mut written_bytes,
710-
)
711-
.map_err(|e| RegisterError::GetXsave(e.into()))?;
712-
};
713-
714-
// Zero out most of the buffer, preserving only XCOMP_BV (520-528).
715-
// Extended components with XSTATE_BV bit=0 will use their init values.
716-
//
717-
// - Legacy region (0-512): x87 FPU + SSE state
718-
// - XSTATE_BV (512-520): Feature bitmap
719-
// - XCOMP_BV (520-528): Compaction bitmap + format bit (KEEP)
720-
// - Reserved (528-576): Header padding
721-
// - Extended (576+): AVX, AVX-512, MPX, PKRU, AMX, etc.
722-
current_state[0..520].fill(0);
723-
current_state[528..].fill(0);
724-
725-
// XSAVE area layout from Intel SDM Vol. 1 Section 13.4.1:
726-
// - Bytes 0-1: FCW (x87 FPU Control Word)
727-
// - Bytes 24-27: MXCSR
728-
// - Bytes 512-519: XSTATE_BV (bitmap of valid state components)
729-
current_state[0..2].copy_from_slice(&FP_CONTROL_WORD_DEFAULT.to_le_bytes());
730-
current_state[24..28].copy_from_slice(&MXCSR_DEFAULT.to_le_bytes());
731-
// XSTATE_BV = 0x3: bits 0,1 = x87 + SSE valid. Explicitly tell hypervisor
732-
// to apply the legacy region from this buffer for consistent behavior.
733-
current_state[512..520].copy_from_slice(&0x3u64.to_le_bytes());
734-
735-
unsafe {
736-
WHvSetVirtualProcessorXsaveState(
737-
self.partition,
738-
0,
739-
current_state.as_ptr() as *const std::ffi::c_void,
740-
buffer_size_needed,
741-
)
742-
.map_err(|e| RegisterError::SetXsave(e.into()))?;
743-
}
744-
745-
Ok(())
746-
}
747-
748669
#[cfg(test)]
749670
#[cfg(not(feature = "nanvix-unstable"))]
750671
fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError> {
@@ -791,6 +712,36 @@ impl VirtualMachine for WhpVm {
791712
Ok(())
792713
}
793714

715+
fn reset_partition(&mut self) -> std::result::Result<(), RegisterError> {
716+
unsafe {
717+
WHvResetPartition(self.partition)
718+
.map_err(|e| RegisterError::ResetPartition(e.into()))?;
719+
720+
// WHvResetPartition resets LAPIC to power-on defaults.
721+
// Re-initialize it when LAPIC emulation is active.
722+
//
723+
// set_sregs filters out APIC_BASE (the snapshot sregs
724+
// default it to 0, which would disable the LAPIC). This
725+
// means APIC_BASE is never written after the VMCB rebuild,
726+
// so the AVIC clean bit stays set and the CPU may use stale
727+
// cached AVIC state. Re-writing the post-reset value forces
728+
// the hypervisor to dirty SVM_CLEAN_FIELD_AVIC.
729+
#[cfg(feature = "hw-interrupts")]
730+
{
731+
Self::init_lapic_bulk(self.partition)
732+
.map_err(|e| RegisterError::ResetPartition(e.into()))?;
733+
734+
let apic_base = self.sregs()?.apic_base;
735+
self.set_registers(&[(
736+
WHvX64RegisterApicBase,
737+
Align16(WHV_REGISTER_VALUE { Reg64: apic_base }),
738+
)])
739+
.map_err(|e| RegisterError::ResetPartition(e.into()))?;
740+
}
741+
}
742+
Ok(())
743+
}
744+
794745
/// Get the partition handle for this VM
795746
fn partition_handle(&self) -> WHV_PARTITION_HANDLE {
796747
self.partition

0 commit comments

Comments
 (0)