irq: MSI first, and poll the INTx lines that never arrive

PCI drivers took their interrupt from INTERRUPT_LINE. On real hardware
that is the 8259's number, not the IOAPIC pin the device is wired to
(that is in ACPI _PRT, which needs AML), so nothing ever arrived. xHCI
is entirely interrupt-driven, so the USB keyboard and a USB root would
have hung even on a clean boot. Under QEMU the two numbers agree,
which is why it never showed.

- pci_irq_attach()/pci_irq_detach(): MSI, then MSI-X if the driver
  allows it, then INTx. The handler is registered before MSI is
  enabled, so no edge is lost. xHCI, e1000, r8169 and rtl8139 use it.
- Lines 24-29 are MSI vectors. The LAPIC error vector moves from 19,
  which is a PCI pin on Intel chipsets, to 30. Nothing is placed at or
  above vector 64 (T_SYSCALL).
- A storming line is masked and its handlers are polled instead of
  being left to eat the CPU. A level line that has had a handler for 2 s
  and never fired is polled every tick by a kernel thread.

Tested in QEMU q35 with the root on a USB stick: xHCI and e1000e on
MSI; then MSI disabled and INTx deliberately misrouted, where xHCI got
zero interrupts and the root still mounted through the poller. i440fx
INTx and the rtl8139 hand-over are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HH7xukt4aaiGj1nQEAoQf1
This commit is contained in:
Nexoniarz 2026-10-22 10:29:24 +02:00
commit 7e8d5e2821
11 changed files with 407 additions and 35 deletions

View file

@ -1169,7 +1169,7 @@ xhci_dump(void)
rd32(x->rt, XHCI_IR0 + IR_IMAN), rd32(x->rt, XHCI_IR0 + IR_IMOD),
rd32(x->rt, XHCI_IR0 + IR_ERSTSZ),
rd64(x->rt, XHCI_IR0 + IR_ERDP), x->ev.deq, x->ev.cycle);
cprintf(" pci irq %d\n", x->pci->irq);
cprintf(" pci irq %d (%s)\n", x->pci->irq_line, pci_irq_kind(x->pci));
for(p = 1; p <= x->maxports; p++){
uint32 sc = rd32(x->op, XHCI_PORTSC(p));
@ -1328,17 +1328,26 @@ xhci_probe(struct pci_dev *d)
if(x->ndevices == 0)
cprintf("xhci: no devices attached\n");
// Probing is over: from here a bulk transfer has a process behind it and
// can sleep rather than spin.
x->can_sleep = 1;
// Now, and only now, turn interrupts on. The order is: claim the line
// (which unmasks it and sets level/active-low), clear anything the
// controller already considers pending so that it drives INTx low, and then
// let it interrupt. The next event is a genuine low-to-high transition,
// which is the only thing an interrupt controller can act on.
if(d->irq != 0xFF){
irq_register(d->irq, xhci_irq, x, "xhci", 1); // PCI: level, active low
//
// MSI or MSI-X when the controller has either, which every xHCI does. On
// real hardware that is not a refinement: the INTx line the config space
// names is the 8259's number, the IOAPIC pin the controller is really on
// is a different one, and an xHCI waiting on INTx waited for ever -- the
// USB keyboard dead and a USB root never mounted.
//
// Probing is over: from here a bulk transfer has a process behind it and
// can sleep rather than spin -- but only if something will wake it. With
// no interrupt at all, transfers go on spinning as they did during probe.
if(pci_irq_attach(d, xhci_irq, x, "xhci", PCI_IRQF_MSIX) < 0){
cprintf("xhci: no interrupt; transfers will be polled\n");
} else {
x->can_sleep = 1;
wr32(x->op, XHCI_USBSTS, STS_EINT);
wr32(x->rt, XHCI_IR0 + IR_IMAN, IMAN_IP | IMAN_IE);