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

116 lines
4.7 KiB
Markdown

# 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](../licensing.md).
## What the spec gives you for free
Everything in [`include/usb.h`](../../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](graphics.md#input) 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](../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.