分析“uboot加载内核过程”

uboot加载内核

目录

uboot加载内核... 1

1       总述... 2

2       osimage模块... 2

2.1             Flash的布局... 2

2.2             flash芯片... 2

2.3             访问flash. 3

2.4             module header信息... 3

2.5             扫描flash. 6

2.6             分析取得的FMH.. 7

2.6.1         root 模块... 7

2.6.2         osimage模块... 8

2.7             构造bootargs. 9

2.7.1         root设备... 9

2.7.2         串口... 9

2.7.3         root文件系统类型... 9

2.7.4         bigphysarea. 10

2.7.5         imagebooted. 10

3       do_bootm_states. 10

3.1             数据结构bootm_headers. 10

3.2             lmb. 12

3.2.1         数据结构... 13

3.2.2         memory地址范围... 14

3.2.3         reserved地址范围... 15

3.3             do_bootm_states. 23

3.3.1         bootm_find_os. 23

3.3.2         bootm_find_other1. 27

3.3.3         bootm_load_os. 28

3.3.4         reserve 内存... 29

3.3.5         找到boot_fn. 29

3.3.6         do_bootm_linux. 29

附录:... 31

OS Image的格式... 31

  1. 总述

扫描flash,找到osimage模块;

找到kernel_fit文件

找到kernel文件

找到设备树文件

拼凑出bootargs

加载进内存指定位置

开始执行内核

  1. osimage模块
    1. Flash的布局

---------------------------------------------------

             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的基本信息。

    1. flash芯片

AST2600通过Fire ware SPI Controller,连接到Flash。

一个Firmware SPI外接了两个SPI-Flash。通过片选信号BMC_FW_SPI_CS0, BMC_FW_SPI_CS1进行选择。

(图中的32MB应该是64MB)

    1. 访问flash

AST2600可以通过内存地址直接访问flash,Firmware SPI Memory Range在下图。可见,AST2600最大支持256M的flash。

比如osimage的位置在0x021A0040。不需要把这个flash位置的数据,读取到内存里面,通过0x021A0040就可以访问到flash里面的数据。

    1. module header信息

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 modulesize 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_fitsize*/

    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

    1. 扫描flash

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;

}

    1. 分析取得的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)

      1. root 模块

RootMtdNo = 2;

FSName = squashfs

这两个参数在构建bootargs时会用到:

Bootargs = [root=/dev/mtdblock2 ro ip=none console=ttyS4,115200 rootfstype=squashfs bigphysarea=6144 imagebooted=1]

      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阶段,不需要其他模块的信息。

    1. 构造bootargs
      1. root设备

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;

}

      1. 串口

strcat(bootargs,"console=");

strcat(bootargs,CONFIG_SPX_FEATURE_GLOBAL_CONSOLE_TTY);

strcat(bootargs,",");

strcat(bootargs,baudrate_str);

      1. root文件系统类型

strcat(bootargs," rootfstype=");

strcat(bootargs,FSName);

      1. bigphysarea

strcat(bootargs," bigphysarea=");

strcat(bootargs,CONFIG_BIGPHYSAREA);

      1. imagebooted

sprintf(imagebooted," imagebooted=%d",imagetoboot);

strcat(bootargs,imagebooted);

最后构造出来的bootargs是

root=/dev/mtdblock2 ro ip=none console=ttyS4,115200 rootfstype=squashfs \

bigphysarea=6144 imagebooted=1

  1. do_bootm_states

首先准备argv,调用do_bootm。就是传递参数启动地址。主要工作在do_bootm_states做。

argv[0] = bootm

argv[1] = 0x21a0040

argv[2] = -

argv[3] = 0xffffffff

argv[4] = <NULL>

    1. 数据结构bootm_headers

如下,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;

    1. lmb

Logical memory blocks,logical memory指的是内存地址,不是实际的内存。区分:

实际的内存              <>  logical;

physical 内存地址         <>  virtual 内存地址。

lmb里面保存的是物理地址,不是虚拟地址。两部分:

  1. memory:系统可以访问的内存地址范围;
  2. reserved:系统保留的、不可以被分配的内存地址范围。

lmb保存的信息,最终通过设备树传递给kernel。

      1. 数据结构

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

      1. 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);

}

NOTEgd的初始化,另行分析。

      1. reserved地址范围

把需要reserved的内存保存到lmb中。

  1. 内核栈空间

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;

    }

}

  1. 内核代码占用的空间

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);

}

  1. 设备树中的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;

        };        

     };

  1. 设备树中的memory reservation block

在memreserve中保留空间

// SPDX-License-Identifier: GPL-2.0-or-later

// Copyright 2019 IBM Corp.

/dts-v1/;

/*添加memreserve的方法:

  1. 添加的位置,在” /dts-v1/”后面;
  2. 语法格式

*/

/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的指针 */

}

/* 取得每一个entryaddresssize */

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;

}

  1. 为设备树保留空间

如下面的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;

}

    1. do_bootm_states

寻找kernel image

寻找设备树image

启动内核

      1. bootm_find_os

通过访问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";

                        };

                };

1bootm_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;

}

2boot_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);

}

4fit_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  bufsize

/* 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;

}

5fit_conf_get_node

根据kernel.its中的如下配置,寻找conf 节点的offset:

  1. 找到configurations节点;
  2. 在子节点里面寻找default property,取出它的值;
  3. 根据节点名字找到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;

}

6fit_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);  

    /* 根据dataalgo,计算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校验被跳过了。

      1.  bootm_find_other1

按照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的类似。

      1. bootm_load_os

加载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;

    }

}

      1. reserve 内存

(1)boot_fdt_add_mem_rsv_regions

把ArcherCity.dts里面描述的reserve memory,在lmb里reserve起来。

(2)boot_relocate_fdt

把设备树从flash里复制到内存中,并把内存在lmb里reserve起来。

      1. 找到boot_fn

去数组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];  

}

      1. do_bootm_linux

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;

}

1image_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;

}

2boot_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)。

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值