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
60 lines
3.5 KiB
Markdown
60 lines
3.5 KiB
Markdown
---
|
|
name: schunix-driver-infrastructure
|
|
description: "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"
|
|
metadata:
|
|
node_type: memory
|
|
type: project
|
|
originSessionId: a665e14b-86bd-4b41-a228-51377f3e9225
|
|
modified: 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]].
|