u-boot启动流程

以下内容根据wowo的文章进行整理学习,多数内容拷贝自wowo的文章,在适当的地方添加自己的理解,在此非常感谢wowo的大神们。

  • u-boot版本:2017.03
  • 开发板:imx8qxp mek
  • u-boot配置:未打开SPL

前言

README文件中的Board Initialisation Flow章节有关于板级初始化流程的说明,如下:
整个u-boot的流程都按照下面的规定走:

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
76
77
78
Board Initialisation Flow:
--------------------------

This is the intended start-up flow for boards. This should apply for both
SPL and U-Boot proper (i.e. they both follow the same rules).

Note: "SPL" stands for "Secondary Program Loader," which is explained in
more detail later in this file.

At present, SPL mostly uses a separate code path, but the function names
and roles of each function are the same. Some boards or architectures
may not conform to this. At least most ARM boards which use
CONFIG_SPL_FRAMEWORK conform to this.

Execution typically starts with an architecture-specific (and possibly
CPU-specific) start.S file, such as:

- arch/arm/cpu/armv7/start.S
- arch/powerpc/cpu/mpc83xx/start.S
- arch/mips/cpu/start.S

and so on. From there, three functions are called; the purpose and
limitations of each of these functions are described below.

lowlevel_init():
- purpose: essential init to permit execution to reach board_init_f()
- no global_data or BSS
- there is no stack (ARMv7 may have one but it will soon be removed)
- must not set up SDRAM or use console
- must only do the bare minimum to allow execution to continue to
board_init_f()
- this is almost never needed
- return normally from this function

board_init_f():
- purpose: set up the machine ready for running board_init_r():
i.e. SDRAM and serial UART
- global_data is available
- stack is in SRAM
- BSS is not available, so you cannot use global/static variables,
only stack variables and global_data

Non-SPL-specific notes:
- dram_init() is called to set up DRAM. If already done in SPL this
can do nothing

SPL-specific notes:
- you can override the entire board_init_f() function with your own
version as needed.
- preloader_console_init() can be called here in extremis
- should set up SDRAM, and anything needed to make the UART work
- these is no need to clear BSS, it will be done by crt0.S
- must return normally from this function (don't call board_init_r()
directly)

Here the BSS is cleared. For SPL, if CONFIG_SPL_STACK_R is defined, then at
this point the stack and global_data are relocated to below
CONFIG_SPL_STACK_R_ADDR. For non-SPL, U-Boot is relocated to run at the top of
memory.

board_init_r():
- purpose: main execution, common code
- global_data is available
- SDRAM is available
- BSS is available, all static/global variables can be used
- execution eventually continues to main_loop()

Non-SPL-specific notes:
- U-Boot is relocated to the top of memory and is now running from
there.

SPL-specific notes:
- stack is optionally in SDRAM, if CONFIG_SPL_STACK_R is defined and
CONFIG_SPL_STACK_R_ADDR points into SDRAM
- preloader_console_init() can be called here - typically this is
done by selecting CONFIG_SPL_BOARD_INIT and then supplying a
spl_board_init() function containing this call
- loads U-Boot or (in falcon mode) Linux


u-boot入口

arch/arm/cpu/armv8/start.S文件中定义:
调用顺序为: _startlowlevel_init_main

  • _start为u-boot启动后的第一个执行地址。之所以是第一个执行的地址,是在arch/arm/cpu/armv8/u-boot.lds链接文件中指定的。
  • lowlevel_init()一般不需要实现,现在基本不用了。
  • _main为arm公共的,在./arch/arm/lib/crt0_64.S中定义,下面说明_main的实现。

_main简要说明

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
/*
* This file handles the target-independent stages of the U-Boot
* start-up where a C runtime environment is needed. Its entry point
* is _main and is branched into from the target's start.S file.
*
* _main execution sequence is:
*
* 1. Set up initial environment for calling board_init_f().
* This environment only provides a stack and a place to store
* the GD ('global data') structure, both located in some readily
* available RAM (SRAM, locked cache...). In this context, VARIABLE
* global data, initialized or not (BSS), are UNAVAILABLE; only
* CONSTANT initialized data are available. GD should be zeroed
* before board_init_f() is called.
*
* 2. Call board_init_f(). This function prepares the hardware for
* execution from system RAM (DRAM, DDR...) As system RAM may not
* be available yet, , board_init_f() must use the current GD to
* store any data which must be passed on to later stages. These
* data include the relocation destination, the future stack, and
* the future GD location.
*
* 3. Set up intermediate environment where the stack and GD are the
* ones allocated by board_init_f() in system RAM, but BSS and
* initialized non-const data are still not available.
*
* 4a.For U-Boot proper (not SPL), call relocate_code(). This function
* relocates U-Boot from its current location into the relocation
* destination computed by board_init_f().
*
* 4b.For SPL, board_init_f() just returns (to crt0). There is no
* code relocation in SPL.
*
* 5. Set up final environment for calling board_init_r(). This
* environment has BSS (initialized to 0), initialized non-const
* data (initialized to their intended value), and stack in system
* RAM (for SPL moving the stack and GD into RAM is optional - see
* CONFIG_SPL_STACK_R). GD has retained values set by board_init_f().
*
* TODO: For SPL, implement stack relocation on AArch64.
*
* 6. For U-Boot proper (not SPL), some CPUs have some work left to do
* at this point regarding memory, so call c_runtime_cpu_setup.
*
* 7. Branch to board_init_r().
*
* For more information see 'Board Initialisation Flow in README.

简单翻译如下:

  • 1.创建调用board_init_f()的C运行环境。关于C运行环境,可以参照最后一章的链接;
  • 2.调用board_init_f()做先前的板级初始化动作;
  • 3.设置中间环境,其中的堆栈和GD是由系统RAM中调用board_init_f()进行分配的;
  • 4a.调用relocate_code重新定位u-boot;
  • 4b.对于SPL,不用重新定位;
  • 5.为调用board_init_r()设置最后的环境;

_main的详细流程如下:

  • 1.设置初始的堆栈,基址由CONFIG_SYS_INIT_SP_ADDR定义。

    1
    2
    3
    4
    5
    #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr x0, =(CONFIG_SPL_STACK)
    #else
    ldr x0, =(CONFIG_SYS_INIT_SP_ADDR)
    #endif
  • 2.分配global data所需的空间,将堆栈16 bits对齐之后,调用board_init_f_alloc_reserve接口,从堆栈开始的地方,为u-boot的global data(struct global_data)分配空间。
    也就是调用common/init/board_init.cboard_init_f_alloc_reserve()函数。按照之前的说明,_main主要是为了调用board_init_f()初始化环境。这个环境提供了stack和放置GD数据结构的地方,这两者都放在可读的RAM(SRAM或锁住的cached等)。在上下文环境中GD、已初始化或未初始化的BSS是不可用的。只有初始化的常量可以使用。

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
`board_init_f_alloc_reserve()`函数的定义如下:
/*
* Allocate reserved space for use as 'globals' from 'top' address and
* return 'bottom' address of allocated space
*
* Notes:
*
* Actual reservation cannot be done from within this function as
* it requires altering the C stack pointer, so this will be done by
* the caller upon return from this function.
*
* IMPORTANT:
*
* Alignment constraints may differ for each 'chunk' allocated. For now:
*
* - GD is aligned down on a 16-byte boundary
*
* - the early malloc arena is not aligned, therefore it follows the stack
* alignment constraint of the architecture for which we are bulding.
*
* - GD is allocated last, so that the return value of this functions is
* both the bottom of the reserved area and the address of GD, should
* the calling context need it.
*/

ulong board_init_f_alloc_reserve(ulong top)
{
/* Reserve early malloc arena */
#if defined(CONFIG_SYS_MALLOC_F)
top -= CONFIG_SYS_MALLOC_F_LEN;
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
top = rounddown(top-sizeof(struct global_data), 16);

return top;
}

这个函数主要用来分配堆栈区域。top地址为CONFIG_SYS_INIT_SP_ADDR,如果定义了CONFIG_SYS_MALLOC_F,reserve 空间的起始地址为CONFIG_SYS_INIT_SP_ADDR - CONFIG_SYS_MALLOC_F_LEN ~ CONFIG_SYS_INIT_SP_ADDR。返回值为指向GD的地址为top-sizeof(struct global_data)。初始化堆栈和reserve空间后的内存layout如下global_data介绍
执行board_init_f_alloc_reserve()后,sp指针就指向了GD,并把值存到x18寄存器里。

1
2
3
4
bl  board_init_f_alloc_reserve
mov sp, x0
/* set up gd here, outside any C code */
mov x18, x0

arch/arm/include/asm/global_data.h文件中定义了指针gd的值从x18寄存器取得,这样的话,我们在后续的过程中就可以使用gd了。

1
2
3
4
5
6
#ifdef CONFIG_ARM64
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18")
#else
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
#endif
#endif

注:在ARM中x0-x7寄存器用于函数调用时参数传递,x0一般用作返回值。
该函数调用之后,DDR SDRAM的layout如下:

  • 3.GD的空间分配后,调用board_init_f_init_reserve,初始化global data,所谓的初始化,无非就是一些清零操作。
    赋值gd_ptrgd->malloc_base。执行完之后,DDR SDRAM的layout如下:
    图1

  • 4.调用common/board_f.cboard_init_f()函数,参数为0。以下对该函数进行详细的说明:

    1
    2
    mov x0, #0
    bl board_init_f

board_init_f()函数

u-boot将需要在board_init_f中初始化的内容,抽象为一系列API。这些API由u-boot声明,由平台的开发者根据实际情况实现。该函数在common/board_f.c文件中定义。
global data进行简单的初始化之后,调用位于init_sequence_f数组中的各种初始化API,进行各式各样的初始化动作。这些API有些需要板级厂商进行实现。以下是对init_sequence_f数组中相关的API进行说明。

  • 1.调用setup_mon_len()设置gd->mon_len的值,这个值表示u-boot代码大小。
  • 2.调用fdtdec_setup()设置gd->fdt_blob的值。
    如果打开了CONFIG_OF_CONTROL,也就是u-boot使用dts,那么会调用fdtdec_setup,设置gd->fdt_blob指针(即device tree所在的存储位置)的值。对ARM平台来说,u-boot的Makefile会通过连接脚本,将dtb文件打包到u-boot image的__dtb_dt_begin位置处,因此不需要特别关心。
    gd->fdt_blob = (ulong *)&_end;,因此通过u-boot.map文件查找到_end的地址为0x0000000080060570 _end = .,在u-boot的命令行模式读取该段内存数据md 80060570,显示如下:
    这段内容开始就是fdt的内容,也可以与dtb的内容对应起来。
    fdtdec_prepare_fdt()函数中,会通过gd->fdt_blob指向区域的值来判断是否是device tree。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    => md 80060570
    80060570: edfe0dd0 6aa20000 48000000 d49c0000 .......j...H....
    80060580: 28000000 11000000 10000000 00000000 ...(............
    80060590: 96050000 8c9c0000 00000000 00000080 ................
    800605a0: 00000000 00004000 00000000 00000000 .....@..........
    800605b0: 00000000 00000000 01000000 00000000 ................
    800605c0: 03000000 24000000 00000000 2c6c7366 .......$....fsl,
    800605d0: 38786d69 2d707871 6464706c 612d3472 imx8qxp-xxxxx4-a
    800605e0: 00326d72 2c6c7366 38786d69 00707871 rm2.fsl,imx8qxp.
    800605f0: 03000000 04000000 0b000000 01000000 ................
    80060600: 03000000 04000000 1c000000 02000000 ................
    80060610: 03000000 04000000 2b000000 02000000 ...........+....
    80060620: 03000000 1f000000 37000000 65657246 ...........7Free
    80060630: 6c616373 2e692065 5138584d 4c205058 scale i.MX8QXP X
    80060640: 52444450 52412034 0000324d 01000000 XXXX4 ARM2......
    80060650: 73757063 00000000 03000000 04000000 cpus............
    80060660: 1c000000 02000000 03000000 04000000 ................
  • 3.调用initf_malloc()设置gd->malloc_limit分配空间限制为CONFIG_SYS_MALLOC_F_LEN

  • 4.调用initf_dm()进行u-boot的driver model的初始化,在这里回去解析fdt的设备并注册与之匹配的驱动。关于这部分的内容,可以参照uboot 驱动模型

  • 5.调用env_init()设置gd->env_addr环境变量的地址。
    env_initcommon/env_mmc.c中定义(文件名不一定)。里面用到了个全局数组default_environment[],该数组在include/env_default.h中定义,数组中定义好多环境变量相关的,我们可以通过在u-boot终端敲pirntenv命令打印环境变量。
    环境变量的值可以在编译u-boot之后查看u-boot.cfg中找到。

  • 6.调用init_baud_rate()设置gd->baudrate波特率,也就是从环境变量中获取baudrate的值。
    1
    gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);

获取当前使用串口波特率,可以有两个途径(优先级从高到低),从baudrate中获取;从CONFIG_BAUDRATE配置项获取。

  • 7.调用serial_init()console_init_f()初始化串口相关的设备和驱动。
    初始化硬件串口,由原厂实现,最终在drivers/serial/文件中实现。
  • 8.display_options()显示u-boot的版本信息和编译信息,具体的定义是编译自动生成在:
    include/generated/version_autogenerated.h文件中。

    1
    2
    #define U_BOOT_VERSION_STRING U_BOOT_VERSION " (" U_BOOT_DATE " - " \
    U_BOOT_TIME " " U_BOOT_TZ ")" CONFIG_IDENT_STRING
  • 9.调用display_text_info()打印u-boot代码段的起始和结束地址,以及BSS段的起始和结束地址。

    1
    2
    printf("U-Boot code: %08lX -> %08lX  BSS: -> %08lX\n",
    text_base, bss_start, bss_end);

上述两条打印语句,在串口打印的内容如下:

1
2
[    0.267] U-Boot 2017.03-g3d43db2-dirty (Mar 08 2018 - 16:12:42 +0800)
[ 0.284] U-Boot code: 80020000 -> 80061430 BSS: -> 800984F8

因此,内存空间由上到下分别是:

1
2
3
4
5
6
7
8
9
--------bss_end  --------------高地址

BSS 段

--------bss_start -------------

代码段

-------- text_base(CONFIG_SYS_TEXT_BASE)------ 低地址

text_baseCONFIG_SYS_TEXT_BASE来决定,text_base也就是start.S中执行_start开始的地方,也就是u-boot的代码段。但是bss_startbss_end的地址在哪里决定还没搞清楚。
这些内容也可以通过编译出来的u-boot.map文件查看到。

1
2
3
4
5
6
7
8
9
10
11
12
Address of section .text set to 0x80020000
0x0000000000000000 . = 0x0
0x0000000000000000 . = ALIGN (0x8)

.text 0x0000000080020000 0x2a898
*(.__image_copy_start)
.__image_copy_start
0x0000000080020000 0x0 arch/arm/lib/built-in.o
0x0000000080020000 __image_copy_start
arch/arm/cpu/armv8/start.o(.text*)
.text 0x0000000080020000 0x110 arch/arm/cpu/armv8/start.o
0x0000000080020000 _start

1
2
3
4
5
6
7
8
9
.bss_start      0x0000000080061430        0x0
*(.__bss_start)
.__bss_start 0x0000000080061430 0x0 arch/arm/lib/built-in.o
0x0000000080061430 __bss_start

.bss_end 0x00000000800984f8 0x0
*(.__bss_end)
.__bss_end 0x00000000800984f8 0x0 arch/arm/lib/built-in.o
0x00000000800984f8 __bss_end
  • 10.调用print_cpuinfo()打印CPU的相关信息。
  • 11.调用show_board_info()打印板级的相关信息,在common/board_info.c文件中定义,主要去获取dts中model节点的信息,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /*
    * If the root node of the DTB has a "model" property, show it.
    * Then call checkboard().
    */
    int __weak show_board_info(void)
    {
    #ifdef CONFIG_OF_CONTROL
    DECLARE_GLOBAL_DATA_PTR;
    const char *model;

    model = fdt_getprop(gd->fdt_blob, 0, "model", NULL);

    if (model)
    printf("Model: %s\n", model);
    #endif

    return checkboard();
    }
  • 12.调用dram_init()初始化系统的DDR,dram_init应该由平台相关的代码实现。
    如果DDR已经初始化过了,则不需要重新初始化,只需要把DDR信息(DDR大小和初始地址)保存在global data中即可。在我们这里,将DDR的大小信息保存在gd->ram_size中。
    按照u-boot的说明,调用dram_init()之后,就要去分配DDR SDRAM的的空间以及relocate u-boot的代码了,注释说明如下,更多关于relocate u-boot,请见下文分解。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*
    * Now that we have DRAM mapped and working, we can
    * relocate the code and continue running from DRAM.
    *
    * Reserve memory at end of RAM for (top down in that order):
    * - area that won't get touched by U-Boot and Linux (optional)
    * - kernel log buffer
    * - protected RAM
    * - LCD framebuffer
    * - monitor code
    * - board info struct
    */

u-boot relocate

这部分的内容同样属于上一篇文章中board_init_f()的范畴内,主要是为了说明方便,本文单独成文描述u-boot relocate。
在说明relocate之前,先说为啥要relocate:

  • 在以前的板子上,u-boot有可能是运行在NOR FLASH或ROM上,空间很小,执行慢,而且不支持写操作,DDR SDRAM初始化完毕之后,需要将其relocate到DDR SDRAM去运行,空间大,执行的速度也比较快,支持写操作;
  • 考虑到后续的kernel是在DDR SDRAM的低端位置解压缩并执行的,为了避免麻烦,u-boot将使用DRAM的顶端地址,即gd->ram_top所代表的位置;

reserve空间

以下内容都是在DDR SDRAM高地址为relocate做reserve的准备。

  • 1.调用setup_dest_addr()设置u-boot的relocaddr地址,通过gd->ram_sizeCONFIG_SYS_SDRAM_BASE(DDR的起始地址)确定gd->ram_topgd->relocaddr,也就是将u-boot重定位到DDR最高的地址,执行完之后gd->relocaddr = gd->ram_top

    1
    2
    3
    4
    5
    6
    7
    #ifdef CONFIG_SYS_SDRAM_BASE
    gd->ram_top = CONFIG_SYS_SDRAM_BASE;
    #endif
    gd->ram_top += get_effective_memsize();
    gd->ram_top = board_get_usable_ram_top(gd->mon_len);
    gd->relocaddr = gd->ram_top;
    printf("Ram top: %08lX\n", (ulong)gd->ram_top);
  • 2.特殊功能所需空间的reserve,如log bufferMMU page tableLCD fb buffertrace buffer等等。

  • 3.调用reserve_uboot(),reserve gd->mon_lenU-Boot code, data & bss的空间。分配完之后,DDR SDRAM布局如下:
    图2
  • 4.调用reserve_malloc(),reserve malloc的空间,大小为TOTAL_MALLOC_LEN,该在include/common.h文件中定义,定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #if defined(CONFIG_ENV_IS_EMBEDDED)
    #define TOTAL_MALLOC_LEN CONFIG_SYS_MALLOC_LEN
    #elif ( ((CONFIG_ENV_ADDR+CONFIG_ENV_SIZE) < CONFIG_SYS_MONITOR_BASE) || \
    (CONFIG_ENV_ADDR >= (CONFIG_SYS_MONITOR_BASE + CONFIG_SYS_MONITOR_LEN)) ) || \
    defined(CONFIG_ENV_IS_IN_NVRAM)
    #define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)
    #else
    #define TOTAL_MALLOC_LEN CONFIG_SYS_MALLOC_LEN
    #endif
  • 5.调用reserve_board()struct bd_info分配空间,此时可以得到gd->bd并将其初始化为0。
    执行完之后,DDR SDRAM布局如下:
    图3

  • 6.调用reserve_global_data()struct global_data分配空间,此时可以得到gd->new_gd的值。
    执行完之后,DDR SDRAM布局如下:
    图4
  • 7.调用reserve_fdt()通过gd->fdt_blob计算出gd->fdt_size的大小,未fdt分配空间,得到gd->new_fdt的值。
  • 8.调用reserve_stacks()设置16字节的irq stack,得到gd->irq_sp的值。
    图5
  • 9.调用setup_dram_config()做RAM configuration,主要是为了填充gd->bd->bi_dram字段,这一部分内容由厂商实现。
  • 10.调用display_new_sp()打印当前的gd->start_addr_sp的值,也就是堆栈指针。至此,reserve空间已完毕,最终的DDR SDRAM的布局如下:
    图6

relocate u-boot

实际上的relocate u-boot就是将以前在低地址的内容拷贝到高地址与之对应的位置,并重新赋值指针。

  • 1.调用reloc_fdt()gd->fdt_blob的内容拷贝到gd->new_fdt,拷贝gd->fdt_size这么多。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    static int reloc_fdt(void)
    {
    #ifndef CONFIG_OF_EMBED
    if (gd->flags & GD_FLG_SKIP_RELOC)
    return 0;
    if (gd->new_fdt) {
    memcpy(gd->new_fdt, gd->fdt_blob, gd->fdt_size);
    gd->fdt_blob = gd->new_fdt;
    }
    #endif

    return 0;
    }
  • 2.调用setup_reloc()计算u-boot代码空间到relocation的位置的偏移,也就是gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;,拷贝GD内容到new_gd,也就是:memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));
    将最重要的GD内容拷贝到新的地址后,后面我们就可以根据GD来做很多事。至此,init_sequence_f[]中定义的函数指针都已执行完毕,也就是board_init_f()函数执行完毕,接下来就会到_main中执行relocate的动作了。

  • 3.调用relocate_code()将u-boot的存放地址根据board_init_f()计算后的地址重新改变,这部分涉及到很多汇编代码以及位置无关代码原理,其中还有很多不明白的,后续等明白了再补充。

board_init_r函数

_main的最后,调用board_init_r()函数,参数1为gd->new_gd,参数2为gd->relocaddr

1
2
3
4
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov x0, x18 /* gd_t */
ldr x1, [x18, #GD_RELOCADDR] /* dest_addr */
b board_init_r /* PC relative jump */

board_init_r()函数在common/board_r.c文件中定义,这个函数中同样也会去执行由init_sequence_r[]定义的一系列函数指针。

  • 1.调用initr_trace(),初始化并使能u-boot的tracing system。
  • 2.调用initr_reloc(),标记gd->flagsGD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT表示: Code was relocated to RAM and Full malloc() is ready。
  • 3.调用initr_caches()去使能dcacheicache
  • 4.调用initr_reloc_global_data()得到monitor_flash_len的值。
  • 5.调用initr_malloc()初始化从malloc_start~gd->relocaddr总共TOTAL_MALLOC_LEN大小空间为0。
  • 6.调用initr_dm(): Save the pre-reloc driver model and start a new one。
  • 7.调用board_init()做板级初始化,这部分的内容一般定义在board/vendor/xxx_board/xxx_board.c文件下,我这里的路径为board/freescale/imx8qxp_mek/imx8qxp_mek.c
  • 8.调用initr_serial()重新初始化初始化串口,在relocate之前,调用serial_init()进行初始化,在relocate之后(也就是这里),调用initr_serial() -> serial_initialize() -> serial_init()
    最终也是调用serial_init()做串口的初始化。
  • 9.调用initr_mmc()做mmc的初始化,相关内容在drivers/mmc/mmc.c文件中定义。
  • 10.调用initr_env()初始化环境变量,并从环境变量获取到load_addr的值,这个环境变量在default_environment[]就已经设定进去了。
  • 11.调用console_init_r(): fully init console as a device。
  • 12.调用board_late_init(),功能类似board_init(),涉及的配置项有CONFIG_BOARD_LATE_INIT
  • 13.调用run_main_loop()执行到main_loop(),开始命令行操作。

main_loop()

common/main.c文件中定义,这里只关注后面的几个函数调用。从这里开始,基本上都是通过环境变量来判断做什么事情。cli_xxxCommand Line Interface的简写。

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
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
const char *s;

bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

#ifdef CONFIG_VERSION_VARIABLE
setenv("ver", version_string); /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */

cli_init();

run_preboot_environment_command();

#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */

s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);

autoboot_command(s);

cli_loop();
panic("No CLI available");
}

  • 1.调用bootdelay_process()主要是为了设置启动延时使用,可以通过CONFIG_BOOTDELAY设置启动延时多少秒。通过s = getenv("bootcmd");获取bootcmd的值做为返回值。
    调用process_fdt_options(gd->fdt_blob);从device tree判断是否有kernel-offsetrootdisk-offset的说明,如果有的话,那么设置kernaddrrootaddr的环境变量。
    从这个来看,支持从device tree中传递去设置环境变量。
  • 2.调用cli_process_fdt()判断是否有secure boot相关的,这里没有。
  • 3.调用autoboot_command()执行环境变量bootcmd的内容,也就是执行相关的命令,在common/autoboot.c文件中定义。通过命令,就可以启动Linux Kernel了。

Boot Linux

README中有Boot Linux章节用来描述如何启动linux。bootm命令用来启动存储在RAM或Flash上的程序,bootargs环境变量用来传递参数给kernel。
我的理解是,u-boot从eMMC读取Image到DDR启动linux,主要是有以下几个步骤:

  • 1.setenv bootargs,设置bootargs用来传递参数给kernel。
  • 2.mmc read ram_addr emmc_blk cnt,从emmc读取Image并load到DDR中去。
  • 3.checkimage ram_addr检查镜像是否正确(非必须)。
  • 4.booti kernel_start_addr ramdisk_start_addr dtb_start_addr,从ram中的去启动kernel、ramdisk、dtb,booti的用法如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [    2.787] => help booti
    booti - boot arm64 Linux Image image from memory

    [ 210.831] Usage:
    [ 210.833] booti [addr [initrd[:size]] [fdt]]
    [ 210.838] - boot arm64 Linux Image stored in memory
    [ 210.843] The argument 'initrd' is optional and specifies the address
    [ 210.849] of an initrd in memory. The optional parameter ':size' allows
    [ 210.856] specifying the size of a RAW initrd.
    [ 210.860] Since booting a Linux kernel requires a flat device-tree, a
    [ 210.867] third argument providing the address of the device-tree blob
    [ 210.874] is required. To boot a kernel with a device-tree blob but
    [ 210.880] without an initrd image, use a '-' for the initrd argument.

关于u-boot的命令行模式和bootm/booti等启动命令的原理,可以参照下面的文章:


U-Boot porting

非常重要的一篇文章,讲述如何在porting一个新板子。
https://elinux.org/images/2/2a/Schulz-how-to-support-new-board-u-boot-linux.pdf


参考资料

Title:u-boot启动流程

Author:Victor Huang

Time:2019-07-03 / 21:07

Link:http://wowothink.com/146db8db/

License: Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)