Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make USBHIDKeyboard work at boot #6964

Merged
merged 23 commits into from
Nov 30, 2023
Merged

Conversation

RefactorFactory
Copy link
Contributor

Add a boot_protocol parameter to the USBHIDKeyboard constructor. When true:

  1. The USB interface descriptor will have an interface subclass of boot and
    and interface protocol of keyboard. This will cause some PC BIOS to send a
    a SET PROTOCOL BOOT request to the device.

  2. The USB report descriptor will not have a report ID because boot protocol
    does not use report IDs.

  3. When the host sends reports to the device, USBHIDKeyboard will accept a
    report ID of 0 instead of checking for HID_REPORT_ID_KEYBOARD(1).

  4. When the device sends reports to the host, it will not send a report ID
    because boot protocol does not use report IDs.

  5. Use endpoint address of 1 for input and output because some PC BIOS require
    it.

By completing this PR sufficiently, you help us to review this Pull Request quicker and also help improve the quality of Release Notes

Checklist

  1. Please provide specific title of the PR describing the change, including the component name (eg. „Update of Documentation link on Readme.md“)
  2. Please provide related links (eg. Issue which will be closed by this Pull Request)
  3. Please update relevant Documentation if applicable
  4. Please check Contributing guide

This entire section above can be deleted if all items are checked.


Description of Change

USBHIDKeyboard didn't work at boot on my devices, so I've prototyped changes that make it work. I'm looking for feedback.

This adds a parameter to the USBHIDKeyboard constructor and when it is true, behavior is changed. I considered removing the parameter and changing the behavior all the time, but it seemed too risky.

This also adds a new parameter to the USBHID constructor, but it defaults to the old behavior.

I considered only using the new behavior if the host sends the SET PROTOCOL BOOT request, but it turns out that some BIOS do not send that request.

It is strange that some PC BIOS require specific endpoint addresses of 1 for input and output. I tried to do the reservation of the endpoint addresses similar to how USBCDC does it.

Some of the code seems ugly, so I'm looking for feedback.

Tests scenarios

I tested this with Arduino-esp32 core v2.0.4 with an ESP32 S2 board, with one old laptop and one new desktop, both from major PC manufacturers.

Related links

@CLAassistant
Copy link

CLAassistant commented Jul 10, 2022

CLA assistant check
All committers have signed the CLA.

@@ -661,6 +665,13 @@ esp_err_t tinyusb_enable_interface(tinyusb_interface_t interface, uint16_t descr
log_e("Interface %s invalid or already enabled", (interface >= USB_INTERFACE_MAX)?"":tinyusb_interface_names[interface]);
return ESP_FAIL;
}
if(interface == USB_INTERFACE_HID && reserve_endpoints){
// Some simple PC BIOS requires specific endpoint addresses for keyboard at boot
if(!tinyusb_reserve_out_endpoint(1) ||!tinyusb_reserve_in_endpoint(1)){
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this reservation of endpoints IN 1, OUT 1, USBCDC will reserve OUT 3, IN 4, IN 5 (line 665), then when USBHID.cpp:tusb_hid_load_descriptor() calls tinyusb_get_free_in_endpoint()/tinyusb_get_free_out_endpoint(), those functions will return IN 3, OUT 4 because those functions prefer returning endpoints whose opposite is already used.

Some alternatives to calling tinyusb_reserve_out_endpoint()/tinyusb_reserve_in_endpoint() here to reserve IN 1, OUT 1:

  1. USBHID.cpp:tusb_hid_load_descriptor() could call tinyusb_get_free_duplex_endpoint() which would likely get endpoints IN 1, OUT 1 because USBHID.cpp:tusb_hid_load_descriptor() runs early enough.
  2. Change tinyusb_get_free_in_endpoint()/tinyusb_get_free_out_endpoint() not to prefer returning endpoints whose opposite is already used. Or add a flag parameter.

@RefactorFactory
Copy link
Contributor Author

Because USBHIDKeyboard is simple, these changes could be greatly simplified by doing some or all of the following:

  1. Remove all the new flags and new parameters.
  2. In the USB interface descriptor, always use an interface subclass of boot protocol.
  3. Never use a report ID in the USB report descriptor or when receiving or sending reports.
  4. Always use endpoints 1 IN, 1 OUT for USBHIDKeyboard.

I'm looking for feedback on whether some of these simplifications should be done.

@RefactorFactory
Copy link
Contributor Author

Any feedback on what I can do to get these changes in? I'm open to suggestions. Thanks.

@VojtechBartoska VojtechBartoska removed the request for review from SuGlider August 10, 2022 12:57
@RefactorFactory
Copy link
Contributor Author

This has a problem on one of my PCs at boot where the PC does not send "SET PROTOCOL BOOT" to the device and it expects the device to send reports prefixed with a report ID. I'll investigate further.

@RefactorFactory RefactorFactory marked this pull request as draft August 20, 2022 04:48
@me-no-dev
Copy link
Member

USB Keyboards actually provide two HID devices, one for boot and another for all other cases. The non-boot device can have extra functions, buttons, etc.
I know tinyusb can have two devices, so looking into implementing that instead, seems like the better option here :)

1. Like a real keyboard, the USB interface descriptor will have an interface
   subclass of boot and an interface protocol of keyboard. This will cause
   some PC BIOS to send a SET PROTOCOL BOOT request to the device.

2. When the device sends reports to the host, if the host requested boot
   protocol, don't send a report ID because boot protocol does not use report
   IDs.

3. To work with some simple PC BIOS:
   a. Use endpoint address of 1 for input and output.
   b. Use separate reports for the shift key. These extra reports can be
      disabled by calling USBHIDKeyboard::setShiftKeyReports(false).
@RefactorFactory
Copy link
Contributor Author

Ok, I've reworked this with some improvements.

I investigated a real keyboard from a major PC manufacturer and found that it makes the main HID device a boot keyboard. The real keyboard that I investigated has another HID device, but it is for special keys. So, like a real keyboard, I made USBHIDKeyboard use a descriptor that makes it a boot device, but it doesn't speak boot protocol (namely, omitting report IDs from reports) unless the host requests it.

This major PC manufacturer of a 2021 PC (which happens to not request boot protocol) needed two other work-arounds:

  1. Use the endpoints 1 IN and 1 OUT. Without this, the device would never become ready at boot. Maybe there is some other deeper problem going on, but this work-around worked.
  2. For reports using the shift key, break out the shift key state changes into separate reports. Without this, shifted characters would not be recognized at all (not even as non-shifted). I found this hard to believe, but this work-around worked. Because this work-around may not be necessary for everyone, I added an API, USBHIDKeyboard::setShiftKeyReports(), that can be called to disable this behavior, but left the default behavior enabled so that users don't have to spend hours rediscovering this problem.

Let me know any feedback, thanks.

@RefactorFactory RefactorFactory marked this pull request as ready for review August 30, 2022 01:10
@VojtechBartoska VojtechBartoska linked an issue Sep 1, 2022 that may be closed by this pull request
1 task
@RefactorFactory
Copy link
Contributor Author

Any feedback on what I can do to get these changes in? I'm open to suggestions. Thanks.

1 similar comment
@RefactorFactory
Copy link
Contributor Author

Any feedback on what I can do to get these changes in? I'm open to suggestions. Thanks.

@me-no-dev
Copy link
Member

sorry for the delay @RefactorFactory . We will look into this very soon and have it in what will become version 3.0.0 (based on IDF 5.x)

@VojtechBartoska VojtechBartoska added the Status: Review needed Issue or PR is awaiting review label Nov 28, 2023
@VojtechBartoska VojtechBartoska modified the milestones: 3.0.0, 3.0.0-Aplha3 Nov 28, 2023
@VojtechBartoska
Copy link
Contributor

Hello @RefactorFactory, please sign CLA. Thanks

Copy link
Member

@P-R-O-C-H-Y P-R-O-C-H-Y left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Can you please sign the CLA as requested?
Thanks @RefactorFactory for the PR!

Copy link
Collaborator

@lucasssvaz lucasssvaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works as expected. Tested on my ASRock motherboard (that didn't work without this PR).

@P-R-O-C-H-Y P-R-O-C-H-Y added Status: Pending Merge Pull Request is ready to be merged and removed Status: Review needed Issue or PR is awaiting review labels Nov 30, 2023
@me-no-dev me-no-dev merged commit 9a9ec09 into espressif:master Nov 30, 2023
44 of 45 checks passed
@RefactorFactory
Copy link
Contributor Author

Hello @RefactorFactory, please sign CLA. Thanks

Looks good. Can you please sign the CLA as requested? Thanks @RefactorFactory for the PR!

I signed the CLA. Sorry it took so long. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Pending Merge Pull Request is ready to be merged Status: To be implemented Selected for Development
Projects
Development

Successfully merging this pull request may close these issues.

USBHIDKeyboard SendReport(): report 1 wait failed
7 participants