# 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. Today it hardcodes route 0 and the root-hub port. A downstream device additionally needs: - **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** (`-device usb-hub,bus=xhci.0,id=h -device usb-kbd,bus=h.0`) 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.