diff --git a/src/arch/x86_64/kernel/apic.rs b/src/arch/x86_64/kernel/apic.rs index 7c33db2720..b70042b7f8 100644 --- a/src/arch/x86_64/kernel/apic.rs +++ b/src/arch/x86_64/kernel/apic.rs @@ -613,6 +613,26 @@ fn ioapic_set_interrupt(irq: u8, apicid: u8, enabled: bool) { ioapic_write(IOAPIC_REG_TABLE + off + 1, ioredirect_upper); } +/// Reconfigures an interrupt line for a legacy PCI interrupt. +/// +/// [`init_ioapic`] sets up every line for the edge triggered, active high +/// behavior of the ISA interrupts. PCI signals its interrupts level triggered +/// and active low instead. +#[cfg(feature = "pci")] +pub(crate) fn ioapic_set_pci_interrupt(irq: u8, apicid: u8) { + assert!(irq <= 24); + + let off = u32::from(irq * 2); + let ioredirect_upper = u32::from(apicid) << 24; + // Vector, active low (bit 13) and level triggered (bit 15). + let ioredirect_lower = u32::from(0x20 + irq) | (1 << 13) | (1 << 15); + + debug!("Configuring irq {irq} as level triggered, active low"); + + ioapic_write(IOAPIC_REG_TABLE + off, ioredirect_lower); + ioapic_write(IOAPIC_REG_TABLE + off + 1, ioredirect_upper); +} + pub fn init_local_apic() { // Mask out all interrupts we don't need right now. local_apic_write(IA32_X2APIC_LVT_TIMER, APIC_LVT_MASK); diff --git a/src/drivers/pci.rs b/src/drivers/pci.rs index a59aaacfe7..0a6a68b3d1 100644 --- a/src/drivers/pci.rs +++ b/src/drivers/pci.rs @@ -73,9 +73,23 @@ impl PciDevice { /// Returns the bar at bar-register `slot`. pub fn get_bar(&self, slot: u8) -> Option { - let header = self.header(); - let endpoint = EndpointHeader::from_header(header, &self.access)?; - endpoint.bar(slot, &self.access) + let endpoint = EndpointHeader::from_header(self.header(), &self.access)?; + let mut header = self.header(); + + // Determining the size of a bar writes all-ones into it and restores the + // old value afterwards. While decoding is enabled, the device decodes + // these intermediate values as real addresses and relocates itself. On a + // passed-through device the host follows that relocation and tries to map + // the bar at an address outside of the guest, which kills the VM. The PCI + // specification requires decoding to be disabled while sizing a bar. + let command = header.command(&self.access); + header.update_command(&self.access, |command| { + command & !(CommandRegister::IO_ENABLE | CommandRegister::MEMORY_ENABLE) + }); + let bar = endpoint.bar(slot, &self.access); + header.update_command(&self.access, |_| command); + + bar } /// Configure the bar at register `slot` @@ -181,6 +195,15 @@ impl PciDevice { return None; } + // A legacy PCI interrupt is signaled level triggered and active + // low, while the IOAPIC defaults to the edge triggered, active + // high behavior of the ISA interrupts. Without the correction a + // passed-through device stops delivering interrupts after its + // first one, because the host only unmasks the interrupt once the + // guest has completed a level triggered one. + #[cfg(target_arch = "x86_64")] + crate::arch::kernel::apic::ioapic_set_pci_interrupt(line, 0); + Some(line) } 5.. => { @@ -268,7 +291,7 @@ impl fmt::Display for PciDevice { let mut slot: u8 = 0; while usize::from(slot) < MAX_BARS { - if let Some(pci_bar) = endpoint.bar(slot, &self.access) { + if let Some(pci_bar) = self.get_bar(slot) { match pci_bar { Bar::Memory64 { address,