From b4aeed84ecedcf3a6ff15ac88cc57cfddd993d7d Mon Sep 17 00:00:00 2001 From: lstocchi Date: Mon, 11 May 2026 13:49:23 +0200 Subject: [PATCH] arch: implement x86_64 Windows/WHP backend Add the WHP-specific register, segment, and MSR configuration in src/arch/src/x86_64/windows/. Uses structured WHV_REGISTER_VALUE writes for FP registers and proper WHP register name mapping for MSRs. Includes the get_page_size helper for Windows. Signed-off-by: lstocchi --- Cargo.lock | 2 + src/arch/Cargo.toml | 8 + src/arch/src/x86_64/layout.rs | 6 + src/arch/src/x86_64/mod.rs | 23 ++- src/arch/src/x86_64/windows/msr.rs | 54 ++++++ src/arch/src/x86_64/windows/regs.rs | 262 ++++++++++++++++++++++++++++ 6 files changed, 353 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3847ccb..9298dbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -624,6 +624,7 @@ dependencies = [ "krun-arch-gen", "krun-smbios", "krun-utils", + "krun-whp", "kvm-bindings", "kvm-ioctls", "libc", @@ -631,6 +632,7 @@ dependencies = [ "tdx", "vm-memory", "vmm-sys-util 0.14.0", + "windows-sys", ] [[package]] diff --git a/src/arch/Cargo.toml b/src/arch/Cargo.toml index adb289c..764b402 100644 --- a/src/arch/Cargo.toml +++ b/src/arch/Cargo.toml @@ -31,5 +31,13 @@ tdx = { version = "0.1.0", optional = true } [target.'cfg(all(target_arch = "x86_64", target_os = "linux"))'.dependencies] linux-loader = { version = "0.13.2", features = ["elf"] } +[target.'cfg(target_os = "windows")'.dependencies] +whp = { package = "krun-whp", version = "=0.1.0-1.18.0", path = "../whp" } +windows-sys = { version = "0.61.2", features = [ + "Win32_Foundation", + "Win32_System_Hypervisor", + "Win32_System_SystemInformation", +] } + [dev-dependencies] utils = { package = "krun-utils", version = "=0.1.0-1.18.0", path = "../utils" } diff --git a/src/arch/src/x86_64/layout.rs b/src/arch/src/x86_64/layout.rs index fe3aad0..a52285d 100644 --- a/src/arch/src/x86_64/layout.rs +++ b/src/arch/src/x86_64/layout.rs @@ -69,6 +69,12 @@ pub const RESET_VECTOR: u64 = 0xfff0; pub const RESET_VECTOR: u64 = 0xffff_fff0; pub const RESET_VECTOR_SEV_AP: u64 = 0xfff3; +/// Address of the AP idle trampoline (`cli; hlt; jmp` loop) used during +/// SMP boot on WHP. APs park here in real mode until the BSP sends +/// INIT + SIPI through the emulated local APIC. +#[cfg(target_os = "windows")] +pub const AP_TRAMPOLINE_START: u64 = 0x1000; + /// The address to load the firmware, if present. pub const FIRMWARE_START: u64 = 0xffff_0000; diff --git a/src/arch/src/x86_64/mod.rs b/src/arch/src/x86_64/mod.rs index 24c3784..00df677 100644 --- a/src/arch/src/x86_64/mod.rs +++ b/src/arch/src/x86_64/mod.rs @@ -21,6 +21,10 @@ pub mod regs; pub mod linux; #[cfg(target_os = "windows")] pub mod windows; +#[cfg(target_os = "windows")] +use std::mem::MaybeUninit; +#[cfg(target_os = "windows")] +use windows_sys::Win32::System::SystemInformation::{GetSystemInfo, SYSTEM_INFO}; use crate::x86_64::layout::{EBDA_START, FIRST_ADDR_PAST_32BITS, MMIO_MEM_START}; #[cfg(feature = "tee")] @@ -68,6 +72,21 @@ pub enum Error { InitrdAddress, } +#[cfg(unix)] +fn get_page_size() -> usize { + unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() } +} +#[cfg(windows)] +fn get_page_size() -> usize { + let sysinfo = unsafe { + let mut info = MaybeUninit::::uninit(); + GetSystemInfo(info.as_mut_ptr()); + info.assume_init() + }; + + sysinfo.dwPageSize as usize +} + /// Returns a Vec of the valid memory addresses. /// These should be used to configure the GuestMemoryMmap structure for the platform. /// Make a hole for the kernel region that will be injected directly from libkrunfw's @@ -80,7 +99,7 @@ pub fn arch_memory_regions( initrd_size: u64, firmware_size: Option, ) -> (ArchMemoryInfo, Vec<(GuestAddress, usize)>) { - let page_size: usize = unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() }; + let page_size: usize = get_page_size(); let size = align_upwards!(size, page_size); @@ -196,7 +215,7 @@ pub fn arch_memory_regions( _initrd_size: u64, _firmware_size: Option, ) -> (ArchMemoryInfo, Vec<(GuestAddress, usize)>) { - let page_size: usize = unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() }; + let page_size: usize = get_page_size(); let size = align_upwards!(size, page_size); if let Some(kernel_load_addr) = kernel_load_addr diff --git a/src/arch/src/x86_64/windows/msr.rs b/src/arch/src/x86_64/windows/msr.rs index cd35112..63f59dc 100644 --- a/src/arch/src/x86_64/windows/msr.rs +++ b/src/arch/src/x86_64/windows/msr.rs @@ -1,2 +1,56 @@ // Copyright 2026 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 + +use crate::x86_64::msr::{MTRR_ENABLE, MTRR_MEM_TYPE_WB}; + +use super::super::msr::Error; +use windows_sys::Win32::System::Hypervisor::{ + WHV_REGISTER_NAME, WHvX64RegisterCstar, WHvX64RegisterLstar, WHvX64RegisterMsrMtrrDefType, + WHvX64RegisterSfmask, WHvX64RegisterStar, WHvX64RegisterSysenterCs, WHvX64RegisterSysenterEip, + WHvX64RegisterSysenterEsp, WHvX64RegisterTsc, +}; + +type Result = std::result::Result; + +const REGISTERS: [(WHV_REGISTER_NAME, u64); 9] = [ + (WHvX64RegisterSysenterCs, 0x0), + (WHvX64RegisterSysenterEsp, 0x0), + (WHvX64RegisterSysenterEip, 0x0), + // x86_64 specific MSRs + (WHvX64RegisterStar, 0x0), + (WHvX64RegisterCstar, 0x0), + (WHvX64RegisterSfmask, 0x0), + (WHvX64RegisterLstar, 0x0), + (WHvX64RegisterTsc, 0x0), + (WHvX64RegisterMsrMtrrDefType, MTRR_ENABLE | MTRR_MEM_TYPE_WB), +]; + +/// Configure MSRs via the WHP API. +pub fn setup_msrs(vcpu: &whp::WhpVcpu) -> Result<()> { + vcpu.set_registers64(REGISTERS).map_err(Error::SetMsrsWhp) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn setup_msrs_on_real_vcpu() { + if whp::check_hypervisor().is_err() { + eprintln!("WHP hypervisor not available, skipping"); + return; + } + let vm = std::sync::Arc::new(whp::WhpVm::new(1).expect("failed to create WHP partition")); + let vcpu = whp::WhpVcpu::new(vm, 0).expect("failed to create vCPU"); + setup_msrs(&vcpu).expect("setup_msrs failed"); + let actual = vcpu + .get_registers64(REGISTERS.map(|(name, _)| name)) + .expect("failed to read register back"); + + assert_eq!( + actual, + REGISTERS.map(|(_, value)| value), + "mismatch for msrs values" + ); + } +} diff --git a/src/arch/src/x86_64/windows/regs.rs b/src/arch/src/x86_64/windows/regs.rs index cd35112..19ae8a8 100644 --- a/src/arch/src/x86_64/windows/regs.rs +++ b/src/arch/src/x86_64/windows/regs.rs @@ -1,2 +1,264 @@ // Copyright 2026 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 + +use vm_memory::GuestMemoryMmap; + +use super::super::gdt::SegmentDescriptor; +use super::super::layout::{ + AP_TRAMPOLINE_START, BOOT_STACK_POINTER, RESET_VECTOR_SEV_AP, ZERO_PAGE_START, +}; +use super::super::regs::{ + EFER_LMA, EFER_LME, Error, X86_CR0_PE, compute_page_tables, compute_segments, +}; +use windows_sys::Win32::System::Hypervisor::*; + +type Result = std::result::Result; + +/// Configure base registers for a given CPU. +/// +/// # Arguments +/// +/// * `vcpu` - Structure for the VCPU that holds the WHP VCPU. +/// * `boot_ip` - Starting instruction pointer. +pub fn setup_regs(vcpu: &whp::WhpVcpu, boot_ip: u64) -> Result<()> { + if vcpu.index() == 0 { + vcpu.set_registers64([ + (WHvX64RegisterRflags, 0x0000_0000_0000_0002u64), + (WHvX64RegisterRip, boot_ip), + (WHvX64RegisterRsp, BOOT_STACK_POINTER), + (WHvX64RegisterRbp, BOOT_STACK_POINTER), + (WHvX64RegisterRsi, ZERO_PAGE_START), + ]) + .map_err(Error::SetWhpRegisters) + } else { + let rip = if cfg!(feature = "tee") { + RESET_VECTOR_SEV_AP + } else { + AP_TRAMPOLINE_START + }; + + vcpu.set_registers64([ + (WHvX64RegisterRflags, 0x0000_0000_0000_0002u64), + (WHvX64RegisterRip, rip), + ]) + .map_err(Error::SetWhpRegisters) + } +} + +/// Configures the segment registers and system page tables for a given CPU. +/// +/// # Arguments +/// +/// * `mem` - The memory that will be passed to the guest. +/// * `vcpu` - Structure for the VCPU that holds the WHP VCPU. +pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &whp::WhpVcpu) -> Result<()> { + if vcpu.index() != 0 { + if cfg!(feature = "tee") { + return Ok(()); + } + return setup_ap_segments(vcpu); + } + + let segs = compute_segments(mem, false)?; + let pt = compute_page_tables(mem)?; + + let to_segment = |seg: &SegmentDescriptor| -> WHV_REGISTER_VALUE { + let mut v: WHV_REGISTER_VALUE = unsafe { std::mem::zeroed() }; + let s = unsafe { &mut v.Segment }; + s.Base = seg.base; + s.Limit = seg.limit; + s.Selector = seg.selector; + s.Anonymous.Anonymous._bitfield = (seg.type_ as u16) + | ((seg.s as u16) << 4) + | ((seg.dpl as u16) << 5) + | ((seg.present as u16) << 7) + | ((seg.avl as u16) << 12) + | ((seg.l as u16) << 13) + | ((seg.db as u16) << 14) + | ((seg.g as u16) << 15); + v + }; + + let to_table = |base: u64, limit: u16| -> WHV_REGISTER_VALUE { + let mut v: WHV_REGISTER_VALUE = unsafe { std::mem::zeroed() }; + let t = unsafe { &mut v.Table }; + t.Base = base; + t.Limit = limit; + v + }; + + let to_reg64 = |val: u64| -> WHV_REGISTER_VALUE { + let mut v: WHV_REGISTER_VALUE = unsafe { std::mem::zeroed() }; + v.Reg64 = val; + v + }; + + let [cr0, cr4, efer] = vcpu + .get_registers64([WHvX64RegisterCr0, WHvX64RegisterCr4, WHvX64RegisterEfer]) + .map_err(Error::GetWhpRegisters)?; + vcpu.set_registers([ + (WHvX64RegisterCs, to_segment(&segs.code_seg)), + (WHvX64RegisterDs, to_segment(&segs.data_seg)), + (WHvX64RegisterEs, to_segment(&segs.data_seg)), + (WHvX64RegisterFs, to_segment(&segs.data_seg)), + (WHvX64RegisterGs, to_segment(&segs.data_seg)), + (WHvX64RegisterSs, to_segment(&segs.data_seg)), + (WHvX64RegisterTr, to_segment(&segs.tss_seg)), + (WHvX64RegisterGdtr, to_table(segs.gdt_base, segs.gdt_limit)), + (WHvX64RegisterIdtr, to_table(segs.idt_base, segs.idt_limit)), + (WHvX64RegisterCr0, to_reg64(cr0 | X86_CR0_PE | pt.cr0_bits)), + (WHvX64RegisterCr3, to_reg64(pt.cr3)), + (WHvX64RegisterCr4, to_reg64(cr4 | pt.cr4_bits)), + (WHvX64RegisterEfer, to_reg64(efer | EFER_LME | EFER_LMA)), + ]) + .map_err(Error::SetWhpRegisters) +} + +/// Reset CS.base to 0 for an AP vCPU so that RIP addresses land in low +/// memory rather than at the default reset CS.base of 0xFFFF_0000. +/// All other segment registers and control registers stay at their +/// power-on defaults (real mode). +fn setup_ap_segments(vcpu: &whp::WhpVcpu) -> Result<()> { + let [mut value] = vcpu + .get_registers([WHvX64RegisterCs]) + .map_err(Error::GetWhpRegisters)?; + value.Segment.Base = 0; + value.Segment.Selector = 0; + vcpu.set_registers([(WHvX64RegisterCs, value)]) + .map_err(Error::SetWhpRegisters) +} + +#[cfg(test)] +mod tests { + use super::super::super::regs::{BOOT_GDT_OFFSET, BOOT_IDT_OFFSET, X86_CR0_PG, X86_CR4_PAE}; + use super::*; + use std::ffi::c_void; + use std::sync::Arc; + use vm_memory::{Bytes, GuestAddress, GuestMemory, GuestMemoryMmap}; + use whp::{WhpVcpu, WhpVm}; + + const GUEST_MEM_SIZE: usize = 0x10000; + + fn create_vm_and_vcpu(vcpu_count: u32) -> (Arc, WhpVcpu) { + let vm = Arc::new(WhpVm::new(vcpu_count).unwrap()); + let vcpu = WhpVcpu::new(vm.clone(), 0).unwrap(); + (vm, vcpu) + } + + fn create_vm_with_memory() -> (Arc, WhpVcpu, GuestMemoryMmap) { + let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), GUEST_MEM_SIZE)]).unwrap(); + let vm = Arc::new(WhpVm::new(1).unwrap()); + + let host_addr = gm.get_host_address(GuestAddress(0)).unwrap(); + unsafe { + vm.map_memory(host_addr as *mut c_void, 0, GUEST_MEM_SIZE as u64) + .unwrap(); + } + + let vcpu = WhpVcpu::new(vm.clone(), 0).unwrap(); + (vm, vcpu, gm) + } + + #[test] + fn test_setup_regs_bsp() { + let (_vm, vcpu) = create_vm_and_vcpu(1); + let boot_ip: u64 = 0x1_0000; + setup_regs(&vcpu, boot_ip).unwrap(); + + let [rflags, rip, rsp, rbp, rsi] = vcpu + .get_registers64([ + WHvX64RegisterRflags, + WHvX64RegisterRip, + WHvX64RegisterRsp, + WHvX64RegisterRbp, + WHvX64RegisterRsi, + ]) + .unwrap(); + + assert_eq!(rflags & 0x2, 0x2); + assert_eq!(rip, boot_ip); + assert_eq!(rsp, BOOT_STACK_POINTER); + assert_eq!(rbp, BOOT_STACK_POINTER); + assert_eq!(rsi, ZERO_PAGE_START); + } + + #[test] + fn test_setup_regs_ap() { + let vm = Arc::new(WhpVm::new(2).unwrap()); + let _bsp = WhpVcpu::new(vm.clone(), 0).unwrap(); + let ap = WhpVcpu::new(vm.clone(), 1).unwrap(); + + setup_regs(&ap, 0).unwrap(); + + let [rflags, rip] = ap + .get_registers64([WHvX64RegisterRflags, WHvX64RegisterRip]) + .unwrap(); + + assert_eq!(rflags & 0x2, 0x2); + let expected_rip = if cfg!(feature = "tee") { + RESET_VECTOR_SEV_AP + } else { + AP_TRAMPOLINE_START + }; + assert_eq!(rip, expected_rip); + } + + #[test] + fn test_setup_sregs() { + let (_vm, vcpu, gm) = create_vm_with_memory(); + setup_sregs(&gm, &vcpu).unwrap(); + + let read_u64 = |offset: u64| -> u64 { gm.read_obj(GuestAddress(offset)).unwrap() }; + + // Verify GDT was written to guest memory + assert_eq!(0x0, read_u64(BOOT_GDT_OFFSET)); + assert_eq!(0xaf_9b00_0000_ffff, read_u64(BOOT_GDT_OFFSET + 8)); + assert_eq!(0xcf_9300_0000_ffff, read_u64(BOOT_GDT_OFFSET + 16)); + assert_eq!(0x8f_8b00_0000_ffff, read_u64(BOOT_GDT_OFFSET + 24)); + assert_eq!(0x0, read_u64(BOOT_IDT_OFFSET)); + + // Verify control registers + let [cr0, cr3, cr4, efer] = vcpu + .get_registers64([ + WHvX64RegisterCr0, + WHvX64RegisterCr3, + WHvX64RegisterCr4, + WHvX64RegisterEfer, + ]) + .unwrap(); + + assert!(cr0 & X86_CR0_PE != 0, "Protected mode not enabled"); + assert!(cr0 & X86_CR0_PG != 0, "Paging not enabled"); + assert_eq!(cr3, 0x9000, "CR3 should point to PML4"); + assert!(cr4 & X86_CR4_PAE != 0, "PAE not enabled"); + assert!(efer & EFER_LME != 0, "Long mode not enabled"); + assert!(efer & EFER_LMA != 0, "Long mode not active"); + + // Verify CS segment via structured register read + let [cs_val] = vcpu.get_registers([WHvX64RegisterCs]).unwrap(); + let cs = unsafe { cs_val.Segment }; + assert_eq!(cs.Base, 0); + assert_eq!(cs.Selector, 0x08); + + // Verify page tables in guest memory + assert_eq!(0xa003, read_u64(0x9000)); // PML4 -> PDPTE + assert_eq!(0xb003, read_u64(0xa000)); // PDPTE -> PDE + for i in 0..512u64 { + assert_eq!((i << 21) + 0x83, read_u64(0xb000 + (i * 8))); + } + } + + #[test] + fn test_setup_ap_segments() { + let vm = Arc::new(WhpVm::new(2).unwrap()); + let _bsp = WhpVcpu::new(vm.clone(), 0).unwrap(); + let ap = WhpVcpu::new(vm.clone(), 1).unwrap(); + + setup_ap_segments(&ap).unwrap(); + + let [cs_val] = ap.get_registers([WHvX64RegisterCs]).unwrap(); + let cs = unsafe { cs_val.Segment }; + assert_eq!(cs.Base, 0); + assert_eq!(cs.Selector, 0); + } +} -- 2.51.2