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

@ -20,7 +20,16 @@
// whether its own device actually raised the interrupt and do nothing if it
// did not.
#define NIRQ 24 // IOAPIC redirection entries on a PC
// Lines 0-23 are the IOAPIC's pins. 24-29 are MSI vectors, which are not pins
// at all -- a device writes the vector number to the local APIC directly --
// and are dispatched through the same table so that a handler cannot tell
// which kind it is on. 30 and 31 are the LAPIC's error and spurious vectors.
// Everything stops below vector 64, which is T_SYSCALL: a line there would
// let a user-mode `int $0x40` dispatch an interrupt and EOI one that is not.
#define NIRQ_IOAPIC 24
#define IRQ_MSI_FIRST 24
#define IRQ_MSI_LAST 29
#define NIRQ 31
#define NCHAIN 4 // handlers that may share one line
typedef void (*irq_handler_t)(void *arg);
@ -41,4 +50,11 @@ int irq_handler_count(int irq);
void irq_dispatch(int irq);
void irq_init(void);
// An MSI vector for pci_irq_attach(). -1 when all six are in use.
int irq_alloc_msi(void);
void irq_free_msi(int irq);
// The thread that polls PCI lines which never interrupt. After sinit().
void irq_poll_start(void);
int irq_info(char *buf, int cap); // /proc/interrupts

View file

@ -74,8 +74,22 @@ struct pci_dev {
// The driver itself, so it can be asked to release the device. The name
// alone cannot be called.
const struct pci_driver *drv;
// How pci_irq_attach() connected the interrupt: PCI_IRQ_INTX, _MSI or
// _MSIX, and on which line. See kernel/dev/pci.c.
uint8 irq_kind;
int irq_line;
};
#define PCI_IRQ_NONE 0
#define PCI_IRQ_INTX 1
#define PCI_IRQ_MSI 2
#define PCI_IRQ_MSIX 3
// pci_irq_attach() flags. MSI is always tried first; MSI-X only when the
// driver says it can take it, because some parts change how they report
// interrupt causes once MSI-X is on (the 82574's IVAR, for one).
#define PCI_IRQF_MSIX 0x1
// One row of a driver's match table. Terminate the table with a row whose
// vendor is 0.
struct pci_match {
@ -90,6 +104,16 @@ struct pci_match {
#include "device.h"
enum dev_class pci_class_of(const struct pci_dev *);
// Connect a device's interrupt to a handler: an MSI vector if the device has
// one, MSI-X if allowed and it has that instead, and its INTx line otherwise.
// Returns the line, or -1 if there is no way to interrupt at all -- which is
// not fatal; a driver with a poll path should say so and carry on.
#include "irq.h"
int pci_irq_attach(struct pci_dev *, irq_handler_t, void *arg,
const char *name, int flags);
void pci_irq_detach(struct pci_dev *, irq_handler_t, void *arg);
const char *pci_irq_kind(const struct pci_dev *);
void pci_describe(const struct pci_dev *, char *out, int cap);
struct pci_driver {

View file

@ -30,7 +30,9 @@
#define IRQ_COM1 4
#define IRQ_MOUSE 12
#define IRQ_IDE 14
#define IRQ_ERROR 19
// 30, not xv6's 19. Vector T_IRQ0+19 is IOAPIC pin 19, which real chipsets
// use for PCI; the LAPIC error interrupt would have shared it.
#define IRQ_ERROR 30
#define IRQ_SPURIOUS 31
// System call interrupt number for legacy int 0x40 or 0x80 if used