uboot源码分析uboot启动流程,uboot-CMD命令调用关系

uboot的最终目的是引导启动内核加载系统,根据这个线索我们可以首先找到uboot引导内核的main函数,查看系统引导的执行跳转的函数 main_loop。
下面对uboot函数的调用关系和主要调用函数进行分析。

一、uboot函数调用关系梳理

函数调用如下:
main.c common

void main_loop(void)
{
	cli_init();

	run_preboot_environment_command();
	s = bootdelay_process();
	autoboot_command(s);
}

当用户没有输入时,通常会在bootdealy延时时间到后通过autoboot_command(s)函数,自动执行uboot配置的bootcmd命令,引导启动内核。
这个main_loop(void)函数会循环执行,通过索引:run_main_loop调用main_loop()函数如下。
board_r.c (common)

static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
	sandbox_main_loop_init();
#endif
	/* main_loop() can return to retry autoboot, if so just run it again */
	for (;;)
		main_loop();
	return 0;
}

在执行 run_main_loop前,uboot的所有初始化工作都在 init_sequence_r[] 初始化函数数组中逐一列出。

static init_fnc_t init_sequence_r[] = {

initr_trace,
	initr_reloc,
initr_caches,
initr_reloc_global_data,
initr_barrier,
	initr_malloc,
	log_init,
	initr_bootstage,	/* Needs malloc() but has its own timer */
	initr_console_record,
	board_init,	/* Setup chipselects */
	set_cpu_clk_info, /* Setup clock information */
	efi_memory_init,
	stdio_init_tables,
	initr_serial,
	initr_announce,
	
	board_early_init_r,
	
	initr_env,
	
	initr_secondary_cpu,
	
	initr_pci,
	
	
	stdio_add_devices,
	initr_jumptable,
	
	console_init_r,		/* fully init console as a device */
	
	console_announce_r,
	show_board_info,
	misc_init_r,		/* miscellaneous platform-dependent init */
	interrupt_init,
	initr_enable_interrupts,
	initr_ethaddr,
	board_late_init,
	initr_scsi,
	initr_net,
	
	run_main_loop,
}
void board_init_r(gd_t *new_gd, ulong dest_addr)
{

if (initcall_run_list(init_sequence_r))

/* NOTREACHED - run_main_loop() does not return */
	hang();
}

uboot会根据对应的函数指针逐一执行初始化工作。

static inline int initcall_run_list(const init_fnc_t init_sequence[])
{
	const init_fnc_t *init_fnc_ptr;
	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)
		ret = (*init_fnc_ptr)();
}

在调用board_init_r之前,通常MCU会做一些与CPU密切相关的初始化工作,即Uboot第一阶段,通常由汇编语言完成。

64位的调用文件:

crt0_64.S (arch\arm\lib) line 152 : 	b	board_init_r			/* PC relative jump */

	b	board_init_r			/* PC relative jump */

	/* NOTREACHED - board_init_r() does not return */

efi_main in efi_app.c (lib\efi) : 	board_init_r(NULL, 0);



ENTRY(_main)
{


}

arm64为调用位置:

start.S	arch\arm\cpu\armv8	8533	2023/3/16
.globl	_start
_start:
bl	lowlevel_init
bl	_main

efi_status_t EFIAPI efi_main(efi_handle_t image,
			     struct efi_system_table *sys_table)
			{
			board_init_r(NULL, 0);	 
				 
}
//uboot已支持uefi
crt0_aarch64_efi.S	arch\arm\lib	3817	2023/3/16	
_start:

bl		efi_main

二、主要调用函数分析

uboot命令执行时的调用关系

static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
	sandbox_main_loop_init();
#endif
	/* main_loop() can return to retry autoboot, if so just run it again */
	for (;;)
	//当程序没有跳转运行kernel时(即按下空格按键时),系统将循环执行接收用户输入的命令。
		main_loop();
	return 0;
}
void main_loop(void)
{
	const char *s;

	bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

	if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
		env_set("ver", version_string);  /* set version variable */
//初始化命令行接口(CLI)
	cli_init();
//执行预启动环境命令、主循环开始前从环境变量中读取并执行的。
	run_preboot_environment_command();

	if (IS_ENABLED(CONFIG_UPDATE_TFTP))
		update_tftp(0UL, NULL, NULL);
// 处理启动延迟。给用户时间来中断自动启动过程并进入命令行。
	s = bootdelay_process();
	if (cli_process_fdt(&s))
		cli_secure_boot_cmd(s);
//执行自动启动命令。这里s可能包含用户通过启动延迟中断选择的命令,或者是在没有中断时,从环境变量中读取的默认自动启动命令。
	autoboot_command(s);
   //进入CLI循环。这个函数通常会等待用户输入,解析输入,并执行相应的命令。
	cli_loop();
	panic("No CLI available");
}

延时函数的实现:

const char *bootdelay_process(void)
{
// 尝试从环境变量中获取bootdelay,否则使用配置的默认值 
	s = env_get("bootdelay");
	debug("### main_loop entered: bootdelay=%d\n\n", bootdelay);
//延时完成,从环境变量中读取bootcmd命令
	if (bootcount_error())
		s = env_get("altbootcmd");
	else
		s = env_get("bootcmd");
		
	s = env_get("bootcmd");
}
D2000#print
bootcmd=run distro_bootcmd
distro_bootcmd=run load_kernel; run load_initrd; run load_fdt; run boot_os

如果用户在2S中内输入了 回车/空格,则会根据用户输入的命令,来运行

void autoboot_command(const char *s)
{
//打印延时,等待命令
	debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

	if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
		int prev = disable_ctrlc(1);	/* disable Control C checking */
#endif
//解析和执行命令字符串中的命令列表,如果没有按下空格,则会执行默认的bootcmd命令
		run_command_list(s, -1, 0);

#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
		disable_ctrlc(prev);	/* restore Control C checking */
#endif
	}

}

如果按下了空格,输入了命令就会执行用户输入的命令

int run_command_list(const char *cmd, int len, int flag)
{
	//解析命令
	rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON);

	rcode = cli_simple_run_command_list(buff, flag);

}

解析输入的命令,当按下回车时,检查命令。

int cli_simple_run_command_list(char *cmd, int flag)
{
	if (*next == '\n')
	{
	/* run only non-empty commands */
	if (*line) 
		if (cli_simple_run_command(line, 0) < 0) {
		
					rcode = 1;
					break;
				}
	
	}


}

处理命令,查找uboot中是否包含该命令

int cli_simple_run_command(const char *cmd, int flag)
{
	if (cmd_process(flag, argc, argv, &repeatable, NULL))
			/* Look up command in command table */
			cmdtp = find_cmd(argv[0]);
				find_cmd_tbl(cmd, start, len);
				/* If OK so far, then do the command */
				rc = cmd_call(cmdtp, flag, argc, argv, &newrep);
	
}

解析命令参数,并执行相应的命令。

void cli_loop(void)
{
	bootstage_mark(BOOTSTAGE_ID_ENTER_CLI_LOOP);
#ifdef CONFIG_HUSH_PARSER
	parse_file_outer();
	/* This point is never reached */
	for (;;);
#elif defined(CONFIG_CMDLINE)
	cli_simple_loop();
#else
	printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n");
#endif /*CONFIG_HUSH_PARSER*/
}

三、uboot命令的由来

在执行uboot命令时,可以看到uboot中有很多命令,那这些命令是怎么载入和实现的呢,下面逐一说明。
command.h include 11540 2023/3/16 6
在uboot中提供了一个cmd_tbl_s结构体,所有的命令都声明在 cmd_tbl_t 这个结构体对象中。

typedef struct cmd_tbl_s	cmd_tbl_t;

cmd_tbl_t 成员变量如下:

struct cmd_tbl_s {
	//命令的名称
	char		*name;		/* Command Name			*/
	//命令的参数个数
	int		maxargs;	/* maximum number of arguments	*/
					/*
					 * Same as ->cmd() except the command
					 * tells us if it can be repeated.
					 * Replaces the old ->repeatable field
					 * which was not able to make
					 * repeatable property different for
					 * the main command and sub-commands.
					 */
	int		(*cmd_rep)(struct cmd_tbl_s *cmd, int flags, int argc,
				   char * const argv[], int *repeatable);
					/* Implementation function	*/
	//命令对应的函数指针
	int		(*cmd)(struct cmd_tbl_s *, int, int, char * const []);
	//命令的使用方法
	char		*usage;		/* Usage message	(short)	*/
#ifdef	CONFIG_SYS_LONGHELP
    //定义命令的帮组信息  --help
	char		*help;		/* Help  message	(long)	*/
#endif
#ifdef CONFIG_AUTO_COMPLETE
	/* do auto completion on the arguments */
	int		(*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
};

在使用时,定义uboot命令如下:
reset命令,调用的函数为do_reset函数。

boot.c	cmd	1417	2023/3/16	16
U_BOOT_CMD(
	reset, 1, 0,	do_reset,
	"Perform RESET of the CPU",
	""
);

poweroff命令,调用的函数为do_poweroff函数。

#ifdef CONFIG_CMD_POWEROFF
U_BOOT_CMD(
	poweroff, 1, 0,	do_poweroff,
	"Perform POWEROFF of the device",
	""
);

定义使用命令示例:
doc/README.commands
在uboot中如果需要添加命令,首先需要包含command.h文件,然后使用U_BOOT_CMD()宏或者U_BOOT_CMD_COMPLETE宏添加命令到cmd_tbl_t 结构体中。

This is done by first including command.h, then using the U_BOOT_CMD() or the
Commands are added to U-Boot by creating a new command structure.
This is done by first including command.h, then using the U_BOOT_CMD() or the
U_BOOT_CMD_COMPLETE macro to fill in a cmd_tbl_t struct.
U_BOOT_CMD(name, maxargs, repeatable, command, "usage", "help")
U_BOOT_CMD_COMPLETE(name, maxargs, repeatable, command, "usage, "help", comp)

子命令定义,定义子命令可以使用 U_BOOT_CMD_MKENT或 U_BOOT_CMD_MKENTCOMPLETE宏
Sub-command definition

Likewise an array of cmd_tbl_t holding sub-commands can be created using either
of the following macros:

* U_BOOT_CMD_MKENT(name, maxargs, repeatable, command, "usage", "help")
* U_BOOT_CMD_MKENTCOMPLETE(name, maxargs, repeatable, command, "usage, "help",
  comp)
static cmd_tbl_t demo_commands[] = {
	U_BOOT_CMD_MKENT(list, 0, 1, do_demo_list, "", ""),
	U_BOOT_CMD_MKENT(hello, 2, 1, do_demo_hello, "", ""),
	U_BOOT_CMD_MKENT(light, 2, 1, do_demo_light, "", ""),
	U_BOOT_CMD_MKENT(status, 1, 1, do_demo_status, "", ""),
};

实际使用的NVME命令,调用函数为do_nvme函数。

nvme.c	cmd	1334	2023/3/16	21
static int do_nvme(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])

U_BOOT_CMD(
	nvme, 8, 1, do_nvme,
	"NVM Express sub-system",
	"scan - scan NVMe devices\n"
	"nvme detail - show details of current NVMe device\n"
	"nvme info - show all available NVMe devices\n"
	"nvme device [dev] - show or set current NVMe device\n"
	"nvme part [dev] - print partition table of one or all NVMe devices\n"
	"nvme read addr blk# cnt - read `cnt' blocks starting at block\n"
	"     `blk#' to memory address `addr'\n"
	"nvme write addr blk# cnt - write `cnt' blocks starting at block\n"
	"     `blk#' from memory address `addr'"
);

U_BOOT_CMD 命令是如何与 cmd_tbl_t 结构体关联起来的呢?

#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)

	U_BOOT_CMD宏定义来自U_BOOT_CMD_COMPLETE宏定义
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\
	U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)			
 U_BOOT_CMD_COMPLETE宏定义由ll_entry_declare函数和U_BOOT_CMD_MKENT_COMPLETE宏定义函数组成
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
	ll_entry_declare(cmd_tbl_t, _name, cmd) =			\
		U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,	\
						_usage, _help, _comp);		

#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help,	\
			    _comp)				\
	_CMD_REMOVE(sub_ ## _name, _cmd)
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,		\
				_usage, _help, _comp)			\
		{ #_name, _maxargs,					\
		 _rep ? cmd_always_repeatable : cmd_never_repeatable,	\
		 _cmd, _usage, _CMD_HELP(_help) _CMD_COMPLETE(_comp) }

#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, _usage,	\
				  _help, _comp)				\
		{ #_name, _maxargs, NULL, 0 ? _cmd : NULL, _usage,	\
			_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
	ll_entry_declare(cmd_tbl_t, _name, cmd) =			\
		U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,	\
						_usage, _help, _comp);


ll_entry_declare函数也是一个宏定义,使用该宏定义的函数会被存放在u_boot_list段(attribute)。

#define ll_entry_declare(_type, _name, _list)				\
	_type _u_boot_list_2_##_list##_2_##_name __aligned(4)		\
			__attribute__((unused,				\
			section(".u_boot_list_2_"#_list"_2_"#_name)))

uboot.lds

 .u_boot_list : {
  KEEP(*(SORT(.u_boot_list*)));
 }

这个宏 ll_entry_declare 是用于在链接时生成的数组中声明一个条目的工具。这种机制在嵌入式系统、特别是像U-Boot这样的引导加载程序中非常有用,因为它允许开发者在编译时注册一系列的条目(如命令、设备驱动程序等),然后在运行时通过遍历这个链接时生成的数组来访问它们。
这个宏的详细解释和用法。
_type:条目的数据类型。
_name:条目的名称,用于在生成的数组中唯一标识该条目。
_list:条目所属列表的名称,这个名称将用于构造最终的变量名和段名。
宏的作用是声明一个全局变量,这个变量被放置在特定的段(section)中,段名由宏参数动态生成。__aligned(4) 确保变量按4字节对齐,这通常是出于硬件访问效率或特定要求的考虑。attribute((unused)) 告诉编译器这个变量可能不会被直接使用,但请保留它,因为它将在其他地方(如链接时)被引用。section 属性指定了变量应该被放置在哪个段中,这个段名是通过宏参数动态构造的,确保了不同列表和名称的条目可以分别存储在不同的段中。

假设我们有一个结构体 struct my_sub_cmd,并希望声明一个名为 my_sub_cmd 的条目,该条目属于名为 cmd_sub 的列表。

struct my_sub_cmd {  
    int x;  
    int y;  
};  
  
ll_entry_declare(struct my_sub_cmd, my_sub_cmd, cmd_sub) = {  
    .x = 3,  
    .y = 4,  
};

这行代码会声明一个全局变量 _u_boot_list_2_cmd_sub_2_my_sub_cmd,
并将其放置在 .u_boot_list_2_cmd_sub_2_my_sub_cmd 段中。这个变量是 struct my_sub_cmd 类型的,并且被初始化为 { .x = 3, .y = 4 }。在U-Boot的初始化或运行时,可以编写代码来遍历 .u_boot_list_2_cmd_sub_2_my_sub_cmd 段中的所有条目,并执行相应的操作。

uboot启动时,是如何读取u_boot_list段中的参数的呢?
static cmd_tbl_t cmd_se_sub[] =
uboot启动时,会通过init函数加载所有的uboot命令

static int initr_manual_reloc_cmdtable(void)
{
	fixup_cmdtable(ll_entry_start(cmd_tbl_t, cmd),
		       ll_entry_count(cmd_tbl_t, cmd));
	return 0;
}

#if defined(CONFIG_NEEDS_MANUAL_RELOC)
void env_reloc(void)
{
	fixup_cmdtable(cmd_env_sub, ARRAY_SIZE(cmd_env_sub));
}
#endif
void fixup_cmdtable(cmd_tbl_t *cmdtp, int size)
{
	int	i;

	if (gd->reloc_off == 0)
		return;

	for (i = 0; i < size; i++) {
		ulong addr;

		addr = (ulong)(cmdtp->cmd) + gd->reloc_off;

		cmdtp->cmd =
			(int (*)(struct cmd_tbl_s *, int, int, char * const []))addr;
		addr = (ulong)(cmdtp->name) + gd->reloc_off;
		cmdtp->name = (char *)addr;
		if (cmdtp->usage) {
			addr = (ulong)(cmdtp->usage) + gd->reloc_off;
			cmdtp->usage = (char *)addr;
		}
#ifdef	CONFIG_SYS_LONGHELP
		if (cmdtp->help) {
			addr = (ulong)(cmdtp->help) + gd->reloc_off;
			cmdtp->help = (char *)addr;
		}
#endif
#ifdef CONFIG_AUTO_COMPLETE
		if (cmdtp->complete) {
			addr = (ulong)(cmdtp->complete) + gd->reloc_off;
			cmdtp->complete =
				(int (*)(int, char * const [], char, int, char * []))addr;
		}
#endif
		cmdtp++;
	}
}

se.c cmd 6734

static __maybe_unused void se_reloc(void)
{
	fixup_cmdtable(cmd_se_sub, ARRAY_SIZE(cmd_se_sub));
}
static int do_se(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{	
	c = find_cmd_tbl(argv[0], &cmd_se_sub[0], ARRAY_SIZE(cmd_se_sub));
}

uboot引导过程总结:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hdh717

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值