[uboot] (Chapter 3) uboot process - uboot-spl code process

Links to the original text: https://blog.csdn.net/ooonebook/article/details/52957395

The following examples take project X project tiny210 (s5pv210 platform, armv7 architecture) as an example.

[uboot] uboot process series:
[project X] tiny210(s5pv210) power-on start-up process (BL0-BL2)
[uboot] (Chapter 1) uboot process - Overview
[uboot] (Chapter 2) uboot process-uboot-spl compilation process

Suggested Reference Articles
[kernel startup process] (Chapter 2) Stage 1: Setting up SVC and closing interrupts
[kernel startup process] (Chapter 6) Phase 1 - Open MMU
Register of CP15 Coprocessor of ARM

It is suggested that we first look at the "project X] tiny210(s5pv210) power-on start-up process (BL0-BL2)", and learn about the BL0 BL1 BL2 stage after power-on, as well as the operation position and function of each stage according to an example.

========================================================================================================

I. Explanation

1. Introduction to uboot-spl

Compiling script project-X/u-boot/arch/arm/cpu/u-boot-spl.lds through uboot-spl

ENTRY(_start)
  • 1

So the code entry function for uboot-spl is _start.
It corresponds to the _start of the path project-X/u-boot/arch/arm/lib/vector.S, and the subsequent analysis begins with this function.

2. Description of CONFIG_SPL_BUILD

As mentioned earlier, when compiling SPL, the compilation parameters will have the following statements:
project-X/u-boot/scripts/Makefile.spl

KBUILD_CPPFLAGS += -DCONFIG_SPL_BUILD
  • 1

So in the process of compiling SPL code, the macro CONFIG_SPL_BUILD is open.
The code for uboot-spl and uboot is generic, and the difference is that they are distinguished by the CONFIG_SPL_BUILD macro.

2. What uboot-spl needs to do

The initial power-on state of the CPU. Many states need to be carefully set up, including CPU state, interrupt state, MMU state and so on.
In the uboot-spl architecture of armv7, the following things need to be done

  • Close interrupt, svc mode
  • Disable MMU, TLB
  • Some Initialization Operations at Chip Level and Board Level
    • IO initialization
    • Clock
    • Memory
    • Options, Serial Port Initialization
    • Option, nand flash initialization
    • Other additional operations
  • Load BL2, jump to BL2

The above work is the core of uboot-spl code flow.

3. Code flow

1. Code flow

The overall code flow is as follows. The core functions of spl are listed below.
_ start --> reset ---> Close interrupt
....................................|
.................................... —— -> cpu_init_cp15--> Close MMU,TLB
....................................|
.................................... —— -> cpu_init_crit--> lowlevel_init--> platform-level and board-level initialization
....................................|
.................................... —— -> main---> board_init_f_alloc_reserve & board_init_f_init_reserve & board_init_f-> load BL2 and jump to BL2
board_init_f is already a C language environment when it is executed. Here we need to finish the work of SPL and jump to BL2.

2,_start

The above has shown that _start is the entry to the entire spl, and the code is as follows:
arch/arm/lib/vector.S

_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
    .word   CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
    b   reset
  • 1
  • 2
  • 3
  • 4
  • 5

It jumps to reset.
Note that the spl process should be terminated in reset, that is, in reset, it should be transferred to BL2, or uboot.
Look at the implementation of reset later.

3,reset

Suggestions for reference [kernel startup process] (Chapter 2) Stage 1: Setting up SVC and closing interrupts Learn why to set up SVC, turn off interrupts, and how to operate.

The code is as follows:
arch/arm/cpu/armv7/start.S

    .globl  reset
    .globl  save_boot_params_ret

reset:
    /* Allow the board to save important registers */
    b   save_boot_params
save_boot_params_ret:
    /*
     * 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
@@ Above by setting CPSR Register settings CPU by SVC Mode, no interruption
@@ Specific operation can be referred to.<[kernel Start-up process] (Chapter II) The First Stage: Establishment SVC,Analysis of Closing Interruption

    /* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl  cpu_init_cp15
@@ call cpu_init_cp15,Initialization coprocessor CP15,To disable MMU and TLB. 
@@ There will be a section to analyze later.

    bl  cpu_init_crit
@@ call cpu_init_crit,Do some key initialization actions, i.e. platform-level and board-level initialization
@@ There will be a section to analyze later.
#endif

    bl  _main
@@ To jump to the main function, that is, to load BL2 And jump to BL2 Subject part
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

4,cpu_init_cp15

Suggestions for reference [kernel startup process] (Chapter 6) Phase 1 - Open MMU Analysis of two articles.
cpu_init_cp15 is mainly used to initialize the CP15 coprocessor. Its main purpose is to turn off its MMU and TLB.
The code is as follows (removing irrelevant parts of the code):
arch/arm/cpu/armv7/start.S

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
@@ All you need to know is that it's right. CP15 Some registers of the processor can be cleared.
@@ Will coprocessor c7\c8 Zero clearing and so on. Refer to the meaning of each register.< ARM Of CP15 Coprocessor Register

    /*
     * 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
@@ Through the introduction of the above article, we can know that cp15 Of c1 Register is MMU Controller
@@ The above pairs MMU Some bits are cleared and positioned to close MMU and cache For specific purposes, let's take a look at the above articles.

ENDPROC(cpu_init_cp15)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

5,cpu_init_crit

cpu_init_crit performs some key initialization actions, i.e. platform-level and board-level initialization. The core of the code is lowlevel_init, as follows
arch/arm/cpu/armv7/start.S

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

So lowlevel_init is the core of this function.
Lowlevel_init is usually implemented by the board-level code itself. But for some platforms, you can also use the generic lowlevel_init, which is defined in arch/arm/cpu/lowlevel_init.S.
Taking tiny210 as an example, in the process of transplanting tiny210, we need to create lowlevel_init.S under board/samsung/tiny210, that is, board-level directory, to implement lowlevel_init internally. (Actually, as long as lowlevel_init is implemented, there is no need to say where it is implemented, but usually the specification creates lowlevel_init.S to implement the lowlevel_init function specifically.

In lowlevel_init, we want to achieve the following:
* Check some reset status
* Close the watchdog
* Initialization of System Clock
* Initialization of Memory and DDR
* Serial port initialization (optional)
* Initialization of Nand flash

Let's take tiny210's lowlevel_init as an example. (Here's an illustration. When tiny210 was transplanted, kangear's lowlevel_init.S file was taken directly.)
This part of the code and platform correlation is very strong, simply introduce it.
board/samsung/tiny210/lowlevel_init.S

lowlevel_init:
    push    {lr}

    /* check reset status  */

    ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
    ldr r1, [r0]
    bic r1, r1, #0xfff6ffff
    cmp r1, #0x10000
    beq wakeup_reset_pre
    cmp r1, #0x80000
    beq wakeup_reset_from_didle
@@ Read Reset Status Register0xE010_a000 The value of the reset state is determined.

    /* IO Retention release */
    ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)
    ldr r1, [r0]
    ldr r2, =IO_RET_REL
    orr r1, r1, r2
    str r1, [r0]
@@ Read Mixed State Register E010_e000 The value of 0, for which some bits are positioned, and after reset, some bits need to be positioned for _______ 0. wakeup position1,I don't understand the details.

    /* Disable Watchdog */
    ldr r0, =ELFIN_WATCHDOG_BASE    /* 0xE2700000 */
    mov r1, #0
    str r1, [r0]
@@ Close the watchdog

@@ This ignores part of the external world. SROM Code for operation

    /* when we already run in ram, we don't need to relocate U-Boot.
     * and actually, memory controller must be configured before U-Boot
     * is running in ram.
     */
    ldr r0, =0x00ffffff
    bic r1, pc, r0      /* r0 <- current base addr of code */
    ldr r2, _TEXT_BASE      /* r1 <- original base addr in ram */
    bic r2, r2, r0      /* r0 <- current base addr of code */
    cmp     r1, r2                  /* compare r0, r1                  */
    beq     1f          /* r0 == r1 then skip sdram init   */
@@ Judge whether it's already there SDRAM Up running, if so, skip the following two pairs ddr Initialization steps
@@ The judgment method is as follows:
@@ 1,Getting the Current pc Pointer address, shield its low24bit,Storage and r1 in
@@ 2,Obtain_TEXT_BASE(CONFIG_SYS_TEXT_BASE)Address, that is uboot Link Address of Code Section, Subsequently in uboot When you write a passage, you will explain it and shield it from being low.24bit
@@ 3,If it's equal, skip it. DDR Initialization part

    /* init system clock */
    bl system_clock_init
@@ Initialize the system clock, and then have time to study how to configure it.

    /* Memory initialize */
    bl mem_ctrl_asm_init
@@ Important note: initialize here DDR Yes!!! I will write an article explaining it later. s5pv210 How to Initialize Platform DDR.

1:
    /* for UART */
    bl uart_asm_init
@@ Serial port initialization, where the serial port will print out a'O'Characters, followed by characters to UTXH_OFFSET In the register, the corresponding characters can be output on the serial port.

    bl tzpc_init

#if defined(CONFIG_NAND)
    /* simple init for NAND */
    bl nand_asm_init
@@ Simply initialize NAND flash,Be on the cards BL2 The mirror image is in nand  flash Above.
#endif

    /* Print 'K' */
    ldr r0, =ELFIN_UART_CONSOLE_BASE
    ldr r1, =0x4b4b4b4b
    str r1, [r0, #UTXH_OFFSET]
@@ Print on Serial Port'K'Character, Representation lowlevel_init Already completed

    pop {pc}
@@ Eject PC Pointer, that is, return.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

When the character'OK'is printed out in the serial port, it indicates that lowlevel_init has been executed.
system_clock_init is where the clock is initialized. The function mem_ctrl_asm_init is where the DDR is initialized. These two functions should be studied later. Here's an impression first.

6,_main

The main goal of spl is to call board_init_f to perform the previous board-level initialization. In tiny210, the main design is to load BL2 to DDR and jump to BL2. DDR has been initialized in the above lowlevel_init.
Because board_init_f is implemented in C language, it is necessary to construct C language environment first.
Note: The code for uboot-spl and uboot is generic. The difference is that they are distinguished by the CONFIG_SPL_BUILD macro.
So in the following code, we only list the spl related parts, that is, the parts included by CONFIG_SPL_BUILD.
arch/arm/lib/crt0.S

ENTRY(_main)

/*
 * Set up initial C runtime environment and call board_init_f(0).
 */
@ Note the comments here, which also show that the main purpose of the following code is to initialize C Running environment, calling board_init_f. 
    ldr sp, =(CONFIG_SPL_STACK)
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
    mov r0, sp
    bl  board_init_f_alloc_reserve
    mov sp, r0
    /* set up gd here, outside any C code */
    mov r9, r0
    bl  board_init_f_init_reserve

    mov r0, #0
    bl  board_init_f

ENDPROC(_main)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

The code is broken down as follows:
(1) Because the following is the C language environment, the first is to set up the stack.

    ldr sp, =(CONFIG_SPL_STACK)
@@ Set the stack to CONFIG_SPL_STACK

    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
@@ The stack is8Byte alignment,2^7bit=2^3byte=8byte

    mov r0, sp
@@ Store the stack address in r0 Register
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

With regard to CONFIG_SPL_STACK, we adopt the previous article.< [project X] tiny210(s5pv210) power-on start-up process (BL0-BL2)>
We already know that the BL1(spl) of s5pv210 runs in IRAM, and the address space of IRAM is 0xD002_0000-0xD003_7FFF. The front part of IRAM is the code part of BL1, so the last space of IRAM is used as a stack.
So CONFIG_SPL_STACK is defined as follows:
include/configs/tiny210.h

#define CONFIG_SPL_STACK    0xD0037FFF
  • 1

Note: The above is not the final stack address, but the temporary stack address!!!

(2) Allocate space for GD

    bl  board_init_f_alloc_reserve
@@ Allocate the front part of the stack to GD Use

    mov sp, r0
@@ Reset the stack pointer SP

    /* set up gd here, outside any C code */
    mov r9, r0
@@ Preservation GD Address to r9 Register
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Note: Although the address of sp is the same as that of GD, the stack grows downward, and GD occupies the back of the address, so there is no conflict.
With regard to GD, struct global_data, it can be simply understood that the global variables of uboot are all put here, which is more important, so a subsequent article will be written to explain global_data. All you need to know here is that you need to allocate space for this structure before you start the C language environment.
board_init_f_alloc_reserve is implemented as follows
common/init/board_init.c

ulong board_init_f_alloc_reserve(ulong top)
{
    /* Reserve early malloc arena */
    /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
    top = rounddown(top-sizeof(struct global_data), 16);
// Now top (that is, r0 register, which has stored temporary pointer address) is subtracted from sizeof(struct global_data), that is, a part of space is reserved for sizeof(struct global_data).
// rounddown represents the next 16 bytes to it

    return top;
// At this point, top stores the address of GD and SP.
//Return top. Notice that after returning, it's actually stored in the r0 register.
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Another point is that GD is not used in spl. It is mainly used in uboot, but it needs to allocate additional space in uboot, which will be explained when describing the process of uboot.

(3) Initialization of GD space
As mentioned earlier, the r0 register stores the address of GD.

    bl  board_init_f_init_reserve
  • 1

board_init_f_init_reserve is implemented as follows
common/init/board_init.c
When compiling SPL, the _USE_MEMCPY macro was not opened, so we removed the irrelevant part of _USE_MEMCPY.

void board_init_f_init_reserve(ulong base)
{
    struct global_data *gd_ptr;
    int *ptr;
    /*
     * clear GD entirely and set it up.
     * Use gd_ptr, as gd may not be properly set yet.
     */

    gd_ptr = (struct global_data *)base;
// Getting the address of GD from r0
    /* zero the area */
    for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); )
        *ptr++ = 0;
// Zero out GD space
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

(4) Jump to the Initialization Function at the Early Stage of Plate Level
The following code

    bl  board_init_f
  • 1

board_init_f needs to be implemented by board-level code itself.
In this function, tiny210 mainly implements loading BL2 from SD card to ddr, and then jumping to the corresponding position of BL2.
The implementation of tiny210 is as follows:
board/samsung/tiny210/board.c

#ifdef CONFIG_SPL_BUILD
void board_init_f(ulong bootflag)
{
    __attribute__((noreturn)) void (*uboot)(void);
    int val;
#define DDR_TEST_ADDR 0x30000000
#define DDR_TEST_CODE 0xaa
    tiny210_early_debug(0x1);
    writel(DDR_TEST_CODE, DDR_TEST_ADDR);
    val = readl(DDR_TEST_ADDR);
    if(val == DDR_TEST_CODE)
        tiny210_early_debug(0x3);
    else
    {
        tiny210_early_debug(0x2);
        while(1);
    }
// First test whether DDR is complete

    copy_bl2_to_ddr();
// Load BL2 code to ddr

    uboot = (void *)CONFIG_SYS_TEXT_BASE;
// The uboot function is set to the loading address of BL2
    (*uboot)();
// Calling the uboot function also jumps to the BL2 code
}
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

For the implementation of copy_bl2_to_ddr, that is, how to load BL2 from SD card or nand flash to DDR, please refer to the following article, [project X] tiny210(s5pv210) code loading instructions.

So far, SPL's task has been completed, and it has jumped into BL2, or uboot.

Keywords: C Makefile

Added by Mistat2000 on Tue, 27 Aug 2019 09:20:01 +0300