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

@ -266,6 +266,57 @@ existing mechanisms for keeping that true mechanically rather than by intention.
---
## Interrupts
A PCI driver connects its interrupt with one call, `pci_irq_attach()`, and
does not choose how. In order of preference:
1. **MSI**: the device writes a vector to the local APIC. No pin, no routing,
nothing to share.
2. **MSI-X**, but only if the driver passes `PCI_IRQF_MSIX`. Some parts change
how they report interrupt causes in that mode; the 82574 needs its IVAR
programmed, which `e1000` does not do, so it does not ask.
3. **INTx**, on the line named by the device's `INTERRUPT_LINE` register.
MSI comes first because INTx on real hardware is **the wrong pin**.
`INTERRUPT_LINE` holds the number the firmware gave the 8259. In APIC mode the
device is wired to an IOAPIC input that only ACPI's `_PRT` describes, and
reading `_PRT` takes an AML interpreter, which SchOS does not have. Under QEMU
the two numbers happen to agree, which is why this went unnoticed until the
drivers were read with a real board in mind.
INTx is therefore backed by the poller in `kernel/dev/irq.c`:
- A level-triggered line that has had a handler for two seconds and has never
fired gets its handlers called every tick, from a kernel thread with
interrupts off.
- A line that **storms** (an idle ISA input configured active-low reads as
asserted forever) is masked and polled instead of being reported and left
running.
Every handler already has to cope with being called when its device is idle,
because lines are shared, so a spurious call costs only a register read.
`/proc/interrupts` marks such a line `[polled]`, and an MSI line `msi`.
The vector layout:
| Lines | What they are |
|---|---|
| 0–23 | IOAPIC pins |
| 24–29 | MSI vectors, allocated by `irq_alloc_msi()` |
| 30 | LAPIC error (it was 19, which is a PCI pin on Intel chipsets) |
| 31 | spurious |
Nothing goes as high as vector 64, which is `T_SYSCALL`.
Tested under QEMU q35 with the root filesystem on a USB stick, in three
configurations:
- xHCI and e1000e on MSI.
- MSI disabled and the INTx line deliberately misrouted. xHCI took no
interrupts at all, the poller carried every transfer, and the root still
mounted.
- The old INTx path, unchanged on i440fx.
---