Skip to content

usb_hid: honor a boot protocol request that arrives after startup - #11282

Open
mikeysklar wants to merge 2 commits into
adafruit:mainfrom
mikeysklar:fix-1136-boot-hid
Open

usb_hid: honor a boot protocol request that arrives after startup#11282
mikeysklar wants to merge 2 commits into
adafruit:mainfrom
mikeysklar:fix-1136-boot-hid

Conversation

@mikeysklar

@mikeysklar mikeysklar commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

What

In boot protocol the boot device sends without a report ID, and the other devices do not send.

Why

#1136, eight years old. Two defects, both putting a stray byte where the host reads modifiers.

1 The boot swap runs only at VM start, so a SET_PROTOCOL(boot) arriving later is missed and reports keep the report ID. The 01 lands in the modifier byte, a held left Ctrl
2 Fixing only the boot device left the others sending their own report IDs into that same byte. CONSUMER_CONTROL sent 03 01 00, read as Ctrl+Shift

Hardware tested

Adafruit Feather RP2040, host Ubuntu 26.04.1. Real SET_PROTOCOL(boot) over usbfs, GET_PROTOCOL read back. Three trials per build.

Single boot keyboard, armed IN transfer while the host is in boot protocol:

Baseline d897c15 9 bytes, 01 prefix
b697e83 8 bytes

Keyboard plus consumer control, same conditions:

keyboard consumer control
b697e83 8 bytes 3 bytes, 03 prefix, 20 per trial
2d2ccf2 8 bytes not sent, 0 per trial

Every arm returns to 9 bytes when report protocol is restored, so behavior tracks protocol.

Not tested: real BIOS, GRUB, KVM, Windows, non-RP2 ports, zephyr-cp.

Scope

Item Status
The host request Sent deliberately over usbfs. Linux usbhid keeps the interface in report protocol, so no shipping OS sends it unprompted
macOS pre-boot Compared at an M2 Startup Options screen against an earlier build of this branch that also dropped the report ID from the descriptor. Neither it nor baseline behaved differently, so those reports in #1136 have another cause
zephyr-cp Same defect, unchanged here. Happy to mirror it
Keyboard LED reports Still dropped in boot protocol with multiple devices. IN direction only
Dropped from this PR Report-ID-less descriptor variants, per @dhalbert above

AI assistance

Written with an LLM agent (Claude). I ran the boards myself.

Prompt Root-cause #1136 from the thread and source, fix it, verify on hardware with controls
Numbers above From the usbfs harness, not the model

@dhalbert

dhalbert commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

I worked on this a lot years ago, as you can tell. I am surprised you need a new report descriptor without the Report ID slot. Part of the point of boot keyboard and boot mouse is that the descriptor sent by the device is ignored. Instead the host assumes the standard descriptors, as described in https://www.usb.org/sites/default/files/hid1_12.pdf in the boot devices sections

So is this a work-around for something that Macs are not doing properly in their "BIOS"? I think the original code solved the issue on PC's.

@mikeysklar

Copy link
Copy Markdown
Collaborator Author

You're right about the descriptor. The bug is timing: the swap runs only at VM start, so a later SET_PROTOCOL is missed. Not Mac specific. I'll cut it to that.

@mikeysklar mikeysklar changed the title usb_hid: drop the report-ID prefix for a sole boot keyboard or mouse usb_hid: honor a boot protocol request that arrives after startup Sep 1, 2026
@mikeysklar

mikeysklar commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

Found a second bug in my own fix, fixed in 2d2ccf2. With two devices enabled only the boot
device lost its report ID, so the other one still put its ID in the host's modifier byte.
CONSUMER_CONTROL sent 03 01 00, read as Ctrl+Shift held.

Testing is a real SET_PROTOCOL(boot) over usbfs now, not simulated. Feather RP2040,
Ubuntu 26.04.1, three trials per build.

host in boot protocol keyboard consumer control
b697e83 8 bytes 3 bytes, 03 prefix
2d2ccf2 8 bytes not sent

Single boot keyboard, baseline d897c15 stays at 9 bytes where b697e83 drops to 8.

Not Mac specific. The original code does work on PCs, but only when the request lands
before code.py starts. This is the case where it arrives after.

mikeysklar and others added 2 commits September 2, 2026 19:22
usb_hid_setup_devices() swaps in the boot keyboard or mouse, whose report
ID is 0, but it only runs from usb_setup_with_vm() at VM start. A
SET_PROTOCOL(boot) arriving while code.py is already running is not acted
on until the next VM restart, so reports keep their report-ID prefix while
the host is reading them as 8-byte boot reports. That matches bitboy85's
report in adafruit#1136: get_boot_device() returns 1 yet a phantom left Ctrl is
held, because the 0x01 prefix lands in the modifier byte.

Check tud_hid_get_protocol() in send_report() instead, and drop the report
ID while the host has the interface in boot protocol.

Measured on a Metro RP2040, with the host request simulated by setting
TinyUSB's protocol_mode over SWD: before, reports stay 9 bytes after the
switch; after, they become 8 bytes on the next send. Default HID
configuration is unchanged.
In boot protocol the host reads the fixed 8-byte boot report and ignores
every other device on the interface. send_report() stripped the report ID
for the boot device but still sent the other devices' reports, so a second
enabled device put its report ID in the host's modifier byte. Enabling
KEYBOARD and CONSUMER_CONTROL together sent 03 01 00, read as Ctrl+Shift
held.

usb_hid_setup_devices() already replaces the device tuple when the host
asks before code.py starts. Do the same at send time for a request that
arrives later.

Also sample the protocol after the ready wait rather than before it.
hidd_reset() zeroes the interface and HID_PROTOCOL_BOOT is 0, so a report
starting during a bus reset could read BOOT, wait two seconds, then send
unprefixed into an interface that had re-enumerated back into report
protocol.

Measured on an Adafruit Feather RP2040, host Ubuntu 26.04.1, using a real
SET_PROTOCOL(boot) control transfer over usbfs. Consumer control reports
seen while the host held boot protocol: 20 per trial before, 0 after,
three trials each.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants