uboot加载内核
目录
扫描flash,找到osimage模块;
找到kernel_fit文件
找到kernel文件
找到设备树文件
拼凑出bootargs
加载进内存指定位置
开始执行内核
---------------------------------------------------
Flash Memory Map
---------------------------------------------------
0x00000000 - 0x00100000 : boot : Ver 13.00.000000
0x00100000 - 0x00300000 : conf : Ver 13.00.000000
0x00300000 - 0x021a0000 : root : Ver 13.00.000000
0x021a0000 - 0x025c0000 : osimage : Ver 13.00.000000
0x025c0000 - 0x02b40000 : www : Ver 13.00.000000
0x02b40000 - 0x03040000 : extlog : Ver 13.00.000000
0x03040000 - 0x03540000 : extlog : Ver 13.00.000000
0x03540000 - 0x03ff0000 : *******FREE*******
0x03ff0000 - 0x04000000 : archerci : Ver 13.00.29345005
osimage位于0x021A0000 ~ 0x025C0000。但是uboot并不知道这个map图。它需要逐个sector去扫描,找出module的基本信息。
-
- flash芯片
AST2600通过Fire ware SPI Controller,连接到Flash。
一个Firmware SPI外接了两个SPI-Flash。通过片选信号BMC_FW_SPI_CS0, BMC_FW_SPI_CS1进行选择。
(图中的32MB应该是64MB)
AST2600可以通过内存地址直接访问flash,Firmware SPI Memory Range在下图。可见,AST2600最大支持256M的flash。
比如osimage的位置在0x021A0040。不需要把这个flash位置的数据,读取到内存里面,通过0x021A0040就可以访问到flash里面的数据。
AMI的镜像rom.ima分成了几个module。 module以sector(1个sector = 0x1,0000(64K))为单位进行分配。一个64M的flash,共有1024个sector。AMI的做法是逐个扫描这些sector,寻找FMH。
//
其实也可以有别的做法,为rom.ima定义一个Table(由于uboot必须位于flash的开始处,所以这个Table,只能在其他位置,比如末尾。或者,uboot占用0x00000000 - 0x00100000,则把这个Table放在0x00100000处)。里面存放各个module的offset。这样就不需要通过扫描flash,寻找FMH了。这样可以节省开机时间。
//
module Header有固定的格式。以下红色的注释,以osimage为例。
/* Flash Module Header */
typedef struct
{
unsigned char FMH_Signature[8]; /* "$MODULE$" */
unsigned char FMH_Ver_Major; /* 1 */
unsigned char FMH_Ver_Minor; /* 8 */
unsigned short FMH_Size; /* 64 */
/* osimage module的size: 0x00420000*/
UINT32 FMH_AllocatedSize;
/* FMH 的位置, 0x021A0000*/
UINT32 FMH_Location;
unsigned char FMH_Reserved[3];
/* 计算FMH_Header_Checksum 前,先把FMH_Header_Checksum = 0x00*/
unsigned char FMH_Header_Checksum; /* Modulo 100 */
MODULE_INFO Module_Info;
unsigned short FMH_End_Signature; /* 0x55AA */
} __attribute__ ((packed)) FMH;
/*FMH 位于sector的开始处*/
/* Module Information Structure */
typedef struct
{
unsigned char Module_Name[8];
unsigned char Module_Ver_Major;
unsigned char Module_Ver_Minor;
unsigned short Module_Type; /* Refer Module Type Values Below */
/* 0x021A0040, 即FMH + 0x40*/
UINT32 Module_Location; /* Offset to Module from start */
/* Module_Size 即 kernel_fit的size*/
UINT32 Module_Size;
unsigned short Module_Flags; /* Refer Module Flags Values Below */
UINT32 Module_Load_Address;
/* 使用命令`crc32 kernel_fit` 可以求出Module_Checksum */
UINT32 Module_Checksum; /* CRC 32 */
unsigned char Module_Ver_Aux[6];
unsigned char Module_Reserved[2];
} __attribute__ ((packed)) MODULE_INFO;
/* boot module 使用了这个结构体。因为FMH没有放在sector的开始处。在sector的结尾处,放置了一个ALT_FMH ,在这里放置了真是的FMH的位置*/
/* Alternate FMH Location - At the end of first erase block */
typedef struct
{
unsigned short FMH_End_Signature; /* 0x55AA */
unsigned char FMH_Header_Checksum; /* Modulo 100 */
unsigned char FMH_Reserved;
UINT32 FMH_Link_Address; /* Actual FMH offset from erase blk start*/
unsigned char FMH_Signature[8]; /* "$MODULE$" */
} __attribute__ ((packed)) ALT_FMH;
以osimage的FMH为例:
FMH_Ver_Major = 1
FMH_Ver_Minor = 8
FMH_Size = 40 00 = 0x40 = 64byte
FMH_AllocatedSize = 00 00 42 00 = 0x00420000 = 4,325,376
FMH_Location = 00 00 1A 02 = 0x021A0000(即osimage在Flash中的offset)
Module_Info
Module_Name = 6F 73 69 6D 61 67 65 00 = “osimage\0”
Module_Ver_Major = 0D
Module_Ver_Minor = 00, Module_version = 13.0
Module_Type = 06 00 = 0x06 = MODULE_PIMAGE /* U-Boot Linux Image */
Module_Location = 40 00 1A 02 = 0x021A0040 (os-image的位置 = FMH + 0x40)
Module_Size = 64 EB 41 00 = 0x0041EB64 = 4,320,100(kernel_fit的size)
Module_Flags = 11 00 = 0x11
/* Values for Module Flags */
#define MODULE_FLAG_BOOTPATH_OS 0x0001
#define MODULE_FLAG_BOOTPATH_DIAG 0x0002
#define MODULE_FLAG_BOOTPATH_RECOVERY 0x0004
#define MODULE_FLAG_COPY_TO_RAM 0x0008
#define MODULE_FLAG_EXECUTE 0x0010
#define MODULE_FLAG_COMPRESSION_MASK 0x00E0 /* Refer Compression Type values */
#define MODULE_FLAG_COMPRESSION_LSHIFT 5
#define MODULE_FLAG_VALID_CHECKSUM 0x0100 /* Validate Checksum if this is set */
osimage是可以execute的。
Module_Load_Address = FF FF FF FF = 0xFFFFFFFF //FLASH_LOADSIZE_UNDEFINED
FMH_End_Signature = AA 55 = 0x55AA
uboot相关代码
函数ScanforFMH扫描flash,寻找FMH
FMH * ScanforFMH(struct spi_flash *flash,u32 SectorAddr, unsigned long SectorSize)
{
FMH *fmh;
ALT_FMH *altfmh;
unsigned long FMH_Offset;
int ret=0;
/*1 在sector的开头,寻找FMH*/
/* read FMH*/
ret = spi_flash_read(flash, SectorAddr, sizeof(FMH), (void *)&fmh_buffer);
/* Check if Normal FMH is found */
fmh = (FMH *)(&fmh_buffer);
fmh = CheckForNormalFMH(fmh);
if (fmh != NULL)
{
/*1.1 找到FMH*/
return fmh;
}
/*2 在sector的末尾,寻找ALT_FMH */
/* If Normal FMH is not found, check for alternate FMH */
ret = spi_flash_read(flash, SectorAddr+SectorSize - sizeof(ALT_FMH), sizeof(ALT_FMH), (void *)&altfmh_buffer);
altfmh = (ALT_FMH*)&altfmh_buffer;
FMH_Offset = CheckForAlternateFMH(altfmh);
/*2.1 没有找到ALT_FMH */
if (FMH_Offset == INVALID_FMH_OFFSET)
return NULL;
/*2.2 找到ALT_FMH */
ret = spi_flash_read(flash, FMH_Offset, sizeof(FMH), (void *)&fmh_buffer);
ret = spi_flash_read(flash, FMH_Offset, sizeof(FMH), (void *)&fmh_buffer);
fmh = (FMH*)&fmh_buffer;
/* If alternate FMH is found, validate it */
fmh = CheckForNormalFMH(fmh);
return fmh;
}
rom.ima共有六个module,分别是boot conf root osimage www extlog extlog。
boot和osimage不占据mtdpartition,其他module分别对应一个mtdpartition。
conf 1 jffs2
root 2 squashfs
www 3 squashfs
extlog 4 jffs2
extlog 5 jffs2
系统启动完成以后,挂载的信息如下:
/dev/mtdblock1 on /conf type jffs2 (rw,relatime)
/dev/root on / type squashfs (ro,relatime)
/dev/mtdblock3 on /usr/local/www type squashfs (ro,relatime)
/dev/mtdblock4 on /extlog type jffs2 (rw,relatime)
/dev/mtdblock5 on /bkupextlog type jffs2 (rw,relatime)
/dev/mtdblock6 on /bkupconf type jffs2 (rw,relatime)
-
-
- root 模块
-
RootMtdNo = 2;
FSName = squashfs
这两个参数在构建bootargs时会用到:
Bootargs = [root=/dev/mtdblock2 ro ip=none console=ttyS4,115200 rootfstype=squashfs bigphysarea=6144 imagebooted=1]
-
-
- osimage模块
-
ExecuteType = 0x6 //MODULE_PIMAGE
ExecuteAddr = 0x21A0040
这两个参数在下面启动os的时候,作为参数传递下去。
分析osimage
通过命令提取出osimage
dd if=rom.ima of=osimage skip=538 bs=64k count=66
解释:0x021a0000 - 0x025c0000, osimage开始与0x021a0000,结束于0x025c0000,以sector为单位,开始于0x21a(538),总共66个sector。
[ScanforFMH 189] SectorAddr = 0x21A0000, SectorSize = 0x10000
[CheckForNormalFMH 64] FMH_End_Signature = 0x55AA
[BootFMH 1343] Module_Name = osimage
[BootFMH 1344] Module_Location = 0x021A0040
[BootFMH 1345] Module_Size = 4320100
[BootFMH 1346] Module_Type = 6
[BootFMH 1347] Module_Load_Address = 0xFFFFFFFF
Module_Type = 6,MODULE_PIMAGE 0x06 /* U-Boot Linux Image*/
/* Values for Module Flags */
#define MODULE_FLAG_BOOTPATH_OS 0x0001
#define MODULE_FLAG_BOOTPATH_DIAG 0x0002
#define MODULE_FLAG_BOOTPATH_RECOVERY 0x0004
#define MODULE_FLAG_COPY_TO_RAM 0x0008
#define MODULE_FLAG_EXECUTE 0x0010
#define MODULE_FLAG_COMPRESSION_MASK 0x00E0 /* Refer Compression Type values */
#define MODULE_FLAG_COMPRESSION_LSHIFT 5
#define MODULE_FLAG_VALID_CHECKSUM 0x0100 /* Validate Checksum if this is set */
其他模块的信息,也扫描到了,但是没有使用。在uboot阶段,不需要其他模块的信息。
rootisinitrd = 0
int Get_bootargs(char *bootargs,int rootisinitrd,int RootMtdNo,int mmc)
{
if(rootisinitrd == 1)
{
sprintf(bootargs,"root=/dev/ram0 ro ip=none ramdisk_blocksize=4096 ");
}
else
{
sprintf(bootargs,"root=/dev/mtdblock%d ro ip=none ",RootMtdNo);
}
return 0;
}
strcat(bootargs,"console=");
strcat(bootargs,CONFIG_SPX_FEATURE_GLOBAL_CONSOLE_TTY);
strcat(bootargs,",");
strcat(bootargs,baudrate_str);
-
-
- root文件系统类型
-
strcat(bootargs," rootfstype=");
strcat(bootargs,FSName);
strcat(bootargs," bigphysarea=");
strcat(bootargs,CONFIG_BIGPHYSAREA);
sprintf(imagebooted," imagebooted=%d",imagetoboot);
strcat(bootargs,imagebooted);
最后构造出来的bootargs是
root=/dev/mtdblock2 ro ip=none console=ttyS4,115200 rootfstype=squashfs \
bigphysarea=6144 imagebooted=1
首先准备argv,调用do_bootm。就是传递参数启动地址。主要工作在do_bootm_states做。
argv[0] = bootm
argv[1] = 0x21a0040
argv[2] = -
argv[3] = 0xffffffff
argv[4] = <NULL>
如下,images作为一个全局变量,保存了启动OS需要的images(不是一个image,有内核和设备树两个image)的基本信息。所以在启动OS之前,需要初始化这个变量。
bootm_headers_t images; /* pointers to os/initrd/fdt images */
/*
* 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; /* env_get("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
/*保留的内存,稍后补充上共有几个需要保留的内存,<address, range>*/
struct lmb lmb; /* for memory mgmt */
#endif
} bootm_headers_t;
Logical memory blocks,logical memory指的是内存地址,不是实际的内存。区分:
实际的内存 <> logical;
physical 内存地址 <> virtual 内存地址。
lmb里面保存的是物理地址,不是虚拟地址。两部分:
- memory:系统可以访问的内存地址范围;
- reserved:系统保留的、不可以被分配的内存地址范围。
lmb保存的信息,最终通过设备树传递给kernel。
struct lmb_property {
phys_addr_t base;
phys_size_t size;
};
struct lmb_region {
unsigned long cnt; /* cnt—>数组region里面成员的个数*/
phys_size_t size; /* size->数组region总的内存size。实际上没有用,是0*/
struct lmb_property region[MAX_LMB_REGIONS+1]; /*变量名表意不清楚*/
};
struct lmb {
struct lmb_region memory;
struct lmb_region reserved;
};
调用lmb_dump_all打印出来的信息显示:
lmb_dump_all:
memory.cnt = 0x1
memory.size = 0x0
/* memory保存系统可用的地址范围*/
memory.reg[0x0].base = 0x80000000
.size = 0x3f000000 /*0x40000000 = 1G*/
/* reserved保存系统可用的地址范围,有四个region*/
reserved.cnt = 0x4
reserved.size = 0x0
reserved.reg[0x0].base = 0x80001000 /*os kernel*/
.size = 0x411d70
reserved.reg[0x1].base = 0x8fff0000 /*device tree*/
.size = 0xd000
reserved.reg[0x2].base = 0xbce00000 /*测试,dt中的reserved memory*/
.size = 0x100000
reserved.reg[0x3].base = 0xbcf484e8 /*内核栈*/
.size = 0x20b7b18
-
-
- memory地址范围
-
AST2600的内存地址分布:
如果是1G的内存,则范围是:
0x8000 0000 – 0xBFFF FFFF ( 0x4000 0000 = 1G)
如果是2G的内存,则范围是:
0x8000 0000 – 0xFFFF FFFF(0x8000 0000 = 2G)
把以上memory的信息保存到lmb中:
boot_start_lmb à lmb_init_and_reserve_range à lmb_add
/* 内存的基地址 ,是在头文件里面以宏的形式定义的。*/
ulong env_get_bootm_low(void)
{
return CONFIG_SYS_SDRAM_BASE;
}
#define CONFIG_SYS_SDRAM_BASE (ASPEED_DRAM_BASE + CONFIG_ASPEED_SSP_RERV_MEM)
#define ASPEED_DRAM_BASE 0x80000000
phys_size_t env_get_bootm_size(void)
{
phys_size_t tmp, size;
phys_addr_t start;
start = gd->bd->bi_dram[0].start; /*start = 0x80000000*/
size = gd->bd->bi_dram[0].size; /*size = 0x3F000000*/
tmp = start;
return size - (tmp - start);
}
NOTE:gd的初始化,另行分析。
-
-
- reserved地址范围
-
把需要reserved的内存保存到lmb中。
- 内核栈空间
boot_start_lmb à lmb_init_and_reserve_range à lmb_reserve_common \
à arch_lmb_reserveà lmb_reserve
/* 为linux保留内核栈空间,位于真个内存范围的最顶端。size = 4M*/
void arch_lmb_reserve(struct lmb *lmb)
{
ulong sp, bank_end;
int bank;
/*
* Booting a (Linux) kernel image
*
* Allocate space for command line and board info - the
* address should be as high as possible within the reach of
* the kernel (see CONFIG_SYS_BOOTMAPSZ settings), but in unused
* memory, which means far enough below the current stack
* pointer.
*/
sp = get_sp(); /* 0xBCF494D8 */
printf("## Current stack ends at 0x%08lx ", sp);
/* adjust sp by 4K to be safe */
sp -= 4096;
for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++)
{
if (!gd->bd->bi_dram[bank].size || sp < gd->bd->bi_dram[bank].start)
continue;
/* Watch out for RAM at end of address space! */
/*
* start = 0x80000000
* size = 0x3F000000 (~1G)
* bank_end = 0xBEFFFFFF
*/
bank_end = gd->bd->bi_dram[bank].start + gd->bd->bi_dram[bank].size - 1;
if (sp > bank_end)
continue;
/*stack 空间大概是4M */
lmb_reserve(lmb, sp, bank_end - sp + 1);
break;
}
}
- 内核代码占用的空间
bootm_load_os à lmb_reserve
static int bootm_load_os(bootm_headers_t *images, int boot_progress)
{
。。。。。。
/* start = 0x80001000 OS的入口地址*/
/* size = 4167K */
lmb_reserve(&images->lmb, images->os.load, image_len);
}
- 设备树中的reserved memory节点
因为在ArcherCity.dts中没有reserved memory,我添加了一段,如下:
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
gfx_memory: framebuffer {
size = <0x01000000>;
alignment = <0x01000000>;
reg = <0x806000000 0x100000>;
compatible = "shared-dma-pool";
reusable;
};
};
虽然在lmb对其进行了保存,但是内核启动时,出错了。如下,内核崩溃:
[ 5.661114] 8<--- cut here ---
[ 5.664538] Unable to handle kernel paging request at virtual address 80620000
[ 5.672594] pgd = 6b2cdc6a
[ 5.675608] [80620000] *pgd=8061940e(bad)
[ 5.680087] Internal error: Oops: 80d [#1] SMP ARM
[ 5.685421] Modules linked in:
[ 5.688831] CPU: 0 PID: 84 Comm: kworker/0:1H Not tainted 5.4.124-ami #4
[ 5.696304] Hardware name: Generic DT based system
[ 5.701650] Workqueue: kblockd blk_mq_run_work_fn
[ 5.706893] PC is at memcpy+0x50/0x330
[ 5.711072] LR is at 0x2a9214a5
[ 5.714571] pc : [<809e0ef0>] lr : [<2a9214a5>] psr: 20000113
[ 5.721559] sp : bb841acc ip : 426e6f70 fp : bb841b34
[ 5.727384] r10: 80ca9a98 r9 : 80620000 r8 : 855222ef
[ 5.733209] r7 : 820724cd r6 : 3241c32d r5 : 261220cc r4 : 3681c925
[ 5.740489] r3 : f67ed24e r2 : 00000160 r1 : bf91b020 r0 : 80620000
引申:既然保存了这段空间,为什么在内核里还会使用它?
调查:内核对lmb的使用?
换了一段空间,可以保存。
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
gfx_memory: framebuffer {
size = <0x01000000>;
alignment = <0x01000000>;
reg = <0xBCE00000 0x100000>;
compatible = "shared-dma-pool";
reusable;
};
};
- 设备树中的memory reservation block
在memreserve中保留空间
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright 2019 IBM Corp.
/dts-v1/;
/*添加memreserve的方法:
- 添加的位置,在” /dts-v1/”后面;
- 语法格式
*/
/memreserve/ 0xBCA00000 0x100000;
/memreserve/ 0xBCB00000 0x100000;
/memreserve/ 0xBCC00000 0x100000;
/memreserve/ 0xBCD00000 0x100000;
#include "projdef.h"
#include "aspeed-g6.dtsi"
#include <dt-bindings/gpio/aspeed-gpio.h>
#include <dt-bindings/i2c/i2c.h>
#include "../../../../../../include/projdef.h"
/ {
model = "AST2600 EVB";
compatible = "aspeed,ast2600";
lmb的结果:
lmb_dump_all:
memory.cnt = 0x1
memory.size = 0x0
memory.reg[0x0].base = 0x80000000
.size = 0x3f000000
reserved.cnt = 0x4
reserved.size = 0x0
reserved.reg[0x0].base = 0x80001000
.size = 0x411d70
reserved.reg[0x1].base = 0x8fff0000
.size = 0xd000
reserved.reg[0x2].base = 0xbca00000 /*lmb合并了连接在一起的region */
.size = 0x500000
reserved.reg[0x3].base = 0xbcf484e8
.size = 0x20b7b18
代码分析:
do_bootm_linux à boot_prep_linux à image_setup_linux à boot_fdt_add_mem_rsv_regions
void boot_fdt_add_mem_rsv_regions(struct lmb *lmb, void *fdt_blob)
{
/*(1) 处理memreserve section*/
/* process memreserve sections */
total = fdt_num_mem_rsv(fdt_blob);
for (i = 0; i < total; i++) {
if (fdt_get_mem_rsv(fdt_blob, i, &addr, &size) != 0)
continue;
/*保存到lmb*/
boot_fdt_reserve_region(lmb, addr, size);
}
/*(2) 处理reserved-memory 节点*/
/* process reserved-memory */
nodeoffset = fdt_subnode_offset(fdt_blob, 0, "reserved-memory");
if (nodeoffset >= 0) {
subnode = fdt_first_subnode(fdt_blob, nodeoffset);
while (subnode >= 0) {
/* check if this subnode has a reg property */
ret = fdt_get_resource(fdt_blob, subnode, "reg", 0, &res);
if (!ret) {
addr = res.start;
size = res.end - res.start + 1;
/*保存到lmb*/
boot_fdt_reserve_region(lmb, addr, size);
}
/* reserved-memory 节点下面可以有多个subnode*/
subnode = fdt_next_subnode(fdt_blob, subnode);
}
}
}
计算memory reservation block的方法:
如上图,memory reservation block的offset是固定的,每一个entry的结构也是固定的:
/* 通过累加,计算memory reservation的项数 */
int fdt_num_mem_rsv(const void *fdt)
{
int i = 0;
while (fdt64_to_cpu(fdt_mem_rsv_(fdt, i)->size) != 0)
i++;
return i;
}
static inline const struct fdt_reserve_entry *fdt_mem_rsv_(const void *fdt, int n)
{
const struct fdt_reserve_entry *rsv_table =
(const struct fdt_reserve_entry *)
((const char *)fdt + fdt_off_mem_rsvmap(fdt));//offset
return rsv_table + n; /* 指向entry的指针 */
}
/* 取得每一个entry的address和size */
int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size)
{
FDT_CHECK_HEADER(fdt);
*address = fdt64_to_cpu(fdt_mem_rsv_(fdt, n)->address);
*size = fdt64_to_cpu(fdt_mem_rsv_(fdt, n)->size);
return 0;
}
- 为设备树保留空间
如下面的kernel.its描述,dtb文件是kernel_fit中的一个image。
/dts-v1/;
/ {
description = "U-Boot fitImage for LTS13 SPX/5.4.85-ami/ast2600";
#address-cells = <1>;
images {
kernel@1 {
。。。。。。
};
fdt@ArcherCity.dtb {
description = "Flattened Device Tree blob";
data = /incbin/("arch/arm/boot/dts/ArcherCity.dtb");
type = "flat_dt";
arch = "arm";
compression = "none";
hash@1 {
algo = "sha256";
};
};
};
刚开始,ArcherCity.dtb位于0x025b1fa0(flash中)。后来relocate到了0x8fff0000(内存中),需要reserve这段内存。
do_bootm_linux à boot_prep_linux à image_setup_linux à image_setup_libfdt
int image_setup_linux(bootm_headers_t *images)
{
/* (1)把ArcherCity.dtb移动到内存中来 */
if (IMAGE_ENABLE_OF_LIBFDT) {
ret = boot_relocate_fdt(lmb, of_flat_tree, &of_size);
}
if (IMAGE_ENABLE_OF_LIBFDT && of_size) {
ret = image_setup_libfdt(images, *of_flat_tree, of_size, lmb);
}
return 0;
}
int image_setup_libfdt(bootm_headers_t *images, void *blob,
int of_size, struct lmb *lmb)
{
/* Delete the old LMB reservation */
if (lmb){
lmb_free(lmb, (phys_addr_t)(u32)(uintptr_t)blob,
(phys_size_t)fdt_totalsize(blob));
}
ret = fdt_shrink_to_minimum(blob, 0);
of_size = ret;
if (*initrd_start && *initrd_end) {
of_size += FDT_RAMDISK_OVERHEAD;
fdt_set_totalsize(blob, of_size);
}
/* Create a new LMB reservation */
if (lmb){
/* (2)把fdt占用的内存保留下来 */
/* base = 0x8fff0000 */
/* size = 0xd000 */
lmb_reserve(lmb, (ulong)blob, of_size);
}
return 0;
}
寻找kernel image
寻找设备树image
启动内核
通过访问kernel_fit,读取和kernel_image相关的信息:
images {
kernel@1 {
description = "Linux kernel";
data = /incbin/("arch/arm/boot/zImage");
type = "kernel";
arch = "arm";
os = "linux";
compression = "none";
load = <0x80001000>;
entry = <0x80001000>;
hash@1 {
algo = "sha256";
};
};
(1)bootm_find_os
do_bootm_states –> bootm_find_os
找到kernel的位置,以及其他属性。
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
(1)取得kernel 节点的位置信息
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv, &images, &images.os.image_start, &images.os.image_len);
(2)取得kernel 节点的property
/* get image parameters */
fit_image_get_type(images.fit_hdr_os, images.fit_noffset_os, &images.os.type); /* kernel */
fit_image_get_comp(images.fit_hdr_os, images.fit_noffset_os, &images.os.comp); /* none */
fit_image_get_os(images.fit_hdr_os, images.fit_noffset_os, &images.os.os); /* linux */
fit_image_get_arch(images.fit_hdr_os, images.fit_noffset_os, &images.os.arch); /* arm */
images.os.end = fit_get_end(images.fit_hdr_os);
fit_image_get_load(images.fit_hdr_os, images.fit_noffset_os, &images.os.load);
if (images.fit_uname_os) {
fit_image_get_entry(images.fit_hdr_os, images.fit_noffset_os, &images.ep);
}
images.os.start = map_to_sysmem(os_hdr);
return 0;
}
(2)boot_get_kernel
do_bootm_states –> bootm_find_os à boot_get_kernel
找到kernel节点的位置
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)
{
image_header_t *hdr;
ulong img_addr;
const void *buf;
const char *fit_uname_config = NULL;
const char *fit_uname_kernel = NULL;
int os_noffset;
/* argv[0] 是kernel_fit的 location*/
img_addr = genimg_get_kernel_addr_fit(argc < 1 ? NULL : argv[0],
&fit_uname_config,
&fit_uname_kernel);
/* 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)) {
case IMAGE_FORMAT_FIT:
fit_get_env_dtb(&fit_uname_config); /* NULL*/
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);
images->fit_hdr_os = map_sysmem(img_addr, 0); /*image位置*/
images->fit_uname_os = fit_uname_kernel;
images->fit_uname_cfg = fit_uname_config;
images->fit_noffset_os = os_noffset;
break;
}
printf(" kernel data at 0x%08lx, len = 0x%08lx (%ld)\n",
*os_data, *os_len, *os_len);
return buf;
}
(3)回顾argv的来源
在BOOTFMH()中,读取module信息以后:
ExecuteAddr = startaddress + le32_to_host(mod->Module_Location); /*0x021A0040*/
……
sprintf(AddrStr,"0x%lx",ExecuteAddr);
argv[1] = &AddrStr[0];
什么时候argv[1] 变成argv[0] 了呢?
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
/* determine if we have a sub command */
argc--; argv++; /* 删除了argv[0], 使得argv[1] 变成了argv[0]*/
return do_bootm_states(cmdtp, flag, argc, argv, ..., &images, 1);
}
(4)fit_image_load
kernel和dtb 都属于image,都是通过这个函数,找到在flash中的位置和大小。
/*根据image_type找到image节点*/
int fit_image_load(bootm_headers_t *images, ulong addr,
const char **fit_unamep, const char **fit_uname_configp,
int arch, int image_type, int bootstage_id,
enum fit_load_op load_op, ulong *datap, ulong *lenp)
{
(1)根据image type找到property name
/**
* IH_TYPE_FLATDT -- "fdt"
* FIT_KERNEL_PROP -- "kernel"
* /
prop_name = fit_get_image_type_property(image_type);
(2)在configurations中找到image的节点的名字
/* 找到conf节点的offset */
cfg_noffset = fit_conf_get_node(fit, fit_uname_config);
fit_base_uname_config = fdt_get_name(fit, cfg_noffset, NULL);
printf(" Using '%s' configuration\n", fit_base_uname_config);
/* Remember this config */
if (image_type == IH_TYPE_KERNEL)
images->fit_uname_cfg = fit_base_uname_config;
(3)根据image的名字找到节点的offset
/* fit_conf_get_prop_node()内部调用很多fdt的接口,
最终根据image的名字,找到了image节点的offset*/
noffset = fit_conf_get_prop_node(fit, cfg_noffset, prop_name);
fit_uname = fit_get_name(fit, noffset, NULL); /* kernel */
(4)哈希校验image
ret = fit_image_select(fit, noffset, images->verify);
(5)取得image节点的arch property
fit_image_get_arch(fit, noffset, &os_arch);
images->os.arch = os_arch;
(6)取得image 的buf和size
/* fit_image_get_data_and_size à fit_image_get_data()
根据节点的offset,定位到节点,根据”data”, 找到真正的数据 */
/* get image data address and length */
fit_image_get_data_and_size(fit, noffset, &buf, &size);
return noffset;
}
(5)fit_conf_get_node
根据kernel.its中的如下配置,寻找conf 节点的offset:
- 找到configurations节点;
- 在子节点里面寻找default property,取出它的值;
- 根据节点名字找到conf节点的offset。
configurations {
default = "conf@ArcherCity.dtb";
conf@ArcherCity.dtb {
description = "1 Linux kernel, FDT blob";
kernel = "kernel@1";
fdt = "fdt@ArcherCity.dtb";
hash@1 {
algo = "sha256";
};
};
};
int fit_conf_get_node(const void *fit, const char *conf_uname)
{
confs_noffset = fdt_path_offset(fit, FIT_CONFS_PATH); /* "/configurations" */
/*default = "conf@ArcherCity.dtb"*/
if (conf_uname == NULL) {
/* get configuration unit name from the default property */
debug("No configuration specified, trying default...\n");
conf_uname = (char *)fdt_getprop(fit, confs_noffset,
FIT_DEFAULT_PROP, &len);
}
noffset = fdt_subnode_offset(fit, confs_noffset, conf_uname);
return noffset;
}
(6)fit_image_verify
fit_image_select à fit_image_verify à fit_image_check_hash
NOTE: 跳过和signature相关的代码,kernel.its没有使用signature。
static int fit_image_check_hash(const void *fit, int noffset, const void *data,
size_t size, char **err_msgp)
{
/* 在hash节点下,找出property “algo”
* algo = sha256 */
fit_image_hash_get_algo(fit, noffset, &algo);
/* 查看在hash下面是否有 uboot-ignore属性,如果有,且不为0,则跳过校验 */
/* 这一步,应该放在第一步,如果跳过,就不需要查找algo了。*/
if (IMAGE_ENABLE_IGNORE) {
fit_image_hash_get_ignore(fit, noffset, &ignore);
if (ignore) {
printf("-skipped ");
return 0;
}
}
/* 取得hash节点下的value 属性 */
fit_image_hash_get_value(fit, noffset, &fit_value, &fit_value_len);
/* 根据data,algo,计算hash值 */
calculate_hash(data, size, algo, value, &value_len);
/* 比较新旧hash值 */
if (value_len != fit_value_len) {
*err_msgp = "Bad hash value len";
return -1;
} else if (memcmp(value, fit_value, value_len) != 0) {
*err_msgp = "Bad hash value";
return -1;
}
return 0;
}
(一)数据校验
比较数据是否一致,使用hash算法:
root@ubuntu:/home/yinming/bmc/svn138/work/Build/tftpboot# shasum -a 256 zImage
780bdc79ec5a4816ffafe315e517325a68b0f0a95b5d0502901207c17c9e3bfa zImage
在kernel_fit中
hash@1 {
value = <0x780bdc79 0xec5a4816 0xffafe315 0xe517325a 0x68b0f0a9 0x5b5d0502 0x901207c1 0x7c9e3bfa>;
algo = "sha256";
};
由此可以判断kernl_fit中装的确实是zImage。
在kernel.its的hash节点下面没有value属性,但是生成的kernel_fit有value属性。value是在编译内核的时候生成,插进去的。
hash@1 {
algo = "sha256";
};
(二)uboot-ignore属性
在kernel.its中,增加uboot-ignore
在uboot代码fit_image_hash_get_ignore,查看其value。
static int fit_image_hash_get_ignore(const void *fit, int noffset, int *ignore)
{
int len;
int *value;
value = (int *)fdt_getprop(fit, noffset, FIT_IGNORE_PROP, &len);
if (value == NULL || len != sizeof(int))
{
printf("[%s %d] \n", __FUNCTION__, __LINE__);
*ignore = 0;
}
else
{
printf("[%s %d] value = %d\n", __FUNCTION__, __LINE__, fdt32_to_cpu(*value));
*ignore = *value;
}
return 0;
}
uboot启动过程中,显示” -skipped + OK”,hash校验被跳过了。
按照kernel.its的描述取得ArcherCity.dtb
fdt@ArcherCity.dtb {
description = "Flattened Device Tree blob";
data = /incbin/("arch/arm/boot/dts/ArcherCity.dtb");
type = "flat_dt";
arch = "arm";
compression = "none";
hash@1 {
algo = "sha256";
};
};
do_bootm_states àbootm_find_other1 àbootm_find_images1 àboot_get_fdt à
boot_get_fdt_fit à fit_image_load(IH_TYPE_FLATDT)
设备树节点的读取,与kernel的类似。
加载kernel到内存
do_bootm_states à bootm_load_os
static int bootm_load_os(bootm_headers_t *images, int boot_progress)
{
/*(1)解压image到内存中*/
err = bootm_decomp_image(os.comp, load, os.image_start, os.type,
load_buf, image_buf, image_len,
CONFIG_SYS_BOOTM_LEN, &load_end);
/*(2)保留image占用的内存*/
lmb_reserve(&images->lmb, images->os.load, (load_end -
images->os.load));
return 0;
}
bootm_load_os –> bootm_decomp_image
把kernel image从flash移动到内存。其实是复制。
int bootm_decomp_image(int comp, ulong load, ulong image_start, int type,
void *load_buf, void *image_buf, ulong image_len,
uint unc_len, ulong *load_end)
{
/*
* Load the image to the right place, decompressing if needed. After
* this, image_len will be set to the number of uncompressed bytes
* loaded, ret will be non-zero on error.
*/
switch (comp) {
case IH_COMP_NONE:
/* load_buf = 80001000, image_buf = 021a0124 */
memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);/*没有压缩,只需搬移*/
break;
}
}
-
-
- reserve 内存
-
(1)boot_fdt_add_mem_rsv_regions
把ArcherCity.dts里面描述的reserve memory,在lmb里reserve起来。
(2)boot_relocate_fdt
把设备树从flash里复制到内存中,并把内存在lmb里reserve起来。
去数组boot_os里面找到linux的启动函数,类似于火箭发射卫星。
static boot_os_fn *boot_os[] = {
[IH_OS_U_BOOT] = do_bootm_standalone,
[IH_OS_LINUX] = do_bootm_linux,
......
};
boot_os_fn *bootm_os_get_boot_func(int os)
{
/* boot_os = 0xBEF54B20 */
return boot_os[os];
}
Linux启动函数的Main Entry point
int do_bootm_linux(int flag, int argc, char * const argv[],
bootm_headers_t *images)
{
boot_prep_linux(images);
boot_jump_linux(images, flag);
return 0;
}
(1)image_setup_linux
boot_prep_linux à image_setup_linux
int image_setup_linux(bootm_headers_t *images)
{
boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);
ret = boot_relocate_fdt(lmb, of_flat_tree, &of_size);
ret = image_setup_libfdt(images, *of_flat_tree, of_size, lmb);
return 0;
}
(2)boot_jump_linux
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
kernel_entry = (void (*)(int, int, uint))images->ep;
/*r2 保存设备树的地址*/
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params;
kernel_entry(0, machid, r2);
}
uboot完成以后的内存布局:
before using DTB | using DTB |
附录:
OS Image的格式
而kernel_fit 是一种使用设备树结构体表示的内核(源文件kernel.its,二进制文件kernel_fit)。它集成了内核使用的设备树(源文件ArcherCity.dts,二进制文件ArcherCity.dtb)。