SchOS/include/irq.h
Nexoniarz 7e8d5e2821 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
2026-10-22 10:29:24 +02:00

60 lines
2.7 KiB
C

#pragma once
// Hardware interrupt registration.
//
// Until this existed, trap() was a switch statement listing every interrupt
// the kernel knew about, and adding a device meant editing the trap handler.
// That is fine while the devices are the four the PC has always had at fixed
// pins; it stops being fine the moment a device's IRQ is whatever the BIOS
// decided to put in its PCI config space this boot. A driver cannot write
// `case T_IRQ0 + IRQ_XHCI:` because there is no such constant -- it only
// learns its line at probe time.
//
// So: a line has a chain of handlers, a driver adds itself to one at probe
// time, and enabling the line is a consequence of registering rather than a
// separate call somebody can forget.
//
// Chains matter more than they look. PCI interrupt pins are *shared*: four
// INTx lines are routed to a handful of IOAPIC inputs, so two unrelated
// devices routinely land on the same one. A handler must therefore check
// whether its own device actually raised the interrupt and do nothing if it
// did not.
// 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);
// Returns 0, or -1 if the line is out of range, reserved, or full.
// Registering enables the line; the last unregister masks it again.
//
// `level_low` selects PCI's signalling (level-triggered, active low) rather
// than ISA's (edge-triggered, active high). Getting this wrong does not fail
// cleanly: an edge-configured level interrupt fires once and then wedges,
// because the device keeps the line asserted and no further edge ever comes.
int irq_register(int irq, irq_handler_t fn, void *arg, const char *name,
int level_low);
void irq_unregister(int irq, irq_handler_t fn, void *arg);
int irq_handler_count(int irq);
// Called from trap() with the IOAPIC pin number, not the vector.
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