SchOS/.claude/memory/schunix-e1000-and-driver-layout.md
Nexoniarz 57aff40228 docs: four directories, and a rule for when a thing leaves the roadmap
docs/ was ten files where a plan, a description, a guide and a bug report all
sat next to each other. It is now architecture/ (what SchOS is), plan/ (what it
will be), guides/ (how to work on it) and history/ (why it is like this), with
an index that states the one rule holding it together: when something is
finished it moves from plan/ to architecture/ and is rewritten as a description
of the thing rather than a record of building it.

A ticked checkbox says the work happened. It does not say what the work
*produced*, and six months later the checkbox is all that is left. The network
stack proved the point: finished, verified end to end, and for a month the only
description of it anywhere was a checkbox four levels of indentation deep.

So roadmap.md went from 534 lines to 181 -- nothing deleted, the done work
graduated. Seven subsystems that were finished and undescribed now have
descriptions: boot, kernel, syscalls, filesystem, networking, graphics, usb,
init. Every one ends with "What it does not do", which is the section that pays;
a document listing only strengths is marketing, and the reader who needs it most
is the one about to trip over a limitation nobody wrote down.

schfs.md still opened with "phases 1 through 3 1/2 are done, phase 4 is next"
long after all five had landed. It is a description now, with the phase record
and the hard-won bugs moved to history/.

Three corrections found on the way, all load-bearing:

  * projects/schcom/README.md claimed SCM_RIGHTS was done. There is no
    sendmsg/recvmsg in the kernel at all, and an entire GUI plan rested on it.
  * The root README said "No TCP/IP" months after the stack landed.
  * history/kernel.md sec.7 was marked "in progress" on a problem that process
    groups solved.

Also adds plan/1.0.md, which draws a finish line so the open list stops being
infinite, and marks schwm/ and schde/ superseded: in Wayland the compositor is
the window manager, so that split dissolves rather than being abandoned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-12 17:13:35 +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/guides/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.