-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Unable to use HID keyboard in boot OS (macOS boot screen) #1136
Comments
The HID keyboard currently presented by CircuitPython is not a boot device keyboard, with its own endpoint(s), which is what the Mac Open Firmware ("BIOS") and regular PC BIOSes expect. It's part of a composite HID device that includes mouse, multimedia keys, and a gamepad. This was to save endpoints for other purposes. We've had one other person who wants a boot-compatible keyboard and we're thinking about that - we have to count up the available endpoints and what we want for the future (e.g. USB MIDI). Also, in the long run, we are thinking about how to provide user-configurable HID devices. |
Thanks. I completely understand and figured that this wasn’t currently supported but thought filing a bug was the best way to ask/suggest implementation of the feature. Is there any roadmap for future HID support? And MIDI sounds quite awesome too :) |
No specific timeline with dates, but your request is noted and it's a vote! |
wonderful, thank you! |
This is marked as closed - was it done? If the fix was the same that I used in the XBox stuff which requires a custom build, is there any example of making this work? |
The fix requires a new descriptor and its own endpoint, at least. I'm not sure what else. Perhaps it could be added as a compile option for the descriptor generating Python script, and then later as a |
I understand this not being in the core CP, but I do think it should remain open until there's some way to do it. I don't mind having to do a custom build (like we do for the XBox Adaptive Controller), but we have found lots of folks who really want to use devices through KVMs, and the composite devices don't work through them. |
BTW, thanks Dan |
Do you need a bootmode mouse HID capability too? |
Um... I don't think so? I think the reason this is needed is that the KVM listens in on the keyboard commands and relays them. That lets it watch for trigger keystrokes ('scroll-scroll-enter') to jump between devices. We don't kneed the triggers, but the stupid KVM doesn't know what to do with composite devices. It might be that we just need a non-composite device w/no other profiles. Or only a single device (no serial/mass storage?) I just know it is a known issue that "complicated" keyboards (even those with media keys) don't work. |
Here is another vote for it :) |
We are currently out of endpoints on most boards with 8 endpoints, having added a second CDC. If someone would like to contribute boot keyboard and mouse descriptors and compile flags, we'd be happy to include them. We would not turn them on normally, since they would remove having MIDI or the second CDC available by default. This is another indicator for user-configurable USB and USB HID descriptors. However, we are pretty much out of space on the smallest builds, and don't have room to add the code for this. It would have to be on something other than the non-Express SAMD21's. |
@dhalbert boot keyboard doesn't need to be on its own interface and/or take extra endpoint. It can be used with existing HID device. Basically the HID device has 2 mode (or protocol): boot mode, and report mode (default).
Most bios will always start with SET_PROTOCOL = BOOT upon enumeration. We only need to handle the callback, set the mode correctly then skip all other report except keyboard. I will update my hid_composite example in my repo so that you could have a clear example to follow in circuitpython later on. Will tag you on the modification later on, may try to submit PR myself as well. Though for the flash size, it may indeed increase a bit of space. |
Aha, great, thanks! I will await your example. It would be more complicated to add a boot mouse, but at least having a keyboard will satisfy many needs. |
each interface can only either support boot keyboard or mouse, to support both, we need an extra interface though. |
But when in boot mode, you don't need MSC and CDC and MIDI, no? |
Good point - it's more a question of having code space on the smallest builds to switch back and forth. |
@hathach Are you sure about the not needed extra endpoint? I tried a few times getting this to work with the arduino environment and to have an idea where the issue is i used this tool https://www.thesycon.de/eng/usb_descriptordumper.shtml and a real keyboard always used an extra endpoint. @dhalbert |
I believe that BOOT mode is missing n-key rollover so we'll want to switch between the two if we can. |
@bitboy85 can you post the descriptor of your real keyboard here along with its hid descriptor for the reference. If it is too long, you could move it to a gist and have a link here. |
@hathach I remember a doc that says, a boot keyboard endpoint requires a packet size of 8 byte. As additional reference: https://www.usb.org/sites/default/files/hid1_11.pdf |
thanks for the descriptor, and the reference. I just go through the hid 1.1 briefly again, and didn't see any requirement for boot protocol to has its own interface. The consumer keyboard has the luxury of additional endpoint to implement it in that way. Though we will see how this works out. |
@obra may know what we need to be a boot keyboard too. |
I also need a keyboard that works at boot time. I'm currently working on a software for a DIY split keyboard with each part having a Raspberry Pi Pico in it. All features I planned are now implemented and I will share the code on GitHub in the comming weeks. I just realized, that it does not work at boot time (at least not under macOS). I hope that this issue can be fixed, because otherwise building fully fledged custom keyboards is out of reach by using CircuitPython. |
You want to have a look at the descriptors we use for https://github.com/keyboardio/KeyboardioHID
The idea is that you should offer up both a Boot Protocol descriptor and a bitmap descriptor.
…On Thu, Mar 11, 2021, at 1:18 AM, akaenner wrote:
I also need a keyboard that works at boot time. I'm currently working on a software for a DIY split keyboard with each part having a Raspberry Pi Pico in it. All features I planned are now implemented and I will share the code on GitHub in the comming weeks. I just realized, that it does not work at boot time (at least not under macOS). I hope that this issue can be fixed, because otherwise building fully fledged custom keyboards is out of reach by using CircuitPython.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#1136 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAALC2AOHJ7R2B6YSWKGPZ3TDCDGTANCNFSM4FQ4LPWA>.
|
As it is possible to config USB devices on a custom build, any hints where to start when trying to implement this? I've read some docs about the requirement.
|
The wakeup problem is different than boot keyboard, and I need to work on that separately. If you plug in the RP2040 when the password prompt appears, does it still not work? My Macs are not set up for this disk encryption, but there may be other boot-time choices (option-shift-whatever, etc.) that I can use to test without having to set one up for encryption. Do you know of some boot-time entry screens similar to the password request? |
No, this also does not work.
I don"t know how to reproduce this behaviour without an encrypted HD. But you can encrypt your HD at any time. It just takes some time to finish. See System Preferences > Security & Privacy > File Vault |
@akaenner I have a simpler test of this, which is to start the Mac while holding down the "D" key, to get it into diagnostics mode. Then it lists the available wifi networks (wait for the spinning to stop), and an external keyboard's up and down arrow keys can go up and down the list of networks. The "D" key is for Intel Macs. For M1 ("Apple silicon"), you just keep holding down the power button to get into a startup menu. https://support.apple.com/en-us/HT202731 Anyway, what I see on an Intel MacBook is that a MacroPad set up for boot keyboard and sending up and down arrows does not work, but a vanilla Dell external keyboard does. So this gives me something to work toward. |
I just wanted to tell that I'm also looking forward to a better HID support. Also, usage with the KVM switch is not robust (the KVM is built into my monitor). |
So currently the default firmware has cdc hard coded to interface 0 because the windows driver is also hard coded to interface 0. Is this a requirement by windows or could it also use a hardcoded interface number 1? (having 0 as unused/reserved for boot keyboard). What about making it the default firmware where the user can choose the interface number? |
Fixing CDC to interface is a requirement due to the limitations of third-party drivers in Windows. A third-party cannot write a "class driver", which identifies the device by class and subclass. That is reserved for system-supplied drivers. I can only identify the device by PID/VID, and (optionally) interface number. If I scramble the interface numbers, then it can assign the wrong driver (e.g. MSC for CDC). I have also seen that at least some versions of Windows expected CDC on interface 0 always (incorrectly). In addition, at least some OS's expect the interface numbers to be sequential, so I assign them sequentially. And it would be a big bang to move CDC away from interface 0, and could cause trouble for current Windows users. Windows driver assignments can get confused if the same device starts presenting different devices on varying interface numbers. We don't need the Adafruit drivers package on Windows 10, because it has a built-in class driver for CDC. So if we stopped supporting Windows 7 and 8.1, the problem would go away. |
Boot keyboard support on interface 0 is now merged into the The boot keyboard suipport works on a few PC BIOS's I tested it on. It does not work on Mac yet, so I am leaving this issue open. I don't understand why, and the errors appear to be below the level of the descriptors: I am seeing unusual USB transmissions, so the issue may be at the USB protocol level. |
I have just downloaded the automatic build and it seems to fix my problems on Windows. The keyboard can be used right after booting. Thanks a lot for the great work. |
I just released my keyboard firmware on github: https://github.com/akaenner/picosplit |
@akaenner : On a quick look I did not see exception handlers. See PR #1929. Without, on my Windows PCs the software would not re-connect on re-boot or when the BIOS takes a few seconds before Windows is booting. If you have exception handlers, please point me to the positions, as I would like to update the PR with the actual exception types instead of catching all. |
@DietmarSchwertberger : I added an exception handler which does a reset. Do you think this is enough? I tested it with a PC running Windows and waking up the computer by pressing a key still does not work. Here is my code: https://github.com/akaenner/picosplit/blob/main/Master/main.py |
You really do not want to use a naked "except" like this, ever. It will catch Please use |
@akaenner : a reset seems a bit hard and unnecessary. |
I've tried with a Lenovo T410 Notebook. Sadly a keypress from the device completly locks up the BIOS. Even the normal keyboard is not working anymore. This happens at the time where the first keypress is send from the CP-device.
For whatever reason it worked a single time out of twenty tries but i'm unable to reproduce. boot.pyfrom digitalio import DigitalInOut, Direction, Pull
import board
import usb_hid
import usb_midi
import usb_cdc
import storage
maintenance_mode = False
BTN_A = DigitalInOut(board.GP19)
BTN_B = DigitalInOut(board.GP20)
BTN_A.direction = Direction.INPUT
BTN_A.pull = Pull.UP
BTN_B.direction = Direction.INPUT
BTN_B.pull = Pull.UP
#QuickButtons
if not BTN_A.value:
if not BTN_B.value:
maintenance_mode = True
if not maintenance_mode:
storage.disable_usb_drive()
usb_cdc.disable()
usb_midi.disable()
usb_hid.enable((usb_hid.Device.KEYBOARD,), boot_device=1 if not maintenance_mode else 0) lsusb -vd 239a:80f4
When comparing to http://www.usblyzer.com/reports/usb-properties/usb-keyboard.html iInterface is 4 guess it should be 0 i tried CP 7.1.0 beta 0 and beta 1 |
Thank you @dhalbert and everyone for everything, I couldn’t get CircuitPython’s HID to work with my KVM either, due to the needed boot keyboard protocol. Special shout-out to @obra and the boot keyboard descriptors in https://github.com/keyboardio/KeyboardioHID, I used it to modify (slightly) the boot descriptor dhalbert graciously provided for a related issue hathach/tinyusb#1129 (comment), and now it works with my KVM! CP 7.1.0 rc1 on a Feather Express M4 and using this boot.py boot.py
import board
import digitalio
import storage
import usb_cdc
import usb_midi
import usb_hid
from time import sleep
switch = digitalio.DigitalInOut(board.D5)
switch.direction = digitalio.Direction.INPUT
switch.pull = digitalio.Pull.UP
sleep(0.1)
BOOT_KEYBOARD_DESCRIPTOR=bytes((
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x06, # Usage (Keyboard)
0xA1, 0x01, # Collection (Application)
0x05, 0x07, # Usage Page (Kbrd/Keypad)
0x19, 0xE0, # Usage Minimum (0xE0)
0x29, 0xE7, # Usage Maximum (0xE7)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x01, # Logical Maximum (1)
0x75, 0x01, # Report Size (1)
0x95, 0x08, # Report Count (8)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, # Report Count (1)
0x75, 0x08, # Report Size (8)
0x81, 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x05, # Report Count (3)
0x75, 0x01, # Report Size (1)
0x05, 0x08, # Usage Page (LEDs)
0x19, 0x01, # Usage Minimum (Num Lock)
0x29, 0x05, # Usage Maximum (Kana)
0x91, 0x02, # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x95, 0x01, # Report Count (1)
0x75, 0x03, # Report Size (5)
0x91, 0x01, # Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x95, 0x06, # Report Count (6)
0x75, 0x08, # Report Size (8)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, # Logical Maximum (255)
0x05, 0x07, # Usage Page (Kbrd/Keypad)
0x19, 0x00, # Usage Minimum (0x00)
0x2A, 0xFF, # Usage Maximum (0xFF)
0x81, 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,
))
boot_keyboard = usb_hid.Device(
report_descriptor=BOOT_KEYBOARD_DESCRIPTOR,
usage=0x06,
usage_page=0x01,
report_ids=(0,),
in_report_lengths=(8,),
out_report_lengths=(1,),
)
maintenance_mode = not switch.value
if not maintenance_mode:
storage.disable_usb_drive()
usb_cdc.disable()
usb_midi.disable()
usb_hid.enable((boot_keyboard,), boot_device=1 )
else:
usb_hid.enable((usb_hid.Device.KEYBOARD,)) |
@retrospecced Thanks for reporting this. I compared your descriptor above with the one I referenced, and here are the diffs. Your changes are on the right hand side. See the 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x06, // Usage (Keyboard) 0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application) 0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Kbrd/Keypad) 0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0xE0, // Usage Minimum (0xE0) 0x19, 0xE0, // Usage Minimum (0xE0)
0x29, 0xE7, // Usage Maximum (0xE7) 0x29, 0xE7, // Usage Maximum (0xE7)
0x15, 0x00, // Logical Minimum (0) 0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1) 0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1) 0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8) 0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Pr 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Pr
0x95, 0x01, // Report Count (1) 0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8) 0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear
0x95, 0x03, // Report Count (3) | 0x95, 0x05, // Report Count (3)
0x75, 0x01, // Report Size (1) 0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Page (LEDs) 0x05, 0x08, // Usage Page (LEDs)
0x19, 0x01, // Usage Minimum (Num Lock) 0x19, 0x01, // Usage Minimum (Num Lock)
0x29, 0x05, // Usage Maximum (Kana) 0x29, 0x05, // Usage Maximum (Kana)
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,P 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,P
0x95, 0x01, // Report Count (1) 0x95, 0x01, // Report Count (1)
0x75, 0x05, // Report Size (5) | 0x75, 0x03, // Report Size (5)
0x91, 0x01, // Output (Const,Array,Abs,No Wrap,Linea 0x91, 0x01, // Output (Const,Array,Abs,No Wrap,Linea
0x95, 0x06, // Report Count (6) 0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8) 0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0) 0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255) | 0x26, 0xFF, // Logical Maximum (255)
0x05, 0x07, // Usage Page (Kbrd/Keypad) 0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0x00, // Usage Minimum (0x00) 0x19, 0x00, // Usage Minimum (0x00)
0x2A, 0xFF, 0x00, // Usage Maximum (0xFF) | 0x2A, 0xFF, // Usage Maximum (0xFF)
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear, 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,
0xC0, // End Collection 0xC0, // End Collection In yours, there are 5 bits of padding and 3 bits of LED's (the comments are incorrect, the
The actual descriptor does not necessarily matter, because when you set Nevertheless, yours works, in your situation. If you change the things I mentioned, does it make any difference? |
Thank you for the analysis on the descriptor, and your corrections to mine also work with my KVM, as you probably expected! When I set boot_device=1, the KVM doesn't work unless I supply your descriptor as a parameter. I don't think it's ignoring it. #usb_hid.enable((usb_hid.Device.KEYBOARD,), boot_device=1 )
# above line does not work with Belkin SOHO KVM
#usb_hid.enable((reference_keyboard,), boot_device=1 )
# above line works fine with Belkin SOHO KVM boot.py (updated)
"""CircuitPython Essentials Storage logging boot.py file"""
import board
import digitalio
import storage
import usb_cdc
import usb_midi
import usb_hid
from time import sleep
switch = digitalio.DigitalInOut(board.D5)
switch.direction = digitalio.Direction.INPUT
switch.pull = digitalio.Pull.UP
sleep(0.1)
REFERENCE_BOOT_KEYBOARD_DESCRIPTOR=bytes((
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x06, # Usage (Keyboard)
0xA1, 0x01, # Collection (Application)
0x05, 0x07, # Usage Page (Kbrd/Keypad)
0x19, 0xE0, # Usage Minimum (0xE0)
0x29, 0xE7, # Usage Maximum (0xE7)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x01, # Logical Maximum (1)
0x75, 0x01, # Report Size (1)
0x95, 0x08, # Report Count (8)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Pr
0x95, 0x01, # Report Count (1)
0x75, 0x08, # Report Size (8)
0x81, 0x01, # Input (Const,Array,Abs,No Wrap,Linear
0x95, 0x03, # Report Count (3)
0x75, 0x01, # Report Size (1)
0x05, 0x08, # Usage Page (LEDs)
0x19, 0x01, # Usage Minimum (Num Lock)
0x29, 0x05, # Usage Maximum (Kana)
0x91, 0x02, # Output (Data,Var,Abs,No Wrap,Linear,P
0x95, 0x01, # Report Count (1)
0x75, 0x05, # Report Size (5)
0x91, 0x01, # Output (Const,Array,Abs,No Wrap,Linea
0x95, 0x06, # Report Count (6)
0x75, 0x08, # Report Size (8)
0x15, 0x00, # Logical Minimum (0)
0x26, 0xFF, 0x00, # Logical Maximum (255)
0x05, 0x07, # Usage Page (Kbrd/Keypad)
0x19, 0x00, # Usage Minimum (0x00)
0x2A, 0xFF, 0x00, # Usage Maximum (0xFF)
0x81, 0x00, # Input (Data,Array,Abs,No Wrap,Linear,
0xC0, # End Collection
))
reference_keyboard = usb_hid.Device(
report_descriptor=REFERENCE_BOOT_KEYBOARD_DESCRIPTOR,
usage=0x06,
usage_page=0x01,
report_ids=(0,),
in_report_lengths=(8,),
out_report_lengths=(1,),
)
maintenance_mode = not switch.value
if not maintenance_mode:
storage.disable_usb_drive()
usb_cdc.disable()
usb_midi.disable()
usb_hid.enable((reference_keyboard,), boot_device=1 )
else:
usb_hid.enable((usb_hid.Device.KEYBOARD,))
#usb_hid.enable((usb_hid.Device.KEYBOARD,), boot_device=1 )
# above line does not work with Belkin SOHO KVM
#usb_hid.enable((reference_keyboard,), boot_device=1 )
# above line works fine with Belkin SOHO KVM
"""output from this boot.py
Adafruit CircuitPython 7.1.0-rc.1 on 2021-12-25; Adafruit Feather M4 Express with samd51j19
Board ID:feather_m4_express
boot.py output:
""" Again, thanks for your time, let me know if I can test anything else for you, as I am super-thankful of all of your time and effort spent on making CircuitPython work for us (especially for first-time Python programmers like myself!) |
You do need a descriptor. How it works is a bit more complicated than one might expect. If you set |
Did some further testing and i think this is a weird one. I didn't change the descriptor. I just added usb_hid.get_boot_device() to check if the host requests boot mode -> Yes it does. So i said it locks up bios or grub boot loader. I found this is only half true. I do not use CTRL key anywhere in my script and i don't have any issues without cp-device so i don't think the error comes from the built-in keyboard. The behaviour is like a permanently pressed ctrl button. CTRL left button. But only one time after pluggin it in. But i can't find a part in my code where i send ctrl without releasing it. So its fixable with kbd.release(Keycode.CONTROL) at the beginning of my script. But it doesn't explain why it is pressed in the first place. Even more confusing: https://github.com/adafruit/Adafruit_CircuitPython_HID/blob/main/adafruit_hid/keyboard.py does a release_all in the init part. Maybe something is going wrong there. I'll take a closer look tomorrow. |
I'm using a KVM, but what you describe is the same behavior I was seeing before I started using the custom descriptor. https://w3c.github.io/uievents/tools/key-event-viewer.html would report left-control was always pressed before my code ran. Also, the KVM wouldn't forward any shift key presses to the host. So an "A" was always showing as control-a (lowercase), and so on. |
Even with extra endpoints disabled the macropad was not working with the KVM switch. A modified boot descriptor is required for the keyboard to work as a boot device, this was taken from the following GitHub issue: adafruit/circuitpython#1136 (comment) An new keyboard object is created that uses the updated boot descriptor. When booting in maintenance_mode the standard keyboard object is used.
Just want to thank @retrospecced as using his boot.py resolved my issues whereby I couldn't access my ThinkPads BIOS or enter the grub menu on Arch Linux. |
@retrospecced thx for your effort. |
@retrospecced's original descriptor has some errors. Did you try the left-hand one in the diff above. |
Ah thx. I've thought the provided source does already contain the fix. Meanwhile i found another descriptor and can confirm working with a startech kvm switch 👍
|
I have a theory of the various keyboard descriptors discussed and the problem where the left Ctrl key seems to be pressed: If the device sends reports prefixed with a 1-byte report ID of I may have found a way to keep CDC and possibly other functions enabled while also using a boot keyboard: Keep the other functions enabled, but make the boot keyboard use an endpoint address of EP 1 IN. By doing this with plain Arduino (no CircuitPython), I was able to use a boot keyboard and CDC with my old PC. |
I started putting together a project with my Trinket M0 to use for switching boot volumes on macOS. My goal was to have it select a volume by keystrokes. However, it doesn't seem to be working.Working correctly in macOS once booted
Using the same code for HID keyboard:
The text was updated successfully, but these errors were encountered: