文章链接
UBoot 启动流程 (1) - 基本流程
UBoot 启动流程 (2) - 平台前期初始化阶段 - board_init_f
UBoot 启动流程 (3) - UBoot 程序重定位 - relocate_code
UBoot 启动流程 (4) - 平台后期初始化阶段 - board_init_r
UBoot 启动流程 (5) - UBoot 运行阶段 - main_loop
UBoot 启动流程 (6) - bootz 命令启动 Linux
目录
1.系统镜像相关的数据结构
1.1.镜像的头部信息 - image_header_t
1.2.镜像的基本信息 - image_info_t
1.3.镜像文件在 uboot 中的描述 - bootm_headers_t
1.3.1.镜像头部信息
1.3.2.系统信息
1.3.3.引导状态
1.3.4.线性内存管理器 - LMB
2.booz 命令启动内核的流程 - do_bootz()
2.1.根据阶段执行对应的函数 - do_bootm_states()
2.2.images 初始化 - bootz_start()
2.2.1.初始化 images 和 LMB - bootm_start()
2.2.2.设置加载地址
2.2.3.检查 zImage 是否有效 - bootz_setup()
2.2.4.在 LMB 中为镜像文件预留空间 - lmb_reserve()
2.2.5.查找 RAMDisk 和设备树 - bootm_find_images()
2.3.禁用中断 - bootm_disable_interrupts()
2.4.准备运行环境并启动内核
2.4.1.查找 OS 对应的启动函数 - bootm_os_get_boot_func()
2.4.2.Linux 启动函数 - do_bootm_linux()
2.4.3.Linux 运行环境准备 - boot_prep_linux()
2.4.4.跳转至 Linux 内核运行 - boot_jump_linux()
3.启动过程概览
1.系统镜像相关的数据结构
-
image_header_t 类型的结构体,保存系统镜像的头部信息,如 CRC 校验码、操作系统类型等;
-
image_info_t 类型的结构体,保存系统镜像的基本信息,如系统镜像的起始地址,加载地址等;
-
bootm_headers_t 类型的结构体,保存系统镜像的所有信息 (包括镜像头部信息、镜像基本信息等),以及平台中涉及系统加载的相关信息 (如系统镜像在内存中的起始地址,设备树的加载地址等);
1.1.镜像的头部信息 - image_header_t
image_header_t 保存系统镜像的头部信息:
// include/image.h
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;
其成员的含义如下 (其中 __be32 表示大端序 uint32 数据类型):
-
ih_magic:镜像头部的魔数,根据该值判断文件是否为镜像文件;
-
ih_hcrc:镜像头部的 CRC 校验码,判断文件是否有效 (是否出错);
-
ih_time:该镜像文件的创建时间;
-
ih_size:整个系统镜像文件的大小;
-
ih_load:系统在内存中的加载地址 (程序存储起始地址);
-
ih_ep:系统在内存中的运行地址 (程序运行起始地址);
-
ih_dcrc:整个系统镜像文件的 CRC 校验码;
-
ih_os:系统代码,例如,Linux 系统的 ih_os 值为 0x05:
// include/image.h
/** Operating System Codes*/
#define IH_OS_INVALID 0 /* Invalid OS */
#define IH_OS_OPENBSD 1 /* OpenBSD */
...
#define IH_OS_LINUX 5 /* Linux */
...
#define IH_OS_QNX 16 /* QNX */
...
#define IH_OS_OPENRTOS 24 /* OpenRTOS */
-
ih_arch:CPU 的架构代码,例如,ARM 架构 SoC 的 ih_arch 为 0x02:
// include/image.h
/** CPU Architecture Codes (supported by Linux)*/
#define IH_ARCH_INVALID 0 /* Invalid CPU */
...
#define IH_ARCH_ARM 2 /* ARM */
#define IH_ARCH_I386 3 /* Intel x86 */
...
#define IH_ARCH_MIPS 5 /* MIPS */
...
#define IH_ARCH_ARM64 22 /* ARM64 */
...
#define IH_ARCH_X86_64 24 /* AMD x86_64, Intel and Via */
-
ih_type:镜像类型,例如系统镜像、Boot 镜像等:
// include/image.h
/** Image Types** "Standalone Programs" are directly runnable in the environment* provided by U-Boot; it is expected that (if they behave* well) you can continue to work in U-Boot after return from* the Standalone Program.* "OS Kernel Images" are usually images of some Embedded OS which* will take over control completely. Usually these programs* will install their own set of exception handlers, device* drivers, set up the MMU, etc. - this means, that you cannot* expect to re-enter U-Boot except by resetting the CPU.* "RAMDisk Images" are more or less just data blocks, and their* parameters (address, size) are passed to an OS kernel that is* being started.* "Multi-File Images" contain several images, typically an OS* (Linux) kernel image and one or more data images like* RAMDisks. This construct is useful for instance when you want* to boot over the network using BOOTP etc., where the boot* server provides just a single image file, but you want to get* for instance an OS kernel and a RAMDisk image.** "Multi-File Images" start with a list of image sizes, each* image size (in bytes) specified by an "uint32_t" in network* byte order. This list is terminated by an "(uint32_t)0".* Immediately after the terminating 0 follow the images, one by* one, all aligned on "uint32_t" boundaries (size rounded up to* a multiple of 4 bytes - except for the last file).** "Firmware Images" are binary images containing firmware (like* U-Boot or FPGA images) which usually will be programmed to* flash memory.** "Script files" are command sequences that will be executed by* U-Boot's command interpreter; this feature is especially* useful when you configure U-Boot to use a real shell (hush)* as command interpreter (=> Shell Scripts).*/
#define IH_TYPE_INVALID 0 /* Invalid Image */
#define IH_TYPE_STANDALONE 1 /* Standalone Program */
#define IH_TYPE_KERNEL 2 /* OS Kernel Image */
...
#define IH_TYPE_RKIMAGE 23 /* Rockchip Boot Image */
#define IH_TYPE_RKSD 24 /* Rockchip SD card */
#define IH_TYPE_RKSPI 25 /* Rockchip SPI image */
#define IH_TYPE_ZYNQIMAGE 26 /* Xilinx Zynq Boot Image */
#define IH_TYPE_COUNT 27 /* Number of image types */
-
ih_comp:文件压缩类型:
// include/image.h
/** Compression Types*/
#define IH_COMP_NONE 0 /* No Compression Used */
#define IH_COMP_GZIP 1 /* gzip Compression Used */
#define IH_COMP_BZIP2 2 /* bzip2 Compression Used */
#define IH_COMP_LZMA 3 /* lzma Compression Used */
#define IH_COMP_LZO 4 /* lzo Compression Used */
#define IH_COMP_LZ4 5 /* lz4 Compression Used */
-
ih_name[IH_NMLEN]:镜像名称,名称最大长度为 32 个字符 (包含 31 个有效字符和 1 个结束符 \0):
// include/image.h
#define IH_NMLEN 32 /* Image Name Length */
1.2.镜像的基本信息 - image_info_t
image_info_t 类型的结构体中,包含了系统镜像的基本信息,如起始地址、所支持的 CPU 架构等:
// include/image.h
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;
其各个成员的含义如下:
-
start, end:镜像文件在存储介质中的起始地址和结束地址;
-
image_start, image_len:系统程序在文件中的起始地址和长度;
-
load:系统程序在内存中的加载地址 (程序的起始地址,如 0x80008000 等);
-
comp:系统镜像的压缩格式;
-
type:镜像类型,例如系统镜像、Boot 镜像等:
-
os:系统类型,如 Linux、Windows 等;
-
arch:系统对应的架构,如 ARM、MIPS 等;
1.3.镜像文件在 uboot 中的描述 - bootm_headers_t
uboot 中使用 bootm_headers_t 类型的变量 images 描述镜像文件 (如 zImage、uImage 等):
// include/image.h
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;
...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;
...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;
...struct lmb lmb; /* for memory mgmt */
...
} bootm_headers_t;
-----------------------------------------------------------------------------// cmd/bootm.c
bootm_headers_t images; /* pointers to os/initrd/fdt images */
1.3.1.镜像头部信息
-
*legacy_hdr_os 为镜像头部信息的指针;
-
legacy_hdr_os_copy 为镜像头部信息的副本;
-
legacy_hdr_valid 标记镜像头部信息是否有效;
// include/image.h
typedef struct bootm_headers {image_header_t *legacy_hdr_os; /* image header pointer */image_header_t legacy_hdr_os_copy; /* header copy */ulong legacy_hdr_valid;...
1.3.2.系统信息
-
os 为系统镜像的基本信息;
-
ep 为系统程序在镜像文件中的起始地址;
-
rd_start, rd_end 分别为虚拟磁盘 RAMDisk 在内存中的起始地址和结束地址;
-
*ft_addr 为设备树的起始地址;
-
ft_len 为设备树的长度;
-
initrd_start 为初始化 RAMDisk 的起始地址;
-
initrd_end 为初始化 RAMDisk 的结束地址;
-
cmdline_start 为 Linux 启动命令行的起始地址;
-
cmdline_end 为 Linux 启动命令行的结束地址;
-
*kbd 为开发板的硬件信息 bd 的指针;
-
verify 为校验使能标志,若环境变量 verify 不为 n 则启用校验;
// include/image.h
typedef struct bootm_headers {...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;
...int verify; /* getenv("verify")[0] != 'n' */
1.3.3.引导状态
-
BOOTM_STATE_xxx 宏定义内核的引导状态,即内核加载过程中的各个阶段;
-
state 为内核当前的引导状态;
// include/image.h
typedef struct bootm_headers {...
#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;
1.3.4.线性内存管理器 - LMB
-
lmb (Logical Memory Blocks) 为线性内存块管理器,用于动态内存分配:
-
memory 为可用的内存区域;
-
reserved 为预留内存区域,如设备映射内存、启动代码所占用的内存;
-
// include/lmb.h
struct lmb {struct lmb_region memory;struct lmb_region reserved;
};
-------------------------------------------// include/image.h
typedef struct bootm_headers {...struct lmb lmb; /* for memory mgmt */
2.booz 命令启动内核的流程 - do_bootz()
bootz 命令用于启动 Linux 内核,其对应的执行函数为 do_bootz(),包含如下几个阶段:
-
bootz_start() 初始化 images 结构体,设置系统的加载地址,并为系统镜像预留内存;
-
bootm_disable_interrupts() 在启动过程中禁用所有中断;
-
准备内核的执行环境并启动内核;
// cmd/bootm.c
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{...// 初始化 images 结构体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(); // 标记需要启动的系统类型为 Linuximages.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;
}
2.1.根据阶段执行对应的函数 - do_bootm_states()
内核的加载过程分为多个阶段,do_bootm_states() 根据阶段标志 states 调用对应的函数:
// common/bootm.c
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],int states, bootm_headers_t *images, int boot_progress)
{.../* 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));...}/* Relocate the ramdisk */// RAMDisk 重定位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 && (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);}// 查找内核对应的启动函数/* From now on, we need the OS boot function */...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) {...}// 执行内核对应的启动函数/* 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);...// 运行内核/* 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);|--> boot_fn(state, argc, argv, images);// 启用中断,执行复位/* 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;
}
2.2.images 初始化 - bootz_start()
uboot 使用 bootm_headers_t 类型的结构体 images 描述镜像文件,内核启动前,先调用 bootz_start() 初始化 images 结构体以及运行环境 (分配内存、查找设备树等):
// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 执行初始化阶段 BOOTM_STATE_STARTret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START, images, 1);/* Setup Linux kernel zImage entry point */// 设置 OS 在内存中的起始地址 0x80800000if (!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);...// 在内存中为镜像文件预留空间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.*/// 查找 RAMDisk、设备树等其他模块if (bootm_find_images(flag, argc, argv))return 1;...return 0;
}
2.2.1.初始化 images 和 LMB - bootm_start()
do_bootm_states() 调用 BOOTM_STATE_START 阶段对应的函数 bootm_start():
-
调用 memset() 清空 images 结构体;
-
boot_start_lmb() 初始化 LMB,并创建一个 dummy lmb,同时在内存中分配内存;
-
bootstage_mark_name() 记录当前阶段的相关信息;
// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 执行初始化阶段 BOOTM_STATE_STARTret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START, images, 1);
------------------------------------------------------------------------------- // common/bootm.c
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],int states, bootm_headers_t *images, int boot_progress)
{...if (states & BOOTM_STATE_START)ret = bootm_start(cmdtp, flag, argc, argv);
-------------------------------------------------------------------------------// common/bootm.c
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[])
{// 清空 images 结构体memset((void *)&images, 0, sizeof(images));// 检查环境变量 verify,确定是否需要对镜像文件进行校验images.verify = getenv_yesno("verify"); // 初始化 LMBboot_start_lmb(&images);|--> lmb_init(&images->lmb); // 将 LMB 的地址、大小等全部置 0,计数值初始化为 1|--> lmb_add(&images->lmb, (phys_addr_t)mem_start, mem_size); // 分配一块 lmb|--> arch_lmb_reserve(&images->lmb);|--> sp = get_sp(); // 获取当前的 SP 指针|--> sp -= 4096; // 4K 字节对齐|--> lmb_reserve(lmb, sp, ...); // 分配栈空间 |--> board_lmb_reserve(&images->lmb); // 未实现该方法// 记录当前阶段的相关信息bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");images.state = BOOTM_STATE_START;return 0;
}
2.2.2.设置加载地址
用户可以在命令行中指定 OS 的加载地址,若未指定则使用 load_addr 中保存的默认地址:
// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 设置 OS 在内存中的起始地址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);}
2.2.3.检查 zImage 是否有效 - bootz_setup()
-
使用 map_sysmem() 将镜像地址映射为可访问的指针 (获取镜像文件的头部信息);
-
检查魔数是否为 Linux 镜像文件;
-
打印镜像文件的起始地址和结束地址;
// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 检查镜像文件是否有效ret = bootz_setup(images->ep, &zi_start, &zi_end);
--------------------------------------------------------------// arch/arm/lib/bootm.c
int bootz_setup(ulong image, ulong *start, ulong *end)
{struct zimage_header *zi;// 检查镜像文件是否有效zi = (struct zimage_header *)map_sysmem(image, 0); // 获取镜像文件的头部信息if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) { // 检查是否为 Linux 镜像文件puts("Bad Linux ARM zImage magic!\n");return 1;}// 打印镜像文件在内存中的起始地址和结束地址*start = zi->zi_start;*end = zi->zi_end;printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n", image, *start, *end);return 0;
}
2.2.4.在 LMB 中为镜像文件预留空间 - lmb_reserve()
调用 lmb_reserve() 为镜像文件预留内存空间 (分配 LMB):
// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 在内存中为镜像文件预留空间lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
----------------------------------------------------------------// lib/lmb.c
long lmb_reserve(struct lmb *lmb, phys_addr_t base, phys_size_t size)
{struct lmb_region *_rgn = &(lmb->reserved);return lmb_add_region(_rgn, base, size); // 分配一个 LMB
}
2.2.5.查找 RAMDisk 和设备树 - bootm_find_images()
-
boot_get_ramdisk() 查找 RAMDisk (IMX 未使用);
-
boot_get_fdt() 查找设备树,并设置设备树文件的起始地址和长度;
// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 查找 RAMDisk、设备树等其他模块if (bootm_find_images(flag, argc, argv))return 1;
-----------------------------------------------------------// common/bootm.c
int bootm_find_images(int flag, int argc, char * const argv[])
{int ret;/* find ramdisk */ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,&images.rd_start, &images.rd_end);if (ret) {puts("Ramdisk image is corrupt or invalid\n");return 1;}#if defined(CONFIG_OF_LIBFDT)/* find flattened device tree */// 在镜像文件中查找设备树ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,&images.ft_addr, &images.ft_len);|--> ... // 在镜像文件中查找设备树|--> *of_flat_tree = fdt_blob; // 将设备树的地址保存至 images.ft_addr|--> *of_size = fdt_totalsize(fdt_blob); // 将设备树的长度保存至 images.ft_len...// 为设备树分配内存,并将设备树在内存中的起始地址,保存至环境变量 fdtaddr 中set_working_fdt_addr((ulong)images.ft_addr);|--> buf = map_sysmem(addr, 0);|--> setenv_hex("fdtaddr", addr);
#endif...return 0;
}
2.3.禁用中断 - bootm_disable_interrupts()
内核加载过程中需要禁用中断,同时禁用以太网和 USB:
// cmd/bootm.c
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{...// 内核启动过程中禁用中断bootm_disable_interrupts();
------------------------------------------------------------------------// common/bootm.c
ulong bootm_disable_interrupts(void)
{ulong iflag;/** 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();... // 禁用以太网和 USBreturn iflag;
}
------------------------------------------------------------------------// arch/arm/lib/interrupts.c
int disable_interrupts (void)
{unsigned long old,temp;__asm__ __volatile__("mrs %0, cpsr\n""orr %1, %0, #0xc0\n""msr cpsr_c, %1": "=r" (old), "=r" (temp):: "memory");return (old & 0x80) == 0;
}
2.4.准备运行环境并启动内核
分别调用 BOOTM_STATE_OS_PREP、BOOTM_STATE_OS_FAKE_GO、BOOTM_STATE_OS_GO 阶段对应的函数:
// cmd/bootm.c
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{...// 标记需要启动的系统类型为 Linuximages.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);
2.4.1.查找 OS 对应的启动函数 - bootm_os_get_boot_func()
不同操作系统所使用的启动函数不同,启动函数定义在 boot_os_fn 类型的结构体数组 boot_os 中,Linux 系统对应的启动函数为 do_bootm_linux():
// 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, // Linux 系统对应的启动函数
#endif
#ifdef CONFIG_BOOTM_NETBSD[IH_OS_NETBSD] = do_bootm_netbsd,
#endif
...
#ifdef CONFIG_BOOTM_OPENRTOS[IH_OS_OPENRTOS] = do_bootm_openrtos,
#endif
};
do_bootm_states() 启动内核时,会调用 bootm_os_get_boot_func() 查找系统对应的启动函数:
// common/bootm.c
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],int states, bootm_headers_t *images, int boot_progress)
{.../* From now on, we need the OS boot function */...boot_fn = bootm_os_get_boot_func(images->os.os); // 查找系统对应的启动函数|--> return boot_os[os]; // boot_fn = do_bootm_linux()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);.../* Call various other states that are not generally used */...// 执行 do_bootm_linux()if (!ret && (states & BOOTM_STATE_OS_PREP))ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
do_bootz() 启动 Linux 内核时,设置了 BOOTM_STATE_OS_PREP 标志,表示需要启动的系统类型为 Linux,因此会执行 do_bootm_linux()
2.4.2.Linux 启动函数 - do_bootm_linux()
检查启动阶段标志,并执行对应的系统环境准备函数 boot_prep_linux() 或运行函数 boot_jump_linux():
// arch/arm/lib/bootm.c
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;}// 运行 Linux 内核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;
}
2.4.3.Linux 运行环境准备 - boot_prep_linux()
处理环境变量 bootargs,其中保存了传递给 Linux 内核的启动参数:
// arch/arm/lib/bootm.c
static void boot_prep_linux(bootm_headers_t *images)
{// 从环境变量 bootargs 中读取 Linux 的启动命令char *commandline = getenv("bootargs");if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) { // 检查是否使用设备树debug("using: FDT\n");if (image_setup_linux(images)) { // 准备启动环境printf("FDT creation failed! hanging...");hang();}} else if (BOOTM_ENABLE_TAGS) {...} else {printf("FDT and ATAGS support not compiled in - hanging\n");hang();}
}
若配置使用设备树,则调用 image_setup_linux() 准备启动环境,包括调整设备树、分配存储 cmdline 的内存、处理 Ramdisk 以及设置启动参数:
-
将 LMB 中的内存区域添加至设备树;
-
在内核中为 cmdline 分配内存;
-
重定位 RAMDisk;
-
重定位设备树;
-
设置设备树中内存相关的节点;
// common/image.c
int image_setup_linux(bootm_headers_t *images)
{ulong of_size = images->ft_len; // 获取设备树的长度char **of_flat_tree = &images->ft_addr; // 获取设备树在镜像文件中的起始地址ulong *initrd_start = &images->initrd_start; // RAMDisk 的起始地址ulong *initrd_end = &images->initrd_end; // RAMDisk 的结束地址struct lmb *lmb = &images->lmb; // LMBulong rd_len;int ret;// 将 LMB 中预留的内存区域,添加到设备树的 /memory 节点和 /reserved-memory 节点,// 使内核可以知道需要预留这些区域if (IMAGE_ENABLE_OF_LIBFDT)boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);// 在内核中分配内存,用于存储 cmdlineif (IMAGE_BOOT_GET_CMDLINE) {ret = boot_get_cmdline(lmb, &images->cmdline_start, &images->cmdline_end);if (ret) {puts("ERROR with allocation of cmdline\n");return ret;}}// 将 RAMDisk 重定位至高地址的内存中,避免和内核文件冲突if (IMAGE_ENABLE_RAMDISK_HIGH) {rd_len = images->rd_end - images->rd_start;ret = boot_ramdisk_high(lmb, images->rd_start, rd_len,initrd_start, initrd_end);if (ret)return ret;}// 如果当前设备树的位置不合适,例如,与内核加载区域冲突,// 则使用 LMB 分配新的内存区域并复制设备树if (IMAGE_ENABLE_OF_LIBFDT) {ret = boot_relocate_fdt(lmb, of_flat_tree, &of_size);if (ret)return ret;}// 设置设备树中的 bootargs 节点,更新 /memory 节点的内存范围,添加预留内存的相关描述if (IMAGE_ENABLE_OF_LIBFDT && of_size) {ret = image_setup_libfdt(images, *of_flat_tree, of_size, lmb);if (ret)return ret;}return 0;
}
2.4.4.跳转至 Linux 内核运行 - boot_jump_linux()
查找 Linux 内核的运行函数 kernel_entry (Linux 系统的第一个函数,类似于 _start) 并执行
内核运行前,会调用 announce_and_cleanup() 打印启动信息并清除之前的缓存 (I-Cache 和 D-Cache):
// arch/arm/lib/bootm.c
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
#ifdef CONFIG_ARM64...
#elseunsigned long machid = gd->bd->bi_arch_number; // 开发板的硬件 ID (使用设备树时无效)char *s;void (*kernel_entry)(int zero, int arch, uint params);unsigned long r2;int fake = (flag & BOOTM_STATE_OS_FAKE_GO);// 运行 Linux 内核的入口kernel_entry = (void (*)(int, int, uint))images->ep;...// 输出调试信息,表示即将开始 Linux 内核的运行阶段debug("## Transferring control to Linux (at address %08lx)" \"...\n", (ulong) kernel_entry);// 记录当前的运行阶段bootstage_mark(BOOTSTAGE_ID_RUN_OS);// 打印启动信息:starting kernel ... 并初始化缓存 (清理掉原来的缓存)announce_and_cleanup(fake);|--> cleanup_before_linux(); // 清除 I-Cache 和 D-Cache// 获取设备树的地址if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)r2 = (unsigned long)images->ft_addr;elser2 = gd->bd->bi_boot_params;...// 运行内核kernel_entry(0, machid, r2);...
}
因为后续将要运行 Linux 内核,之前 uboot 所使用的数据缓存与指令缓存不再需要,调用 announce_and_cleanup() 将其清除,最后执行 kernel_entry() 运行 Linux 内核,其第二个参数 r2 为设备树的地址或者启动命令 bootargs
kernel_entry() 由操作系统定义,是其运行的第一个函数,类似 uboot 的 _start() 函数
3.启动过程概览
综上,uboot 启动 Linux 内核的过程大致如下图所示: