以下内容根据wowo的文章进行整理学习,多数内容拷贝自wowo的文章,在适当的地方添加自己的理解,在此非常感谢wowo的大神们。
- u-boot版本:2017.03
- 开发板:imx8qxp mek
- u-boot配置:未打开SPL
前言
在README
文件中的Board Initialisation Flow
章节有关于板级初始化流程的说明,如下:
整个u-boot的流程都按照下面的规定走:
1 | Board Initialisation Flow: |
u-boot入口
在arch/arm/cpu/armv8/start.S
文件中定义:
调用顺序为: _start
→ lowlevel_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 | /* |
简单翻译如下:
- 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)
#endif2.分配
global data
所需的空间,将堆栈16 bits对齐之后,调用board_init_f_alloc_reserve
接口,从堆栈开始的地方,为u-boot的global data(struct global_data)
分配空间。
也就是调用common/init/board_init.c
的board_init_f_alloc_reserve()
函数。按照之前的说明,_main
主要是为了调用board_init_f()
初始化环境。这个环境提供了stack和放置GD
数据结构的地方,这两者都放在可读的RAM(SRAM或锁住的cached等)。在上下文环境中GD
、已初始化或未初始化的BSS
是不可用的。只有初始化的常量
可以使用。
1 | `board_init_f_alloc_reserve()`函数的定义如下: |
这个函数主要用来分配堆栈区域。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 | bl board_init_f_alloc_reserve |
在arch/arm/include/asm/global_data.h
文件中定义了指针gd
的值从x18
寄存器取得,这样的话,我们在后续的过程中就可以使用gd
了。
1 | #ifdef CONFIG_ARM64 |
注:在ARM中x0-x7
寄存器用于函数调用时参数传递,x0
一般用作返回值。
该函数调用之后,DDR SDRAM的layout如下:
3.
GD
的空间分配后,调用board_init_f_init_reserve
,初始化global data
,所谓的初始化,无非就是一些清零操作。
赋值gd_ptr
和gd->malloc_base
。执行完之后,DDR SDRAM的layout如下:4.调用
common/board_f.c
的board_init_f()
函数,参数为0。以下对该函数进行详细的说明:1
2mov 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_init
在common/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_STRING9.调用
display_text_info()
打印u-boot代码段的起始和结束地址,以及BSS段的起始和结束地址。1
2printf("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",
text_base, bss_start, bss_end);
上述两条打印语句,在串口打印的内容如下:
1 | [ 0.267] U-Boot 2017.03-g3d43db2-dirty (Mar 08 2018 - 16:12:42 +0800) |
因此,内存空间由上到下分别是:
1 | --------bss_end --------------高地址 |
text_base
由CONFIG_SYS_TEXT_BASE
来决定,text_base
也就是start.S
中执行_start
开始的地方,也就是u-boot的代码段。但是bss_start
和bss_end
的地址在哪里决定还没搞清楚。
这些内容也可以通过编译出来的u-boot.map文件查看到。
1 | Address of section .text set to 0x80020000 |
1 | .bss_start 0x0000000080061430 0x0 |
- 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_size
和CONFIG_SYS_SDRAM_BASE
(DDR的起始地址)确定gd->ram_top
和gd->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 buffer
、MMU page table
、LCD fb buffer
、trace buffer
等等。- 3.调用
reserve_uboot()
,reservegd->mon_len
和U-Boot code
,data & bss
的空间。分配完之后,DDR SDRAM布局如下: 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
#endif5.调用
reserve_board()
为struct bd_info
分配空间,此时可以得到gd->bd
并将其初始化为0。
执行完之后,DDR SDRAM布局如下:- 6.调用
reserve_global_data()
为struct global_data
分配空间,此时可以得到gd->new_gd
的值。
执行完之后,DDR SDRAM布局如下: - 7.调用
reserve_fdt()
通过gd->fdt_blob
计算出gd->fdt_size
的大小,未fdt分配空间,得到gd->new_fdt
的值。 - 8.调用
reserve_stacks()
设置16字节的irq stack,得到gd->irq_sp
的值。 - 9.调用
setup_dram_config()
做RAM configuration,主要是为了填充gd->bd->bi_dram
字段,这一部分内容由厂商实现。 - 10.调用
display_new_sp()
打印当前的gd->start_addr_sp
的值,也就是堆栈指针。至此,reserve空间已完毕,最终的DDR SDRAM的布局如下:
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
13static 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 | /* call board_init_r(gd_t *id, ulong dest_addr) */ |
board_init_r()
函数在common/board_r.c
文件中定义,这个函数中同样也会去执行由init_sequence_r[]
定义的一系列函数指针。
- 1.调用
initr_trace()
,初始化并使能u-boot的tracing system。 - 2.调用
initr_reloc()
,标记gd->flags
为GD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT
表示: Code was relocated to RAM and Full malloc() is ready。 - 3.调用
initr_caches()
去使能dcache
、icache
。 - 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_xxx
是Command Line Interface
的简写。
1 | /* We come here after U-Boot is initialised and ready to process commands */ |
- 1.调用
bootdelay_process()
主要是为了设置启动延时使用,可以通过CONFIG_BOOTDELAY
设置启动延时多少秒。通过s = getenv("bootcmd");
获取bootcmd
的值做为返回值。
调用process_fdt_options(gd->fdt_blob);
从device tree判断是否有kernel-offset
和rootdisk-offset
的说明,如果有的话,那么设置kernaddr
和rootaddr
的环境变量。
从这个来看,支持从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
参考资料
C运行环境:
http://microchipdeveloper.com/tls2101:c-runtime-environment
http://web.cs.ucdavis.edu/~pandey/Teaching/ECS142/Lects/runtime.pdf位置无关原理:
https://blog.csdn.net/ooonebook/article/details/53047992
https://blog.csdn.net/skyflying2012/article/details/37660265
https://github.com/lentinj/u-boot/blob/master/doc/README.arm-relocationu-boot启动命令:
https://blog.csdn.net/ooonebook/article/details/53495021
https://blog.csdn.net/ooonebook/article/details/53164198
http://www.wowotech.net/x_project/bubblegum_uboot_booti.html整体参考资料:
http://www.wowotech.net/u-boot/boot_flow_1.html
http://www.wowotech.net/u-boot/boot_flow_2.html
http://www.wowotech.net/u-boot/fit_image_overview.html
https://blog.csdn.net/ooonebook/article/details/52957395