Author Topic: Making a Keyboard-to-Controller adapter, USB-level questions (Interfaces, NKRO)  (Read 1811 times)

0 Members and 1 Guest are viewing this topic.

Offline NessDan

  • Thread Starter
  • Posts: 3
Hi everyone, need USB-level protocol advice / knowledge!

I've been trying to make a keyboard adapter for the Nintendo Switch (https://keyboard.gg/) for over a year now. Right now, I've been doing some USB-level sniffing of my NKRO keyboard (Corsair K65) and found out it has 3 interfaces:

  • Interface 0: The "BOOT" protocol, 6-KRO (only used if the physical "BIOS" switch is on)
  • Interface 1: The Multimedia keys (Volume, Mute, etc.)
  • Interface 2: NKRO (From what I've gathered from testing, just enables certain bits if certain keys are pressed.)

I've made a video of me diving into all three of these points (around 1:46): https://photos.app.goo.gl/7j98istA1GyRfbjE6

My questions are these:
  • How would one find out about the NKRO bitmappings and what keys they are assigned to? I can do trials myself, but that only covers my U.S. TKL keyboard.
  • Is the NKRO format pretty universal (so I'd only have to code for it once) or does each keyboard implement it differently?
  • Looking just at the HID Report Descriptors, how can I pick the "interface of interest"? Do I just need to listen to all of them? I feel like only the boot interface for keyboards is clearly defined (SubClass = 1, Protocol = 1) and I'd have to do some real deep logic to take advantage of the NKRO interfaces.
  • Is there a term for what I'm doing now? Writing code to translate raw HID data into keyboard inputs?

Attaching some USB properties and pasting them below:

Code: [Select]
Connection Status Device connected
Current Configuration 1
Speed Full (12 Mbit/s)
Device Address 5
Number Of Open Pipes 3

Device Descriptor Corsair K65 Gaming Keyboard
Offset Field Size Value Description
0 bLength 1 12h 
1 bDescriptorType 1 01h Device
2 bcdUSB 2 0200h USB Spec 2.0
4 bDeviceClass 1 00h Class info in Ifc Descriptors
5 bDeviceSubClass 1 00h 
6 bDeviceProtocol 1 00h 
7 bMaxPacketSize0 1 08h 8 bytes
8 idVendor 2 1B1Ch 
10 idProduct 2 1B07h 
12 bcdDevice 2 0101h 1.01
14 iManufacturer 1 01h "Corsair"
15 iProduct 1 02h "Corsair K65 Gaming Keyboard"
16 iSerialNumber 1 00h 
17 bNumConfigurations 1 01h 

Configuration Descriptor 1 Bus Powered, 100 mA
Offset Field Size Value Description
0 bLength 1 09h 
1 bDescriptorType 1 02h Configuration
2 wTotalLength 2 0054h 
4 bNumInterfaces 1 03h 
5 bConfigurationValue 1 01h 
6 iConfiguration 1 00h 
7 bmAttributes 1 A0h Bus Powered, Remote Wakeup
 4..0: Reserved  ...00000   
 5: Remote Wakeup  ..1.....  Yes
 6: Self Powered  .0......  No, Bus Powered
 7: Reserved (set to one)
(bus-powered for 1.0)  1.......   
8 bMaxPower 1 32h 100 mA

Interface Descriptor 0/0 HID, 1 Endpoint
Offset Field Size Value Description
0 bLength 1 09h 
1 bDescriptorType 1 04h Interface
2 bInterfaceNumber 1 00h 
3 bAlternateSetting 1 00h 
4 bNumEndpoints 1 01h 
5 bInterfaceClass 1 03h HID
6 bInterfaceSubClass 1 01h Boot Interface
7 bInterfaceProtocol 1 01h Keyboard
8 iInterface 1 00h 

HID Descriptor
Offset Field Size Value Description
0 bLength 1 09h 
1 bDescriptorType 1 21h HID
2 bcdHID 2 0111h 1.11
4 bCountryCode 1 00h 
5 bNumDescriptors 1 01h 
6 bDescriptorType 1 22h Report
7 wDescriptorLength 2 004Fh 79 bytes

Endpoint Descriptor 81 1 In, Interrupt, 8 ms
Offset Field Size Value Description
0 bLength 1 07h 
1 bDescriptorType 1 05h Endpoint
2 bEndpointAddress 1 81h 1 In
3 bmAttributes 1 03h Interrupt
 1..0: Transfer Type  ......11  Interrupt
 7..2: Reserved  000000..   
4 wMaxPacketSize 2 0008h 8 bytes
6 bInterval 1 08h 8 ms

Interface Descriptor 1/0 HID, 1 Endpoint
Offset Field Size Value Description
0 bLength 1 09h 
1 bDescriptorType 1 04h Interface
2 bInterfaceNumber 1 01h 
3 bAlternateSetting 1 00h 
4 bNumEndpoints 1 01h 
5 bInterfaceClass 1 03h HID
6 bInterfaceSubClass 1 00h 
7 bInterfaceProtocol 1 00h 
8 iInterface 1 00h 

HID Descriptor
Offset Field Size Value Description
0 bLength 1 09h 
1 bDescriptorType 1 21h HID
2 bcdHID 2 0111h 1.11
4 bCountryCode 1 00h 
5 bNumDescriptors 1 01h 
6 bDescriptorType 1 22h Report
7 wDescriptorLength 2 0017h 23 bytes

Endpoint Descriptor 82 2 In, Interrupt, 8 ms
Offset Field Size Value Description
0 bLength 1 07h 
1 bDescriptorType 1 05h Endpoint
2 bEndpointAddress 1 82h 2 In
3 bmAttributes 1 03h Interrupt
 1..0: Transfer Type  ......11  Interrupt
 7..2: Reserved  000000..   
4 wMaxPacketSize 2 0002h 2 bytes
6 bInterval 1 08h 8 ms

Interface Descriptor 2/0 HID, 1 Endpoint
Offset Field Size Value Description
0 bLength 1 09h 
1 bDescriptorType 1 04h Interface
2 bInterfaceNumber 1 02h 
3 bAlternateSetting 1 00h 
4 bNumEndpoints 1 01h 
5 bInterfaceClass 1 03h HID
6 bInterfaceSubClass 1 00h 
7 bInterfaceProtocol 1 00h 
8 iInterface 1 00h 

HID Descriptor
Offset Field Size Value Description
0 bLength 1 09h 
1 bDescriptorType 1 21h HID
2 bcdHID 2 0111h 1.11
4 bCountryCode 1 00h 
5 bNumDescriptors 1 01h 
6 bDescriptorType 1 22h Report
7 wDescriptorLength 2 0039h 57 bytes

Endpoint Descriptor 83 3 In, Interrupt, 1 ms
Offset Field Size Value Description
0 bLength 1 07h 
1 bDescriptorType 1 05h Endpoint
2 bEndpointAddress 1 83h 3 In
3 bmAttributes 1 03h Interrupt
 1..0: Transfer Type  ......11  Interrupt
 7..2: Reserved  000000..   
4 wMaxPacketSize 2 000Fh 15 bytes
6 bInterval 1 01h 1 ms

Interface 0 HID Report Descriptor Keyboard
Item Tag (Value) Raw Data
Usage Page (Generic Desktop) 05 01 
Usage (Keyboard) 09 06 
Collection (Application) A1 01 
    Usage Page (Keyboard/Keypad) 05 07 
    Usage Minimum (Keyboard Left Control) 19 E0 
    Usage Maximum (Keyboard Right GUI) 29 E7 
    Logical Minimum (0) 15 00 
    Logical Maximum (1) 25 01 
    Report Size (1) 75 01 
    Report Count (8) 95 08 
    Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) 81 02 
    Report Count (1) 95 01 
    Report Size (8) 75 08 
    Input (Cnst,Ary,Abs) 81 01 
    Report Count (5) 95 05 
    Report Size (1) 75 01 
    Usage Page (LEDs) 05 08 
    Usage Minimum (Num Lock) 19 01 
    Usage Maximum (Kana) 29 05 
    Output (Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) 91 02 
    Report Count (1) 95 01 
    Report Size (3) 75 03 
    Output (Cnst,Ary,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) 91 01 
    Report Count (6) 95 06 
    Report Size (8) 75 08 
    Logical Minimum (0) 15 00 
    Logical Maximum (255) 26 FF 00 
    Usage Page (Keyboard/Keypad) 05 07 
    Usage Minimum (Undefined) 19 00 
    Usage Maximum 2A FF 00 
    Input (Data,Ary,Abs) 81 00 
    Usage Page (Consumer Devices) 05 0C 
    Usage (Undefined) 09 00 
    Logical Minimum (-128) 15 80 
    Logical Maximum (127) 25 7F 
    Report Size (8) 75 08 
    Report Count (8) 95 08 
    Feature (Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) B1 02 
End Collection C0 

Interface 1 HID Report Descriptor Consumer Control
Item Tag (Value) Raw Data
Usage Page (Consumer Devices) 05 0C 
Usage (Consumer Control) 09 01 
Collection (Application) A1 01 
    Usage Minimum (Undefined) 19 00 
    Usage Maximum 2A FF 0F 
    Logical Minimum (0) 15 00 
    Logical Maximum (4095) 26 FF 0F 
    Report Size (16) 75 10 
    Report Count (1) 95 01 
    Input (Data,Ary,Abs) 81 00 
End Collection C0 

Interface 2 HID Report Descriptor Keyboard
Item Tag (Value) Raw Data
Usage Page (Generic Desktop) 05 01 
Usage (Keyboard) 09 06 
Collection (Application) A1 01 
    Usage Page (Keyboard/Keypad) 05 07 
    Usage Minimum (Keyboard Left Control) 19 E0 
    Usage Maximum (Keyboard Right GUI) 29 E7 
    Logical Minimum (0) 15 00 
    Logical Maximum (1) 25 01 
    Report Size (1) 75 01 
    Report Count (8) 95 08 
    Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) 81 02 
    Usage Minimum (Undefined) 19 00 
    Usage Maximum (Keypad =) 29 67 
    Logical Minimum (0) 15 00 
    Logical Maximum (1) 25 01 
    Report Size (1) 75 01 
    Report Count (104) 95 68 
    Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) 81 02 
    Usage (Keypad Comma) 09 85 
    Usage Minimum (Keyboard International 1) 19 87 
    Usage Maximum (Keyboard International 5) 29 8B 
    Usage (Keyboard LANG1) 09 90 
    Usage (Keyboard LANG2) 09 91 
    Logical Minimum (0) 15 00 
    Logical Maximum (1) 25 01 
    Report Size (1) 75 01 
    Report Count (8) 95 08 
    Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) 81 02 
End Collection C0 

Offline ethereal

  • Posts: 21
1. You need to parse the HID descriptors.

2. There are 3 usual methods for implementing NKRO (https://github.com/qmk/qmk_firmware/blob/master/docs/usb_nkro.txt). But each keyboard/microcontroller tends to implement USB stuff in its particular way.

3. You have to parse the HID descriptors of all the USB interfaces. Then you find which reports belong to the Keyboard class and listen to all of them. Some keyboards present themselves as multiple virtual keyboards and you have to get the events of all of them.

4. I guess that would be named "HID parsing and decoding".

Offline NessDan

  • Thread Starter
  • Posts: 3
Thank you so much ethereal!

1. You need to parse the HID descriptors.

Awesome! So I'd need to write logic to parse messages like the below so I can interpret the data I'm getting from my reports?:

Code: [Select]
Interface 0 HID Report Descriptor Keyboard
Item Tag (Value) Raw Data
Usage Page (Generic Desktop) 05 01
Usage (Keyboard) 09 06
Collection (Application) A1 01
    Usage Page (Keyboard/Keypad) 05 07
    Usage Minimum (Keyboard Left Control) 19 E0
    Usage Maximum (Keyboard Right GUI) 29 E7
    Logical Minimum (0) 15 00
    Logical Maximum (1) 25 01
    Report Size (1) 75 01
    Report Count (8) 95 08
    Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) 81 02
    Input (Cnst,Ary,Abs) 81 01
    Usage Minimum (Undefined) 19 00
    Usage Maximum 2A FF 00
    Logical Minimum (0) 15 00
    Logical Maximum (255) 26 FF 00
    Report Size (8) 75 08
    Report Count (6) 95 06
    Input (Data,Ary,Abs) 81 00
    Usage Page (LEDs) 05 08
    Usage Minimum (Num Lock) 19 01
    Usage Maximum (Scroll Lock) 29 03
    Logical Minimum (0) 15 00
    Logical Maximum (1) 25 01
    Report Size (1) 75 01
    Report Count (3) 95 03
    Output (Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) 91 02
    Report Count (5) 95 05
    Output (Cnst,Ary,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) 91 01
End Collection C0

May be an odd question, but this must have already been done before in C right? Is it possible there exists a library that handles this? Or should I write it from scratch? (I'm using a FTDI / Bridgetek FT90X chip)

Offline ethereal

  • Posts: 21
You're welcome!

I see the FT9xx comes with a toolchain (https://www.ftdichip.com/Firmware/FT90xToolchain.htm). I guess it doesn't have a HID library...

The Linux kernel has a generic HID parser/decoder (in C like all kernel code). Maybe you can use some part of it: https://elixir.bootlin.com/linux/v5.10/source/drivers/hid

There is also another project that implements a generic HID parser and decoder: hid-tools (https://gitlab.freedesktop.org/libevdev/hid-tools). This one is written in Python (so maybe it's of no use to you if the FT9xx comes with no Python interpreter).

Do you really need a generic HID parser and decoder? Maybe a simple implementation, capable of interacting only with keyboards, would do the trick. I think some BIOS projects like coreboot (https://coreboot.org/) implement code to interact with USB keyboards. These would use, most probably, the boot protocol, so they would be limited to 6KRO mode only. But their code would be much simpler to follow and maybe it's all you need.
« Last Edit: Wed, 06 January 2021, 06:57:31 by ethereal »

Offline NessDan

  • Thread Starter
  • Posts: 3
You're welcome!

I see the FT9xx comes with a toolchain (https://www.ftdichip.com/Firmware/FT90xToolchain.htm). I guess it doesn't have a HID library...

That's what I'm using right now! They actually do have a lot of USB related functions but nothing around parsing the HID Report Descriptor - they always assume the shape of the data (e.g. if it's a keyboard, they always treat the report as a BOOT report.)

Quote
Do you really need a generic HID parser and decoder? Maybe a simple implementation, capable of interacting only with keyboards, would do the trick. I think some BIOS projects like coreboot (https://coreboot.org/) implement code to interact with USB keyboards. These would use, most probably, the boot protocol, so they would be limited to 6KRO mode only. But their code would be much simpler to follow and maybe it's all you need.

Honestly, a simple implementation would probably work just fine too. Coreboot unfortunately handles only the BOOT keyboard report but I appreciate you passing that to me.

I think I'm going to get a really naive parser made myself - something small that only handles keyboard reports.

I really appreciate all the help ethereal!