SchOS/.claude/memory/schunix-driver-infrastructure.md
Nexoniarz 3182da3965 Carry project memory in the repo, with tools/memory-sync.sh
Claude Code keeps per-project memory at <home>/.claude/projects/<key>/,
where <key> is the project's absolute path with every separator and the
drive colon turned into a dash. That makes it both rename-fragile and
machine-specific, and this project has hit each of those once:

  - D:\DEV\Schunix -> D:\DEV\SchOS changed the key, and 43 memories were
    invisible until moved across by hand.
  - Development happens from Windows and from Linux, and each looks in a
    different place for the same checkout.

Committing .claude/memory/ fixes both -- git carries it across renames
and between hosts -- and `import` puts it wherever the local machine
expects. .claude/settings.local.json stays ignored: those are per-machine
permissions.

The colon maps to a dash rather than being dropped. Deleting it gives
D-DEV-SchOS, which matches nothing and silently finds zero memories; that
was the first attempt. On Git Bash the key comes from `cygpath -w`,
because Claude Code is a Windows program and sees D:\DEV\SchOS, not
/d/DEV/SchOS.
2026-08-09 14:27:22 +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/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/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.