1、main_loop
41 void main_loop(void)
42 {
43 const char *s; // 用于存储启动命令字符串
44
/* 标记当前启动阶段为"main_loop",用于启动过程分析和调试 */
45 bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
46
/* 如果启用了版本变量功能,将U-Boot版本信息保存到环境变量"ver"中 */
47 if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
48 env_set("ver", version_string); /* set version variable */
49
/* 初始化命令行接口(CLI),准备接收和处理用户命令 */
50 cli_init();
51
/* 如果启用了预启动命令功能,执行预定义的环境命令 */
52 if (IS_ENABLED(CONFIG_USE_PREBOOT))
53 run_preboot_environment_command();
54
/* 如果启用了TFTP更新功能,尝试通过TFTP协议进行固件更新 */
55 if (IS_ENABLED(CONFIG_UPDATE_TFTP))
56 update_tftp(0UL, NULL, NULL);
57
/*
* 处理启动延时:
* 1. 读取环境变量bootdelay的值
* 2. 在延时期间检测用户输入(如按键中断)
* 3. 返回配置的启动命令字符串(通常是CONFIG_BOOTCOMMAND)
*/
58 s = bootdelay_process();
59 if (cli_process_fdt(&s))
60 cli_secure_boot_cmd(s);
61
/*
* 尝试自动启动:
* 1. 如果在bootdelay期间没有用户中断,执行启动命令
* 2. 如果启动成功,将不会返回
*/
62 autoboot_command(s);
63
/*
* 如果自动启动没有执行(或被中断),进入交互式命令行循环:
* 1. 等待并处理用户输入的命令
* 2. 正常情况下不会退出此循环
*/
64 cli_loop();
65 panic("No CLI available");
66 }
关键位置:
第58行 s = bootdelay_process();
第62行 autoboot_command(s);
正常启动情况下,由 bootdelay_process() 函数获取启动命令字符串s,然后由autoboot_command函数进行自动执行该命令
2、bootdelay_process
332 const char *bootdelay_process(void)
333 {
334 char *s;
335 int bootdelay;
336
/* 增加启动计数器,用于记录启动尝试次数 */
337 bootcount_inc();
338
/* 获取环境变量"bootdelay"的值,若未设置则使用默认配置CONFIG_BOOTDELAY */
339 s = env_get("bootdelay");
340 bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
341
/*
* 如果启用了设备树控制,尝试从设备树获取bootdelay覆盖值
* 优先级: 设备树 > 环境变量 > 编译时默认值
*/
342 if (IS_ENABLED(CONFIG_OF_CONTROL))
343 bootdelay = fdtdec_get_config_int(gd->fdt_blob, "bootdelay",
344 bootdelay);
345
346 debug("### main_loop entered: bootdelay=%d\n\n", bootdelay);
347
348 if (IS_ENABLED(CONFIG_AUTOBOOT_MENU_SHOW))
349 bootdelay = menu_show(bootdelay);
350 bootretry_init_cmd_timeout();
351
352 #ifdef CONFIG_POST
/* 检查启动计数器是否超过限制 */
353 if (gd->flags & GD_FLG_POSTFAIL) {
354 s = env_get("failbootcmd");
355 } else
356 #endif /* CONFIG_POST */
357 if (bootcount_error())
/* 如果启动失败次数过多,使用"altbootcmd"备用命令 */
358 s = env_get("altbootcmd");
359 else
/* 正常情况下使用"bootcmd"主启动命令 */
360 s = env_get("bootcmd");
361
/* 如果启用了设备树控制,处理设备树中的其他启动选项 */
362 if (IS_ENABLED(CONFIG_OF_CONTROL))
363 process_fdt_options(gd->fdt_blob);
364 stored_bootdelay = bootdelay;
365
366 return s;
367 }
函数整体功能:处理启动延迟逻辑,返回需要执行的启动命令字符串指针(通常是bootcmd或备选命令)
因此,正常启动时,bootdelay_process 最后返回的加载命令来自bootcmd,首先应该关注的是 bootcmd 指令
3、bootcmd
/include/env_default.h
34 #ifdef CONFIG_BOOTCOMMAND
35 "bootcmd=" CONFIG_BOOTCOMMAND "\0"
36 #endif
/include/configs/mx6ul_14x14_evk.h
126 #define CONFIG_BOOTCOMMAND \
127 "run findfdt;" \
128 "mmc dev ${mmcdev};" \
129 "mmc dev ${mmcdev}; if mmc rescan; then " \
130 "if run loadbootscript; then " \
131 "run bootscript; " \
132 "else " \
133 "if run loadimage; then " \
134 "run mmcboot; " \
135 "else run netboot; " \
136 "fi; " \
137 "fi; " \
138 "else run netboot; fi"
第127行 run findfdt :
在i.MX6UL处理器启动过程中,根据板卡名称和版本号匹配对应的设备树文件(14x14版本 → imx6ul-14x14-evk.dtb)
/include/configs/mx6ul_14x14_evk.h
116 "findfdt="\
117 "if test $fdt_file = undefined; then " \
118 "if test $board_name = EVK && test $board_rev = 9X9; then " \
119 "setenv fdt_file imx6ul-9x9-evk.dtb; fi; " \
120 "if test $board_name = EVK && test $board_rev = 14X14; then " \
121 "setenv fdt_file imx6ul-14x14-evk.dtb; fi; " \
122 "if test $fdt_file = undefined; then " \
123 "echo WARNING: Could not determine dtb to use; fi; " \
124 "fi;\0" \
128行:mmc dev和129行:
mmc dev {mmcdev}; if mmc rescan
首先,切换当前MMC设备到{mmcdev}变量指定的设备编号。
mx6ul_14x14_evk的mmcdev变量被定义为1,USDHC2,查看imx6ull手册可知是eMMC设备
/include/configs/mx6ul_14x14_evk.h
65 "mmcdev="__stringify(CONFIG_SYS_MMC_ENV_DEV)"\0" \
...
160 #define CONFIG_SYS_MMC_ENV_DEV 1 /* USDHC2 */
因此,这两行的逻辑:
先执行mmcdev{mmcdev}切换到 EMMC 上,然后通过mmc rescan,扫描看有没有 SD 卡或者 EMMC 存在,没有的话直接执行 run netboot,从网络启动 Linux。如果 mmc 设备存在的话:
首先执行130行: run loadbootscript
"loadbootscript=" \
"fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};\0" \
展开后便是:
loadbootscript=fatload mmc 1:1 0x80800000 boot.scr;
从 mmc1 的分区 1 中读取文件 boot.src 到 DRAM 的 0x80800000 处,如果 mmc1 的分区 1 中没有 boot.src 这个文件,就执行133行:run loadimage
"loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}\0" \
展开便是:
loadimage=fatload mmc 1:1 0x80800000 zImage
即从 mmc1 的分区中,读取 zImage 到内存的 0x80800000 处,如果环境中存在zlmage文件,
随即执行134行:run mmcboot:
77 "mmcboot=echo Booting from mmc ...; " \
78 "run mmcargs; " \
79 "if test ${boot_fdt} = yes || test ${boot_fdt} = try; then " \
80 "if run loadfdt; then " \
81 "bootz ${loadaddr} - ${fdt_addr}; " \
82 "else " \
83 "if test ${boot_fdt} = try; then " \
84 "bootz; " \
85 "else " \
86 "echo WARN: Cannot load the DT; " \
87 "fi; " \
88 "fi; " \
89 "else " \
90 "bootz; " \
91 "fi;\0" \
第78行 run mmcargs:运行环境变量 mmcargs,mmcargs 用来设置 bootargs
第79行 if test ${boot_fdt}… :判断boot_fdt是否为 yes 或者 try,这里boot_fdt=try,因此执行80行
第80行 run loadfdt:从 mmc1 的分区 1 中读取 imx6ull-14x14-evk.dtb 文件放到指定位置,如果成功的话那就调用命令 bootz 启动 linux:
bootz 0x80800000 - 0x83000000
4、bootz
bootz 命令绑定到 do_bootz 函数,主要功能是解析参数并启动 zImage 格式的 Linux 内核,代码整体调用流程如下:
bootz (cmd/bootm.c)
├─ do_bootz()
│ ├─ bootz_start() // 验证镜像头信息
│ │ ├─ image_get_kernel()
│ │ └─ image_get_ramdisk()
│ ├─ bootm_find_images() // 查找额外镜像(设备树等)
│ └─ do_bootm_states() // 执行启动过程
│ └─ do_bootm_linux() // 具体Linux启动处理
│ ├─ boot_prep_linux()
│ │ ├─ image_setup_linux() // 设备树处理
│ │ └─ board_prep_linux() // 板级准备
│ └─ boot_jump_linux()
│ ├─ announce_and_cleanup()
│ ├─ armv8_setup_psci() // ARM64 PSCI设置
│ ├─ do_nonsec_virt_switch()
│ └─ kernel_entry() // OS linux入口点
/cmd/bootz.c
106 U_BOOT_CMD(
107 bootz, CONFIG_SYS_MAXARGS, 1, do_bootz,
108 "boot Linux zImage image from memory", bootz_help_text
109 );
61 int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
62 {
63 int ret;
64
65 /* Consume 'bootz' */
66 argc--; argv++;
67
68 if (bootz_start(cmdtp, flag, argc, argv, &images))
69 return 1;
70
71 /*
72 * We are doing the BOOTM_STATE_LOADOS state ourselves, so must
73 * disable interrupts ourselves
74 */
75 bootm_disable_interrupts();
76
77 images.os.os = IH_OS_LINUX;
78 ret = do_bootm_states(cmdtp, flag, argc, argv,
79 #ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
80 BOOTM_STATE_RAMDISK |
81 #endif
82 BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
83 BOOTM_STATE_OS_GO,
84 &images, 1);
85
86 return ret;
87 }
关键行:
第68行bootz_start:初始化启动镜像信息
第75行bootm_disable_interrupts():禁用中断,因为后续将自行处理BOOTM_STATE_LOADOS状态
第77行images.os.os = IH_OS_LINUX:明确指定操作系统类型为Linux
第78-84行do_bootm_states:调用do_bootm_states执行各个BOOT 阶段–BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO 和BOOTM_STATE_OS_GO
5、bootz_start
25 static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
26 char * const argv[], bootm_headers_t *images)
27 {
28 int ret;
29 ulong zi_start, zi_end;
30
31 ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
32 images, 1);
33
34 /* Setup Linux kernel zImage entry point */
35 if (!argc) {
36 images->ep = load_addr;
37 debug("* kernel: default image load address = 0x%08lx\n",
38 load_addr);
39 } else {
40 images->ep = simple_strtoul(argv[0], NULL, 16);
41 debug("* kernel: cmdline image address = 0x%08lx\n",
42 images->ep);
43 }
44
45 ret = bootz_setup(images->ep, &zi_start, &zi_end);
46 if (ret != 0)
47 return 1;
48
49 lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
50
55 if (bootm_find_images(flag, argc, argv))
56 return 1;
57
58 return 0;
59 }
第31-32行:调用 do_bootm_states 执行 BOOTM_STATE_START 状态,这是多阶段引导过程的第一步
第35-43行:如果没有提供参数(argc=0),使用默认加载地址 load_addr,如果提供了参数,将第一个参数作为16进制地址解析为入口地址,调试信息输出选择的地址。使用 bootz 命令启动系统的时候有填写设置系统在 DRAM 中的存储位置,因此入口地址 images->ep=0X80800000
第45-47行:调用 bootz_setup 设置zImage,获取镜像的起始(zi_start)和结束(zi_end)地址
第49行:使用 lmb_reserve 保留内核镜像占用的内存区域,防止其他引导组件使用这部分内存
第55-56行:调用 bootm_find_images 查找并处理其他引导镜像(如设备树、ramdisk等)
6、bootz_setup
/arch/arm/lib/zimage.c
21 int bootz_setup(ulong image, ulong *start, ulong *end)
22 {
23 struct arm_z_header *zi = (struct arm_z_header *)image;
24
25 if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC &&
26 zi->zi_magic != BAREBOX_IMAGE_MAGIC) {
27 #ifndef CONFIG_SPL_FRAMEWORK
28 puts("zimage: Bad magic!\n");
29 #endif
30 return 1;
31 }
32
33 *start = zi->zi_start;
34 *end = zi->zi_end;
35 #ifndef CONFIG_SPL_FRAMEWORK
36 printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n",
37 image, *start, *end);
38 #endif
39
40 return 0;
41 }
第23-31行:镜像头检查,将输入地址转换为 arm_z_header 结构体指针,检查 magic number 是否匹配:LINUX_ARM_ZIMAGE_MAGIC: 标准 Linux zImage 魔数,BAREBOX_IMAGE_MAGIC: Barebox 引导加载程序的魔数
第33-34行:设置镜像范围,从镜像头中提取 zi_start 和 zi_end 字段,通过输出参数返回这些值。start 和 end这两个指针,用于存放解析出的内核映像的起始和结束地址
7、bootm_find_images
239 int bootm_find_images(int flag, int argc, char * const argv[])
240 {
241 int ret;
242
243 /* find ramdisk */
244 ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
245 &images.rd_start, &images.rd_end);
246 if (ret) {
247 puts("Ramdisk image is corrupt or invalid\n");
248 return 1;
249 }
250
251 #if IMAGE_ENABLE_OF_LIBFDT
252 /* find flattened device tree */
253 ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,
254 &images.ft_addr, &images.ft_len);
255 if (ret) {
256 puts("Could not find a valid device tree\n");
257 return 1;
258 }
259 if (CONFIG_IS_ENABLED(CMD_FDT))
260 set_working_fdt_addr(map_to_sysmem(images.ft_addr));
261 #endif
262
263 #if IMAGE_ENABLE_FIT
264 #if defined(CONFIG_FPGA)
265 /* find bitstreams */
266 ret = boot_get_fpga(argc, argv, &images, IH_ARCH_DEFAULT,
267 NULL, NULL);
268 if (ret) {
269 printf("FPGA image is corrupted or invalid\n");
270 return 1;
271 }
272 #endif
273
274 /* find all of the loadables */
275 ret = boot_get_loadable(argc, argv, &images, IH_ARCH_DEFAULT,
276 NULL, NULL);
277 if (ret) {
278 printf("Loadable(s) is corrupt or invalid\n");
279 return 1;
280 }
281 #endif
282
283 return 0;
284 }
第244-249行是RAMDISK 处理:查找 ramdisk
第251-261行是进行设备树处理 :获取设备树地址和长度,获取后将起始地址和长度分别写到images 的 ft_addr 和 ft_len 成员变量中。
8、do_bootm_states
/common/bootm.c
521 int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
522 int states, bootm_headers_t *images, int boot_progress)
523 {
...
534 if (states & BOOTM_STATE_START)
535 ret = bootm_start(cmdtp, flag, argc, argv);
536
537 if (!ret && (states & BOOTM_STATE_FINDOS))
538 ret = bootm_find_os(cmdtp, flag, argc, argv);
539
540 if (!ret && (states & BOOTM_STATE_FINDOTHER))
541 ret = bootm_find_other(cmdtp, flag, argc, argv);
542
543 /* Load the OS */
544 if (!ret && (states & BOOTM_STATE_LOADOS)) {
545 iflag = bootm_disable_interrupts();
546 ret = bootm_load_os(images, 0);
547 if (ret && ret != BOOTM_ERR_OVERLAP)
548 goto err;
549 else if (ret == BOOTM_ERR_OVERLAP)
550 ret = 0;
551 }
552
...
577 boot_fn = bootm_os_get_boot_func(images->os.os);
578 need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
579 BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
580 BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
581 if (boot_fn == NULL && need_boot_fn) {
582 if (iflag)
583 enable_interrupts();
584 printf("ERROR: booting os '%s' (%d) is not supported\n",
585 genimg_get_os_name(images->os.os), images->os.os);
586 bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
587 return 1;
588 }
...
622 /* Now run the OS! We hope this doesn't return */
623 if (!ret && (states & BOOTM_STATE_OS_GO))
624 ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
625 images, boot_fn);
...
638 }
第534-551行: 调用bootm_start、bootm_find_os、bootm_find_other、bootm_disable_interrupts、bootm_load_os等,初始状态处理 (BOOTM_STATE_START)、查找操作系统及ramdisk、设备树等辅助资源、禁用中断确保加载过程原子性、将内核镜像加载到正确内存位置
第577行bootm_os_get_boot_func: 获取特定操作系统的启动函数即实际运行函数do_bootm_linux
/common/bootm_os.c
503 #ifdef CONFIG_BOOTM_LINUX
504 [IH_OS_LINUX] = do_bootm_linux,
/cmd/bootz.c
77 images.os.os = IH_OS_LINUX;
第624行boot_selected_os:是不会返回的调用,将控制权完全移交给操作系统
9、boot_selected_os
int boot_selected_os(int argc, char * const argv[], int state,
bootm_headers_t *images, boot_os_fn *boot_fn)
{
arch_preboot_os();
board_preboot_os();
boot_fn(state, argc, argv, images);
/* Stand-alone may return when 'autostart' is 'no' */
if (images->os.type == IH_TYPE_STANDALONE ||
IS_ENABLED(CONFIG_SANDBOX) ||
state == BOOTM_STATE_OS_FAKE_GO) /* We expect to return */
return 0;
bootstage_error(BOOTSTAGE_ID_BOOT_OS_RETURNED);
debug("\n## Control returned to monitor - resetting...\n");
return BOOTM_ERR_RESET;
}
此时boot_fn就是do_bootm_linux:
/arch/arm/lib/bootm.c
418 int do_bootm_linux(int flag, int argc, char * const argv[],
419 bootm_headers_t *images)
420 {
421 /* No need for those on ARM */
422 if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
423 return -1;
424
425 if (flag & BOOTM_STATE_OS_PREP) {
426 boot_prep_linux(images);
427 return 0;
428 }
429
430 if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
431 boot_jump_linux(images, flag);
432 return 0;
433 }
434
435 boot_prep_linux(images);
436 boot_jump_linux(images, flag);
437 return 0;
438 }
第435行:调用 boot_prep_linux() 进行 Linux 启动前的准备工作,主要包括:
(1)从环境变量中获取 bootargs 作为内核命令行参数
(2)如果启用了设备树支持(IMAGE_ENABLE_OF_LIBFDT)且存在设备树(images->ft_len),调用 image_setup_linux() 设置设备树
(3)调用板级特定的准备函数
第436行:调用 boot_jump_linux() 跳转到 Linux 内核执行,boot_jump_linux()–>kernel_entry
/* Subcommand: GO */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
...
// ARM32架构处理
unsigned long machid = gd->bd->bi_arch_number; // 机器ID
char *s;
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2; // 参数寄存器
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
/* 关键点1:获取32位内核入口 */
kernel_entry = (void (*)(int, int, uint))images->ep;
/* 关键点2:机器ID优先级(环境变量 > 板级配置) */
s = env_get("machid");
if (s) {
if (strict_strtoul(s, 16, &machid) < 0) {
debug("strict_strtoul failed!\n");
return;
}
printf("Using machid 0x%lx from environment\n", machid);
}
...
/* 关键点3:选择设备树或传统参数 */
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params;
if (!fake) {
...
/* 关键点4:传统启动调用 */
kernel_entry(0, machid, r2); // 参数:0, machid, 设备树/参数地址
}
#endif
}
其中,images->ep见结构体bootm_headers:
348 typedef struct bootm_headers {
...
379 image_info_t os; /* os image info */
380 ulong ep; /* entry point of OS */
...
412 } bootm_headers_t;
因此,可以看到,ARMv7架构最终由UBOOT进入Linux内核的步骤:
- 获取入口点:kernel_entry = images->ep
- 设置机器ID:从环境变量或板级数据获取, 环境变量 > 板级配置
- 设备树传递 :r2 = ft_addr 或 bi_boot_params
- 跳转内核: kernel_entry(0, machid, r2)
可以看到,kernel_entry指向OS linux的入口点,内核启动!!!