Mini2440之uboot移植流程之linux内核启动分析(六)
在前面的章节关于u-boot
的源码,以及u-boot
的移植这一块我们介绍完了。接下来,我们应该开始进入第二个阶段,linux
内核移植,以及驱动开发。
但是在这之前,我们遗漏了u-boot
中的一个重要环节没有介绍,就是u-boot
如何执行bootm
命令,如何实现linux
内核启动。
我们在《Mini440之uboot
移植之源码分析命令解析(五) 》介绍过如果配置了CONFIG_BOOTCOMMAND
宏:
#define CONFIG_BOOTCOMMAND "nand read 0x30000000 kernel; bootm 0x30000000" //bootcmd
那么在执行autoboot_command
函数的时候,将会执行bootcmd
中保存的命令。
nand read 0x30000000 kernel
:这里将Nand Flash kernel
分区的内核镜像加载到地址0x30000000
;bootm 0x30000000
:启动linux
内核;bootm
这个命令用于启动一个内核镜像,这个镜像就是uImage
文件,它会从uImage
镜像文件的头部取得一些信息,这些信息包括:CPU
架构、操作系统类型、文件类型、压缩方式、加载地址、运行的入口地址等;
一、内核镜像
前面我们说了u-boot
启动linux内
核,从Nand Flash
内核分区中加载uImage
镜像,那么什么是uImage
镜像呢?说到这个我们就不得不聊一聊内核镜像的几种文件格式了。
1.1 介绍
linux
内核编译之后一般会生成一下两个文件,一个是Image
,一个是zImage
,其中:
Image
为内核镜像文件(可以直接在芯片上运行原生二进制文件);- 而
zImage
为内核的镜像压缩文件(但它不仅爱是一个压缩文件,在文件的开头部分内嵌有gzip
解压缩代码)。
但是这两种镜像的格式并没有办法提供给u-boot
的足够的信息来进行load
、jump
或者验证操作等等。因此,u-boot
提供了mkimage
工具,来将zImage
制作为u-boot
可以识别的格式,将生成的文件称之uImage
。
需要注意的是:这里并不是说u-boot
一定不支持zImage
镜像文件的启动,一般可以通过配置CONFIG_ZIMAGE_BOOT
使u-boot
支持zImage
启动。
1.1.1 Legacy uImage
Legacy uImage
它是在zImage
之前加上了一个长度为64
字节的头,说明这个内核的CPU架构、操作系统类型、文件类型、压缩方式、加载地址、运行的入口地址等。
1.1.2 FIT uImage
FIT uImage
是在Legacy uImage
的基础上,为了满足Linux Flattened Device Tree(FDT)
的标准,而重新改进和定义出来的一种镜像文件格式;它一般将kernel
、fdt
、ramdisk
等等镜像打包到一个itb
镜像文件中;u-boot
只要获得了这个镜像文件,就可以得到kernel
、fdt
、ramdisk
等等镜像的具体信息和内容。
1.1.3 Image
、zImage
、uImage
发展历程
为什么会出现Legacy uImage
,然后又出现FIT uImage
?
最开始出现的是Image
,就一个普通的内核镜像。然后为了节省空间,有了zImage
,进行了压缩可以节省空间。
u-boot
启动一个Image
或者zImage
,还必须要给它传递一些参数;
- 镜像文件的类型,如
kernel image
、dtb
文件、ramdisk image
等等? - 镜像文件需要放在内存的的哪个位置(加载地址)?
- 镜像文件需要从内存哪个位置开始执行(入口地址)?
- 镜像文件是否有压缩?
- 镜像文件是否有一些完整性校验的信息(如
CRC
)?
这种方式的不足就在于,镜像本身没有带有这些参数的,用工具制作完镜像后,还需要另外再向u-boot提供这些参数,才能正常启动(就是比较麻烦)。
如果可以把这些参数,在制作镜像的时候就一起弄到镜像里面,然后u-boot
一读取镜像,就马上可以知道这些参数了。不需要在制作好镜像之后再另外告诉u-boot
这些参数。这种带有以上参数的镜像格式就是Legacy uImage
。
最后一个FIT uImage
,其主要目的是为了支持基于device tree
的unify kernel
。
1.2 Legacy uImage
1.2.1 配置
使能需要打开的宏:
CONFIG_IMAGE_FORMAT_LEGACY=y
注意,这个宏在自动生成的autoconf.mk
中会自动配置,不需要额外配置。
1.2.2 制作
编译完u-boot
之后,使用u-boot
目录下tools/mkimage
工具来制作uImage
。命令如下:
mkimage -A arm -O linux -C none -T kernel -a 0x20008000 -e 0x20008040 -n Linux_Image -d zImage uImage
各个参数意义如下
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' // 镜像名称,注意不能超过32B
-d ==> use image data from 'datafile' // 输入文件
-x ==> set XIP (execute in place)
1.2.3 启动内核
将生成的Legacy uImage
下载到内存中,使用bootm <download addr>
命令启动kernel
。
但是注意,如果使用Legacy uImage
后面还需要根据实际情况决定是否需要传入ramdisk
以及dtb
的在内存中的地址,否则可能会导致bootm
失败。
格式如下:
bootm # 后面没有地址则使用CONFIG_SYS_LOAD_ADDR地址作为启动地址
bootm <Legacy uImage addr> <ramdisk addr> <dtb addr>
1.3 FIT uImage
1.3.1 FIT
介绍
FIT
是flattened image tree
的简称,它采用了device tree source filse(DTS)
的语法,生成的image
文件也和dtb
文件类似(称做itb
)。
其通过一定语法和格式将一些需要使用到的镜像(例如kernel
、dtb
以及文件系统)组合到一起生成一个image file
。其生成步骤如下图所示:
其中image source file(.its)
和device tree source file(.dts)
类似,负责描述要生成的image file
的信息。mkimage
和dtc
工具,可以将.its
文件以及对应的image data file
,打包成一个image file
。
这里我们有必要对涉及到的这几类文件进行一个总结:
its
文件 :image source file
,类似于dts
文件,负责描述要声称的image
的的信息。需要自行进行构造;itb
文件 :最终得到的image
文件,类似于dtb
文件,也就是u-boot
可以直接对其进行识别和解析的FIT uImage
;mkimage
:mkimage
则负责dtc
的角色,用于通过解析its
文件、获取对应的镜像,最终生成一个u-boo
t可以直接进行识别和解析的itb
文件;image data file
:实际使用到的镜像文件;
mkimage
将its
文件以及对应的image data file
,打包成一个itb
文件,也就是u-boot
可以识别的image file(FIT uImage)
。我们将这个文件下载到么内存中,使用bootm
命令就可以执行了。
1.3.2 配置
使能需要打开的宏:
CONFIG_FIT=y
1.3.3 创建its
文件
因为mkimage
是根据its
文件中的描述来打包镜像生成itb
文件(FIT uImage
),所以首先需要制作一个its
文件,在its
文件中描述需要被打包的镜像,主要是kernel
镜像,dtb
文件,ramdisk
镜像。
关于its
文件的语法,可以参考这篇博客《FIT
介绍》。简单的例子如下:
展开查看详情
/*
* U-Boot uImage source file for "X project"
*/
/dts-v1/;
/ {
description = "U-Boot uImage source file for X project";
#address-cells = <1>;
images {
kernel@tiny210 {
description = "Unify(TODO) Linux kernel for project-x";
data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/linux/arch/arm/boot/zImage");
type = "kernel";
arch = "arm";
os = "linux";
compression = "none";
load = <0x20008000>;
entry = <0x20008000>;
};
fdt@tiny210 {
description = "Flattened Device Tree blob for project-x";
data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/linux/arch/arm/boot/dts/s5pv210-tiny210.dtb");
type = "flat_dt";
arch = "arm";
compression = "none";
};
ramdisk@tiny210 {
description = "Ramdisk for project-x";
data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/rootfs/initramfs.gz");
type = "ramdisk";
arch = "arm";
os = "linux";
compression = "gzip";
};
};
configurations {
default = "conf@tiny210";
conf@tiny210 {
description = "Boot Linux kernel with FDT blob";
kernel = "kernel@tiny210";
fdt = "fdt@tiny210";
ramdisk = "ramdisk@tiny210";
};
};
};
注意,可以有多个kernel
节点或者fdt
节点等等,兼容性更强。同时,可以有多种configurations
,来对kernel
、fdt
、ramdisk
来进行组合,使FIT-uImage
可以兼容于多种板子,而无需重新进行编译烧写。
1.3.4 生成FIT uImage
生成的命令相对Legacy uImage
较为简单,因为信息都在its
文件里面描述了:
${UBOOT_OUT_DIR}/tools/mkimage -f ${UIMAGE_ITS_FILE} ${UIMAGE_ITB_FILE}
其中-f
指定需要编译的source
文件,并在后面指定需要生成的image
文件(一般以.itb
为后缀,例如u-boot.itb
)。
1.3.5 启动内核
将生成的FIT uImage
下载到内存某个地址,使用如下命令启动;
bootm # 后面没有地址则使用CONFIG_SYS_LOAD_ADDR地址作为启动地址
bootm <FIT uImage addr>
bootm <FIT uImage addr>:<image name> # 单独加载FIT中<image name>镜像。
bootm <FIT uImage addr>#<config name> # 使用载FIT中指定<config name>启动
u-boot
会自动解析出FIT uImage
中,kernel
、ramdisk
、dtb
的信息,使用起来相当方便。更多可以参考《U-Boot FDT Overlay FIT usage
》。
1.4 其他
更多关于内核镜像的介绍以及制作的内容参考:《Rockchip RK3399
- 移植linux 5.2.8
`》。
此外有关bootm
更多信息:
Usage:
bootm [addr [arg ...]]
- boot application image stored in memory
passing arguments 'arg ...'; when booting a Linux kernel,
'arg' can be the address of an initrd image
When booting a Linux kernel which requires a flat device-tree
a third argument is required which is the address of the
device-tree blob. To boot that kernel without an initrd image,
use a '-' for the second argument. If you do not pass a third
a bd_info struct will be passed instead
For the new multi component uImage format (FIT) addresses
must be extended to include component or configuration unit name:
addr:<subimg_uname> - direct component image specification
addr#<conf_uname> - configuration specification
Use iminfo command to get the list of existing component
images and configurations.
Sub-commands to do part of the bootm sequence. The sub-commands must be
issued in the order below (it's ok to not issue all sub-commands):
start [addr [arg ...]]
loados - load OS image
ramdisk - relocate initrd, set env initrd_start/initrd_end
fdt - relocate flat device tree
cmdline - OS specific command line processing/setup
bdt - OS specific bd_t processing
prep - OS specific prep before relocation or go
go - start OS
二、linux
内核启动
2.1 autoboot_command
autoboot_command
函数定义在common/autoboot.c
文件中:
void autoboot_command(const char *s)
{
debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
run_command_list(s, -1, 0);
}
}
如果在u-boot
启动倒计时结束之前,没有按下任何键,将会执行那么将执行run_command_list
,此函数会执行参数s指定的一系列命令,也就是bootcmd
中配置中的命令,bootcmd
中保存着默认的启动命令。
在默认环境变量default_environment
中定义有:
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
2.2 do_bootm
由于要执行bootm
命令,所以我们需要打开与bootm
命令相关的文件进行分析,bootm
命令定义在cmc/bootm.c
文件中:
U_BOOT_CMD(
bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
"boot application image from memory", bootm_help_text
);
找到对应的do_bootm
函数,去除无用的代码:
展开查看详情
/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
/* determine if we have a sub command */
argc--; argv++;
if (argc > 0) {
char *endp;
simple_strtoul(argv[0], &endp, 16);
/* endp pointing to NULL means that argv[0] 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);
}
// 到这里参数中的bootm参数会被去掉
return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
BOOTM_STATE_LOADOS |
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO, &images, 1);
}
当执行bootm 0x30000000
,函数入参:
- 第一个参数是
bootm
命令结构体; flag
是命令标志位;argv[0]='bootm"
,argv[1]="0x30000000"
;argc=2
;
这里进入函数之后argc=1
,argv[0]=0x30000000
。
bootm
的核心是do_bootm_states
,以全局变量bootm_headers_t images
作为do_bootm_states
的参数,该变量在cmd/bootm.c
文件中声明。
bootm_headers_t images; /* pointers to os/initrd/fdt images */
bootm
会根据参数以及参数指向的镜像来填充这个结构体里面的成员。 最终再使用这个结构体里面的信息来填充kernel
启动信息并且到跳转到kernel
中,下面详细说明这个函数。
三、do_bootm_states
函数
3.1 函数声明
我们先来看一下这个函数的声明:
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
int states, bootm_headers_t *images, int boot_progress);
3.2.1 bootm_headers_t
参数bootm_headers_t *images
是一个复杂的数据结构,bootm_headers_t
定义在include/image.h
文件中:
展开查看详情
/*
* Legacy and FIT format headers used by do_bootm() and do_bootm_<os>()
* routines.
*/
typedef struct bootm_headers {
/*
* Legacy os image header, if it is a multi component image
* then boot_get_ramdisk() and get_fdt() will attempt to get
* data from second and third component accordingly.
*/
image_header_t *legacy_hdr_os; /* image header pointer */
image_header_t legacy_hdr_os_copy; /* header copy */
ulong legacy_hdr_valid;
#if IMAGE_ENABLE_FIT
const char *fit_uname_cfg; /* configuration node unit name */
void *fit_hdr_os; /* os FIT image header */
const char *fit_uname_os; /* os subimage node unit name */
int fit_noffset_os; /* os subimage node offset */
void *fit_hdr_rd; /* init ramdisk FIT image header */
const char *fit_uname_rd; /* init ramdisk subimage node unit name */
int fit_noffset_rd; /* init ramdisk subimage node offset */
void *fit_hdr_fdt; /* FDT blob FIT image header */
const char *fit_uname_fdt; /* FDT blob subimage node unit name */
int fit_noffset_fdt;/* FDT blob subimage node offset */
void *fit_hdr_setup; /* x86 setup FIT image header */
const char *fit_uname_setup; /* x86 setup subimage node name */
int fit_noffset_setup;/* x86 setup subimage node offset */
#endif
#ifndef USE_HOSTCC
image_info_t os; /* os image info */
ulong ep; /* entry point of OS */
ulong rd_start, rd_end;/* ramdisk start/end */
char *ft_addr; /* flat dev tree address */
ulong ft_len; /* length of flat device tree */
ulong initrd_start;
ulong initrd_end;
ulong cmdline_start;
ulong cmdline_end;
bd_t *kbd;
#endif
int verify; /* getenv("verify")[0] != 'n' */
#define BOOTM_STATE_START (0x00000001)
#define BOOTM_STATE_FINDOS (0x00000002)
#define BOOTM_STATE_FINDOTHER (0x00000004)
#define BOOTM_STATE_LOADOS (0x00000008)
#define BOOTM_STATE_RAMDISK (0x00000010)
#define BOOTM_STATE_FDT (0x00000020)
#define BOOTM_STATE_OS_CMDLINE (0x00000040)
#define BOOTM_STATE_OS_BD_T (0x00000080)
#define BOOTM_STATE_OS_PREP (0x00000100)
#define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* 'Almost' run the OS */
#define BOOTM_STATE_OS_GO (0x00000400)
int state;
#ifdef CONFIG_LMB
struct lmb lmb; /* for memory mgmt */
#endif
} bootm_headers_t;
bootm_headers_t
用于Legacy
或FIT
方式镜像的启动,其中包括了os/initrd/fdt images
的信息。我们这里大概介绍一下和这个结构体的成员变量:
Legacy uImage
相关:
legacy_hdr_os
:Legacy uImage
的镜像头;对应就是Legacy uImage
加载到内存的起始地址;legacy_hdr_os_copy
:Legacy uImage
的镜像头备份;legacy_hdr_valid
:用于表示Legacy uImage
的头部指针是否可用,也就是这是否是一个Legacy uImage
;
FIT uImage
相关:
fit_uname_cfg
:配置节点名;fit_hdr_os
:FIT uImage
中kernel
镜像头;对应就是FIT uImage
加载到内存的的起始地址;fit_uname_os
:FIT uImage
中kernel
的节点名称;比如我们之前案例中的kernel@tiny210
;fit_noffset_os
:FIT uImage
中kernel
的节点偏移,直接代表了kernel
节点;也就是说通过fit_hdr_os
、fit_noffset_os
就可以获取到itb
文件中描述的kernel
节点的信息;fit_hdr_rd
:FIT uImage
中ramdisk
的镜像头;fit_uname_rd
:FIT uImage
中ramdisk
的节点名;fit_noffset_rd
:FIT uImage
中ramdisk
的节点偏移;fit_hdr_fdt
:FIT uImage
中FDT
的镜像头;fit_uname_fdt
:FIT uImage
中FDT
的节点名;fit_noffset_fdt
:FIT uImage
中FDT
的节点偏移;
其它:
os
:操作系统信息的结构体,包含os.load
、os.type
、os.os
等字段信息;ep
:操作系统的入口地址;rd_start
:ramdisk
在内存上的起始地址;rd_end
:ramdisk
在内存上的结束地址ft_addr
:fdt
在内存上的地址;ft_len
:fdt
在内存上的长度;verify
:是否需要验证;state
:状态标识,用于标识对应的bootm
需要做什么操作;
其中legacy_hdr_os
是image_header_t
类型。这个结构尤为重要,下面来介绍。
3.2.2 image_header_t
u-boot
使用image_header_t
来表示Legacy uImage
的头部信息,定义在include/image.h
文件:
/*
* Legacy format image header,
* all data in network byte order (aka natural aka bigendian).
*/
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;
比较重要的成员有:
-
ih_magic
:镜像的魔数,用来校验是不是一个Legacy uImage
;为0x27051956
则表示是一个Legacy uImage
; -
ih_siz
e:数据镜像长度,和zImage
长度一样,不包含头部信息; -
ih_load
:镜像的加载地址;和mkimage
使用的参数一致;zImage
解压之后得到Image
会加载到这个地址上; -
ih_ep
:镜像的入口地址;和mkimage
使用的参数一致;内核启动的入口地址; -
ih_os
:镜像的系统; -
ih_type
:镜像的类型;
3.2.3 状态说明
do_bootm_state
s的state
参数是一大堆的标志宏,这些标志宏就是u-boot
启动时需要的阶段,每个阶段都有一个宏来表示。
#define BOOTM_STATE_START (0x00000001)
#define BOOTM_STATE_FINDOS (0x00000002)
#define BOOTM_STATE_FINDOTHER (0x00000004)
#define BOOTM_STATE_LOADOS (0x00000008)
#define BOOTM_STATE_RAMDISK (0x00000010)
#define BOOTM_STATE_FDT (0x00000020)
#define BOOTM_STATE_OS_CMDLINE (0x00000040)
#define BOOTM_STATE_OS_BD_T (0x00000080)
#define BOOTM_STATE_OS_PREP (0x00000100)
#define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* 'Almost' run the OS */
#define BOOTM_STATE_OS_GO (0x00000400)
其中:
BOOTM_STATE_START
:开始执行bootm
的一些准备动作;BOOTM_STATE_FINDOS
:查找内核镜像,需要注意的是内核镜像有多种,比如Image
、zImage
、uImage
等,像Image
内核镜像加载到内存,就可以直接运行,而像zImage
这种是压缩之后的内核镜像文件,它在头部包含了解压缩代码,而uImage
在zImage
之前又加了0x40
的头信息,而u-boot
引导的内核镜像文件一般为zImage
(bootz
命令)和uImage
(bootm
命令);BOOTM_STATE_FINDOTHER
:查找内核镜像外的其它镜像,比如FDT
、ramdisk
等;BOOTM_STATE_LOADOS
:查找到内核镜像后,解析加载的内核镜像的头信息,根据内核镜像的格式是zImage
、uImage
还是设备树,有不同的处理逻辑,比uImage
需要解压,最终会将vmlinux
加载到images.os.load
指向的内存空间;BOOTM_STATE_RAMDISK
:操作ramdisk
;BOOTM_STATE_FDT
:操作FDT
;BOOTM_STATE_OS_CMDLINE
:操作commandline
;BOOTM_STATE_OS_BD_T
:跳转到内核前的准备动作;BOOTM_STATE_OS_PREP
:执行跳转前的准备动作 ;BOOTM_STATE_OS_FAKE_GO
:伪跳转,一般都能直接跳转到kernel
中去BOOTM_STATE_OS_GO
:设置启动参数,跳转到kernel
所在的地址上 ;
上面划掉的可以先忽略掉。do_bootm_states
根据states
来判断要执行的操作。在这些流程中,起传递作用的是bootm_headers_t images
这个数据结构,有些流程是解析内核镜像,往这个结构体里写数据。 而跳转的时候,则需要使用到这个结构体里面的数据。
3.3 do_bootm_states
函数
do_bootm_states
函数定义在cmd/bootm.c
文件:
展开查看详情
/**
* Execute selected states of the bootm command.
*
* Note the arguments to this state must be the first argument, Any 'bootm'
* or sub-command arguments must have already been taken.
*
* Note that if states contains more than one flag it MUST contain
* BOOTM_STATE_START, since this handles and consumes the command line args.
*
* Also note that aside from boot_os_fn functions and bootm_load_os no other
* functions we store the return value of in 'ret' may use a negative return
* value, without special handling.
*
* @param cmdtp Pointer to bootm command table entry
* @param flag Command flags (CMD_FLAG_...)
* @param argc Number of subcommand arguments (0 = no arguments)
* @param argv Arguments
* @param states Mask containing states to run (BOOTM_STATE_...)
* @param images Image header information
* @param boot_progress 1 to show boot progress, 0 to not do this
* @return 0 if ok, something else on error. Some errors will cause this
* function to perform a reboot! If states contains BOOTM_STATE_OS_GO
* then the intent is to boot an OS, so this function will not return
* unless the image type is standalone.
*/
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
int states, bootm_headers_t *images, int boot_progress)
{
boot_os_fn *boot_fn;
ulong iflag = 0;
int ret = 0, need_boot_fn;
images->state |= states;
/*
* Work through the states and see how far we get. We stop on
* any error.
*/
if (states & BOOTM_STATE_START)
ret = bootm_start(cmdtp, flag, argc, argv);
if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(cmdtp, flag, argc, argv);
if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
ret = bootm_find_other(cmdtp, flag, argc, argv);
argc = 0; /* consume the args */
}
/* Load the OS */
if (!ret && (states & BOOTM_STATE_LOADOS)) {
ulong load_end;
iflag = bootm_disable_interrupts();
ret = bootm_load_os(images, &load_end, 0);
if (ret == 0)
lmb_reserve(&images->lmb, images->os.load,
(load_end - images->os.load));
else if (ret && ret != BOOTM_ERR_OVERLAP)
goto err;
else if (ret == BOOTM_ERR_OVERLAP)
ret = 0;
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
if (images->os.os == IH_OS_LINUX)
fixup_silent_linux();
#endif
}
/* Relocate the ramdisk */
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH // 不执行
if (!ret && (states & BOOTM_STATE_RAMDISK)) {
ulong rd_len = images->rd_end - images->rd_start;
ret = boot_ramdisk_high(&images->lmb, images->rd_start,
rd_len, &images->initrd_start, &images->initrd_end);
if (!ret) {
setenv_hex("initrd_start", images->initrd_start);
setenv_hex("initrd_end", images->initrd_end);
}
}
#endif
#if IMAGE_ENABLE_OF_LIBFDT && defined(CONFIG_LMB) // 不执行
if (!ret && (states & BOOTM_STATE_FDT)) {
boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,
&images->ft_len);
}
#endif
/* From now on, we need the OS boot function */
if (ret)
return ret;
boot_fn = bootm_os_get_boot_func(images->os.os);
need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
if (boot_fn == NULL && need_boot_fn) {
if (iflag)
enable_interrupts();
printf("ERROR: booting os '%s' (%d) is not supported\n",
genimg_get_os_name(images->os.os), images->os.os);
bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
return 1;
}
/* Call various other states that are not generally used */
if (!ret && (states & BOOTM_STATE_OS_CMDLINE))
ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_BD_T))
ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_PREP))
ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
#ifdef CONFIG_TRACE // 不执行
/* Pretend to run the OS, then run a user command */
if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
char *cmd_list = getenv("fakegocmd");
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
images, boot_fn);
if (!ret && cmd_list)
ret = run_command_list(cmd_list, -1, flag);
}
#endif
/* Check for unsupported subcommand. */
if (ret) {
puts("subcommand not supported\n");
return ret;
}
/* Now run the OS! We hope this doesn't return */
if (!ret && (states & BOOTM_STATE_OS_GO))
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
images, boot_fn);
/* Deal with any fallout */
err:
if (iflag)
enable_interrupts();
if (ret == BOOTM_ERR_UNIMPLEMENTED)
bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);
else if (ret == BOOTM_ERR_RESET)
do_reset(cmdtp, flag, argc, argv);
return ret;
}
代码具体执行流程:
-
初始化
images->state |= states
; -
states
跟宏BOOTM_STATE_START
进行与操作,通过执行bootm_start
; -
states
跟宏BOOTM_STATE_FINDOS
进行与操作,通过执行bootm_find_os
; -
states
跟宏BOOTM_STATE_FINDOTHER
进行与操作,通过执行bootm_find_other
; -
states
跟宏BOOTM_STATE_LOADOS
进行与操作,通过关闭中断,执行bootm_load_os
; -
states
跟宏BOOTM_STATE_OS_PREP
进行与操作,通过执行boot_fn
; -
states
跟宏BOOTM_STATE_OS_GO
进行与操作,通过执行boot_selected_os
;
boot_selected_os
,这函数里面就执行do_bootm_linux
跳转到我们的内核去运行了,如无意外,到了这里一般情况下就不返回了。
我们根据状态参数,绘制出这个函数的执行流程:
3.3.1 bootm_start
boot_start
函数定义在common/bootm.c
文件:
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
memset((void *)&images, 0, sizeof(images));
images.verify = getenv_yesno("verify");
boot_start_lmb(&images);
bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
images.state = BOOTM_STATE_START;
return 0;
}
代码具体执行流程:
- 清空
images
结构体; - 获取环境遍历
verify
,并赋值给images.verify
; - 执行
boot_start_lmb()
初始化images.lmb
; - 执行
bootstage_mark_name
,记录启动阶段的名字; - 设置
images.state = BOOTM_STATE_START
;
3.3.2 bootm_find_os
bootm_find_os
函数用于获取操作系统相关的一些信息,例如类型、压缩方式,加载地址、入口地址等。
定义在common/bootm.c
:
展开查看详情
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
const void *os_hdr;
bool ep_found = false;
int ret;
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv, // 1. 获取到Legacy uImage的头部,并将其指针返回给os_hdr
&images, &images.os.image_start, &images.os.image_len);
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)) { // 2. 根据不同的内核镜像格式,如Android、Legacy uImage、FIT uImage,获取操作系统的信息
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
case IMAGE_FORMAT_LEGACY: // Legacy uImage
images.os.type = image_get_type(os_hdr); // 从头部image_header中获得镜像类型并存储到images.os.type中
images.os.comp = image_get_comp(os_hdr); // 从头部image_header中获得压缩类型类型并存储到images.os.comp中
images.os.os = image_get_os(os_hdr); // 从头部image_header中获得操作系统类型并存储到images.os.os中
images.os.end = image_get_image_end(os_hdr); // 当前镜像的尾地址
images.os.load = image_get_load(os_hdr); // 从头部image_header中获得加载地址并存储到images.os.load中
images.os.arch = image_get_arch(os_hdr); // 从头部image_header中获得cpu体系结构类型并存储到images.os.arch中
break;
#endif
#if IMAGE_ENABLE_FIT
case IMAGE_FORMAT_FIT: // FIT uImage
if (fit_image_get_type(images.fit_hdr_os, // 获取itb文件中kernel节点中的"type"属性
images.fit_noffset_os,
&images.os.type)) {
puts("Can't get image type!\n");
bootstage_error(BOOTSTAGE_ID_FIT_TYPE);
return 1;
}
if (fit_image_get_comp(images.fit_hdr_os, // 获取itb文件中kernel节点中的"comp"属性
images.fit_noffset_os,
&images.os.comp)) {
puts("Can't get image compression!\n");
bootstage_error(BOOTSTAGE_ID_FIT_COMPRESSION);
return 1;
}
if (fit_image_get_os(images.fit_hdr_os, images.fit_noffset_os, // 获取itb文件中kernel节点中的"os"属性
&images.os.os)) {
puts("Can't get image OS!\n");
bootstage_error(BOOTSTAGE_ID_FIT_OS);
return 1;
}
if (fit_image_get_arch(images.fit_hdr_os, // 获取itb文件中kernel节点中的"arch"属性
images.fit_noffset_os,
&images.os.arch)) {
puts("Can't get image ARCH!\n");
return 1;
}
images.os.end = fit_get_end(images.fit_hdr_os);
if (fit_image_get_load(images.fit_hdr_os, images.fit_noffset_os, // 获取itb文件中kernel节点中的"load"属性
&images.os.load)) {
puts("Can't get image load address!\n");
bootstage_error(BOOTSTAGE_ID_FIT_LOADADDR);
return 1;
}
break;
#endif
#ifdef CONFIG_ANDROID_BOOT_IMAGE
case IMAGE_FORMAT_ANDROID: // Android
images.os.type = IH_TYPE_KERNEL;
images.os.comp = IH_COMP_NONE;
images.os.os = IH_OS_LINUX;
images.os.end = android_image_get_end(os_hdr);
images.os.load = android_image_get_kload(os_hdr);
images.ep = images.os.load;
ep_found = true;
break;
#endif
default:
puts("ERROR: unknown image format type!\n");
return 1;
}
/* If we have a valid setup.bin, we will use that for entry (x86) */
if (images.os.arch == IH_ARCH_I386 ||
images.os.arch == IH_ARCH_X86_64) {
ulong len;
ret = boot_get_setup(&images, IH_ARCH_I386, &images.ep, &len);
if (ret < 0 && ret != -ENOENT) {
puts("Could not find a valid setup.bin for x86\n");
return 1;
}
/* Kernel entry point is the setup.bin */
} else if (images.legacy_hdr_valid) { // Legacy uImage类型
images.ep = image_get_ep(&images.legacy_hdr_os_copy); // 从头部image_header中获得镜像的入口地址并存储到images.ep中
#if IMAGE_ENABLE_FIT
} else if (images.fit_uname_os) { // FIT uImage类型
int ret;
ret = fit_image_get_entry(images.fit_hdr_os, // 获取itb文件中kernel节点中的"entry"属性
images.fit_noffset_os, &images.ep);
if (ret) {
puts("Can't get entry point property!\n");
return 1;
}
#endif
} else if (!ep_found) {
puts("Could not find kernel entry point!\n");
return 1;
}
if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {
images.os.load = images.os.image_start;
images.ep += images.os.load;
}
images.os.start = map_to_sysmem(os_hdr);
return 0;
}
假设我们使用的内核镜像格式为Legacy uImage
,那么代码具体执行流程如下。
3.3.2.1 boot_get_kernel
调用boot_get_kernel
函数获取内核镜像在内存地址和大小:
展开查看详情
/**
* boot_get_kernel - find kernel image
* @os_data: pointer to a ulong variable, will hold os data start address
* @os_len: pointer to a ulong variable, will hold os data length
*
* boot_get_kernel() tries to find a kernel image, verifies its integrity
* and locates kernel data.
*
* returns:
* pointer to image header if valid image was found, plus kernel start
* address and length, otherwise NULL
*/
static const void *boot_get_kernel(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[], bootm_headers_t *images,
ulong *os_data, ulong *os_len)
{
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
image_header_t *hdr;
#endif
ulong img_addr;
const void *buf;
const char *fit_uname_config = NULL;
const char *fit_uname_kernel = NULL;
#if IMAGE_ENABLE_FIT
int os_noffset;
#endif
// common/image.c, get the real kernel address and return 2 FIT strings
img_addr = genimg_get_kernel_addr_fit(argc < 1 ? NULL : argv[0],
&fit_uname_config,
&fit_uname_kernel);
bootstage_mark(BOOTSTAGE_ID_CHECK_MAGIC);
/* check image type, for FIT images get FIT kernel node */
*os_data = *os_len = 0;
buf = map_sysmem(img_addr, 0);
switch (genimg_get_format(buf)) {
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
case IMAGE_FORMAT_LEGACY:
printf("## Booting kernel from Legacy Image at %08lx ...\n",
img_addr);
hdr = image_get_kernel(img_addr, images->verify);
if (!hdr)
return NULL;
bootstage_mark(BOOTSTAGE_ID_CHECK_IMAGETYPE);
/* get os_data and os_len */
switch (image_get_type(hdr)) {
case IH_TYPE_KERNEL:
case IH_TYPE_KERNEL_NOLOAD:
*os_data = image_get_data(hdr);
*os_len = image_get_data_size(hdr);
break;
case IH_TYPE_MULTI:
image_multi_getimg(hdr, 0, os_data, os_len);
break;
case IH_TYPE_STANDALONE:
*os_data = image_get_data(hdr);
*os_len = image_get_data_size(hdr);
break;
default:
if (cmdtp)
printf("Wrong Image Type for %s command\n",
cmdtp->name);
bootstage_error(BOOTSTAGE_ID_CHECK_IMAGETYPE);
return NULL;
}
/*
* copy image header to allow for image overwrites during
* kernel decompression.
*/
memmove(&images->legacy_hdr_os_copy, hdr,
sizeof(image_header_t));
/* save pointer to image header */
images->legacy_hdr_os = hdr;
images->legacy_hdr_valid = 1;
bootstage_mark(BOOTSTAGE_ID_DECOMP_IMAGE);
break;
#endif
#if IMAGE_ENABLE_FIT
case IMAGE_FORMAT_FIT:
os_noffset = fit_image_load(images, img_addr,
&fit_uname_kernel, &fit_uname_config,
IH_ARCH_DEFAULT, IH_TYPE_KERNEL,
BOOTSTAGE_ID_FIT_KERNEL_START,
FIT_LOAD_IGNORED, os_data, os_len);
if (os_noffset < 0)
return NULL;
images->fit_hdr_os = map_sysmem(img_addr, 0);
images->fit_uname_os = fit_uname_kernel;
images->fit_uname_cfg = fit_uname_config;
images->fit_noffset_os = os_noffset;
break;
#endif
#ifdef CONFIG_ANDROID_BOOT_IMAGE
......
#endif
default:
if (cmdtp)
printf("Wrong Image Format for %s command\n",
cmdtp->name);
bootstage_error(BOOTSTAGE_ID_FIT_KERNEL_INFO);
return NULL;
}
debug(" kernel data at 0x%08lx, len = 0x%08lx (%ld)\n",
*os_data, *os_len, *os_len);
return buf;
}
执行流程如下:
(1) 调用genimg_get_kernel_addr_fit
获取内核镜像加载到内存的起始地址;
对于Legacy uImage
就是uImage
加载到内存的起始地址,也就是我们启动命令传入的0x30000000
参数;
bootm 0x30000000
对于FIT uImage
,解析启动命令,比如:
bootm <FIT uImage addr>#<config name>
调用fit_parse_conf
函数用于解析FIT
配置字符串;
<addr>
是fit blob
(不包含image
)加载到内存的地址,并将其返回,即设置成img_addr
的值;<conf>
是启动内核抵用放入配置名称,并将其设置为fit_uname_config
的值;
如果启动命令是bootm <FIT uImage addr>
那么只会初始化img_addr
的值,即fit blob
(不包含image
)加载到内存的地址;fit_uname_config
、fit_uname_kernel
均为初始化;
(2) 调用genimg_get_format
获取内核镜像格式:除了我们前面介绍的Legacy uImage
、FIT uImage
、还有安卓等格式;
(3) 然后初始化images.os.image_start
;
-
对于
Legacy uImage
,其值为内核镜像加载到内存的起始地址 + 镜像头64
字节,即zImage
加载到内存的起始地址;也就是0x30000040
; -
对于
FIT uImage
,其值为itb
文件中kernel
节点data
属性指向的内核镜像文件在内存中的地址,一般也是zImage
加载到内存的起始地址;
(5 )初始化images.os.image_len
(即参数os_data
),内核镜像数据长度,即zImage
的大小;
(6) 初始化images.legacy_hdr_os
(即参数os_len
):指向0x30000000
地址;
(7) 将images.legacy_hdr_os
指向的前64
个字节拷贝到images.legacy_hdr_os_copy
;即位uImage
的头部做了一个备份;
(8) 设置images.legacy_hdr_valid = 1
; 说明uImage
的头部指针指向的头部是可用的,这是一个Legacy uImage
类型
(9) 如果是FIT uImage
,还会初始化images
中部分与fit
相关的字段;
// 从fit_uname_config参数所指定的配置中加载kernel镜像, common/image-fit.c
// 这里是首先从FTI中获取configurations节点下名称为fit_uname_config(默认为conf)的子节点的偏移,
// 然后获取conf节点IH_TYPE_KERNEL(kernel)属性的值,一般为kernel = "kernel"
// 从FIT中获取images节点下名称为kernel的子节点的偏移,赋值为noffset变量
// ....
// 设置fit_uname_kernel为"kernel",即kernel = "kernel"中的值
// 返回noffset
os_noffset = fit_image_load(images, img_addr,
&fit_uname_kernel, &fit_uname_config,
IH_ARCH_DEFAULT, IH_TYPE_KERNEL,
BOOTSTAGE_ID_FIT_KERNEL_START,
FIT_LOAD_IGNORED, os_data, os_len);
if (os_noffset < 0)
return NULL;
images->fit_hdr_os = map_sysmem(img_addr, 0);
images->fit_uname_os = fit_uname_kernel;
images->fit_uname_cfg = fit_uname_config;
images->fit_noffset_os = os_noffset;
3.3.2.2 genimg_get_format
根据内核镜像文件的类型,去获取到内核信息,并初始化images.os
的各个成员。
对于Legacy uImage
,初始化:
- 内核镜像类型
images.os.type
;即images.legacy_hdr_os->ih_type
; - 内核压缩方式
images.os.comp
; 即images.legacy_hdr_os->ih_comp
; - 内核操作系统类型
images.os.os
;即images.legacy_hdr_os->ih_os
; - 内核镜像在内存的结束地址
image.os.end
; - 内核要装载到内存的地址
images.os.load
;即images.legacy_hdr_os->ih_load
; - 内核的
CPU
体系架构images.os.arch
;即images.legacy_hdr_os->ih_arch
;
对于FIT uImage
,调用如下函数进行初始化:
- 内核镜像类型
images.os.type
; 获取itb
文件中kernel
节点中的type
属性; - 内核压缩方式
images.os.comp
; 获取itb
文件中kernel
节点中的comp
属性; - 内核操作系统类型
images.os.os
;获取itb
文件中kernel
节点中的os
属性; - 内核的
CPU
体系架构images.os.arch
;获取itb
文件中kernel
节点中的arch
属性; - 内核要装载到内存的地址
images.os.load
;获取itb
文件中kernel
节点中的load
属性;
3.3.2.3 入口地址
最后从头部image_header
中获得镜像入口地址并存储到images.ep
,即images.legacy_hdr_os->ih_ep
;其实就是内核的启动地址了;
这里要说明一下,现在Legacy uImage
内核镜像文件所在的内存地址是0x30000000
,而内核启动的内存地址是在images.ep
成员所指向的地址;
3.3.3 bootm_find_other
查找内核镜像外的其它镜像,比如dtb
、ramdisk
等。
static int bootm_find_other(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
if (((images.os.type == IH_TYPE_KERNEL) ||
(images.os.type == IH_TYPE_KERNEL_NOLOAD) ||
(images.os.type == IH_TYPE_MULTI)) &&
(images.os.os == IH_OS_LINUX ||
images.os.os == IH_OS_VXWORKS))
return bootm_find_images(flag, argc, argv);
return 0;
}
函数调用链如下:
->bootm_find_other
->bootm_find_images
->boot_get_ramdisk --获取images.rd_start和images.rd_end。
->fit_image_load --从FIT镜像中获取ramdisk文件
->boot_get_fdt --获取images.ft_addr和images.ft_len。
->boot_get_fdt_fit --从FIT中获取dtb文件
->fit_image_load --根据fdt镜像名获取dtb文件
3.3.4 bootm_load_os
static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,
int boot_progress)
{
image_info_t os = images->os;
ulong load = os.load;
ulong blob_start = os.start;
ulong blob_end = os.end;
ulong image_start = os.image_start;
ulong image_len = os.image_len;
bool no_overlap;
void *load_buf, *image_buf;
int err;
load_buf = map_sysmem(load, 0); // 获取zImage解压之后,得到的Image的存放地址
image_buf = map_sysmem(os.image_start, image_len); // 获取zImage镜像在内存中的地址
err = bootm_decomp_image(os.comp, load, os.image_start, os.type, // 重点,解压缩,重定位
load_buf, image_buf, image_len,
CONFIG_SYS_BOOTM_LEN, load_end);
if (err) {
bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE);
return err;
}
flush_cache(load, *load_end - load);
debug(" kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end);
bootstage_mark(BOOTSTAGE_ID_KERNEL_LOADED);
no_overlap = (os.comp == IH_COMP_NONE && load == image_start);
if (!no_overlap && (load < blob_end) && (*load_end > blob_start)) {
debug("images.os.start = 0x%lX, images.os.end = 0x%lx\n",
blob_start, blob_end);
debug("images.os.load = 0x%lx, load_end = 0x%lx\n", load,
*load_end);
/* Check what type of image this is. */
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");
return BOOTM_ERR_OVERLAP;
} else {
puts("ERROR: new format image overwritten - must RESET the board to recover\n");
bootstage_error(BOOTSTAGE_ID_OVERWRITTEN);
return BOOTM_ERR_RESET;
}
}
return 0;
}
首先通过images.os.image_start
获取zImage
在内存的地址,然后通过images.os.load
获取zImage
解压之后Image的存放地址;
最后调用bootm_decomp_image
解压zImage
内核镜像文件,并将解压后的Image
存放到images.os.load
地址处,至此kernel
镜像已经完成加载。
3.3.5 boot_fn
boot_fn
的定义为:
boot_os_fn *boot_fn;
可以看出它是一个boot_os_fn
类型的函数指针。它的定义为
/*
* Continue booting an OS image; caller already has:
* - copied image header to global variable `header'
* - checked header magic number, checksums (both header & image),
* - verified image architecture (PPC) and type (KERNEL or MULTI),
* - loaded (first part of) image to header load address,
* - disabled interrupts.
*
* @flag: Flags indicating what to do (BOOTM_STATE_...)
* @argc: Number of arguments. Note that the arguments are shifted down
* so that 0 is the first argument not processed by U-Boot, and
* argc is adjusted accordingly. This avoids confusion as to how
* many arguments are available for the OS.
* @images: Pointers to os/initrd/fdt
* @return 1 on error. On success the OS boots so this function does
* not return.
*/
typedef int boot_os_fn(int flag, int argc, char * const argv[],
bootm_headers_t *images);
extern boot_os_fn do_bootm_linux;
然后boot_fn
在do_bootm
函数中被赋值为:
boot_fn = bootm_os_get_boot_func(images->os.os);
bootm_os_get_boot_func
函数返回的是boot_os[os]
,boot_os
是一个函数指针数组,定义在common/bootm_os.c
文件中:
static boot_os_fn *boot_os[] = {
[IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX
[IH_OS_LINUX] = do_bootm_linux,
#endif
#ifdef CONFIG_BOOTM_NETBSD
[IH_OS_NETBSD] = do_bootm_netbsd,
#endif
#ifdef CONFIG_LYNXKDI
[IH_OS_LYNXOS] = do_bootm_lynxkdi,
#endif
#ifdef CONFIG_BOOTM_RTEMS
[IH_OS_RTEMS] = do_bootm_rtems,
#endif
#if defined(CONFIG_BOOTM_OSE)
[IH_OS_OSE] = do_bootm_ose,
#endif
#if defined(CONFIG_BOOTM_PLAN9)
[IH_OS_PLAN9] = do_bootm_plan9,
#endif
#if defined(CONFIG_BOOTM_VXWORKS) && \
(defined(CONFIG_PPC) || defined(CONFIG_ARM))
[IH_OS_VXWORKS] = do_bootm_vxworks,
#endif
#if defined(CONFIG_CMD_ELF)
[IH_OS_QNX] = do_bootm_qnxelf,
#endif
#ifdef CONFIG_INTEGRITY
[IH_OS_INTEGRITY] = do_bootm_integrity,
#endif
#ifdef CONFIG_BOOTM_OPENRTOS
[IH_OS_OPENRTOS] = do_bootm_openrtos,
#endif
};
u-boot
支持的操作操作系统类型宏在include/config_defaults.h
文件中定义:
/* Support bootm-ing different OSes */
#define CONFIG_BOOTM_LINUX 1
#define CONFIG_BOOTM_NETBSD 1
#define CONFIG_BOOTM_PLAN9 1
#define CONFIG_BOOTM_RTEMS 1
#define CONFIG_BOOTM_VXWORKS 1
我们的内核是linux
操作系统,因此boot_fn
指向的是do_bootm_linux
。
3.3.6 boot_selected_os
int boot_selected_os(int argc, char * const argv[], int state,
bootm_headers_t *images, boot_os_fn *boot_fn)
{
arch_preboot_os();
boot_fn(state, argc, argv, images);
/* Stand-alone may return when 'autostart' is 'no' */
if (images->os.type == IH_TYPE_STANDALONE ||
state == BOOTM_STATE_OS_FAKE_GO) /* We expect to return */
return 0;
bootstage_error(BOOTSTAGE_ID_BOOT_OS_RETURNED);
#ifdef DEBUG
puts("\n## Control returned to monitor - resetting...\n");
#endif
return BOOTM_ERR_RESET;
}
函数最后一个参数就是我们传入的do_bootm_linux
函数,这里将会执行该函数,进行linux
内核的启动。
四、do_bootm_linux
函数
do_bootm_linux
函数在不同的硬件平台有不同的实现,我们这里查看的ARM
架构的实现代码,代码位于arch/arm/lib/bootm.c
文件:
/* Main Entry point for arm bootm implementation
*
* Modeled after the powerpc implementation
* DIFFERENCE: Instead of calling prep and go at the end
* they are called if subcommand is equal 0.
*/
int do_bootm_linux(int flag, int argc, char * const argv[],
bootm_headers_t *images)
{
/* No need for those on ARM */
if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE) // 不执行
return -1;
if (flag & BOOTM_STATE_OS_PREP) { // 执行
boot_prep_linux(images);
return 0;
}
if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) { // 执行
boot_jump_linux(images, flag);
return 0;
}
boot_prep_linux(images);
boot_jump_linux(images, flag);
return 0;
}
我们一点一点分析这里函数的执行流程,其流程大致如下:
4.1 boot_prep_linux
首先先执行BOOTM_STATE_OS_PREP
的代码,这里调用的是boot_prep_linux
,函数位于arch/arm/lib/bootm.c
,这个函数跟内核传递参数有关系,为内核设置启动参数,u-boot
向内核传递参数就是在这里做的准备:
/* Subcommand: PREP */
static void boot_prep_linux(bootm_headers_t *images)
{
char *commandline = getenv("bootargs");
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) { // 不执行
#ifdef CONFIG_OF_LIBFDT
debug("using: FDT\n");
if (image_setup_linux(images)) {
printf("FDT creation failed! hanging...");
hang();
}
#endif
} else if (BOOTM_ENABLE_TAGS) {
debug("using: ATAGS\n");
setup_start_tag(gd->bd);
if (BOOTM_ENABLE_SERIAL_TAG) // 不执行
setup_serial_tag(¶ms);
if (BOOTM_ENABLE_CMDLINE_TAG) // 执行
setup_commandline_tag(gd->bd, commandline);
if (BOOTM_ENABLE_REVISION_TAG) // 不执行
setup_revision_tag(¶ms);
if (BOOTM_ENABLE_MEMORY_TAGS) // 执行
setup_memory_tags(gd->bd);
if (BOOTM_ENABLE_INITRD_TAG) { //执行
/*
* In boot_ramdisk_high(), it may relocate ramdisk to
* a specified location. And set images->initrd_start &
* images->initrd_end to relocated ramdisk's start/end
* addresses. So use them instead of images->rd_start &
* images->rd_end when possible.
*/
if (images->initrd_start && images->initrd_end) {
setup_initrd_tag(gd->bd, images->initrd_start,
images->initrd_end);
} else if (images->rd_start && images->rd_end) {
setup_initrd_tag(gd->bd, images->rd_start,
images->rd_end);
}
}
setup_board_tags(¶ms);
setup_end_tag(gd->bd);
} else {
printf("FDT and ATAGS support not compiled in - hanging\n");
hang();
}
}
这里先调用char *commandline = getenv("bootargs")
;从u-boot
的环境变量中获取到我们传入的启动参数,比如上一节我们提到的启动参数:
noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0
如果使用了设备树的话,这里将会调用image_setup_linux
函数,该函数会在设备树的choosen
节点下添加bootargs
属性,其属性的内容就是bootargs
环境变量的值。
不过这里我们没有使用设备树,因此走的是设置各种tag的逻辑,关于这一块具体内容可以看《Mini2440
之BootLoader
简单实现》第3.3节内容,下面是一个tag
在内存的分布示意图,图中地址只供参考,实际上:
u-boot
中gd->bd
:指向地址空间为CONFIG_SYS_SDRAM_BASE
+PHYS_SDRAM_1_SIZE
-uboot
大小 -64kb
-4MB
-sizeof(bd_t)
:因此第一个tag
地址位于gd->bd->bi_boot_params
,而不是图中的0x30000100
;kernel
地址位于0x30000000
,而不是图中的0x30008000
;
4.1.1 struct tag
struct tag
的结构如下:
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
/*
* Acorn specific
*/
struct tag_acorn acorn;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};
/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE 0x00000000
struct tag_header {
u32 size;
u32 tag;
};
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x54410001
struct tag_core {
u32 flags; /* bit 0 = read-only */
u32 pagesize;
u32 rootdev;
};
/* it is allowed to have multiple ATAG_MEM nodes */
#define ATAG_MEM 0x54410002
struct tag_mem32 {
u32 size;
u32 start; /* physical start address */
};
...
tag
结构包括tag
头hdr
和各种类型的tag_*
;hdr
来标志当前的tag
是哪种类型。
4.1.2 setup_start_tag
调用setup_start_tag
设置启动要用到的tag
,在这里有一个全局静态变量static struct tag *params
;bd->bi_boot_params
的值赋给它:
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *)bd->bi_boot_params;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
setup_start_tag
是初始化了第一个tag
,类型为tag_core
。 最后调用tag_next
跳到第一个tag
末尾,为下一个tag
赋值做准备。
4.1.3 setup_commandline_tag
在头文件smdk2410.
h配置了:
#define CONFIG_CMDLINE_TAG /* enable passing of ATAGs */
#define CONFIG_SETUP_MEMORY_TAGS
#define CONFIG_INITRD_TAG
因此,会执行setup_commandline_tag
、setup_memory_tags
、setup_initrd_tag
(与ramdisk
相关,这里我们没有使用)。
setup_commandline_tag
代码如下:
static void setup_commandline_tag(bd_t *bd, char *commandline)
{
char *p;
if (!commandline)
return;
/* eat leading white space */
for (p = commandline; *p == ' '; p++);
/* skip non-existent command lines so the kernel will still
* use its default command line.
*/
if (*p == '\0')
return;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size =
(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline, p);
params = tag_next (params);
}
可以看出,这里调用了strcpy
来赋值字符串,赋值的字符串正是,函数开头使用getenv
获取的启动参数bootargs
字符串。
4.1.4 setup_memory_tags
setup_memory_tags
代码如下:
static void setup_memory_tags(bd_t *bd)
{
int i;
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { // 1
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size;
params = tag_next (params);
}
}
如果有多片内存ram
,会循环为每一片的ram
设置一个tag
。
4.1.5 setup_board_tags
setup_board_tags
,这个是板级实现,如果没有实现则跳过:
__weak void setup_board_tags(struct tag **in_params) {}
setup_end_tag
最后将最末尾的tag
设置为ATAG_NONE
,标志tag
结束:
static void setup_end_tag(bd_t *bd)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
由此可知我们的启动参数params
是一片连续的内存,这片内存有很多个tag
,我们通过调用不同的程序来设置这些tag
。
4.2 boot_jump_linux
然后执行BOOTM_STATE_OS_GO
里的代码,调用boot_jump_linux
函数,位于arch/arm/lib/bootm.c
。该函数会将tag
的首地址也就是gd->bd->bi_boot_params
传给内核,并跳转到内核地址、启动内核,让内核解析这些tag
。
/* Subcommand: GO */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
unsigned long machid = gd->bd->bi_arch_number; // 获取机器码
char *s;
void (*kernel_entry)(int zero, int arch, uint params); // 内核入口函数
unsigned long r2;
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
kernel_entry = (void (*)(int, int, uint))images->ep; // 指定为内核入口地址
s = getenv("machid");
if (s) {
if (strict_strtoul(s, 16, &machid) < 0) {
debug("strict_strtoul failed!\n");
return;
}
printf("Using machid 0x%lx from environment\n", machid);
}
debug("## Transferring control to Linux (at address %08lx)" \
"...\n", (ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup(fake);
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) // 使能设备树
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params;
if (!fake) {
kernel_entry(0, machid, r2);
}
}
主要流程:
- 获取
gd->bd->bi_arch_number
为machid
,如果有env
则用env
的machid
; - 设置内核入口地址
kernel_entry
,将images->ep
赋值给它作为跳转到内核执行的入口; r2
设置为为gd->bd->bi_boot_params
,也就是tag
启动参数的起始地址;kernel_entr
y传入其余相关参数并执行kernel_entry
启动;
关于machid
的值可以参考《内核解析U-boot
传入的machid
》,S3C2440
默认传入的是362
。
参考文章
[1] 七,移植linux-3.19
内核
[2] [uboot
] uboot
启动kernel
篇(二)——bootm
跳转到kernel
的流程
[4] S5PV210-uboot
解析(五)-do_bootm
函数分析
[5] Linux
内核镜像格式