0%

Linux驱动学习日记(8) Uboot学习(三)

Bootz总体启动流程

  当bootz命令输入后,U-Boot会执行以下操作:

图 1 bootz流程

启动流程详解

images全局变量

  这个全局变量是一个结构体,主要容纳了OS(Linux内核)信息、设备树、启动状态机等信息,它是 Linux 启动全过程中,所有镜像、地址、长度、入口点、状态的唯一权威记录。

do_bootz函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int ret;

/* Consume 'bootz' */
argc--; argv++;

if (bootz_start(cmdtp, flag, argc, argv, &images))
return 1;

/*
* We are doing the BOOTM_STATE_LOADOS state ourselves, so must
* disable interrupts ourselves
*/
bootm_disable_interrupts();

images.os.os = IH_OS_LINUX;
ret = do_bootm_states(cmdtp, flag, argc, argv,
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO,
&images, 1);

return ret;
}

  这个函数首先调用 bootz_start() 完成启动前的准备工作,如果准备失败则直接返回。然后关闭所有中断。设置 images.os.os 为 IH_OS_LINUX,也就是设置系统镜像为 Linux,表示我们要启动的是 Linux 系统!后面会用到 images.os.os 来挑选具体的启动函数。最后调用函数 do_bootm_states 来执行不同的 BOOT 阶段,这里要执行的 BOOT 阶段有: BOOTM_STATE_OS_PREP 、 BOOTM_STATE_OS_FAKE_GO 和 BOOTM_STATE_OS_GO。

bootz_start 函数

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
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[], bootm_headers_t *images)
{
int ret;
ulong zi_start, zi_end;

ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
images, 1);

/* Setup Linux kernel zImage entry point */
if (!argc) {
images->ep = load_addr;
debug("* kernel: default image load address = 0x%08lx\n",
load_addr);
} else {
images->ep = simple_strtoul(argv[0], NULL, 16);
debug("* kernel: cmdline image address = 0x%08lx\n",
images->ep);
}

ret = bootz_setup(images->ep, &zi_start, &zi_end);
if (ret != 0)
return 1;

lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);

/*
* Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
* have a header that provide this informaiton.
*/
if (bootm_find_images(flag, argc, argv))
return 1;
return 0;
}

  bootz_setup() 会算出 zImage 相关的“占用区间”(用于 LMB 预留),也就是 U-Boot 接下来绝对不能踩的内存范围。第一段,进入BOOTM_STATE_START,初始化启动状态机+LMB。第二段,确定zimage在内存中的地址并写入images->ep。第三段,调用 bootz_setup 函数,此函数会判断当前的系统镜像文件是否为 Linux 的镜像文件,并且会打印出镜像相关信息。第五段会调用bootm_find_images函数,这个函数主要是查找设备树(dtb)文件,找到以后就将设备树的起始地址和长度分别写到 images 的 ft_addr 和 ft_len 成员变量中。我们使用 bootz 启动 Linux 的时候已经指明了设备树在 DRAM 中的存储地址,因此 images.ft_addr=0X83000000,长度根据具体的设备树文件而定,比如我现在使用的设备树文件长度为 0X8C81,因此 images.ft_len=0X8C81。

do_bootm_states 函数

  函数 do_bootm_states 根据不同的 BOOT 状态执行不同的代码段,通过如下代码来判断 BOOT 状态:

states & BOOTM_STATE_XXX

  在 do_bootz 函数中会用到 BOOTM_STATE_OS_PREP 、 BOOTM_STATE_OS_FAKE_GO和 BOOTM_STATE_OS_GO 这三个 BOOT 状态, bootz_start 函数中会用到 BOOTM_STATE_START 这个 BOOT 状态。本质上这个函数只是个状态机。

  在BOOTM_STATE_START状态下,会初始化启动状态机、初始化bootstage并作通用合法性检查。BOOTM_STATE_OS_PREP状态下,会关闭中断、清理cache、关闭MMU、确保CPU模式正常、最终确认 FDT / initrd / bootargs 已就位。BOOTM_STATE_OS_FAKE_GO可以理解为彩排,调试 / 追踪启动流程,而不真的把控制权交给 OS。BOOTM_STATE_OS_GO才是真正的跳转,一般会准备寄存器,确认CPU状态并且函数指针跳转。

结语

  通过对 bootz 启动 Linux 内核流程的分析,可以看出 U-Boot 并不是简单地“跳转到内核”,而是通过一套完整而严谨的启动状态机,将内核镜像、设备树、内存布局以及 CPU 状态逐步整理到 Linux 所期望的初始运行环境中。