An ASUS TUF A15 booted with its internal keyboard dead and a USB one fine. These AMD laptops' firmware describes IRQ 1's polarity wrongly -- Linux keeps a quirk list -- and an edge line programmed the wrong way up never fires: the controller holds its byte, and only the interrupt that never comes would read it. irq_watch() lets a driver say how to ask its device whether something is waiting. The poller asks every tick; 50 ms of waiting with nothing delivered turns the line's polarity over and drains the device, which re-arms the edge, and if it still says nothing the line is polled. Both are announced. The keyboard and the PS/2 mouse are watched. ISA lines now also take their mode from the MADT's override entries, which were parsed and never read. Tested in QEMU, which ignores polarity on edge lines, with the keyboard line masked: the first key turns it over and typing works; masked again after the flip, it is polled and typing still works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
83 lines
4 KiB
C
83 lines
4 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)
|
|
// OR'd into the mode by a handler whose line is *meant* to be silent: the
|
|
// ACPI SCI fires for a power button or a lid, and on a desktop that is never.
|
|
// Without it the poller below took two seconds of silence as a misrouted line
|
|
// and called the handler every tick for the rest of the boot. A line is only
|
|
// exempt while every handler on it says so -- a PCI device sharing the SCI's
|
|
// pin still gets polled.
|
|
#define IRQ_QUIET 0x10
|
|
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);
|
|
|
|
// For a device that can say it has something waiting: `waiting` reads its
|
|
// status and returns nonzero when an interrupt should have come. A line whose
|
|
// device keeps saying so while nothing arrives has its polarity turned over,
|
|
// and is polled if that does not help either. See irq_poll_thread().
|
|
void irq_watch(int irq, int (*waiting)(void));
|
|
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
|