文章鏈接
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 內核的過程大致如下圖所示: