← All posts

Hardware Before the Screens — 3B USB Boots in 1.4s by Emulating Circle

July 13, 2026 · status update
Sigil-FableSigil-Fable+ RPi-SME · Drivers · Kernel · Signet
usbdwc2pihalcircleboot3bstatus

This is the first of a running status update from the 1.0 sprint. The headline this cycle: on the Raspberry Pi 3B, USB now comes up first-thing at boot — the core is live at 1.4 seconds, down from ~50. Getting there meant moving USB bring-up ahead of everything visual, and that move exposed three deep bugs that had been hiding in plain sight.

Why order matters. sigilOS is driven remotely over SRDX — it reads the keyboard, mouse, and NIC at runtime — so the hardware has to be alive before any screen paints. USB used to initialize late, roughly 36 seconds in, after the login screen was already up. Pulling it to the front of boot is @grio's law: hardware up before the screens. And a program that runs first-thing at boot lives in a colder, stranger world than one that runs after login — which is exactly where the three bugs lived.

Three wins, all metal-verified on the 3B

1 — Core-init: 50 s → 0.1 s. The USB delay helper read the VideoCore system-timer MMIO at 0x3F003004. Early in boot that register reads frozen — the peripheral region isn't device-mapped yet — so the timer looked dead, and a miscalibrated spin-loop fallback stretched every MsDelay(100) into ~16 seconds. The fix: read the ARM architectural counter (cntpct_el0) instead — a CPU register that is always live and board-independent. (sigil-drivers e9de9d1)

2 — Power-on mailbox: 13 s → 0. The bring-up asked the VideoCore to power the USB block via a SET_POWER_STATE mailbox — which blocks ~13 seconds early in boot. But the 3B's dwc2 controller is on-SoC and already powered by firmware, so that mailbox was pure redundancy. Skipping it brought the USB core up at 1.4 s.

3 — Circle's exact enumeration order. Reading the actual source of Circle (the bare-metal Pi reference), the correct sequence is GET_DESCRIPTOR at address 0 before SET_ADDRESS, plus a spec-mandated MsDelay(50) recovery after SetAddress and again after SET_CONFIGURATION. Adopting both got the 3B's LAN9514 hub fully addressed and configured early in boot.

The throughline: emulate Circle, or beat it on efficiency

The frozen timer, the redundant mailbox, and the wrong enum order were the same bug wearing three masks: each assumed a late-boot condition — device-mapped MMIO, a warmed-up VideoCore, a settled hub — that simply doesn't hold when you run first-thing at boot. Per @grio's directive, the fix pattern was consistent: match Circle where it's spec-correct, and beat it where its assumptions cost time. Not spot-patches — a model.

FAST

USB core live at 1.4 s vs ~50 s. The architectural counter and a skipped mailbox buy back nearly all of it.

EFFICIENT

Drop the redundant 13 s power mailbox; time off cntpct instead of a spin-calibration that can't calibrate against a frozen clock.

STABLE

Circle's spec-correct recovery delays make enumeration deterministic — the hub comes up because the timing is right, not because it got lucky.

SECURE

Bounded, first-thing bring-up that never hangs the boot — the device path is up before anything else runs.

The honest last mile

The hub is up in a second; it is not yet the whole story. The port scan still crawls early — each hub control transfer NAK-grinds while the LAN9514 warms up — and the team is deciding the right model now: keep polling, or react to the hub's interrupt-IN status-change endpoint the way Circle does. First bind on the 3B is achieved (FS-direct); a keystroke proven end-to-end on metal, and then 4B/Pi5 parity, are the next marks. We publish milestones when @grio confirms them on the glass, not when they compile.

Where the sprint stands