diff --git a/Cargo.lock b/Cargo.lock index ecb90d195..c510a580f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -670,6 +670,7 @@ dependencies = [ "krun-polly", "krun-rutabaga-gfx", "krun-utils", + "krun-whp", "kvm-bindings", "kvm-ioctls", "libc", diff --git a/src/devices/Cargo.toml b/src/devices/Cargo.toml index eacb6cc97..c82f8e28e 100644 --- a/src/devices/Cargo.toml +++ b/src/devices/Cargo.toml @@ -56,3 +56,6 @@ kvm-ioctls = "0.22" [target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies] vm-fdt = ">= 0.2.0" + +[target.'cfg(target_os = "windows")'.dependencies] +whp = { package = "krun-whp", version = "=0.1.0-1.18.0", path = "../whp" } diff --git a/src/devices/src/legacy/ioapic.rs b/src/devices/src/legacy/ioapic.rs index af752c906..0cc54ae69 100644 --- a/src/devices/src/legacy/ioapic.rs +++ b/src/devices/src/legacy/ioapic.rs @@ -1,23 +1,19 @@ -use crossbeam_channel::unbounded; -#[cfg(not(feature = "tdx"))] -use kvm_bindings::{kvm_enable_cap, KVM_CAP_SPLIT_IRQCHIP}; -use kvm_bindings::{ - kvm_irq_routing_entry, kvm_irq_routing_entry__bindgen_ty_1, kvm_irq_routing_msi, KvmIrqRouting, - KVM_IRQ_ROUTING_MSI, -}; +//! Common IOAPIC register emulation, shared across hypervisor backends. +//! +//! The generic [`Ioapic`] struct handles all MMIO register reads/writes +//! (ioregsel, iowin, redirection table). Backend-specific interrupt +//! injection is delegated through the [`IoApicBackend`] trait. -use kvm_ioctls::{Error, VmFd}; +use std::sync::Mutex; use utils::eventfd::EventFd; -use utils::worker_message::WorkerMessage; use crate::bus::BusDevice; use crate::legacy::irqchip::IrqChipT; use crate::Error as DeviceError; const IOAPIC_BASE: u32 = 0xfec0_0000; -const APIC_DEFAULT_ADDRESS: u32 = 0xfee0_0000; -const IOAPIC_NUM_PINS: usize = 24; +pub(super) const IOAPIC_NUM_PINS: usize = 24; const IO_REG_SEL: u64 = 0x00; const IO_WIN: u64 = 0x10; @@ -27,140 +23,47 @@ const IO_APIC_ID: u8 = 0x00; const IO_APIC_VER: u8 = 0x01; const IO_APIC_ARB: u8 = 0x02; -const IOAPIC_LVT_DELIV_MODE_SHIFT: u64 = 8; -const IOAPIC_LVT_DEST_MODE_SHIFT: u64 = 11; +pub(super) const IOAPIC_LVT_DELIV_MODE_SHIFT: u64 = 8; +pub(super) const IOAPIC_LVT_DEST_MODE_SHIFT: u64 = 11; const IOAPIC_LVT_DELIV_STATUS_SHIFT: u64 = 12; const IOAPIC_LVT_REMOTE_IRR_SHIFT: u64 = 14; -const IOAPIC_LVT_TRIGGER_MODE_SHIFT: u64 = 15; -const IOAPIC_LVT_MASKED_SHIFT: u64 = 16; -const IOAPIC_LVT_DEST_IDX_SHIFT: u64 = 48; +pub(super) const IOAPIC_LVT_TRIGGER_MODE_SHIFT: u64 = 15; +pub(super) const IOAPIC_LVT_MASKED_SHIFT: u64 = 16; +pub(super) const IOAPIC_TRIGGER_EDGE: u64 = 0; const IOAPIC_VER_ENTRIES_SHIFT: u64 = 16; const IOAPIC_ID_SHIFT: u64 = 24; -const MSI_DATA_VECTOR_SHIFT: u64 = 0; -const MSI_ADDR_DEST_MODE_SHIFT: u64 = 2; -const MSI_ADDR_DEST_IDX_SHIFT: u64 = 4; -const MSI_DATA_DELIVERY_MODE_SHIFT: u64 = 8; -const MSI_DATA_TRIGGER_SHIFT: u64 = 15; - -const IOAPIC_LVT_REMOTE_IRR: u64 = 1 << IOAPIC_LVT_REMOTE_IRR_SHIFT; -const IOAPIC_LVT_TRIGGER_MODE: u64 = 1 << IOAPIC_LVT_TRIGGER_MODE_SHIFT; +pub(super) const IOAPIC_LVT_REMOTE_IRR: u64 = 1 << IOAPIC_LVT_REMOTE_IRR_SHIFT; +pub(super) const IOAPIC_LVT_TRIGGER_MODE: u64 = 1 << IOAPIC_LVT_TRIGGER_MODE_SHIFT; const IOAPIC_LVT_DELIV_STATUS: u64 = 1 << IOAPIC_LVT_DELIV_STATUS_SHIFT; const IOAPIC_RO_BITS: u64 = IOAPIC_LVT_REMOTE_IRR | IOAPIC_LVT_DELIV_STATUS; const IOAPIC_RW_BITS: u64 = !IOAPIC_RO_BITS; -const IOAPIC_DM_MASK: u64 = 0x7; +pub(super) const IOAPIC_DM_MASK: u64 = 0x7; const IOAPIC_ID_MASK: u64 = 0xf; -const IOAPIC_VECTOR_MASK: u64 = 0xff; +pub(super) const IOAPIC_VECTOR_MASK: u64 = 0xff; -const IOAPIC_DM_EXTINT: u64 = 0x7; +pub(super) const IOAPIC_DM_EXTINT: u64 = 0x7; const IOAPIC_REG_REDTBL_BASE: u64 = 0x10; -const IOAPIC_TRIGGER_EDGE: u64 = 0; - -/// 63:56 Destination Field (RW) -/// 55:17 Reserved -/// 16 Interrupt Mask (RW) -/// 15 Trigger Mode (RW) -/// 14 Remote IRR (RO) -/// 13 Interrupt Input Pin Polarity (INTPOL) (RW) -/// 12 Delivery Status (DELIVS) (RO) -/// 11 Destination Mode (DESTMOD) (RW) -/// 10:8 Delivery Mode (DELMOD) (RW) -/// 7:0 Interrupt Vector (INTVEC) (RW) -type RedirectionTableEntry = u64; - -#[derive(Debug, Default)] -pub struct IoApicEntryInfo { - masked: u8, - trig_mode: u8, - _dest_idx: u16, - _dest_mode: u8, - _delivery_mode: u8, - _vector: u8, - - addr: u32, - data: u32, -} - -#[derive(Default)] -struct MsiMessage { - address: u64, - data: u64, -} - -#[derive(Debug)] -pub struct IoApic { +pub struct IoApicRegs { id: u8, ioregsel: u8, - irr: u32, - ioredtbl: [u64; IOAPIC_NUM_PINS], + pub(super) irr: u32, + pub(super) ioredtbl: [u64; IOAPIC_NUM_PINS], version: u8, - irq_eoi: [i32; IOAPIC_NUM_PINS], - irq_routes: Vec, - irq_sender: crossbeam_channel::Sender, } -impl IoApic { - pub fn new( - vm: &VmFd, - _irq_sender: crossbeam_channel::Sender, - ) -> Result { - #[cfg(not(feature = "tdx"))] - { - let mut cap = kvm_enable_cap { - cap: KVM_CAP_SPLIT_IRQCHIP, - ..Default::default() - }; - cap.args[0] = 24; - vm.enable_cap(&cap)?; - } - - let mut ioapic = Self { +impl IoApicRegs { + fn new() -> Self { + Self { id: 0, ioregsel: 0, irr: 0, ioredtbl: [1 << IOAPIC_LVT_MASKED_SHIFT; IOAPIC_NUM_PINS], version: 0x20, - irq_eoi: [0; IOAPIC_NUM_PINS], - irq_routes: Vec::with_capacity(IOAPIC_NUM_PINS), - irq_sender: _irq_sender, - }; - - (0..IOAPIC_NUM_PINS).for_each(|i| ioapic.add_msi_route(i)); - - let mut routing = KvmIrqRouting::new(ioapic.irq_routes.len()).unwrap(); - let routing_entires = routing.as_mut_slice(); - routing_entires.copy_from_slice(ioapic.irq_routes.as_slice()); - vm.set_gsi_routing(&routing)?; - - Ok(ioapic) - } - - fn add_msi_route(&mut self, virq: usize) { - let msg = MsiMessage::default(); - let kroute = kvm_irq_routing_entry { - gsi: virq as u32, - type_: KVM_IRQ_ROUTING_MSI, - flags: 0, - u: kvm_irq_routing_entry__bindgen_ty_1 { - msi: kvm_irq_routing_msi { - address_lo: msg.address as u32, - address_hi: (msg.address >> 32) as u32, - data: msg.data as u32, - ..Default::default() - }, - }, - ..Default::default() - }; - - // 4095 is the max irq number for kvm (MAX_IRQ_ROUTES - 1) - if self.irq_routes.len() < 4095 { - self.irq_routes.push(kroute); - } else { - error!("ioapic: not enough space for irq"); } } @@ -169,157 +72,47 @@ impl IoApic { self.ioredtbl[index] &= !IOAPIC_LVT_REMOTE_IRR; } } +} - fn parse_entry(&self, entry: &RedirectionTableEntry) -> IoApicEntryInfo { - let vector = (entry & IOAPIC_VECTOR_MASK) as u8; - let dest_idx = ((entry >> IOAPIC_LVT_DEST_IDX_SHIFT) & 0xffff) as u16; - let delivery_mode = ((entry >> IOAPIC_LVT_DELIV_MODE_SHIFT) & IOAPIC_DM_MASK) as u8; - let trig_mode = ((entry >> IOAPIC_LVT_TRIGGER_MODE_SHIFT) & 1) as u8; - let dest_mode = ((entry >> IOAPIC_LVT_DEST_MODE_SHIFT) & 1) as u8; - - if delivery_mode as u64 == IOAPIC_DM_EXTINT { - panic!("ioapic: libkrun does not have PIC support"); - } - - IoApicEntryInfo { - masked: ((entry >> IOAPIC_LVT_MASKED_SHIFT) & 1) as u8, - trig_mode, - _dest_idx: dest_idx, - _dest_mode: dest_mode, - _delivery_mode: delivery_mode, - _vector: vector, - - addr: ((APIC_DEFAULT_ADDRESS as u64) - | ((dest_idx as u64) << MSI_ADDR_DEST_IDX_SHIFT) - | ((dest_mode as u64) << MSI_ADDR_DEST_MODE_SHIFT)) as u32, - data: (((vector as u64) << MSI_DATA_VECTOR_SHIFT) - | ((trig_mode as u64) << MSI_DATA_TRIGGER_SHIFT) - | ((delivery_mode as u64) << MSI_DATA_DELIVERY_MODE_SHIFT)) - as u32, - } - } - - fn update_msi_route(&mut self, virq: usize, msg: &MsiMessage) { - let kroute = kvm_irq_routing_entry { - gsi: virq as u32, - type_: KVM_IRQ_ROUTING_MSI, - flags: 0, - u: kvm_irq_routing_entry__bindgen_ty_1 { - msi: kvm_irq_routing_msi { - address_lo: msg.address as u32, - address_hi: (msg.address >> 32) as u32, - data: msg.data as u32, - ..Default::default() - }, - }, - ..Default::default() - }; - - for entry in self.irq_routes.iter_mut() { - if entry.gsi == kroute.gsi { - *entry = kroute; - } - } - } - - fn update_routes(&mut self) { - for i in 0..IOAPIC_NUM_PINS { - let info = self.parse_entry(&self.ioredtbl[i]); - - if info.masked == 0 { - let msg = MsiMessage { - address: info.addr as u64, - data: info.data as u64, - }; - - self.update_msi_route(i, &msg); - } - } +// Implemented per hypervisor to handle interrupt injection and routing. +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); - let (response_sender, response_receiver) = unbounded(); - self.irq_sender - .send(WorkerMessage::GsiRoute( - response_sender.clone(), - self.irq_routes.clone(), - )) - .unwrap(); - if !response_receiver.recv().unwrap() { - error!("unable to set GSI Routes for IO APIC"); - } - } + /// 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); - fn service(&mut self) { - for i in 0..IOAPIC_NUM_PINS { - let mask = 1 << i; - - if self.irr & mask > 0 { - let mut coalesce = 0; - - let entry = self.ioredtbl[i]; - let info = self.parse_entry(&entry); - if info.masked == 0 { - if info.trig_mode as u64 == IOAPIC_TRIGGER_EDGE { - self.irr &= !mask; - } else { - coalesce = self.ioredtbl[i] & IOAPIC_LVT_REMOTE_IRR; - self.ioredtbl[i] |= IOAPIC_LVT_REMOTE_IRR; - } + /// Called from `IrqChipT::set_irq` to assert an interrupt line. + fn set_irq( + &mut self, + irq_line: Option, + interrupt_evt: Option<&EventFd>, + regs: &mut IoApicRegs, + ) -> Result<(), DeviceError>; +} - if coalesce > 0 { - continue; - } +struct IoapicInner { + regs: IoApicRegs, + backend: B, +} - let (response_sender, response_receiver) = unbounded(); - if info.trig_mode as u64 == IOAPIC_TRIGGER_EDGE { - self.irq_sender - .send(WorkerMessage::IrqLine( - response_sender.clone(), - i as u32, - true, - )) - .unwrap(); - if !response_receiver.recv().unwrap() { - error!( - "unable to set IRQ LINE for IRQ {} with active set to {}", - i, true - ); - } +pub struct Ioapic { + inner: Mutex>, +} - self.irq_sender - .send(WorkerMessage::IrqLine( - response_sender.clone(), - i as u32, - false, - )) - .unwrap(); - if !response_receiver.recv().unwrap() { - error!( - "unable to set IRQ LINE for IRQ {} with active set to {}", - i, false - ); - } - } else { - self.irq_sender - .send(WorkerMessage::IrqLine( - response_sender.clone(), - i as u32, - true, - )) - .unwrap(); - if !response_receiver.recv().unwrap() { - error!( - "unable to set IRQ LINE for IRQ {} with active set to {}", - i, true - ); - } - } - } - } +impl Ioapic { + pub(super) fn from_backend(backend: B) -> Self { + Self { + inner: Mutex::new(IoapicInner { + regs: IoApicRegs::new(), + backend, + }), } } } -impl IrqChipT for IoApic { +impl IrqChipT for Ioapic { fn get_mmio_addr(&self) -> u64 { IOAPIC_BASE as u64 } @@ -330,31 +123,24 @@ impl IrqChipT for IoApic { fn set_irq( &self, - _irq_line: Option, + irq_line: Option, interrupt_evt: Option<&EventFd>, ) -> Result<(), DeviceError> { - if let Some(interrupt_evt) = interrupt_evt { - if let Err(e) = interrupt_evt.write(1) { - error!("Failed to signal used queue: {e:?}"); - return Err(DeviceError::FailedSignalingUsedQueue(e)); - } - } else { - error!("EventFd not set up for irq line"); - return Err(DeviceError::FailedSignalingUsedQueue(std::io::Error::new( - std::io::ErrorKind::NotFound, - "EventFd not set up for irq line", - ))); - } - Ok(()) + let mut inner = self.inner.lock().unwrap(); + let IoapicInner { regs, backend } = &mut *inner; + backend.set_irq(irq_line, interrupt_evt, regs) } } -impl BusDevice for IoApic { +impl BusDevice for Ioapic { fn read(&mut self, _vcpuid: u64, offset: u64, data: &mut [u8]) { + let mut inner = self.inner.lock().unwrap(); + let IoapicInner { regs, .. } = &mut *inner; + let val = match offset { IO_REG_SEL => { debug!("ioapic: read: ioregsel"); - self.ioregsel as u32 + regs.ioregsel as u32 } IO_WIN => { // the data needs to be 32-bits in size @@ -363,18 +149,18 @@ impl BusDevice for IoApic { return; } - match self.ioregsel { + match regs.ioregsel { IO_APIC_ID | IO_APIC_ARB => { debug!("ioapic: read: IOAPIC ID"); - ((self.id as u64) << IOAPIC_ID_SHIFT) as u32 + ((regs.id as u64) << IOAPIC_ID_SHIFT) as u32 } IO_APIC_VER => { debug!("ioapic: read: IOAPIC version"); - self.version as u32 + regs.version as u32 | ((IOAPIC_NUM_PINS as u32 - 1) << IOAPIC_VER_ENTRIES_SHIFT) } _ => { - let index = (self.ioregsel as u64 - IOAPIC_REG_REDTBL_BASE) >> 1; + let index = (regs.ioregsel as u64 - IOAPIC_REG_REDTBL_BASE) >> 1; debug!("ioapic: read: ioredtbl register {index}"); let mut val = 0u32; @@ -382,12 +168,12 @@ impl BusDevice for IoApic { // Therefore, we need to check if we are reading the // upper 32 bits or the lower if index < IOAPIC_NUM_PINS as u64 { - if self.ioregsel & 1 > 0 { + if regs.ioregsel & 1 > 0 { // read upper 32 bits - val = (self.ioredtbl[index as usize] >> 32) as u32; + val = (regs.ioredtbl[index as usize] >> 32) as u32; } else { // read lower 32 bits - val = (self.ioredtbl[index as usize] & 0xffff_ffffu64) as u32; + val = (regs.ioredtbl[index as usize] & 0xffff_ffffu64) as u32; } } val @@ -416,56 +202,76 @@ impl BusDevice for IoApic { // convert data into a u32 int with native endianness let arr = [data[0], data[1], data[2], data[3]]; let val = u32::from_ne_bytes(arr); + + let mut inner = self.inner.lock().unwrap(); + let IoapicInner { regs, backend } = &mut *inner; + match offset { IO_REG_SEL => { debug!("ioapic: write: ioregsel"); - self.ioregsel = val as u8 + regs.ioregsel = val as u8 } IO_WIN => { - match self.ioregsel { + match regs.ioregsel { IO_APIC_ID => { debug!("ioapic: write: IOAPIC ID"); - self.id = ((val >> IOAPIC_ID_SHIFT) & (IOAPIC_ID_MASK as u32)) as u8 + regs.id = ((val >> IOAPIC_ID_SHIFT) & (IOAPIC_ID_MASK as u32)) as u8 } // NOTE: these are read-only registers, so they should never be written to IO_APIC_VER | IO_APIC_ARB => debug!("ioapic: write: IOAPIC VERSION"), _ => { - if self.ioregsel < (IO_WIN as u8) { + if regs.ioregsel < (IO_WIN as u8) { debug!("invalid write; ignore"); return; } - let index = (self.ioregsel as u64 - IOAPIC_REG_REDTBL_BASE) >> 1; + let index = (regs.ioregsel as u64 - IOAPIC_REG_REDTBL_BASE) >> 1; debug!("ioapic: write: ioredtbl register {index}"); if index >= IOAPIC_NUM_PINS as u64 { warn!("ioapic: write: virq out of pin range {index}"); return; } - let ro_bits = self.ioredtbl[index as usize] & IOAPIC_RO_BITS; + let ro_bits = regs.ioredtbl[index as usize] & IOAPIC_RO_BITS; // check if we are writing to the upper 32-bits of the // register or the lower 32-bits - if self.ioregsel & 1 > 0 { - self.ioredtbl[index as usize] &= 0xffff_ffff; - self.ioredtbl[index as usize] |= (val as u64) << 32; + if regs.ioregsel & 1 > 0 { + regs.ioredtbl[index as usize] &= 0xffff_ffff; + regs.ioredtbl[index as usize] |= (val as u64) << 32; } else { - self.ioredtbl[index as usize] &= !0xffff_ffff; - self.ioredtbl[index as usize] |= val as u64; + regs.ioredtbl[index as usize] &= !0xffff_ffff; + regs.ioredtbl[index as usize] |= val as u64; } // restore RO bits - self.ioredtbl[index as usize] &= IOAPIC_RW_BITS; - self.ioredtbl[index as usize] |= ro_bits; - self.irq_eoi[index as usize] = 0; + regs.ioredtbl[index as usize] &= IOAPIC_RW_BITS; + regs.ioredtbl[index as usize] |= ro_bits; // if the trigger mode is EDGE, clear IRR bit - self.fix_edge_remote_irr(index as usize); - self.update_routes(); - self.service(); + regs.fix_edge_remote_irr(index as usize); + backend.on_entry_changed(regs, index as usize); } } } - IO_EOI => todo!(), + IO_EOI => { + #[cfg(target_os = "windows")] + { + let vector = (val as u64 & IOAPIC_VECTOR_MASK) as u8; + 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; + 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 new file mode 100644 index 000000000..a2864e9bc --- /dev/null +++ b/src/devices/src/legacy/ioapic_kvm.rs @@ -0,0 +1,311 @@ +use crossbeam_channel::unbounded; +#[cfg(not(feature = "tdx"))] +use kvm_bindings::{kvm_enable_cap, KVM_CAP_SPLIT_IRQCHIP}; +use kvm_bindings::{ + kvm_irq_routing_entry, kvm_irq_routing_entry__bindgen_ty_1, kvm_irq_routing_msi, KvmIrqRouting, + KVM_IRQ_ROUTING_MSI, +}; + +use kvm_ioctls::{Error, VmFd}; + +use utils::eventfd::EventFd; +use utils::worker_message::WorkerMessage; + +use crate::Error as DeviceError; + +use super::ioapic::{ + IoApicBackend, IoApicRegs, 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, +}; + +const APIC_DEFAULT_ADDRESS: u32 = 0xfee0_0000; + +const MSI_DATA_VECTOR_SHIFT: u64 = 0; +const MSI_ADDR_DEST_MODE_SHIFT: u64 = 2; +const MSI_ADDR_DEST_IDX_SHIFT: u64 = 4; +const MSI_DATA_DELIVERY_MODE_SHIFT: u64 = 8; +const MSI_DATA_TRIGGER_SHIFT: u64 = 15; + +const IOAPIC_LVT_DEST_IDX_SHIFT: u64 = 48; + +/// 63:56 Destination Field (RW) +/// 55:17 Reserved +/// 16 Interrupt Mask (RW) +/// 15 Trigger Mode (RW) +/// 14 Remote IRR (RO) +/// 13 Interrupt Input Pin Polarity (INTPOL) (RW) +/// 12 Delivery Status (DELIVS) (RO) +/// 11 Destination Mode (DESTMOD) (RW) +/// 10:8 Delivery Mode (DELMOD) (RW) +/// 7:0 Interrupt Vector (INTVEC) (RW) +type RedirectionTableEntry = u64; + +#[derive(Debug, Default)] +struct IoApicEntryInfo { + masked: u8, + trig_mode: u8, + _dest_idx: u16, + _dest_mode: u8, + _delivery_mode: u8, + _vector: u8, + + addr: u32, + data: u32, +} + +#[derive(Default)] +struct MsiMessage { + address: u64, + data: u64, +} + +#[derive(Debug)] +pub struct IoApicKvmBackend { + irq_eoi: [i32; IOAPIC_NUM_PINS], + irq_routes: Vec, + irq_sender: crossbeam_channel::Sender, +} + +impl IoApicKvmBackend { + fn add_msi_route(&mut self, virq: usize) { + let msg = MsiMessage::default(); + let kroute = kvm_irq_routing_entry { + gsi: virq as u32, + type_: KVM_IRQ_ROUTING_MSI, + flags: 0, + u: kvm_irq_routing_entry__bindgen_ty_1 { + msi: kvm_irq_routing_msi { + address_lo: msg.address as u32, + address_hi: (msg.address >> 32) as u32, + data: msg.data as u32, + ..Default::default() + }, + }, + ..Default::default() + }; + + // 4095 is the max irq number for kvm (MAX_IRQ_ROUTES - 1) + if self.irq_routes.len() < 4095 { + self.irq_routes.push(kroute); + } else { + error!("ioapic: not enough space for irq"); + } + } + + fn parse_entry(&self, entry: &RedirectionTableEntry) -> IoApicEntryInfo { + let vector = (entry & IOAPIC_VECTOR_MASK) as u8; + let dest_idx = ((entry >> IOAPIC_LVT_DEST_IDX_SHIFT) & 0xffff) as u16; + let delivery_mode = ((entry >> IOAPIC_LVT_DELIV_MODE_SHIFT) & IOAPIC_DM_MASK) as u8; + let trig_mode = ((entry >> IOAPIC_LVT_TRIGGER_MODE_SHIFT) & 1) as u8; + let dest_mode = ((entry >> IOAPIC_LVT_DEST_MODE_SHIFT) & 1) as u8; + + if delivery_mode as u64 == IOAPIC_DM_EXTINT { + panic!("ioapic: libkrun does not have PIC support"); + } + + IoApicEntryInfo { + masked: ((entry >> IOAPIC_LVT_MASKED_SHIFT) & 1) as u8, + trig_mode, + _dest_idx: dest_idx, + _dest_mode: dest_mode, + _delivery_mode: delivery_mode, + _vector: vector, + + addr: ((APIC_DEFAULT_ADDRESS as u64) + | ((dest_idx as u64) << MSI_ADDR_DEST_IDX_SHIFT) + | ((dest_mode as u64) << MSI_ADDR_DEST_MODE_SHIFT)) as u32, + data: (((vector as u64) << MSI_DATA_VECTOR_SHIFT) + | ((trig_mode as u64) << MSI_DATA_TRIGGER_SHIFT) + | ((delivery_mode as u64) << MSI_DATA_DELIVERY_MODE_SHIFT)) + as u32, + } + } + + fn update_msi_route(&mut self, virq: usize, msg: &MsiMessage) { + let kroute = kvm_irq_routing_entry { + gsi: virq as u32, + type_: KVM_IRQ_ROUTING_MSI, + flags: 0, + u: kvm_irq_routing_entry__bindgen_ty_1 { + msi: kvm_irq_routing_msi { + address_lo: msg.address as u32, + address_hi: (msg.address >> 32) as u32, + data: msg.data as u32, + ..Default::default() + }, + }, + ..Default::default() + }; + + for entry in self.irq_routes.iter_mut() { + if entry.gsi == kroute.gsi { + *entry = kroute; + } + } + } + + fn update_routes(&mut self, regs: &IoApicRegs) { + for i in 0..IOAPIC_NUM_PINS { + let info = self.parse_entry(®s.ioredtbl[i]); + + if info.masked == 0 { + let msg = MsiMessage { + address: info.addr as u64, + data: info.data as u64, + }; + + self.update_msi_route(i, &msg); + } + } + + let (response_sender, response_receiver) = unbounded(); + self.irq_sender + .send(WorkerMessage::GsiRoute( + response_sender.clone(), + self.irq_routes.clone(), + )) + .unwrap(); + if !response_receiver.recv().unwrap() { + error!("unable to set GSI Routes for IO APIC"); + } + } + + fn service(&mut self, regs: &mut IoApicRegs) { + for i in 0..IOAPIC_NUM_PINS { + let mask = 1 << i; + + if regs.irr & mask > 0 { + let mut coalesce = 0; + + let entry = regs.ioredtbl[i]; + let info = self.parse_entry(&entry); + if info.masked == 0 { + if info.trig_mode as u64 == IOAPIC_TRIGGER_EDGE { + regs.irr &= !mask; + } else { + coalesce = regs.ioredtbl[i] & IOAPIC_LVT_REMOTE_IRR; + regs.ioredtbl[i] |= IOAPIC_LVT_REMOTE_IRR; + } + + if coalesce > 0 { + continue; + } + + let (response_sender, response_receiver) = unbounded(); + if info.trig_mode as u64 == IOAPIC_TRIGGER_EDGE { + self.irq_sender + .send(WorkerMessage::IrqLine( + response_sender.clone(), + i as u32, + true, + )) + .unwrap(); + if !response_receiver.recv().unwrap() { + error!( + "unable to set IRQ LINE for IRQ {} with active set to {}", + i, true + ); + } + + self.irq_sender + .send(WorkerMessage::IrqLine( + response_sender.clone(), + i as u32, + false, + )) + .unwrap(); + if !response_receiver.recv().unwrap() { + error!( + "unable to set IRQ LINE for IRQ {} with active set to {}", + i, false + ); + } + } else { + self.irq_sender + .send(WorkerMessage::IrqLine( + response_sender.clone(), + i as u32, + true, + )) + .unwrap(); + if !response_receiver.recv().unwrap() { + error!( + "unable to set IRQ LINE for IRQ {} with active set to {}", + i, true + ); + } + } + } + } + } + } +} + +impl IoApicBackend for IoApicKvmBackend { + fn on_entry_changed(&mut self, regs: &mut IoApicRegs, index: usize) { + self.irq_eoi[index] = 0; + self.update_routes(regs); + self.service(regs); + } + + fn on_eoi(&mut self, _regs: &mut IoApicRegs) { + // TODO: implement + } + + fn set_irq( + &mut self, + _irq_line: Option, + interrupt_evt: Option<&EventFd>, + _regs: &mut IoApicRegs, + ) -> Result<(), DeviceError> { + if let Some(interrupt_evt) = interrupt_evt { + if let Err(e) = interrupt_evt.write(1) { + error!("Failed to signal used queue: {e:?}"); + return Err(DeviceError::FailedSignalingUsedQueue(e)); + } + } else { + error!("EventFd not set up for irq line"); + return Err(DeviceError::FailedSignalingUsedQueue(std::io::Error::new( + std::io::ErrorKind::NotFound, + "EventFd not set up for irq line", + ))); + } + Ok(()) + } +} + +pub type IoApic = Ioapic; + +impl Ioapic { + pub fn new( + vm: &VmFd, + _irq_sender: crossbeam_channel::Sender, + ) -> Result { + #[cfg(not(feature = "tdx"))] + { + let mut cap = kvm_enable_cap { + cap: KVM_CAP_SPLIT_IRQCHIP, + ..Default::default() + }; + cap.args[0] = 24; + vm.enable_cap(&cap)?; + } + + let mut backend = IoApicKvmBackend { + irq_eoi: [0; IOAPIC_NUM_PINS], + irq_routes: Vec::with_capacity(IOAPIC_NUM_PINS), + irq_sender: _irq_sender, + }; + + (0..IOAPIC_NUM_PINS).for_each(|i| backend.add_msi_route(i)); + + let mut routing = KvmIrqRouting::new(backend.irq_routes.len()).unwrap(); + let routing_entries = routing.as_mut_slice(); + routing_entries.copy_from_slice(backend.irq_routes.as_slice()); + vm.set_gsi_routing(&routing)?; + + Ok(Ioapic::from_backend(backend)) + } +} diff --git a/src/devices/src/legacy/ioapic_whp.rs b/src/devices/src/legacy/ioapic_whp.rs new file mode 100644 index 000000000..15dab8609 --- /dev/null +++ b/src/devices/src/legacy/ioapic_whp.rs @@ -0,0 +1,134 @@ +// 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::{ + IoApicBackend, IoApicRegs, 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, IOAPIC_LVT_TRIGGER_MODE_SHIFT, IOAPIC_NUM_PINS, + IOAPIC_TRIGGER_EDGE, IOAPIC_VECTOR_MASK, +}; + +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; + } + + 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 9fc38ee0f..457dc7bdf 100644 --- a/src/devices/src/legacy/mod.rs +++ b/src/devices/src/legacy/mod.rs @@ -13,8 +13,12 @@ mod gicv3; mod hvfgicv3; #[cfg(target_arch = "x86_64")] mod i8042; -#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +#[cfg(target_arch = "x86_64")] 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; @@ -56,7 +60,9 @@ pub use self::hvfgicv3::HvfGicV3; #[cfg(target_arch = "x86_64")] pub use self::i8042::{Error as I8042DeviceError, I8042Device}; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] -pub use self::ioapic::IoApic; +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 268e5519e..7c91fb4b4 100644 --- a/src/whp/src/lib.rs +++ b/src/whp/src/lib.rs @@ -189,6 +189,9 @@ struct WhvInterruptControl { pub enum InterruptType { Fixed = 0, LowestPriority = 1, + Nmi = 4, + Init = 5, + Sipi = 6, } #[repr(u8)]