How to fix the “hello_world.c” demo for ARM devices

U-Boot allows to dynamically load standalone applications and run them directly in the environment provided by the bootloader. Standalone applications can use some resources of U-Boot such as console I/O functions or interrupt services and they are designed to work out-of-the-box on different architectures (see $UBOOT_PKG/doc/README.standalone). Two demos are provided and automatically compiled with U-boot: the “Hello world” application will print the arguments provided by the user on the console, while the CPM timer application will generate an interruption every second (this last one is designed to work only on MPC8xx CPUs). Sources and generated binaries of the demos can be found in the folder $UBOOT_PKG/examples/standalone/.

Trying to run the “Hello world” app as it is on the Amber board will cause a bootloader freeze, due to some inconsistencies in the configuration files for the ARM architecture.
By investigating the configuration files two main discrepancies have been found: the first one is relative to the register holding the pointer to the jump table for exported functions and the second one is relative to start and load addresses for the standalone applications. We’re going to describe how to fix them to be able to successfully run the “Hello world” app on the Amber board.
This tutorial refers to U-boot 2013.10 version (branch imx_v2013.10_v4 on the varigit repository).

How U-boot manage standalone apps

The binary file of the demo apps on U-Boot can be loaded dynamically on a specific address and with a specified baudrate with the prompt command loadb [ off ] [ baud ]. Once the app has been loaded at the specified address on memory it can be started with the prompt command go addr [arg ...], passing ‘arg’ as arguments. These are the steps to correctly load and run the “Hello world” demo app using C-Kermit:

$ loadb
CTRL+\+c  
$ send $UBOOT_PKG/examples/standalone/hello_world.bin
$ connect
$ go 12000000 Hello world!  

The “Hello world” demo contains calls to the printf U-Boot function. U-Boot functions need to be exported in order to be used in standalone applications; all exported functions are listed in the $UBOOT_PKG/include/_exports.h file.

The functions are exported by U-Boot via a jump table and the pointer to that jump table is machine-dependent. According to the definition in $UBOOT_PKG/arch/arm/include/asm/global_data.h, the dedicated register that holds the pointer to the jump table on ARM is “r9”.

Setting the right register for the pointer to the jump table

In the application folder, along with the sources and the compiled binaries of the demos, there’s a stubs.c file that define some crucial macros for exported functions. Each macro defines, for each architecture, the register holding the pointer to the jump table and sets the offset corresponding to the exported function in the table. In particular, for the ARM architecture, we have:

/*
* r8 holds the pointer to the global_data, ip is a call-clobbered
* register
*/
#define EXPORT_FUNC(x) \
asm volatile (			\
"	.globl " #x "\n"		\
#x ":\n"				\
"	ldr	ip, [r8, %0]\n"		\
"	ldr	pc, [ip, %1]\n"		\
        : : "i"(offsetof(gd_t, jt)), "i"(XF_ ## x * sizeof(void *)) : "ip");

The first ldr instruction identifies the position of the jump table containing all the addresses for the exported functions within the global data section, while the second ldr instruction identifies the specific function to jump to within the jump table. For example, aprintf call will be resolved by the compiler in:

.globl printf
printf:
ldr    ip, [r8, #84]
ldr    pc, [ip, #20]

As we can see, the stubs.c file refers to “r8” as the register holding the pointer to the jump table, although the proper register for the ARM architecture should be “r9” as described earlier. In order for the processor to jump to the proper function, “r9” needs to be set instead of “r8” in the macro.

Setting the right addresses for the standalone application

The default load and start addresses of the applications on an ARM device are as set on the $UBOOT_PKG/arch/arm/config.mk file:
CONFIG_STANDALONE_LOAD_ADDR = 0x0c100000. Even if 0x0c100000 is set as the default address, calling loadb on the U-Boot prompt will return 0x12000000 as the default load and start address:

VAR_SOM_MX6 on Amber board (sd)> loadb
## Ready for binary (kermit) download to 0x12000000 at 115200 bps...
## Total Size      = 0x0000024e = 590 Bytes
## Start Addr      = 0x12000000

The address 0x12000000 is configured in $UBOOT_PKG/include/configs/mx6var_common.h as:
#define CONFIG_LOADADDR 0x12000000.
This inconsistency between the load address used by loadb command and the load address set by the standalone application’s linker cause bad memory accesses. Setting CONFIG_STANDALONE_LOAD_ADDR = 0x12000000 in the $UBOOT_PKG/arch/arm/config.mk allows the “Hello World” application to run successfully.

Applying the patch

A patch has been developed to automatically fix the issues described earlier. To apply the patch:

1.Download standalone.patch and place it in the $UBOOT_PKG folder.

2.From a terminal run:

$ cd uboot-imx/
$ patch -p0 < standalone.patch