SchOS/.claude/memory/schunix-e1000-and-driver-layout.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

5.4 KiB

name description metadata
schunix-e1000-and-driver-layout The e1000 NIC driver, the drivers/ reorganisation by device class, and virtio-gpu as the QEMU default.
node_type type originSessionId modified
memory project 9d8a2aef-bcf0-47a0-96c6-f706efab4a95 2026-08-08T20:17:14.134Z

2026-08-08. Three changes, all requested by Nexoniarz in one go.

drivers///

kernel/drivers/
  registry.c            the one list of what exists
  gpu/vga/  gpu/virtio_gpu/
  net/e1000/
  bus/xhci/
  usb/core/  usb/hid/  usb/storage/  usb/hub/

One directory per driver, not per class (Nexoniarz asked for this specifically), so a driver that grows a register header, a second source file or a firmware blob has somewhere to put it.

Matches DEVCLASS_* in include/device.h. Flat was eight files with four called usb_something, and each driver added made it worse.

Makefile note: the compile rule is now @mkdir -p $(dir $@), not a fixed path — a hardcoded mkdir only creates the top directory and every subdirectory build fails with "unable to open output file". KERN_DRIVERS is built with += per class rather than one long $(addprefix ...); scripted edits kept collapsing the line continuations.

virtio-gpu is the QEMU default

QEMU_DISPLAY = -vga std -device virtio-gpu-pci in the Makefile, used by all three qemu targets. Keep -vga std — the bootloader draws the splash through VBE in 16-bit real mode and there is no virtio path that early, so a machine with only virtio-gpu boots blind until the kernel is up.

FreeBSD reference tree

Refrence/freebsd-src (spelled that way on disk) is a full FreeBSD checkout, re-cloned 2026-08-08 after the NetBSD one was deleted for space. Never built, never in an image; recorded in docs/layout.md and docs/licensing.md §4.

It is 2.1 GB — measured, and the same reason the NetBSD one was deleted. Only sys/dev, sys/net and sys/netinet have ever been read, so a sparse checkout (--filter=blob:none --sparse, then sparse-checkout set sys/dev sys/net sys/netinet) is ~100 MB and loses nothing. Single-file fetches from raw.githubusercontent.com answered every licence question so far and cost nothing at all.

sys/dev/e1000/ is Intel BSD-3-clause with zero advertising clauses — checked. Its device ID table was transcribed into include/e1000.h, taking the driver from binding one card (QEMU's 82540EM) to the whole 8254x family: 82540/41/44/45/46/47, ~27 IDs. That is the shape of adoption that works — data, not logic. The driver stays written from Intel manual 317453, and NOTICE carries the Intel attribution.

The PCIe 8257x parts (82571-82574) were deliberately excluded: similar register map, different NVM access and init. Claiming a device the driver cannot initialise is worse than not claiming it — it binds, reports a nonsense MAC, and fails for reasons that look nothing like the cause.

e1000

include/e1000.h + kernel/drivers/net/e1000.c, from Intel's public manual 317453. Worked on the first boot:

e1000: 52:54:0:12:34:56, link up, irq 11

Proven to carry real traffic, 2026-08-08: echo n > /proc/sysrq runs e1000_selftest(), which hand-builds an ARP request for 10.0.2.2 and waits for the reply. Under QEMU user-mode networking:

e1000: asking who has 10.0.2.2
e1000: 64 bytes from 52:55:a:0:2:2 type 806
e1000: ARP reply -- 10.0.2.2 is at 52:55:a:0:2:2
e1000: self-test passed, 1 tx 1 rx

That one exchange validates the whole path: transmit descriptor format, the DD handshake, TDT/RDT bookkeeping, DMA addresses, the receive-ring handback, and the MAC filter -- an ARP reply is unicast to us, so a wrong RAL/RAH means it never arrives. SLIRP is silent until spoken to, which is why the test has to transmit before it can expect to receive. Do this kind of check before layering a protocol stack on a driver: otherwise the stack's first failure is indistinguishable from its own bugs.

Run with -netdev user,id=n0 -device e1000,netdev=n0. Provides e1000_transmit(), e1000_receive() and the MAC — no IP stack yet, deliberately: a stack written before a driver has nothing to test against and every bug in it looks like a driver bug.

Details that are easy to get wrong and are commented in the source:

  • Set E1000_TXD_STAT_DD on every transmit descriptor at init. The hardware only sets it after using one, so a driver that waits for a bit the device has never had reason to write waits forever.
  • RDT starts at NRX-1, not NRX. One descriptor must always be held back or "ring full" and "ring empty" are indistinguishable.
  • Clear the multicast table. It is undefined after reset, so left alone the device accepts multicast for addresses nobody asked for.
  • Read the MAC from EEPROM and fall back to RAL/RAH. Some cards leave the EEPROM path unimplemented and you end up with all zeroes.
  • irq_register(..., level_low = 1) — PCI interrupts are level-triggered, active low. Edge-configured, it fires once then wedges.
  • Rings and buffers come from dma_alloc; the device reads them by physical address and kalloc pages are contiguous only by accident.
  • Enable the receiver after the ring base is set, or it DMAs into physical 0, which on this kernel is the boot page table.

ops is null on the pci_driver: there is no net class in the device model yet. Adding one is the natural first step when the IP stack arrives.