usb: don't 'correct' SuperSpeed EP0 max packet size — it's an exponent

Regression from the B3 MPS0 work, caught on real hardware: a SuperSpeed
hub (and the SuperSpeed boot stick) failed to enumerate. bMaxPacketSize0
(device descriptor byte 7) is a LITERAL size for USB 2.0 and below
(8/16/32/64) but an EXPONENT for SuperSpeed (9 = 2^9 = 512). The refresh
read the exponent 9 as a size and issued Evaluate Context to set EP0 to
9 bytes, corrupting the control endpoint so every following transfer
failed. SuperSpeed's EP0 is fixed at 512 and needs no correction, so
the refresh is now skipped for speed >= 4; only full/low/high speed,
where the field is a literal that can differ from the speed default,
still run it. QEMU tolerated the wrong MPS0 — real silicon does not
(the class of bug the harness can't reach, flagged real-HW-pending).

This likely also explains intermittent no-storage/no-logs on a normal
boot: the boot stick is SuperSpeed and hit the same corruption. Full
suite green (orderly-shutdown's lone failure was its known QMP-timing
flake — three clean re-runs).
This commit is contained in:
Daniel Samson
2026-07-21 21:26:56 +01:00
parent 0ac07dadc9
commit 983b4ed05a
@@ -807,6 +807,15 @@ pub const Controller = struct {
/// MPS0; QEMU forgives it — the classic full-speed-mouse-on-real-hardware
/// failure (M20).
fn refreshMaxPacketSize0(self: *Controller, device: *Device) bool {
// SuperSpeed (and above) fix EP0's max packet size at 512, and encode
// bMaxPacketSize0 as an EXPONENT (9 = 2^9 = 512), not a literal size —
// the default is already correct and byte 7 must NOT be read as a size.
// Only full/low/high speed carry a literal 8/16/32/64 that can differ
// from the speed default and need this correction. (Reading the SS
// exponent as a size set EP0 to 9 bytes and broke every following
// transfer — a SuperSpeed hub failing to enumerate on real hardware.)
if (device.speed >= 4) return true;
var head: [8]u8 = undefined;
const request = usb_abi.getDescriptor(.device, 0, 0, 8);
if (!self.controlTransfer(device, request, head[0..], true)) return false;