[Solved] uConsole CM4: built-in keyboard dead, garbage USB descriptors (5/.3/,%) — root cause + software fix
TL;DR: If your uConsole keyboard doesn’t work and lsusb shows the keyboard with a
garbled name like 5/.3/,% and wMaxPacketSize 0, your mainboard USB hub chip is likely
defective: bit 6 of every byte passing through its Transaction Translator is stuck at 0.
The keyboard itself is fine. There is a software workaround (below) that forces the USB
host port to Full Speed and bypasses the broken block entirely — my keyboard and trackball
work perfectly now. The proper fix is a mainboard replacement (warranty).
Symptoms
- Built-in keyboard and trackball completely dead, external USB-A port misbehaving.
- The keyboard is detected, but with corrupted descriptors:
usb 1-1.1: config 1 has an invalid descriptor of length 8, skipping remainder of the config
usb 1-1.1: config 1 has 1 interface, different from the descriptor's value: 3
usb 1-1.1: config 1 interface 0 altsetting 0 endpoint 0x81 has invalid wMaxPacketSize 0
usb 1-1.1: Product: 5\x03/.3/,% <- should be "uConsole"
usb 1-1.1: Manufacturer: \x03,/#+7/2+\x10 <- should be "ClockworkPI"
- The same keyboard connected directly to a PC enumerates perfectly — so the keyboard
and its firmware are not the problem.
Root cause
Comparing raw descriptor dumps (uConsole vs. a known-good read of the same keyboard on a
PC), every single byte is ANDed with 0xBF — bit 6 (0x40) always reads as zero:
| Field | Good | On uConsole |
|---|---|---|
bMaxPacketSize0 |
0x40 (64) |
0x00 |
wTotalLength |
0x0064 |
0x0024 |
bmAttributes |
0xC0 |
0x80 |
wDescriptorLength |
0xDB (219) |
0x9B (155) |
"uConsole" |
75 43 6F 6E 73 6F 6C 65 |
35 03 2F 2E 33 2F 2C 25 = 5␃/.3/,% |
(The serial number 20230713 survives untouched — ASCII digits have no bit 6.)
Why this points at the hub chip (Genesys 05e3:0608) and nothing else:
- Not the keyboard — it’s perfect when plugged into a PC directly.
- Not the wire/connector — USB packets carry a CRC16; a bit flipped on the wire makes
the packet fail CRC and get retried, it can never be silently delivered. Corrupted data
arriving as valid packets means the corruption happens inside a store-and-forward
buffer, between one CRC check and the next CRC generation. - Not the CM4 or the driver — the BCM2711 has two independent USB2 host controllers
on the same port: dwc2 (default) and xHCI (otg_mode=1). I tested both; the corruption
is byte-for-byte identical under both. Two different controllers with different FIFOs
and DMA engines can’t share a single-bit defect. - That leaves exactly one component: the hub’s Transaction Translator. When the hub
uplink runs at High Speed (480M) and the keyboard at Full Speed (12M), every FS packet
is CRC-checked by the hub, stored in the hub’s internal SRAM, and re-sent upstream with
a recomputed CRC. A stuck-low bit in that SRAM corrupts the data invisibly and
deterministically — which is exactly what the dumps show. Notably, the hub’s own
descriptors read fine (they don’t go through the TT), and only FS/LS traffic is broken.
So: defective silicon in the hub chip. Reflowing won’t help (the fault is inside the die,
the external pins only carry serial USB where a positional bit error is impossible).
The workaround
The TT is only in the data path when the hub uplink runs at High Speed. Force the host
port to Full Speed and the hub becomes a dumb USB 1.1-style repeater — the broken SRAM
is bypassed completely.
The dwc2 driver has no config option for this in host mode (the maximum-speed DT
property is ignored, unlike dwc3, and the driver is built into the kernel), but the
controller itself supports it: bit 2 (FSLSSUPP) of the HCFG register at 0xFE980400.
Set the bit, reset the port, and the hub renegotiates at 12 Mbps.
Two files make it permanent and self-healing (re-applies on boot, resume, replug):
/usr/local/sbin/dwc2-force-fs.py (mode 755):
#!/usr/bin/env python3
# uConsole workaround: the mainboard USB hub (05e3:0608) has a stuck-low
# bit 6 in its Transaction Translator SRAM, corrupting all FS/LS traffic
# when the hub uplink runs at High Speed. Forcing the dwc2 host port to
# Full Speed (HCFG.FSLSSUPP) bypasses the TT entirely.
import mmap, os, fcntl, struct
DWC2_BASE = 0xFE980000
HCFG_OFF = 0x400
FSLSSUPP = 0x4
try:
with open("/sys/bus/usb/devices/1-1/speed") as f:
if f.read().strip() != "480":
raise SystemExit(0) # already full-speed, nothing to do
fd = os.open("/dev/mem", os.O_RDWR | os.O_SYNC)
m = mmap.mmap(fd, 0x1000, offset=DWC2_BASE)
hcfg = struct.unpack_from("<I", m, HCFG_OFF)[0]
if not hcfg & FSLSSUPP:
struct.pack_into("<I", m, HCFG_OFF, hcfg | FSLSSUPP)
with open("/sys/bus/usb/devices/1-1/devnum") as f:
dev = int(f.read())
h = os.open("/dev/bus/usb/001/%03d" % dev, os.O_WRONLY)
fcntl.ioctl(h, 0x5514, 0) # USBDEVFS_RESET -> renegotiate at FS
os.close(h)
except FileNotFoundError:
pass
/etc/udev/rules.d/99-dwc2-force-fs.rules:
# When the built-in hub enumerates at High Speed, force the port to Full Speed
ACTION=="add", SUBSYSTEM=="usb", KERNEL=="1-1", ATTR{speed}=="480", RUN+="/usr/bin/python3 /usr/local/sbin/dwc2-force-fs.py"
Then sudo udevadm control --reload and either reboot or run the script once by hand
(sudo python3 /usr/local/sbin/dwc2-force-fs.py).
Verify: lsusb -t should show the hub at 12M, and the keyboard should identify as
uConsole again:
/: Bus 001.Port 001: Dev 001, Class=root_hub, Driver=dwc2/1p, 480M
|__ Port 001: Dev 006, If 0, Class=Hub, Driver=hub/4p, 12M
|__ Port 001: Dev 007, If 0, Class=HID, Driver=usbhid, 12M
usb 1-1.1: Product: uConsole
usb 1-1.1: Manufacturer: ClockworkPI
Caveats
- The whole internal USB bus drops to 12 Mbps. The keyboard/trackball don’t care (they’re
Full Speed anyway), but the external USB-A port and the 4G module slot are capped at
~1 MB/s. Fine for input devices and modems, slow for flash drives. - Register address
0xFE980000is BCM2711-specific (CM4). Other core modules (A06, R01,
CM5) have different USB controllers — this exact workaround won’t apply, though the
same diagnosis method (compare descriptor dumps, look for a bit pattern) will. - If you have this defect, a High-Speed device in the USB-A port probably works while any
FS/LS device (mouse/keyboard) shows garbage — HS traffic bypasses the TT. That’s a
quick way to confirm the diagnosis without a second computer. - This is a workaround, not a repair — I’d still recommend contacting ClockworkPI support
(cs@clockworkpi.com) for a mainboard replacement if you’re in warranty. That’s what I’m
doing; the workaround keeps the device usable meanwhile.
Hope this saves someone the week of debugging it cost me. Happy to answer questions or
share the full analysis write-up.
(Diagnosed with the help of Claude over SSH — the giveaway was noticing that the garbage
string 5/.3/,% is exactly uConsole with bit 6 cleared in every byte.)