天天看點

uboot分析:uboot啟動核心

本文簡述了uboot啟動核心的過程。

(注:本文參考資料:朱有鵬嵌入式課程。本文為個人學習記錄,如有錯誤,歡迎指正。)

1. U-Boot啟動核心概述

  • U-Boot啟動完成後,最終進入到main_loop()循環中。若在bootdelay倒計時為0之前,U-Boot控制台有輸入,則進入指令解析-執行的循環;若控制台無輸入,U-Boot将啟動核心。
  • U-Boot啟動核心可歸結為以下四個步驟:  

                           1)将核心搬移至DDR中;

                           2)校驗核心格式、CRC;

                           3)準備傳參;

                           4)跳轉執行核心。

2. U-Boot啟動核心過程分析

2.1 将核心搬移至DDR中

  • 在/uboot/lib_arm/board.c->start_armboot()函數調用/uboot/common/main.c->main_loop()函數,main_loop()函數中包含了核心的啟動代碼。
s = getenv ("bootcmd");        //擷取bootcmd環境變量的值
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

if (bootdelay >= 0 && s && !abortboot (bootdelay)) 
        {
            ................................

            #ifndef CFG_HUSH_PARSER
       run_command (s, 0);   //執行bootcmd環境變量中的指令
            ................................
        }      
  • 環境變量bootcmd的值在開發闆配置檔案(/uboot/include/configs/x210_sd.h)中定義。
#if defined(CFG_FASTBOOT_NANDBSP)
#define CONFIG_BOOTCOMMAND    "nand read C0008000 600000 400000; nand read 30A00000 B00000 180000;  \
                                                                 bootm C0008000 30A00000"
#elif defined(CFG_FASTBOOT_SDMMCBSP)
#define CONFIG_BOOTCOMMAND    "movi read kernel C0008000; movi read rootfs 30A00000 180000; \
                                                                 bootm C0008000 30A00000"
#endif      
  • 環境變量bootcmd包含如下指令:
bootcmd=nand read C0008000 600000 400000;  /*将kernel(大小0x00400000位元組)從nand中的0x00600000位址處拷貝到DDR中的 0xc0008000位址處*/
nand read 30A00000 B00000 180000; /*将rootfs(大小0x00180000位元組)從nand中的0x00B00000位址處拷貝到DDR中的 0x30A08000位址處*/
bootm C0008000 30A00000                 //啟動kernel、rootfs      
  • U-Boot能準确識别kernel、rootfs在NandFlash中的位置,是因為U-Boot中已對NandFlash進行分區。燒錄時,嚴格按照分區要求将uboot、kernel、rootfs燒錄進NandFlash中即可。
  • 在U-Boot下執行mtd指令,即可檢視各個分區的情況。

2.2 校驗核心格式、CRC

  • 核心格式有兩類:zImage和uImage。
  • 并不是所有U-Boot都支援zImage,是否支援就看其配置檔案(x210_sd.h)中是否定義CONFIG_ZIMAGE_BOOT這個宏。是以有些uboot是支援zImage啟動的,有些則不支援。但是所有的uboot肯定都支援uImage啟動。
zImage          Linux核心經過編譯後生成一個ELF格式的可執行檔案,vmlinux。再通過arm-linux-objcopy工具進行加工,最後進行壓縮,得到zImage格式的核心鏡像,可以燒錄進啟動媒體中。
uImage uImage是由zImage加工得到的。uboot中的mkimage工具将zImage加工生成uImage來給uboot啟動。這個加工過程其實就是在zImage前面加上64位元組的uImage的頭資訊即可。
  • ulmage格式的核心頭部資訊在/uboot/include/Image.h中定義。
  • ih_load是加載位址,即核心在DDR中的位址(運作位址);ih_ep是核心入口位址。
typedef struct image_header {
uint32_t    ih_magic;    /* Image Header Magic Number    */
uint32_t    ih_hcrc;    /* Image Header CRC Checksum    */
uint32_t    ih_time;    /* Image Creation Timestamp    */
uint32_t    ih_size;    /* Image Data Size    */
uint32_t    ih_load;    /* Data     Load  Address    */
uint32_t    ih_ep;    /* Entry Point Address    */
uint32_t    ih_dcrc;    /* Image Data CRC Checksum    */
uint8_t    ih_os;    /* Operating System    */
uint8_t    ih_arch;    /* CPU architecture    */
uint8_t    ih_type;    /* Image Type    */
uint8_t    ih_comp;    /* Compression Type    */
uint8_t ih_name[IH_NMLEN];    /* Image Name    */
} image_header_t;      
  • 執行環境變量bootcmd中的指令"bootm  C0008000  30A00000",實質是執行do_bootm()函數(/uboot/common/Cmd_bootm.c->do_bootm())。
  • do_bootm()函數在标号after_header_check之前,都是在校驗核心的頭部資訊。根據頭部資訊,判斷核心格式和進行CRC校驗。
  • 實際上,從NandFlash中讀取的uImage可以放置在DDR中的任意位置,隻要不破壞U-Boot占有的記憶體空間即可。因為do_bootm()函數内部從頭部擷取核心的加載位址,當發現該uImage目前所處的位址與加載位址不同時,會将核心搬移至加載位址中。一般情況下,都會将核心搬移至加載位址中,便不用使用do_bootm()函數來搬移核心,提高效率。
  • do_bootm()函數将根據系統類型,啟動核心。此處将調用/uboot/lib_arm/Bootm.c/->do_bootm_linux ()函數啟動核心。
after_header_check:
os = hdr->ih_os;
#endif

switch (os) {
default:    /* handled by (original) Linux case */

        case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
    fixup_silent_linux();
#endif
    do_bootm_linux (cmdtp, flag, argc, argv, &images);
    break;

case IH_OS_NETBSD:
    do_bootm_netbsd (cmdtp, flag, argc, argv, &images);
    break;
    .........................................      

2.3 準備傳參

  • U-Boot中以标記連結清單(tagged list)形式來傳遞啟動參數。
  • 标記是一種資料結構,标記連結清單就是挨着存放的多個标記。
  • 标記有多種類型,在/uboot/include/asm-arm/Setup.h中定義。标記連結清單以ATAG_CORE開始,以标記ATAG_NONE結束,中間包含其他标記。
#define ATAG_CORE    0x54410001                    //起始标記
#define ATAG_NONE    0x00000000                    //結束标記
#define ATAG_MEM    0x54410002
#define ATAG_VIDEOTEXT    0x54410003
#define ATAG_RAMDISK    0x54410004
#define ATAG_INITRD    0x54410005
#define ATAG_INITRD2    0x54420005
#define ATAG_SERIAL    0x54410006
#define ATAG_REVISION    0x54410007
#define ATAG_VIDEOLFB    0x54410008
#define ATAG_CMDLINE    0x54410009
#define ATAG_ACORN    0x41000101
#define ATAG_MEMCLK    0x41000402
#define ATAG_MTDPART    0x41001099      
  • 标記的資料結構定義在/uboot/include/asm-arm/Setup.h中
struct tag_header
{
  u32 size;     //表示标記的類型
  u32 tag;      //表示标記的結構
};
 
struct tag {
        struct tag_header hdr;
       //不同的标記類型使用不同的聯合,每一個标記對應一個資料結構體
         union { 
                struct tag_core         core;
                struct tag_mem32        mem;
                struct tag_videotext    videotext;
                struct tag_ramdisk      ramdisk;
                struct tag_initrd       initrd;
                struct tag_serialnr     serialnr;
                struct tag_revision     revision;
                struct tag_videolfb     videolfb;
                struct tag_cmdline      cmdline;

                /*
                * Acorn specific
                */
                struct tag_acorn        acorn;
                /*
                 * DC21285 specific
                 */
                struct tag_memclk       memclk;
                struct tag_mtdpart      mtdpart_info;
        } u;
};      

标記清單舉例:

static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;

params->hdr.tag = ATAG_CORE;          //起始标記
params->hdr.size = tag_size (tag_core);

params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;

params = tag_next (params);
}

#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd)
{
int i;

for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM; 
params->hdr.size = tag_size (tag_mem32);

params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size;

params = tag_next (params);
}
}

#endif

..........................................................//中間還有多個标記

static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;   //結束标記
params->hdr.size = 0;
}      
  • do_bootm_linux ()函數啟動核心前,先将U-Boot中的啟動參數傳給核心。
  • 參數分析:

1)0 : 相當于mov r0  #0。

2)machid : U-Boot中的機器碼,從全局變量bd中擷取。核心機器碼和U-Boot機器碼必須一緻才能啟動核心。

3)bd->bi_boot_params : 啟動參數位址。

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], bootm_headers_t *images)
{
    .....................................

    //标記連結清單初始化

    setup_start_tag (bd);
    setup_serial_tag (&params);
    setup_revision_tag (&params);
    setup_memory_tags (bd);
    setup_commandline_tag (bd, commandline);
    setup_initrd_tag (bd, initrd_start, initrd_end);
    setup_videolfb_tag ((gd_t *) gd);
    setup_mtdpartition_tag();
    setup_end_tag (bd);
    .......................................
    void    (*theKernel)(int zero, int arch, uint params);       //定義函數指針theKernel
     ......................................
    theKernel = (void (*)(int, int, uint))ep;                             //将入口位址指派給theKernel
    ......................................
    theKernel (0, machid, bd->bi_boot_params);                //傳參,調用theKernel
    ......................................
}      

2.4 跳轉執行核心

  • theKernel()函數執行成功後,核心将讀取啟動參數,并開始啟動。