SchOS/.claude/memory/schunix-driver-infrastructure.md
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

3.5 KiB

name description metadata
schunix-driver-infrastructure Dynamic IRQ registration, a DMA arena and PCI capabilities exist now; PCI interrupts are level/active-low and the old ioapicenable only did edge/high
node_type type originSessionId modified
memory project a665e14b-86bd-4b41-a228-51377f3e9225 2026-08-04T22:21:01.983Z

Built 2026-08-04, before USB, because xHCI, any NIC and virtio-gpu all need the same three things and none of them existed.

  • kernel/dev/irq.c + include/core/irq.h — irq_register(irq, fn, arg, name, level_low). trap.c used to be a switch listing every interrupt by name, which cannot work for a PCI device: its line comes out of config space at probe time, so there is no constant to write a case label for. Chains of up to 4 handlers per line, because PCI INTx pins are shared and a handler must recognise that its own device is idle and return. Registration publishes an entry then bumps n with a barrier, so dispatch needs no lock. /proc/interrupts (minor 12) shows counts and trigger mode.
  • kernel/core/dma.c + include/core/dma.h — dma_alloc(size, align) / dma_free(p, size) (Linux's contract: caller remembers the size). Carved off the top of physical memory in main.c before kinit1, because kalloc's free list makes no contiguity promise. x86 PCI is cache coherent so no PAT games are needed — but ordering is still your problem.
  • pci_find_cap() and pci_msi_enable() in kernel/dev/pci.c.

Four traps worth remembering:

  1. PCI interrupts are level-triggered, active low. ioapicenable() hardcoded edge/active-high, which is right for every ISA-era device and wrong for every PCI one — and it fails silently: you get exactly one interrupt, then the device holds the line asserted, no further edge ever comes, and it is never heard from again. ioapicroute(irq, cpu, level_low) is the real call now.
  2. IRQ_ERROR (19) and IRQ_SPURIOUS (31) are LAPIC vectors that xv6 numbered as if they were IOAPIC pins, so T_IRQ0+19 is already taken. A PCI device routed to pin 19 would collide. irq_register refuses them.
  3. cprintf has no field widths. %04x is not a directive to it: it prints % and 0 literally, consumes no argument, and every argument after it lands in the wrong place. It understands only %d %u %l %x %p %b %s %%.
  4. Unaligned MMIO reads return garbage. Reading a uint32 at BAR offset 2 to get xHCI's HCIVERSION returned zero. Read the aligned dword and shift.
  5. Never let a device raise an interrupt before its line is claimed. This one cost real time. xHCI transfers completed, completion events sat in the event ring, USBSTS.EINT and IMAN.IP were both set -- and the interrupt count stayed at zero. Cause: the controller asserted INTx during enumeration, while the IOAPIC entry was still masked. An interrupt that arrives at a masked pin is dropped, not deferred (the 82093AA manual says so, and QEMU implements it), and INTx is a level -- so the line then sits high forever with no further transition for anyone to notice. Unmasking afterwards finds nothing pending. The fix is ordering: enumerate with controller interrupts off, then irq_register, then clear the device's pending status so it drives the line low, and only then enable. The next event is a genuine low-to-high transition.

Verify with pwsh -File tools/bootcheck.ps1 -Usb. See schunix-usb-xhci, schunix-pci-drivers.