README: rewritten around what runs (Linux software, Xorg, apk, Linux's drivers), the honest gaps, measured numbers. plan/: 1.0.md is the finish line, where each clause of the acceptance test stands, and the register (one failing clause: a root that survives a reboot); built plans (foundations, hybrid, alpine, xorg) and the decided-against one (wayland) are gone, their content lives in architecture/; roadmap.md is only what is still open. guides: building/layout/testing/adding-a-driver (Linux code may be used). history: four entries (ls -lR, TLB shootdowns, MDIO 0x02460246, netlink MSG_TRUNC). Website: cards, stack, download page, docs index. Code comments that pointed at deleted plan docs point at the architecture docs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
6.1 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/.
Three sources, in the order to try them:
- A native driver, written from the specification. The default, and what every driver in the kernel is.
- Linux's own driver, unmodified, through LDLS (ldls.md), when the device is one Linux supports and SchOS cannot afford to write a driver for — a NIC with a PHY behind it, a GPU, a Wi-Fi card. It runs in a process, so a crash costs the interface and not the machine. This is how the laptop's Realtek NIC is driven.
- BSD or Linux code taken into the tree. Licensing no longer forbids either: SchOS is
GPL-2.0-or-later, so GPL-2.0-only code combines with it, and BSD-2/3 code can be used directly with
a
NOTICEentry — see ../licensing.md. The reason to prefer 1 and 2 is maintenance, not law: a driver copied in is a driver that has to be tracked.
Reading another kernel to understand hardware is fine and normal.
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/ net/rtl8139/ net/r8169/ storage/ahci/ storage/nvme/ audio/ac97.c 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)none today — Xorg draws through /dev/fb0DEVCLASS_NETstruct netdev_ops(netdev.h)the TCP/IP stack DEVCLASS_AUDIOstruct audio_ops/dev/dspA 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)