以下内容根据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