Skip to content
Open
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
20 changes: 20 additions & 0 deletions src/arch/x86_64/kernel/apic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
31 changes: 27 additions & 4 deletions src/drivers/pci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,23 @@ impl<T: ConfigRegionAccess> PciDevice<T> {

/// Returns the bar at bar-register `slot`.
pub fn get_bar(&self, slot: u8) -> Option<Bar> {
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`
Expand Down Expand Up @@ -181,6 +195,15 @@ impl<T: ConfigRegionAccess> PciDevice<T> {
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.. => {
Expand Down Expand Up @@ -268,7 +291,7 @@ impl<T: ConfigRegionAccess> fmt::Display for PciDevice<T> {

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,
Expand Down