SchOS/docs/architecture/usb.md
Nexoniarz 2556a955b3 docs: architecture describes what is built now
kernel (threads, demand paging, TLB shootdowns, dynamic linking, RAM direct map), syscalls, filesystem (/proc, /sys,
/dev), terminals (12, VT handshake, ptys), networking (r8169 through LDLS), LDLS (module sets; Linux code may ship),
devices (what stays in the kernel; hybrid.md's lasting content), boot (the loader's placement of the kernel),
graphics (fbdev, GMMOS, QR 1-40), audio (ALSA over OSS), debugging, usb. New: packages.md (the apk root) and
desktop.md (Xorg on the framebuffer).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
2026-10-26 07:57:25 +01:00

141 lines
6.5 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 does not decide it: the BSD stacks are BSD-2/3-clause and compatible, and
since SchOS became GPL-2.0-or-later Linux's GPL-2.0-only drivers are compatible too
— see [../licensing.md](../licensing.md). The argument against porting is the
framework underneath, not the licence.
## What the spec gives you for free
Everything in [`include/dev/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. From then on the interrupt handler is the event ring's only reader:
command completions and endpoint-0 completions are filed per slot, and whoever
issued them is woken to collect them. That is what lets enumeration happen
again later, from a thread, while keyboards are interrupting.
Before reset the driver takes the controller from the firmware (USB Legacy
Support: OS Owned, wait for BIOS Owned to clear, SMIs off), and on Intel 7/8/9
series chipsets switches the ports over from the EHCI controllers, which is
where the firmware leaves them.
### Hot-plug
The `usbhotplug` kernel thread owns every enumeration after boot. It looks at
the root ports whenever the controller reports a port change and every quarter
second regardless, and at hubs whenever their status-change endpoint reports
one. A device that arrives is debounced for 100 ms and enumerated exactly as at
boot; one that leaves has its endpoints closed (failing any transfer in flight),
its drivers' `disconnect` called, its slot disabled and its memory freed, and
the same for everything behind it if it was a hub. A device that will not
enumerate is left alone until it is unplugged, not retried for ever.
The thread also drives key repeat for USB keyboards, which do not repeat by
themselves: 500 ms, then about 33 a second, as the PS/2 keyboard's firmware
does.
## Class drivers
| | |
|---|---|
| **HID** (`usb/hid/`) | Keyboards in boot protocol. Pointers in report protocol, from their report descriptor: buttons, X, Y and wheel, relative (mice) or absolute (tablets, VM pointers), with report IDs. A boot mouse whose descriptor cannot be used falls back to boot protocol. Feeds the same input paths as PS/2. |
| **Mass storage** (`usb/storage/`) | Bulk-only transport and SCSI. Up to four sticks, `usb0`..`usb3`, each registered with the block layer with its partitions; pulling one out unregisters it. |
| **Hub** (`usb/hub/`) | Route strings, hub slot contexts, per-port reset and power, and the status-change endpoint for hot-plug behind the hub. |
Class drivers are offered **every interface** of a device, each as its own
`usb_dev`, once the configuration has been read, from the list in
`kernel/drivers/registry.c` — a wireless receiver's keyboard and mouse are
interfaces 0 and 1 and get a driver each. 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
- **Pulling out a stick a filesystem is mounted from panics** on that
filesystem's next read, by the rule in `blkdev_rw()`: a filesystem that went on
with missing blocks would be guessing.
- **HID is keyboards and pointers.** Consumer-control interfaces (volume keys),
game pads and the rest are declined. Caps, Num and Scroll Lock light their LEDs
on USB and PS/2 keyboards, one state shared between keyboards.
- **USB 3 hubs** are not driven: the hub driver speaks the USB 2 hub protocol.
- **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.
- **One controller.** A second xHCI card would bind and then contend for state
the driver keeps per-driver rather than per-instance.