115 lines
6.3 KiB
Markdown
115 lines
6.3 KiB
Markdown
# USB hubs (M22)
|
||
|
||
A hub is USB **bus infrastructure**, not an application peripheral, so hub
|
||
topology is handled **inside the `usb-xhci-bus` driver** — the process that owns
|
||
the controller's device slots and contexts. A device behind a hub is not reached
|
||
by any hub-specific software path: it is reached by the **controller**,
|
||
programmed with a *route string* in its slot context. Route strings and slot
|
||
contexts are xHCI hardware concepts that only exist inside the controller driver,
|
||
so that is where hub handling belongs. Class drivers (HID, storage) stay separate
|
||
and unaware — the hub is transparent to them; a keyboard behind a hub reaches the
|
||
same `usb-hid-keyboard` driver as one on a root port.
|
||
|
||
This is a deliberate scoping choice, not a microkernel compromise: the USB *bus*
|
||
driver handles USB *bus* topology. The alternative — a separate `usb-hub`
|
||
class-driver process plus a cross-process enumeration protocol — would only
|
||
shuttle the bus's own topology state (slot ids, route strings, TT linkage) out to
|
||
another process and back, since the hub driver cannot build a slot context
|
||
itself.
|
||
|
||
## The compound-hub reality
|
||
|
||
A USB 3.0 hub is physically **two hubs** sharing each connector: a SuperSpeed hub
|
||
and a USB 2.0 companion hub, enumerated as **separate devices on separate root
|
||
ports**. A full- or low-speed device plugged into a USB 3.0 hub attaches to the
|
||
**USB 2.0 companion**, not the SuperSpeed hub. So supporting full-speed devices
|
||
(keyboards, mice) behind a hub means driving the USB 2.0 companion and handling
|
||
**transaction translators** — there is no SuperSpeed-only shortcut that reaches a
|
||
full-speed keyboard.
|
||
|
||
## Slot-context fields for a downstream device
|
||
|
||
`buildAddressInputContext` fills the Slot Context from the device record: a
|
||
root-port device carries route 0 and its own root-hub port. A downstream device
|
||
additionally carries:
|
||
|
||
- **Route String** (Slot Context dword 0, bits 19:0) — 5 tiers × 4 bits, each
|
||
tier the downstream hub-port number. Composed as
|
||
`route = (parent_route << 4) | hub_port`, capped at the xHCI 5-tier max.
|
||
- **Root Hub Port Number** (dword 1, bits 23:16) — the *root* port the whole hub
|
||
chain hangs off, inherited from the parent hub (not the hub's own port number).
|
||
- **Speed** (dword 0, bits 23:20) — read from the hub's downstream port status
|
||
after reset, not assumed.
|
||
- **Parent Hub Slot ID** (dword 2, bits 7:0) + **Parent Port Number** (dword 2,
|
||
bits 13:8) — the **transaction translator**: set when a full/low-speed device
|
||
sits behind a high-speed hub, so the controller routes split transactions
|
||
through that hub's TT. For a multi-TT hub, **MTT** (Slot Context dword 0 bit
|
||
25) is set and the TT port is the device's own hub port.
|
||
|
||
## Detection: the status-change interrupt endpoint
|
||
|
||
A hub has one interrupt IN endpoint that returns a **port-status-change bitmap**
|
||
(bit N set = port N changed). The bus arms an interrupt transfer on it (reusing
|
||
the controller's existing interrupt-endpoint machinery, but serviced
|
||
**in-process** — no class-driver subscription IPC), and on each report:
|
||
|
||
1. For each changed port, `GET_STATUS` (hub class request) reads the port's
|
||
connect/enable/reset state and speed, and `CLEAR_FEATURE(C_PORT_*)`
|
||
acknowledges the change.
|
||
2. On a **connect**: `SET_FEATURE(PORT_RESET)`, wait for reset-complete via a
|
||
later status-change report, read the enabled speed, then `setupDevice` with
|
||
the composed route string / root port / TT fields, `enumerate`, and register
|
||
the interfaces — exactly the existing path, recursing if the new device is
|
||
itself a hub.
|
||
3. On a **disconnect**: tear down the downstream device (report each interface
|
||
`ChildRemoved`, Disable Slot) — the B3 teardown path, keyed by the device's
|
||
route rather than a root port.
|
||
|
||
## Hub setup (once, when the hub enumerates)
|
||
|
||
When the bus scan (or a hot-plug bring-up) finds a device of class 9:
|
||
|
||
1. Read the **hub descriptor** (class GET_DESCRIPTOR, type 0x2A for a USB 3.0
|
||
hub / 0x29 for USB 2.0) → downstream port count, characteristics.
|
||
2. For a USB 3.0 hub, `SET_FEATURE(BH_PORT_RESET)` semantics and the depth
|
||
(`SET_HUB_DEPTH`) so the hub knows its tier for route-string forwarding.
|
||
3. `SET_FEATURE(PORT_POWER)` each downstream port.
|
||
4. Configure the hub's slot as a hub: **Hub** bit (Slot Context dword 0 bit 26),
|
||
**Number of Ports** (dword 1, bits 31:24), **TT Think Time** and **MTT** for a
|
||
USB 2.0 multi-TT hub — via an Evaluate/Configure Endpoint on the hub's slot.
|
||
5. Arm the status-change interrupt endpoint.
|
||
|
||
## Testing
|
||
|
||
QEMU's `usb-hub` is a USB 2.0 single-TT hub. A **static boot topology** on a
|
||
dedicated second controller (`-device qemu-xhci,id=xhci2 -device
|
||
usb-hub,bus=xhci2.0,port=1 -device usb-kbd,bus=xhci2.0,port=1.1` — isolated from
|
||
the boot controller's auto-assigned devices, whose ports the hub would collide
|
||
with) presents the downstream device connected from the start, so the bus reads
|
||
it on the first status-change report — exercising the full path (hub setup, TT slot context,
|
||
downstream enumerate, class-driver bind) without needing a hot-plug event. A new
|
||
`usb-hub` QEMU case asserts the hub enumerates, the downstream keyboard
|
||
enumerates behind it, and `usb-hid-keyboard` binds.
|
||
|
||
Real-hardware validation (the user's SuperSpeed Genesys hub + full-speed
|
||
keyboard/mouse on its USB 2.0 companion) is flagged separately — the compound
|
||
USB 3.0 hub path is not modelled by QEMU's USB 2.0 hub.
|
||
|
||
## Milestones (all complete)
|
||
|
||
- **B4a** ✓ — hub recognition + setup: detect class 9 in the scan, read the hub
|
||
descriptor, configure the slot as a hub, power downstream ports, log the
|
||
topology.
|
||
- **B4b** ✓ — downstream enumeration: the in-process status-change subscription,
|
||
port reset, Address Device with route string + root port + TT fields,
|
||
enumerate + register. A full-speed keyboard behind a USB2 hub binds
|
||
`usb-hid-keyboard` in QEMU.
|
||
- **B4c** ✓ — disconnect teardown (recursive: a hub takes its subtree with it)
|
||
and hub-behind-hub recursion (route strings compose across tiers). QEMU's hub
|
||
*does* raise downstream status changes, so both connect and disconnect are
|
||
harness-tested (`usb-hub`, `usb-hub-nested`, `usb-hub-unplug`).
|
||
|
||
Real-hardware validation of the user's SuperSpeed Genesys hub with full-speed
|
||
devices on its USB 2.0 companion remains pending — QEMU's USB 2.0 hub does not
|
||
model the compound USB 3.0 hub.
|