From 085a65349deab46fd0a16baedff3fbb9b5403e02 Mon Sep 17 00:00:00 2001 From: lstocchi Date: Mon, 18 May 2026 12:38:58 +0200 Subject: [PATCH] devices: add WHP IOAPIC backend for Windows x86_64 WHP emulates the LAPIC but not the IOAPIC. Add a WhpIoapicBackend that plugs into the common IOAPIC register emulation and delivers interrupts through WHvRequestInterrupt. The backend translates IOAPIC redirection-table entries into WHP InterruptRequest structs, handling physical/logical destination modes, edge/level trigger modes, and delivery modes (Fixed, LowestPriority, NMI, INIT, SIPI). Level-triggered interrupts track Remote-IRR as the KVM backend does. Assisted-by: Cursor:claude-opus-4.6 Signed-off-by: lstocchi --- src/devices/src/legacy/ioapic.rs | 28 +++++- src/devices/src/legacy/ioapic_kvm.rs | 4 + src/devices/src/legacy/ioapic_whp.rs | 136 +++++++++++++++++++++++++++ src/devices/src/legacy/mod.rs | 4 + src/whp/src/lib.rs | 3 + 5 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 src/devices/src/legacy/ioapic_whp.rs diff --git a/src/devices/src/legacy/ioapic.rs b/src/devices/src/legacy/ioapic.rs index 1d906b5..113db46 100644 --- a/src/devices/src/legacy/ioapic.rs +++ b/src/devices/src/legacy/ioapic.rs @@ -79,6 +79,10 @@ pub trait IoApicBackend: Send + 'static { /// Called after the guest updates a redirection table entry. fn on_entry_changed(&mut self, regs: &mut IoApicRegs, index: usize); + /// Called when a guest EOI clears Remote-IRR for `index`. + /// Backends should re-deliver if the pin is still asserted (IRR set). + fn on_eoi(&mut self, regs: &mut IoApicRegs); + /// Called from `IrqChipT::set_irq` to assert an interrupt line. fn set_irq( &mut self, @@ -249,7 +253,29 @@ impl BusDevice for Ioapic { } } } - IO_EOI => todo!(), + IO_EOI => { + #[cfg(target_os = "windows")] + { + let vector = (val as u64 & IOAPIC_VECTOR_MASK) as u8; + let mut cleared = false; + for i in 0..IOAPIC_NUM_PINS { + let entry = regs.ioredtbl[i]; + if (entry & IOAPIC_VECTOR_MASK) as u8 == vector + && entry & IOAPIC_LVT_REMOTE_IRR != 0 + { + regs.ioredtbl[i] &= !IOAPIC_LVT_REMOTE_IRR; + cleared = true; + } + } + if cleared { + backend.on_eoi(regs); + } + } + #[cfg(not(target_os = "windows"))] + { + todo!() + } + } _ => unreachable!(), } } diff --git a/src/devices/src/legacy/ioapic_kvm.rs b/src/devices/src/legacy/ioapic_kvm.rs index e05cbfd..1c935d7 100644 --- a/src/devices/src/legacy/ioapic_kvm.rs +++ b/src/devices/src/legacy/ioapic_kvm.rs @@ -249,6 +249,10 @@ impl IoApicBackend for IoApicKvmBackend { self.service(regs); } + fn on_eoi(&mut self, _regs: &mut IoApicRegs) { + // TODO: implement + } + fn set_irq( &mut self, _irq_line: Option, diff --git a/src/devices/src/legacy/ioapic_whp.rs b/src/devices/src/legacy/ioapic_whp.rs new file mode 100644 index 0000000..c206675 --- /dev/null +++ b/src/devices/src/legacy/ioapic_whp.rs @@ -0,0 +1,136 @@ +// Copyright 2026 Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! WHP IOAPIC backend. +//! +//! WHP emulates the LAPIC but NOT the IOAPIC. This backend provides +//! interrupt injection through `WHvRequestInterrupt`, plugging into the +//! common IOAPIC register emulation in [`super::ioapic`]. + +use std::io; +use std::sync::Arc; + +use whp::{InterruptDestinationMode, InterruptRequest, InterruptTriggerMode, InterruptType, WhpVm}; + +use crate::Error as DeviceError; +use utils::eventfd::EventFd; + +use super::ioapic::{ + IOAPIC_DM_EXTINT, IOAPIC_DM_MASK, IOAPIC_LVT_DELIV_MODE_SHIFT, IOAPIC_LVT_DEST_MODE_SHIFT, + IOAPIC_LVT_MASKED_SHIFT, IOAPIC_LVT_REMOTE_IRR, IOAPIC_LVT_TRIGGER_MODE_SHIFT, IOAPIC_NUM_PINS, + IOAPIC_TRIGGER_EDGE, IOAPIC_VECTOR_MASK, IoApicBackend, IoApicRegs, Ioapic, +}; + +const IOAPIC_LVT_DEST_IDX_SHIFT: u64 = 56; + +pub struct WhpIoapicBackend { + vm: Arc, +} + +impl WhpIoapicBackend { + fn service(regs: &mut IoApicRegs, vm: &WhpVm) { + for i in 0..IOAPIC_NUM_PINS { + let mask = 1u32 << i; + if regs.irr & mask == 0 { + continue; + } + + let entry = regs.ioredtbl[i]; + if (entry >> IOAPIC_LVT_MASKED_SHIFT) & 1 != 0 { + continue; + } + + let vector = (entry & IOAPIC_VECTOR_MASK) as u32; + let dest = ((entry >> IOAPIC_LVT_DEST_IDX_SHIFT) & 0xff) as u32; + let dest_mode = ((entry >> IOAPIC_LVT_DEST_MODE_SHIFT) & 1) as u8; + let trigger = (entry >> IOAPIC_LVT_TRIGGER_MODE_SHIFT) & 1; + let deliv_mode = ((entry >> IOAPIC_LVT_DELIV_MODE_SHIFT) & IOAPIC_DM_MASK) as u8; + + if deliv_mode as u64 == IOAPIC_DM_EXTINT { + error!("ioapic: ExtINT delivery mode not supported (pin {i})"); + continue; + } + + if trigger == IOAPIC_TRIGGER_EDGE { + regs.irr &= !mask; + } else { + if entry & IOAPIC_LVT_REMOTE_IRR != 0 { + continue; + } + regs.ioredtbl[i] |= IOAPIC_LVT_REMOTE_IRR; + // Clear IRR to prevent infinite interrupt storms since we don't + // have a mechanism to track line de-assertion for level-triggered IRQs. + regs.irr &= !mask; + } + + let req = InterruptRequest { + interrupt_type: match deliv_mode { + 1 => InterruptType::LowestPriority, + 4 => InterruptType::Nmi, + 5 => InterruptType::Init, + 6 => InterruptType::Sipi, + _ => InterruptType::Fixed, + }, + destination_mode: if dest_mode == 0 { + InterruptDestinationMode::Physical + } else { + InterruptDestinationMode::Logical + }, + trigger_mode: if trigger == IOAPIC_TRIGGER_EDGE { + InterruptTriggerMode::Edge + } else { + InterruptTriggerMode::Level + }, + destination: dest, + vector, + }; + + if let Err(e) = vm.request_interrupt(&req) { + error!("ioapic: WHvRequestInterrupt failed for pin {i}: {e}"); + } + } + } +} + +impl IoApicBackend for WhpIoapicBackend { + fn on_entry_changed(&mut self, regs: &mut IoApicRegs, _index: usize) { + Self::service(regs, &self.vm); + } + + fn on_eoi(&mut self, regs: &mut IoApicRegs) { + Self::service(regs, &self.vm); + } + + fn set_irq( + &mut self, + irq_line: Option, + _interrupt_evt: Option<&EventFd>, + regs: &mut IoApicRegs, + ) -> Result<(), DeviceError> { + let irq = irq_line.ok_or_else(|| { + DeviceError::FailedSignalingUsedQueue(io::Error::new( + io::ErrorKind::InvalidData, + "IRQ line not configured", + )) + })?; + + if irq as usize >= IOAPIC_NUM_PINS { + return Err(DeviceError::FailedSignalingUsedQueue(io::Error::new( + io::ErrorKind::InvalidInput, + format!("IRQ {irq} out of IOAPIC pin range"), + ))); + } + + regs.irr |= 1 << irq; + Self::service(regs, &self.vm); + Ok(()) + } +} + +pub type WhpIoapic = Ioapic; + +impl Ioapic { + pub fn new(vm: Arc) -> Self { + Ioapic::from_backend(WhpIoapicBackend { vm }) + } +} diff --git a/src/devices/src/legacy/mod.rs b/src/devices/src/legacy/mod.rs index 00f8a0f..457dc7b 100644 --- a/src/devices/src/legacy/mod.rs +++ b/src/devices/src/legacy/mod.rs @@ -17,6 +17,8 @@ mod i8042; mod ioapic; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod ioapic_kvm; +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +mod ioapic_whp; mod irqchip; #[cfg(all(target_os = "linux", target_arch = "riscv64"))] mod kvmaia; @@ -59,6 +61,8 @@ pub use self::hvfgicv3::HvfGicV3; pub use self::i8042::{Error as I8042DeviceError, I8042Device}; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub use self::ioapic_kvm::IoApic; +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +pub use self::ioapic_whp::WhpIoapic; #[cfg(any(test, feature = "test_utils"))] pub use self::irqchip::test_utils::DummyIrqChip; pub use self::irqchip::{IrqChip, IrqChipDevice, IrqChipT}; diff --git a/src/whp/src/lib.rs b/src/whp/src/lib.rs index 07ece0c..735c290 100644 --- a/src/whp/src/lib.rs +++ b/src/whp/src/lib.rs @@ -211,6 +211,9 @@ struct WhvInterruptControl { pub enum InterruptType { Fixed = 0, LowestPriority = 1, + Nmi = 4, + Init = 5, + Sipi = 6, } #[repr(u8)] -- 2.51.2