UBOOT学习笔记(七):UBOOT启动--从main_loop()到跳转到kernel入口点(UBOOT完结篇)

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的入口点,内核启动!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值