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
5.4 KiB
Adding a driver
The device model itself is described in ../architecture/devices.md. This is the procedure.
Where to get one from
The best source is the specification, not another kernel. xHCI, AHCI,
virtio, NVMe and the Intel datasheets are public documents with no licence on
what you write from them. kernel/drivers/bus/xhci/xhci.c came from the spec,
and so did net/e1000/ and gpu/virtio_gpu/.
BSD: yes. Linux: no. This is a licence incompatibility rather than a
preference, and it decides the whole strategy — see
../licensing.md. BSD-2 and BSD-3 code can be used directly
with a NOTICE entry; GPL-2.0-only code can never be, at any distance. Reading
Linux to understand hardware is fine and normal; translating a Linux driver
function by function produces a derivative work whatever the identifiers say.
One caveat about "port it from BSD" as a plan, learned by looking: FreeBSD's
sys/dev/usb is roughly 100k lines welded to newbus, devclass, bus_dma,
callouts, taskqueues and FreeBSD's locking. Porting it means porting FreeBSD's
device framework first and finding the USB stack somewhere underneath.
BSD is far more valuable as the reference you check yourself against than as the code you paste. Read it when the spec is ambiguous — which it is, often — and write your own.
Where a table of constants is genuinely faster to transcribe than to retype
from a PDF — device IDs, register offsets — taking it from a BSD tree is fine
after the licence check, and goes in NOTICE. That is data; the logic stays
ours.
The procedure
-
Write
kernel/drivers/<class>/<name>/<name>.cexporting astruct pci_driverorstruct usb_driver. One directory per driver, so a driver that grows a register header, a second source file or a firmware blob has somewhere to put it:kernel/drivers/ registry.c gpu/vga/ gpu/virtio_gpu/ net/e1000/ bus/xhci/ usb/core/ usb/hid/ usb/storage/ usb/hub/ -
Declare it in
kernel/drivers/registry.cand add it to the array. -
Add the object to
KERN_DRIVERSin theMakefile. -
Fill in the class vtable for what it is, and point
.opsat it. That is the whole of what the rest of the kernel sees:class vtable consumer DEVCLASS_DISPLAYstruct display_ops(display.h)the compositor DEVCLASS_NETstruct netdev_ops(netdev.h)the TCP/IP stack A display driver that owns a linear framebuffer also publishes it with
gfx_attach_scanout()(gfx.h). That is what the text console and the crash screen draw on, and it is separate from the display class on purpose: the class is about frames — acquire, present, vblank — and the crash screen has to work when nothing else in the system does.A driver that fills these in never mentions its consumer, and its consumer never mentions it. Getting this wrong has a characteristic shape: the e1000 shipped with
.ops = 0and the network stack callede1000_transmit()directly, sokernel/net/was full of one driver's name and the second NIC driver would have had to edit the protocol stack to add itself.
Private headers go next to the driver, not in include/. The build adds
the driver's own directory to the include path, so e1000.h — a register map
that exactly one file has any use for — lives in kernel/drivers/net/e1000/.
include/ is for headers the rest of the kernel needs.
Registration in the device model is automatic — pci_bind() and
usb_bind_all() publish whatever they claim, so a driver cannot forget to. Set
devclass on a USB driver, or the device lands in other.
A driver that can drive more than one card at once keeps its instances in
an array and implements .attached, which the bus calls once the struct device exists; hang the instance on dev->priv there and read it back in
every op. It cannot be done inside probe(), because the device is published
afterwards. Skipping this is how a driver ends up refusing the second card, as
the e1000 did until it was asked to drive two.
The best source for a new driver is the specification, not another kernel:
xHCI, AHCI, virtio, NVMe and the Intel datasheets are public documents with no
licence on what you write from them. kernel/drivers/bus/xhci/xhci.c came from the spec, and so did
net/e1000/ and gpu/virtio_gpu/. Where a table of constants is genuinely
faster to transcribe than to retype from a PDF -- device IDs, register offsets --
taking it from a BSD tree is fine after the licence check, and goes in NOTICE.
That is data; the logic stays ours.
Checklist
- One directory under
kernel/drivers/<class>/<name>/ - Exports a
struct pci_driverorstruct usb_driver - Declared in
kernel/drivers/registry.cand added to its array - Object added to
KERN_DRIVERSin theMakefile - Class vtable filled in and
.opspointed at it .attachedimplemented if it can drive more than one carddevclassset, if it is a USB driver- Private headers next to the driver, not in
include/ - A
NOTICEentry, if anything was copied — before the commit, not after - A selftest reachable from
/proc/sysrq, if the class has one (netdev_selftestis the model)