SchOS/include/core/irq.h
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

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