250205_Linux_011UBOOT启动流程详解 1

参考资料:https://doc.embedfire.com/lubancat/build_and_deploy/zh/latest/building_image/boot_image_analyse/boot_image_analyse_down.html

学习并不是照着书死读,而是能者居之。

1初始阶段:上电 —> 设备(裸机)初始化 —> 启动设备 —> 镜像头 —> Uboot启动入口
2uboot启动第一阶段:初始化硬件-->复制uboot第二阶段代码至RAM-->设置堆栈-->清除BSS段-->跳转到第二阶段入口处
3uboot启动第二阶段:开发板相关初始化-->NAND/EMMC初始化-->环境变量初始化-->串口控制台初始化-->中断初始化-->网络初始化-->进入run_main_loop函数
4uboot加载内核阶段:硬件初始化与内存分配-->u-boot自身重定位-->内核加载到DDR-->传递环境变量与设备树-->搭建内核运行环境-->控制权交接,启动Linux内核-->Linux内核开始运行

uboot.lds到__start阶段:

编译后在uboot的解压目录的uboot.lds是uboot的连接脚本,是uboot的入口。
__image_copy_start -->0x87800000 这里是镜像文件的起始地址
.vectors -> 0x87800000 用于存放中断向量表
arch/arm/cpu/armv7/start.o start.c

__image_copy_end 结束镜像文件重定向。

一、reset函数

二、uboot启动流程

1、reset函数

bicne=bic + ne

①、reset函数目的是将处理器设置为SVC模式,并且关闭FIQ和IRQ.

②、设置中断向量。

③、初始化CP15

④、

2、lowlevel_init函数

CONFIG_SYS_INIT_SP_ADDR

#define CONFIG_SYS_INIT_SP_ADDR \

(CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)

#define CONFIG_SYS_INIT_SP_OFFSET \

(CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)

#define CONFIG_SYS_INIT_RAM_SIZE   IRAM_SIZE

#define IRAM_SIZE                    0x00020000

#define CONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR

#define IRAM_BASE_ADDR 0x00900000      6ULL内部OCRAM

#define GENERATED_GBL_DATA_SIZE 256

0x00900000 + CONFIG_SYS_INIT_SP_OFFSE =>

0x00900000  + CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE =>

0x00900000 + 0x00020000 – 256 = 0x0091ff00

设置SP指针、R9寄存器

3、s_init函数

空函数。

4、_main函数

5、board_init_f函数

initcall_run_lis此函数会调用一系列的函数,这些函数保存在init_sequence_f数组里面,

version_string[] = U_BOOT_VERSION_STRING

#define U_BOOT_VERSION_STRING  U_BOOT_VERSION " (" U_BOOT_DATE " - " \

U_BOOT_TIME " " U_BOOT_TZ ")" CONFIG_IDENT_STRING

U_BOOT_VERSION = U-Boot 2016.03

TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)

CONFIG_SYS_MALLOC_LEN   (16 * SZ_1M)

CONFIG_ENV_SIZE SZ_8K

mx6ullevk.c mx6ullevk.h这两个文件长打交道

6、relocate_code函数

relocate_code函数有一个参数,r0=gd->relocaddr=0X9FF47000,uboot重定位后的首地址。

r1=0X87800000 源地址起始地址。

r4=0X9FF47000-0X87800000=0X18747000 偏移。

r2=0x8785dc6c

当简单粗暴的将uboot从0X87800000拷贝到其他地方以后,关于函数调用、全局变量引用就会出问题。Uboot对于这个的处理方法就是采用位置无关码,这个就需要借助于.rel.dyn段。

8785dcf8 <rel_a>:

8785dcf8: 00000000 andeq r0, r0, r0

878042b4 <rel_test>:

878042b4: e59f300c ldr r3, [pc, #12] ; 878042c8 <rel_test+0x14>

878042b8: e3a02064 mov r2, #100 ; 0x64

878042bc: e59f0008 ldr r0, [pc, #8] ; 878042cc <rel_test+0x18>

878042c0: e5832000 str r2, [r3]

878042c4: ea00d64c b 87839bfc <printf>

878042c8: 8785dcf8 ; <UNDEFINED> instruction: 0x8785dcf8

878042cc: 87842aaf strhi r2, [r4, pc, lsr #21]

设置r3为878042b4+8+12=878042c8的值,r3=8785dcf8。这里并没有直接去读取rel_a的地址,而是借助了878042c。

878042c8叫做Label。

重定位以后

9ffa4cf8 <rel_a>:

9ffa4cf8: 00000000 andeq r0, r0, r0

9ff4b2b4<rel_test>:

9ff4b2b4: e59f300c ldr r3, [pc, #12] ; 878042c8 <rel_test+0x14>

9ff4b2b8: e3a02064 mov r2, #100 ; 0x64

9ff4b2bc: e59f0008 ldr r0, [pc, #8] ; 878042cc <rel_test+0x18>

9ff4b2c0: e5832000 str r2, [r3]

9ff4b2c4: ea00d64c b 87839bfc <printf>

9ff4b2c8:   8785dcf8 ; <UNDEFINED> instruction: 0x8785dcf8

9ff4b2cc:   87842aaf strhi r2, [r4, pc, lsr #21]

Label中的值还是原来的!必须要将8785dcf8换为重定位后的rel_a地址。读取9ff4b2c8里面的数据,也就是老的rel_a的地址=8785dcf8+0x18747000=0x9ffa4cf8

重定位以后,需要对所有的Label保存的数据加上偏移!!

8785dcec: 87800020 strhi r0, [r0, r0, lsr #32]

8785dcf0: 00000017 andeq r0, r0, r7, lsl r0

……

8785e2fc: 878042c8 strhi r4, [r0, r8, asr #5]

8785e300: 00000017 andeq r0, r0, r7, lsl r0

878042c8+offset = 读取新的Label处的数据+offset

完成这个功能在连接的时候需要加上”-pie”

7、relocate_vectors函数

设置VBAR寄存器为重定位后的中断向量表起始地址。

8、board_init_r函数

Board_init_r函数和board_init_f函数很类似。board_init_r也是执行init_sequence_r初始化序列。

9、run_main_loop函数

run_main_loop

-> main_loop

-> bootdelay_process 获取bootdelay的值,然后保存到stored_bootdelay

全局变量里面,获取bootcmd环境变量值,并且将其

返回

-> autoboot_command 参数是bootcmd的值。

-> abortboot 参数为boot delay,此函数会处理倒计时

-> abortboot_normal 参数为boot delay,此函数会处理倒计时

-> cli_loop  uboot命令模式处理函数。

-> parse_file_outer

-> parse_stream_outer

-> parse_stream 解析输入的字符,得到命令

-> run_list 运行命令

-> run_list_real

-> run_pipe_real

-> cmd_process  处理命令,也就是执行命令

10、cli_loop函数

11、cmd_process函数

Uboot使用U_BOOT_CMD来定义一个命令。CONFIG_CMD_XXX来使能uboot中的某个命令。

U_BOOT_CMD最终是定义了一个cmd_tbl_t类型的变量,所有的命令最终都是存放在.u_boot_list段里面。cmd_tbl_t的cmd成员变量就是具体的命令执行函数,命令执行函数都是do_xxx。

cmd_process

->find_cmd  从.u_boot_list段里面查找命令,当找到对应的命令以后以返回值的

形式给出,为cmd_tbl_t类型

->cmd_call

->cmdtp->cmd  直接引用cmd成员变量

三、bootz启动Linux内核过程

Uboot启动Linux内核使用bootz命令,bootm。。bootz是如何启动Linux内核?uboot的生命是怎么终止的呢?linux又是怎么启动的呢?

1、image全局变量

bootm_headers_t images;

2、do_bootz函数

tftp 80800000 zImage

tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb

bootz 80800000 - 83000000

bootz命令的执行函数,do_xxxx,do_bootz是bootz的执行函数。

do_bootz

-> bootz_start

-> do_bootm_states 阶段为BOOTM_STATE_START

->  bootm_start 对images全局变量清零,

-> images->ep = 0X80800000

->bootz_setup 判断zImage是否正确

-> bootm_find_images

· -> boot_get_fdt 找到设备树,然后将设备树起始地址和长度,写入到images

的ft_addr和ft_len成员变量中。

-> bootm_disable_interrupts 关闭中断相关

-> images.os.os = IH_OS_LINUX;   表示要启动Linux系统

-> do_bootm_states  状态BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO 、

BOOTM_STATE_OS_GO,

-> bootm_os_get_boot_func  查找Linux内核启动函数。找到Linux内核启动函数

do_bootm_linux,赋值给boot_fn。

-> boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images); 就是do_bootm_linux。

-> boot_prep_linux  启动之前的一些工作,对于使用设备树来说,他会将

Bootargs传递给Linux内核,通过设备树完成。也就是向

Linux内核传参。

-> boot_selected_os BOOTM_STATE_OS_GO, do_bootm_linux

-> do_bootm_linux,BOOTM_STATE_OS_GO

-> boot_jump_linux

-> machid= gd->bd->bi_arch_number;

-> void (*kernel_entry)(int zero, int arch, uint params);

-> kernel_entry = (void (*)(int, int, uint))images->ep; 0X80800000。

-> announce_and_cleanup 输出Starting kernel……

-> kernel_entry(0, machid, r2);   启动Linux内核。Uboot的最终使命,启动Linux内核。

zimage_header 的zi_magic为zimage的幻数,魔术数。应该为0x016f2818。前面有9个32位的数据,那么9*4=36,0~35,第36个字节的数据开始就是zimage的幻数。

3、do_bootm_states函数

4、bootm_os_get_boot_func函数

5、do_bootm_linux函数