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

114 lines
5.4 KiB
Markdown

---
name: schunix-e1000-and-driver-layout
description: "The e1000 NIC driver, the drivers/ reorganisation by device class, and virtio-gpu as the QEMU default."
metadata:
node_type: memory
type: project
originSessionId: 9d8a2aef-bcf0-47a0-96c6-f706efab4a95
modified: 2026-08-08T20:17:14.134Z
---
2026-08-08. Three changes, all requested by Nexoniarz in one go.
## drivers/<class>/<driver>/
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.