The firmware's AML is now interpreted. uACPI 6.1.1 (MIT) is fetched by ports/uacpi, checksum-verified, and compiled into the kernel unmodified, the same way lwIP is. kernel/acpi/host.c is its host layer: memory, mapping, port and PCI config I/O, time, mutexes and events that spin before the scheduler exists, the SCI, and an "acpi" thread for the work the SCI defers. PCI interrupts go where the board says. pci_init() now routes every device's INTx through the root bridge's _PRT before drivers bind. It follows link devices' _CRS, uses a bridge's own _PRT where the namespace has one, and swizzles through bridges that don't. Before this, INTx waited on the 8259's line number, which is the wrong pin in APIC mode. irq_register() takes a trigger mode rather than a level/low flag, because q35's links and most SCIs are level, active-high. The MADT's interrupt source overrides are now parsed, and are what places the SCI. The power button syncs the filesystems and powers off. shutdown() and reboot() go through the firmware's S5 and reset register first, and only then fall back to the emulators' ports, which a real board does not answer. Tested under QEMU: q35 with MSI off routes xHCI to GSI 22, level/active-high, and it takes real interrupts with nothing polled. system_powerdown powers off q35 and i440fx (QEMU exits 0), and q35 with 4 CPUs boots from NVMe with USB hot-plug working alongside. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
70 lines
3.2 KiB
C
70 lines
3.2 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.
|
|
//
|
|
// It is a mode, not a flag. 0 and 1 are the two above, which is all a PCI or
|
|
// ISA device needs; the other two are what ACPI can say about a line -- q35's
|
|
// PCI links and most machines' SCI are level-triggered *active high*, and
|
|
// programming one active low leaves the pin reading as permanently asserted.
|
|
#define IRQ_EDGE_HIGH 0 // ISA
|
|
#define IRQ_LEVEL_LOW 1 // PCI INTx
|
|
#define IRQ_LEVEL_HIGH 2
|
|
#define IRQ_EDGE_LOW 3
|
|
#define IRQ_IS_LEVEL(m) ((m) == IRQ_LEVEL_LOW || (m) == IRQ_LEVEL_HIGH)
|
|
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
|