正式學習bootloader,基於u-boot1.1.4(啓動流程框架)

 先分析一下u-boot啓動的兩個階段,分別對應start.S和board.c這兩個文件。帶着兩個目的:一是分析一下啓動的流程,二是熟悉一下彙編。

    轉載請註明出處,有誤的地方請指正。源碼基於u-boot1.1.4版本。
    先看board/smsk2410/u-boot.lds這個鏈接腳本,可以知道目標程序的各部分鏈接順序。

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
    . = 0x00000000; /*指定可執行image文件的全局入口點,通常這個地址都放在ROM(flash)0x0位置。必須使編譯器知道這個地址,通常都是修改此處來完成*/

    . = ALIGN(4);
    .text :
    {
     cpu/arm920t/start.o (.text)
     *(.text)
    }

    . = ALIGN(4);
    .rodata : { *(.rodata) }

    . = ALIGN(4);
    .data : { *(.data) }

    . = ALIGN(4);
    .got : { *(.got) }

    . = .;
    __u_boot_cmd_start = .;
    .u_boot_cmd : { *(.u_boot_cmd) }
    __u_boot_cmd_end = .;

    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) }
    _end = .;
}

    第一個要鏈接的是cpu/arm920t/start.o,那麼U-Boot的入口指令一定位於這個程序中。下面詳細分析一下程序跳轉和函數的調用關係以及函數實現。
1.Stage1:cpu/arm920t/start.S
    這個彙編程序是U-Boot的入口程序,開頭就是復位向量的代碼。
U-Boot啓動代碼流程圖

_start: b reset //復位向量 ;;設置異常向量表
       ldr pc, _undefined_instruction
       ldr pc, _software_interrupt
       ldr pc, _prefetch_abort
       ldr pc, _data_abort
       ldr pc, _not_used
       ldr pc, _irq //中斷向量
       ldr pc, _fiq //中斷向量

 /* the actual reset code */
reset: //復位啓動子程序
       /* 設置CPU爲SVC32模式 */
       mrs r0,cpsr
       bic r0,r0,#0x1f ;;位清除,將某些位的值置0:r0 = r0 AND ( !0x1f)
       orr r0,r0,#0xd3 ;;邏輯或,將r0與立即數進行邏輯或,放在r0中(第一個)
       msr cpsr,r0
/* 關閉看門狗 */
 /* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
#endif

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
    ldr r0, =pWTCON
    mov r1, #0x0
    str r1, [r0]
/* 禁止所有中斷和設置CPU頻率 */
    /*
     * mask all IRQs by setting all bits in the INTMR - default
     */
    mov r1, #0xffffffff
    ldr r0, =INTMSK
    str r1, [r0]
# if defined(CONFIG_S3C2410)
    ldr r1, =0x3ff
    ldr r0, =INTSUBMSK
    str r1, [r0]
# endif

    /* FCLK:HCLK:PCLK = 1:2:4 */ ;;FCLK用於CPU,HCLK用於AHB,PCLK用於APB
    /* default FCLK is 120 MHz ! */
    ldr r0, =CLKDIVN ;;根據硬件手冊來設置CLKDIVN寄存器
    mov r1, #3 ;;用戶手冊的推薦值
    str r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */


/* 這些初始化代碼在系統重起的時候執行,運行時熱復位從RAM中啓動不執行 */
    /*
     * we do sys-critical inits only at reboot,
     * not when booting from
     */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl cpu_init_crit ;;跳轉去初始化CPU
#endif
;;#ifdef CONFIG_INIT_CRITICAL 原文中的,估計是1.1.16版本的
;; bl cpu_init_crit
;;#endif

/* CPU和RAM兩個關鍵的初始化子程序 */
/* 初始化CPU */
cpu_init_crit:
    /*
     * flush v4 I/D caches 設置CP15
     */
    mov r0, #0
    mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ ;;使I/D cache失效:將寄存器r0的數據傳送到協處理器p15的c7中。C7寄存器位對應cp15中的cache控制寄存器
    mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ ;;使TLB操作寄存器失效:將r0數據送到cp15的c8、c7中。C8對應TLB操作寄存器

    /*
     * disable MMU stuff and caches 禁止MMU和caches
     */
    mrc p15, 0, r0, c1, c0, 0 ;;先把c1和c0寄存器的各位置0(r0 = 0)
    bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
    bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) ;;這裏我本來有個疑問:爲什麼要分開設置。因爲arm彙編要求的立即數格式所決定的
    orr r0, r0, #0x00000002 @ set bit 2(??) (A) Align ;;上一條已經設置bit1爲0,這一條又設置爲1??
    orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
    mcr p15, 0, r0, c1, c0, 0 ;;用上面(見下面)設定的r0的值設置c1??(cache類型寄存器)和c0(control字寄存器),以下爲c0的位定義
;;bit8: 0 = Disable System protection
;;bit9: 0 = Disable ROM protection
;;bit0: 0 = MMU disabled
;;bit1: 0 = Fault checking disabled 禁止糾錯
;;bit2: 0 = Data cache disabled
;;bit7: 0 = Little-endian operation
;;bit12: 1 = Instruction cache enabled


    /* 配置內存區控制寄存器 ??有待分析,是1.1.4版本的
     * before relocating, we have to setup RAM timing
     * because memory timing is board-dependend, you will
     * find a lowlevel_init.S in your board directory.
     */
    mov ip, lr
    bl lowlevel_init ;;位於board/smdk2410/lowlevel_init.S:用於完成芯片存儲器的初始化,執行完成後返回
    mov lr, ip
    mov pc, lr

relocate: /* 把U-Boot重新定位到RAM */
       adr r0, _start /* r0是代碼的當前位置 */ ;;adr僞指令,彙編器自動通過當前PC的值算出 如果執行到_start時PC的值,放到r0中:
當此段在flash中執行時r0 = _start = 0;當此段在RAM中執行時_start = _TEXT_BASE(在board/smdk2410/config.mk中指定的值爲0x33F80000,即u-boot在把代碼拷貝到RAM中去執行的代碼段的開始)
       ldr r1, _TEXT_BASE /* 測試判斷是從Flash啓動,還是RAM */ ;;此句執行的結果r1始終是0x33FF80000,因爲此值是又編譯器指定的(ads中設置,或-D設置編譯器參數)
       cmp r0, r1 /* 比較r0和r1,調試的時候不要執行重定位 */
       beq stack_setup /* 如果r0等於r1,跳過重定位代碼 */
       /* 準備重新定位代碼 */ ;;以上確定了復位啓動代碼是在flash中執行的(是系統重啓,而不是軟復位),就需要把代碼拷貝到RAM中去執行,以下爲計算即將拷貝的代碼的長度
       ldr r2, _armboot_start ;;前面定義了,就是_start
       ldr r3, _bss_start ;;所謂bss段,就是未被初始化的靜態變量存放的地方,這個地址是如何的出來的?根據board/smsk2410/u-boot.lds內容?
       sub r2, r3, r2 /* r2 得到armboot的大小 */
       add r2, r0, r2 /* r2 得到要複製代碼的末尾地址 */
copy_loop: /* 重新定位代碼 */ ;;開始循環拷貝啓動的代碼到RAM中
       ldmia {r3-r10} /*從源地址[r0]複製 */ ;;r0指向_start(=0)
       stmia {r3-r10} /* 複製到目的地址[r1] */ ;;r1指向_TEXT_BASE(=0x33F80000)
       cmp r0, r2 /* 複製數據塊直到源數據末尾地址[r2] */
       ble copy_loop
 ;;這裏附上u-boot各存儲區域的映射圖,從網上找的,這下對於這幾個地址的位置就一目瞭然了!

 
/* 初始化堆棧等 */
stack_setup:
       ldr r0, _TEXT_BASE /* 上面是128 KiB重定位的u-boot */
       sub r0, r0, #CFG_MALLOC_LEN /* 向下是內存分配空間 */    
       sub r0, r0, #CFG_GBL_DATA_SIZE /* 然後是bdinfo結構體地址空間 */
#ifdef CONFIG_USE_IRQ
       sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif        ;;這些宏定義在/include/configs/smdk2410.h中:
#define CFG_MALLOC_LEN    (CFG_ENV_SIZE + 128*1024)        ;;64K+128K=0xC0
#define CFG_ENV_SIZE    0x10000        /* Total Size of Environment Sector 64k*/
#define CONFIG_STACKSIZE    (128*1024)    /* regular stack 128k */
#define CFG_GBL_DATA_SIZE     128    /* size in bytes reserved for initial data */
用0x33F8000 – 0xC0 – 0x80得到_TEXT_BASE向下(低地址)的堆棧指針sp的起點地址
       sub sp, r0, #12 /* 爲abort-stack預留3個字 */    ;;得到最終sp指針初始值
clear_bss:
       ldr r0, _bss_start /* 找到bss段起始地址 */
       ldr r1, _bss_end /* bss段末尾地址 */
       mov r2, #0x00000000 /* 清零 */
clbss_l:str r2, [r0] /* bss段地址空間清零循環... */
       add r0, r0, #4
       cmp r0, r1
       bne clbss_l
       /* 跳轉到start_armboot函數入口,_start_armboot字保存函數入口指針 */
       ldr pc, _start_armboot
_start_armboot: .word start_armboot ;;start_armboot函數在lib_arm/board.c中實現
 
2.Stage2:lib_arm/board.c
    此文件是u-boot Stage2部分,入口爲Stage1最後調用的start_armboot函數。注意上面最後ldr到pc的是_start_armboot這個地址,而非start_armboot變量。
    start_armboot是U-Boot執行的第一個C語言函數,完成系統初始化工作,進入主循環,處理用戶輸入的命令。
 

void start_armboot (void)
{
       DECLARE_GLOBAL_DATA_PTR;
//此宏定義了一個gd_t類型的指針 *gd,並指名用r8寄存器來存儲:

#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
       ulong size;
       init_fnc_t **init_fnc_ptr;
       char *s;
       /* Pointer is writable since we allocated a register for it     上面那個宏的作用*/
       gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
//此C語句引用的是start.S中的地址標號_armboot_start,但是得到的卻是其中所指的變量_start的值(在RAM中,_start = 0x33F80000)。    Ps:    _armboot_start:    .word _start

//gd是全局變量,位置在堆棧區以下(低地址):

typedef struct global_data {
    bd_t *bd;
    unsigned long flags;
    unsigned long baudrate;
    unsigned long have_console; /* serial_init() was called */
    unsigned long reloc_off; /* Relocation Offset */        //此變量有什麼用?

    unsigned long env_addr; /* Address of Environment struct */
    unsigned long env_valid; /* Checksum of Environment valid? */
    unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
    unsigned char vfd_type; /* display type */
#endif
#if 0
    unsigned long cpu_clk; /* CPU clock in Hz! */
    unsigned long bus_clk;
    unsigned long ram_size; /* RAM size */
    unsigned long reset_status; /* reset status register at boot */
#endif
    void **jt; /* jump table */
} gd_t;
       /* compiler optimization barrier needed for GCC >= 3.4 */
       __asm__ __volatile__("": : :"memory");
       memset ((void*)gd, 0, sizeof (gd_t));
       gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));    //得到bd的起點

       memset (gd->bd, 0, sizeof (bd_t));
       monitor_flash_len = _bss_start - _armboot_start;
       /* 順序執行init_sequence數組中的初始化函數 */
       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
              if ((*init_fnc_ptr)() != 0) {
                      hang ();
              }
       }
       /*配置可用的Flash */
       size = flash_init ();        //初始化Nor flash的函數,函數實現在下面

       display_flash_config (size);    //打印到控制檯:Flash: 512 kB

       /* _armboot_start 在u-boot.lds鏈接腳本中定義 */
       mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);        //將CFG_MALLOC_LEN區域用memset函數清零(直接往目的地址寫0)

       /* 配置環境變量,重新定位 */
       env_relocate ();        //剛纔的初始化函數中有一個是env_init(),根據CRC校驗來初始化gd->env_addr變量(自己設定的還是初始值),此函數是作用是將環境變量值從某個flash和RAM之間的拷貝。下圖描述了ENV的初始化過程:

 

/* 從環境變量中獲取IP地址,放到全局變量gd中 */
       gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
       /* 以太網接口MAC 地址,放到全局變量gd中,實現過程有待研究*/
          {
        int i;
        ulong reg;
        char *s, *e;
        uchar tmp[64];

        i = getenv_r ("ethaddr", tmp, sizeof (tmp));
        s = (i > 0) ? tmp : NULL;

        for (reg = 0; reg < 6; ++reg) {
            gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
            if (s)
                s = (*e) ? e + 1 : e;
        }
    }
       devices_init (); /* 獲取列表中的設備 */
       jumptable_init ();
       console_init_r (); /* 完整地初始化控制檯設備 */
       enable_interrupts (); /* 使能例外處理 */
       /* 通過環境變量初始化 */
       if ((s = getenv ("loadaddr")) != NULL) {
               load_addr = simple_strtoul (s, NULL, 16);
       }
//以上幾個初始化函數有待研究

       /* main_loop()總是試圖自動啓動,循環不斷執行 */
       for (;;) {
               main_loop (); /* 主循環函數處理執行用戶命令 -- common/main.c */
       }
       /* NOTREACHED - no way out of command loop except booting */
}

    以上總體的瀏覽了u-boot的啓動過程,搞的比較亂,以後有空再修改整理一下,有幾個地方還有待弄清,還有Stage2中的多個初始化函數,待續。。。

 

原文地址:http://blog.chinaunix.net/u1/58780/showart_458430.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章