lentinj/tp-compact-keyboard

Customise USB Compact Keyboard Firmware

Opened this issue · 158 comments

(I am only half joking here)

The keyboard has an upgradable firmware and a documented microcontroller:
https://support.lenovo.com/us/en/documents/pd026745
http://www.sonix.com.tw/article-en-1002-3048

It would be great to have a keyboard with a working middle mouse button. A completely clean room firmware would allow this.

As a bonus, it would be great to implement proper negative inertia, which has disappeared from newer TrackPoint devices:
http://blogs.epfl.ch/icenet/documents/Ykt3Eext.pdf

Interesting. Did you try upgrading your own keyboard? Notice any different behaviour other than the listed key combination not working?

Yes, I did update it. No visible changes. I tried sending it some new parameters from Linux but it still has the annoying middle-button behaviors.

So I have been doing a little reverse engineering (I'm getting this keyboard tomorrow and just wanted to tinker a little bit) and these are my findings so far:

  • Lenovo's firmware upgrade tool tp_compact_usb_kb_with_trackpoint_fw.exe file is a modified version of Sonix's SONiX_USB_MCU_ISP_Tool_V2.3.0.6.exe (BinDiff shows a big percentage of exactly matching functions), you can see it in a resource editor and when comparing side by side:
    resource fun
  • Inside Lenovo's tool is a BINARY resource which is an executable originally named LenovoTest.exe (Sonix's tool has no such thing)
  • This executable does absolutely nothing, but appended to the end is a 24892 bytes file that appears to be an SN8 file (albeit encypted with a simple XOR key 0x5A)
  • When using Sonix's SN8 C Studio with a template for the SN8F2288 chip combined with the C2AsmDemo source code you can get it to produce an SN8 file of similar size with similar contents (most of the header appears to match).
  • The function that decodes (part of) the SN8 file is located at 0x40E080 in Lenovo's tool for people that might be interested in looking further.
  • A disassembler is available for the SN8F2288 instruction set called dissn8.

My plan is to figure out how the SN8 file format works (probably it just directly flashes certain regions to the ROM) and create disassembler cores for IDA/Binary Ninja to be able to reverse engineer the Lenovo firmware.

dal00 commented

@mrexodia Did you have time to do some more work on this?

@dal00 I did do some work on this but recently I didn’t have much time. I came in contact with @vpelletier from https://github.com/vpelletier/dissn8 and he in fact reversed most of the firmware.

At the moment I’m working on an emulator for the whole chip to do debugging on changes to the firmware without risking to brick my board. This is coming along nicely and at the moment I’m implementing USB support inside qemu.

It’s mostly a project I work on during flights though so don’t expect too much.

Also, doing a clean room firmware could be possible but is a significant effort without benefits other than having an open source firmware. My plan is to just hook in the real firmware and replace the keyboard routine with something more to my liking...

dal00 commented

@mrexodia Cool, thanks for the update! What kind of changes do you have in mind?

Hi,

Mostly I want to change mappings for Fn+something to arbitrary keycodes (F13 and certain other keys that no keyboard has for a personal project) and also allow swapping the Fn/Ctrl keys (although I got used to that by now).

Probably I could have made my changes by now but I’m just doing the emulator stuff to learn 😀

Here is some progress on an interactive disassembler/emulator for the platform: https://github.com/mrexodia/SN8F2288_gui

aiju commented

I figured out how to disable the automatic hardware "scrolling".
If you start with version 3.30 of the firmware (SHA-256 7116a3819ee094857d21e4671cb6cf953d582372126f0f6728f6b2421eda7bd4) and write 0x5A5A to locations 476076, 475710, 475718 and 481870 (in decimal), you should get a file with SHA-256 7fd326d15862211932ce73965c2ba2b86d87b918a54f25d7af37eef1b29d27ba.

If you use that to flash the firmware, it functions as a normal three button mouse would (obviously, one can still do software fake mouse wheel shenanigans if one wants to). I think it still intersperses some scrolling reports depending on the mode, but all the actual scrolling calculations should be disabled.

This seems to work fine with Windows (with, and presumably without, the Lenovo driver), but Linux (the lenovo_hid driver, I think) seems to ignore the middle mouse button events. I mostly intend to use the keyboard with Windows (all my Linux machines are actual Thinkpads :)), so I'm not going to debug this further.

Some obvious NB:

  1. I cannot, and will not, guarantee this won't brick your keyboard. Use at your own risk.
  2. I cannot, and will not, provide patched binaries (for reasons of copyright).

Can you elaborate on the process to come to this conclusion for the patch?

aiju commented

I used dissn8 and looked for code that writes to the USB endpoint FIFO and from there figured out which memory locations are used to hold button state, and from there which places check for middle mouse button. (I'm not really sure to what extent I can legally share specifics about this...)

Kwarf commented

I really want to swap the Fn and Ctrl keys. It's crazy that this isn't possible with the default firmware, since their laptops have the feature to swap. It's increcibly hard to press Ctrl+Shift with the current (ISO) layout.

Do you know if this would even be possible with just a FW mod, or if the key matrix is designed so that some combinations wouldn't register? I just want to know if there's any point in looking into this, or if i should just do the HW mod.

From what I looked at when disassembling the keyboard, it is just a laptop keyboard glued to a plastic body (you can remove the body without removing any screws if you want to look inside yourself). This tells me that there is no problem with the design of the key matrix.

It is possible to modify the firmware to switch the Ctrl and Fn keys, but I'm hesitant of trying because I don't want to brick the keyboard. Recently I implemented interrupts and timers is my emulator and I can now observe pins being read/written, but no more time 😄

Did you guys make any progress with this?

I really want to be able to swap the Fn and Ctrl keys as well.

This is the closest to progress I got: https://github.com/mrexodia/SN8F2288_gui

Personally I just got used to it and had to drop the project for time reasons...

I improved dissn8 some more, the biggest addition being the ability to generate global function call graph and per-function branch graphs (in graphviz "dot" format), closely second to per-address read/write/set/clear access tracking.

I also continued my firmware analysis a bit further, but still haven't made up my mind about releasing the dissn8 .cfg file I write firmware-specific findings to: there is no code in it but symbolic names I came up with for ram & rom addresses, and some comments. IANAL, but this does not look "clean room" enough to me. In any case, the firmware can be fully disassembled with the chip declaration file only, all the (few) jump types the CPU has are supported. The only things missing in dissn8 are ram bank selection (which this firmware doesn't use, and given how it uses B0 and non-B0 instruction variants for the same variables it looks like the compiler used does not support them itself) and possible direct program counter assignments as opposed to increments (which this firmware also doesn't use).

Just a cautionary word: firmware flashing is purely handled by the firmware itself, the chip does not on its own assist in getting the device on the USB bus. So the next step after a bad flash will very likely be to poke at whatever JTAG capabilities the chip may have (I have not looked into this myself). Given how obscure the code is in some places (I think I found a periodic event occurring every 14.5 hours... that can't be right), combined with the at places obscure documentation of the chip itself (timer source can be external or internal, internal being "fcpu or fclk" but without any indication of which it really is - and there is a factor of 2 to 8 between these two...), this means I would be very surprised if someone achieves extended firmware modifications without bricking a few.

I am making enough progress on the analysis that I think I can prepare a description for a clean-room reimplementation:

  • general microcontroler settings (clock source frequency, clock ratios, ...) as these cannot be modified by reflashing and need to be known to respect timing constraints
  • port settings & what is connected on which (4 pins are still resisting my attempts at the moment)
  • keyboard matrix (scancodes for each cell)
  • structure of i²c messages exchanged with the mouse controler
  • flash general layout (where is built-in flasher, where is persistent storage and what is stored)
  • flashing protocol (I may be able to provide one implemented in python)
    (maybe a bit more ?)

The rest should be available already: CPU doc, USB HID doc. A libre assembler tool should be easy to implement. A libre C compiler should be a lot more effort, and I doubt it would be worth it (unless we have an sdcc dev in the audience maybe ? But then it will be another arch costing in maintenance for very few known device types...). So the implementation will likely have to be done in pure assembler.

To give a very general idea of the amount of effort needed, the proprietary firmware has around 6500 assembler instructions (flasher included). This chip's assembly dialect is very simple (a bit simpler than the 8051, to give a more widely known example). I believe this would require a few weeks of work, maybe a few days in collaborative sprint mode [EDIT: "days" is very optimistic, let's leave it at "weeks"].

Is anyone motivated to take this on, and non-tainted by the proprietary implementation ?

Fun discovery of the day: if you press "return" while plugging the keyboard, it boots in flash mode. It's still under firmware control, but that means that if we do not touch the early initialisation code & flashing program, we can have a quite fail-safe setup for experimenting.

Fun discovery of the day: if you press "return" while plugging the keyboard, it boots in flash mode. It's still under firmware control, but that means that if we do not touch the early initialisation code & flashing program, we can have a quite fail-safe setup for experimenting.

Sadly, that's not possible... Early initialisation code calls functions well past this offset, before checking key state. Also, erase pages are huge compared to how fast the code progresses in initialising everything, so custom firmware would get an already initialised state with interrupts enabled, and would have to carefuly place stub functions to satisfy early init, and tread around the memory offsets it uses...

Such frankenware seems much more dangerous than reimplementing the key polling ourselves early in the init.

I have most of the clean-room spec written. I still feel there is something fishy with i²c, and the chip at the other end seems famously undocumented (custom ASIC ?), so I have to check some more before they are ready.

I still would like to hear that someone is motivated to work on the implementation part of the firmware.

In addition, I would also like to hear from anyone willing to review the clearoom spec without being candidate for implementation, in order to advise on what is missing and what shouldn't be there (ex: I feel the keyboard matrix may be too much, as it can be deduced during implementation by using the keyboard - although some cells will not be found until enough layouts have been tested).

I have written a flashing tool, and have verified with actual hardware all except the dangerous commands. I ordered a second keyboard to not brick the one I use (non-local layout). I do not intend to release it until I have used it myself, so it's not in my repository yet.

Curiosity killed the... mouse.

Below, a cautionary tale for the curious:

<my life>
I was snooping around the i²c bus, which has no probe pad. So I soldered 2 wires to the pull-up resistors on SCL and SDA. Which are surface-mount components. One successful measure later, and I was tearing down my setup.

Then a brainfart hit, and I burned myself with the soldering iron. The recoil caused SCL's pull-up to lift. No way to get solder back under it. I removed it completely, resoldered the pads, and the resistor suddenly decided it wanted to travel at high speed to somewhere.

So I discovered a new feature of the firmware: if it cannot initialise the mouse chip (via i²c), it reboots the keyboard. So it won't work as a mouseless keyboard until I get my hands on what seems to be a 3.3k 1005 and somehow succeed in my first intentional SMC solder.
</my life>

The good thing is that now I'm less scared of trying my flashing tool, as boot-time flashing mode is still available (RETURN press on plug is checked before mouse init).

The good thing is that now I'm less scared of trying my flashing tool, as boot-time flashing mode is still available (RETURN press on plug is checked before mouse init).

Done. flashsn8 pushed to my repository. Works-for-me state. No warranty, use at your own risk. Tested on linux with python-libusb1 installed (as of Debian sid).

A libre assembler tool should be easy to implement.

Done, pushed to my repository.

It is very crude, error reporting is abysmal (what's a line number ?), but it's able to assemble the source produced by dissn8, and end up with the same binary image as dissn8's input and should be able to cope with some style variations.

So the libre toolchain is now bootstrapped.

So it won't work as a mouseless keyboard until I get my hands on what seems to be a 3.3k 1005 and somehow succeed in my first intentional SMC solder.

Success !

It was hard, and I had to wipe the board clean of burned flux twice, but I eventually succeeded. I suspect I lifted the pad along with the original resistor, and had to make a solder bridge to a nearby via.

Now that board, which sustained a few erase/program cycles with my tool (with the original firmware), is back in working order. And I now have a spare keyboard in another layout - and could confirm I could swap the key matrices between both.

I'm currently busy writing asm libraries for the chip. I've written flasher interfacing, a bit-banging I²C and am progressing on USB implementation - but I did not test much yet. I²C is a pain to check even in the original toolchain's simulator, as it does not have an accurate port emulation. I expanded the features of the assembler enough to be able to work on modular sources (ram allocation, symbol exports/imports, anonymous jump targets).

EDIT: Just pushed the - still very crude and 95% untested - asm libraries in my repository.

Hey Vincent!

Just wanted to chip in and say I'm following attentively your progress, as I'm writing this from my very own compact USB keyboard.

I really appreciate – and judging by the reactions on your comments, I'm not alone – that you continue to report your progress regularly, so cheers!

Thanks a lot for the support, it really helps keeping my motivation !

I will try to push this as much as I can, although I'm still worried that I cannot on my own implement the final firmware: I am tainted by my analysis of the proprietary implementation. Even though keyboard reading is not rocket science, copyright is copyright, and I do not want to do all this work (as fun as it may be), then push a firmware just to have it taken down by a DMCA claim. So I'm implementing from specs as much as I can when specs exist (and am fairly confident I am not reproducing idiosyncrasies of the proprietary firmware, although there are only so many ways to implement a USB SETUP packet dispatcher), but will need some external help to tie it all together with the board- & device-specific stuff.

On a related motivational note, I realized that one feature I miss from old thinkpad keyboards is the Fn+up/down/left/right key combo for stop/play|pause/previous/next multimedia keys. A custom firmware would bring these back.

Now that I have a bunch of untested ASM code around, my next goal is to test it automatically, and for this I need a scriptable emulator (ex: run until this address is reached in at most this many cycles, then flip that port bit, then run until that address is reached...). I'm not yet sure how to go: there is @mrexodia 's emulator but I am not familiar enough with C++ to add scripting to it. Or I could implement an emulator in python, but this will also take quite some time as I have to start from scratch.

With regards to the emulator, in the end it didn't take me very long to implement emulation of the instructions. The main issue for me is implementing the I2C/USB/timers/interrupts because I'm not at all familiar with stuff at a level this low...

I like the progress a lot though!

With regards to the emulator, in the end it didn't take me very long to implement emulation of the instructions.

I started implementing one and I confirm instructions is not the hardest part.

The main issue for me is implementing the I2C/USB/timers/interrupts

This is indeed the hardest part.

Ports are also harder than they seem on the surface: is the port driving the line (high/low), does it have an internal pull-up, is there an external pull-up, is the line pulled low externally... I find the SN8F2288 pin schema in the spec underwhelming:

  • internal pull-up is not described as controlled by pin direction. This would make sense if all pins were open-drain (so the chip could either be driving high or pulling low through the pull-up), but only 4 pins are documented as having an extra open-drain configuration. Having totem-pole outputs and pull-ups makes no sense to me.
  • it is not specified whether reading a port bit reads the drive bit from register or actually samples pin state: on an open-drain the pin could be set to let the line float (1) but reading from that same pin could produce a 0 if anything else is pulling the line low.

So far I could implement pins by following my own assumptions (read samples from line, pull-up disabled on output pins), but I have not verified on the actual hardware (...because I do not have any firmware code nearly ready for a first flash). Each pin is basically a voltage divider circuit, and I compare computed voltage to logic thresholds. I'm sure it can be simplified (binary voltage & binary impedance ?) but I'm happy that this model is enough to get a bit-banging I²C implementation able to communicate.

Simulator progress:

  • instructions work (I'm sure there are a few bugs left - like interrupts firing while a certain instruction is running). I have implemented them at instruction granularity level (and not cpu cycle), as I do not know what each cycle does in multi-cycle instructions and it is easier to implement interrupts this way.
  • non-memory registers are implemented (ex: writing to PECMD triggers the right action, writing to WATCHDOG resets the watchdog counter, ...)
  • @YZ implemented, raises an exception if YZ points at @YZ (I suspect that on original chip this would be either a constant value or a "hidden" normal ram cell). I also assume pointing YZ to a register and accessing @YZ does the same as accessing that register.
  • call stack is implemented, but I do not know which address the µC pushes (return address or call address). I assume return address, as it makes interrupts easier to implement.
  • interrupts & reset are implemented
  • clocks are implemented (Fhosc & Flosc), with all power modes supported (but untested)
  • watchdog is implemented (12-bits counter on Flosc)
  • timers are implemented and work, but no buzzer/external counter support for TCx.
  • dummy ADC, MSP, UART, PWM, SIO placeholders. I realized that SIO is really I²C without the name. It is even on the same port as original firmware uses for bit-banging implementation, just not on the same pins. No idea why they didn't use it.
  • incomplete USB implementation (no way for script to do anything with the bus yet)
  • flash erase & program works (only tested on the config save page managed by original firmware)
  • ports are implemented, without interrupt support

On scriptability level, it is terse:

  • one-instruction step function
  • write & read watchers
  • current cycle number
  • current run time
  • overhead is huge (around 1 second to simulate 10ms of cpu time). Improving this is very low on the priority list (feature completeness comes before, as does correctness on details). I will try pypy before trying to optimize the code.

I also implemented (separately) board-specific IOs: key matrix, minimal I²C slave understanding the same protocol as the real one (...all constant bytes considered magic, I have no idea on their meaning). With that board I am able currently to at least reach a successful mouse init (about 200ms of run-time in the original firmware, including 50ms of flash page erase). I still have to test the key matrix. USB is not implemented at all (need in-CPU-emulator support for this). I²C slave implementation is ugly, with code duplication - I failed a lot on this part.

On to the next: USB support. Once working and I could get a few HID reports from the original firmware I will probably push the simulator.

EDIT: Woops, I pinged "yz" user when talking about the register. Sorry.

Once working and I could get a few HID reports from the original firmware I will probably push the simulator.

I did not go all the way to HID reports yet, but USB EP0 is working. I pushed the simulators for the CPU and the board. Next, HID reports.

On my machine (i5-5200U @ 2.20GHz) the very basic test scenario I committed (which simulates around 152ms of µC time) takes 6.7s with python2.7 and 1.9s with pypy 7.0.0 .

And a sneak peek at the output of current test scenario, which simulates USB enumeration & configuration and ends as soon as mouse init over I²C is over:

$ pypy ./ku1255_sim.py board_fw.bin
./simsn8.py:1105: UserWarning: Ignoring write to 0x00a5: 0x00
  value,
./simsn8.py:1114: UserWarning: Ignoring write to 0x00c3: 0x00
  value,
./simsn8.py:1114: UserWarning: Ignoring write to 0x00d3: 0xff
  value,
./simsn8.py:1114: UserWarning: Ignoring write to 0x00e3: 0xff
  value,
pre-address device desc: 12 01 00 02 00 00 00 08
post-address device desc: 12 01 00 02 00 00 00 08
full device desc: 12 01 00 02 00 00 00 08 ef 17 47 60 30 03 01 02 00 01
config desc head: 09 02 3b 00 02 01 00 a0
len 59
config desc: 09 02 3b 00 02 01 00 a0 32 09 04 00 00 01 03 01 01 00 09 21 00 01 00 01 22 51 00 07 05 81 03 3f 00 0a 09 04 01 00 01 03 01 02 00 09 21 00 01 00 01 22 d3 00 07 05 82 03 3f 00 0a
1
0
   103.834ms start condition
Received write address, asserting SDA
Received data byte 0xfc
   104.042ms stop condition
   150.874ms start condition
Received read address, asserting SDA
Sending 0x80
CPU ACK
Sending 0x00
CPU ACK
Sending 0x00
CPU ACK
Sending 0x00
CPU ACK
Sending 0x00
CPU NACK
   151.498ms stop condition
   152.124ms start condition
Received write address, asserting SDA
Received data byte 0xc4

A very short progress report this time: I can get an HID report from mouse (but I did not test very intensely either), but key matrix is full of bugs:

  • I can get some keycodes but not all, and it seems to depend on what key presses happened before, or what delay between presses. As each keypress needs thousands of instructions to run, I did not debug step-by-step so far. I suspect a bug in Port class and/or related impedance-retrieval functions.
  • USB endpoints are behaving differently from the real keyboard: on real keyboard transfers are only ACK'ed by device when there is something to send: either a key press, or a key release. In my test code, there seems to be a pump-priming effect, where the first key press causes a report to be sent but after that they never stop. Which means reports and keypresses get out of sync, and I can't be sure what key state the report corresponds to. Adding super-long sleeps (to let µC run and hopefully stabilise) does not seem to help.

I think I need to step back for a while to stop running in circles.

Progress at last !

I could finally identify several mistakes in voltage & impedance computation for the key matrix, two mistakes in instructions emulation, and finally I can get HID reports for key events, pressing all keys one by one.

One thing worries me though: I could not find in the spec how the µC behaves when receiving an enabled interrupt while already in the interrupt handler (ex: FT0IEN is set but FGIE is cleared). It is hard for me to imagine that interrupt would just be lost, but I can't be sure, and I don't know how/when it gets retriggered. Early firmwares should avoid relying one more than one interrupt source.

I implemented this in my emulator iirc. From the manual there was something like

if the interrupt is enabled, and no other interrupt is in progress

This seems to be done by the GIE register.

Specifically:

Once interrupt service is executed, the GIE bit in STKP register will clear to “0” for stopping other interrupt request. On the contrast, when interrupt service exits, the GIE bit will set to “1” to accept the next interrupts’ request.

True. I may be looking too hard for ambguity, but "next interrupt" can mean "next trigger at the source" (and answer my question) or just "next to be handled" and the ambiguity remains. The latter could be true if the interrupts are level-triggered rather than edge-triggered.

My very limited knowledge in FPGA/HDL is insufficient to help me make a guess.

At least, I tried in vendor's emulator to set an IRQ flag with corresponding IEN and GIE set, and it did not trigger the interrupt handler. So either there is a hidden register set by actual interrupt sources which would somehow be cleared (when entering interrupt handler ? when firmware-visible IRQ flag is cleared ?) or the IRQ is really lost.

One thing which annoys me especially if the IRQ is lost is that the USB device in the µC sets a flag when receiving a CONTROL USB packet which makes the EP0 buffer immutable from USB until after firmware cleared that flag. Losing corresponding interrupt makes the device stuck. Maybe original firmware relies on another interrupt firing "soon" after, letting the USB interrupt be found (and this time losing whatever just triggered, which would be TC1). But that would seem like a big hack. Even with interrupt re-firing I'm already facing issues where some random-ish key presses take much longer than others to get reported on USB - and I do not know why yet.

Currently I'm trying to test every single instruction on original emulator, taking its behaviour as gospel and checking how mine reacts. So far it let me find that I am not incrementing PC in the correct sequence during instruction execution: it needs to be incremented before the instruction runs, and if instruction has another increment (conditionals) then that must be done separately later.

From when I was implementing interrupts I remember that there was a timer interrupt that was fired constantly. I guess if the USB interrupt disappears that timer would indeed pick up the USB interrupt. Not entirely sure though, I just observed a timer being hit frequently, but I didn't get far in startup because I didn't implement any voltage/usb/i2c stuff.

Once interrupt service is executed, the GIE bit in STKP register will clear to “0” for stopping other interrupt request. On the contrast, when interrupt service exits, the GIE bit will set to “1” to accept the next interrupts’ request.

I found another hint that your understanding is correct:

If the T1IEN = 0, the trigger event T1IRQ is still set to be "1". Moreover, the system won’t execute interrupt vector even when the T1IEN is set to be "1"

This phrasing is missing on several interrupt descriptions, and on some where it is present *IEN is replaced by *IRQ, which is probably a bad copy/paste/replace. But I believe this means "if this interrupt source was disabled when it triggered, enabling it later will not cause interrupt handler execution". Which very much looks like edge-triggered interrupts, and I guess this sentence is likely to also apply to GIEN.

So I still have a bug somewhere in my simulator, as simulation dies on second USB standard request (setAddress, but I think it's really just because it's the second, or because of a race against TC1).

Currently I'm trying to test every single instruction

Done. It did find a few minor bugs: stack overflow warning was off-by-one (cough), SBC was not using the carry flag properly, nibble carry was very broken, logical operations reading from memory and writing to A were taking one cycle too much.

There are remaining uncertainties about how instructions explicitly affect PCL, so it's better to avoid using it outside of B0ADD PCL, A (jump tables), and even then the table must begin and end within the same PCH value. I get the feeling of how vendor's simulator behaves, but I do not know if this perfectly mirrors what the hardware is doing.

Having an enabled timer just as a missed-interrupts garbage collector seems a good idea. Original firmware takes quite some time between getting on the USB bus and enabling a timer, which was what caused my simulation to get stuck (some USB request getting into a large timeout whenever the interrupt lost the race).

I discovered that vendor's assembler does a few extra checks on when PCL can be read and apparently when/how {,B0}BTS{0,1} can be used. The details are implemented in MacroAssembler_MCU.dll and I do not think I can have the patience of finding my way through it (disassembled x86 is a whole other beast to me, especially if it turns out to come from C++ - I'm not familiar with the language in its human-readable form, and even less in its CPU-readable form).

Next step: finding bugs in asmlib.

Just wanted to say that this is an amazing effort! A lot of respect for the skills to hack on this, unfortunately I don't have enough experience with this corner of tech to contribute. Most important to me would be support for swapping Fn and Ctrl. Would it also be possible to add Home and End to respectively Fn+ArrowLeft and Fn+ArrowRight? Anyhow, keep up the good stuff and thanks a lot for even making an effort 🎉

Just wanted to join in... I also started working on a custom firmware based on this article (https://hohlerde.org/rauch/elektronik/projekte/tpkbd-fix/, in German)... At the moment I'm trying to set up a working environment.

Swapping the FN and CTRL keys would be astonishing for the keyboard!

Any progress on this so far by anyone? Then I would not have to do it myself :D

aiju commented

I suspect the FN+CTRL swap can be done by hexediting the normal firmware binary (like my middle mouse button change).

@aiju Never thought about that, actually... How Did you figure out which values had to be replaced?

aiju commented

Study the disassembly to find a place to modify. (See my comment above)
Then I had a script that replaced the bytes in the .exe and reran the disassembly and diffed against the original, to make sure I'm changing the bytes I intend to change.
It took me some trial and error to get it working. I was worried about bricking it but I took the risk and it worked out fine. (But, of course, the risk is real; be careful)

Currently Fn+Arrow{Up,Down,Left,Right} does nothing.
It is really helpful to be able to do Fn+LeftArrow for HomeKey and Fn+RightArrow for EndKey
Is there anyway to achieve this using the asmlib firmware?

I will look through IDA tonight for this feature. Maybe it already exists as an option in the stock firmware. It would be a lot easier to enable by sending an hid command.

Is there anyway to achieve this using the asmlib firmware?

As the name tries to imply, dissn8 asmlib is just a library of functions for the chip. It does not implement a full keyboard firmware, but a keyboard firmware should be possible to implement from it. I have sadly burned out on this project so I did not finish the USB part of the library (which is by far the most complicated part) so even asmlib itself will need to be debugged while implementing the firmware.

What needs to be done outside of the scope of asmlib is something like:

  • reading the key matrix: debouncing, key rollover resolution
  • implementing special key combinations (top row alternate functions, SysRq, Pause, ...) so the keyboard reports different scan codes for these
  • trackpoint (small protocol over bit-banged i²c, pointer acceleration, ...)
  • implementing the USB HID protocol
  • saving options to flash (Fn lock, pointer acceleration, ...)
  • booting to flash mode: original keyboard reads "return" key state early during power-up init to allow flashing if the firmware has bugs in its more complex parts

And beyond this, some minor compatibility work/care to take:

  • find a way to tell the computer that this is not really a lenovo keyboard, so quirks are not needed
  • store options in a different place to the original firmware, to allow flashing back (or erase that area)

Then the fun options can be implemented: more key combos, no flash mode access from normal firmware (IOW without the power-up time magic key pressed), restoring multimedia keys or home/end on arrow keys, ...

All this is a lot of work. I tried to document the board surrounding the sn8 chip (in the most verifiable way: the ku1255_sim.py emulator, able to run the original firmware). There are uncertainties about the accuracy of my emulation of the chip though: maybe the spec is too vague, maybe I misread it, maybe the vendor's emulator is not precise enough and instruction subcycles are not accurately represented... I suspect a few keyboards will die in the process of writing a better firmware, unless done extraordinarily carefully and defensively. Bricking is a very real risk with this chip. Once the firmware cannot jump on its own to the re-flashing entry point (flasher from on_chip_flasher.asm) or the flashing functions are damaged (they are not protected against flashing), unbricking will require desoldering the chip and figuring out the (AFAIK) undocumented pin-level bit-banging flash protocol (which should not be extremely complex, but can quickly get frustrating).

Lastly, a few recommendations to minimize bricking risk when writing a firmware for this chip:

  • avoid IRQs. The firmware will anyway be a big busy loop and there is a single IRQ handler anyway, so it should not be very different to polling, and avoids any weird effect of context switches that my emulator gets wrong, or cascading IRQs, etc. The re-flashing functions implement USB without an IRQ handler, so it must be possible to completely do without.
  • avoid touching the program counter (outside of normal instruction sequencing), be careful about the base address of computed jumps (do not cross 256 words page boundaries) when you have to
  • avoid touching the stack pointer (outside of jump & return instruction)

For people who brick their devices I do programmer on arduino https://github.com/Marisa-Chan/sn8fisp

Thanks to the amazing work of @vpelletier and @mrexodia I managed to swap Fn and Left Control in the firmware. Here's the TLDR:

  • Download the firmware update tool for FW ver. 3.30 from Lenovo's homepage (sha256sum of tp_compact_usb_kb_with_trackpoint_fw.exe is 7116a3819ee094857d21e4671cb6cf953d582372126f0f6728f6b2421eda7bd4)
  • Open the file in the hex editor of your choice
  • Change position 0x74004 (475140 decimal) from 0xba to 0xf5 (this changes the function of the "LeftControl" key to "Fn"
  • Change position 0x740BA (475322 decimal) from 0xf5 to 0xba (this changes the function of the "Fn" key to "LeftControl"
  • The sha256sum of the modified file is 123143092dab578550c87a62526b07a6c5f06c047f2455be87971aa51577e300
  • Use this file to update your keyboard's firmware
  • It worked for me, but I will not guarantee that it won't brick your keyboard. Use at your own risk!

More detailed description:

  • I used the info in this issue and https://github.com/vpelletier/dissn8 to get a human readable assembler file of the firmware
  • After a lot of to and fro I found out that there is a table in the ROM at word 0x05a0 that translates the keyboard matrix to Usage IDs as specified in "Universal Serial Bus HID Usage Tables", rev. 1.12 (October 28, 2004), table 12 from the USB Implementer's forum.
  • The actual translation is done by the function at word 0x0186 (label func_0186 when using the aforementioned disassembler. Handling of the "Fn" key is at label func_0186_015f and following.
  • But for swapping "Fn" and "LeftControl" we need to modify the translation table at word 0x05a0:
    • The "Fn" key is encoded as 0x00af at word 0x0615, i.e. byte 0xc2a (0x740BA in the .exe), since it is XORed with 0x5a the value in the .exe is 0xf5. To make the "Fn" key work as "LeftControl", it must be changed to 0xe0 ^ 0x5a = 0xba.
    • The "LeftControl" key is encoded as 0x00e0 at word 0x05ba, i.e. 0xb74 bytes (0x74004 in the .exe), since it is XORed with 0x5a the value in the .exe is 0xba. To make the "LeftControl" key work as "Fn", it must be changed to 0xaf ^ 0x5a = 0xf5.

@federvieh Can confirm your instructions worked for the azerty version of the keyboard too. I've been looking for a way to swap Fn <-> Left ctrl since months now, thanks a lot for your hard work and those of @vpelletier and @mrexodia !

THANK YOU! Same as @ovelny I confirm it works on my AZERTY keyboard, I can at last rest my numb left little finger that has done so many weird acrobatics just to be able to do "Ctrl+Shift" for 3 years now. Can I send you guys some beer money somehow?

EDIT: in case you were wondering why it was more of a problem for an AZERTY layout:

lenovo-nonsense

With the QWERTY layout the shift bar is wider so it's less of a problem, and I'm just noticing this now 😅

I'm sure the people who made that decision had never seen/used the AZERTY layout...

Would these instructions also work for the ThinkPad Compact Bluetooth Keyboard as well you think?

@tbjornli : AFAIK the bluetooth variant has a completely different controller (ARM ?), so it will require a separate disassembly and analysis. The advantage is that the tools for this should already exist, and will be of higher quality than what I wrote.

About key layouts, the firmware is indeed layout-independent, the layout is purely implemented by the physical key matrix. The firmware has all possible key events in its key lookup table, some entries being unreachable depending on used layout.

Truly impressive work!

I just received the ThinkPad TrackPoint Keyboard II. Maybe people here like to know:

  • Great: Fn+ArrowLeft and Fn+ArrowRight produce respectively Home and End
  • Not so great: Ctrl and Fn can't be swapped. Quite the blunder by Lenovo IMO 😑

The obvious question: would the instructions by @federvieh work for this particular model as well?

cunei commented
  • Great: Fn+ArrowLeft and Fn+ArrowRight produce respectively Home and End

This is a piece of information I spent weeks trying to determine, without success. Thanks! Time to buy one.
Do Fn+Up and Fn+Down produce PgUp and PgDn as well? (one can hope...)

@cunei Mea culpa - I messed up. The keyboard does not map Fn+ArrowLeft and Fn+ArrowRight to Home and End. I really thought it did. Wishful thinking and no proper testing... today was a weird day 🤦‍♂️ 😕
For me this means that the 'II' does not have any advantages over the Compact Bluetooth version. I'm off to find another external keyboard (that also has backlight...).

You can really do a lot, once you understand the assembler code. E.g. I have mapped the printscr key to menu, insert to Fn, Fn+F10 to insert. I completely remapped the lower row to fit my habbits (I'm using the neo keyboard layout) and a lot more. Unfortunately, I can't share my modifications, because I don't have the copyright. :-(

Perhaps we could even port QMK to the ThinkPad TrackPoint Keyboard II? Do we know which controller it has? If it's indeed a 32-bit ARM microcontroller, it's going to be much easier to RE and target than the weird 8-bit Sonix architecture.

@federvieh Thank you so much for your work!! I can finally use my thinkpad usb keyboard while not losing my self over annoying ctrl position! And I can confirm that this works also for the german QWERTZ keyboard.

I didn't even knew what hex editing was before I encountered your instruction, but your explanation was pretty straight forward and very on point. I had almost no problem, except for the confusion from the notation "0x". Now I kinda get it and I assume this is a common expression in the computer science, but at first I couldn't find "0x" anywhere and was totally confused. It is surprising though that this seemingly simple modification can make things so much easier.

This saved my keyboard as well. I was willing to do the hardware mod sooner or later, because many people were saying that there is no software/firmware solution for fn and ctrl swap for usb keyboard. But I am so glad I didn't give up the hope.

Thanks again, @federvieh and everyone on this thread who made this possible! My keyboard also says thanks for saving her :)

@psj8710 great to hear.

I want to give it a try as well. I have both the TP Compact Keyboard Bluetooth and the TP TrackPoint Keyboard II. Am I correct that both of those are not adjustable with the instructions by @federvieh?

Which of those would you suggest to try to hack? For both I didn't find an firmware update tool. Just a driver for the BT version and a setup file for the II version - would these be valid starting points?

Another question:
Did anyone manage to map Fn+ArrowLeft and Fn+ArrowRight to Home and End for the TP Compact Keyboard USB? If this is the case I might as well get the old USB version 😅

Am I correct that both of those are not adjustable with the instructions by @federvieh?

Correct, they use an entirely different SoC (well, the bluetooth keyboard definitely does, I'm reasonably sure TP Keyboard II does), so those instructions won't help you.

Which of those would you suggest to try to hack?

The old BT keyboard is driven by a BCM20730 (photos of guts here: http://bdm.cc/2016/06/lenovo-bluetooth-keyboard-repairs/ ). This is an ARM based SoC, so if you can find a firmware update tool then there will be more standard tools for poking around in it's assembler source.

If Lenovo don't have one (I don't know of one meself, but I've considered the keyboard done for some time), playing around with the standard BCM20730 utilities might be interesting. There's some geekhack threads on a "Bluetool" program that sound like they'd be a reasonable starting point.

Another question:
Did anyone manage to map Fn+ArrowLeft and Fn+ArrowRight to Home and End for the TP Compact Keyboard USB? If this is the case I might as well get the old USB version 😅

I haven't this specific modification, but it's definitly possible. As you can probably imagine, it's not as easy as swapping keys. You'll have to rewrite some of the assembler of the original firmware. Fortunately, you can do this without buying the keyboard. The firmware is downloadable from the Lenovo homepage. As I wrote in the initial instructions, the part with the fn-handling is at func_0186_015f.

Just stumble on this thread but I will also be interested if anyone ever make a new firmware for the Trackpoint Keyboard II !

Thanks to the amazing work of @vpelletier and @mrexodia I managed to swap Fn and Left Control in the firmware. Here's the TLDR:

  • Download the firmware update tool for FW ver. 3.30 from Lenovo's homepage (sha256sum of tp_compact_usb_kb_with_trackpoint_fw.exe is 7116a3819ee094857d21e4671cb6cf953d582372126f0f6728f6b2421eda7bd4)
  • Open the file in the hex editor of your choice
  • Change position 0x74004 (475140 decimal) from 0xba to 0xf5 (this changes the function of the "LeftControl" key to "Fn"
  • Change position 0x740BA (475322 decimal) from 0xf5 to 0xba (this changes the function of the "Fn" key to "LeftControl"
  • The sha256sum of the modified file is 123143092dab578550c87a62526b07a6c5f06c047f2455be87971aa51577e300
  • Use this file to update your keyboard's firmware
  • It worked for me, but I will not guarantee that it won't brick your keyboard. Use at your own risk!

More detailed description:

  • I used the info in this issue and https://github.com/vpelletier/dissn8 to get a human readable assembler file of the firmware

  • After a lot of to and fro I found out that there is a table in the ROM at word 0x05a0 that translates the keyboard matrix to Usage IDs as specified in "Universal Serial Bus HID Usage Tables", rev. 1.12 (October 28, 2004), table 12 from the USB Implementer's forum.

  • The actual translation is done by the function at word 0x0186 (label func_0186 when using the aforementioned disassembler. Handling of the "Fn" key is at label func_0186_015f and following.

  • But for swapping "Fn" and "LeftControl" we need to modify the translation table at word 0x05a0:

    • The "Fn" key is encoded as 0x00af at word 0x0615, i.e. byte 0xc2a (0x740BA in the .exe), since it is XORed with 0x5a the value in the .exe is 0xf5. To make the "Fn" key work as "LeftControl", it must be changed to 0xe0 ^ 0x5a = 0xba.
    • The "LeftControl" key is encoded as 0x00e0 at word 0x05ba, i.e. 0xb74 bytes (0x74004 in the .exe), since it is XORed with 0x5a the value in the .exe is 0xba. To make the "LeftControl" key work as "Fn", it must be changed to 0xaf ^ 0x5a = 0xf5.

This works like a charm for me.
I have ThinkPad Compact USB Keyboard with TrackPoint https://support.lenovo.com/us/en/accessories/pd026745.

There's an update from Nov 25 for the II. No changelog. Still no option for swapping Ctrl<->Fn in the control panel?

Thanks to the amazing work of @vpelletier and @mrexodia I managed to swap Fn and Left Control in the firmware. Here's the TLDR:

* Download the firmware update tool for FW ver. 3.30 from Lenovo's homepage (sha256sum of `tp_compact_usb_kb_with_trackpoint_fw.exe` is `7116a3819ee094857d21e4671cb6cf953d582372126f0f6728f6b2421eda7bd4`)

* Open the file in the hex editor of your choice

* Change position 0x74004 (475140 decimal) from 0xba to 0xf5 (this changes the function of the "LeftControl" key to "Fn"

* Change position 0x740BA (475322 decimal) from 0xf5 to 0xba (this changes the function of the "Fn" key to "LeftControl"

* The sha256sum of the modified file is `123143092dab578550c87a62526b07a6c5f06c047f2455be87971aa51577e300`

* Use this file to update your keyboard's firmware

* It worked for me, but I will not guarantee that it won't brick your keyboard. Use at your own risk!

More detailed description:

* I used the info in this issue and https://github.com/vpelletier/dissn8 to get a human readable assembler file of the firmware

* After a lot of to and fro I found out that there is a table in the ROM at word 0x05a0 that translates the keyboard matrix to Usage IDs as specified in "Universal Serial Bus HID Usage Tables", rev. 1.12 (October 28, 2004), table 12 from the USB Implementer's forum.

* The actual translation is done by the function at word 0x0186 (label `func_0186` when using the aforementioned disassembler.  Handling of the "Fn" key is at label `func_0186_015f` and following.

* But for swapping "Fn" and "LeftControl" we need to modify the translation table at word 0x05a0:
  
  * The "Fn" key is encoded as 0x00af at word 0x0615, i.e. byte 0xc2a (0x740BA in the .exe), since it is XORed with 0x5a the value in the .exe is 0xf5. To make the "Fn" key work as "LeftControl", it must be changed to 0xe0 ^ 0x5a = 0xba.
  * The "LeftControl" key is encoded as 0x00e0 at word 0x05ba, i.e. 0xb74 bytes (0x74004 in the .exe), since it is XORed with 0x5a the value in the .exe is 0xba. To make the "LeftControl" key work as "Fn", it must be changed to 0xaf ^ 0x5a = 0xf5.

Awesome! just did this and it works great! Is it possible to also switch the function of right alt key into the special key (similar to mouse right click) as this keyboard doesnt have this. I used to use sharpkey in my previous job, but I dont have admin accesss in my current office laptop so is not able to use the same. Is it possible to use the firmware to switch the function of right alt key ? Or anyone have any other options that does not require admin access?

Thanks

OK since i have no knowledge of assembly, I have somehow managed to find the address in where the right alt is in the exe. Right alt is in 0x7409E. I managed to found this by going to address 0x74004 then search for value BC (value for right alt as per USB HID Usage table).

However can anyone advise what is the keyboard usage ID for the menu key (equivalent of mouse right click) ? I tried using 76 - Keyboard Menu but it doesnt do anything and E7 - Keyboard Right GUI is giving me the windows key.

However can anyone advise what is the keyboard usage ID for the menu key (equivalent of mouse right click) ? I tried using 76 - Keyboard Menu but it doesnt do anything and E7 - Keyboard Right GUI is giving me the windows key.

On a generic Lenovo USB keyboard I've got the menu key is KEY_COMPOSE, i.e. 0x65.

On a generic Lenovo USB keyboard I've got the menu key is KEY_COMPOSE, i.e. 0x65.

Thanks! That does it!
For those who wonder, following the same guide by federvieh, you can change the right alt to menu key by changing address 0x7409E from BC to 3F

Jyny commented

Perhaps we could even port QMK to the ThinkPad TrackPoint Keyboard II? Do we know which controller it has? If it's indeed a 32-bit ARM microcontroller, it's going to be much easier to RE and target than the weird 8-bit Sonix architecture.

@leoluk
maybe it's NRF52832.
shown in the video www.youtube.com/watch?v=dql8XsqlqxE

Perhaps we could even port QMK to the ThinkPad TrackPoint Keyboard II? Do we know which controller it has? If it's indeed a 32-bit ARM microcontroller, it's going to be much easier to RE and target than the weird 8-bit Sonix architecture.

@leoluk
maybe it's NRF52832.
shown in the video www.youtube.com/watch?v=dql8XsqlqxE

I can confirm It's NRF52832. See https://fccid.io/A5MKC-1957/Internal-Photos/Internal-Photos-4619942.pdf

Besides I tried disassemble it today (but I'm unable to move further after detach the cover and the base so I gave up swapping fn-ctrl physically.) The chip is painted by some kind of white paint so the exact chip info cannot be identified but it really looks like NRF52832. It still leave the last "2" of the first line.

I'm a one that love trackpad but really want to swap fn-ctrl. So I guess we can find a way to port QMK now...?

Because of this I was finally willing to buy the keyboard. Instructions by @federvieh were super clear, now I have ctrl and fn swapped! Amazing! Without the swap I would break my fingers while using emacs.

Do we have any solution (besides hard mod) to swap Ctrl and Fn keys for the newer version of Lenovo keyboard? I mean Thinkpad Trackpoint Keyboard II to be precise.

Do we have any solution (besides hard mod) to swap Ctrl and Fn keys for the newer version of Lenovo keyboard?

Unfortunately not from my side. It's a bridge too far considering my technical experience in these matters and the time I can allocate to it. I want to join the effort if someone with more experience is talking the lead :)

Thanks for information - I fully understand. I hope that Lenovo will fix this by their own software/firmware as many people complain on this Fn Ctrl layout. For many of potential Lenovo customers it is a deal breaker and they will buy keyboard from another vendor. I think that I will also switch to something else if swap soultion won't be available soon.

It is definitely a wider problem. I spoke to a Lenovo reseller in my hometown and he is very interested in an (unofficial) swap ctrl/fn-solution as many of his customers are complaining about this quite inexplicable decision by Lenovo. We didn't venture into whether he would actually allocate funds towards a solution, but I personally would rather not b/c of support/liability.

@mrexodia thanks for tuning in!

Some info about the chip was posted earlier: #32 (comment)

I don't think there is any firmware available for download. Lenovo only provides this Windows setup executable as files for this version of the keyboard. Do you have instructions on how to 'dump' the firmware?

cunei commented

this quite inexplicable decision by Lenovo.

The layout per se is not entirely inexplicable: it may depend on history, and other factors. You will find a similar arrangement on early ThinkPad models (see this 2000's ThinkPad T20, for example). Incidentally, the same Fn/Ctrl is also found on all Macintosh laptop keyboards. I used Macintosh laptops for two decades, so I am used to that layout: even though I am in the minority, I was happy to find that keyboard arrangement on Lenovo ThinkPads, when I decided on a new laptop to switch to.

On the other hand, I was quite irked when I discovered that Fn+up/down cannot be mapped to PgUp/PgDn, no matter what. Granted, the physical PgUp and PgDn keys are just next to the arrow keys, but it was very instinctive for me to associate "Fn+Arrows" with "bigger jumps": PgUp-PgDn-Home-End, respectively, on the Mac. So my wish for customization of the ThinkPad TrackPoint Keyboard II would be to have those key combinations back, if possible. It would be great.

At least Lenovo keyboards have Fn+left/right for Home/End, and the page up/down keys nearby. Conversely, these days Dell maps Fn+up/down to PgUp/PgDn, but NOT Fn-left/right to Home/End, which is way more infuriating. I am at a loss why both manufacturers thought it a good idea to implement only one direction, and not the other...

this quite inexplicable decision by Lenovo.

The layout per se is not entirely inexplicable: it may depend on history, and other factors.

I agree and I completely understand Lenovo sticking to their default ThinkPad keyboard layout on a ThinkPad external keyboard. With "this quite inexplicable decision" I was referring to the missed opportunity for Lenovo to cater to a share of its ThinkPad users that switch Fn and Ctrl via a BIOS setting on their laptops (available for at least a couple of generations). Already a large amount of requests for this specific setting existed for the previous lineup of external ThinkPad keyboards. I can't phantom it being hard to implement for them on a technical level, but I would be curious to hear about it when someone thinks differently.

BTW seriously interested in if you think the ThinkPad's default (and Mac) layout are superior or whether it's just about 'muscle memory'. Personally, I feel that handling the modifier with my pinky gives me a lot more reach for combinations instead of hitting it with my thumb (as I guess this is how the default layout is used most commonly).

Conversely, these days Dell maps Fn+up/down to PgUp/PgDn, but NOT Fn-left/right to Home/End, which is way more infuriating. I am at a loss why both manufacturers thought it a good idea to implement only one direction, and not the other...

Yeah, definitely inexplicable.

associate "Fn+Arrows" with "bigger jumps": PgUp-PgDn-Home-End ... So my wish for customization of the ThinkPad TrackPoint Keyboard II would be to have those key combinations back, if possible. It would be great.

Agree. This keyboard doesn't even follow the recent ThinkPad standard Fn+Left/Fn+Right being Home/End.

I feel that what is happening in this repo is similar to Norton filling-in obvious gaps in MS-DOS back in the days.

Yeah, the problem is not that Lenovo shifted Ctrl and Fn keys on their Thinkpad keyboards as they were doing this for many years in the past. The issue here is that they gave possibility to change this on laptops (BIOS/software) but for external wireless keyboard it is not possible. It would make sense if no one will complain on this but as far as I know many people don't like shifted Ctrl and Fn layout so not giving swap function for second version of their wireless keyboard is not understandable at all for me. It looks like Lenovo does not care about their customers and what they are saying to them.

If anyone would give me a tutorial how to dump firmware from Thinkpad Trackpoint Keyboard II I can try to do this.

Thanks to the amazing work of @vpelletier and @mrexodia I managed to swap Fn and Left Control in the firmware. Here's the TLDR:

  • Download the firmware update tool for FW ver. 3.30 from Lenovo's homepage (sha256sum of tp_compact_usb_kb_with_trackpoint_fw.exe is 7116a3819ee094857d21e4671cb6cf953d582372126f0f6728f6b2421eda7bd4)
  • Open the file in the hex editor of your choice
  • Change position 0x74004 (475140 decimal) from 0xba to 0xf5 (this changes the function of the "LeftControl" key to "Fn"
  • Change position 0x740BA (475322 decimal) from 0xf5 to 0xba (this changes the function of the "Fn" key to "LeftControl"
  • The sha256sum of the modified file is 123143092dab578550c87a62526b07a6c5f06c047f2455be87971aa51577e300
  • Use this file to update your keyboard's firmware
  • It worked for me, but I will not guarantee that it won't brick your keyboard. Use at your own risk!

More detailed description:

  • I used the info in this issue and https://github.com/vpelletier/dissn8 to get a human readable assembler file of the firmware

  • After a lot of to and fro I found out that there is a table in the ROM at word 0x05a0 that translates the keyboard matrix to Usage IDs as specified in "Universal Serial Bus HID Usage Tables", rev. 1.12 (October 28, 2004), table 12 from the USB Implementer's forum.

  • The actual translation is done by the function at word 0x0186 (label func_0186 when using the aforementioned disassembler. Handling of the "Fn" key is at label func_0186_015f and following.

  • But for swapping "Fn" and "LeftControl" we need to modify the translation table at word 0x05a0:

    • The "Fn" key is encoded as 0x00af at word 0x0615, i.e. byte 0xc2a (0x740BA in the .exe), since it is XORed with 0x5a the value in the .exe is 0xf5. To make the "Fn" key work as "LeftControl", it must be changed to 0xe0 ^ 0x5a = 0xba.
    • The "LeftControl" key is encoded as 0x00e0 at word 0x05ba, i.e. 0xb74 bytes (0x74004 in the .exe), since it is XORed with 0x5a the value in the .exe is 0xba. To make the "LeftControl" key work as "Fn", it must be changed to 0xaf ^ 0x5a = 0xf5.

A little bit late to the party, but I just wanted to say thank you very much. It worked perfectly. Keep it up!

cunei commented

BTW seriously interested in if you think the ThinkPad's default (and Mac) layout are superior or whether it's just about 'muscle memory'.

Personally, I find that it is mostly about muscle memory. I keep switching between Lenovo, Mac, and Dell keyboards, and in the end the annoyance is mostly about having to adjust. It always takes several minutes (and a few errors) to get used to, even if you keep using different ones all the time. But which one is better is really about habit, I suppose.

Personally, I feel that handling the modifier with my pinky gives me a lot more reach for combinations instead of hitting it with my thumb (as I guess this is how the default layout is used most commonly).

...thumb? Honestly, that never occurred to me. I just use my pinky for Ctrl, even with the Fn/Ctrl order. Actually, I have to stretch my hand a bit less for Ctrl-V in that case: with the Ctrl/Fn order the 'Ctrl' and 'V' keys are so far apart that I have to keep my hand suspended in order to get to both with the left hand (pinky + index). When using the Fn/Ctrl order, instead, I just rest the wrist on the laptop, and pivot on it to get to either Crtl or Fn with the little finger. Since the 'Ctrl' and 'V' keys are closer, that combination becomes rather more comfortable to compose. So, maybe there is a small advantage in Fn/Ctrl actually, at least for me.

But as you observed, in any case, there is no reason not to let users pick the arrangement that they prefer. Which is why we are all avidly monitoring this thread..!

@mrexodia thanks for tuning in!

Some info about the chip was posted earlier: #32 (comment)

I don't think there is any firmware available for download. Lenovo only provides this Windows setup executable as files for this version of the keyboard. Do you have instructions on how to 'dump' the firmware?

Just took a quick look and I'm afraid that until Lenovo releases an official firmware update it likely won't be possible to analyze/customize the firmware at all (unless somebody who has the keyboard manages to dump it from the chip somehow). For the first keyboard the firmware image was released as part of an official update. If you want more details, here is a technical writeup about how I extracted it: https://mrexodia.github.io/reversing/2019/09/28/Analyzing-keyboard-firmware-part-1. I'll take another look later:tm:, but I don't own the second 2 myself so I couldn't really experiment even if there is some kind of HID packets you can send to download the firmware from the device...

This discussion has rekindled my hope to swap the Fn and Left Ctrl buttons on my ThinkPad SK-8855 external USB keyboard.

Does anyone by any chance have the SK-8855 firmware (I can't find it online?) and links to help educate a curious stranger on the internet on how to locate the correct Fn and Left Ctrl positions in a Hex editor?

Any and all help is greatly appreciated!

I have received my Trackpoint Keyboard II yesterday. It is indeed a very nice keyboard, but the FN key problems break it for me. I have ordered cheap Bluetooth beacons with the same nRF52832 chip in them to test firmware dumping and writing before I brick this keyboard. If I succeed, I'll post the firmware somewhere.

I have received my Trackpoint Keyboard II yesterday. It is indeed a very nice keyboard, but the FN key problems break it for me. I have ordered cheap Bluetooth beacons with the same nRF52832 chip in them to test firmware dumping and writing before I brick this keyboard. If I succeed, I'll post the firmware somewhere.

I really hope someone will figure this out.

Probably a long shot, but the new X12 Detachable's keyboard cover also can't switch its FN and CTRL keys despite having the option visible in the bundled software. It's got an M032SE3AE on board and a BIN file is generated by the firmware upgrade tool. It's an ARM Cortex M0, but I haven't had any luck disassembling the file.

Could you share the file/updater? mrexodia (at) muslje.com if you don’t want to upload it here for some reason.

On Fri, 26 Mar 2021 at 03:45, DSGMods @.***> wrote: Probably a long shot, but the new X12 Detachable's keyboard cover also can't switch its FN and CTRL keys despite having the option visible in the bundled software. It's got an M032SE3AE on board and a BIN file is generated by the firmware upgrade tool. It's an ARM Cortex M0, but I haven't had any luck disassembling the file. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#32 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASYFGMJSUY4QTP5M5WW2SDTFPYMBANCNFSM4BX7FQBA .

Here's the update tool and the files generated by it. On running the updater, a folder with two files are generated, command.exe and a BIN file.
KBFW.zip

Thanks to the amazing work of @vpelletier and @mrexodia I managed to swap Fn and Left Control in the firmware. Here's the TLDR:

  • Download the firmware update tool for FW ver. 3.30 from Lenovo's homepage (sha256sum of tp_compact_usb_kb_with_trackpoint_fw.exe is 7116a3819ee094857d21e4671cb6cf953d582372126f0f6728f6b2421eda7bd4)
  • Open the file in the hex editor of your choice
  • Change position 0x74004 (475140 decimal) from 0xba to 0xf5 (this changes the function of the "LeftControl" key to "Fn"
  • Change position 0x740BA (475322 decimal) from 0xf5 to 0xba (this changes the function of the "Fn" key to "LeftControl"
  • The sha256sum of the modified file is 123143092dab578550c87a62526b07a6c5f06c047f2455be87971aa51577e300
  • Use this file to update your keyboard's firmware
  • It worked for me, but I will not guarantee that it won't brick your keyboard. Use at your own risk!

More detailed description:

  • I used the info in this issue and https://github.com/vpelletier/dissn8 to get a human readable assembler file of the firmware

  • After a lot of to and fro I found out that there is a table in the ROM at word 0x05a0 that translates the keyboard matrix to Usage IDs as specified in "Universal Serial Bus HID Usage Tables", rev. 1.12 (October 28, 2004), table 12 from the USB Implementer's forum.

  • The actual translation is done by the function at word 0x0186 (label func_0186 when using the aforementioned disassembler. Handling of the "Fn" key is at label func_0186_015f and following.

  • But for swapping "Fn" and "LeftControl" we need to modify the translation table at word 0x05a0:

    • The "Fn" key is encoded as 0x00af at word 0x0615, i.e. byte 0xc2a (0x740BA in the .exe), since it is XORed with 0x5a the value in the .exe is 0xf5. To make the "Fn" key work as "LeftControl", it must be changed to 0xe0 ^ 0x5a = 0xba.
    • The "LeftControl" key is encoded as 0x00e0 at word 0x05ba, i.e. 0xb74 bytes (0x74004 in the .exe), since it is XORed with 0x5a the value in the .exe is 0xba. To make the "LeftControl" key work as "Fn", it must be changed to 0xaf ^ 0x5a = 0xf5.

Does this work with the Bluetooth gen1 variant of the keyboard?

Does this work with the Bluetooth gen1 variant of the keyboard?

Nope. See for example: #32 (comment)

ducwp commented

Thanks to the amazing work of @vpelletier and @mrexodia I managed to swap Fn and Left Control in the firmware. Here's the TLDR:

  • Download the firmware update tool for FW ver. 3.30 from Lenovo's homepage (sha256sum of tp_compact_usb_kb_with_trackpoint_fw.exe is 7116a3819ee094857d21e4671cb6cf953d582372126f0f6728f6b2421eda7bd4)
  • Open the file in the hex editor of your choice
  • Change position 0x74004 (475140 decimal) from 0xba to 0xf5 (this changes the function of the "LeftControl" key to "Fn"
  • Change position 0x740BA (475322 decimal) from 0xf5 to 0xba (this changes the function of the "Fn" key to "LeftControl"
  • The sha256sum of the modified file is 123143092dab578550c87a62526b07a6c5f06c047f2455be87971aa51577e300
  • Use this file to update your keyboard's firmware
  • It worked for me, but I will not guarantee that it won't brick your keyboard. Use at your own risk!

More detailed description:

  • I used the info in this issue and https://github.com/vpelletier/dissn8 to get a human readable assembler file of the firmware

  • After a lot of to and fro I found out that there is a table in the ROM at word 0x05a0 that translates the keyboard matrix to Usage IDs as specified in "Universal Serial Bus HID Usage Tables", rev. 1.12 (October 28, 2004), table 12 from the USB Implementer's forum.

  • The actual translation is done by the function at word 0x0186 (label func_0186 when using the aforementioned disassembler. Handling of the "Fn" key is at label func_0186_015f and following.

  • But for swapping "Fn" and "LeftControl" we need to modify the translation table at word 0x05a0:

    • The "Fn" key is encoded as 0x00af at word 0x0615, i.e. byte 0xc2a (0x740BA in the .exe), since it is XORed with 0x5a the value in the .exe is 0xf5. To make the "Fn" key work as "LeftControl", it must be changed to 0xe0 ^ 0x5a = 0xba.
    • The "LeftControl" key is encoded as 0x00e0 at word 0x05ba, i.e. 0xb74 bytes (0x74004 in the .exe), since it is XORed with 0x5a the value in the .exe is 0xba. To make the "LeftControl" key work as "Fn", it must be changed to 0xaf ^ 0x5a = 0xf5.

A little bit late to the party, but I just wanted to say thank you very much. It worked perfectly. Keep it up!

Thanks bro. I did it work perfectly.

Thanks bro. I did it work perfectly.

I used HxD editor and go to 00475136 (dec) change BA to F5, go to 00475312 change F5 to BA then save it.

Hey, glad to hear this worked for you! What keyboard model did you use this on? SK-8855, something different? :)

ducwp commented

Thanks bro. I did it work perfectly.
I used HxD editor and go to 00475136 (dec) change BA to F5, go to 00475312 change F5 to BA then save it.

Hey, glad to hear this worked for you! What keyboard model did you use this on? SK-8855, something different? :)

My keyboard model is KU-1255, I have no experience with SK-8855.

mbeko commented

I came here from the Super User question regarding the swapping of the Fn and Ctrl keys. User David Baird reported key combinations like Ctrl+Alt+ArrowUp not working after applying @federvieh's solution.

I rely heavily on this combination as it switches the workspace in Ubuntu. So I would like to know if it indeed doesn't work before I swap the keys.

Can someone of those who patched the firmware confirm if Ctrl+Alt+ArrowUp works?

I can indeed confirm Ctrl+Alt+ArrowUp doesn't work with the patched firmware. Never noticed it!

mbeko commented

Thanks for the confirmation! Now that we know about the issue, maybe one of the assembler wizards will be able to fix it.

I figured out how to disable the automatic hardware "scrolling".
If you start with version 3.30 of the firmware (SHA-256 7116a3819ee094857d21e4671cb6cf953d582372126f0f6728f6b2421eda7bd4) and write 0x5A5A to locations 476076, 475710, 475718 and 481870 (in decimal), you should get a file with SHA-256 7fd326d15862211932ce73965c2ba2b86d87b918a54f25d7af37eef1b29d27ba.

If you use that to flash the firmware, it functions as a normal three button mouse would (obviously, one can still do software fake mouse wheel shenanigans if one wants to). I think it still intersperses some scrolling reports depending on the mode, but all the actual scrolling calculations should be disabled.

This seems to work fine with Windows (with, and presumably without, the Lenovo driver), but Linux (the lenovo_hid driver, I think) seems to ignore the middle mouse button events. I mostly intend to use the keyboard with Windows (all my Linux machines are actual Thinkpads :)), so I'm not going to debug this further.

Some obvious NB:

  1. I cannot, and will not, guarantee this won't brick your keyboard. Use at your own risk.
  2. I cannot, and will not, provide patched binaries (for reasons of copyright).

Literally signed up to thank you for this. Was about to return this thing. Hardware scrolling turning on when holding middle click button would make the keyboard unusable for games. Sometimes going down the rabbit hole pays off.

+1 if anyone has any idea on how we can flash the firmware for thinkpad keyboard ii

Jyny commented

The way to dump and flash ThinkPad TrackPoint Keyboard II firmware, maybe is same as airtag[1] due to the same mcu nRF52832.

Debug port is on back side of PCB.
IMG_0342

[1] https://www.youtube.com/watch?v=_E0PWQvW-14

The way to dump and flash ThinkPad TrackPoint Keyboard II firmware, maybe is same as airtag[1] due to the same mcu nRF52832.

Debug port is on back side of PCB.
IMG_0342

[1] https://www.youtube.com/watch?v=_E0PWQvW-14

Looks interesting...so you had to disassemble the keyboard to access the motherboard and flash the firmware. Guess there is no way to do this via the usb C power connection (Does it also deliver data)?

Also, obvious question but have you tried flashing it?