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
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. |
|
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/dev/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_DDon 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.