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
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.