world leader in high performance signal processing
Trace: » debugging

Debugging Das U-Boot

In the ideal world, everything should “just work”. In reality, porting to new boards is far from that. This page should help give direction in such situations.

First thing to do is check the Hardware Design Checklist for the Blackfin® Processors.

Startup Sequence

Here we'll explain how things go from power on to command line. The very early startup sequence may differ slightly depending on whether you're booting an LDR, booting in bypass mode (parallel flash), or simply executing a binary via the U-Boot go command.

Generally speaking, the files in question (in order) are:

  • cpu/blackfin/initcode.c (first if booting from LDR, second otherwise)
  • cpu/blackfin/start.S
  • cpu/blackfin/cpu.c
  • lib_blackfin/board.c

One thing to keep in mind is that the Blackfin can make PC-relative function calls. That means you can make function calls at any time without having yet relocated into the final place in memory. The Blackfin startup process takes advantage of this feature many times.

Some common conventions used by U-Boot:

  • functions that end in _f need to be careful about things they do as relocation from flash is not yet finished
  • functions that end in _r are used once U-Boot has finished relocating itself and global data structures have generally been initialized

However, due to the way the Blackfin processor boots (LDRs), the notion of first executing in flash for a while, then relocating, then executing in ram does not really apply to the Blackfin port. All code is relocated from flash to external memory either by the on-chip Boot ROM or by the initial start function (in bypass mode).

LDR

Since the LDR format has the ability to execute small pieces of code (init blocks), as well as load chunks of code into specific memory addresses, the startup sequence process is streamlined nicely. For a general overview of the LDR process, please see Booting Overview.

  1. Boot ROM loads and executes LDR init blocks: cpu/blackfin/initcode.c:initcode()
    1. U-Boot programs clock/memory settings (PLL/EBIU) and interrupt sources (IWR/SIC_IWR)
  2. Boot ROM loads U-Boot LDR into external memory
  3. Boot ROM clears U-Boot BSS section
  4. Boot ROM jumps to start of U-Boot: cpu/blackfin/start.S:_start()
  5. U-Boot starts up like normal

Common

Here we will start at the ELF entry point of U-Boot which is cpu/blackfin/start.S:_start(). When booting in bypass mode (parallel flash), or using the U-Boot go command, this is the first thing executed.

  1. U-Boot starts at ELF entry point: cpu/blackfin/start.S:_start()
    1. Initialize stack to L1 scratch space
    2. Initialize hardware watchdog (if enabled)
    3. Disable nested interrupts and enable CYCLES (which the udelay() function needs)
    4. Initialize minimal C runtime environment
    5. Call cpu/blackfin/initcode.c:initcode() (only in bypass mode)
      1. Program clock/memory settings (PLL/EBIU) and interrupt sources (IWR/SIC_IWR)
    6. Relocate U-Boot from point of execution to external memory monitor base
      1. only as needed (according to how U-Boot was started)
      2. only copies U-Boot to monitor base -- does not start executing there
    7. Call memset() to zero BSS
    8. Set stack to configured address (default is external memory)
    9. Lower self to the lowest Interrupt level (IVG15)
      1. this is when U-Boot starts executing at configured memory monitor base
    10. Jump to the next step …
  2. U-Boot initializes the cpu: cpu/blackfin/cpu.c:cpu_init_f()
    1. Relocate L1 functions/data from external memory into L1 memory (only as needed)
    2. Turn on hardware trace buffer (if enabled via debug dumping)
    3. Enable reset on double fault (if panic hang is disabled)
    4. Jump to the next step …
  3. U-Boot initializes important services: lib_blackfin/board.c:board_init_f()
    1. Initialize the Instruction and Data CPLB tables
    2. Initialize exception callbacks
    3. Turn on Instruction and Data caches (if enabled)
    4. Initialize the global and board data structures
    5. Initialize IRQ callbacks
    6. Initialize the default environment
    7. Initialize the console baudrate
    8. Initialize the console
    9. Call board-specific checkboard()
      1. Do whatever board-specific very early things
    10. Initialize the Blackfin on-chip RTC
    11. Initialize the core timers
    12. Jump to the next step …
  4. U-Boot initializes remaining services: lib_blackfin/board.c:board_init_r()
    1. Initialize malloc subsystem
    2. Initialize parallel flash (if enabled)
    3. Initialize SPI flash (if enabled)
    4. Initialize NAND flash (if enabled)
    5. Relocate saved environment from flash (or wherever configured)
    6. Initialize common U-Boot devices
    7. Initialize common U-Boot function jumptable
    8. Call board-specific misc_init_r()
      1. Do whatever board-specific misc/late init things
    9. Initialize the network (if enabled)
    10. Initialize I2C (if enabled)
    11. Jump to the next step …
  5. U-Boot executes common code main_loop()
    1. See autoboot / U-Boot console / whatever like normal

Debug Options

All of these options are placed into your board configuration file. They require a CONFIG_ prefix to be added (it has been omitted here for readability).

DEBUG_DUMP

When an unhandled exception or interrupt occurs, the state of the processor will be dumped to the console as well as the hardware trace buffer. You can use this information to easily locate the code that triggered the exception.

For more in-depth information for how to use the hardware trace effectively as well as some examples, see the Debugging Applications document.

This option adds little overhead in terms of code size (1 kB) and adds no runtime overhead as it is only executed when an unhandled exception occurs -- which should never happen in at runtime.

DEBUG_DUMP_SYMS

The normal dump output will only include undecoded addresses. You need to translate them by hand to the actual symbols by comparing them to the objdump output of the U-Boot ELF. This can get tedious very quickly while doing development, so this option will embed the symbol table in the U-Boot binary. When a dump occurs, the addresses will be translated automatically so the output will include both the address and the associated symbol.

This option adds quite a bit of overhead in terms of code size (11 kB) but adds no runtime overhead as it is only executed by the dump code mentioned above.

DEBUG_EARLY_SERIAL

This will turn on serial output as early as possible (almost at power on) so that status messages will be constantly written to the UART console. Additional status messages will also be enabled at significant steps in the booting process.

The actual early implementation details can be found in the cpu/blackfin/serial.h and cpu/blackfin/serial.c files.

This option adds little overhead in terms of code size (2 kB) but adds runtime overhead and output that is really only suitable for debugging.

DEBUG_NULL_PTR

If you have code that may be accessing initialized data (or NULL pointers), then you can catch them. This will cover the start of memory (first 1kB) with two CPLB's. That way, any memory access to that region (data or instruction) will cause an exception. Any unhandled exceptions will cause a processor dump (see the DEBUG_DUMP options).

Keep in mind that any valid accesses will also be flagged, so this should only be enabled during development. You will be unable to use standard U-Boot commands such as memory display, copy, fill, etc… on the protected memory region.

This option adds little overhead (~70 bytes), but prevents the start of memory from being used.

PANIC_HANG

This option is pretty straight forward. Upon a crash that would automatically reset the board, the board will instead hang and constantly execute the emuexcpt instruction. If a JTAG ICE is hooked up, it will automatically be triggered.

HW_WATCHDOG

While not strictly a debug option, it can be quite useful while debugging remotely. This will enable the on-chip Blackfin hardware watchdog right after power on, and then common pieces of U-Boot will routinely poke it. If some piece of code gets hung up, the watchdog will reset the processor.

HW_WATCHDOG_TIMEOUT_INITCODE

If you're booting an LDR image, you should use a “largish” timeout value here as the processor will not be able to poke the watchdog until the LDR has finished completely loading. When booting over the UART or slow SPI devices, it can take a few seconds before U-Boot can take over. The default timeout is set to 20 seconds.

HW_WATCHDOG_TIMEOUT_START

Once U-Boot has started executing (before initialization/relocation), the watchdog will be programmed with a 5 second timeout. If your initialization process can take a while, increase this timeout as needed.

Troubleshooting

Memory Timings

The most common problem people hit is external memory setup. Either the hardware is not solid and so cannot run at all or at the max theoretical frequency, or the timings are wrong for the specified SCLK and memory part.

This is often observed when enabling early serial debug and then only seeing a string of letters that end in >.

Rather than figure out the timings yourself, read the sdram page (and in particular, read the spreadsheet at the bottom).

If things still aren't working, slow down the timings from whatever the memory datasheet says. Once you have a known working configuration, you can try speeding things back up until they fail, and then reviewing the hardware signals to fix any problems there.

It might also be helpful to connect a JTAG device and examine external memory to make sure the contents match exactly the u-boot binary that should be there. Failing memory devices/timings will show random bit errors.

JTAG Loading

Sometimes it is useful to load U-Boot directly up into external memory and bypass the flash programming process. Here are a few notes to show you how with GDB and a JTAG connection. By themselves, these commands will not work. You have to connect to a remote jtag target first, so see the gdbproxy if you need more information.

Initcode

First, since GDB will load things directly into external memory, we need to make sure external memory has been set up properly first. Normally this is handled by the Boot ROM calling our initcode, but since we are bypassing the Boot ROM, we need to load it by hand.

Start with a simple piece of code like so (call it init.S):

#include <asm/blackfin.h>
.global __start
__start:
	sp.l = LO(L1_SRAM_SCRATCH_END - 20);
	sp.h = HI(L1_SRAM_SCRATCH_END - 20);
	call _initcode;
1:
	emuexcpt;
	jump 1b;
.size __start, .-__start

Then just link it against the initcode to produce a standalone ELF like so (make sure to replace the -mcpu argument with the appropriate value for your board):

$ bfin-elf-gcc -nostartfiles init.S arch/blackfin/cpu/initcode.o -o init.elf -Iinclude -D__ASSEMBLY__ -mcpu=bf547 

GDB Load

Now, armed with these two ELF's (init.elf and u-boot), we can get things running with GDB.

The important commands we run are:

  1. set remotetimeout 300 - do not time out when working with slow JTAG devices
  2. load init.elf - load the initcode into on-chip memory …
  3. continue - … and execute it -- the emuexcpt instruction we used earlier will automatically signal the JTAG when it is done
  4. symbol-file u-boot - you may need this when call memset fails …
  5. load u-boot - load u-boot into the right place …
  6. call memset(&_bss_vma, 0, &_bss_len) - clear the BSS since GDB will not do it for us …
  7. continue - … and start executing U-Boot

Example

Here is a sample run. Note that the addresses and sizes may be significantly different from your setup -- that is perfectly normal!

If you're using this procedure repeatedly, enter this sequence in .gdbinit (working directory)

$ bfin-elf-gdb ./u-boot
(gdb) set remotetimeout 300

... connect to your jtag device ...

(gdb) load init.elf
Loading section .text, size 0x32c lma 0xffa00000
Start address 0xffa0031c, load size 812
Transfer rate: 1676 bits/sec, 812 bytes/write.
(gdb) c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0xffa00328 in ?? ()

(gdb) load u-boot
Loading section .text, size 0xa778 lma 0xfeb00000
Loading section .rodata, size 0x31e4 lma 0xfeb0a778
Loading section .data, size 0x2300 lma 0xfeb0d95c
Loading section .u_boot_cmd, size 0x280 lma 0xfeb0fc5c
Loading section .text_l1, size 0x40 lma 0xfeb0fedc
Start address 0xfeb00000, load size 65308
Transfer rate: 7829 bits/sec, 9329 bytes/write.
(gdb) call memset(&_bss_vma, 0, &_bss_len) (note for BF609 board you may skip this)
warning: Unable to restore previously selected frame.
$6 = 0xfeb0ff1c
Current language:  auto; currently asm
(gdb) c
Continuing.