NicoHood/HID

KVM Switch (Disable CDC Serial for better BIOS/UEFi/Custom compatibility)

bitboy85 opened this issue · 31 comments

I'm trying to use an Arduino micro as Bootkeyboard to control a KVM Switch. It seems the Keyboard is not recognized at all.
Are there different modes i could try?

The KVM support a Hotkey which is 2x CTRL_LEFT + 1 to 4 would be glad getting it to work so i can enter the same input on multiple pcs.

The code works fine on Windows and could be visualized with the on screen keyboard.

After editing a few files die arduino keyboard is at least passed through the kvm and made available to the clients.
The KVM Hotkeys to switch the PC are still not working.
My newer Test machine doesn't show a keyboard error if the device is connected.

I compared a few settings/values with this tool: https://www.thesycon.de/eng/usb_descriptordumper.shtml between the micro and a standard usb keyboard.

Possible reasons

  • The arduino doesn't enter "Bootmode" I've tried few changes to force bootmode but had no success
  • The keycodes for those hotkeys are not correctly send (works in windows). Needed is Scroll_Lock and/or LEFT_CTRL
  • The interface number is wrong. I got an interface number of 2, the real keyboard uses an interface id of 0 for boot mode. Sadly i couldn't find the place in the code where i could change it
  • PAKET_SIZE / Endpoint size is wrong. A micro uses by default 64, but a real keyboard only 8.

What i've done so far. I don't know if all these steps are nessessary but i will document them for anyone who finds this by googling.

  • I have removed CDC by applying this patch: arduino/Arduino#6387 (comment)
  • removed bootloader by using another arduino as ISP
  • applied patches provided here: #102 (comment)
  • changed device descriptor. A real keyboard gives a device class of 0x00, a subclass of 0x00, and a protocol of 0x00. A default micro shows up different.
    The required changes are in the USBCore.cpp
    add 4 lines and change line 77:
#define DEVICE_CLASS      0x00 // Default 0xEF
#define DEVICE_SUB_CLASS  0x00 // Default 0x02
#define DEVICE_PROTOCOL   0x00 // Default 0x01
#define MAX_PAKET_SIZE    64 // Default 64

D_DEVICE(DEVICE_CLASS,DEVICE_SUB_CLASS,DEVICE_PROTOCOL,MAX_PAKET_SIZE,USB_VID,USB_PID,0x100,IMANUFACTURER,IPRODUCT,ISERIAL,1);

I guess that SET_PROTOCOL / GET_PROTOCOL are not correctly working. I've tried using an LED to see if it enters Bootmode but it seems it doesn't use it. Tried a few things to force by editing values but it wasn't working and maybe i did somethiong wrong.

Small Update: To get Passthrough in the KVM working CDC needs to be disabled. The bootloader can be there so its still possible to upload sketches without external Programmer, you just need to reset the Board manually by hitting Reset.

So the solution is to remove the CDC device? That seems correct, as the arduino reports itself as kinda "mixed device" which is not a keyboard 100%. Some devices might not like this behavior and expect a pure keyboard.

No. From what i've seen the CDC doesn't matter. Maybe it doesn't matter at all how many devices are registered as long as one device follows exactly the boot specifications in the USB Doc.
My guess by now is:
a Bootkeyboard requires a device class of 0x00, a subclass of 0x00, and a protocol of 0x00. The Paket size needs to be 8 bit instead of 64. Maybe the bootKeyboards needs to be the first interface 0. (Currently CDC is the first interface)
Sadly i wasn't able to implement this. I failed changing the paket size from 64 to 8 bit. Just changing the value in the USBcore.cpp doesn't work. It leads to an error where the device cannot be identified at all. And from what i've seen the default HID core that comes with arduino needs to be changed too. So thats a lot of work here.
If its helpful i can add the results of the usb_descriptordumper tool. But you can easily get the results on your own.

Short update: I got a Teensy and configured it as keyboard, mouse Joystick and it works on my KVM device.
So a mixed device isn't an issue.
Also i need to correct my post above: device class needs to be 0x03 instead of 0x00.

Uhm... I dont know why it does not work. If you have any update, please let me know.

Well, as i said. The current implementation does not follow the requirements for a real boot keyboard. I guess you are from germany so maybe its better to explain it in german.

Yeah, I think the arduino implementation is tight to 64bit report descriptors. I dont know if that is the main issue. The classes might be another problem, however there are quick and dirty workarounds to change them and remove the CDC endpoint. But yes, if you have any detail to add in german, please do so.

Hi @bitboy85 - thanks for documenting all this ; would be great if we can get the final summary to get it working (with or without disabling CDC) . 🚀

Just for information, it may be useful to someone. The patch for disabling the CDC is incorrect. As it is written, it leaves the first endpoints in non-configured state. I found out that this prevents Apple UEFI from detecting the keyboard to allow the user to enter the menu. This is probably why KVM switches go crazy. I figured out how it works and wrote a fixed version of the patch that is guaranteed to disable the CDC and leave no artifacts.
https://github.com/pikvm/kvmd/blob/master/hid/patches/optional-usb-serial.patch

I had no time digging further into this so i decided to use a teensy board with a working boot keyboard. So the above is more or less the final summary.
A boot compliant device registers in fact as two devices. A normal HID which is detected and used by the operating system and a "boot-keyboard" which is detected and used by system with reduced usb support. Like the mentioned KVM Switch or older BIOS.

As said above i got stuck at using a packet size of 8 Bit which is required by the usb standard. And i have no idea how to implement 2 devices. Maybe this isn't even possible with the current version of the provided usb libraries.

In case of teensy it is possible to use Serial, keyboard, mouse and joystick in one device so i guess it doesn't matter at all if CDC is used or not.

Thank you for the update @bitboy85 ; I've ordered the Teensy for now . Appreciate all the help you have given!

Just for comparison: A working teensy implementation looks like this:

Information for device Teensy Keyboard/Mouse/Joystick (VID=0x16C0 PID=0x0482):

Connection Information:

Device current bus speed: FullSpeed
Device supports USB 1.1 specification
Device supports USB 2.0 specification
Device address: 0x0008
Current configuration value: 0x00
Number of open pipes: 0

Device Descriptor:

0x12 bLength
0x01 bDescriptorType
0x0200 bcdUSB
0x00 bDeviceClass
0x00 bDeviceSubClass
0x00 bDeviceProtocol
0x40 bMaxPacketSize0 (64 bytes)
0x16C0 idVendor
0x0482 idProduct
0x0271 bcdDevice
0x00 iManufacturer
0x01 iProduct
0x00 iSerialNumber
0x01 bNumConfigurations

Configuration Descriptor:

0x09 bLength
0x02 bDescriptorType
0x008D wTotalLength (141 bytes)
0x05 bNumInterfaces
0x01 bConfigurationValue
0x00 iConfiguration
0xC0 bmAttributes (Self-powered Device)
0x32 bMaxPower (100 mA)

Interface Descriptor:

0x09 bLength
0x04 bDescriptorType
0x00 bInterfaceNumber
0x00 bAlternateSetting
0x01 bNumEndPoints
0x03 bInterfaceClass (Human Interface Device Class)
0x01 bInterfaceSubClass
0x01 bInterfaceProtocol
0x00 iInterface

HID Descriptor:

0x09 bLength
0x21 bDescriptorType
0x0111 bcdHID
0x00 bCountryCode
0x01 bNumDescriptors
0x22 bDescriptorType (Report descriptor)
0x003F bDescriptorLength

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x83 bEndpointAddress (IN endpoint 3)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0008 wMaxPacketSize (1 x 8 bytes)
0x01 bInterval (1 frames)

Interface Descriptor:

0x09 bLength
0x04 bDescriptorType
0x01 bInterfaceNumber
0x00 bAlternateSetting
0x01 bNumEndPoints
0x03 bInterfaceClass (Human Interface Device Class)
0x01 bInterfaceSubClass
0x02 bInterfaceProtocol
0x00 iInterface

HID Descriptor:

0x09 bLength
0x21 bDescriptorType
0x0111 bcdHID
0x00 bCountryCode
0x01 bNumDescriptors
0x22 bDescriptorType (Report descriptor)
0x0038 bDescriptorLength

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x84 bEndpointAddress (IN endpoint 4)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0008 wMaxPacketSize (1 x 8 bytes)
0x01 bInterval (1 frames)

Interface Descriptor:

0x09 bLength
0x04 bDescriptorType
0x02 bInterfaceNumber
0x00 bAlternateSetting
0x02 bNumEndPoints
0x03 bInterfaceClass (Human Interface Device Class)
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface

HID Descriptor:

0x09 bLength
0x21 bDescriptorType
0x0111 bcdHID
0x00 bCountryCode
0x01 bNumDescriptors
0x22 bDescriptorType (Report descriptor)
0x0021 bDescriptorLength

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x81 bEndpointAddress (IN endpoint 1)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0040 wMaxPacketSize (1 x 64 bytes)
0x01 bInterval (1 frames)

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x02 bEndpointAddress (OUT endpoint 2)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0020 wMaxPacketSize (1 x 32 bytes)
0x02 bInterval (2 frames)

Interface Descriptor:

0x09 bLength
0x04 bDescriptorType
0x03 bInterfaceNumber
0x00 bAlternateSetting
0x01 bNumEndPoints
0x03 bInterfaceClass (Human Interface Device Class)
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface

HID Descriptor:

0x09 bLength
0x21 bDescriptorType
0x0111 bcdHID
0x00 bCountryCode
0x01 bNumDescriptors
0x22 bDescriptorType (Report descriptor)
0x0055 bDescriptorLength

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x85 bEndpointAddress (IN endpoint 5)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x000C wMaxPacketSize (1 x 12 bytes)
0x02 bInterval (2 frames)

Interface Descriptor:

0x09 bLength
0x04 bDescriptorType
0x04 bInterfaceNumber
0x00 bAlternateSetting
0x01 bNumEndPoints
0x03 bInterfaceClass (Human Interface Device Class)
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface

HID Descriptor:

0x09 bLength
0x21 bDescriptorType
0x0111 bcdHID
0x00 bCountryCode
0x01 bNumDescriptors
0x22 bDescriptorType (Report descriptor)
0x0028 bDescriptorLength

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x86 bEndpointAddress (IN endpoint 6)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0008 wMaxPacketSize (1 x 8 bytes)
0x04 bInterval (4 frames)

Microsoft OS Descriptor is not available. Error code: 0x0000001F

String Descriptor Table

Index LANGID String
0x00 0x0000
0x01 0x0000 "Teensy Keyboard/Mouse/Joystick"

And here an arduino micro. I guess the bold part is wrong. Somewhere in the usb boot documentation i read that a boot Interface (sry, in earlier posts i wrote device) must have a packet size of 8 byte. But i'm unable to figure out where in the code this value is set.

Information for device Arduino Micro (VID=0x2341 PID=0x8037):

Connection Information:

Device current bus speed: FullSpeed
Device supports USB 1.1 specification
Device supports USB 2.0 specification
Device address: 0x001C
Current configuration value: 0x00
Number of open pipes: 0

Device Descriptor:

0x12 bLength
0x01 bDescriptorType
0x0200 bcdUSB
0xEF bDeviceClass (Miscellaneous device)
0x02 bDeviceSubClass
0x01 bDeviceProtocol
0x40 bMaxPacketSize0 (64 bytes)
0x2341 idVendor
0x8037 idProduct
0x0100 bcdDevice
0x01 iManufacturer "Arduino LLC"
0x02 iProduct "Arduino Micro"
0x03 iSerialNumber "C"
0x01 bNumConfigurations

Configuration Descriptor:

0x09 bLength
0x02 bDescriptorType
0x0064 wTotalLength (100 bytes)
0x03 bNumInterfaces
0x01 bConfigurationValue
0x00 iConfiguration
0xA0 bmAttributes (Bus-powered Device, Remote-Wakeup)
0xFA bMaxPower (500 mA)

Interface Association Descriptor:

0x08 bLength
0x0B bDescriptorType
0x00 bFirstInterface
0x02 bInterfaceCount
0x02 bFunctionClass (Communication Device Class)
0x02 bFunctionSubClass (Abstract Control Model - ACM)
0x00 bFunctionProtocol
0x00 iFunction

Interface Descriptor:

0x09 bLength
0x04 bDescriptorType
0x00 bInterfaceNumber
0x00 bAlternateSetting
0x01 bNumEndPoints
0x02 bInterfaceClass (Communication Device Class)
0x02 bInterfaceSubClass (Abstract Control Model - ACM)
0x00 bInterfaceProtocol
0x00 iInterface

CDC Header Functional Descriptor:

0x05 bFunctionalLength
0x24 bDescriptorType
0x00 bDescriptorSubtype
0x0110 bcdCDC

CDC Call Management Functional Descriptor:

0x05 bFunctionalLength
0x24 bDescriptorType
0x01 bDescriptorSubtype
0x01 bmCapabilities
0x01 bDataInterface

CDC Abstract Control Management Functional Descriptor:

0x04 bFunctionalLength
0x24 bDescriptorType
0x02 bDescriptorSubtype
0x06 bmCapabilities

CDC Union Functional Descriptor:

0x05 bFunctionalLength
0x24 bDescriptorType
0x06 bDescriptorSubtype
0x00 bControlInterface
0x01 bSubordinateInterface(0)

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x81 bEndpointAddress (IN endpoint 1)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0010 wMaxPacketSize (1 x 16 bytes)
0x40 bInterval (64 frames)

Interface Descriptor:

0x09 bLength
0x04 bDescriptorType
0x01 bInterfaceNumber
0x00 bAlternateSetting
0x02 bNumEndPoints
0x0A bInterfaceClass (CDC Data)
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x02 bEndpointAddress (OUT endpoint 2)
0x02 bmAttributes (Transfer: Bulk / Synch: None / Usage: Data)
0x0040 wMaxPacketSize (64 bytes)
0x00 bInterval

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x83 bEndpointAddress (IN endpoint 3)
0x02 bmAttributes (Transfer: Bulk / Synch: None / Usage: Data)
0x0040 wMaxPacketSize (64 bytes)
0x00 bInterval

Interface Descriptor:

0x09 bLength
0x04 bDescriptorType
0x02 bInterfaceNumber
0x00 bAlternateSetting
0x01 bNumEndPoints
0x03 bInterfaceClass (Human Interface Device Class)
0x01 bInterfaceSubClass
0x01 bInterfaceProtocol
0x00 iInterface

HID Descriptor:

0x09 bLength
0x21 bDescriptorType
0x0101 bcdHID
0x00 bCountryCode
0x01 bNumDescriptors
0x22 bDescriptorType (Report descriptor)
0x0045 bDescriptorLength

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x84 bEndpointAddress (IN endpoint 4)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0040 wMaxPacketSize (1 x 64 bytes)
0x01 bInterval (1 frames)

Microsoft OS Descriptor is not available. Error code: 0x0000001F

String Descriptor Table

Index LANGID String
0x00 0x0000 0x0409
0x01 0x0409 "Arduino LLC"
0x02 0x0409 "Arduino Micro"
0x03 0x0409 "C"


Connection path for device:
USB-xHCI-kompatibler Hostcontroller
Root Hub
Arduino Micro (VID=0x2341 PID=0x8037) Port: 1

Running on: Windows 10 or greater (Build Version 18363)

Brought to you by TDD v2.15.0, Jun 8 2020, 17:18:07

Sry for spamming but i thinks its better to post where i get before i can't remember what i've done.

So first things first:
In the file ImprovedKeyLayouts.h which is also included in the BootKeyboard i found this:

// Keyboard Modifiers
enum KeyboardMods : uint16_t {
	MOD_LEFT_CTRL		= (1 <<  8),
	MOD_LEFT_SHIFT		= (1 <<  9),
	MOD_LEFT_ALT		= (1 << 10),
	MOD_LEFT_GUI		= (1 << 11),
	MOD_RIGHT_CTRL		= (1 << 12),
	MOD_RIGHT_SHIFT		= (1 << 13),
	MOD_RIGHT_ALT		= (1 << 14),
	MOD_RIGHT_GUI		= (uint16_t)(1 << 15),
};

If i read the usb documentation correctly, the modifiers keys are represented by a single byte.
@NicoHood is there a reason why you used uint16_t ? Maybe it gets cut down to 8 Bits later in the code but for now i changed it to this:

enum KeyboardMods : uint8_t {
	MOD_LEFT_CTRL		= (1 <<  0),
	MOD_LEFT_SHIFT		= (1 <<  1),
	MOD_LEFT_ALT		= (1 << 2),
	MOD_LEFT_GUI		= (1 << 3),
	MOD_RIGHT_CTRL		= (1 << 4),
	MOD_RIGHT_SHIFT		= (1 << 5),
	MOD_RIGHT_ALT		= (1 << 6),
	MOD_RIGHT_GUI		= (uint8_t)(1 << 7),
};

So now we have an issue with file Bootkeyboard.cpp; Line 73 this creates the Endpoint and its attributes, at least it should.
D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01)

You can change USB_EP_SIZE and the following 0x01 (is it interval?) to whatever value you want, it does NOT change attributes in the endpoint descriptor, its overwritten somewhere

This leads us to the file /arduino/avr/cores/arduino/USBAPI.h. As you can see this file does not belong to the HID-project as it comes with arduino by default.

Sadly, in the file is the following quote:

// This definitions is usefull if you want to reduce the EP_SIZE to 16
// at the moment only 64 and 16 as EP_SIZE for all EPs are supported except the control endpoint
#ifndef USB_EP_SIZE
#define USB_EP_SIZE 64
#endif

Well it is possible to hack a size of 8 into it (which will additionally require changes in USBCORE.cpp) but just forcing the size to be 8 will not magically make the paket an 8 byte sized one. So it still does not work with the kvm switch.

Out of interest, which KVM switch do you own?
I have an Aten CS1924. My keyboard code (based on NicoHood's Boot keyboard) works if CDC is disabled, but not if it's enabled; I didn't have to change the EP size.
See arduino/ArduinoCore-avr#383 for the changes I did in Arduino Core for AVR (I hope it gets merged; the SAMD core already supports this)

Its a Startech 4 Port KVM https://www.startech.com/de-de/server-management/sv431dpua
In issue #34 someone also mentioned that its not working on his kvm.

It seems it depends on the usb implementation of the hardware manufacturer if it works or not. HID-project also seems to work in some BIOSes and in some not.

To everyone who still has problems with keyboard in bios:
Checkout this comment #305 (comment) where someone is happy to fix the issue, if he can get his hands on a not working device. Maybe someone of you can help here?

If there is some cheap KVM that does not work, I could buy it. Aten is too expensive for me :)

The way to fix the issue is to disable CDC, I don't think you can do much within this lib.
If you want to do anything about it you could either try to convince the Arduino maintainers to finally merge arduino/ArduinoCore-avr#383 or implement an alternative that allows switching on/off CDC at runtime - but this might not be easy, I think you'd have to somehow reset USB so both Arduino and the device it's connected to get to know that the USB devices changed.

@DanielGibson Thanks for reminding me of this. I totally forgot about this one. Can you ping me once it is merged, so we can add that to the example sketch and the wiki as well? That information should not get lost.

Thank you all for commenting in that PR, hopefully this helps reminding the Arduino maintainers of the issue :)

I'll try to remember to ping you (but now that you've commented on it you'll probably get an e-mail from Github when it gets merged?)

@NicoHood arduino/ArduinoCore-avr#383 (CDC_DISABLED) has been merged

So what should we do now? How would be disable it from the arduino ide?

Good question - can you somehow set a compilerflag in the arduino-ide? It'd be -DCDC_DISABLED.
Otherwise one could un-comment the //#define CDC_DISABLED line in cores/arduino/USBDesc.h

Though I guess until the next Arduino (core? ide?) release (presumably 1.8.4) that actually ships these changes they won't be accessible to most users anyway

Years ago it was not possible to pass custom defined variables to the compiler. I am not sure if it works nowadays. Can you find that out maybe? Then we have a solution till the next release.

It depends on the build environment. There is no problem for Platformio to pass parameters.

Arduino IDE: https://forum.arduino.cc/t/arduino-ide-where-can-i-pass-defines-to-the-compiler/680845/3

uzi18 commented

@NicoHood @DanielGibson @mdevaev got some problems with HID Keyboard on Arduino Leonardo.
I want to connect it to Windows CE based system, but it's asking me for driver name. It's not working.

Found this issue, tried to define CDC_DISABLED at build_flags. From this moment Leonardo is programmed by usbasp.
After flashed this version still have COM port available but without access, is it normal?
HID 2.8.4, latest platform.io
Lenovo USB Keyboard works ok.
Any hints?

uzi18 commented

@bitboy85 Do you remember which Teensy you have used?

No, can't remember. As an alternative you may have a look at rpi pico.
Circuitpython was updated to support keyboard boot protocol.
https://cdn-learn.adafruit.com/downloads/pdf/customizing-usb-devices-in-circuitpython.pdf

uzi18 commented

No, can't remember. As an alternative, you may have a look at rpi pico.

@bitboy85 ok will check and report later

If you are interested, I studied the TinyUSB stack for compliance with the HID standard and it passed all my tests with a rush. However, I had to write a custom keyboard event handler: https://github.com/pikvm/kvmd/blob/master/hid/pico/src/ph_usb.c