SchOS/docs/guides/adding-a-driver.md
Nexoniarz cb3d28e3d6 docs: README, plan, guides and the site describe what is built now
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
2026-10-26 08:08:05 +01:00

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:

  1. A native driver, written from the specification. The default, and what every driver in the kernel is.
  2. 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.
  3. 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 NOTICE entry — 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

  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/  net/rtl8139/  net/r8169/
      storage/ahci/  storage/nvme/
      audio/ac97.c
      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) none today — Xorg draws through /dev/fb0
    DEVCLASS_NET struct netdev_ops (netdev.h) the TCP/IP stack
    DEVCLASS_AUDIO struct audio_ops /dev/dsp

    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 = 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)