GSoC2011(Week 1): Analysis of U-boot ARM boot code

This is such a busy week. At the end of last week, I have just ordered my OpenRD-Ultimate box but sadly it will be delivered at the end of June. So if I just wait for that box, I will not be able to test my code. That’s terrible! After talking with my mentor, I decided first porting coreboot to RealView Versatile/PB926EJ-S board then to OpenRD-Ultimate. Since qemu can emulate PB926EJ-S, I can test my code on it quickly and freely. After this work, the basic layout, libs and headers for ARM are ready to use. So I can start to port coreboot to OpenRD-Ultimate then.

In this week, I am studying the code of U-boot for board VersatilePB. You can get the source code I am using at here. It is not the newest release but the newer ones have bugs for this board which make the building fail.
Versatile/PB926EJ-S uses the ARM926EJ-S™ cpu core. So the boot code for it is at U-boot/arch/arm/cpu/arm926ejs/start.S. It will set the CPU mode, initialize the interrupts and SDRAM, then relocate the loader code, at last, I will jump to the code in ram to continue the boot.
First, let’s look at the interrupt vector table. It is at the beginning of start.S.

.globl _start
_start:
    b    reset

    ldr    pc, _undefined_instruction
    ldr    pc, _software_interrupt
    ldr    pc, _prefetch_abort
    ldr    pc, _data_abort
    ldr    pc, _not_used
    ldr    pc, _irq
    ldr    pc, _fiq

_undefined_instruction:
    .word undefined_instruction
_software_interrupt:
    .word software_interrupt
_prefetch_abort:
    .word prefetch_abort
_data_abort:
    .word data_abort
_not_used:
    .word not_used
_irq:
    .word irq
_fiq:
    .word fiq

    .balignl 16,0xdeadbeef

.start is the position where cpu fetches the first instruction, it jumps to actual reset code. Others are jump instructions for other interrupt functions.
Then following is some important addresses including TEXT_BASE, _start (C code address where this Assembler code will jump to at end), bss_start and bss_end.

_TEXT_BASE:
    .word    TEXT_BASE

.globl _armboot_start
_armboot_start:
    .word _start

.globl _bss_start
_bss_start:
    .word __bss_start

.globl _bss_end
_bss_end:
    .word _end

_bss_start and _bss_end are defined in the board-specific linker script and TEXT_BASE is defined in the board-specific config file.
Then is the actual reset code. It sets CPU to SVC32 mode, flushes v4 I/D caches, disables MMU and caches.

reset:

    mrs    r0,cpsr
    bic    r0,r0,#0x1f
    orr    r0,r0,#0xd3
    msr    cpsr,r0

    bl    cpu_init_crit
   
cpu_init_crit:

    mov    r0, #0
    mcr    p15, 0, r0, c7, c7, 0    /* flush v3/v4 cache */
    mcr    p15, 0, r0, c8, c7, 0    /* flush v4 TLB */

    mrc    p15, 0, r0, c1, c0, 0
    bic    r0, r0, #0x00002300    /* clear bits 13, 9:8 (–V- –RS) */
    bic    r0, r0, #0x00000087    /* clear bits 7, 2:0 (B— -CAM) */
    orr    r0, r0, #0x00000002    /* set bit 2 (A) Align */
    orr    r0, r0, #0x00001000    /* set bit 12 (I) I-Cache */
    mcr    p15, 0, r0, c1, c0, 0

following, control passes to board-specific lowlevel_init function using following code.

mov    ip, lr        /* perserve link reg across call */
bl    lowlevel_init    /* go setup pll,mux,memory */
mov    lr, ip        /* restore link */
mov    pc, lr        /* back to my caller */

This is the last change we do some init before relocation. Normally, we set the CPU Clock Speed and init the RAM here. But since this board(Versatile/PB) has its own boot monitor running before U-boot and init the RAM for us. So we have nothing to do in the function lowlevel_init. Actually, the lowlevel_init function (U-boot/board/armltd/versatile/lowlevel_init.S) looks like that:

.globl lowlevel_init
lowlevel_init:

    /* All done by Versatile's boot monitor! */
    mov pc, lr

It does nothing but just return to the caller.
After this function, the cpu_init_crit function just comes to an end. At here, all the necessary init before relocation have finished. Relocation code follows:

relocate:                /* relocate UBoot to RAM        */
    adr    r0, _start        /* r0 < current position of code   */
    ldr    r1, _TEXT_BASE        /* test if we run from flash or RAM */
    cmp     r0, r1                  /* don't reloc during debug         */
    beq     stack_setup
    ldr    r2, _armboot_start
    ldr    r3, _bss_start
    sub    r2, r3, r2        /* r2 < size of armboot            */
    add    r2, r0, r2        /* r2 < source end address         */

copy_loop:
    ldmia    r0!, {r3r10}        /* copy from source address [r0]    */
    stmia    r1!, {r3r10}        /* copy to   target address [r1]    */
    cmp    r0, r2            /* until source end addreee [r2]    */
    ble    copy_loop

First, it compares the reset address and TEXT_BASE, if they are the same, we are running U-boot directly in RAM so we don’t need to relocate, if not, it will copy the code between _armboot_start and _bss_start to TEXT_BASE which is in RAM. Then we will set up the stack:

stack_setup:
    ldr    r0, _TEXT_BASE        /* upper 128 KiB: relocated uboot   */
    sub    sp, r0, #128        /* leave 32 words for abort-stack   */
    sub    r0, r0, #CONFIG_SYS_MALLOC_LEN    /* malloc area                      */
    sub    r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo                        */

    sub    sp, r0, #12        /* leave 3 words for abort-stack    */
    bic    sp, sp, #7        /* 8-byte alignment for ABI compliance */

clear_bss:
    ldr    r0, _bss_start        /* find start of bss segment        */
    ldr    r1, _bss_end        /* stop here                        */
    mov    r2, #0x00000000        /* clear                            */

clbss_l:str    r2, [r0]        /* clear loop…                    */
    add    r0, r0, #4
    cmp    r0, r1
    ble    clbss_l

OK. Now, we are ready to jump the C code.

ldr    pc, _start_armboot

_start_armboot:
    .word start_armboot

start_armboot() is defined in file U-boot/arm/arm/lib/board.c. It is the 2ed stage of boot. In this function, U-boot will fully init the board, then start the main_loop waiting for the input from user or just booting the kernel.
Now, let’s move to the init_sequence function list. All the functions in this list will be executed one after another in function start_armboot().

init_fnc_t *init_sequence[] = {
    board_init,        /* basic board dependent setup */
    timer_init,        /* initialize timer */
    env_init,        /* initialize environment */
    init_baudrate,        /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    console_init_f,        /* stage 1 init of console */
    display_banner,        /* say that we are here */
    dram_init,        /* configure available RAM banks */
    display_dram_config,
    NULL,
};

First, board_init() is in file U-boot/board/armltd/versatile/versatile.c. It will set CPU clock frequency and then enable i-cache.
Then, timer_init() is in file U-boot/arch/arm/cpu/arm926ejs/versatile/timer.c. It will disable the timer first then set timer to the following mode.

/*
* Timer Mode : Free Running
* Interrupt : Disabled
* Prescale : 8 Stage, Clk/256
* Tmr Siz : 16 Bit Counter
* Tmr in Wrapping Mode
*/

Since we have set CONFIG_ENV_IS_IN_FLASH to y, env_init() is in file U-boot/common/env_flash.c.
It saves environment variables address to gd->env_addr.
Following is init_baudrate(). It is in file U-boot/arch/arm/lib/board.c. And it is just read the baudrate config from environment then save it in gd->baudrate and gd->bd->bi_baudrate.
This board uses AMBA PL011 UART device, so serial_init() is in file U-boot/drivers/serial/serial_pl01x.c. It will init the UART device by writing proper values into UART control registers.
console_init_f() is in file U-boot/common/console.c and its function is trival. Just set gd->have_console to 1.
Then call display_banner() to show that we have already done something.
As saying before, this board using boot monitor to init ram, so dram_init() (in file U-boot/board/armltd/versatile/versatile.c) does noting but return.
Wo…..After display_dram_config(), we finish the init sequences.
Wait! We don’t finish the whole init process.
After that, mem_malloc_init() is called and now we can use malloc to allocate memory.
Then flash_init() is called to init flash controller. stdio_init() will init all standard I/O devices the board has. jumptable_init() will set gd->jt to a list of common function pointers.
Then console_init_r(), it will add console devices into global device list and init output and input consoles.
Great! We have done so mush now. Since we don’t make use of interrupts during booting, so we don’t need to enable interrupts.
At here, we have finished the all init sequences and all the things on board are ready to use.
So, why not call the main_loop()?

2 thoughts on “GSoC2011(Week 1): Analysis of U-boot ARM boot code”

  1. Hi Hamo,

    Working with the emulation is a good idea until you can get the hardware. Looks like you made good progress the first week.

    Marc

  2. Hi posting is having much information. Thanks for the post. I would like to know some more information.

    In this posting, at what line the code copied from the FLASH to SDRAM?
    Also “But since this board(Versatile/PB) has its own boot monitor running before U-boot and init the RAM for us”,
    if a bootmonitor is running before uboot, how/where it is started in the uboot code?

Comments are closed.