SchOS/docs/guides/adding-a-driver.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

112 lines
5.4 KiB
Markdown

# Adding a driver
The device model itself is described in
[../architecture/devices.md](../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](../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
1. Write `kernel/drivers/<class>/<name>/<name>.c` exporting a
`struct pci_driver` or `struct 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/
2. Declare it in `kernel/drivers/registry.c` and add it to the array.
3. Add the object to `KERN_DRIVERS` in the `Makefile`.
4. Fill in the **class vtable** for what it is, and point `.ops` at it. That is
the whole of what the rest of the kernel sees:
| class | vtable | consumer |
|---|---|---|
| `DEVCLASS_DISPLAY` | `struct display_ops` ([display.h](../../include/display.h)) | the compositor |
| `DEVCLASS_NET` | `struct netdev_ops` ([netdev.h](../../include/netdev.h)) | the TCP/IP stack |
A display driver that owns a linear framebuffer also publishes it with
`gfx_attach_scanout()` ([gfx.h](../../include/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 = 0` and the network stack called `e1000_transmit()`
directly, so `kernel/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_driver` or `struct usb_driver`
- [ ] Declared in `kernel/drivers/registry.c` **and** added to its array
- [ ] Object added to `KERN_DRIVERS` in the `Makefile`
- [ ] Class vtable filled in and `.ops` pointed at it
- [ ] `.attached` implemented if it can drive more than one card
- [ ] `devclass` set, if it is a USB driver
- [ ] Private headers next to the driver, not in `include/`
- [ ] A `NOTICE` entry, if anything was copied — **before** the commit, not after
- [ ] A selftest reachable from `/proc/sysrq`, if the class has one
(`netdev_selftest` is the model)