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引导过程总结: