1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
# Coolermaster RGB L hackery
! describe USB architecture and interfaces.
## A tale of what happens to a keyboard with "Windows-only" drivers
A little while ago I set out to find a good keyboard, having concluded I've been doing less for lack of wanting to type. To be arbitrarily difficult, I wanted a keyboard that met all of the following:
* Backlit
* Mechanical (Cherry Blues, to deafen myself)
* Individual-key RGB
* Host-controllable key colors
* Linux support for key coloring
* Bonus: 10-key input
Several keyboards fit {backlight,mechanical blues,individual rgb}, but I only found one that didn't suggest key recoloring to be a difficult process. Corsair's RGB keyboard offerings seem good, but only had red switches - but https://github.com/ccMSC/ckb appears to be a good tool for key controls.
Eventually I settled on a Coolermaster Masterkeys RGB - the ridiculous name gave it a few extra points. There's clear support from reviews for interactive color updates, so latency is low... ish. Advertising materials indicate there's a 72mhz ARM chip hidden in there, so if there's any need, maybe we can hack away at that for something more specialized. Even has an "SDK" for writing code to change key colors from $application\_of\_choice! Individual-key RGB, comes with Cherry Blues, seems like a decent option...
### But the driver and configuration tools are Windows only
Picking apart the keyboard communication protocol and writing my own controller is totally my jam, so this isn't so bad. But first, some Linux USB interaction background:
The most reliable way of interacting with USB devices from outside the kernel seems to be via usbfs. This is the pseduo-filesystem that provides nodes to interact with USB devices. It seems that the typical pattern is to open the appropriate USB device file, and issue ioctl()'s to actually interact with it. At least, that's what I do, and that seems to work well.
*there is, of course, libusb, but (for no good reason) I don't want to include a large library as a dependency for my tiny keyboard controller - from memory libusb is larger than the sum of all code I plan to write for this, and all I'm doing is finding a USB device and reading/writing to it!*
**also, this is notably different in a few places from trying to do these same operations as a kernel module. But I don't want to write kernel code, and this doesn't need to be, so USB device interaction from the kernel isn't really mentioned here**
Of all aspects of USB device interaction from Linux, finding the appropriate device seems the least robust, and by extension, the scariest. The only method that appears portable is to assume the usbfs device tree exists at `/sys/bus/usb/devices/` and traverse that for a USB device whose vendor and product IDs match our keyboard. **what happens if I plug in two of the same keyboard??**
So, walking all entries in `/sys/bus/usb/devices/` and looking for a directory `foo` where `foo/idVendor` and `foo/idProduct` match the keyboard (`2516` and `003b`, respectively), then recording `foo/busnum` and `foo/devnumb`, appears to be The Way. What happens if the USB device tree isn't at `sys/bus/usb/devices/` (perhaps by compile options?) -- it breaks! I'd love to be robust against this, but don't know how to be.
Once we've obtained `bus` and `dev` numbers, we can build the path to the usbfs node - `/dev/bus/usb/_bus_/_dev_`. Opening this like any other file gives us a handle for all the `iotcl()` later on.
Now that we have a handle for the right device (hopefully), our process has to take ownership of it. I'm rude here, so I issue a `USBDEVFS_IOCTL` with `USBDEVFS_DISCONNECT` to break any prior ownership of the device, then `ioctl(..., USBDEVFS_CLAIMINTERFACE, ...)` to own it in my process.
At this point we're ready to roll for anything keyboard-specific! To at least try to be a good neighbor, I do issue `ioctl(..., USBDEVFS_RELEASEINTERFACE, ...)` before exiting the program.
## Coolermaster Masterkeys RGB controlling
[/] protocol sniffing
[ ] ARM firmware dump
[ ] driver RE
[ ] controller RE
With USB device interaction out of the way, we're left with the question of what the right incantations are to control the keyboard. Fortuitously it turns out we can just sniff packets of the keyboard interacting with the Windows tooling and replay that to control it from Linux. USBPCap and Wireshark were extremely helpful here.
### VERY WIP UNDERSTANDING OF STOCK PROTOCOL
The keyboard talks with a few endpoints either direction - one to send key information, one to recieve programming information from the host, and a third keyboard-side for indeterminate use. The Windows controller tool communicates with it, but I've not been able to figure out to what end.
It seems like messages are structured around 64-byte blobs, usually, with most responses echoing the same blob back with a few bits sometimes flipped. When the keyboard programming tool starts, it sends a packet like
```
0x51, 0x28, 0x00, 0x00, ...
```
that I suspect initializes the keyboard to recieve control messages from the host.
From here, there are a handful of messages that I've figured out:
* 0x52 0x00 - this reads which of the four P-states the keyboard in. The response is in the echoed back packet.
* 0x41 0x00 0x00 0x00 X - this sets the keyboard to P-state X.
* 0x41 0x01 - this sets the keyboard in a mode to recieve custom key colors from the host
* 0x40 0x20 - this gets a version number string from the keyboard, presumably to check if the ARM firmware blob needs updating.
* 0x51 0xa8 P 0x00 (R, G, B) * 20 - this sets 20 key RGB values for indices `P*20` to `P*20 + 19`. For an RGB-L, the pages are `0, 2, 4, 6, 8, a, c, d`. I don't know why `d` rather than `e`, since `c` definitely has 20 keys associated with it.
|