From 9292422458b9954782782328cbd8266de69431d4 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Mon, 20 Jul 2026 18:44:53 +0900 Subject: [PATCH] drivers: always re-arm shared IRQ lines after interrupt delivery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same bug class as the ahcid fix (ad40fffd): the kernel masks the IRQ line when it delivers an interrupt and only re-arms it on the userspace write-back (irq scheme write -> acknowledge() -> pic/ioapic_unmask). When a driver acks only if the interrupt was its own, one foreign interrupt on a shared INTx line leaves the line masked forever and all later interrupts for the device are lost. - e1000d: ack unconditionally; ICR is read-to-clear, so the ownership check doubles as the device-level status clear - ihdad: ack before the not-ours continue - vboxd: ack unconditionally; device-side ack and event handling stay conditional on host_events - xhcid: ack unconditionally in the IRQ reactor; for INTx the ownership check already clears IMAN.IP (RW1C) and device interrupts are masked before re-arming, so no storm. MSI/MSI-X vectors are never shared, so behavior there is unchanged (every delivery is ours by construction) Verified: cargo check -Z build-std --target x86_64-unknown-redox for e1000d, ihdad, vboxd, xhcid — clean, no new warnings. --- drivers/audio/ihdad/src/main.rs | 10 ++++++-- drivers/net/e1000d/src/main.rs | 11 ++++++--- drivers/usb/xhcid/src/xhci/irq_reactor.rs | 30 ++++++++++++++++------- drivers/vboxd/src/main.rs | 13 +++++++--- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs index 11d80133a0..e4c92d729f 100755 --- a/drivers/audio/ihdad/src/main.rs +++ b/drivers/audio/ihdad/src/main.rs @@ -120,10 +120,16 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let mut irq = [0; 8]; irq_file.irq_handle().read(&mut irq).unwrap(); - if !device.irq() { + let ours = device.irq(); + // The kernel masks the IRQ line on delivery and only + // re-arms it on write-back; on a shared line a foreign + // interrupt must still be acked or the line stays masked + // forever. + irq_file.irq_handle().write(&mut irq).unwrap(); + + if !ours { continue; } - irq_file.irq_handle().write(&mut irq).unwrap(); readiness_based .poll_all_requests(&mut device) diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs index f62eaad7e9..01f32a0d6f 100644 --- a/drivers/net/e1000d/src/main.rs +++ b/drivers/net/e1000d/src/main.rs @@ -79,9 +79,14 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { Source::Irq => { let mut irq = [0; 8]; irq_file.read(&mut irq).expect("e1000d: IRQ read failed"); - if unsafe { scheme.adapter().irq() } { - irq_file.write(&mut irq).expect("e1000d: IRQ ack failed"); - + // Reading ICR clears it, so this is also the device-level + // status clear required before re-arming the line. + let ours = unsafe { scheme.adapter().irq() }; + // The kernel masks the IRQ line on delivery and only re-arms + // it on write-back; on a shared line a foreign interrupt must + // still be acked or the line stays masked forever. + irq_file.write(&mut irq).expect("e1000d: IRQ ack failed"); + if ours { scheme.tick().expect("e1000d: failed to handle IRQ") } } diff --git a/drivers/usb/xhcid/src/xhci/irq_reactor.rs b/drivers/usb/xhcid/src/xhci/irq_reactor.rs index 2079caa493..c89efd04d8 100644 --- a/drivers/usb/xhcid/src/xhci/irq_reactor.rs +++ b/drivers/usb/xhcid/src/xhci/irq_reactor.rs @@ -218,19 +218,31 @@ impl IrqReactor { .read(&mut buffer) .expect("xhcid irq_reactor: failed to read from /scheme/irq"); - if !self.hci.received_irq() { - // continue only when an IRQ to this device was received + let ours = self.hci.received_irq(); + + if ours { + self.mask_interrupts(); + + trace!("IRQ reactor received an IRQ"); + } + + // The kernel masks the IRQ line when it delivers the interrupt and + // only re-arms it on the write-back (acknowledge() -> pic/ioapic + // unmask). On a shared INTx line a foreign device's interrupt + // arrives here with IMAN.IP clear; skipping the ack then leaves + // the line masked forever and every later xHC interrupt is lost. + // Always re-arm, whether or not the interrupt turned out to be + // ours. When it was ours, received_irq() already cleared IMAN.IP + // (RW1C) and interrupts are device-masked above, so this cannot + // re-trigger a storm. + let _ = self.irq_file.as_mut().unwrap_or_else(|| unreachable!( + "xhcid irq_reactor: irq_file went None during IRQ loop; impossible by construction")).write(&buffer); + + if !ours { trace!("no interrupt pending"); continue 'trb_loop; } - self.mask_interrupts(); - - trace!("IRQ reactor received an IRQ"); - - let _ = self.irq_file.as_mut().unwrap_or_else(|| unreachable!( - "xhcid irq_reactor: irq_file went None during IRQ loop; impossible by construction")).write(&buffer); - // TODO: More event rings, probably even with different IRQs. let mut event_ring = hci_clone.primary_event_ring.lock().unwrap_or_else(|e| e.into_inner()); diff --git a/drivers/vboxd/src/main.rs b/drivers/vboxd/src/main.rs index b9e42d4aa8..0fbdf3e76d 100644 --- a/drivers/vboxd/src/main.rs +++ b/drivers/vboxd/src/main.rs @@ -368,11 +368,16 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let host_events = vmmdev.host_events.read(); if host_events != 0 { port.write(ack_events.physical() as u32); - if let Err(err) = irq_file.write(&irq) { - eprintln!("vboxd: failed to acknowledge IRQ: {err}"); - std::process::exit(1); - } + } + // The kernel masks the IRQ line on delivery and only re-arms + // it on write-back; ack unconditionally or a foreign interrupt + // on a shared line leaves the line masked forever. + if let Err(err) = irq_file.write(&irq) { + eprintln!("vboxd: failed to acknowledge IRQ: {err}"); + std::process::exit(1); + } + if host_events != 0 { if host_events & VBOX_EVENT_DISPLAY == VBOX_EVENT_DISPLAY { port.write(display_change.physical() as u32); if let Some(ref mut display) = display_opt {