Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Adding a Delay to the ARM DAPLink Bootloader

DZone's Guide to

Adding a Delay to the ARM DAPLink Bootloader

The ARM mbed DAPLink bootloader solved a Windows 10 issue that bricks boards, but timing can force it into bootloader mode at device power-up. Here's how to fix it.

· IoT Zone
Free Resource

Learn how Bluetooth mesh helps you create industrial-grade networks. Download the mesh overview.

The ARM mbed USB MSD bootloader that is used on many silicon vendor boards has a big problem: It is vulnerable to operating systems like Windows 10, which can brick your board (see Bricking and Recovering OpenSDA Boards in Windows 8 and 10). To recover the board, typically a JTAG/SWD programmer has to be used. In past articles (see the links section at the end), I have described how to recover from that situation, including using an unofficial new bootloader that (mostly) solves the problem. The good news is that ARM (mbed) has released an official and fixed bootloader. The bad news is that this bootloader does not work on every board because of a timing issue: The bootloader mostly enters bootloader mode while executing the application.

Outline

This article describes how to patch the ARM mbed DAPLink bootloader so it works with relaxed timing. It describes how to analyze the bootloader, how to write a small assembly program, and how to inject it into the bootloader to work around a weakness in the ARM bootloader during power-up.

Problem

The mbed (or OpenSDA) bootloader uses a virtual USB MSD (mass storage device) to update the board with a new application binary. The problem with MSD is that it might get confused about what the host machine is sending, e.g. if the host is scanning the new device for viruses/etc. Because the developers did not foresee such a situation, the receiving packets might brick the bootloader and board. Luckily, the board can be unbricked with a JTAG/SWD programmer like a P&E Multilink or a Segger J-Link (or use a NXP Freedom board. See the links section).

ARM has released a new bootloader (v244). The approach requires pyOCD, which is, in my honest opinion, yet another can of worms. Instead, I recommend investing into a SWD/JTAG programming device (you get an NXP LPCLink2 or a Segger J-Link EDU for $20 these days). The latest DAPlink releases can be found here.

Bootloader Mode or Not?

While that bootloader v244 is supposed to fix the Windows 10 issues, I have found that it works on most of the NXP boards, but fails on others, especially on custom boards. The problem manifests in the following way: Instead of booting the board into application mode after power-up, the board enters Bootloader Mode:

DAPLink in Bootloader Mode

DAPLink in bootloader mode

After a lot of trial and error, I isolated the problem to a power-on issue: Depending on how (and how fast) the board gets powered up, it might (or might not) enter bootloader mode, or does it in a random way. The thing is that on the NXP OpenSDA boards, the K20 PTB1 pin, is connected to the target CPU reset line:

Enter Bootloader Pin in OpenSDA

Enter the bootloader pin in OpenSDA (click to enlarge)

The bootloader on the K20/OpenSDA checks the voltage on PTB1/the reset line during startup: If the level is LOW, it enters bootloader mode. Otherwise, it starts the application.

The problem with that is that this is very timing sensitive: Consider the case where, during power up, the K20/bootloader runs a bit faster than the logic level of the reset line that gets pulled up (by a pull-up resistor). This gets even trickier if different power supplies are used with a different reset line capacitance. As a result, if the K20/bootloader comes up ‘too fast’ — it might ‘see’ a LOW on the reset line and enters bootloader mode. This especially happens if the board is plugged in by the USB port and gets powered up.

A workaround is to keep the K20/bootloader in reset for a few seconds until all the voltages have been stabilized. But always keeping the reset button pressed while powering the board is painful.

The obvious solution would be to change the bootloader to add a short delay (say one second) until it checks that PTB1 pin. But building the mbed bootloader from sources is definitely not easy or simple.

ARM engineers, if you read this: Please consider making it easy to build the bootloader with a simple make file using standard GNU tools. And don’t overcomplicate the bootloader with a proprietary RTOS, making it very hard to understand and build. Thank you!

So I thought: Adding a small patch to delay the bootloader should not be a big deal. And indeed that was accomplished in less than half an hour.

Bootloader Vector Table

Opening the 0244_k20dx_bl_0x8000.bin shows the reset vector entry: On reset or power up, it will start execution from address 0x624 (minus the thumb bit):

Bootloader Vector Table

Bootloader vector table

So all I need is to jump to a small delay routine instead and then continue with the execution at 0x624.

Delay Routine

Using ARM assembly code, I wrote a small (nested) delay routine:

static void delay(void) {
 __asm (
 "mov r1, 0x01 \n"
 "Loop1: \n"
 "mov r0, 0x20 \n"
 "Loop2: \n"
 "subs r0, #1 \n"
 "nop \n"
 "cmp r0, #0 \n"
 "bgt Loop2 \n"
 "subs r1, #1 \n"
 "bgt Loop1 \n"
#if 1
 /* jump to startup code */
 "mov r0, #0x600 \n" /* _startup is at 0x625 */
 "add r0, #0x25 \n" /* 0x25 because of thumb bit */
 "blx r0 \n" /* jump! */
#else
 "bx lr \n"
#endif
 "nop \n" /* make sure things are properly aligned */
 );
}


The above function delays for a short time (depending on the clock speed), then jumps to 0x624.

You can adjust the time with the two loop counters, but make sure it is not too long. Otherwise, it could trigger the watchdog. Disable the watchdog in that piece of code, too.

I verified the code with the debugger to be sure it works properly.

Watchdog? Watchdog!

In case the watchdog kicks into the delay loop, it is necessary to disable it first. In the v244 bootloader, the watchdog-disabling code is located at address 0xfe4. For that case, I have extended the delay loop to first disable the watchdog:

static void delay(void) {
 __asm (
 "mov r0, #0xf00 \n" /* watchdog disable code at address 0xfe5 */
 "add r0, #0xe5 \n"
 "blx r0 \n" /* jump to code disabling the watchdog at 0xfe5 */
 "mov r1, #0x2 \n"
 "Loop1: \n"
 "mov r0, #0x20 \n"
 "Loop2: \n"
 "subs r0, #1 \n"
 "nop \n"
 "cmp r0, #0 \n"
 "bgt Loop2 \n"
 "subs r1, #1 \n"
 "bgt Loop1 \n"
#if 1
 /* jump to startup code */
 "mov r0, #0x600 \n" /* _startup is at 0x625 */
 "add r0, #0x25 \n" /* 0x25 because of thumb bit */
 "bx r0 \n" /* jump! */
#else
 "bx lr \n"
#endif
 "nop \n" /* make sure things are properly aligned */
 );
}


Which gives the following machine code:

static const uint8_t delay_code[] = {
 0x4F,0xF4,0x70,0x60, /* mov.w r0, #0xf00 */
 0x00,0xf1,0xe5,0x00, /* add.w r0, #0xe5 */
 0x80,0x47, /* blx r0 */
 0x4F,0xF0,0x02,0x01, /* mov.w r1, #2 */
 0x4f,0xf4,0x20,0x00, /* mov.w r0, #0x20 */
 0x01,0x38, /* subs 50, #1 */
 0x00,0xBF, /* nop */
 0x00,0x28, /* cmp r0, #0 */
 0x3F,0xF7,0xFB,0xAF, /* bgt.w Loop2 */
 0x01,0x39, /* subs r1, #1 */
 0x3F,0xF7,0xF6,0xAF, /* bgt.w Loop1 */
#if 1
 /* jump to startup code */
 0x4f,0xf4,0xc0,0x60, /* move.w 50, #0x600 */
 0x00,0xf1,0x25,0x00, /* add.w r0, #0x25 */
 0x00,0x47, /* bx r0 */
#else
 0x70,0x47 /* bx lr */
#endif
 0x00,0xBF, /* nop */
};


Machine Code

To patch the bootloader, I need the machine code of the delay loop. The easiest way to get this is with Eclipse and a JTAG debugger:

Image title

Debugging the patch

Using the memory view in Eclipse, I can see the op codes:

Image title

Machine code with different loop counter values

That machine code gets quickly transformed (copy-paste) into an array of bytes. I have used the lower loop counters below:

static const uint8_t delay_code[] = {
 0x4F,0xF0,0x01,0x01, /* mov.w r1, #0x1 */
 0x4f,0xf4,0x20,0x00, /* mov.w r0, #0x20 */
 0x01,0x38, /* subs 50, #1 */
 0x00,0xBF, /* nop */
 0x00,0x28, /* cmp r0, #0 */
 0x3F,0xF7,0xFB,0xAF, /* bgt.w Loop2 */
 0x01,0x39, /* subs r1, #1 */
 0x3F,0xF7,0xF6,0xAF, /* bgt.w Loop1 */
#if 1
 /* jump to startup code */
 0x4f,0xf4,0xc0,0x60, /* move.w 50, #0x600 */
 0x00,0xf1,0x25, /* add.w r0, #0x25 */
 0x00,0x80,0x47, /* blx r0 */
#else
 0x70,0x47 /* bx lr */
#endif
 0x00,0xBF, /* nop */
};


And the patch can be quickly tested that way too:

void (*f)(void); /* function pointer */
 
f = (void(*)(void))(&delay_code[0]); /* assign function pointer */
f(); /* call it! */


With this, I verified that my patch is working.

Patching the Bootloader

I now have the series of bytes I have to insert. The next step is to patch the bootloader itself. One easy way is to directly edit the .bin file with a binary file editor.

Using the SRecord tool to manipulate the binary would have been another option.

I decided to write it at the end of the vector table, which is filled up with the default vector entry (0x0000063F). Fill it up with NOPs.

Inserted Code

Inserted Code

Finally, I need to route the reset vector to my patch at address 0x3D0: For this, I change the original 0x625 at address 0x4 to jump to my code at 0x3d0:

Patched Reset Vector

Patched reset vector

That's it! Save the file and program the new bootloader to the board(s). Now all my boards work without any power-on issues

Summary

While the new ARM mbed DAPlink bootloader solves the Windows 10 vulnerability, it still has the problem of not dealing with power-on glitches in a reliable way. I have patched the bootloader with an extra delay loop. The same approach to patch any firmware can be used, of course, for anything else. All I need is some assembly programming, a binary editor, and an SWD/JTAG programmer.

You can find the patched bootloader binaries on GitHub.

Happy patching!

Links

For a deeper look into Bluetooth mesh, check out this technical insight for developers.

Topics:
bootloader ,arm ,iot ,delays ,daplink

Published at DZone with permission of Erich Styger, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}