Commit e1adf51
authored
feat: add hardware interrupt support (#1272)
* feat: add PIC emulation, OutBAction variants, and guest halt mechanism
- Add PvTimerConfig (port 107) and Halt (port 108) to OutBAction enum
- Add userspace 8259A PIC emulation for MSHV/WHP (KVM uses in-kernel PIC)
- Add halt() function in guest exit module using Halt port instead of HLT
- Add default no-op IRQ handler at IDT vector 0x20 for PIC-remapped IRQ0
- Update dispatch epilogue to use Halt port before cli+hlt fallback
- Add hw-interrupts feature flag to hyperlight-host
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* feat(kvm): add hardware interrupt support with in-kernel IRQ chip
- Create IRQ chip (PIC + IOAPIC + LAPIC) and PIT before vCPU creation
- Add hw-interrupts run loop that handles HLT re-entry, Halt port, and
PvTimerConfig port (ignored since in-kernel PIT handles scheduling)
- Non-hw-interrupts path also recognizes Halt port for compatibility
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* feat(mshv): add hardware interrupt support with SynIC timer
- Enable LAPIC in partition flags for SynIC direct-mode timer delivery
- Configure LAPIC (SVR, TPR, LINT0/1, LVT Timer) during VM creation
- Install MSR intercept on IA32_APIC_BASE to prevent guest from
disabling the LAPIC globally
- Add SynIC STIMER0 configuration via PvTimerConfig IO port
- Add userspace PIC emulation integration for MSHV
- Restructure run_vcpu into a loop for HLT re-entry and hw IO handling
- Bridge PIC EOI to LAPIC EOI for SynIC timer interrupt acknowledgment
- Handle PIT/speaker/debug IO ports in userspace
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* feat(whp): add hardware interrupt support with software timer
Add WHP hardware interrupt support using a host-side software timer
thread that periodically injects interrupts via WHvRequestInterrupt.
Key changes:
- Detect LAPIC emulation support via WHvGetCapability
- Initialize LAPIC via bulk interrupt-controller state API
(WHvGet/SetVirtualProcessorInterruptControllerState2) since
individual APIC register writes fail with ACCESS_DENIED
- Software timer thread for periodic interrupt injection
- LAPIC EOI handling for PIC-only guest acknowledgment
- PIC emulation integration for MSHV/WHP shared 8259A
- Filter APIC_BASE from set_sregs when LAPIC emulation active
- HLT re-entry when timer is active
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* test: add hardware interrupt unit and integration tests
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* ci: add hw-interrupts test step to CI and Justfile
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix: add halt port IO write and restore hw_timer_interrupts test
Add Halt outb (port 108) before cli/hlt in guest init and dummyguest so
KVM's in-kernel LAPIC does not absorb the HLT exit. Also restore the
hw_timer_interrupts integration test that was inadvertently dropped.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* experiment: replace in-kernel PIT with irqfd + host timer thread
Replace create_pit2() with a host-side timer thread that fires an
EventFd registered via register_irqfd for GSI 0 (IRQ0). The in-kernel
IRQ chip (PIC + IOAPIC + LAPIC) is kept for proper interrupt routing.
When the guest writes to PvTimerConfig (port 107), the host parses
the requested period and spawns a timer thread that periodically
writes to the EventFd. Guest PIT port writes (0x40-0x43) exit to
userspace and are silently ignored.
Tested with nanvix hello-c.elf: works correctly.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* experiment: MSHV — replace SynIC timer with request_virtual_interrupt + host timer thread
Replace the SynIC-based timer (STIMER0_CONFIG/COUNT + SCONTROL) with a
host timer thread that periodically calls request_virtual_interrupt()
(HVCALL_ASSERT_VIRTUAL_INTERRUPT, hypercall 148).
This makes MSHV consistent with the KVM irqfd and WHP software timer
patterns — all three platforms now use: (1) start timer on PvTimerConfig,
(2) host thread periodically injects interrupt, (3) stop on Halt/Drop.
Changes:
- Remove SynIC dependency: make_default_synthetic_features_mask,
SCONTROL, STIMER0_CONFIG, STIMER0_COUNT imports and usage
- Set synthetic_proc_features to 0 (no SynIC features needed)
- Wrap vm_fd in Arc<VmFd> for thread-safe sharing
- Add timer_stop + timer_thread fields (same pattern as KVM)
- PvTimerConfig handler spawns timer thread with request_virtual_interrupt
- Add Drop impl for timer cleanup
- Remove build_stimer_config() and period_us_to_100ns() helpers
- Remove SynIC-related unit tests
Kept unchanged: LAPIC init, MSR intercept, PIC emulation, EOI bridging
(all still required for interrupt delivery).
NOT RUNTIME TESTED — MSHV not available on this system.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* experiment: eliminate PIC state machine — hardcode vector 0x20, no-op PIC ports
Remove the Pic struct from both MSHV and WHP. The nanvix guest always
remaps IRQ0 to vector 0x20 via PIC ICW2, so the host can hardcode the
timer vector instead of tracking the ICW initialization sequence.
PIC ports (0x20/0x21/0xA0/0xA1) are now accepted as no-ops. The only
PIC-related logic retained is LAPIC EOI bridging: when the guest sends
a non-specific EOI on port 0x20, the host clears the LAPIC ISR bit.
This is 3 lines of logic vs the ~130-line pic.rs state machine.
This addresses the PR 1272 reviewer concern about emulating "a good
fraction of the IC controller" — the PIC state machine was the
unnecessary complexity, not the timer delivery mechanism.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix: delete unused pic.rs file
The PIC state machine was eliminated in the previous experimental
commits (MSHV/WHP now hardcode vector 0x20 and no-op PIC ports).
Remove the orphaned pic.rs file and its mod declaration.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* style: rustfmt fixes in kvm.rs
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* refactor: address PR 1272 review feedback
- Remove _default_irq_handler (timer thread, not PIT, injects IRQs)
- Remove cli;hlt after outb port 108 in guest asm and exit.rs
- Revert to #[derive(Debug)] for all backends
- Revert feature_val to upstream unsafe { features.as_uint64[0] }
- Remove MSR intercept entirely from MSHV
- Extract shared hw_interrupts module (LAPIC helpers, IO port handling,
EOI bridging) to reduce duplication between MSHV and WHP
- Replace lapic_timer_active with timer_thread.is_some()
- Error on missing LAPIC emulation in WHP instead of fallback path
- Remove lapic_emulation field from WhpVm
- Remove Nanvix mentions from comments
- Add Intel SDM references to LAPIC register writes
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix: reset timer_stop flag before spawning timer thread
During evolve(), the guest init sequence halts via OutBAction::Halt,
which sets timer_stop=true. When the guest later configures a timer
via PvTimerConfig, the timer thread inherits the already-true stop
flag and exits immediately without ever firing.
Reset the flag to false right before spawning the timer thread in
both KVM and MSHV backends. WHP was not affected because it creates
a fresh AtomicBool each time.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* Address copilot review feedback
- KVM: data[..4] → data.get(..4) to avoid panic on short IO OUT
- KVM/MSHV: stop timer thread when period_us == 0
- WHP: check LAPIC state buffer >= 0x374 before init_lapic_registers
- hw_interrupts: add bounds checks + panic messages to write/read_lapic_u32
- init.rs/dispatch.rs: use xor eax,eax; out dx,eax; cli; hlt halt sequence
- simpleguest: guard IDT vector 0x20 against short IDT limit
- simpleguest: early return on period_us <= 0
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* Fix formatting (rustfmt nightly)
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* Fix clippy collapsible-if in KVM timer config
Signed-off-by: Daniil Baturin <daniil.baturin@microsoft.com>
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix: remove disallowed assert! macros from LAPIC helpers
The hyperlight-host clippy.toml disallows assert!/assert_eq!/assert_ne!
in non-test code. Revert to natural slice bounds checking which already
panics on out-of-bounds access.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* refactor: extract hw-interrupts and default vCPU run into separate methods
Split the two cfg-gated code paths in run_vcpu into
run_vcpu_hw_interrupts and run_vcpu_default helper methods on KvmVm,
keeping run_vcpu as a thin dispatcher. This makes each path easier to
read and reason about independently.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix: preserve RAX across halt sequence in init and dispatch
The host reads RAX after the init halt to obtain the dispatch_function
address (stored by generic_init's return value). Similarly, the host
reads RAX after dispatch to get the guest function return value.
The previous 'xor eax, eax' before 'out dx, eax' zeroed RAX, causing
the host to store NextAction::Call(0) and subsequent guest calls to
jump to address 0 (triggering Double Fault on MSHV/WHP).
Remove the xor; the port-108 halt signal only needs the port number,
not a specific data value.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* refactor: split OutBAction into OutBAction + VmAction, move hw_interrupts to x86_64/
- Split OutBAction enum: PvTimerConfig and Halt moved to new VmAction
enum. VmAction ports are intercepted at the hypervisor level in
run_vcpu and never reach the sandbox outb handler, so the split
eliminates unreachable match arms.
- Move hw_interrupts.rs to virtual_machine/x86_64/hw_interrupts.rs
since it contains x86-64 specific helpers (LAPIC, PIC, PIT).
- Remove halt() from hyperlight_guest::exit — all halt sites use
inline assembly with options(noreturn) to avoid stack epilogue issues.
- Extract handle_pv_timer_config() method in KVM backend.
- Remove intermediate variable in WhpVm::new().
- Restore create_vm_with_args() comment in MSHV backend.
- Add memory fence after IDT writes in simpleguest for CoW safety.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix: address PR 1272 review feedback (round 2)
- Add trace-level log for unexpected VcpuExit::Hlt in KVM hw-interrupts
loop (jsturtevant thread 1)
- Fix MSHV EINTR handling: always return Cancelled so InterruptHandle::kill()
is honoured even when the timer thread is active (jsturtevant thread 3)
- Add min/max bounds (100µs–10s) on guest-provided timer period across
all backends to prevent runaway injection or excessive sleep
(jsturtevant thread 4)
- Stop timer thread in WHP Halt handler before returning, matching
KVM/MSHV behaviour (jsturtevant thread 6)
- Use MSHV_PT_BIT_LAPIC named constant instead of magic 1u64 for
pt_flags (ludfjig nit)
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* refactor: extract TimerThread struct into shared hw_interrupts module
Replace per-backend Arc<AtomicBool> + Option<JoinHandle> + manual Drop
with a shared TimerThread struct in x86_64/hw_interrupts.rs. Each
backend now passes a closure for the inject call (eventfd.write on KVM,
request_virtual_interrupt on MSHV, WHvRequestInterrupt on WHP).
This removes ~60 lines of duplicated timer management logic and
ensures consistent start/stop/drop semantics across all backends.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* refactor: deduplicate WhpVm::new(), use #[cfg] for test exclusion
- WhpVm::new(): partition creation sequence now appears once with
#[cfg(feature = "hw-interrupts")] around just the LAPIC capability
check and LAPIC-specific setup, eliminating ~20 lines of duplication.
- Replace #[cfg_attr(feature = "hw-interrupts", ignore)] with
#[cfg(not(feature = "hw-interrupts"))] on tests that don't work
with hw-interrupts, so they are excluded at compile time rather
than at runtime.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* style: fix rustfmt in KVM trace macro
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* style: fix rustfmt import ordering for TimerThread
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix: revert test exclusion to runtime ignore for hw-interrupts compatibility
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* refactor: extract shared PvTimerConfig handler and fix timer reconfiguration
Extract handle_pv_timer_config() into hw_interrupts.rs as a shared
function used by KVM, MSHV, and WHP backends. This eliminates
duplicated timer-config parsing/clamping/start logic.
Fix MSHV timer reconfiguration: previously MSHV only started a timer
if self.timer.is_none(), ignoring reconfiguration requests. Now all
backends consistently stop any existing timer before starting a new
one when period_us > 0.
Fix WHP do_lapic_eoi to log errors from set_lapic_state instead of
silently discarding them.
Fix MSHV request_virtual_interrupt to log errors via tracing::warn
instead of silently discarding them with let _.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* refactor: extract hw-interrupts setup from constructors
KVM: extract irqfd/eventfd setup into KvmVm::setup_irqfd(), called
from new() via Self::setup_irqfd(&vm_fd).
MSHV: extract LAPIC initialization into MshvVm::init_lapic(), called
from new() via Self::init_lapic(&vcpu_fd). Move lapic_regs_as_u8 and
lapic_regs_as_u8_mut helper functions closer to the hw-interrupts impl
block for better locality.
WHP: extract check_lapic_emulation_support() and
enable_lapic_emulation() from inline blocks in new(). Move
LAPIC_STATE_MAX_SIZE and init_lapic_bulk into the hw-interrupts impl
block since they are only used with that feature.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix(kvm): return error on unexpected VcpuExit::Hlt in hw-interrupts loop
With hw-interrupts enabled, VcpuExit::Hlt should never reach
userspace because the in-kernel LAPIC handles HLT internally.
Previously this was silently ignored with a trace log and continue.
Now it returns RunVcpuError::UnexpectedExit to surface the problem.
Add UnexpectedExit(String) variant to RunVcpuError for cases where
the vCPU exits in a way that shouldn't happen under normal operation.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* refactor: use shared constant for halt port in guest assembly
Replace hardcoded port number 108 in dispatch_function and
pivot_stack inline assembly with a const operand referencing
hyperlight_common::outb::VmAction::Halt. This ensures the guest
assembly stays in sync with the host-side port definitions.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* style: rustfmt
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix(kvm): copy IO data before calling handle_pv_timer_config
The data slice borrows from the KVM run buffer (self.vcpu_fd), so
calling self.handle_pv_timer_config(data) would create a conflicting
mutable borrow. Copy data to a local Vec before calling the method.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* style: fix rustfmt import grouping in error.rs
Restore the combined `use {anyhow, serde_json};` import that was
split during rebase, matching the format expected by nightly rustfmt.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix(whp): use WHvX64LocalApicEmulationModeXApic constant
Replace hardcoded literal 1 with the named constant from the
windows crate for LAPIC emulation mode.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix: resolve clippy expect_used and rustfmt rebase artifacts
Replace .expect() with match + tracing::warn on eventfd clone failure
in KVM handle_pv_timer_config to satisfy clippy::expect_used lint.
Restore combined `use {anyhow, serde_json};` import in error.rs that
was split during rebase.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
* fix(kvm): remove UnexpectedExit, let catch-all handle unexpected HLT
Remove the dedicated VcpuExit::Hlt match arm and UnexpectedExit error
variant in the hw-interrupts run loop. Unexpected HLT now falls through
to the Ok(other) catch-all, returning VmExit::Unknown which gets
converted to the existing UnexpectedVmExit in hyperlight_vm.
Signed-off-by: danbugs <danilochiarlone@gmail.com>
---------
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Signed-off-by: Daniil Baturin <daniil.baturin@microsoft.com>1 parent 137e964 commit e1adf51
File tree
17 files changed
+1600
-189
lines changed- .github/workflows
- src
- hyperlight_common/src
- hyperlight_guest_bin/src/arch/amd64
- hyperlight_host
- src/hypervisor
- hyperlight_vm
- virtual_machine
- kvm
- mshv
- x86_64
- tests
- tests/rust_guests
- dummyguest/src
- simpleguest/src
17 files changed
+1600
-189
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
108 | 113 | | |
109 | 114 | | |
110 | 115 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
90 | 93 | | |
91 | 94 | | |
92 | 95 | | |
| |||
151 | 154 | | |
152 | 155 | | |
153 | 156 | | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
154 | 160 | | |
155 | 161 | | |
156 | 162 | | |
| |||
286 | 292 | | |
287 | 293 | | |
288 | 294 | | |
| 295 | + | |
289 | 296 | | |
290 | 297 | | |
291 | 298 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| 89 | + | |
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
| |||
106 | 107 | | |
107 | 108 | | |
108 | 109 | | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
109 | 126 | | |
110 | 127 | | |
111 | 128 | | |
| |||
124 | 141 | | |
125 | 142 | | |
126 | 143 | | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
71 | 74 | | |
72 | 75 | | |
73 | | - | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
173 | 173 | | |
174 | 174 | | |
175 | 175 | | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
176 | 179 | | |
177 | 180 | | |
178 | | - | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
133 | 133 | | |
134 | 134 | | |
135 | 135 | | |
| 136 | + | |
136 | 137 | | |
137 | 138 | | |
138 | 139 | | |
| |||
Lines changed: 6 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1550 | 1550 | | |
1551 | 1551 | | |
1552 | 1552 | | |
| 1553 | + | |
1553 | 1554 | | |
1554 | 1555 | | |
1555 | 1556 | | |
| |||
1695 | 1696 | | |
1696 | 1697 | | |
1697 | 1698 | | |
| 1699 | + | |
1698 | 1700 | | |
1699 | 1701 | | |
1700 | 1702 | | |
| |||
1754 | 1756 | | |
1755 | 1757 | | |
1756 | 1758 | | |
| 1759 | + | |
1757 | 1760 | | |
1758 | 1761 | | |
1759 | 1762 | | |
| |||
1885 | 1888 | | |
1886 | 1889 | | |
1887 | 1890 | | |
| 1891 | + | |
1888 | 1892 | | |
1889 | 1893 | | |
1890 | 1894 | | |
| |||
1927 | 1931 | | |
1928 | 1932 | | |
1929 | 1933 | | |
| 1934 | + | |
1930 | 1935 | | |
1931 | 1936 | | |
1932 | 1937 | | |
| |||
1980 | 1985 | | |
1981 | 1986 | | |
1982 | 1987 | | |
| 1988 | + | |
1983 | 1989 | | |
1984 | 1990 | | |
1985 | 1991 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
469 | 469 | | |
470 | 470 | | |
471 | 471 | | |
| 472 | + | |
472 | 473 | | |
473 | 474 | | |
474 | 475 | | |
| |||
0 commit comments