• uboot启动linux kernel的流程


    前言

    本文在u-boot启动流程分析这篇文章的基础上,简要梳理uboot启动linux kernel的流程。

    流程图

    请添加图片描述
    其中,

    • autoboot_command位于uboot/common/autoboot.c
    • run_command_list位于uboot/common/cli.c
    • do_bootm位于uboot/cmd/bootm.c
    • do_bootm_states位于uboot/common/bootm.c
    • do_bootm_linux位于uboot/arch/arm/lib/bootm.c
    • boot_prep_linux位于uboot/arch/arm/lib/bootm.c
    • boot_jump_linux位于uboot/arch/arm/lib/bootm.c

    autoboot_command

    void autoboot_command(const char *s)
    {
    	debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "");
    
    	if (s && (stored_bootdelay == -2 ||
    		 (stored_bootdelay != -1 && !abortboot(stored_bootdelay)))) {
    		bool lock;
    		int prev;
    
    		lock = IS_ENABLED(CONFIG_AUTOBOOT_KEYED) &&
    			!IS_ENABLED(CONFIG_AUTOBOOT_KEYED_CTRLC);
    		if (lock)
    			prev = disable_ctrlc(1); /* disable Ctrl-C checking */
    
    		run_command_list(s, -1, 0);
    
    		if (lock)
    			disable_ctrlc(prev);	/* restore Ctrl-C checking */
    	}
    
    	if (IS_ENABLED(CONFIG_USE_AUTOBOOT_MENUKEY) &&
    	    menukey == AUTOBOOT_MENUKEY) {
    		s = env_get("menucmd");
    		if (s)
    			run_command_list(s, -1, 0);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    其中,abortboot会等待一段时间timeout(由环境变量bootdelay设定),
    如果有按键被按下,则会返回1,此时将停止启动linux kernel,返回命令行。如果在timeout后依然无按键被按下,则继续执行启动linux kernel的命令。

    run_command_list

    run_command_list调用do_bootm的流程实现的关键点:
    在这里插入图片描述
    在上面的代码中,将"bootm"和do_bootm这个函数绑定。

    U_BOOT_CMD的定义在uboot/include/command.h中,可以自行查看源代码。

    do_bootm

    int do_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
    {
    #ifdef CONFIG_NEEDS_MANUAL_RELOC
    	static int relocated = 0;
    
    	if (!relocated) {
    		int i;
    
    		/* relocate names of sub-command table */
    		for (i = 0; i < ARRAY_SIZE(cmd_bootm_sub); i++)
    			cmd_bootm_sub[i].name += gd->reloc_off;
    
    		relocated = 1;
    	}
    #endif
    
    	/* determine if we have a sub command */
    	argc--; argv++;
    	if (argc > 0) {
    		char *endp;
    
    		simple_strtoul(argv[0], &endp, 16);
    		/* endp pointing to NULL means that argv[0] was just a
    		 * valid number, pass it along to the normal bootm processing
    		 *
    		 * If endp is ':' or '#' assume a FIT identifier so pass
    		 * along for normal processing.
    		 *
    		 * Right now we assume the first arg should never be '-'
    		 */
    		if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
    			return do_bootm_subcommand(cmdtp, flag, argc, argv);
    	}
    
    	return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
    		BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
    		BOOTM_STATE_LOADOS |
    #ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
    		BOOTM_STATE_RAMDISK |
    #endif
    #if defined(CONFIG_PPC) || defined(CONFIG_MIPS)
    		BOOTM_STATE_OS_CMDLINE |
    #endif
    		BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
    		BOOTM_STATE_OS_GO, &images, 1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    这个函数最核心的部分就是调用do_bootm_states。

    do_bootm_states

    int do_bootm_states(struct cmd_tbl *cmdtp, int flag, int argc,
    		    char *const argv[], int states, bootm_headers_t *images,
    		    int boot_progress)
    {
    	boot_os_fn *boot_fn;
    	ulong iflag = 0;
    	int ret = 0, need_boot_fn;
    
    	images->state |= states;
    
    	/*
    	 * Work through the states and see how far we get. We stop on
    	 * any error.
    	 */
    	if (states & BOOTM_STATE_START)
    		ret = bootm_start(cmdtp, flag, argc, argv);
    
    	if (!ret && (states & BOOTM_STATE_FINDOS))
    		ret = bootm_find_os(cmdtp, flag, argc, argv);
    
    	if (!ret && (states & BOOTM_STATE_FINDOTHER))
    		ret = bootm_find_other(cmdtp, flag, argc, argv);
    
    	/* Load the OS */
    	if (!ret && (states & BOOTM_STATE_LOADOS)) {
    		iflag = bootm_disable_interrupts();
    		ret = bootm_load_os(images, 0);
    		if (ret && ret != BOOTM_ERR_OVERLAP)
    			goto err;
    		else if (ret == BOOTM_ERR_OVERLAP)
    			ret = 0;
    	}
    
    	/* Relocate the ramdisk */
    #ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
    	if (!ret && (states & BOOTM_STATE_RAMDISK)) {
    		ulong rd_len = images->rd_end - images->rd_start;
    
    		ret = boot_ramdisk_high(&images->lmb, images->rd_start,
    			rd_len, &images->initrd_start, &images->initrd_end);
    		if (!ret) {
    			env_set_hex("initrd_start", images->initrd_start);
    			env_set_hex("initrd_end", images->initrd_end);
    		}
    	}
    #endif
    #if IMAGE_ENABLE_OF_LIBFDT && defined(CONFIG_LMB)
    	if (!ret && (states & BOOTM_STATE_FDT)) {
    		boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
    		ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,
    					&images->ft_len);
    	}
    #endif
    
    	/* From now on, we need the OS boot function */
    	if (ret)
    		return ret;
    	boot_fn = bootm_os_get_boot_func(images->os.os);
    	need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
    			BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
    			BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
    	if (boot_fn == NULL && need_boot_fn) {
    		if (iflag)
    			enable_interrupts();
    		printf("ERROR: booting os '%s' (%d) is not supported\n",
    		       genimg_get_os_name(images->os.os), images->os.os);
    		bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
    		return 1;
    	}
    
    
    	/* Call various other states that are not generally used */
    	if (!ret && (states & BOOTM_STATE_OS_CMDLINE))
    		ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
    	if (!ret && (states & BOOTM_STATE_OS_BD_T))
    		ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
    	if (!ret && (states & BOOTM_STATE_OS_PREP)) {
    		ret = bootm_process_cmdline_env(images->os.os == IH_OS_LINUX);
    		if (ret) {
    			printf("Cmdline setup failed (err=%d)\n", ret);
    			ret = CMD_RET_FAILURE;
    			goto err;
    		}
    		ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
    	}
    
    #ifdef CONFIG_TRACE
    	/* Pretend to run the OS, then run a user command */
    	if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
    		char *cmd_list = env_get("fakegocmd");
    
    		ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
    				images, boot_fn);
    		if (!ret && cmd_list)
    			ret = run_command_list(cmd_list, -1, flag);
    	}
    #endif
    
    	/* Check for unsupported subcommand. */
    	if (ret) {
    		puts("subcommand not supported\n");
    		return ret;
    	}
    
    	/* Now run the OS! We hope this doesn't return */
    	if (!ret && (states & BOOTM_STATE_OS_GO))
    		ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
    				images, boot_fn);
    
    	/* Deal with any fallout */
    err:
    	if (iflag)
    		enable_interrupts();
    
    	if (ret == BOOTM_ERR_UNIMPLEMENTED)
    		bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);
    	else if (ret == BOOTM_ERR_RESET)
    		do_reset(cmdtp, flag, argc, argv);
    
    	return ret;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122

    该函数的核心部分:

    • bootm_load_os将kernel镜像加载到内存中。
    • 调用do_bootm_linux启动kernel。

    do_bootm_linux

    int do_bootm_linux(int flag, int argc, char *const argv[],
    		   bootm_headers_t *images)
    {
    	/* No need for those on ARM */
    	if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
    		return -1;
    
    	if (flag & BOOTM_STATE_OS_PREP) {
    		boot_prep_linux(images);
    		return 0;
    	}
    
    	if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
    		boot_jump_linux(images, flag);
    		return 0;
    	}
    
    	boot_prep_linux(images);
    	boot_jump_linux(images, flag);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • boot_prep_linux负责准备传递给kernel的参数。
    • boot_jump_linux负责跳转执行kernel代码。

    boot_prep_linux

    static void boot_prep_linux(bootm_headers_t *images)
    {
    	char *commandline = env_get("bootargs");
    
    	if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {
    #ifdef CONFIG_OF_LIBFDT
    		debug("using: FDT\n");
    		if (image_setup_linux(images)) {
    			printf("FDT creation failed! hanging...");
    			hang();
    		}
    #endif
    	} else if (BOOTM_ENABLE_TAGS) {
    		debug("using: ATAGS\n");
    		setup_start_tag(gd->bd);
    		if (BOOTM_ENABLE_SERIAL_TAG)
    			setup_serial_tag(&params);
    		if (BOOTM_ENABLE_CMDLINE_TAG)
    			setup_commandline_tag(gd->bd, commandline);
    		if (BOOTM_ENABLE_REVISION_TAG)
    			setup_revision_tag(&params);
    		if (BOOTM_ENABLE_MEMORY_TAGS)
    			setup_memory_tags(gd->bd);
    		if (BOOTM_ENABLE_INITRD_TAG) {
    			/*
    			 * In boot_ramdisk_high(), it may relocate ramdisk to
    			 * a specified location. And set images->initrd_start &
    			 * images->initrd_end to relocated ramdisk's start/end
    			 * addresses. So use them instead of images->rd_start &
    			 * images->rd_end when possible.
    			 */
    			if (images->initrd_start && images->initrd_end) {
    				setup_initrd_tag(gd->bd, images->initrd_start,
    						 images->initrd_end);
    			} else if (images->rd_start && images->rd_end) {
    				setup_initrd_tag(gd->bd, images->rd_start,
    						 images->rd_end);
    			}
    		}
    		setup_board_tags(&params);
    		setup_end_tag(gd->bd);
    	} else {
    		printf("FDT and ATAGS support not compiled in - hanging\n");
    		hang();
    	}
    
    	board_prep_linux(images);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    可以看到,u-boot使用的是tag的方式传参。共计分为以下几类tag:

    • setup_serial_tag设定与板子序列号(64位)相关的参数。
    • setup_commandline_tag设置命令行启动参数,参数来自环境变量"bootargs"。
    • setup_revision_tag 设置修订版本。
    • setup_memory_tags设置内存区块相关参数。
    • setup_initrd_tag设置ramdisk相关的参数。

    注:以上参数并非都是必要的。

    boot_jump_linux

    static void boot_jump_linux(bootm_headers_t *images, int flag)
    {
    #ifdef CONFIG_ARM64
    	void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,
    			void *res2);
    	int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
    
    	kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
    				void *res2))images->ep;
    
    	debug("## Transferring control to Linux (at address %lx)...\n",
    		(ulong) kernel_entry);
    	bootstage_mark(BOOTSTAGE_ID_RUN_OS);
    
    	announce_and_cleanup(fake);
    
    	if (!fake) {
    #ifdef CONFIG_ARMV8_PSCI
    		armv8_setup_psci();
    #endif
    		do_nonsec_virt_switch();
    
    		update_os_arch_secondary_cores(images->os.arch);
    
    #ifdef CONFIG_ARMV8_SWITCH_TO_EL1
    		armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,
    				    (u64)switch_to_el1, ES_TO_AARCH64);
    #else
    		if ((IH_ARCH_DEFAULT == IH_ARCH_ARM64) &&
    		    (images->os.arch == IH_ARCH_ARM))
    			armv8_switch_to_el2(0, (u64)gd->bd->bi_arch_number,
    					    (u64)images->ft_addr, 0,
    					    (u64)images->ep,
    					    ES_TO_AARCH32);
    		else
    			armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,
    					    images->ep,
    					    ES_TO_AARCH64);
    #endif
    	}
    #else
    	unsigned long machid = gd->bd->bi_arch_number;
    	char *s;
    	void (*kernel_entry)(int zero, int arch, uint params);
    	unsigned long r2;
    	int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
    
    	kernel_entry = (void (*)(int, int, uint))images->ep;
    #ifdef CONFIG_CPU_V7M
    	ulong addr = (ulong)kernel_entry | 1;
    	kernel_entry = (void *)addr;
    #endif
    	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);
    	}
    
    	debug("## Transferring control to Linux (at address %08lx)" \
    		"...\n", (ulong) kernel_entry);
    	bootstage_mark(BOOTSTAGE_ID_RUN_OS);
    	announce_and_cleanup(fake);
    
    	if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
    		r2 = (unsigned long)images->ft_addr;
    	else
    		r2 = gd->bd->bi_boot_params;
    
    	if (!fake) {
    #ifdef CONFIG_ARMV7_NONSEC
    		if (armv7_boot_nonsec()) {
    			armv7_init_nonsec();
    			secure_ram_addr(_do_nonsec_entry)(kernel_entry,
    							  0, machid, r2);
    		} else
    #endif
    			kernel_entry(0, machid, r2);
    	}
    #endif
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    在这里插入图片描述

    • r2 = gd->bd->bi_boot_params; 是将TAG列表所在的内存地址赋值给通用寄存器R2。

    • kernel_entry(0, machid, r2); 跳转执行kernel的代码(自此uboot的使命完成,生命周期结束)。

    综上,Uboot传递给linux kernel的参数是通过R2传递的。当linux kernel启动后,会从R2中拿到TAG列表的地址,然后将TAG参数解析出来使用。

  • 相关阅读:
    基于springboot实现招聘信息管理系统项目【项目源码+论文说明】计算机毕业设计
    【操作系统】第三章:内存管理
    Apache-maven的安装与配置(IDEA)
    第二章——古典密码学及算法实现
    嵌入式开发:ST-LINK V2.1仿真器,Type-C接口
    Java面试题07-HashMap的扩容原理
    C++——string常用接口模拟实现
    Android如何管理多进程
    自定义开发odoo14的统计在线用户人数
    Android OpenCV 身份证识别实战
  • 原文地址:https://blog.csdn.net/weixin_39589699/article/details/134247002