u-boot-2014.04 startup process analysis

Publisher:DazzlingSpiritLatest update time:2018-10-12 Source: eefocusKeywords:u-boot Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

I recently started to use uboot, and now I need to port the 2014.4 version of uboot to the company's armv7 development board.

When searching online for articles about the uboot startup process, most of them are about older versions of uboot, so I decided to record the new version of uboot startup process and share it with everyone.


For uboot, I wrote a column to record some of my understanding. Friends who are interested can click the following link:

u-boot study notes


This is a hard-earned work, let’s share it with everyone. Please indicate the source when reprinting!

Author : kerneler

Email :karse0104@163.com

-------------------------------------------------------------------------

# (C) Copyright 2000-2013  

# Wolfgang Denk, DENX Software Engineering, wd@denx.de.  

#  

# SPDX-License-Identifier:  GPL-2.0+  

  

VERSION = 2014   

PATCHLEVEL = 04  

SUBLEVEL =  

EXTRAVERSION =  

NAME =  

-------------------------------------------------------------------------

As of the time I wrote this article, this version of uboot is the latest version.


The 2014.4 version of uboot starts to the command line and the important functions are: _start, _main, board_init_f, relocate_code, board_init_r.

1_start

For any program, the entry function is determined at link time, and the entry of uboot is determined by the link script. The default directory of the armv7 link script under uboot is arch/arm/cpu/u-boot.lds. This can be specified in the configuration file with CONFIG_SYS_LDSCRIPT.

The entry address is also determined by the connector and can be specified in the configuration file by CONFIG_SYS_TEXT_BASE. This will be added to the -Ttext option of the ld connector during compilation.

The configuration compilation principle of uboot is also worth learning. I would like to write another article to record it, so I won’t go into details here.

View u-boot.lds

----------------------------------------------------------------------------

OUTPUT_ARCH(arm)  

ENTRY(_start)  

SECTIONS  

{  

    . = 0x00000000;  

  

    . = ALIGN(4);  

    .text :  

    {     

        *(.__image_copy_start)  

        CPUDIR/start.o (.text*)  

        *(.text*)  

    }     

  

    . = ALIGN(4);  

    .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }  

  

    . = ALIGN(4);  

    .data : {   

        *(.data*)  

 

    }     

----------------------------------------------------------------------------


The definitions of these macros in the linker script are in linkage.h. As the name suggests, the entry point of the program is _start., followed by the text segment, data segment, etc.

 

_start is in arch/arm/cpu/armv7/start.S, and the analysis is as follows:

------------------------------------------------------------------------------

.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  

#ifdef CONFIG_SPL_BUILD  

_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  

_pad:           .word 0x12345678 // now 16*4=64   

#else  

.globl _undefined_instruction  

_undefined_instruction: .word undefined_instruction  

.globl _software_interrupt  

_software_interrupt:    .word software_interrupt  

.globl _prefetch_abort  

_prefetch_abort:    .word prefetch_abort  

.globl _data_abort  

_data_abort:        .word data_abort  

.globl _not_used  

_not_used:      .word not_used  

.globl _race  

_irq:           .word irq   

.globl_fiq  

_fiq: .word fiq   

_pad:           .word 0x12345678 // now 16*4=64  

#endif  // CONFIG_SPL_BUILD   

  

.global _end_vect  

_end_vect:  

  

 

    .balignl 16,0xdeadbeef  

------------------------------------------------------------------------------

.global declares _start as a global symbol, and _start will be linked to by the connector, which is the entry address in the link script.

The above code sets the arm exception vector table. The arm exception vector table is as follows:

 

address 

abnormal 

Enter Mode

describe

0x00000000 

Reset

Management Model

When the reset level is valid, a reset exception occurs and the program jumps to the reset handler for execution.

0x00000004 

Undefined instruction

Undefined mode

When an instruction that cannot be processed is encountered, an undefined instruction exception is generated

0x00000008

Software interrupts

Management Model

Execute SWI instruction to generate privileged operation instructions for programs in user mode

0x0000000c

Stored Instructions

Abort Mode

The address of the processor prefetch instruction does not exist, or the address does not allow the current instruction to access, resulting in an instruction prefetch abort exception.

0x00000010

Data Operations

Abort Mode

When the address of the processor data access instruction does not exist or the address does not allow the current instruction to access, a data abort exception is generated.

0x00000014

Unused

Unused

Unused

0x00000018

IRQ

IRQ

When the external interrupt request is valid and the I bit in the CPSR is 0, an IRQ exception is generated.

0x0000001c

FIQ

FIQ

When the fast interrupt request pin is valid and the F bit in the CPSR is 0, an FIQ exception is generated


The 8 exceptions each occupy 4 bytes, so a jump instruction is filled in at the entrance of each exception, jumping directly to the corresponding exception handling function. The reset exception jumps directly to the reset function, and the other 7 exceptions use ldr to load the processing function entry address into pc.

 

The following assembly defines seven exception entry functions. CONFIG_SPL_BUILD is not defined here, so the latter one is used.

Next, in the definition of _end_vect, .balignl is used to specify that the following code should be aligned to 16 bytes, and 0xdeadbeef is used for the missing bytes, so as to facilitate more efficient memory access. Next, analyze the following code

------------------------------------------------------------

#ifdef CONFIG_USE_IRQ  

// IRQ stack memory (calculated at run-time)   

.globl IRQ_STACK_START  

IRQ_STACK_START:  

    .word   0x0badc0de  

  

// IRQ stack memory (calculated at run-time)   

.globl FIQ_STACK_START  

FIQ_STACK_START:  

    .word 0x0badc0de  

#endif  

  

// IRQ stack memory (calculated at run-time) + 8 bytes   

.globl IRQ_STACK_START_IN  

IRQ_STACK_START_IN:  

    .word   0x0badc0de  

-----------------------------------------------------------
If interrupts are used in uboot, the starting address of the interrupt handling function stack is declared here. The value given here is 0x0badc0de, which is an illegal value. The comment also shows that this value will be recalculated at runtime. I found that the code is in interrupt_init.

-------------------------------------------------------------

reset:  

    bl  save_boot_params  

     

    // disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode, 

    // except if in HYP mode already 

       

    mrs r0, cpsr  

    and r1, r0, #0x1f       @ mask mode bits  

    teq r1, #0x1a       @ test for HYP mode  

    bicne   r0, r0, #0x1f       @ clear all mode bits  

    orrne   r0, r0, #0x13       @ set SVC mode  

    orr r0, r0, #0xc0       @ disable FIQ and IRQ  

    msr cpsr,r0  

-------------------------------------------------------------

After power-on or restart, the first instruction the processor gets is b reset, so it will jump directly to the reset function. Reset first jumps to save_boot_params, as follows:

/  

ENTRY(cpu_init_cp15)  

    // 

    // Invalidate L1 I/D 

    //  

    mov r0, #0          @ set up for MCR  

    mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs  

    mcr p15, 0, r0, c7, c5, 0   @ invalidate icache  

    mcr p15, 0, r0, c7, c5, 6   @ invalidate BP array  

    mcr     p15, 0, r0, c7, c10, 4  @ DSB  

    mcr     p15, 0, r0, c7, c5, 4   @ ISB  

  

    // 

    // disable MMU stuff and caches 

    //  

    mrc p15, 0, r0, c1, c0, 0  

    bic r0, r0, #0x00002000 @ clear bits 13 (--V-)  

    bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)  

    orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align  

    orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB  

#ifdef CONFIG_SYS_ICACHE_OFF  

    bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache  

#else  

    orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache  

#endif  

    mcr p15, 0, r0, c1, c0, 0  

#ifdef CONFIG_ARM_ERRATA_716044  

    mrc p15, 0, r0, c1, c0, 0   @ read system control register  

    orr r0, r0, #1 << 11    @ set bit #11  

    mcr p15, 0, r0, c1, c0, 0   @ write system control register  

#endif  

  

#if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))  

    mrc p15, 0, r0, c15, c0, 1  @ read diagnostic register  

    orr r0, r0, #1 << 4     @ set bit #4  

    mcr p15, 0, r0, c15, c0, 1  @ write diagnostic register  

#endif  

  

#ifdef CONFIG_ARM_ERRATA_743622  

    mrc p15, 0, r0, c15, c0, 1  @ read diagnostic register  

    orr r0, r0, #1 << 6     @ set bit #6  

    mcr p15, 0, r0, c15, c0, 1  @ write diagnostic register  

#endif  

  

#ifdef CONFIG_ARM_ERRATA_751472  

    mrc p15, 0, r0, c15, c0, 1  @ read diagnostic register  

    orr r0, r0, #1 << 11    @ set bit #11  

    mcr p15, 0, r0, c15, c0, 1  @ write diagnostic register  

#endif  

#ifdef CONFIG_ARM_ERRATA_761320  

    mrc p15, 0, r0, c15, c0, 1  @ read diagnostic register  

    orr r0, r0, #1 << 21    @ set bit #21  

    mcr p15, 0, r0, c15, c0, 1  @ write diagnostic register  

#endif  

  

    mov pc, lr          @ back to my caller  

 

ENDPROC(cpu_init_cp15)  

------------------------------------------------------------------------


The cpu_init_cp15 function configures the cp15 coprocessor related registers to set the processor's MMU, cache and tlb. If CONFIG_SYS_ICACHE_OFF is not defined, icache will be turned on. MMU and tlb will be turned off.
The specific configuration process can be compared with the cp15 registers, which will not be described in detail here.

 

Next, look at cpu_init_crit

--------------------------------------------------------------------------

/  

ENTRY(cpu_init_crit)  

    // 

     * Jump to board specific initialization... 

     * The Mask ROM will have already initialized 

     * basic memory. Go here to bump up clock rate and handle 

     * wake up conditions. 

    //  

    b   lowlevel_init       @ go setup pll,mux,memory  

 

ENDPROC(cpu_init_crit)  

--------------------------------------------------------------------------


From the comments, we can understand that the lowlevel_init function called by cpu_init_crit is an initialization function related to a specific development board. In this function, some pll initialization will be done. If it is not started from mem, memory initialization will be done to facilitate subsequent copying to mem for operation.

 

The lowlevel_init function needs to be transplanted to implement clk initialization and ddr initialization

After returning from cpu_init_crit, the work of _start is completed, and then _main is called. Let's summarize the work of _start:

1 The part summarized above, initialize the exception vector table, set the svc mode, and disable interrupts

2 Configure cp15 and initialize mmu cache tlb

3 Board level initialization, pll memory initialization

2_main

The _main function is in arch/arm/lib/crt0.S. The function of the main function is explained in detail in the comments. Let's analyze it in sections.

------------------------------------------------------------------------------

ENTRY(_main)  

   

// Set up initial C runtime environment and call board_init_f(0).   

  

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)  

    ldr sp, =(CONFIG_SPL_STACK)  

#else  

    ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)  

#endif  

    bic sp, sp, #7  // 8-byte alignment for ABI compliance  

    sub sp, sp, #GD_SIZE    // allocate one GD above SP   

    bic sp, sp, #7  // 8-byte alignment for ABI compliance  

    mov r9, sp      // GD is above SP  

    mov r0, #0  

 

    bl  board_init_f  

----------------------------------------------------------------------------

First, load the value defined by CONFIG_SYS_INIT_SP_ADDR into the stack pointer sp. This macro definition is specified in the configuration header file.

This code provides the environment for the board_init_f C function call, that is, the stack pointer sp is initialized

8-byte alignment, then minus GD_SIZE, this macro definition refers to the size of the global structure gd, which is 160 bytes here. This structure is used to store some global information of uboot and requires a separate memory.

Finally, sp is saved in the r9 register. Therefore, the address in the r9 register is the first address of the gd structure.

If you want to use the gd structure in all the following codes, you must add the DECLARE_GLOBAL_DATA_PTR macro definition to the file, as follows:

  1. #define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r9")  

view plain cop

The first address of the gd structure is the value in r9.

The C language function stack grows downward, where sp is the top of the malloc space minus the gd bd space. At first I was puzzled. If sp is set here, then all future C function calls will be in the malloc space, and the stack space will overlap. Don't worry, you will understand after reading board_init_f.

Next, let's talk about the code above _main. Then r0 is assigned to 0, that is, parameter 0 is 0, and board_init_f is called

3.board_init_f

When porting uboot, make a minimal version first. Many configuration options are not turned on. For example, hardware such as fb mmc are not turned on by default. Only the basic ddr serial is configured. This ensures that uboot can start normally and enter the command line before adding others.

Our analysis here is based on the most streamlined version, so that we can explain the uboot startup process more concisely.

The board_init_f function mainly initializes the global information structure gd according to the configuration.

I don't quite understand the meaning of some of the members in the gd structure. Here I will only talk about the members that I understand and that play a role later.

  1. gd->mon_len = (head)&__bss_end - (head)_start;  

Initialize mon_len, which represents the size of the uboot code.

------------------------------------------------------------------------------

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {  

    if ((*init_fnc_ptr)() != 0) {  

        hang ();  

    }  

}  

-------------------------------------------------------------------------------
Traverse and call all functions of init_sequence. The definition of init_sequence is as follows:

------------------------------------------------------------------------------

init_fnc_t *init_sequence[] = {  

    arch_cpu_init,      // basic arch cpu dependent setup   

    mark_bootstage,  

#ifdef CONFIG_OF_CONTROL  

    fdtdec_check_fdt,  

#endif  

#if defined(CONFIG_BOARD_EARLY_INIT_F)  

    board_early_init_f,  

#endif  

    timer_init,     // initialize timer   

#ifdef CONFIG_BOARD_POSTCLK_INIT  

    board_postclk_init,  

#endif  

#ifdef CONFIG_FSL_ESDHC  

    get_clocks,  

#endif  

    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   

    print_cpuinfo,      // display cpu info (and speed)   

#if defined(CONFIG_DISPLAY_BOARDINFO)  

    checkboard,     // display board info   

#endif  

#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)  

    init_func_i2c,  

#endif  

    dram_init,      // configure available RAM banks   

    NULL,  

};  

------------------------------------------------------------------------------
arch_cpu_init needs to be implemented. To start uboot first, you can write an empty function here.

 

timer_init is implemented in lib/time.c. It is also an empty function, but it has the __WEAK keyword. If you implement it yourself, it will call this function you implemented.

For the most streamlined uboot, what needs to be done well is ddr and serial, so we are most concerned about serial_init, console_init_f and dram_init.

First look at serial_init

------------------------------------------------------------------------------

int serial_init(void)  

{  

    return get_current()->start();  

}  

static struct serial_device *get_current(void)  

{  

    struct serial_device *dev;  

  

    if (!(gd->flags & GD_FLG_RELOC))  

        dev = default_serial_console();  

    else if (!serial_current)  

        dev = default_serial_console();  

    else  

        dev = serial_current;  

  

    // We must have a console device   

    if (!dev) {  

#ifdef CONFIG_SPL_BUILD  

        puts("Cannot find console\n");  

        hang();  

#else  

        panic("Cannot find console\n");  

#endif  

    }  

  

    return dev;  

 

}  

---------------------------------------------------------------------------------------------- 

 

gd->flags has not been initialized yet, serial_current is used to store the serial we are currently using, and it has not been initialized here either, so the final serial_device is default_serial_console(), which is implemented in the serial driver to return a default debugging serial port.

 

The serial_device structure represents a serial port device, and its members need to be implemented in your own serial driver.

In this way, get_current in serial_init obtains the default debugging serial port structure given in the serial port driver, executes start, and performs some specific serial port initialization.

console_init_f sets have_console in gd to 1. This function will not be described in detail.

display_banner,print_cpuinfo uses the current debugging serial port to print the uboot information.

Next is dram_init.

dram_init initializes gd->ram_size so that the code after board_init_f can plan the dram space.

The dram_init implementation can be achieved by defining macro definitions in the configuration file, or by reading the ddrc controller to obtain dram information.

Continue to analyze board_init_f, the remaining code will plan the sdram space!

-------------------------------------------------------------------

#if defined(CONFIG_SYS_MEM_TOP_HIDE)  

    // 

     * Subtract specified amount of memory to hide so that it won't 

     * get "touched" at all by U-Boot. By fixing up gd->ram_size 

     * the Linux kernel should now get passed the now "corrected" 

     * memory size and won't touch it either. This should work 

     * for arch/ppc and arch/powerpc. Only Linux board ports in 

     * arch/powerpc with bootwrapper support, that recalculate the 

     * memory size from the SDRAM controller setup will have to 

     * get fixed. 

       

    gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE;  

#endif  

  

 

    addr = CONFIG_SYS_SDRAM_BASE + get_effective_memsize();  

-------------------------------------------------------------------

The CONFIG_SYS_MEM_TOP_HIDE macro definition hides part of the memory space. The comment indicates that there is an interface in the kernel for the PPC processor to use the value provided by uboot, which we will not consider here.

The value of addr is CONFIG_SYS_SDRAM_BASE plus ram_size, which is the top of the available sdram.

--------------------------------------------------------------------

#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))  

    // reserve TLB table   

    gd->arch.tlb_size = PGTABLE_SIZE;  

    addr -= gd->arch.tlb_size;  

  

    // round down to next 64 kB limit   

    addr &= ~(0x10000 - 1);  

  

    gd->arch.tlb_addr = addr;  

    debug("TLB table from lx to lx\n", addr, addr + gd->arch.tlb_size);  

#endif  

  

    // round down to next 4 kB limit   

    addr &= ~(4096 - 1);  

 

    debug("Top of RAM usable for U-Boot at: lx\n", addr);  

---------------------------------------------------------------------

If icache and dcache are turned on, a tlb space of PATABLE_SIZE is reserved, and the first address of tlb storage is assigned to gd->arch.tlb_addr.

 

Finally, the value of addr is the address of tlb, which is 4kB aligned.

--------------------------------------------------------------------------------------

#ifdef CONFIG_LCD  

#ifdef CONFIG_FB_ADDR  

    gd->fb_base = CONFIG_FB_ADDR;  

#else  

    // reserve memory for LCD display (always full pages)   

    addr = lcd_setmem(addr);  

    gd->fb_base = addr;  

#endif // CONFIG_FB_ADDR   

#endif // CONFIG_LCD   

  

    // reserve memory for U-Boot code, data & bss 

    // round down to next 4 kB limit   

    addr -= gd->mon_len;  

    addr &= ~(4096 - 1);  

  

 

    debug("Reserving %ldk for U-Boot at: lx\n", gd->mon_len >> 10, addr);  

--------------------------------------------------------------------------------------

If you need to use frambuffer, use configuration fb first address CONFIG_FB_ADDR or call lcd_setmem to get fb size. There are board-level related functions that need to be implemented, but in order to start uboot first, the fb option is not turned on. The addr value is the fb first address.

 

gd->fb_base stores the first address of fb.

Then -gd->mon_len reserves space for the uboot code. At this point, the value of addr is determined, and addr is used as the target addr of uboot relocate.

At this point, we can see that the uboot space is now divided from the top to the bottom.

First, let's summarize the division of sdram space above addr:

From high to low: top-->hide mem-->tlb space(16K)-->framebuffer space-->uboot code space-->addr

Next, we need to determine the value of addr_sp.

---------------------------------------------------------------------------------------

#ifndef CONFIG_SPL_BUILD  

    // 

     * reserve memory for malloc() arena 

       

    addr_sp = addr - TOTAL_MALLOC_LEN;  

    debug("Reserving %dk for malloc() at: lx\n",  

            TOTAL_MALLOC_LEN >> 10, addr_sp);  

    // 

     * (permanently) allocate a Board Info struct 

     * and a permanent copy of the "global" data 

       

    addr_sp -= sizeof (bd_t);  

    bd = (bd_t *) addr_sp;  

    gd->bd = bd;  

    debug("Reserving %zu Bytes for Board Info at: lx\n",  

            sizeof (bd_t), addr_sp);  

#ifdef CONFIG_MACH_TYPE  

    gd->bd->bi_arch_number = CONFIG_MACH_TYPE; // board id for Linux   

#endif  

      

    addr_sp -= sizeof (gd_t);  

    id = (gd_t *) addr_sp;  

    debug("Reserving %zu Bytes for Global Data at: lx\n",  

            sizeof (gd_t), addr_sp);  

#ifndef CONFIG_ARM64  

    // setup stackpointer for exeptions   

    gd->irq_sp = addr_sp;  

#ifdef CONFIG_USE_IRQ  

    addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);  

    debug("Reserving %zu Bytes for IRQ stack at: lx\n",  

        CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);  

#endif  

    // leave 3 words for abort-stack      

    addr_sp -= 12;  

  

    // 8-byte alignment for ABI compliance   

    addr_sp &= ~0x07;  

#else   // CONFIG_ARM64   

    // 16-byte alignment for ABI compliance   

    addr_sp &= ~0x0f;  

 

#endif  // CONFIG_ARM64   

---------------------------------------------------------------------------------------


 

 

First, reserve malloc len, which I define as 0x400000.

The comments state that a permanent copy is made for bd and gd.

Space is reserved for the global information bd_t structure, with the first address stored in gd->bd.

Leave space for the gd_t structure. The first address is stored in id.
Save this address in gd->irq_sp as the exception stack pointer. We do not use interrupts in uboot.

Finally, 12 bytes are reserved for abort stack, which I don’t understand.

At this point, the value of addr_sp is determined, and the space allocation above addr_sp is summarized.

From high to low: addr-->malloc len(0x400000)-->bd len-->gd len-->12 byte-->addr_sp (the stack grows downward, and the space below addr_sp is used as stack space)

-----------------------------------------------------------------------

gd->bd->bi_baudrate = gd->baudrate;  

// Ram ist board specific, so move it to board code ...   

dram_init_banksize();  

display_dram_config();  // and display it   

  

gd->relocaddr = addr;  

gd->start_addr_sp = addr_sp;  

gd->reloc_off = addr - (ulong)&_start;  

debug("relocation Offset is: lx\n", gd->reloc_off);  

if (new_fdt) {  

    memcpy(new_fdt, gd->fdt_blob, fdt_size);  

    gd->fdt_blob = new_fdt;  

}  

 

memcpy(id, (void *)gd, sizeof(gd_t));  

-----------------------------------------------------------------------

Assign gd->baudrate to bd->bi_baudrate, gd->baudrate was initialized in baudrate_init above.

 

dram_init_banksize() is a board-level function that needs to be implemented. Get the bank information of ddr according to the ddrc on the board. Fill it in gd->bd->bi_dram[CONFIG_NR_DRAM_BANKS].

gd->relocaaddr is the target addr, gd->start_addr_sp is the target addr_sp, gd->reloc_off is the offset between the target addr and the actual code start address. reloc_off is very important and will be used as a parameter of the relocate_code function later to copy the code.

Finally, copy the data of the gd structure to the new address id.

The board_init_f function re-divides the sdram space. It can be seen that the stack space and the heap space are separated, so there is no problem before _main calls board_init_f.

And before the space is replanned, there is no problem of initializing the heap and using the heap space, such as the malloc function, so the previous problem of stack space overlap is an over-concern.

At this point, board_init_f ends and returns to _main

Four_main

After board_init_f is finished, the code is as follows:

-------------------------------------------------------------------------

#if ! defined(CONFIG_SPL_BUILD)  

  

//Set up intermediate environment (new sp and gd) and call 

//relocate_code(addr_moni). Trick here is that we'll return 

//'here' but relocated.  

  

    ldr sp, [r9, #GD_START_ADDR_SP] // sp = gd->start_addr_sp   

    bic sp, sp, #7  // 8-byte alignment for ABI compliance   

    ldr r9, [r9, #GD_BD]        // r9 = gd->bd   

    sub r9, r9, #GD_SIZE        // new GD is below bd   

  

    adr lr, here  

    ldr r0, [r9, #GD_RELOC_OFF]     // r0 = gd->reloc_off   

    add lr, lr, r0  

    ldr r0, [r9, #GD_RELOCADDR]     // r0 = gd->relocaddr   

    b   relocate_code  

 

here:  

-------------------------------------------------------------------------

This assembly is very interesting. The first four assembly lines implement the update of the new gd structure.

 

First, update sp and align sp to 8 bytes to facilitate the alignment of the stack opened by the subsequent functions.

Then get the gd->bd address to r9. Note that gd->bd has been updated to the newly allocated bd in board_init_f. The next assembly line will subtract the size of bd from r9, so that the newly allocated gd in board_init_f is obtained!

The subsequent assembly is to prepare for relocate_code. First, the here address is loaded, and then the new address offset is added to lr, which is the new here after code relocate. The relocate_code return bar goes to lr, which is the here of the new location!

Finally, save the new address of code in r0 and jump to relocate_code

五 relocate_code

The relocate_code function is in arch/arm/lib/relocate.S. This function implements copying the uboot code to relocaddr.

This part is the most core and the most difficult to understand code in the entire uboot. I wrote a separate article to introduce the working principle of this part. Friends who are interested can see the following link

http://blog.csdn.net/skyflying2012/article/details/37660265

I won’t go into details here.

We need to summarize here. From the above analysis, we can see that

The new version of uboot allocates sdram space from top to bottom.

No matter where uboot is started from, the code in spiflash, nandflash, sram, etc. will be relocated to a position at the top of sdram and continue to run.

I found a 2010.6 version of uboot and took a general look at the boot code. It determines whether relocate is needed by judging whether _start and TEXT_BASE (link address) are equal. If uboot is started from sdram, relocate is not needed.

The new version of uboot still has major changes in this regard.

I think there are two advantages to this change. First, you don't need to consider the boot method, all relocate code. Second, you don't need to consider the uboot link address, because it needs to be relocated.

Uboot sdram space planning diagram:


6_main

Return to _main from relocate_code, and then the last section of main code is as follows:

----------------------------------------------------------------------

// Set up final (full) environment   

  

    bl  c_runtime_cpu_setup // we still call old routine here   

  

    ldr r0, =__bss_start    // this is auto-relocated!   

    ldr r1, =__bss_end      // this is auto-relocated!   

  

    mov r2, #0x00000000     // prepare zero to clear BSS   

  

clbss_l:cmp r0, r1          // while not at end of BSS   

    strlo   r2, [r0]        // clear 32-bit BSS word   

    addlo   r0, r0, #4      // move to next   

    blo clbss_l  

  

    bl coloured_LED_init  

    bl red_led_on  

  

    // call board_init_r(gd_t *id, ulong dest_addr)   

    mov     r0, r9                  // gd_t   

    ldr r1, [r9, #GD_RELOCADDR] // dest_addr   

    // call board_init_r   

    ldr pc, =board_init_r   // this is auto-relocated!   

  

 

    // we should not return here.   

------------------------------------------------------------------------------------ 

First jump to c_runtime_cpu_setup, as follows:

-----------------------------------------------------------------------

ENTRY(c_runtime_cpu_setup)  

// 

 * If I-cache is enabled invalidate it 

   

#ifndef CONFIG_SYS_ICACHE_OFF  

    mcr p15, 0, r0, c7, c5, 0   @ invalidate icache  

    mcr     p15, 0, r0, c7, c10, 4  @ DSB   

    mcr     p15, 0, r0, c7, c5, 4   @ ISB   

#endif  

// 

 * Move vector table 

   

    // Set vector address in CP15 VBAR register   

    ldr     r0, =_start  

    mcr     p15, 0, r0, c12, c0, 0  @Set VBAR  

  

    bx  lr    

  

ENDPROC(c_runtime_cpu_setup)  

----------------------------------------------------------------------
If icache is enable, then icache will be invalidated to ensure that instructions are updated from sdram to cache.

 

Then update the first address of the exception vector table. Since the code is relocated, the exception vector table is also relocated.

Returning from c_runtime_cpu_setup, the following assembly is to clear the bss segment.

Next, coloured_LED_init and red_led_on are called respectively. Many development boards have LED indicator lights. Here, the power-on indicator light can be turned on for debugging purposes.

Finally, r0 is assigned the gd pointer, r1 is assigned the relocaddr, and the final board_init_r is entered!

7board_init_r

Parameter 1 is the new gd pointer, parameter 2 is the relocate addr, which is the new code address

-------------------------------------------------------------------------

gd->flags |= GD_FLG_RELOC;  // tell others: relocation done   

bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");  

  

monitor_flash_len = (ulong)&__rel_dyn_end - (ulong)_start;  

  

// Enable caches   

enable_caches();  

  

debug("monitor flash len: lX\n", monitor_flash_len);  

 

board_init();   // Setup chipselects   

-------------------------------------------------------------------------


Set gd->flags, the flag has been relocated. I don't understand the function of the monitor_flash_len variable. Enable cache, and finally board_init is the board support function that needs to be implemented. Do the basic initialization of the development board.

--------------------------------------------------------------------------

#ifdef CONFIG_CLOCKS  

    set_cpu_clk_info();  

#endif  

  

    serial_initialize();  

  

    debug("Now running in RAM - U-Boot at: lx\n", dest_addr);  

--------------------------------------------------------------------------
If CONFIG_CLOCKS is turned on, set_cpu_clk_info is also a board support function that needs to be implemented.

 

Let's focus on serial_initialize. For the most streamlined uboot that can start normally, serial and ddr must work normally.

 

The implementation is in drivers/serial/serial.c as follows:

---------------------------------------------------------------------------

void serial_initialize(void)  

{  

    mpc8xx_serial_initialize();  

    ns16550_serial_initialize();  

    pxa_serial_initialize();  

    s3c24xx_serial_initialize();  

    s5p_serial_initialize();  

    mpc512x_serial_initialize();。。。。  

   mxs_auart_initialize();  

    arc_serial_initialize();  

    vc0718_serial_initialize();  

  

    serial_assign(default_serial_console()->name);  

 

}  

---------------------------------------------------------------------------

All serial port drivers will implement a xxxx_serial_initialize function and add it to serial_initialize.

In the xxxx_serial_initialize function, all required serial ports (represented by the structure struct serial_device, which implements the basic transceiver configuration) are registered by calling serial_register. serial_register is as follows:

----------------------------------------------------------------------------

void serial_register(struct serial_device *dev)  

{  

#ifdef CONFIG_NEEDS_MANUAL_RELOC  

    if (dev->start)  

        dev->start += gd->reloc_off;  

    if (dev->stop)  

        dev->stop += gd->reloc_off;  

    if (dev->setbrg)  

        dev->setbrg += gd->reloc_off;  

    if (dev->getc)  

        dev->getc += gd->reloc_off;  

    if (dev->tstc)  

        dev->tstc += gd->reloc_off;  

    if (dev->putc)  

        dev->putc += gd->reloc_off;  

    if (dev->puts)  

        dev->puts += gd->reloc_off;  

#endif  

      

    dev->next = serial_devices;  

    serial_devices = dev;  

 

}  

----------------------------------------------------------------------------

That is, add your serial_dev to the global linked list serial_devices.

You can imagine that if you have 4 serial ports, you can define 4 serial devices in your serial port driver, implement the corresponding transceiver configuration, and then register the 4 serial ports with serial_register.
Go back to serial-initialize, and finally call serial_assign. As we said before, default_serial_console means that you give a default debugging serial port in the serial port driver. serial_assign is as follows:

-----------------------------------------------------------------------------

int serial_assign(const char *name)  

{  

    struct serial_device *s;  

  

    for (s = serial_devices; s; s = s->next) {  

        if (strcmp(s->name, name))  

            continue;  

        serial_current = s;  

        return 0;  

    }  

  

    return -SINGLE SELECT;  

------------------------------------------------------------------------------
serial_assign is to find the specified default debugging serial port from the serial_devices list. The condition is the name of the serial port. Finally, serial_current is the current default serial port.

In summary, the work of serial_initialize is to register all serial ports in all serial drivers into the serial_devices list, and then find the specified default serial port.

-------------------------------------------------------------------------------------

// The Malloc area is immediately below the monitor copy in DRAM 

    malloc_start = dest_addr - TOTAL_MALLOC_LEN;  

    mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN);  

-------------------------------------------------------------------------------------

According to our previous analysis in board_init_f, the part below relocate addr is the reserved space of malloc. Here we get the first address of malloc, malloc_start.

-------------------------------------------------------------------------------------

void mem_malloc_init(ulong start, ulong size)  

{     

    mem_malloc_start = start;  

    mem_malloc_end = start + size;  

    mem_malloc_brk = start;  

  

    memset((void *)mem_malloc_start, 0, size);  

  

    malloc_bin_reloc();  

}  

--------------------------------------------------------------------------------------

In mem_malloc_init, the space reserved by malloc is initialized, the starting address, the ending address, and cleared. We have already relocate, and there is no operation in malloc_bin_reloc.

The following code in board_init_r is to initialize some peripherals, such as mmc flash eth, setting of environment variables, and enabling of interrupts, etc. Here we need to talk about two functions about the serial port, stdio_init and console_init_r.
Looking at the stdio_init code, we only define serial, which will be transferred to serial_stdio_init, as follows:

--------------------------------------------------------------------------------------

void serial_stdio_init(void)  

{  

    struct stdio_dev dev;  

    struct serial_device *s = serial_devices;  

      

    while (s) {  

        memset(&dev, 0, sizeof(dev));  

      

        strcpy(dev.name, s->name);  

        dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;  

      

        dev.start = s->start;  

        dev.stop = s->stop;  

        dev.putc = s->putc;  

        dev.puts = s->puts;  

        dev.getc = s->getc;  

        dev.tstc = s->tstc;  

  

        stdio_register(&dev);  

  

        s = s->next;  

    }  

}  

--------------------------------------------------------------------------------------

Initialize a stdio_dev for all serial devices on the serial_devices list, with flag as output input, call stdio-register, and add stdio_dev to the global devs list.

As you can imagine, serial_stdio_init is implemented in drivers/serial/serial.c. uboot uses the kernel layering concept here. Under drivers/serial are specific serial drivers, which call serial_register to register in serial_devices. This can be said to be a universal serial driver layer. The universal serial layer calls serial-stdio-init to register all serials in the stdio device. This is the universal stdio layer.

It seems that the layering concept is still very important!
After calling stdio_init in board_init_r, console_init_r is called, as shown below

----------------------------------------------------------------------------------------------

int console_init_r(void)  

{  

    struct stdio_dev *inputdev = NULL, *outputdev = NULL;  

    int i;  

    struct list_head *list = stdio_get_list();  

    struct list_head *pos;  

    struct stdio_dev *dev;  

  

    // Scan devices looking for input and output devices   

    list_for_each(pos, list) {  

        dev = list_entry(pos, struct stdio_dev, list);  

  

        if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {  

            inputdev = dev;  

        }  

        if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {  

            outputdev = dev;  

        }  

        if(inputdev && outputdev)  

            break;  

    }  

  

   if (outputdev != NULL) {  

        console_setfile(stdout, outputdev);  

        console_setfile(stderr, outputdev);  

    }  

  

    // Initializes input console   

    if (inputdev != NULL) {  

        console_setfile(stdin, inputdev);  

    }  

  

#ifndef CONFIG_SYS_CONSOLE_INFO_QUIET  

    stdio_print_current_devices();  

#endif // CONFIG_SYS_CONSOLE_INFO_QUIET   

  

    // Setting environment variables   

    for (i = 0; i < 3; i++) {  

        setenv(stdio_names[i], stdio_devices[i]->name);  

    }  

  

    gd->flags |= GD_FLG_DEVINIT;    // device initialization completed   

  

    return 0;  

}  

----------------------------------------------------------------------------------------------
The first half of console_init_r is very clear. It searches for the dev with flag output or input from the devs.list list. If only serial has registered stdio_dev before, then outputdev and inputdev are both the first serial we registered.
Then console_setfile is called as follows:

-------------------------------------------------------------------------------

static int console_setfile(int file, struct stdio_dev * dev)  

{  

    int error = 0;  

  

    if (dev == NULL)  

        return -1;  

  

    switch (file) {  

    case stdin:  

    case stdout:  

    case stderr:  

        // Start new device   

        if (dev->start) {  

            error = dev->start();  

            // If it's not started dont use it   

            if (error < 0)  

                break;  

        }  

  

        // Assign the new device (leaving the existing one started)   

        stdio_devices[file] = dev;  

  

        // Update monitor functions 

        // (to use the console stuff by other applications) 

           

        switch (file) {  

        case stdin:  

            gd->jt[XF_getc] = dev->getc;  

            gd->jt[XF_tstc] = dev->tstc;  

            break;  

        case stdout:  

            gd->jt[XF_putc] = dev->putc;  

            gd->jt[XF_puts] = dev->puts;  

            gd->jt[XF_printf] = printf;  

            break;  

        }  

  

       break;  

  

    default:        // Invalid file ID   

        error = -1;  

    }  

    return error;  

}  

-------------------------------------------------------------------------------

First, run the start of the device, which is the start function of a specific serial implementation. Then put stdio_device into the stdio_devices global array. This array has 3 members, stdout, stderr, and stdin. Finally, an operation function will be set in gd.
In console_init_r, the flag status in gd will be changed to GD_FLG_DEVINIT. It means that the device initialization is complete.
After board_init_r completes the board-level initialization, it finally enters an infinite loop, prints the command line, and waits for command input and parsing. The uboot startup process is over here!

A lot of space is used above to explain the serial to console architecture under uboot from bottom to top. Let's take a look at the top-to-bottom process from printf to the final serial output in actual use.

First, let's look at printf, which is implemented in common/console.c as follows:

--------------------------------------------------------------------------------

int printf(const char *fmt, ...)  

{  

    va_list args;  

    uint i;  

    char printbuffer[CONFIG_SYS_PBSIZE];  

          

#if !defined(CONFIG_SANDBOX) && !defined(CONFIG_PRE_CONSOLE_BUFFER)  

    if (!gd->have_console)  

        return 0;  

#endif    

      

    va_start(args, fmt);  

      

    // For this to work, printbuffer must be larger than 

     * anything we ever want to print. 

        

    i = vscnprintf(printbuffer, sizeof(printbuffer), fmt, args);  

    va_end(args);  

      

    // Print the string   

    puts(printbuffer);  

    return i;  

}  

--------------------------------------------------------------------------------
The string concatenation is the same as the general printf implementation, and finally puts is called. The puts implementation is also in console.c, as follows:

--------------------------------------------------------------------------------

void puts(const char *s)  

{  

#ifdef CONFIG_SANDBOX  

    if (!gd) {  

        os_puts(s);  

        return;  

    }  

#endif  

  

#ifdef CONFIG_SILENT_CONSOLE  

    if (gd->flags & GD_FLG_SILENT)  

        return;  

#endif  

  

#ifdef CONFIG_DISABLE_CONSOLE  

    if (gd->flags & GD_FLG_DISABLE_CONSOLE)  

        return;  

#endif  

  

    if (!gd->have_console)  

        return pre_console_puts(s);  

  

    if (gd->flags & GD_FLG_DEVINIT) {  

        // Send to the standard output   

        fputs(stdout, s);  

    } else {  

        // Send directly to the handler   

        serial_puts(s);  

    }  

}  

------------------------------------------------------------------------------------------------ 

gd->have_console is set in console_init_f of board_init_f, and flag's GD_FLG_DEVINIT is set at the end of console_init_r in board_init_r just now.

If GD_FLG_DEVINIT is not set, indicating that the console is not registered, it is after board_init_f and before board_init_r is executed. At this time, serial_puts is called as follows:

--------------------------------------------------------------------------------


void serial_puts(const char *s)  

{  

    get_current()->puts(s);  

 

}         

-------------------------------------------------------------------------------- 

Directly call the function in serial.c, which is completely consistent with the configuration of serial_init in board_init_f. It only finds a default serial port to use, and ignores other serial ports.
If GD_FLG_DEVINIT is set, it means that the console registration is completed. Call fputs as follows:

----------------------------------------------------------------------------------

void fputs(int file, const char *s)  

{     

    if (file < MAX_FILES)  

        console_puts(file, s);  

}     

  

static inline void console_puts(int file, const char *s)  

{  

    stdio_devices[file]->puts(s);  

}  

-----------------------------------------------------------------------------------

fputs calls console_puts to find the member stdio_device corresponding to stdout from the global stdio_devices, calls puts, and finally calls the puts function of the specific serial.

After analysis, it can be summarized as follows:
It can be seen that for serial, uboot implements a two-level initialization:

stage 1, only initializes the default console serial, and after printf to puts, it will directly call the puts function of the specific serial port to implement printing

stage 2, register all serials as stdio_device, and select the specified debugging serial port as the stdout, stdin, and stderr of stdio_devices. After printf to puts, it finds the corresponding stdio_device in the global stdio_devices, calls puts of stdio-device, and finally calls puts of the specific serial to implement printing.

To distinguish these two stages, the gd flag, GD_FLG_DEVINIT, is used.


Keywords:u-boot Reference address:u-boot-2014.04 startup process analysis

Previous article:ARM CP15 Coprocessor Description
Next article:Another way to write Tiny210 character device driver

Recommended ReadingLatest update time:2024-11-17 02:59

u-boot-2009.08 transplantation on mini2440 (VI) --- add boot kernel function
Migration environment 1. Host environment: CentOS 5.5 under VMare, 1G memory. 2. Integrated development environment: Eclipse IDE 3. Compilation environment: arm-linux-gcc v4.4.3, arm-none-eabi-gcc v4.5.1. 4. Development board: mini2440, 2M nor flash, 128M nand flash. 5.u-boot version: u-boot-2009.08 6. References: ht
[Microcontroller]
uboot-2011.12 ported to S3C2440 (Sequence 2) - binutils binary tool set and u-boot
Overview binutils is a set of binary tools, including addr2line, ar, gprof, nm, objcopy, objdumpr, ranlib, size, strings, strip, etc. ar software ar is used to create, modify, and extract library files. ar requires at least two parameters to run, for example: $ ar rv libtest.a add.o minus.o It means to make add.o an
[Microcontroller]
U-Boot-2011.03 transplant nandflash to mini2440
u-boot2011.03 supports s3c2440, and the registers are defined in s3c24x0_cpu.h in the arch/arm/includer/asm/ directory. The code contains the s3c2410 read and write nandflash functions, so it is modified based on s3c2410 U-Boot source code download address http://www.linuxidc.com/Linux/2011-07/38897.htm
[Microcontroller]
OK6410A development board (three) 20 u-boot-2021.01 boot analysis U-boot image running part system clock
The registers mainly involved in setting the system clock Belongs to 3 SYSTEM CONTROLLER Range 0x7E00_F000 0x7E00_FFFF S3C6410 clock |---APLL---------ARMCLK --- for CPU | External crystal (XTIPLL) + internal OSC (oscillator) | 0     | |----HCLK   --- for AXI/AHB bus peripherals   |---OM --- Master Cloc
[Microcontroller]
OK6410A development board (three) 20 u-boot-2021.01 boot analysis U-boot image running part system clock
How to use TF/SD card to create Exynos 4412 u-boot boot disk in Ubuntu
/** ****************************************************************************** * @author Maoxiao Hu * @version V1.0.1 * @date Feb-2015 ****************************************************************************** * COPYRIGHT 2015 ISE of SHANDONG UNIVERSITY *******************************************************
[Microcontroller]
How to use TF/SD card to create Exynos 4412 u-boot boot disk in Ubuntu
Transplantation on Embedded Linux System Based on ADSP BF533
1 Introduction Boot Loader is a boot program that runs before the operating system kernel runs. It is used to initialize hardware devices, change the processor operation mode, reorganize interrupt vectors and establish memory space mapping, thereby bringing the system's hardware and software to an appropr
[Microcontroller]
Transplantation on Embedded Linux System Based on ADSP BF533
OK6410A development board (three) 13 u-boot-2021.01 boot analysis SPL image running part boot detailed analysis
url : git@github.com:lisider/u-boot.git branch : ok6410a commit id : e63a4077ad3aea53107495b0b68b95e720fe6033 config :ok6410a_mini_defconfig // There are 67 .S .s .c files involved From the entrance to the exit // Run at 0x0c00 0000 // The entry is the b reset at the _start label in arch/arm/lib/vectors.S rese
[Microcontroller]
Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号