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
114 lines
5.4 KiB
Markdown
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/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_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.
|