Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ValidatePullRequest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ jobs:
arch: arm64
- cpu_vendor: apple
arch: X64
include:
- hypervisor: hyperv-ws2025
cpu_vendor: arm
arch: arm64
config: debug
- hypervisor: hyperv-ws2025
cpu_vendor: arm
arch: arm64
config: release
uses: ./.github/workflows/dep_build_test.yml
secrets: inherit
with:
Expand Down
37 changes: 30 additions & 7 deletions .github/workflows/dep_build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ jobs:
github.run_id,
github.run_number,
github.run_attempt)
|| inputs.hypervisor == 'hyperv-ws2025'
&& format('["self-hosted", "Windows", "ARM64", "1ES.Pool=hl-windows-arm", "JobId=build-and-test-{0}-{1}-{2}-{3}"]',
inputs.config,
github.run_id,
github.run_number,
github.run_attempt)
|| format('["self-hosted", "{0}", "arm64", "{1}"]',
inputs.hypervisor == 'hvf' && 'macos' || 'Linux',
inputs.hypervisor)) }}
Expand All @@ -79,6 +85,21 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Configure ARM64 libclang
if: runner.os == 'Windows' && inputs.arch == 'arm64'
shell: pwsh
run: |
if (-not $env:VSINSTALLDIR) {
throw "Visual Studio developer environment did not set VSINSTALLDIR"
}
$libclang = Join-Path $env:VSINSTALLDIR "VC\Tools\Llvm\ARM64\bin\libclang.dll"
if (-not (Test-Path $libclang)) {
throw "ARM64 libclang not found at '$libclang'"
}
$llvmPath = Split-Path $libclang
"LIBCLANG_PATH=$llvmPath" >> $env:GITHUB_ENV
$llvmPath >> $env:GITHUB_PATH

- name: Fix cargo home permissions
if: runner.os == 'Linux'
run: |
Expand Down Expand Up @@ -140,7 +161,7 @@ jobs:
just test ${{ inputs.config }} ${{ inputs.hypervisor == 'mshv3' && 'mshv3' || 'kvm' }}

- name: Run no-surrogate VM tests (WHP only)
if: runner.os == 'Windows'
if: runner.os == 'Windows' && inputs.arch == 'X64'
env:
HYPERLIGHT_MAX_SURROGATES: "0"
HYPERLIGHT_INITIAL_SURROGATES: "0"
Expand Down Expand Up @@ -173,35 +194,37 @@ jobs:
assert_ran "$OUT" snapshot_evolve_restore_handles_state_correctly restore_from_loaded_snapshot

- name: Run Rust tests with hw-interrupts
if: runner.arch == 'X64'
if: inputs.arch == 'X64'
run: |
# with hw-interrupts feature enabled (+ explicit driver on Linux)
just test ${{ inputs.config }} ${{ runner.os == 'Linux' && (inputs.hypervisor == 'mshv3' && 'mshv3,hw-interrupts' || 'kvm,hw-interrupts') || 'hw-interrupts' }}

- name: Run Rust Gdb tests
if: runner.arch == 'X64'
if: inputs.arch == 'X64'
env:
RUST_LOG: debug
run: just test-rust-gdb-debugging ${{ inputs.config }}

- name: Run Rust Crashdump tests
if: runner.arch == 'X64'
if: inputs.arch == 'X64'
env:
RUST_LOG: debug
run: just test-rust-crashdump ${{ inputs.config }}

- name: Run Rust Tracing tests
if: runner.arch == 'X64' && runner.os == 'Linux'
if: inputs.arch == 'X64' && runner.os == 'Linux'
env:
RUST_LOG: debug
run: just test-rust-tracing ${{ inputs.config }}

- name: Install oras
if: runner.os != 'Windows' || inputs.arch != 'arm64'
uses: oras-project/setup-oras@1d808f7d7f6995cc68b7bf507bfe5c5446e1dc9d # v2.0.1
with:
version: 1.3.1

- name: Log in to GHCR
if: runner.os != 'Windows' || inputs.arch != 'arm64'
env:
GHCR_USER: ${{ github.actor }}
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -210,13 +233,13 @@ jobs:
echo "${GHCR_TOKEN}" | oras login ghcr.io -u "${GHCR_USER}" --password-stdin

- name: Snapshot goldens (pull and verify)
if: ${{ inputs.should_regen_goldens != 'true' }}
if: ${{ inputs.should_regen_goldens != 'true' && (runner.os != 'Windows' || inputs.arch != 'arm64') }}
run: |
just snapshot-goldens-pull ${{ inputs.config }}
just snapshot-goldens-verify ${{ inputs.config }}

- name: Snapshot goldens (regenerate and verify)
if: ${{ inputs.should_regen_goldens == 'true' }}
if: ${{ inputs.should_regen_goldens == 'true' && (runner.os != 'Windows' || inputs.arch != 'arm64') }}
run: |
# regenerate goldens from current branch
just snapshot-goldens-regenerate ${{ inputs.config }}
Expand Down
5 changes: 5 additions & 0 deletions src/hyperlight_common/src/arch/aarch64/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ pub const SCRATCH_TOP_GPA: usize = 0x0000_000f_ffff_bfff;
pub const IO_PAGE_GVA: u64 = 0x0000_ffff_ffff_e000;
pub const IO_PAGE_GPA: u64 = 0x0000_000f_ffff_f000;

// WHP reserves these guest physical addresses for its emulated GIC.
pub const WHP_GITS_TRANSLATOR_BASE_GPA: u64 = 0xeff6_8000;
pub const WHP_GICR_BASE_GPA: u64 = 0xeffe_e000;
pub const WHP_GICD_BASE_GPA: u64 = 0xffff_0000;

pub const fn io_page() -> Option<(crate::vmem::PhysAddr, crate::vmem::VirtAddr)> {
Some((IO_PAGE_GPA, IO_PAGE_GVA))
}
Expand Down
2 changes: 2 additions & 0 deletions src/hyperlight_common/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ mod arch;
pub use arch::{
SCRATCH_TOP_GPA, SCRATCH_TOP_GVA, SNAPSHOT_PT_GVA_MAX, SNAPSHOT_PT_GVA_MIN, io_page,
};
#[cfg(target_arch = "aarch64")]
pub use arch::{WHP_GICD_BASE_GPA, WHP_GICR_BASE_GPA, WHP_GITS_TRANSLATOR_BASE_GPA};

use crate::virtq;

Expand Down
2 changes: 1 addition & 1 deletion src/hyperlight_host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ tracing-core = "0.1.36"
tracing-opentelemetry = { version = "0.33.0", optional = true }
hyperlight-common = { workspace = true, default-features = true, features = [ "std" ] }
hyperlight-guest-tracing = { workspace = true, default-features = true, optional = true }
vmm-sys-util = "0.15.0"
crossbeam-channel = "0.5.15"
thiserror = "2.0.18"
chrono = { version = "0.4", optional = true }
Expand Down Expand Up @@ -90,6 +89,7 @@ kvm-bindings = { version = "0.14", features = ["fam-wrappers"], optional = true
kvm-ioctls = { version = "0.25", optional = true }
mshv-bindings = { version = "0.7", optional = true }
mshv-ioctls = { version = "0.7", optional = true}
vmm-sys-util = "0.15.0"

[dev-dependencies]
uuid = { version = "1.23.3", features = ["v4"] }
Expand Down
15 changes: 12 additions & 3 deletions src/hyperlight_host/src/hypervisor/hyperlight_vm/aarch64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ use super::{
#[cfg(hvf)]
use crate::hypervisor::HvfInterruptHandle;
use crate::hypervisor::InterruptHandleImpl;
#[cfg(any(kvm, mshv3))]
#[cfg(target_os = "linux")]
use crate::hypervisor::LinuxInterruptHandle;
#[cfg(target_os = "windows")]
use crate::hypervisor::WindowsInterruptHandle;
#[cfg(gdb)]
use crate::hypervisor::gdb::{DebugCommChannel, DebugMsg, DebugResponse};
use crate::hypervisor::hyperlight_vm::get_guest_log_filter;
Expand All @@ -22,6 +24,8 @@ use crate::hypervisor::regs::{CommonFpu, CommonRegisters, CommonSpecialRegisters
use crate::hypervisor::virtual_machine::hvf::HvfVm;
#[cfg(kvm)]
use crate::hypervisor::virtual_machine::kvm::KvmVm;
#[cfg(target_os = "windows")]
use crate::hypervisor::virtual_machine::whp::WhpVm;
use crate::hypervisor::virtual_machine::{
HypervisorType, RegisterError, ResetVcpuError, VirtualMachine, VmError,
get_available_hypervisor,
Expand All @@ -46,7 +50,7 @@ impl HyperlightVm {
next_action: NextAction,
rsp_gva: u64,
page_size: usize,
config: &SandboxConfiguration,
#[cfg_attr(target_os = "windows", allow(unused_variables))] config: &SandboxConfiguration,
#[cfg(gdb)] _gdb_conn: Option<DebugCommChannel<DebugResponse, DebugMsg>>,
#[cfg(crashdump)] _rt_cfg: SandboxRuntimeConfig,
#[cfg(feature = "mem_profile")] _trace_info: MemTraceInfo,
Expand All @@ -59,13 +63,14 @@ impl HyperlightVm {
let mut 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),
#[cfg(hvf)]
Some(HypervisorType::Hvf) => {
Box::new(HvfVm::new(interrupt_handle.clone()).map_err(VmError::CreateVm)?)
}
#[cfg(target_os = "windows")]
Some(HypervisorType::Whp) => Box::new(WhpVm::new().map_err(VmError::CreateVm)?),
None => return Err(CreateHyperlightVmError::NoHypervisorFound),
};
vm.set_sregs(&CommonSpecialRegisters::defaults(root_pt_addr))
Expand All @@ -74,6 +79,10 @@ impl HyperlightVm {
let interrupt_handle: Arc<dyn InterruptHandleImpl> =
Arc::new(LinuxInterruptHandle::new(config));

#[cfg(target_os = "windows")]
let interrupt_handle: Arc<dyn InterruptHandleImpl> =
Arc::new(WindowsInterruptHandle::new(vm.partition_handle()));

let snapshot_slot = 0u32;
let scratch_slot = 1u32;
let vm_can_reset_vcpu = vm.can_reset_vcpu();
Expand Down
4 changes: 2 additions & 2 deletions src/hyperlight_host/src/hypervisor/regs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ pub(crate) use x86_64::*;

#[cfg(target_arch = "aarch64")]
mod aarch64;
#[cfg(target_os = "windows")]
#[cfg(all(target_arch = "x86_64", target_os = "windows"))]
use std::collections::HashSet;

#[cfg(target_arch = "aarch64")]
pub(crate) use aarch64::*;

#[cfg(target_os = "windows")]
#[cfg(all(target_arch = "x86_64", target_os = "windows"))]
#[derive(Debug, PartialEq)]
pub(crate) enum FromWhpRegisterError {
MissingRegister(HashSet<i32>),
Expand Down
3 changes: 3 additions & 0 deletions src/hyperlight_host/src/hypervisor/regs/aarch64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ pub(crate) struct CommonDebugRegs {

#[cfg(kvm)]
pub(crate) mod kvm_reg;

#[cfg(target_os = "windows")]
pub(crate) mod whp_reg;
49 changes: 49 additions & 0 deletions src/hyperlight_host/src/hypervisor/regs/aarch64/whp_reg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 The Hyperlight Authors.

use windows::Win32::System::Hypervisor::{WHV_REGISTER_NAME, WHV_REGISTER_VALUE};

#[repr(C, align(16))]
#[derive(Clone, Copy, Default)]
pub(crate) struct Align16<T>(pub(crate) T);

const _: () = {
assert!(core::mem::size_of::<WHV_REGISTER_VALUE>() == 16);
assert!(core::mem::size_of::<Align16<WHV_REGISTER_VALUE>>() == 16);
assert!(core::mem::align_of::<Align16<WHV_REGISTER_VALUE>>() == 16);
};

pub(crate) const WHV_ARM64_REGISTER_X0: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00020000);
pub(crate) const WHV_ARM64_REGISTER_FP: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x0002001D);
pub(crate) const WHV_ARM64_REGISTER_LR: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x0002001E);
pub(crate) const WHV_ARM64_REGISTER_SP_EL0: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00020020);
pub(crate) const WHV_ARM64_REGISTER_SP_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00020021);
pub(crate) const WHV_ARM64_REGISTER_PC: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00020022);
pub(crate) const WHV_ARM64_REGISTER_PSTATE: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00020023);

pub(crate) const WHV_ARM64_REGISTER_Q0: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00030000);

pub(crate) const WHV_ARM64_REGISTER_SCTLR_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00040002);
pub(crate) const WHV_ARM64_REGISTER_CPACR_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00040004);
pub(crate) const WHV_ARM64_REGISTER_TTBR0_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00040005);
pub(crate) const WHV_ARM64_REGISTER_TCR_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00040007);
pub(crate) const WHV_ARM64_REGISTER_MAIR_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x0004000B);
pub(crate) const WHV_ARM64_REGISTER_VBAR_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x0004000C);
pub(crate) const WHV_ARM64_REGISTER_FPCR: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00040012);
pub(crate) const WHV_ARM64_REGISTER_FPSR: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00040013);
pub(crate) const WHV_ARM64_REGISTER_GICR_BASE_GPA: WHV_REGISTER_NAME =
WHV_REGISTER_NAME(0x00063000);

pub(crate) fn xreg(index: u32) -> WHV_REGISTER_NAME {
match index {
0..=28 => WHV_REGISTER_NAME(WHV_ARM64_REGISTER_X0.0 + index as i32),
29 => WHV_ARM64_REGISTER_FP,
30 => WHV_ARM64_REGISTER_LR,
_ => panic!("Invalid ARM64 GP register index: {index}"),
}
}

pub(crate) fn qreg(index: u32) -> WHV_REGISTER_NAME {
debug_assert!(index < 32, "Invalid ARM64 SIMD register index: {index}");
WHV_REGISTER_NAME(WHV_ARM64_REGISTER_Q0.0 + index as i32)
}
Loading
Loading