From failing to find JTAG, to an image on the screen
Porting Linux to an old GPS: Part 1
I have a pair of Omnitech car navigation GPS units from around 2007 that have been gathering dust since ubitquitous smartphones made them obsolete. They are designed to run Windows CE, which appeared to be loaded from an SD card. For a long time, I’ve been interested in porting Linux to some device that was never designed to run it, and this device seemed like a promising candidate.
The first step was to identify the SoC and other important components on the board to judge whether the port would even be feasible. One of the units had already been disassembled for a previous ill fated project that resulted in damage to the screen and GPS antenna, so I decided to tear it down further. An initial inspection found no visible signs of the SoC, RAM or other core components, but much of the PCB was hidden under metal RF shields. I pried away some of the easier ones, but found nothing but small ICs and what appeared to be voltage regulators. The last RF shield was soldered in place, but as it must cover the SoC, I had to remove it. After some careful desoldering, I was greeted with a Freescale (now NXP) i.MX21 SoC, a pair of Qimonda 256 Mbit RAM chips and a pair of Spansion 32 Mbit flash chips. A 266 MHz ARMv5 CPU with only 64 MB of RAM makes for a rather underpowered system by today’s standards, but it may still be useful for something.
Next I needed to find a way to execute code on the processor. The manual for the i.MX21 is publically available, and shows that the processor can boot from USB or flash, depending on the state of a set of pins on the IC. These pins must obviously be configured to boot the board from the onboard flash chip which must contain a bootloader that then loads the rest of WinCE from the SD card. As an alternative to booting from USB, the SoC also has a JTAG interface that would allow writing to the flash as well as allowing debugging. I also hoped to find a UART to use for debugging. Unfortunately, the i.MX21 is in a BGA package mounted to a multi-layer PCB, making following traces very difficult. I was unable to identify an obvious JTAG port, but there were a few promising test points. I used various tools, including JTAGEnum, to probe the test points, but was unable to get a response. As far as I can tell, most of the tests points had fixed voltages, while one was clearly the 32.768 kHz clock that drives the SoC.
At this point, I was about to give up on the project, when I started thinking about whether it would be possible to use the existing WinCE bootloader to boot Linux. Doing some research, it turned out that the WinCE boot process is fairly standardized and well documented. The bootloader loads the file NK.bin from the SD card, which is a B000FF file (named after the magic string at the beginning of the file) that contains data as well as the location where it should be placed in memory. Pretty much all the .exe and .dll files that make up the core of WinCE are embedded in NK.bin. The bootloader then searches for the NK.exe file using the table of contents (TOC) structure also contained within NK.bin and jumps to its address. Therefore, replacing the contents of NK.exe should allow me to execute whatever code I want.
With no JTAG access or even serial output, I had to make a blind leap from the WinCE bootloader to some kind of visible output on the device. The bootloader provides no feedback, simply hanging at a “System loading…” screen if something went wrong. To minimize the chance of problems I first wrote a tool that just replaced NK.exe in the original NK.bin. I wrote a basic assembly program that was supposed to trigger a watchdog reset, which would hopefully be visible on the device. I’m still not sure why, but I could not get this to do anything, so I instead tried to write to the framebuffer. After a bunch of trial and error, I eventually saw a line of black pixels appear on the display. Now that I was sure I could get code execution, I went back and wrote a tool to generate NK.bin from scratch, which worked without much trouble.
To get a better understanding of the framebuffer, I tried to display an image, but any access to the image buffer in RAM seemed to cause the device to freeze. Eventually, I realized that the addresses in the NK.bin file did not fall within the region of the address space that the manual said was mapped to RAM, leading to the realization that the bootloader must be doing some kind of remapping. Most ARM instructions use relative addressing, so basic code worked, but when I tried to access the large image buffer, it was failing due the use of incorrect absolute addresses. Fixing the memory map in the linker script allowed the image to be displayed correctly.
In hindsight, using the WinCE bootloader actually made things a lot easier, as I didn’t have to initialize the DRAM or the LCD, both of which require a large set of parameters that might have been hard to determine.