uboot-bootm和bootz启动内核

1 images 全局变量#

不管是bootz还是bootm命令,启动kernel都会用到images全局变量。images 定义在文件 cmd/bootm.c
image
include/image.h 中的定义了bootm_headers_t结构:该结构描述的是bootm启动时的头部信息。该结构又包含了系统镜像头部和系统镜像。

1.1 bootm头部结构#

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
304 typedef struct bootm_headers {
305 /*
306 * Legacy os image header, if it is a multi component image
307 * then boot_get_ramdisk() and get_fdt() will attempt to get
308 * data from second and third component accordingly.
309 */
310 image_header_t *legacy_hdr_os; /* image header pointer */
311 image_header_t legacy_hdr_os_copy; /* header copy */
312 ulong legacy_hdr_valid;
313
......
333
334 #ifndef USE_HOSTCC
335 image_info_t os; /* OS 镜像信息 */
336 ulong ep; /* OS 入口点 */
337
338 ulong rd_start, rd_end; /* ramdisk 开始和结束位置 */
339
340 char *ft_addr; /* 设备树地址 */
341 ulong ft_len; /* 设备树长度 */
342
343 ulong initrd_start; /* initrd 开始位置 */
344 ulong initrd_end; /* initrd 结束位置 */
345 ulong cmdline_start; /* cmdline 开始位置 */
346 ulong cmdline_end; /* cmdline 结束位置 */
347 bd_t *kbd;
348 #endif
349
350 int verify; /* getenv("verify")[0] != 'n' */
351
352 #define BOOTM_STATE_START (0x00000001)
353 #define BOOTM_STATE_FINDOS (0x00000002)
354 #define BOOTM_STATE_FINDOTHER (0x00000004)
355 #define BOOTM_STATE_LOADOS (0x00000008)
356 #define BOOTM_STATE_RAMDISK (0x00000010)
357 #define BOOTM_STATE_FDT (0x00000020)
358 #define BOOTM_STATE_OS_CMDLINE (0x00000040)
359 #define BOOTM_STATE_OS_BD_T (0x00000080)
360 #define BOOTM_STATE_OS_PREP (0x00000100)
361 #define BOOTM_STATE_OS_FAKE_GO (0x00000200)/*'Almost' run the OS*/
362 #define BOOTM_STATE_OS_GO (0x00000400)
363 int state;
364
365 #ifdef CONFIG_LMB
366 struct lmb lmb; /* 内存管理相关,不深入研究 */
367 #endif
368 } bootm_headers_t;

第 352~362 行这 11 个宏定义表示 BOOT 的不同阶段。

1.1.1 系统镜像头部结构#

先来看下image_header_t结构,也就是系统镜像头部信息:
image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct image_header {
__be32 ih_magic; /* Image Header Magic Number */
__be32 ih_hcrc; /* Image Header CRC Checksum */
__be32 ih_time; /* Image Creation Timestamp */
__be32 ih_size; /* Image Data Size */
__be32 ih_load; /* Data Load Address */
__be32 ih_ep; /* Entry Point Address */
__be32 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;

1.1.2 系统镜像结构#

再来看下image_info_t结构,也就是系统镜像信息结构:
image

1
2
3
4
5
6
7
typedef struct image_info {
ulong start, end; /* start/end of blob */
ulong image_start, image_len; /* start of image within blob, len of image */
ulong load; /* load addr for the image */
uint8_t comp, type, os; /* compression, type of image, os type */
uint8_t arch; /* CPU architecture */
} image_info_t;

2 do_bootz 函数#

do_bootz 函数定义在cmd/bootm.c
image
先执行bootz_start。先执行BOOTM_STATE_START 阶段。
第 638 行,设置images.os.os IH_OS_LINUX,也就是设置系统镜像为 Linux,表示我们要启动的是 Linux 系统!后面会用到 images.os.os 来挑选具体的启动函数。
第 639 行,调用函数do_bootm_states来执行不同的 BOOT 阶段,这里要执行的 BOOT 阶段有:BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO 和BOOTM_STATE_OS_GO

2.1 bootz_start#

image

  1. 调用函数 do_bootm_states,执行 BOOTM_STATE_START 阶段。

  2. 593 行,设置 images 的 ep,也就是系统镜像的入口点,使用bootz命令启动系统的时候就会设置系统在 DRAM 中的存储位置,这个存储位置就是系统镜像的入口点,因此 images->ep=0X80800000。镜像加载地址定义在include/configs/mx6ullevk.h
    image

  3. 调用bootz_setup函数,此函数会判断当前的系统镜像文件是否为 Linux 的镜像文件,并且会打印出镜像相关信息,bootz_setup 函数稍后会讲解。

  4. 调用bootm_find_images查找ramdisk设备树(dtb)文件,但是我们没有用到 ramdisk,因此此函数在这里仅仅用于查找设备树(dtb)文件,此函数稍后也会讲解。

2.1.1 bootm_start#

执行BOOTM_STATE_START阶段时,执行bootm_start
image
初始化verfify 成员, 设置images状态为 BOOTM_STATE_START

2.1.2 bootz_setup#

定义在文件 arch/arm/lib/bootm.c:
image

  1. LINUX_ARM_ZIMAGE_MAGIC就是 ARM Linux 系统魔术数
  2. 从传递进来的参数 image(也就是系统镜像首地址)中获取zimage头。
  3. 判断image是否为 ARM 的 Linux 系统镜像,如果不是的话就直接返回,并且打印出“Bad Linux ARM zImage magic!”,比如我们输入一个错误的启动命令:
    bootz 80000000 – 900000000
    因为我们并没有在0X80000000处存放 Linux 镜像文件(zImage),因此上面的命令肯定会执行出错如下:
    image
  4. 初始化函数bootz_setup的参数 start 和 end
  5. 打印启动信息,如果 Linux 系统镜像正常的话打印如下:
    image

2.1.3 bootm_find_images#

定义在文件 common/bootm.c
image

  1. 查找 ramdisk,但是我们没有用到 ramdisk,因此这部分代码不用管。
  2. 查找设备树(dtb)文件,找到以后就将设备树的起始地址和长度分别写到images ft_addrft_len 成员变量中。我们使用 bootz 启动 Linux 的时候已经指明了设备树在DRAM 中的存储地址,因此 images.ft_addr=0X83000000,长度根据具体的设备树文件而定,比如我现在使用的设备树文件长度为 0X8C81,因此 images.ft_len=0X8C81

2.2 do_bootm_states#

image
前面将state先处理了BOOTM_STATE_START阶段,接下来处里下面三个状态:

1
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO

2.2.1 bootm_os_get_boot_func#

进入第658行,通过bootm_os_get_boot_func来查找系统启动函数。由于前面提到images->os.os 就是系统类型设置 为 IH_OS_LINUX,根据这个os类型来选择对应的启动函数名为do_bootm_linux:
image

2.2.2 boot_pre_linux#

第 676-677 行,处理BOOTM_STATE_OS_PREP状态,调用函数 do_bootm_linux,do_bootm_linux调用boot_prep_linux来完成具体的处理过程。boot_prep_linux 主要用于处理环境变量bootargs,bootargs 保存着传递给Linux kernel的参数:
image

设备树的chosen节点下存放了子节点bootargs,bootargs子节点存放bootargs环境变量

2.2.3 boot_jump_linux#

第699行,调用函数boot_selected_os启动 Linux 内核,此函数第 4 个参数为 Linux 系统镜像头,第 5 个参数就是 Linux 系统启动函数 do_bootm_linuxboot_selected_os 函数定义在文件common/bootm_os.c如下:
image
最终调用 boot_selected_os->boot_fn(即do_bootm_linux)->boot_jump_linux来启动 Linux 内核:
image
boot_jump_linux
image

  1. 我们的板子IMX6ULL是armv7 32位架构,因此从else开始,第293 行,变量 machid 保存机器 ID,如果不使用设备树的话这个机器 ID 会被传递给 Linux内核,Linux 内核会在自己的机器 ID 列表里面查找是否存在与uboot传递进来的machid匹配的项目,如果存在就说明 Linux 内核支持这个机器,那么 Linux 就会启动!如果使用设备树的话这个machid就无效了,设备树存有一个“兼容性”这个属性,Linux 内核会比较“兼容性”属性的值(字符串)来查看是否支持这个机器。

  2. 第 295 行,函数 kernel_entry,看名字“内核_进入”,说明此函数是进入 Linux 内核的,也就是最终的大boss!此函数有三个参数:zero,arch,params,第一个参数zero同样为 0;第二个参数为机器 ID;第三个参数 ATAGS 或者设备树(DTB)首地址ATAGS 是传统的方法,用于传递一些命令行信息啥的,如果使用设备树的话就要传递设备树(DTB)

  3. 第 299 行,获取 kernel_entry 函数,函数kernel_entry并不是 uboot 定义的,而是 Linux 内核定义的,Linux 内核镜像文件的第一行代码就是函数 kernel_entry,而 images->ep 保存着 Linux内核镜像的起始地址,起始地址保存的正是 Linux 内核第一行代码!

  4. 第 313 行,调用函数announce_and_cleanup来打印一些信息并做一些清理工作:
    image
    因此每次启动 Linux 之前输出“Starting kernel ...”信息如下:
    image

  5. 继续回到函数 boot_jump_linux,第 315~318 行是设置寄存器 r2 的值。为什么要设置r2的值呢?Linux 内核一开始是汇编代码,因此函数kernel_entry就是个汇编函数。向汇编函数传递参数要使用 r0、r1 和 r2(参数数量不超过 3 个的时候),所以 r2 寄存器就是函数kernel_entry的第三个参数。

  6. 第 316 行,如果使用设备树的话,r2 应该是设备树的起始地址,而设备树地址保存在 imagesftd_addr成员变量中。

  7. 第 317 行,如果不使用设备树的话,r2 应该是uboot传递给 Linux 的参数起始地址,也就是环境变量bootargs的值,

  8. 最后调用调用kernel_entry函数进入 Linux 内核,至此Uboot的整个运行流程结束,uboot 的使命也就完成了。

3 总结bootz启动过程#

image

4 uboot 启动 Linux 测试#

4.1 EMMC 启动 Linux#

编译出来的 Linux 镜像文件 zImage 和设备树文件保存在 EMMC中,uboot 从 EMMC 中读取这两个文件并启动,这个是我们产品最终的启动方式。但是我们目前还没有讲解如何移植linux 和设备树文件,以及如何将 zImage 和设备树文件保存到 EMMC中。不过大家拿到手的 I.MX6U-ALPHA 开发板(EMMC 版本)已经将 zImage 文件和设备树文件烧写到了 EMMC 中,所以我们可以直接读取来测试。先检查一下 EMMC 的分区 1 中有没有zImage 文件和设备树文件,输入命令ls mmc 1:1:
image
EMMC 分区 1 中存在zimageimx6ull-alientek-emmc.dtb这两个文件,所以我们可以测试新移植的 uboot能不能启动 linux 内核。设置 bootargs bootcmd这两个环境变量:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;'
saveenv
直接输入 boot,或者run bootcmd即可启动 Linux 内核,如果 Linux 内核启动成功的话就会输出如下:
image

4.2 网络启动 Linux#

调试过程中由于我们不断更改kernel, 那么如果每次都烧录进emmc,从emmc启动就很繁琐,直接从网络启动。
先将zImagedtb文件放在tftp共享目录下,通过nfs 或者 tftp从 Ubuntu 中下载zImage和设备树文件:

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'
saveenv
image
image

5 附录:比特与十六进制转换快查表#

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
/*
* include/linux/sizes.h
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __LINUX_SIZES_H__
#define __LINUX_SIZES_H__
//
#define SZ_1 0x00000001
#define SZ_2 0x00000002
#define SZ_4 0x00000004
#define SZ_8 0x00000008
#define SZ_16 0x00000010
#define SZ_32 0x00000020
#define SZ_64 0x00000040
#define SZ_128 0x00000080
#define SZ_256 0x00000100
#define SZ_512 0x00000200
//
#define SZ_1K 0x00000400
#define SZ_2K 0x00000800
#define SZ_4K 0x00001000
#define SZ_8K 0x00002000
#define SZ_16K 0x00004000
#define SZ_32K 0x00008000
#define SZ_64K 0x00010000
#define SZ_128K 0x00020000
#define SZ_256K 0x00040000
#define SZ_512K 0x00080000
//
#define SZ_1M 0x00100000
#define SZ_2M 0x00200000
#define SZ_4M 0x00400000
#define SZ_8M 0x00800000
#define SZ_16M 0x01000000
#define SZ_32M 0x02000000
#define SZ_64M 0x04000000
#define SZ_128M 0x08000000
#define SZ_256M 0x10000000
#define SZ_512M 0x20000000
//
#define SZ_1G 0x40000000
#define SZ_2G 0x80000000
#endif /* __LINUX_SIZES_H__ */

6 mkimage工具#

用来制作不压缩或者压缩的多种可启动映象文件。比如内核启动镜像(给zImage镜像添加64字节的头信息成uImage),再比如rootfs.img

1
2
3
4
5
6
7
8
9
10
11
12
13
Usage: ./mkimage -l image
-l ==> list image header information
./mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
-A ==> set architecture to 'arch'
-O ==> set operating system to 'os'
-T ==> set image type to 'type'
-C ==> set compression type 'comp'
-a ==> set load address to 'addr' (hex)
-e ==> set entry point to 'ep' (hex)
-n ==> set image name to 'name'
-d ==> use image data from 'datafile'
-x ==> set XIP (execute in place)
./mkimage [-D dtc_options] -f fit-image.its fit-image
选项 作用
-A 指定CPU的架构,比如:arm或者x86
-O 指定操作系统的类型,比如:linux
-T 指定镜像的类型,比如:standalone、kernel、ramdisk、multi、firmware、script、filesystem
-C 指定压缩的方式,比如:none 不压缩、gzip 用gzip的压缩方式、bzip2 用bzip2的压缩方式
-a 指定镜像在内存中的加载地址
-e 指定镜像运行的入口点地址
-n 指定镜像的名字
-d 指定制作镜像的源文件
-x 镜像是否可以片上执行

mkimage命令举例如下:

1
#mkimage -n "My Kernel" -A arm -O linux -T kernel -C gzip –a 0x8000 –e 0x8000 -d kernel.gz kernel.img

以上命令将压缩了的内核二进制文件kernel.gz转换成u-boot能够辨认的二进制文件kernel.img,并指定kernel.img的名字为“My Kernel”,处理器体系架构为arm,操作系统类型为linux,程序类型为操作系统内核,程序由gzip压缩,程序的链接起始地址为0x8000,程序的入口地址为0x8000,注意这两个地址一定要是物理地址而不是对应的虚拟地址。

1
#mkimage -n "My Rootfs" -A arm -O linux -T ramdisk -C gzip -d ramdisk.gz ramdisk.img

以上命令将压缩了的ramdisk根文件系统二进制文件ramdisk.gz转换成u-boot能够辨认的二进制文件ramdisk.img,并指定ramdisk.img的名字为“My Rootfs”,处理器体系架构为arm,操作系统类型为linux,程序类型为ramdisk,程序由gzip压缩,不需要指定ramdisk的链接起始地址和入口地址。

如果我们将kernel.imgramdisk.img文件分别写到flash芯片的0xFF0000000xFF200000位置,系统启动后进入u-boot命令行界面,执行以下u-boot命令:

1
# bootm 0xFF000000 0xFF200000

那么u-boot将把0xFF000000位置的Linux内核解压缩到RAM中的0x8000位置,再将0xFF200000位置的ramdisk文件系统映像文件刨掉u-boot头部后复制到RAM中的某个位置,然后跳转到内核的入口地址0x8000位置启动内核,同时把板子信息、ramdisk在RAM中的起始地址和结束地址、命令行字符串传给内核,这样Linux开始启动运行。

如果没有可以片上执行的norflash:

  1. mkimage
    mkimage -n 'linux-2.6.14' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008040 -d zImage zImage.img
  2. tftp下载到ddr
    ftp 0x30008000 zImage.img
  3. 启动内核
    bootm 0x30008000