SchOS/docs/architecture/usb.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

4.7 KiB

USB

An xHCI host controller driver, a device-independent core, and three class drivers. Written from the specification.


Why not ported from BSD

"Take the USB stack from BSD" is the obvious plan and it does not survive contact with the code.

FreeBSD's sys/dev/usb is roughly 100k lines welded to newbus, devclass, bus_dma, callouts, taskqueues and FreeBSD's locking primitives, none of which exist here. Porting it means porting FreeBSD's device framework first and then discovering the USB stack underneath. NetBSD's is more portable and the same argument applies with a smaller multiplier.

The specification is public and the controller is not complicated.

BSD is much more valuable as the reference you check yourself against than as the code you paste.

Licensing points the same way: the BSD stacks are BSD-2/3-clause and compatible, Linux's drivers are GPL-2.0-only and are not — see ../licensing.md.

What the spec gives you for free

Everything in include/usb.h comes from USB 2.0 chapter 9, which every device implements regardless of speed, class or vendor.

That is what makes enumeration possible at all: before you know anything about a device you can still ask it what it is, because the question is standardised.

The controller

kernel/drivers/bus/xhci/. The full bring-up path:

  reset → DCBAA → command ring → event ring → scratchpad
  → port enumeration → Enable Slot → Address Device → control transfers
  → configuration and interface descriptors → SET_CONFIGURATION
  → interrupt and bulk endpoints

Rings are producer rings: the driver writes TRBs and the controller reads them, with a Link TRB as the last entry pointing back at the start — which is how a fixed-size buffer becomes an endless queue. 64 TRBs per ring, 16 device slots.

Enumeration polls; data uses interrupts

And that is not laziness. pci_bind_all() runs from kmain() with interrupts disabled and no scheduler, so a driver that waited for an interrupt during probe would wait forever.

Interrupts are registered afterwards, for when a device is actually delivering data.

Class drivers

HID (usb/hid/) Boot protocol keyboard and mouse, driven from the IRQ handler, feeding the same input paths as PS/2.
Mass storage (usb/storage/) Bulk-only transport and SCSI, presenting a block device. A superblock reads back through bread().
Hub (usb/hub/) Route strings, hub slot contexts, per-port reset and power.

Class drivers are offered a device by the core once its interface descriptor has been read, from the list in kernel/drivers/registry.c. Whichever claims it publishes a struct device in the bus-agnostic list, with the class the driver chose rather than the one the descriptor claimed — the descriptor says what the device says it is, and the driver knows what it made of it.

What proves the hub works

A mouse behind a hub enumerating a tier deep. Hubs are the part of USB where route strings and slot contexts stop being theoretical, and a hub driver that handles only directly-attached devices looks identical to a working one until something is plugged into it.

The USB HID grab detail

The HID path updates its prev[] key state before returning even when the device is grabbed — otherwise the next report looks like every held key had just been pressed again. See graphics.md for the grab itself.

Testing

bootcheck -Usb attaches an xHCI controller with a keyboard, a mouse and a mass-storage device.

Nothing on the default machine has a USB port, so without that switch the driver is never asked to do anything and a broken one looks exactly like a working one.

echo u > /proc/sysrq and echo s > /proc/sysrq drive the storage path.


What it does not do

  • A USB disk cannot be mounted or booted from. The block device works and bread() reads a superblock off it; the root device is compiled in rather than selectable, so nothing can be told to use it. This is the largest single gap and it is item 1 of ../plan/roadmap.md.
  • Boot protocol only for HID. No report descriptor parsing, so anything that is not a plain keyboard or mouse is not understood.
  • No isochronous transfers, so no audio and no cameras.
  • No USB 3 SuperSpeed-specific features beyond what enumeration needs — the USB2 and USB3 descriptor paths both work, the rest of the difference is unused.
  • 16 device slots, compile-time.
  • No hot-unplug. A device removed is not noticed; the device model has no removal path either.
  • One controller. A second xHCI card would bind and then contend for state the driver keeps per-driver rather than per-instance.