bootrom/spl/uboot/linux逐級加載是如何實現的?

關鍵詞:bootrom、spl、uboot、linux、mksheader、sb_header、mkimage、image_header_t等等。node

 

首先看一個典型的bootrom->spl->uboot->linux流程log,主要分爲4個部分,中間有3個銜接點。linux

# Hello DeepEye
-- Boot from SD card --
sdio initialize done.
sd card read done.
--------------------------------------------------------------------------->bootrom-spl分割線,以上是bootrom輸出內容,從存儲介質中讀取spl到片內RAM中,並判斷magic number。
U-Boot SPL 2016.07-00058-g6c3df97-dirty (Aug 03 2019 - 10:57:57)
Boot reason: >(Normal).
ddr4 2400 4GB init...
...
Welcome to SPL!Load U-Boot from SD ...
---------------------------------------------------------------------------->spl-uboot分割線,以上是spl運行階段,主要進行pll、時鐘、串口等初始化;最主要的是初始化了DDR以及從存儲介質中將uboot加載到DDR中。
U-Boot 2016.07 (Jul 24 2019 - 20:13:09 +0800)DeepEye1000

DRAM:  3.9 GiB
MMC:   deepeye_sdhci: 0, deepeye_sdhci: 1
Using default environment
...
## Booting kernel from Legacy Image at 86000000 ...
   Image Name:   Linux-4.9.56
   Image Type:   Sandbox Linux Kernel Image (gzip compressed)
   Data Size:    4921279 Bytes = 4.7 MiB
   Load Address: 80000000
   Entry Point:  80000000
   Verifying Checksum ... OK
   Uncompressing Kernel Image ... OK
dtb_load_addr: 0x8F000000

Starting kernel ... 
---------------------------------------------------------------------------->uboot-linux分割線,uboot相較於spl功能更加豐富。提供了豐富的命令,能夠操做文件系統、腳本、加載不一樣操做系統等等。
[    0.000000] Linux version 4.9.56 (al@al-B250-HD3) (gcc version 6.3.0 (C-SKY Tools V3.8.10-kstq-nd-r2 Glibc-2.9
[    0.000000] C-SKY: https://github.com/c-sky/csky-linux
[    0.000000] Phys. mem: 4032MB
...
[    4.382887] Freeing unused kernel memory: 276k freed
[    4.387879] This architecture does not have kernel memory protection.
[    4.702756] EXT4-fs (mmcblk1p2): re-mounted. Opts: nodelalloc,data=journal
Starting mdev...
Starting network: OK
...
----------------------------------------------------------------------------->linux包括kernel和rootfs,內核中初始化了外設、操做系統組件、掛載了文件系統,並調用init初始化用戶空間環境。

下面主要分析不一樣階段之間如何銜接。git

1. 各階段主要做用 

bootrom是固化在芯片內部的一塊rom,初始化各類接口,並從中讀取內容加載到片內SRAM中。由於存儲設備接口相對簡單,大部分不須要適配便可存取。可是DDR等須要修改代碼進行適配。github

因此就須要spl,spl被加載到片內SRAM中,片內SRAM不須要初始化便可運行,可是容量有限。spl運行起來後進行必要的初始化後,初始化DDR,並將uboot從存儲設備中讀到DDR中。算法

uboot運行在DDR中,則不受空間大小限制,能夠進行復雜的操做。支持包括不一樣文件系統、腳本執行、多種操做系統加載等等操做。其中主要的工做是從存儲設備中讀取kernel,解析後跳轉到kernel執行。bash

linux這裏包括kernel和rootfs兩部分,kernel進行系統組件初始化、設備初始化,在一切準備就緒後,調用第一個用戶空間程序init進行用戶空間初始化。數據結構

2. bootrom加載spl分析

bootrom從不一樣存儲介質中讀取spl,這些存儲介質多是SD、eMMC或者USB接口、串口等等。架構

從讀取內容中解析spl的sb_header,驗證magic_num、肯定spl大小、加載到load_addr、進行crc校驗結果crc16對比。最後跳轉到entry_point進行spl運行。less

下面瞭解一下sb_header結構體?以及sb_header是如何生成的?最後bootrom中是經過如何處理sb_header加載spl的?函數

2.1 sb_header數據結構

sb_header是bootrom和spl協調一致的數據結構,spl在頭部包含此部分數據,bootrom在運行的時候解析它。

#define MAGIC_NUM   0x44454550

typedef struct second_boot_header{
    unsigned int magic_num;----------------------------二者約定的魔數0x44454550。
    unsigned int data_size;----------------------------去掉頭部的spl大小。
    unsigned int load_addr;----------------------------從頭部開始的地址。
    unsigned int entry_point;--------------------------去掉頭部開始的地址。
    unsigned int crc16;--------------------------------不包括sb_header的crc校驗結果。
}sb_header;

2.2 從u-boot-spl->u-boot-spl.bin->u-boot-spl-bh.bin流程

生成u-boot-spl以及u-boot-spl.map文件:

cmd_spl/u-boot-spl := (cd spl && csky-abiv2-linux-ld.bfd   -EL  -T u-boot-spl.lds  --gc-sections -Bstatic --gc-sections -Ttext 0xfc000000 arch/csky/cpu/ck807_810/start.o --start-group arch/csky/cpu/built-in.o arch/csky/cpu/ck807_810/built-in.o arch/csky/lib/built-in.o board/csky/deepeye1000/built-in.o board/csky/common/built-in.o common/spl/built-in.o common/init/built-in.o common/built-in.o cmd/built-in.o drivers/built-in.o dts/built-in.o fs/built-in.o lib/built-in.o --end-group -L /home/al/csky_toolchain/gcc_v3.8.10-kstq-nd-r2/opt/ext-toolchain/bin/../lib/gcc/csky-linux-gnuabiv2/6.3.0/hard-fp -lgcc -Map u-boot-spl.map -o u-boot-spl)

其中-T u-boot-spl.lds表示從u-boot-spl.lds中讀取連接腳本;-Ttext 0xfc000000表示從.text段起始地址爲0xfc000000,而且start.o是spl的起始;--start-group和--end-group表示一個group的起始和結束標誌,中間是group的內容;-Map u-boot-spl.map表示輸出map文件到u-boot-spl.map;-o u-boot-spl表示輸出可執行文件到u-boot-spl。

經過u-boot-spl生成u-boot-spl-nodtb.bin文件:

cmd_spl/u-boot-spl-nodtb.bin := csky-abiv2-linux-objcopy  -j .text -j .rodata -j .data -j .u_boot_list -j .dtb.init.rodata  -O binary spl/u-boot-spl spl/u-boot-spl-nodtb.bin

-j表示將要copy的section名稱,-O表示輸出文件格式,這個命令將u-boot-spl中特定section以binary格式輸出到u-boot-spl-nodtb.bin中。

u-boot-spl.bin和u-boot-spl.nodtb.bin是一樣文件:

cmd_spl/u-boot-spl.bin := cp spl/u-boot-spl-nodtb.bin spl/u-boot-spl.bin

從u-boot-spl.bin到u-boot-spl-bh.bin主要是mksheader給u-boot-spl.bin加了個sb_header頭。

spl/u-boot-spl.bin: spl/u-boot-spl
    @:
    tools/mksheader 0xfc000000 0xfc000180 spl/u-boot-spl.bin spl/u-boot-spl-bh.bin chmod +x spl/u-boot-spl-bh.bin

2.2.1 mksheader給spl加sb_header頭

給spl加sb_header頭這個工做是由mksheader來作的,決定了load_addr和entry_point,而後根據結果填充了data_size和crc16。

mksheader讀取u-boot-spl.bin文件,加上sb_header頭以後,生成新的u-boot-spl-bh.bin文件。下面簡單看看mksheader這個工具是如何給spl添加sb_header頭。

int main(int argc, char *argv[])
{
    unsigned short crc_data;
    FILE *file_in = NULL;
    FILE *file_out = NULL;
    int len;
    unsigned char *file_buff = NULL;
    sb_header header_data;
    unsigned int entry_point;
    unsigned int load_addr;

    if (argc < 5) {
        printf("Please input like this: %s load_addr entry_addr file_input file_output\n", argv[0]);
        exit(1);
    }
    load_addr = strtoul(argv[1], 0, 0);
    entry_point = strtoul(argv[2], 0, 0);-------------------分別指定load_addr和entry_point。 
    len = get_file_size(argv[3]);---------------------------獲取輸入文件的大小,指爲data_size。
    file_buff = malloc(len);
    if (!file_buff) {
        perror("open file failed:\n");
        exit(1);
    }

    file_in = fopen(argv[3], "rb+");
    if(!file_in) {
        perror("open file_in failed:\n");
        exit(1);
    }

    file_out = fopen(argv[4], "wb+");
    if(!file_out) {
        perror("open file_out failed:\n");
        exit(1);
    }

    if (fread(file_buff, 1, len, file_in) != 1) {-----------將u-boot-spl.bin文件讀到file_buff中。
        ;
        // perror("read input file failed.\n");
        // exit(1);
    }

    crc_data = check_sum(file_buff, len);-------------------對u-boot-spl.bin文件進行crc校驗,結果寫到crc16中。 // printf("crc_data=0x%x, len=0x%x load_addr=0x%x entry_point=0x%x\n",
        // crc_data, len, load_addr ,entry_point);

    header_data.magic_num = 0x44454550;---------------------寫入magic_num。
    header_data.data_size = len;
    header_data.entry_point = entry_point;
    header_data.load_addr = load_addr;
    header_data.crc16 = crc_data;

    fwrite(&header_data, 1, sizeof(header_data), file_out);
    fwrite(file_buff, 1, len, file_out);--------------------分別將sb_header和u-boot-spl.bin寫入到u-boot-spl-bh.bin中。

    fclose(file_in);
    fclose(file_out);
    return 0;
}

下面經過BeyondCompare對比u-boot-spl.bin和u-boot-spl-bh.bin的差別:

二者相差只有u-boot-spl-bh.bin多了20字節的頭,分別是magic_num(0x44454550)、data_size(0x0000a924)、load_addr(0xfc000000)、entry_point(0xfc000180)、crc16(0x00001e48)。

2.3 bootrom解析sb_header頭

下面以sd爲例介紹bootrom是如何經過解析sb_header來加載spl的。

int sd_card_boot(void)
{
    int i;
    sb_header header;
    u8* p_header = &header;
    u32 buffer_addr = 0,block_cnt=0,col=0;
    u16 crc16 = 0;
    LOAD_ENTRY enter_jump_func;
    
    char buffer_r[MMC_MAX_BLOCK_LEN];
    memset(buffer_r,0x00,MMC_MAX_BLOCK_LEN);-------------------------------------分配一個block大小buffer_r,即512字節。 if (mmc_bread(&sd_card_mmc,SD_CARD_BOOT_ADDR, 1, (u32)buffer_r) != 1)---------在SD的第66個sector,即33KB處讀取一個block。
    {
        debug("mmc_bread failed.\n");
        return -BOOT_FAILED;
    }
    memcpy(p_header,buffer_r,sizeof(header));-------------------------------------取buffer_r的sb_header大小內容到p_header中。
    sdio_debug("magic=0x%x size=0x%x load_addr=0x%x entry_addr=0x%x crc16=0x%x \n",\
        header.magic_num,header.data_size,header.load_addr,header.entry_point,header.crc16);

    if(header.magic_num != MAGIC_NUM)----------------------------------------------檢查magic_num是否正確。
    {
        debug("magic_num error.\n");
        return -BOOT_FAILED;//boot failed
    }
    //get data len, may be need check len (max) error return -1
    if(header.data_size > SPL_MAX_LEN)
    {
        debug("data_size error.\n");
        return -BOOT_FAILED;//boot failed
    }
    //check load address
    if(header.load_addr < SRAM_START_ADDRESS)
    {
        debug("load_addr error.\n");
        return -BOOT_FAILED;//boot failed
    }
    if((header.load_addr+header.data_size)>SPL_MAX_ADDRESS)
    {
        debug("the data is out of bounds.\n");
        return -BOOT_FAILED;//boot failed
    }

    buffer_addr = header.load_addr;--------------------------------------------將整個u-boot-spl.bin加載到的地址。
    block_cnt = (header.data_size+sizeof(header)) / sd_card_mmc.read_bl_len;---須要讀取總block數目。
    col = (header.data_size+sizeof(header))%sd_card_mmc.read_bl_len;

    for(i=0;i<block_cnt;i++)
    {
        if (mmc_bread(&sd_card_mmc,SD_CARD_BOOT_ADDR+i, 1, (u32)buffer_addr) != 1)
        {
            debug("mmc_bread failed.\n");
            return -BOOT_FAILED;
        }
        if(i == 0)
        {
            memcpy((void*)buffer_addr,(void*)(buffer_r+sizeof(header)),(sd_card_mmc.read_bl_len-sizeof(header)));---這裏注意在加載的時候已經將sb_header內容剔除,因此SRAM中load_addr地址不包含sb_header。
            buffer_addr += (sd_card_mmc.read_bl_len-sizeof(header));
        }
        else
        {
            buffer_addr += sd_card_mmc.read_bl_len;
        }    
    }
    if(col)
    {
        if (mmc_bread(&sd_card_mmc,SD_CARD_BOOT_ADDR+block_cnt, 1, (u32)buffer_r) != 1)
        {
            debug("mmc_bread failed.\n");
            return -BOOT_FAILED;
        }
        memcpy((void*)buffer_addr,(void*)buffer_r,col);

    }
    info_debug("sd card read done.\n");

    crc16 = check_sum((u8*)header.load_addr, header.data_size);
    if(crc16 != (u16)header.crc16)----------------------------------------------進行crc校驗並比較,校驗的內容是u-boot-spl.bin而不是u-boot-spl-bh.bin。
    {
        debug("checksum error.\n");
        return -BOOT_FAILED;//boot failed
    }

    enter_jump_func = (LOAD_ENTRY)header.entry_point;---------------------------spl的執行地址。
    enter_jump_func();----------------------------------------------------------將控制權交給spl。 return 0;
}

bootrom中首先讀取spl的sb_header,進行magic_num檢查,以及一些地址範圍檢查;而後根據load_addr和data_size將u-boot-spl.bin加載到SRAM中;在對u-boot-spl.bin進行crc校驗後,跳轉到entry_point進行執行。

遺留問題:爲何entry_point和load_addr相差0x00000180。

經查跟start.S彙編中的_start入口函數的偏移有關,在_start()以前有0x180字節的異常handler。

這是跟平臺相關的,好比不少平臺load_addr和entry_point就是相等的。

3. spl加載uboot分析

3.1 重要數據結構

struct spl_image_info是spl加載uboot.bin所須要的信息,其全局變量爲spl_image。

struct spl_image_info {
    const char *name;
    u8 os;------------------表示類型,爲uboot。
    u32 load_addr;----------uboot.bin加載到DDR中的地址。
    u32 entry_point;--------從spl跳轉到uboot的入口地址。
    u32 size;---------------uboot大小。
    u32 flags;
};

3.2 mkimage給uboot加image_header_t頭

mkimage給u-boot.bin加image_header_t後變成u-boot.img。

MKIMAGEFLAGS_u-boot.img = -A $(ARCH) -T firmware -C none -O u-boot \
    -a $(CONFIG_SYS_TEXT_BASE) -e $(CONFIG_SYS_UBOOT_START) \
    -n "U-Boot $(UBOOTRELEASE) for $(BOARD) board"

quiet_cmd_mkimage = MKIMAGE $@
cmd_mkimage = $(objtree)/tools/mkimage $(MKIMAGEFLAGS_$(@F)) -d $< $@ \
    $(if $(KBUILD_VERBOSE:1=), >/dev/null)

uboot的編譯從u-boot->u-boot-nodtb.bin->u-boot.bin->u-boot.img,經歷的過程以下:

-Ttext 0x17a00000表示.text段其實地址爲0x17a00000;-o u-boot表示可執行輸出文件爲u-boot;-T u-boot.lds表示從u-boot.lds中讀取連接腳本;程序從start.o中起始;map文件輸出到u-boot.map中。

cmd_u-boot := csky-abiv2-linux-ld.bfd   -EL   --gc-sections -Bstatic -Ttext 0x17a00000 -o u-boot -T u-boot.lds arch/csky/cpu/ck807_810/start.o --start-group  arch/csky/cpu/built-in.o  arch/csky/cpu/ck807_810/built-in.o  arch/csky/lib/built-in.o  board/csky/common/built-in.o  board/csky/deepeye1000/built-in.o  cmd/built-in.o  common/built-in.o  disk/built-in.o  drivers/built-in.o  drivers/dma/built-in.o  drivers/gpio/built-in.o  drivers/i2c/built-in.o  drivers/mmc/built-in.o  drivers/mtd/built-in.o  drivers/mtd/onenand/built-in.o  drivers/mtd/spi/built-in.o  drivers/net/built-in.o  drivers/net/phy/built-in.o  drivers/pci/built-in.o  drivers/power/built-in.o  drivers/power/battery/built-in.o  drivers/power/fuel_gauge/built-in.o  drivers/power/mfd/built-in.o  drivers/power/pmic/built-in.o  drivers/power/regulator/built-in.o  drivers/serial/built-in.o  drivers/spi/built-in.o  drivers/usb/common/built-in.o  drivers/usb/dwc3/built-in.o  drivers/usb/emul/built-in.o  drivers/usb/eth/built-in.o  drivers/usb/gadget/built-in.o  drivers/usb/gadget/udc/built-in.o  drivers/usb/host/built-in.o  drivers/usb/musb-new/built-in.o  drivers/usb/musb/built-in.o  drivers/usb/phy/built-in.o  drivers/usb/ulpi/built-in.o  fs/built-in.o  lib/built-in.o  net/built-in.o  test/built-in.o  test/dm/built-in.o --end-group -L /home/al/csky_toolchain/gcc_v3.8.10-kstq-nd-r2/opt/ext-toolchain/bin/../lib/gcc/csky-linux-gnuabiv2/6.3.0/hard-fp -lgcc -Map u-boot.map

而後將u-boot中特殊section以binary格式拷貝到u-boot-nodtb.bin中:

cmd_u-boot-nodtb.bin := csky-abiv2-linux-objcopy --gap-fill=0xff  -j .text -j .rodata -j .data -j .u_boot_list -j .dtb.init.rodata -O binary  u-boot u-boot-nodtb.bin

u-boot.bin和u-boot-nodtb.bin是同樣的:

cmd_u-boot.bin := cp u-boot-nodtb.bin u-boot.bin

u-boot.img是在u-boot.bin中加了

cmd_u-boot.img := ./tools/mkimage -A csky -T firmware -C none -O u-boot -a 0x17a00000 -e 0x17a00180 -n "U-Boot 2016.07-00058-g6c3df97-dirty for deepeye1000 board" -d u-boot.bin u-boot.img  >/dev/null

u-boot.img相較於u-boot.bin多了個image_header/image_header_t,共64字節大小。

ih_magic(0x27051956)、ih_hcrc(0x40700f2b)、ih_time(0x5da3e81e)、ih_size(0x00028510,即去掉header以後的data大小)、ih_load(0x0a000000,uboot加載地址)、id_ep(0x0a000180,uboot入口執行地址)、ih_dcrc(0x6f71d5c9)、ih_os(0x11,IH_OS_U_BOOT)、ih_arch(0x13,IH_ARCH_SANDBOX)、ih_type(0x05,IH_TYPE_FIRMWARE)、ih_comp(0x00,IH_COMP_NONE),後面的就是ih_name字符串。

3.3 加載uboot流程

boot_init_r()中根據啓動模式的不一樣,從存儲介質指定地址中讀取內容。而後從中解析image_header_t,若是不存在則經過uboot指定。但最終都是賦給spl_image。

void board_init_r(gd_t *dummy1, ulong dummy2)
{
    u8 boot_mode;

    printf("Welcome to SPL!\n");
...
    boot_mode = get_bootmode() & 0x07;
    debug("%s:%d, boot mode %d\n", __FUNCTION__, __LINE__, boot_mode);
    switch(boot_mode) {
...
#ifdef CONFIG_SPL_SD_SUPPORT
    case BOOT_TYPE_SDCARD:
        printf("Load U-Boot from SD ...\n");
        spl_mmc_load_image(BOOT_DEVICE_MMC2);----------------從sd中讀取uboot,並解析頭到spl_iamge中,將uboot加載到DDR中。 break;
#endif
...
    default:
        printf("Invalid boot mode 0x%x ...\n", boot_mode);
        while (1);
    }

    switch (spl_image.os) {
    case IH_OS_U_BOOT:
        debug("Jumping to U-Boot\n");
        break;
    default:
        debug("Unsupported OS image.. Jumping nevertheless..\n");
    }
...
    debug("loaded - jumping to U-Boot...");
    jump_to_image_no_args(&spl_image);----------------------從spl_image.entry_point指定的地址開始執行uboot,這個程序沒有返回值。 while (1);
}

int spl_mmc_load_image(u32 boot_device)
{
    struct mmc *mmc = NULL;
    u32 boot_mode;
    int err = 0;
    __maybe_unused int part;

    err = spl_mmc_find_device(&mmc, boot_device);
    if (err)
        return err;

    err = mmc_init(mmc);
...
    boot_mode = spl_boot_mode(boot_device);
    err = -EINVAL;
    switch (boot_mode) {
...
    case MMCSD_MODE_RAW:
        debug("spl: mmc boot mode: raw\n");
...
#if defined(CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR)
        err = mmc_load_image_raw_sector(mmc,
            CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR);--------------CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR是uboot在sd中的起始sector。 if (!err)
            return err;
#endif
...
    }
    return err;
}

static int mmc_load_image_raw_sector(struct mmc *mmc, unsigned long sector)
{
    unsigned long count;
    struct image_header *header;
    int ret = 0;

    header = (struct image_header *)(CONFIG_SYS_TEXT_BASE -
                     sizeof(struct image_header));---------------------CONFIG_SYS_TEXT_BASE是uboot.bin加載到DDR中的地址,因此header即uboot.bin往前移struct image_header一段地址。 /* read image header to find the image size & load address */
    count = blk_dread(mmc_get_blk_desc(mmc), sector, 1, header);-------將uboot.bin第一個sector寫入到header地址。
    debug("hdr read sector %lx, count=%lu\n", sector, count);
...
    if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) &&...
    } else {
        ret = mmc_load_legacy(mmc, sector, header);
    }

end:
    if (ret) {
#ifdef CONFIG_SPL_LIBCOMMON_SUPPORT
        puts("mmc_load_image_raw_sector: mmc block read error\n");
#endif
        return -1;
    }

    return 0;
}

DECLARE_GLOBAL_DATA_PTR;

static int mmc_load_legacy(struct mmc *mmc, ulong sector,
               struct image_header *header)
{
    u32 image_size_sectors;
    unsigned long count;
    int ret;

    ret = spl_parse_image_header(header);-----------------解析header數據到spl_image中。 if (ret)
        return ret;

    /* convert size to sectors - round up */
    image_size_sectors = (spl_image.size + mmc->read_bl_len - 1) /
                 mmc->read_bl_len;------------------------將須要讀取的uboot大小轉換成sector數目。 /* Read the header too to avoid extra memcpy */
    count = blk_dread(mmc_get_blk_desc(mmc), sector, image_size_sectors,
              (void *)(ulong)spl_image.load_addr);--------讀取指定數目secotor到spl_image.load_addr中。
    debug("read %x sectors to %x\n", image_size_sectors,
          spl_image.load_addr);
    if (count != image_size_sectors)
        return -EIO;

    return 0;
}

int spl_parse_image_header(const struct image_header *header)
{
    u32 header_size = sizeof(struct image_header);

    if (image_get_magic(header) == IH_MAGIC) {-----------------定義struct image_header的狀況,從header中獲取信息填充spl_header。 ...
    } else {
#ifdef CONFIG_SPL_PANIC_ON_RAW_IMAGE
...
#else
        /* Signature not found - assume u-boot.bin */
        debug("mkimage signature not found - ih_magic = %x\n",
            header->ih_magic);
        spl_set_header_raw_uboot();---------------------------沒有經過mkimage生成uboot.bin文件狀況,spl_image內容不從struct image_header中獲取。 #endif
    }
    return 0;
}

void spl_set_header_raw_uboot(void)
{
    spl_image.size = CONFIG_SYS_MONITOR_LEN;-----------------表示uboot最大地址。
    spl_image.entry_point = CONFIG_SYS_UBOOT_START;----------uboot在DDR中起始運行地址。
    spl_image.load_addr = CONFIG_SYS_TEXT_BASE;--------------uboot在DDR中加載開始地址。
    spl_image.os = IH_OS_U_BOOT;-----------------------------表示下一階段文件類型是uboot。
    spl_image.name = "U-Boot";
}
__weak void __noreturn jump_to_image_no_args(struct spl_image_info *spl_image)

  {
      typedef void __noreturn (*image_entry_noargs_t)(void);

      image_entry_noargs_t image_entry =
      (image_entry_noargs_t)(unsigned long)spl_image->entry_point;---跳轉到entry_point地址開始執行uboot。

      debug("image entry point: 0x%X\n", spl_image->entry_point);
      image_entry();
  }

一樣遺留問題:爲何entry_point相對load_addr日後偏移0x00000180。

跟spl一樣緣由,由於異常處理函數地址佔用了0x180字節。因此_start()函數從0x180開始。

4. uboot加載linux分析

首先分析幾個重要數據結構image_header_t、bootm_headers_t,而後分析mkimage是如何給zImage加image_header_t後變成uImage的,最後是uboot命令bootm是如何解析image_header_t並加載linux的。

4.1 重要數據結構

image_header_t是legacy鏡像文件的頭,bootm_headers_t是bootm命令所使用的參數,這些參數主要從image_header_t中獲取。

image_header_t是靜態的,bootm_headers_t是動態的,還包括其餘一些執行bootm執行時所須要的參數。

struct lmb是linux內存範圍以及reserved區域。

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數據。
    image_header_t    legacy_hdr_os_copy;    /* header copy */----------------拷貝後的image_header_t,後續修改使用。
    ulong        legacy_hdr_valid;--------------------------------------------對image_header_t的檢驗是否經過,1表示經過。 ...

  #ifndef USE_HOSTCC
      image_info_t os; /* os image info */
      ulong ep; /* entry point of OS */-----------------------------------------鏡像執行的入口點。

      ulong rd_start, rd_end;/* ramdisk start/end */

      char *ft_addr; /* flat dev tree address */
      ulong ft_len; /* length of flat device tree */

      ulong initrd_start;
      ulong initrd_end;
      ulong cmdline_start;
      ulong cmdline_end;
      bd_t *kbd;
  #endif

    int        verify;        /* getenv("verify")[0] != 'n' */----------------1表示須要對data進行crc校驗。

#define    BOOTM_STATE_START    (0x00000001)
#define    BOOTM_STATE_FINDOS    (0x00000002)
#define    BOOTM_STATE_FINDOTHER    (0x00000004)
#define    BOOTM_STATE_LOADOS    (0x00000008)
#define    BOOTM_STATE_RAMDISK    (0x00000010)
#define    BOOTM_STATE_FDT        (0x00000020)
#define    BOOTM_STATE_OS_CMDLINE    (0x00000040)
#define    BOOTM_STATE_OS_BD_T    (0x00000080)
#define    BOOTM_STATE_OS_PREP    (0x00000100)
#define    BOOTM_STATE_OS_FAKE_GO    (0x00000200)    /* 'Almost' run the OS */
#define    BOOTM_STATE_OS_GO    (0x00000400)
    int        state;

#ifdef CONFIG_LMB
    struct lmb    lmb;        /* for memory mgmt */
#endif
} bootm_headers_t;

  typedef struct image_info {
      ulong start, end; /* start/end of blob */----------------------------------------整個鏡像包括image_header_t和image data的起始地址。
      ulong image_start, image_len; /* start of image within blob, len of image */-----對應image減去image_header_t大小的地址,image_len對應ih_size。
      ulong load; /* load addr for the image */----------------------------------------對應ih_load。
      uint8_t comp, type, os; /* compression, type of image, os type */----------------對應ih_comp、ih_type、ih_os。
      uint8_t arch; /* CPU architecture */---------------------------------------------對應ih_arch。
  } image_info_t;

typedef struct image_header {
    __be32        ih_magic;    /* Image Header Magic Number    */---------識別鏡像的magic numver:#define IH_MAGIC 0x27051956。
    __be32        ih_hcrc;    /* Image Header CRC Checksum    */----------指的是image_header_t這部分的crc校驗值,在比較以前首先將ih_crc清空,而後對image_header_t的crc校驗結果和ih_hcrc進行比較。
    __be32        ih_time;    /* Image Creation Timestamp    */-----------鏡像建立時間。
    __be32        ih_size;    /* Image Data Size        */----------------除去image_header_t後的image大小。
    __be32        ih_load;    /* Data     Load  Address        */---------鏡像被加載到的地址。
    __be32        ih_ep;        /* Entry Point Address        */----------linux今後處開始執行。
    __be32        ih_dcrc;    /* Image Data CRC Checksum    */------------鏡像除去image_header_t部分的crc校驗值。
    uint8_t        ih_os;        /* Operating System        */------------鏡像的OS類型,好比IH_OS_LINUX、IH_OS_LINUX等等。
    uint8_t        ih_arch;    /* CPU architecture        */--------------CPU架構類型,好比IH_ARCH_ARM、IH_ARCH_SANDBOX等等。
    uint8_t        ih_type;    /* Image Type            */----------------鏡像類型,好比IH_TYPE_KERNEL、IH_TYPE_RAMDISK等等。
    uint8_t        ih_comp;    /* Compression Type        */--------------鏡像壓縮類型。
    uint8_t        ih_name[IH_NMLEN];    /* Image Name        */----------鏡像名稱。
} image_header_t;

struct lmb_property {
    phys_addr_t base;
    phys_size_t size;
};

struct lmb_region {
    unsigned long cnt;
    phys_size_t size;
    struct lmb_property region[MAX_LMB_REGIONS+1];
};

struct lmb {
    struct lmb_region memory;
    struct lmb_region reserved;
};

4.2 uImage頭生成image_header_t流程

內核從vmlinux到生成uImage,經歷過Image和zImage。

從vmlinux->Image->zImage->uImage,須要經歷以下基本編譯命令。

cmd_arch/csky/boot/Image := csky-abiv2-linux-objcopy -O binary  vmlinux arch/csky/boot/Image
cmd_arch
/csky/boot/zImage := (cat arch/csky/boot/Image | gzip -n -f -9 > arch/csky/boot/zImage) || (rm -f arch/csky/boot/zImage ; false)
cmd_arch
/csky/boot/uImage := /bin/bash ./scripts/mkuboot.sh -A sandbox -O linux -C gzip -T kernel -a 80000000 -e 80000000 -n 'Linux-4.9.56' -d arch/csky/boot/zImage arch/csky/boot/uImage

從vmlinux到Image,objcopy僅拷貝binary部分到Image;從Image到zImage,使用gzip進行壓縮;從zImage到uImage,通過mkuboot.sh調用mkimage命令添加image_header_t頭。

因此後面對uImage的處理是一個反向的過程:須要解析頭,而後進行gunzip處理,纔會獲得和Image一樣內容。

mkimage添加image_header_t的過程參考mkimage.c和default_image.c(apt-get source u-boot-tools獲取相關源碼)。

static void image_set_header(void *ptr, struct stat *sbuf, int ifd,
                struct image_tool_params *params)
{
    uint32_t checksum;
    char *source_date_epoch;
    time_t time;

    image_header_t * hdr = (image_header_t *)ptr;

    checksum = crc32(0,
            (const unsigned char *)(ptr +
                sizeof(image_header_t)),
            sbuf->st_size - sizeof(image_header_t));

    source_date_epoch = getenv("SOURCE_DATE_EPOCH");
    if (source_date_epoch != NULL) {
        time = (time_t) strtol(source_date_epoch, NULL, 10);

        if (gmtime(&time) == NULL) {
            fprintf(stderr, "%s: SOURCE_DATE_EPOCH is not valid\n",
                __func__);
            time = 0;
        }
    } else {
        time = sbuf->st_mtime;
    }

    /* Build new header */
    image_set_magic(hdr, IH_MAGIC);
    image_set_time(hdr, time);
    image_set_size(hdr, sbuf->st_size - sizeof(image_header_t));
    image_set_load(hdr, params->addr);
    image_set_ep(hdr, params->ep);
    image_set_dcrc(hdr, checksum);
    image_set_os(hdr, params->os);
    image_set_arch(hdr, params->arch);
    image_set_type(hdr, params->type);
    image_set_comp(hdr, params->comp);

    image_set_name(hdr, params->imagename);

    checksum = crc32(0, (const unsigned char *)hdr,
                sizeof(image_header_t));

    image_set_hcrc(hdr, checksum);
}

4.3 bootm命令解析

do_bootm()是bootm命令的函數,調用do_bootm_states()進行不一樣states的順序執行。bootm_start()進行lmb準備工做;boot_find_os()檢查鏡像的頭,並填充到bootm_headers_t中;bootm_load_os()將鏡像解壓到指定地址;bootm_os_get_boot_func()根據os類型,選擇合適的boot_fn;以後使用boot_fn進行各類架構相關的加載工做。

int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
...
    /* determine if we have a sub command */
    argc--; argv++;
    if (argc > 0) {
        char *endp;

        simple_strtoul(argv[0], &endp, 16);

        if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
            return do_bootm_subcommand(cmdtp, flag, argc, argv);
    }

    return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
        BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
        BOOTM_STATE_LOADOS |
        BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
        BOOTM_STATE_OS_GO, &images, 1);
}

int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
            int states, bootm_headers_t *images, int boot_progress)
{
    boot_os_fn *boot_fn;
    ulong iflag = 0;
    int ret = 0, need_boot_fn;

    images->state |= states;

    if (states & BOOTM_STATE_START)
        ret = bootm_start(cmdtp, flag, argc, argv);

    if (!ret && (states & BOOTM_STATE_FINDOS))
        ret = bootm_find_os(cmdtp, flag, argc, argv);

    if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
        ret = bootm_find_other(cmdtp, flag, argc, argv);
        argc = 0;    /* consume the args */
    }

    /* Load the OS */
    if (!ret && (states & BOOTM_STATE_LOADOS)) {
        ulong load_end;

        iflag = bootm_disable_interrupts();
        ret = bootm_load_os(images, &load_end, 0);
        if (ret == 0)
            lmb_reserve(&images->lmb, images->os.load,
                    (load_end - images->os.load));
        else if (ret && ret != BOOTM_ERR_OVERLAP)
            goto err;
        else if (ret == BOOTM_ERR_OVERLAP)
            ret = 0;
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
        if (images->os.os == IH_OS_LINUX)
            fixup_silent_linux();
#endif
    }
...
    boot_fn = bootm_os_get_boot_func(images->os.os);----------------------------------根據os類型找到對應的boot_fn,對於linux便是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 */
    if (!ret && (states & BOOTM_STATE_OS_CMDLINE))------------------------------------調用boot_fn()執行不一樣state的功能。
        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);
...
    if (!ret && (states & BOOTM_STATE_OS_GO))
        ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
                images, boot_fn);-----------------------------------------------------最後一步,切換到linux。
...
return ret;
}

bootm_start()主要更新struct lmb相關的內存數據以及reserved區域。

static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc,
               char * const argv[])
{
    memset((void *)&images, 0, sizeof(images));
    images.verify = getenv_yesno("verify");-----------------------------是否須要verify進行checksum。

    boot_start_lmb(&images);--------------------------------------------填充images->lmb,包括總內存memory和預留內存reserved。

    bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
    images.state = BOOTM_STATE_START;

    return 0;
}

static void boot_start_lmb(bootm_headers_t *images)
{
    ulong        mem_start;
    phys_size_t    mem_size;

    lmb_init(&images->lmb);

    mem_start = getenv_bootm_low();
    mem_size = getenv_bootm_size();-------------------------------------分別從環境變量中獲取bootm_low和bootm_size兩個變量。

    lmb_add(&images->lmb, (phys_addr_t)mem_start, mem_size);------------增長mem region區域到數據結構中。

    arch_lmb_reserve(&images->lmb);
    board_lmb_reserve(&images->lmb);
}

image_get_kernel()對image文件進行驗證,並輸出相關信息。

   Image Name:   Linux-4.9.56
   Image Type:   Sandbox Linux Kernel Image (gzip compressed)
   Data Size:    4921279 Bytes = 4.7 MiB
   Load Address: 80000000
   Entry Point:  80000000
   Verifying Checksum ... OK
   Uncompressing Kernel Image ... OK

bootm_find_os()主要對image_header_t進行檢查,並進行鏡像data的校驗,填充bootm命令運行所須要的數據結構bootm_headers_t。

static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
             char * const argv[])
{
    const void *os_hdr;
    bool ep_found = false;
    int ret;

    /* get kernel image header, start address and length */
    os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
            &images, &images.os.image_start, &images.os.image_len);-----返回值指向image_header_t,同時獲取了鏡像數據開始和大小。
...
    /* get image parameters */
    switch (genimg_get_format(os_hdr)) {--------------------------------os_hdr是鏡像header開始,分別判斷不一樣格式的magic number,好比image_header_t、fdt_header、andr_img_hdr。 #if defined(CONFIG_IMAGE_FORMAT_LEGACY)
    case IMAGE_FORMAT_LEGACY:
        images.os.type = image_get_type(os_hdr);------------------------這些數據參照bootm_headers_timage_header_t兩個數據結構。
        images.os.comp = image_get_comp(os_hdr);
        images.os.os = image_get_os(os_hdr);

        images.os.end = image_get_image_end(os_hdr);
        images.os.load = image_get_load(os_hdr);
        images.os.arch = image_get_arch(os_hdr);
        break;
#endif
...
    }
...
    if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {
        images.os.load = images.os.image_start;
        images.ep += images.os.load;
    }

    images.os.start = map_to_sysmem(os_hdr);

    return 0;
}

static const void *boot_get_kernel(cmd_tbl_t *cmdtp, int flag, int argc,
                   char * const argv[], bootm_headers_t *images,
                   ulong *os_data, ulong *os_len)
{
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
    image_header_t    *hdr;
#endif
    ulong        img_addr;
    const void *buf;
    const char    *fit_uname_config = NULL;
    const char    *fit_uname_kernel = NULL;
#if IMAGE_ENABLE_FIT
    int        os_noffset;
#endif

    img_addr = genimg_get_kernel_addr_fit(argc < 1 ? NULL : argv[0],
                          &fit_uname_config,
                          &fit_uname_kernel);------------------bootm的入參包括了鏡像文件的加載地址。

    bootstage_mark(BOOTSTAGE_ID_CHECK_MAGIC);

    /* copy from dataflash if needed */
    img_addr = genimg_get_image(img_addr);---------------------若是沒有定義CONFIG_HAS_DATAFLASH,返回的是原地址。 /* check image type, for FIT images get FIT kernel node */
    *os_data = *os_len = 0;
    buf = map_sysmem(img_addr, 0);
    switch (genimg_get_format(buf)) {
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
    case IMAGE_FORMAT_LEGACY:
        printf("## Booting kernel from Legacy Image at %08lx ...\n",
               img_addr);
        hdr = image_get_kernel(img_addr, images->verify);------主要對鏡像的image_header_t進行magic number、header crc、data crc檢查等。 if (!hdr)
            return NULL;
        bootstage_mark(BOOTSTAGE_ID_CHECK_IMAGETYPE);

        /* get os_data and os_len */
        switch (image_get_type(hdr)) {
        case IH_TYPE_KERNEL:
        case IH_TYPE_KERNEL_NOLOAD:
            *os_data = image_get_data(hdr);
            *os_len = image_get_data_size(hdr);
            break;
...
        }

        memmove(&images->legacy_hdr_os_copy, hdr,
            sizeof(image_header_t));

        images->legacy_hdr_os = hdr;

        images->legacy_hdr_valid = 1;---------------------------代表image_header_t檢查經過。
        bootstage_mark(BOOTSTAGE_ID_DECOMP_IMAGE);
        break;
#endif
...
    default:
        printf("Wrong Image Format for %s command\n", cmdtp->name);
        bootstage_error(BOOTSTAGE_ID_FIT_KERNEL_INFO);
        return NULL;
    }

    debug("   kernel data at 0x%08lx, len = 0x%08lx (%ld)\n",
          *os_data, *os_len, *os_len);

    return buf;
}

int genimg_get_format(const void *img_addr)
{
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
    const image_header_t *hdr;

    hdr = (const image_header_t *)img_addr;
    if (image_check_magic(hdr))
        return IMAGE_FORMAT_LEGACY;
#endif
...
    return IMAGE_FORMAT_INVALID;
}

static image_header_t *image_get_kernel(ulong img_addr, int verify)
{
    image_header_t *hdr = (image_header_t *)img_addr;

    if (!image_check_magic(hdr)) {---------------------------------檢查ih_magic。
        puts("Bad Magic Number\n");
        bootstage_error(BOOTSTAGE_ID_CHECK_MAGIC);
        return NULL;
    }
    bootstage_mark(BOOTSTAGE_ID_CHECK_HEADER);

    if (!image_check_hcrc(hdr)) {----------------------------------檢查ih_hcrc,檢查以前先拷貝一個image_header_t,而後清空ih_hcrc,再進行crc校驗對比。
        puts("Bad Header Checksum\n");
        bootstage_error(BOOTSTAGE_ID_CHECK_HEADER);
        return NULL;
    }

    bootstage_mark(BOOTSTAGE_ID_CHECK_CHECKSUM);
    image_print_contents(hdr);-------------------------------------打印鏡像名稱、類型、大小等等信息。 if (verify) {--------------------------------------------------進行進行data部分crc校驗。
        puts("   Verifying Checksum ... ");
        if (!image_check_dcrc(hdr)) {
            printf("Bad Data CRC\n");
            bootstage_error(BOOTSTAGE_ID_CHECK_CHECKSUM);
            return NULL;
        }
        puts("OK\n");
    }
    bootstage_mark(BOOTSTAGE_ID_CHECK_ARCH);

    if (!image_check_target_arch(hdr)) {---------------------------ih_arch檢查。
        printf("Unsupported Architecture 0x%x\n", image_get_arch(hdr));
        bootstage_error(BOOTSTAGE_ID_CHECK_ARCH);
        return NULL;
    }
    return hdr;
}

void image_print_contents(const void *ptr)
{
    const image_header_t *hdr = (const image_header_t *)ptr;
    const char __maybe_unused *p;

    p = IMAGE_INDENT_STRING;
    printf("%sImage Name:   %.*s\n", p, IH_NMLEN, image_get_name(hdr));
    if (IMAGE_ENABLE_TIMESTAMP) {
        printf("%sCreated:      ", p);
        genimg_print_time((time_t)image_get_time(hdr));
    }
    printf("%sImage Type:   ", p);
    image_print_type(hdr);
    printf("%sData Size:    ", p);
    genimg_print_size(image_get_data_size(hdr));
    printf("%sLoad Address: %08x\n", p, image_get_load(hdr));
    printf("%sEntry Point:  %08x\n", p, image_get_ep(hdr));

    if (image_check_type(hdr, IH_TYPE_MULTI) ||
            image_check_type(hdr, IH_TYPE_SCRIPT)) {
        int i;
        ulong data, len;
        ulong count = image_multi_count(hdr);

        printf("%sContents:\n", p);
        for (i = 0; i < count; i++) {
            image_multi_getimg(hdr, i, &data, &len);

            printf("%s   Image %d: ", p, i);
            genimg_print_size(len);

            if (image_check_type(hdr, IH_TYPE_SCRIPT) && i > 0) {
                printf("%s    Offset = 0x%08lx\n", p, data);
            }
        }
    }
}

bootm_find_other()嘗試從boot文件中解析出ramdisk等部分。

static int bootm_find_other(cmd_tbl_t *cmdtp, int flag, int argc,
                char * const argv[])
{
    if (((images.os.type == IH_TYPE_KERNEL) ||
         (images.os.type == IH_TYPE_KERNEL_NOLOAD) ||
         (images.os.type == IH_TYPE_MULTI)) &&
        (images.os.os == IH_OS_LINUX ||
         images.os.os == IH_OS_VXWORKS))
        return bootm_find_images(flag, argc, argv);

    return 0;
}

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;
    }
...
    return 0;
}

int boot_get_ramdisk(int argc, char * const argv[], bootm_headers_t *images,
        uint8_t arch, ulong *rd_start, ulong *rd_end)
{
    ulong rd_addr, rd_load;
    ulong rd_data, rd_len;
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
    const image_header_t *rd_hdr;
#endif
    void *buf;
#ifdef CONFIG_SUPPORT_RAW_INITRD
    char *end;
#endif

    const char *select = NULL;

    *rd_start = 0;
    *rd_end = 0;

    if (argc >= 2)
        select = argv[1];

    /*
     * Look for a '-' which indicates to ignore the
     * ramdisk argument
     */
    if (select && strcmp(select, "-") ==  0) {
        debug("## Skipping init Ramdisk\n");
        rd_len = rd_data = 0;
    } else if (select || genimg_has_config(images)) {
...if (!rd_data) {
        debug("## No init Ramdisk\n");
    } else {
        *rd_start = rd_data;
        *rd_end = rd_data + rd_len;
    }
    debug("   ramdisk start = 0x%08lx, ramdisk end = 0x%08lx\n",
            *rd_start, *rd_end);

    return 0;
}

bootm_load_os()主要是將鏡像的data部分調用os.comp解壓算法從images.os.image_start解壓到images.os.load。

static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,
             int boot_progress)
{
    image_info_t os = images->os;
    ulong load = os.load;
    ulong blob_start = os.start;
    ulong blob_end = os.end;
    ulong image_start = os.image_start;
    ulong image_len = os.image_len;
    bool no_overlap;
    void *load_buf, *image_buf;
    int err;

    load_buf = map_sysmem(load, 0);------------------------------os.image_start是解壓前鏡像存放地址,os.load是解壓後鏡像存放地址。
    image_buf = map_sysmem(os.image_start, image_len);-----------鏡像的存放地址爲0x86000000,image_header_t的大小爲64字節,因此os.image_start地址爲0x86000040。     err = bootm_decomp_image(os.comp, load, os.image_start, os.type,
                 load_buf, image_buf, image_len,
                 CONFIG_SYS_BOOTM_LEN, load_end);---------------image_buf是解壓前數據存放處,load_buf是解壓後數據存放處;load是解壓數據起始地址,load_end是解壓後數據末地址。     if (err) {
        bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE);
        return err;
    }
    flush_cache(load, ALIGN(*load_end - load, ARCH_DMA_MINALIGN));

    debug("   kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end);
    bootstage_mark(BOOTSTAGE_ID_KERNEL_LOADED);------------------數據已經被解壓加載到指定地址,能夠執行。

    no_overlap = (os.comp == IH_COMP_NONE && load == image_start);
...
    return 0;
}

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)
{
...
    switch (comp) {
...
#ifdef CONFIG_GZIP
    case IH_COMP_GZIP: {
        ret = gunzip(load_buf, unc_len, image_buf, &image_len);--調用具體解壓算法進行解壓縮。 break;
    }
#endif /* CONFIG_GZIP */...
    }
...
    *load_end = load + image_len;

    puts("OK\n");

    return 0;
}

bootm_os_get_boot_func()根據os的類型,執行kernel的entry point。

對於Linux來講就是do_bootm_linux,根據架構進行準備必要的準備,而後跳轉到entry point,將CPU執行權交給Linux。

boot_os_fn *bootm_os_get_boot_func(int os)
{
    return boot_os[os];
}

static boot_os_fn *boot_os[] = {
    [IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX
    [IH_OS_LINUX] = do_bootm_linux,
#endif...
};

int do_bootm_linux(int flag, int argc, char * const argv[],
           bootm_headers_t *images)
{
    void (*theKernel)(int magic, void * params);
    char *tmp;
    unsigned int dtb_load_addr;

    theKernel = (void (*)(int, void *))images->ep;----------------images->ep是uboot跳轉到linux的入口點。
    printf("\nStarting kernel ... \n\n");

    disable_interrupts();
    flush_cache(0,0);
    theKernel (0x20150401, (void *)dtb_load_addr);----------------跳轉到linux,兩個入參。 return 1;
}

5. 小結

從以上分析可知,每個階段啓動下一階段都是經過識別頭開始的。

mksheader給spl加sb_header頭,bootrom進行解析;mkimage給uboot加image_header_t頭,spl進行解析;mkimage給kernel加image_header_t頭,uboot進行解析。

都是經過工具在程序代碼以前加上一個頭,而後上一級工具進行解析加載。

相關文章
相關標籤/搜索