drivers: always re-arm shared IRQ lines after interrupt delivery

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.
This commit is contained in:
Red Bear OS
2026-07-20 18:44:53 +09:00
parent 54e89a337a
commit 9292422458
4 changed files with 46 additions and 18 deletions
+8 -2
View File
@@ -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)
+8 -3
View File
@@ -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")
}
}
+21 -9
View File
@@ -218,19 +218,31 @@ impl<const N: usize> IrqReactor<N> {
.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());
+9 -4
View File
@@ -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 {