usb_hid: honor a boot protocol request that arrives after startup - #11282
usb_hid: honor a boot protocol request that arrives after startup#11282mikeysklar wants to merge 3 commits into
Conversation
dd114ae to
cd80fd8
Compare
|
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. |
|
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. |
cd80fd8 to
8861fe9
Compare
|
Found a second bug in my own fix, fixed in 2d2ccf2. With two devices enabled only the boot Testing is a real
Single boot keyboard, baseline Not Mac specific. The original code does work on PCs, but only when the request lands |
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>
5806ac3 to
2d2ccf2
Compare
With boot_device=0 there is no boot device to match, so a boot protocol request would have silently dropped every report. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Yes. Tested the default HID devices (no boot_device) on a Feather RP2040 under Ubuntu. Reports are identical before and after the change, in both report protocol and after the host sends SET_PROTOCOL(boot). No regression. Also added a small guard so the new code only runs when a boot device is enabled, in 3b30ce4. |
`python-version: 3.x` resolves to 3.14 now. Many of the Zephyr build requirements have no 3.14 wheels yet, so `west packages pip --install` builds 16 of them from source (grpcio_tools, ruamel.yaml.clib, esptool, docopt, ...) in every zephyr-cp board job: 303 s, against 33 s for the same command in the zephyr-tests job, which already pins 3.13. Pin 3.13 for the zephyr-cp port only; the other ports install their Python dependencies from the cached venv and are left on 3.x. Measured on the Build CI run of adafruit#11282 (job "ports (zephyr-cp) / board (nordic_nrf54l15dk)" against "zephyr-tests / zephyr"): 29 zephyr-cp board jobs per wide PR, about 4.5 minutes each.
What
In boot protocol the boot device sends without a report ID, and other devices do not send.
Why
usb_hid_setup_devices()swaps in the boot device only at VM start, so a laterSET_PROTOCOL(boot)is missed.Reports keep their report ID, and a boot-protocol host reads that byte as modifiers.
A phantom modifier results:
01reads as left Ctrl,03fromCONSUMER_CONTROLas Ctrl+Shift. Fixes #1136.Hardware tested
Feather RP2040. Real
SET_PROTOCOL(boot)over usbfs on Ubuntu 26.04.1, three trials per build.d897c1501prefix03prefixReport protocol is byte-identical before and after, and the default configuration still enumerates and runs.
Also checked at a macOS pre-boot screen on an M2, same either way, so those reports have another cause.
Not tested against a real BIOS, GRUB or KVM.
AI assistance
Written with an LLM agent (Claude). I ran the boards myself; numbers are from the usbfs harness.