######################################################## Kinesis Freestyle 2 / Alcor Semiconductor AU9410 Hacking ######################################################## .. sidebar:: Contents .. contents:: :local: I recently acquired a `Kinesis Freestyle 2 for Mac `_ and really enjoy how it feels, but the layout is not quite to my taste. Having found http://alvarop.com/2013/08/kinesis-freestyle-2-keyboard-mod-to-fix-media-keys and https://github.com/rbasoalto/kinesis-freestyle-fw-hack I decided to follow along and am documenting my effort for the world to see. Theory of Operation ################### At its core, this keyboard is little more than an wrapper around the Alcor Semiconductor AU9410 chip, which is probably how these things should be. The reference manual for that chip is, unfortunately, about as clear as mud, but so it goes. Being a USB keyboard, the chip leans heavily on the USB HID standard, which may be found at http://www.usb.org/developers/hidpage/Hut1_12v2.pdf . This page will make heavy reference to said document as well, naturally enough. In any case, there's an I2C EEPROM off the board that is all that we really care about. This appears to hold a bunch of scary USB descriptors for the host (confusingly, the manual talks about putting *hub* descriptors here), some configuration of the chip itself, and, most importantly, an array of 4-byte values that correspond to the 19x8 keyboard matrix, at 0x1A0 bytes into this memory. (Which makes sense, as it's a 0x400 byte memory and our array requires 0x260 bytes and 0x1A0 + 0x260 = 0x400.) The keyboard has essentially two mappings for its keys: those when the FN switch is active and those when it's not. The Kinesis boards use the AU9410's ability to have the FN button toggle the mode rather than requre the FN button be pressed down. Moreover, when FN is active, the chip pulls LED2 on. These behaviors are documented in the manual (sort of): * Bit 4 of byte 1 in the EEPROM is 1 for FN toggling and 0 otherwise. * Bit 5 of byte 1 is 1 for FN mode to sink LED2 and 0 otherwise. As far as I have been able to divine, there are three fields: a flag and page byte, a "fn-active" value (two bytes, from the USB HID page chosen by the page byte), and a "fn-inactive" value (1 byte, always from the HID keyboard codepage?). * The first byte (at lowest memory address) is a kind of "table selector" for when the FN mode is active. I have observed the following values: +-------+---------------------------------------------------------------+ | 0x00 | Use HID Keyboard page (0x07) | +-------+---------------------------------------------------------------+ | 0x01 | Never observed? | +-------+---------------------------------------------------------------+ | 0x02 | Use HID Generic Desktop page (0x01) (Alcor manual only) | +-------+---------------------------------------------------------------+ | 0x03 | Use HID Consumer page (0x0C) | +-------+---------------------------------------------------------------+ | 0x04 | Unclear (Alcor manual only) | +-------+---------------------------------------------------------------+ Bit 0x40 indicates a role reversal of the two fields: that is, the fn-inactive value gets sent when fn is active, and the fn-active value is sent when fn is inactive. None of the bits in 0xB8 have been observed in the manual or a dump. * The next two bytes determine the behavior for the FN-active mode (unless the reversal bit is set above). If the first byte was 0x00, then the MSB of this pair is the USB usage value from the HID keyboard page. The LSB appears to be a set of flags, which can apparently be OR'd together (0x0C appears in some dumps): +-------+---------------------------------------------------------------+ | 0x08 | Act as if Left GUI (usage 0xE3) was held during stroke | +-------+---------------------------------------------------------------+ | 0x04 | Act as if Left Alt (usage 0xE2) was held during stroke | +-------+---------------------------------------------------------------+ | 0x02 | Never seen in dumps or manual | | | Conjectured to be Left Shift (usage 0xE1) during stroke | +-------+---------------------------------------------------------------+ | 0x01 | Act as of Left Control (usage 0xE0) was held during stroke | +-------+---------------------------------------------------------------+ The other four bits have never been observed in the manual or a dump. The Mac variant uses 0x08 extensively for its special keys. For other pages, these represent the entry in the USB HID page chosen in little-endian form. (That is, the LSB comes first in the firmware.) * The last byte (at the highest memory address) appears to be the HID usage value from the HID Keyboard page (0x07) that the chip sends when FN mode is inactive. 0x00 corresponds to "no action" and the chip could not generate anything above 0xFF, but those are reserved anyway. .. note:: Because Kinesis uses the FN-toggle functionality, almost all of the keyboard's "normal" keys are redundantly programmed, in that they look like (LSB) ``00 00 VV VV`` (MSB) so that they function identically in FN-active and FN-inactive modes. Integrated USB Hub ################## There's a USB hub in the right half unit, too, based on a 4-port chip with only three of them actually used. Surely we can have fun with that! Since I2C is multi-master, we could maybe even stick an I2C to USB interface in there and use it to emulate the onboard EEPROM and act as if we can reprogram the keyboard live! Firmware Layout ############### Based on my "for Mac" edition. It may be helpful to have http://kinesis-ergo.com/wp-content/uploads/2013/06/freestyle2-mac-layout-800x307.jpg open while looking at this table. +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | | 8 (00) | 7 (04) | 6 (08) | 5 (0C) | 4 (10) | 3 (14) | 2 (18) | 1 (1C) | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 19 (1A0) | null | null | null | null | null | null | null | null | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 18 (1C0) | null | null | null | null | ?r alt? | ?r alt? | null | null | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 17 (1E0) | ?eject? | ?r shift?| tab back | tab fwd | web back | web fwd | left alt | ?5 %? | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 16 (200) | ?F1? | ?Y? | copy | sel all | undo | l shift | r shift | ?4 $? | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 15 (220) | end | home | left arr | null | up arr | ?HUT 58? | ?HUT 67? | ?HUT 57? | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 14 (240) | pg down | pg up | ?HUT 56? | ?HUT 55? | ?HUT 63? | ?HUT 5B? | ?HUT 5E? | ?HUT 61? | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 13 (260) | null | off/slp | rght arr | ?HUT 54? | ?HUT 62? | ?HUT 5A? | ?HUT 5D? | ?r shift? | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 12 (280) | eject | delete | down arr | ?HUT 53? | left spc | ?HUT 59? | ?HUT 5C? | ?r ctrl? | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 11 (2A0) | F10 | F9 | F12 | return | F11 | \\ | | backspace | ?r alt? | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 10 (2C0) | null | ?V? | rght gui | ?G? | left gui | ?r ctrl? | cut | 6 | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 9 (2E0) | 0 ) | \- _ | / ? | ?HUT 32? | ' " | ; : | [ { | P | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 8 (300) | 9 ( | F8 | null | . > | null | L | null | O | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 7 (320) | 8 * | ?= +? | ?HUT 87? | , < | null | K | ] } | I | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 6 (340) | ?7? | null | N | M | H | J | Y | U | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 5 (360) | 4 $ | 5 % | B | V | G | F | T | R | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 4 (380) | 3 # | F2 | r space | C | F4 | D | F3 | E | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 3 (3A0) | 2 @ | F1 | F7 | X | ?HUT 64? | S | caps lock | W | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 2 (3C0) | 1 ! | ` ~ | F6 | Z | escape | A | tab | Q | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ | 1 (3E0) | F5 | l ctrl | paste | r alt | null | ?delete? | ?6? | ?6? | +-----------+----------+----------+----------+----------+----------+----------+-----------+-----------+ "null" in the above table means that the bytes are ``00 00 00 00`` in my firmware dump, suggesting that there is no key at that position in the matrix in any product variant. Note that this table is full of insanity! There's no clear "keyboard cut in half" feel to it, suggesting that the inter-half linking cable is carrying many, many more wires than it would have to. WTF. Additionally crazy are that some keycodes are duplicated. They have been marked above as ?x?; it is unclear what these are doing in the design. Perhaps the Kinesis keymaps are based on other products or are interchangable among several? The use of "?HUT xx?" indicates a key that's really unusual; xx are the hex value from HUT keyboard page (0x07). Note that HUT 53 -- HUT 63 are keypad scancodes; the tight packing of these scancodes into the rectangle bounded by 230 -- 29C suggests either a version with a keypad or that the same firmware is used in the Kinesis standalone keypad product. Note that the mac version of this product lacks a "r ctrl" key; it seems plausible that 2D4 is used on the PC version even if it is not on the mac? The positions for HUT 32 and HUT 87 are likely used on the internationalized variant. Programming Header ################## The connector on the keyboard internal board is layed out as follows, when viewed with the keyboard opened but the board still in place. The silk screen with pin numbers is on the other side of the board, annoyingly enough. +-------------+----------+----------+ | GROUND (5) | SDA (4) | SCL (3) | +-------------+----------+----------+ | Protect (2) | KEY | 3.3V (1) | +-------------+----------+----------+ Protect is active-high and pulled high by default. Pull low to enable writing to the chip. Example Interaction with Bus Pirate ################################### http://ww1.microchip.com/downloads/en/devicedoc/21081G.pdf documents the EEPROM's I2C protocol pretty well. * ``[0xA0 0x00][0xa1 r:1024]`` reads entire firmware * ``[0xA6 0xA8 A B C D]`` writes the bytes ``A B C D`` to address ``3A8``; the ``6`` in ``A6`` is the leading ``3`` of the address shifted over one. * ``[0xA6 0xA8][0xA7 r:4]`` reads four bytes from ``3A8``. The ``A7`` is ``A6`` with the bottom bit turned on to indicate that we are doing a sequential read. Don't get the banks wrong between the two halves or the chip will bark at you and hang the buspirate's I2C interface. * ``@`` will read the AUX pin, which can be easily connected to write protect. ``a`` will set it low to enable writing to the chip; ``A`` will protect it again. Switching Left Control and Caps Lock ==================================== * ``[0xA6 0xE4][0xA7 r:4]`` reveals that left control is ``0x00 0x00 0xE0 0xE0``. * ``[0xA6 0xB8][0xA7 r:4]`` reveals that capslock is ``0x00 0x00 0x39 0x39``. * ``[0xA6 0xB8 0x00 0x00 0xE0 0xE0]`` sets capslock to actually scan as left control. * ``[0xA6 0xE4 0x00 0x00 0xE0 0x39]`` sets left control to actually scan as capslock when Fn is pressed and as control otherwise, since I happen to not like caps lock all that much. Swapping Delete and Backslash ============================= Similar to the above: ``[0xA4 0xB4 0x00 0x00 0x2A 0x2A][0xA4 0xB8 0x00 0x00 0x31 0x31]`` I've gone back and forth on this and am currently *not* so swapping these keys. Given the shape of the keyboard and how I type, it's easier for me to hit the bigger delete key. Making F keys non-Fn ==================== While here, I kicked the keyboard over to the UNIX special keys for some of them. +-----+--------------------------------------------------------------------+ | F1 | ``[0xA6 0xA4 0x00 0x00 0x3A 0x69]`` (F1 / F14) | +-----+--------------------------------------------------------------------+ | F2 | ``[0xA6 0x84 0x00 0x00 0x3B 0x6A]`` (F2 / F15) | +-----+--------------------------------------------------------------------+ | F3 | ``[0xA6 0x98 0x00 0x00 0x3C 0x74]`` | | | (F3 / UNIX "Execute"; was ``00 01 52 3C``) | +-----+--------------------------------------------------------------------+ | F4 | ``[0xA6 0x90 0x00 0x00 0x3D 0x75]`` | | | (F4 / UNIX "Help"; was ``00 00 45 3D``) | +-----+--------------------------------------------------------------------+ | F5 | ``[0xA6 0xE0 0x00 0x00 0x3E 0x7E]`` | | | (F5 / UNIX "Find"; was ``03 B6 00 3E``) | +-----+--------------------------------------------------------------------+ | F6 | ``[0xA6 0xC8 0x00 0x00 0x3F 0x79]`` | | | (F6 / UNIX "Again"; was ``03 CD 00 3F``) | +-----+--------------------------------------------------------------------+ | F7 | ``[0xA6 0xA8 0x00 0x00 0x40 0x76]`` | | | (F7 / UNIX "Menu"; was ``03 B5 00 40``) | +-----+--------------------------------------------------------------------+ | F8 | ``[0xA6 0x04 0x40 0x00 0x7F 0x41]`` | | | (swapped and UNIX Mute'd; was ``03 E2 00 41``) | +-----+--------------------------------------------------------------------+ | F9 | ``[0xA4 0xA4 0x40 0x00 0x81 0x42]`` | | | (swapped and UNIX Vol Down'd; was ``03 EA 00 42``) | +-----+--------------------------------------------------------------------+ | F10 | ``[0xA4 0xA0 0x40 0x00 0x80 0x43]`` | | | (swapped and UNIX Vol Up'd; was ``03 E9 00 43``) | +-----+--------------------------------------------------------------------+ | F11 | ``[0xA4 0xB0 0x40 0x01 0x51 0x44]`` (swapped; was ``00 01 51 44``) | +-----+--------------------------------------------------------------------+ | F12 | ``[0xA4 0xA8 0x40 0x0C 0x07 0x45]`` (swapped; was ``00 0C 07 45``) | +-----+--------------------------------------------------------------------+ UNIX-like Left Special Keys =========================== +-------------+--------------------------------------------------------------+ | Undo | Undo (both fn) ``[0xA4 0x10 0x00 0x00 0x7A 0x7A]`` | +-------------+--------------------------------------------------------------+ | Cut | Cut (both fn) ``[0xA4 0xD8 0x00 0x00 0x7B 0x7B]`` | +-------------+--------------------------------------------------------------+ | Copy | Copy (both fn) ``[0xA4 0x08 0x00 0x00 0x7C 0x7C]`` | +-------------+--------------------------------------------------------------+ | Paste | Paste (both fn) ``[0xA6 0xE8 0x00 0x00 0x7D 0x7D]`` | +-------------+--------------------------------------------------------------+ | Select All | Select (both fn) ``[0xA4 0x0C 0x00 0x00 0x77 0x77]`` | +-------------+--------------------------------------------------------------+ Swapping Home/End/PageUp/PageDown ================================= +-----+------------------------+-------------------------+ | Pos | Original | Now | +-----+--------------------------------------------------+ | 220 | ``00 00 4D 4D`` (end) | ``00 00 4E 4E`` (pgdn) | +-----+--------------------------------------------------+ | 224 | ``00 00 4A 4A`` (home) | ``00 00 4B 4B`` (pgup) | +-----+--------------------------------------------------+ | 240 | ``00 00 4E 4E`` (pgdn) | ``00 00 4D 4D`` (end) | +-----+--------------------------------------------------+ | 244 | ``00 00 4B 4B`` (pgup) | ``00 00 4A 4A`` (home) | +-----+--------------------------------------------------+ Other Remappings ================ +-------+-----------------------------------------------------------------------+ | Power | Pause / Print Screen (with fn) ``[0xA4 0x64 0x00 0x00 0x48 0x46]`` | +-------+-----------------------------------------------------------------------+ | Eject | Insert (both fn) ``[0xA4 0x80 0x00 0x00 0x49 0x49]`` | +-------+-----------------------------------------------------------------------+ | Web | Escape (both fn) ``[0xA2 0xF4 0x00 0x00 0x29 0x29]`` | | Next | | +-------+-----------------------------------------------------------------------+ | Web | Alt+Leftarrow / Stop (with fn) | | Back | ``[0xA2 0xF0 0x00 0x04 0x50 0x78]`` | +-------+-----------------------------------------------------------------------+ .. | Tab | .. | Next | .. +-------+-----------------------------------------------------------------------+ .. | Tab | .. | Back | .. +-------+-----------------------------------------------------------------------+