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:
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user