2021–05–30:
PinePhone keyboard – more observations
After further eyeballing of the firmware in Ghidra, with the help of a linker
symbol map, I've reverse engineered the USB flashing protocol completely.
I'll not publish complete details at this time, but here's a general idea of
how it works:
- There are two flash memory address spaces, one for code, other for code
options.
- Code option flash contains a flag that the bootloader reads and if it's not
of certain value, it will not procede to the main app, but will stay in the main
bootloader loop, waiting for USB commands.
- Bootloader also checks P80 GPIO status during boot. If it's pulled low
externally, it will not boot the main app, but will again stay in the bootloader
main loop. This can be used to enter flashing mode if the main app is broken.
- On Pinephone keyboard, P80 GPIO is connected to one of the columns of the
keyboard matrix.
- It should be possible with a HW modification, to enter bootloader mode by
pressing a specific key during power up. Row GPIOs are all hi-Z during powerup,
so you'd need to add a resistor between GND and the P80 line, to make the P80
pin's voltage < 0.3 Vdd (internal pull-up value is not documented, so the
right choice can't be made at this time)
- The bootloader exposes 4 HID interfaces, one for sending commands, one for
receiving responses to commands, one for sending bulk data in 64B blocks, one
for receiving bulk data in 64B blocks.
- There are 24 commands implemented by the bootloader. Categories of
commands are:
- reading version of the booltoader/mcu type,
- challenge/response authentication for higher privileged flashing mode,
- abort/reset,
- checksum reading/writing,
- in-app programming mode enter/finish/cancel,
- flash read/write for code and option areas.
- Upon entry to IAP flashing mode, bootloader writes a mark at the end of the
option flash that signifies that the main app is not flashed, and that the
bootloader should stay in flashing mode on next reset.
- This makes flashing rather robust. Once you start flashing, until the
flashing is complete and everyhting is verified, you should be able to always
easily recover from failed flashing attempts.
- Actual flashing is done by an initiating command that contains a key,
starting address and length aligned to 64B (erase block size). Then appropriate
number of 64B HID reports are expected on data receiving HID interface, and then
the completion command is sent to the command interface, which should contain a
checksum for the block calculated by the flashing tool. Firmware will comapre
this checksum with the checksum it calculated, and complete the flashing
operation. There are also some provisions in the firmware to read back the data
from flash and automatically repeat the flashing of the block if they
don't match.
- Similar pattern is followed by the reading commands.
- The bootloader firmware also contains 9 entry fixed address entry point
table for calling into the bootloader from the main app. This table has only two
entries filled. One for making the checksum of the bootloader, and the other for
jumping into flashing mode from the main app.
- We can modify the main app, so that it first checks whether a certain
keyboard key is pressed after reset and jump to flashing mode if it is. With
this modification, the keyboard controller will either always end up in flashing
mode if previous flashing attempt failed, or the main app will send it to
flashing mode if the key is pressed during reset/powerup, even if there's a bug
further down in the main app.
- Last line of defense against messups during development is the P80 pin HW
modification mentioned above.
- And even laster :) line of defense is OCD flashing mode accessible via
P81/P82 pins exposed on the PCB, that can be used to re-flash the bootloader
itself. But this mode is undocumented.
All the details of the flashing protocol are known to me now, and I have a
flashing tool in the works. It should be possible to finish most of it even
prior to getting my hands on any hardware, but I guess there's no hurry at
this point with all the details known. :)
Out of all this research I personally plan to do these HW modifications on
the keyboard to help make it more useable/robust:
- Expose some way to be able to pull down P80 during powerup/reset.
- Expose some HW reset pin (HW reset pin can be configured on several GPIOs,
and hopefully clock pin of the OCD interface also serves as a reset pin, which
would be the best outcome).
- Maybe repurpose the side button in the keyboard for some of this.
- Put charger chip on the I2C bus somehow.
Overall, the situation around flashing arbitrary firmware to the keyboard
controller reliably seems quite reasonable, without too many gotchas for the
end-users. With a known good keyboard firmware users should be able to recover
from flashing failures without the need to open the keyboard or a huge risk of
bricking the keyboard.
The only thing that will be troublesome to end-users is the possible
non-confirmance of the charging circuit to USB specification, as mentioned in
the previous post. It will be probably challenging to insert the keyboard into
the USB ports on the computer without the port shutting down due to
overcurrent.
I'll be also looking at the actual keyboard firmware (main app). Most of the
existing code deals with exposing the USB HID interface, which is not used by
Pinephone. Actual keyboard interface used by Pinephone is HID over I2C. Code for
that is much simpler. There's a lot of dead code in the existing firmware
provided by the vendor. All that's needed is just reading out currently pressed
keys from the key matrix, and providing updates over I2C on changes. That should
be a few hundred lines of code tops, not the current ~6000 lines. USB interface
is only used for initiating the switch to flashing mode of the bootlader from
the main app, but we can instead use a fixed key combination to do that (Pine
key+F+M, for example) and drop all that other dead weight from the firmware.
This will make firmware also much smaller and faster to flash (currently
it's 19 KiB, or 16 KiB when compiled with optimizations, but it can be
~2 KiB).
If you'd like to support the effort to make FOSS firmware flashing tool and
customizable firmware for the Pinephone keyboard, donations are
welcome.