Skip to content

Escalate USB recovery when keyboard HID writes time out - #1516

Draft
adamshiervani wants to merge 1 commit into
devfrom
fix/hidg0-write-timeout-recovery
Draft

Escalate USB recovery when keyboard HID writes time out#1516
adamshiervani wants to merge 1 commit into
devfrom
fix/hidg0-write-timeout-recovery

Conversation

@adamshiervani

@adamshiervani adamshiervani commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #1512

After a UDC rebind, the DWC3 race can leave /dev/hidg0 openable but dead: every write times out while the UDC still reports configured. Recovery verifies health by reopening the chardev — which succeeds in that state — and writeWithTimeout swallows deadline errors (host-suspend tolerance), so the existing escalation to a full gadget reconfigure never fires. Keyboard input is silently dropped (mouse fine, state configured) until identifier profiles are cycled by hand.

Changes

  • The recovery ladder verifies with a write probe, not just a reopen: re-send the current keys-down state (a no-op for the host) with the timeout surfaced. Probe failing while configured → escalate to the full reconfigure, so recovery completes before the user types.
  • Consecutive keyboard write timeouts are counted; the streak resets on success, on recovery, and outside configured (where timeouts are expected, e.g. host suspend). 3 in a row while configured → full reconfigure, rate-limited to 30s. Covers breakage with no preceding detach.

Testing

  • Unit tests for probe, streak accounting, and escalation policy (stalled endpoint simulated with a full pipe).
  • New ra-hid-write-recovery e2e reproduces the bug deterministically: unbind usbhid from the gadget keyboard interface on the host → gadget writes time out while configured. Fails on dev (keyboard dead for the full 120s window), passes here (recovery ~2s after the first timeout, confirmed by re-enumeration in host dmesg).
  • On device: a UDC unbind whose rebind left hidg0 unusable converges via ladder → reconfigure in one extra re-enumeration — no input needed, no reconfigure loop after.

After a UDC rebind, the RV1106 DWC3 race can leave /dev/hidg0 openable
but non-functional: every write times out while the UDC still reports
"configured". The rebind recovery path verified health by reopening the
chardev — which succeeds in this state — and writeWithTimeout swallows
the deadline errors (host-suspend tolerance), so nothing ever escalated
to the full gadget reconfigure that actually fixes it. Keyboard input
was silently dropped until the user manually cycled identifier profiles
(#1512).

Two complementary mechanisms:

1. Write-probe verification in the recovery ladder: after reopening the
   keyboard HID file, re-send the current keys-down state (a no-op for
   the host) and, unlike the regular report path, surface a timeout.
   While the UDC reports "configured", a reopen that probes broken is
   not success — the ladder escalates to the full reconfigure instead
   of declaring victory, fixing the gadget before the user ever types.

2. Runtime write-timeout streak: consecutive keyboard write timeouts
   are counted (reset on success, on recovery, and while the state is
   not "configured", where timeouts are expected — e.g. host suspend).
   When the streak reaches 3 while "configured", recovery runs the full
   reconfigure directly, rate-limited to one attempt per 30s. This
   catches breakage that occurs without a preceding detach event.

Fixes #1512
@BenDManning

Copy link
Copy Markdown

Exact stacked candidate is now available in #1554 at 52ed0f0b0a9a5487c6767d32352843aa217c607d, based directly on this PR commit 798896ec4a0e2d0d1639af0f2e8d89ff935e1dbf.

It keeps the recovery ladder here, but makes acknowledgement synchronous: non-configured USB state, temporary HID errors, the first timeout, short writes, and disabled mouse endpoints all return errors. The timeout gate covers keyboard, absolute mouse, relative mouse, and wheel over both mouse endpoints. Failed keyboard reports do not advance the state used by the recovery probe, so an errored report is not replayed later as if it had succeeded.

Deterministic pipe-stall tests cover first-failure and successful exact-byte delivery for every report path. Upstream Build and test, Go, UI, and CLA checks all pass at the exact head. No firmware was installed or flashed.

@BenDManning

Copy link
Copy Markdown

Post-review candidate supersedes the earlier 52ed0f0 checkpoint: exact head is now 2408a67f23a69f04c2758588f01ee836bf7222fb in #1554. The only follow-up changes consolidate the endpoint test catalog, reject disabled keyboard/keypress endpoints consistently with disabled mouse endpoints, and colocate the all-endpoint timeout-streak helper. Independent Standards and Spec re-reviews report zero findings. Build/test, Go, UI, and CLA checks all pass at the new exact head. No firmware was installed or flashed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Keyboard HID file not ready after UDC rebind - automatic recovery fails, manual identifier cycling required

2 participants