在uboot启动第二阶段的最后,程序进入了一个死循环,实际是在等待超时和等待用户命令的输入,然后根据不同的命令去执行uboot的不同功能,实际uboot就是一个单片机程序,只有一个进程在运行。uboot引导kernel的启动,首先是从环境变量bootcmd中获取启动命令,然后通过执行bootcmd里面的命令来实现kernel的启动的。
uboot第二阶段启动可以查看博客《uboot启动第二阶段start_armboot函数分析》,uboot其它内容可以查看博客《序言和目录》
(一)命令获取
main_loop 函数解析,这里函数非常的长,将hi3521a默认没有定义的功能代码删除,得到下面的代码:
void main_loop (void)
{
#ifndef CONFIG_SYS_HUSH_PARSER
static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, }; //①
int len;
int flag;
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
char *s;
int bootdelay;
#endif
#ifdef CONFIG_VERSION_VARIABLE //定义CONFIG_VERSION_VARIABLE=1
{
extern char version_string[];
setenv ("ver", version_string); /* set version variable */ ②
}
#endif /* CONFIG_VERSION_VARIABLE */
#ifdef CONFIG_AUTO_COMPLETE
install_auto_complete(); //③
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
#ifdef CONFIG_HI3536_A7
s = getenv("slave_cmd");
#else
s = getenv("bootcmd");
#endif
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) { //④
run_command (s, 0); //⑤
}
#endif /* CONFIG_BOOTDELAY */
/*
* Main Loop for Monitor Command Processing
*/
#ifdef CONFIG_SYS_HUSH_PARSER
parse_file_outer();
/* This point is never reached */
for (;;);
#else
for (;;) { //⑥
#ifdef CONFIG_PRODUCT_HDMI_PHY
{
extern void update_hdmi_status(void);
update_hdmi_status();
}
#endif
len = readline (CONFIG_SYS_PROMPT);
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer);
else if (len == 0)
flag |= CMD_FLAG_REPEAT;
if (len == -1)
puts ("<INTERRUPT>\n");
else
#ifdef CONFIG_BOOT_RETRY_TIME
rc = run_command(lastcommand, flag);
#else
run_command(lastcommand, flag);
#endif
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
#endif /*CONFIG_SYS_HUSH_PARSER*/
}
①定义一个静态局部变量,用来记录我们的历史操作命令
②添加一个环境变量ver,用来记录uboot的版本信息
③添加自动补全的功能
④从环境变量中读取bootdelay的值和bootcmd的值,如果它们不为空,那么进入abortboot (bootdelay)函数执行,我们看abortboot的函数体实现
static __inline__ int abortboot(int bootdelay)
{
int abort = 0;
#ifdef CONFIG_MENUPROMPT
printf(CONFIG_MENUPROMPT);
#else
printf("Hit any key to stop autoboot: %2d ", bootdelay);
#endif
#if defined CONFIG_ZERO_BOOTDELAY_CHECK
/*
* Check if key already pressed
* Don't check if bootdelay < 0
*/
if (bootdelay >= 0) {
if (tstc()) { /* we got a key press */
(void) getc(); /* consume input */
puts ("\b\b\b 0");
abort = 1; /* don't auto boot */
}
}
#endif
while ((bootdelay > 0) && (!abort)) {
int i;
--bootdelay;
/* delay 100 * 10ms */
for (i=0; !abort && i<100; ++i) {
if (tstc()) { /* we got a key press */ //(1)
abort = 1; /* don't auto boot */
bootdelay = 0; /* no more delay */
# ifdef CONFIG_MENUKEY
menukey = getc();
# else
(void) getc(); /* consume input */
# endif
break;
}
udelay(10000);
}
printf("\b\b\b%2d ", bootdelay);
}
putc('\n');
#ifdef CONFIG_SILENT_CONSOLE
if (abort)
gd->flags &= ~GD_FLG_SILENT;
#endif
return abort;
}
- 从标准输入中获取1个字符,
- 如果有获取到,直接退出,
- 如果没有获取到,则进入下面的while循环继续获取字符,直到获取到字符或是有延时已经到了,退出循环
- ⑤执行bootcmd命令
- 如果没有检测到字符输入,则执行bootcmd命令,让uboot去引导kernel启动,这个详细见后面介绍。
- ⑥循环获取命令
- 循环获取命令,以回车为结束符,获取到命令调用run_command去执行命令。
(二)bootcmd命令解析
2.1U_BOOT_CMD命令格式:
U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")
各个参数的意义如下:
- name:命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串
- maxargs:命令的最大参数个数
- repeatable:是否自动重复(按Enter键是否会重复执行)
- command:该命令对应的响应函数指针
- usage:简短的使用说明(字符串)
- help:较详细的使用说明(字符串)
U_BOOT_CMD宏在include/command.h中定义:
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
- “##”与“#”都是预编译操作符,“##”有字符串连接的功能,“#”表示后面紧接着的是一个字符串
其中Struct_Section在include/command.h中定义如下:
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
- 凡是带有attribute ((unused,section (“.u_boot_cmd”))属性声明的变量都将被存放在”.u_boot_cmd”段中,并且即使该变量没有在代码中显式的使用编译器也不产生警告信息。
.u_boot_cmd
在u-Boot连接脚本 u-boot.lds中定义了.u_boot_cmd段:
. = .;
__u_boot_cmd_start = .; /*将 __u_boot_cmd_start指定为当前地址 */
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .; /* 将__u_boot_cmd_end指定为当前地址 */
- 这表明带有“.u_boot_cmd”声明的函数或变量将存储在“u_boot_cmd”段。
- 这样只要将u-boot所有命令对应的cmd_tbl_t变量加上“.u_boot_cmd”声明,编译器就会自动将其放在“u_boot_cmd”段,查找cmd_tbl_t变量时只要在 __u_boot_cmd_start 与 __u_boot_cmd_end 之间查找就可以了。
cmd_tbl_t
cmd_tbl_t在include/command.h中定义如下:
struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
int repeatable; /* autorepeat allowed? */
/* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
char *usage; /* Usage message (short) */
#ifdef CONFIG_SYS_LONGHELP
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
};
typedef struct cmd_tbl_s cmd_tbl_t;
一个cmd_tbl_t结构体变量包含了调用一条命令的所需要的信息。
- 对于环境变量bootcmd,执行run_command(bootcmd, flag)之后,最终是将bootcmd中的参数解析为命令,海思hi3521a中默认参数是bootcmd=bootm 0x82000000
- 相当于执行bootm 0x82000000 命令
- 最终将调用do_bootm函数,do_bootm函数在cmd_bootm.c中实现
(三)bootm命令解析
/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong iflag;
ulong load_end = 0;
int ret;
boot_os_fn *boot_fn;
/* determine if we have a sub command */
if (argc > 1) { //(1)
char *endp;
simple_strtoul(argv[1], &endp, 16);
/* endp pointing to NULL means that argv[1] 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);
}
if (bootm_start(cmdtp, flag, argc, argv)) //(2)
return 1;
/*
* We have reached the point of no return: we are going to
* overwrite all exception vector code, so we cannot easily
* recover from any failures any more...
*/
iflag = disable_interrupts(); //(3)
usb_stop(); //(4)
ret = bootm_load_os(images.os, &load_end, 1); //(5)
if (ret < 0) { //(6)
if (ret == BOOTM_ERR_RESET)
do_reset (cmdtp, flag, argc, argv);
if (ret == BOOTM_ERR_OVERLAP) {
if (images.legacy_hdr_valid) {
if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI)
puts ("WARNING: legacy format multi component "
"image overwritten\n");
} else {
puts ("ERROR: new format image overwritten - "
"must RESET the board to recover\n");
show_boot_progress (-113);
do_reset (cmdtp, flag, argc, argv);
}
}
if (ret == BOOTM_ERR_UNIMPLEMENTED) {
if (iflag)
enable_interrupts();
show_boot_progress (-7);
return 1;
}
}
lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load)); //(7)
if (images.os.type == IH_TYPE_STANDALONE) {
if (iflag)
enable_interrupts();
/* This may return when 'autostart' is 'no' */
bootm_start_standalone(iflag, argc, argv);
return 0;
}
show_boot_progress (8);
boot_fn = boot_os[images.os.os]; //(8)
if (boot_fn == NULL) {
if (iflag)
enable_interrupts();
printf ("ERROR: booting os '%s' (%d) is not supported\n",
genimg_get_os_name(images.os.os), images.os.os);
show_boot_progress (-8);
return 1;
}
arch_preboot_os();
boot_fn(0, argc, argv, &images); //(9)
show_boot_progress (-9);
do_reset (cmdtp, flag, argc, argv); //(10)
return 1;
}
- (1)默认启动命令为:bootm 0x82000000;argc=1;argv[0]=bootm;argv[1]=0x82000000
- 所以这个判断语句不会进去
- (2) bootm_start 在这里主要是获取内核镜像的头文件和内核镜像的一些参数
- (3)关闭中断
- (4)关闭usb设备
- (5)这里主要是将uImage文件的头结构去掉,后面的内核数据往前面移动image_header_t结构体长度
- (6)上面一步出错的异常处理
- (7)lmb_reserve 这个函数在hi3521a中没有实现,实际是一个空函数
- (8)获取linux启动启动内核的函数地址
- (9)调用启动内核的函数,在海思设备上,实际上是调用函数 do_bootm_linux, 由do_bootm_linux 实现uboot参数传递到kernel并且跳转到kernel的开始位置去执行。
- (10)正常情况在上一步就已经将控制权移交到kernel去运行了,不会运行到这里,如果运行到这里表示出现错误了,执行系统复位操作,实际执行的是\arch\arm\lib\reset.c下的复位函数。
3.1 bootm_start函数解析
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
void *os_hdr;
int ret;
memset ((void *)&images, 0, sizeof (images));
images.verify = getenv_yesno ("verify"); //(1)
bootm_start_lmb();
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len); //(2)
if (images.os.image_len == 0) {
puts ("ERROR: can't get kernel image!\n");
return 1;
}
/* get image parameters */
switch (genimg_get_format (os_hdr)) {
case IMAGE_FORMAT_LEGACY: //(3)
images.os.type = image_get_type (os_hdr);
images.os.comp = image_get_comp (os_hdr);
images.os.os = image_get_os (os_hdr);
images.os.end = image_get_image_end (os_hdr);
images.os.load = image_get_load (os_hdr);
break;
default:
puts ("ERROR: unknown image format type!\n");
return 1;
}
/* find kernel entry point */
if (images.legacy_hdr_valid) {
images.ep = image_get_ep (&images.legacy_hdr_os_copy);
} else {
puts ("Could not find kernel entry point!\n");
return 1;
}
if (((images.os.type == IH_TYPE_KERNEL) ||
(images.os.type == IH_TYPE_MULTI)) &&
(images.os.os == IH_OS_LINUX)) {
/* find ramdisk */
ret = boot_get_ramdisk (argc, argv, &images, IH_INITRD_ARCH,
&images.rd_start, &images.rd_end); //(4)
if (ret) {
puts ("Ramdisk image is corrupt or invalid\n");
return 1;
}
}
images.os.start = (ulong)os_hdr;
images.state = BOOTM_STATE_START; //(5)
return 0;
}
- (1)从环境变量中获取verify的值
- (2)boot_get_kernel 在这里面主要是解析内核进项头文件的一些参数,并且校验kernel镜像文件是否有效
- (3)镜像文件参数赋值,uImage镜像文件是IMAGE_FORMAT_LEGACY 这个类型(旧类型),下面有一个CONFIG_FIT宏定义被删除了,这个是设备树类型传参数才使用的,在海思设备上没有配置。
- (4)解析ramdisk镜像,用来判断是否有虚拟磁盘,在海思设备上没有这个。
- (5)启动状态赋值
- 在这里需要注意一个,在uboot中镜像参数的获取,使用了宏定义来实现,这个宏定义使用了连接符号,所以比较难看出来。
- 宏定义及数据结构定义如下:
/*******************************************************************/
/* Legacy format specific code (prefixed with image_) */
/*******************************************************************/
static inline uint32_t image_get_header_size (void)
{
return (sizeof (image_header_t));
}
#define image_get_hdr_l(f) \
static inline uint32_t image_get_##f(const image_header_t *hdr) \
{ \
return uimage_to_cpu (hdr->ih_##f); \
}
image_get_hdr_l (magic); /* image_get_magic */
image_get_hdr_l (hcrc); /* image_get_hcrc */
image_get_hdr_l (time); /* image_get_time */
image_get_hdr_l (size); /* image_get_size */
image_get_hdr_l (load); /* image_get_load */
image_get_hdr_l (ep); /* image_get_ep */
image_get_hdr_l (dcrc); /* image_get_dcrc */
#define image_get_hdr_b(f) \
static inline uint8_t image_get_##f(const image_header_t *hdr) \
{ \
return hdr->ih_##f; \
}
image_get_hdr_b (os); /* image_get_os */
image_get_hdr_b (arch); /* image_get_arch */
image_get_hdr_b (type); /* image_get_type */
image_get_hdr_b (comp); /* image_get_comp */
typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
} image_header_t;
有一个uImage文件,它的数据头如下:
解析出来就是:
- ih_magic = 0x27051956
- ih_hcrc = 0x93CE3402
- ih_time = 0x5DC23726
- ih_size = 0x38EAB8
- ih_load = 0x80008000
- ih_ep = 0x80008000
- ih_dcrc = 0xD3C0AAAA
- ih_os = 0x05 /**IH_OS_LINUX**/
- ih_arch = 0x02 /**IH_ARCH_ARM**/
- ih_type = 0x02 /**IH_TYPE_KERNEL**/
- ih_comp = 0x00 /**IH_COMP_NONE**/
- ih_name = Linux-3.18.20
3.2do_bootm_linux函数解析
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
bd_t *bd = gd->bd;
char *s;
int machid = bd->bi_arch_number;
void (*theKernel)(int zero, int arch, uint params);
#ifdef CONFIG_CMDLINE_TAG
#ifdef CONFIG_HI3536_A7
char *commandline = getenv("slave_bootargs");
#else
char *commandline = getenv("bootargs"); //(1)
#endif
#endif
if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))
return 1;
theKernel = (void (*)(int, int, uint))images->ep; //(2)
s = getenv ("machid"); //(3)
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}
show_boot_progress (15);
debug ("## Transferring control to Linux (at address %08lx) ...\n",
(ulong) theKernel);
setup_start_tag (bd); //(4)
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline); //(5)
if (images->rd_start && images->rd_end)
setup_initrd_tag (bd, images->rd_start, images->rd_end);
setup_eth_use_mdio_tag(bd, getenv("use_mdio"));
setup_eth_mdiointf_tag(bd, getenv("mdio_intf"));
setup_ethaddr_tag(bd, getenv("ethaddr"));
setup_end_tag (bd); //(6)
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif
cleanup_before_linux (); //(7)
theKernel (0, machid, bd->bi_boot_params); //(8)
/* does not return */
return 1;
}
- (1)获取环境变量bootargs中的值,该环境变量用来传递参数给kernel
- (2)images->ep的地址是kernel的程序的入口地址,也就是将函数指针theKernel指向kernel最先执行的地方。
- (3)获取环境变量machid,这个应该是机器码,海思设备没有定义在环境变量中
- (4)这里是建立一个链表用来存放传递给内核的参数,在board_init函数中有赋值 gd->bd->bi_boot_params = CFG_BOOT_PARAMS;
- CFG_BOOT_PARAMS = 0x80000000 + 0x0100 = 0x80000100
- (5)将commandline的值添加到链表中
- (6)结束参数的填充
- (7)启动linux内核前的一个清除操作,主要是关闭中断,关闭缓存等操作
- (8)由前面我们知道theKernel实际指向的是kernel的入口地址,执行这一句之后,uboot就结束了运行,kernel正式运行就从这里开始。
到这里就完成了uboot自己的启动和引导内核启动的整个过程,其它内容可以参考博客《序言和目录》
goodbye,有缘再见 ~
参考内容:
- https://www.cnblogs.com/cslunatic/p/3891752.html
- https://blog.csdn.net/qq_33894122/article/details/86129765