vboot完全解讀


          上半個月在學習bootloader,突然找到了一個非常好的vboot,vboot只有最基本的內核引導功能(基於s3c2440,從nand flash啟動),對其深入研究后,發現對bootloader有了比較全面的理解,雖然沒有像uboot那么多功能,但vboot已經實現了bootloader最核心的功能,其他像什么網絡功能、燒寫功能等等也只是一些裸機驅動而已。學習bootloader需要有匯編的基礎,如果有單片機編程經驗的話那更是“如魚得水”了。

       先看vboot的整體架構,下面是vboot包含的所有文件:

很簡單是吧,其中核心的文件是head.S、main.c和nand.c,vboot.bin已經是編譯出來的二進制文件,用於燒寫在nand flash里。先看mem.lds文件,這是一個鏈接腳本,從那里可以找到程序的入口:

1 SECTIONS { 
2   . = 000000;
3   .myhead ALIGN(0): {*(.text.FirstSector)}
4   .text ALIGN(512): { *(.text) }
5   .bss ALIGN(4)  : { *(.bss*)  *(COMMON) }
6   .data ALIGN(4) : { *(.data*) *(.rodata*) }
7 } 

比較簡單,程序入口位於text.FirstSector這個段里(因為程序是從nand flash的0地址開始執行的),它在head.S文件里定義:

 1     .section .text.FirstSector
 2     .globl first_sector
 3 
 4 first_sector:
 5 @ 0x00: Reset
 6     b    Reset
 7 
 8 @ 0x04: Undefined instruction exception
 9 UndefEntryPoint:
10     b    UndefEntryPoint
11 
12 @ 0x08: Software interrupt exception
13 SWIEntryPoint:
14     b    SWIEntryPoint
15 
16 @ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort)
17 PrefetchAbortEnteryPoint:
18     b    PrefetchAbortEnteryPoint
19 
20 @ 0x10: Data Access Memory Abort
21 DataAbortEntryPoint:
22     b    DataAbortEntryPoint
23 
24 @ 0x14: Not used
25 NotUsedEntryPoint:
26     b    NotUsedEntryPoint
27 
28 @ 0x18: IRQ(Interrupt Request) exception
29 IRQEntryPoint:
30     b    IRQHandle
31 
32 @ 0x1c: FIQ(Fast Interrupt Request) exception
33 FIQEntryPoint:
34     b    FIQEntryPoint
35 
36 @0x20: Fixed address global value. will be replaced by downloader.
37 
38     .long ZBOOT_MAGIC
39     .byte OS_TYPE, HAS_NAND_BIOS, (LOGO_POS & 0xFF), ((LOGO_POS >>8) &0xFF)
40     .long OS_START
41     .long OS_LENGTH
42     .long OS_RAM_START
43     .string LINUX_CMD_LINE

第5~34行的作用是安裝異常向量表,在這里除了復位,其他異常都沒有定義具體的執行代碼。

 1 .section .text
 2 Reset:
 3     @ 關閉看門狗
 4     mov    r1, #0x53000000
 5     mov    r2, #0x0
 6     str    r2, [r1]
 7 
 8     @ 關閉中斷
 9     mov    r1, #INT_CTL_BASE
10     mov    r2, #0xffffffff
11     str    r2, [r1, #oINTMSK]
12     ldr    r2, =0x7ff
13     str    r2, [r1, #oINTSUBMSK]    
14 
15     @ 初始化系統時鍾
16     mov    r1, #CLK_CTL_BASE
17     mvn    r2, #0xff000000
18     str    r2, [r1, #oLOCKTIME]   @設置LOCKTIME寄存器
19     
20     mov    r1, #CLK_CTL_BASE
21     ldr    r2, clkdivn_value
22     str    r2, [r1, #oCLKDIVN]    @設置分頻寄存器
23 
24     mrc    p15, 0, r1, c1, c0, 0        @ read ctrl register 
25     orr    r1, r1, #0xc0000000          @ Asynchronous 異步總線模式 
26     mcr    p15, 0, r1, c1, c0, 0        @ write ctrl register
27 
28     mov    r1, #CLK_CTL_BASE
29     ldr r2, =S3C2440_UPLL_48MHZ_Fin12MHz
30     str r2, [r1, #oUPLLCON]
31 
32     nop
33     nop
34     nop
35     nop
36     nop
37     nop
38     nop
39     nop
40     nop
41     
42     ldr    sp, DW_STACK_START           @ setup stack pointer
43 
44     ldr     r2, mpll_value_USER         @ clock user set 12MHz
45     str    r2, [r1, #oMPLLCON]
46     bl    memsetup
47 
48     @ set GPIO for UART
49     mov    r1, #GPIO_CTL_BASE
50     add    r1, r1, #oGPIO_H
51     ldr    r2, gpio_con_uart    
52     str    r2, [r1, #oGPIO_CON]
53     ldr    r2, gpio_up_uart
54     str    r2, [r1, #oGPIO_UP]    
55     bl    InitUART
56 
57 
58     @ get read to call C functions
59     mov    fp, #0            @ no previous frame, so fp=0
60     mov    a2, #0            @ set argv to NULL 
61 
62     bl    Main            
63 
64 1:    b    1b @

第4~6行,關閉看門狗,以免系統不斷復位;第9~13行,關閉中斷;第16~18行,設置系統時鍾穩定(鎖定)時間;第20~22行,設置時鍾分頻比為1:4:8(FCLK:HCLK:PCLK);第24~26行,設置為異步總線模式(因為FCLK已經不等於HCLK);第28~30,行,設置UPLL為48MHZ,用於USB通信;第42行,設置棧指針,為下面調用c程序做准備;第44~45行,設置FCLK為400MHZ,那么HCLK=100MHZ,PCLK=50MHZ;第46行,跳到內存初始化程序:

 1 memsetup:
 2     @ initialise the static memory 
 3 
 4     @ set memory control registers
 5     mov    r1, #MEM_CTL_BASE
 6     adrl    r2, mem_cfg_val
 7     add    r3, r1, #52       @13*4
 8 1:  ldr    r4, [r2], #4
 9     str    r4, [r1], #4
10     cmp    r1, r3
11     bne    1b
12     mov    pc, lr

2440總共有13個設置內存的寄存器,因此第7行的立即數是52(13*4);第8~11行,通過循環設置13個寄存器的值。返回到memsetup下面的代碼:

 1 @ set GPIO for UART
 2     mov    r1, #GPIO_CTL_BASE
 3     add    r1, r1, #oGPIO_H
 4     ldr    r2, gpio_con_uart    
 5     str    r2, [r1, #oGPIO_CON]
 6     ldr    r2, gpio_up_uart
 7     str    r2, [r1, #oGPIO_UP]    
 8     bl    InitUART
 9 
10 
11     @ get read to call C functions
12     mov    fp, #0            @ no previous frame, so fp=0
13     mov    a2, #0            @ set argv to NULL 
14 
15     bl    Main            
16 
17 1:    b    1b @

第2~8行,用於初始化串口(115200bps,8N1);第12~13行,設置兩個arm寄存器;第15行,跳到Main函數執行。在main.c文件里:

 1 void Main(void)
 2 {
 3     MMU_EnableICache();
 4     MMU_EnableDCache();
 5 
 6     Port_Init();
 7     NandInit();
 8 
 9     if (g_page_type == PAGE_UNKNOWN) {
10         Uart_SendString("\r\nunsupport NAND\r\n");
11         for(;;);
12     }
13 
14     GetParameters();
15 
16     Uart_SendString("loading Image of Linux from Nand Flash...\n\r");
17     ReadImageFromNand();
18 }

第3~4行,使能Dcache和Icache:

static inline void MMU_EnableICache(void)
{

    asm (
        "mrc p15,0,r0,c1,c0,0\n"
        "orr r0,r0,#(1<<12)\n"
        "mcr p15,0,r0,c1,c0,0\n"
    );
}

static inline void MMU_EnableDCache(void)
{
    asm (
        "mrc p15,0,r0,c1,c0,0\n"
        "orr r0,r0,#(1<<2)\n"
        "mcr p15,0,r0,c1,c0,0\n"
    );
}

第6行,初始化一些IO口(沒用到);第7行,初始化nand flash控制器,在nand.c文件里定義:

void NandInit(void)
{
    NFCONF = (TACLS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4) | (0 << 0);
    NFCONT =
        (0 << 13) | (0 << 12) | (0 << 10) | (0 << 9) | (0 << 8) | (0 << 6) |
        (0 << 5) | (1 << 4) | (1 << 1) | (1 << 0);
    NFSTAT = 0;
    NandReset();
    NandCheckId();
}

設置具體nand flash芯片的時序參數、頁的大小和位寬等,初始化之后,就可以讀寫nand flash了。回到Main函數的第14行調用的GetParameters()函數的定義:

static inline void GetParameters(void)
{
    U32 Buf[2048];
    g_os_type = OS_LINUX;
    //內核在flash中的起始地址
    g_os_start = 0x50000;
    //內核映像的大小
    g_os_length = 0x300000;
    //內核被拷貝到內存的起始地址
    g_os_ram_start = 0x30008000;

    // vivi LINUX CMD LINE
    //從flash的參數分區中讀命令行參數
    NandReadOneSector((U8 *)Buf, 0x40000);
    if (Buf[0] == 0x49564956 && Buf[1] == 0x4C444D43) {
        memcpy(g_linux_cmd_line, (char *)&(Buf[2]), sizeof g_linux_cmd_line);
    }
}

設置了內核映像在nand flash的起始地址和大小,還有設置內核映像被拷貝到ram的起始地址,命令行參數是通過BIOS(nor flash里的supervivi)寫到nand flash的0x40000地址處,通過NandReadOneSector()把它讀出來,其中Buf[0]、Buf[1]這兩個值是“暗藏值”,是對應於具體的BIOS的,是由BIOS寫進去的,位於命令行參數的第一和第二個字,因為BIOS的代碼不不開源的,無法修改,所以移植vboot的時候只要是用這個BIOS來燒寫vboot就不用修改兩個值(不用太糾結,我曾糾結了很久)。從memcpy()函數也可以知道,Buf[0]和Buf[1]這兩個值是用來識別具體的BIOS的,沒用於命令行參數。現在看NandReadOneSector()函數:

 1 int NandReadOneSector(U8 * buffer, U32 addr)
 2 {
 3     int ret;
 4     
 5     switch(g_page_type) {
 6     case PAGE512:
 7         ret = NandReadOneSectorP512(buffer, addr);
 8         break;
 9     case PAGE2048:
10         ret = NandReadOneSectorP2048(buffer, addr);
11         break;
12     default:
13         for(;;);
14     }
15     return ret;
16 }

因為我板子(GT2440)上的nand flash是64M的,頁的大小為512字節,所以看第7行的調用:

static inline int NandReadOneSectorP512(U8 * buffer, U32 addr)
{
    U32 sector;
    sector = addr >> 9;

    NandReset();
#if 0
    NF_RSTECC();
    NF_MECC_UnLock();
#endif
    NF_nFCE_L();

    NF_CLEAR_RB();
    NF_CMD(0x00);

    NF_ADDR(0x00);
    NF_ADDR(sector & 0xff);
    NF_ADDR((sector >> 8) & 0xff);
    NF_ADDR((sector >> 16) & 0xff);

    delay();
    NF_DETECT_RB();

    ReadPage512(buffer, &NFDATA);
#if 0
    NF_MECC_Lock();
#endif
    NF_nFCE_H();

    return 1;
}

該函數里前面那些是設置讀操作,設置讀起始地址,核心是調用ReadPage512()函數,它由匯編實現,在head.S里:

 1 .globl ReadPage512
 2 
 3 ReadPage512:
 4     stmfd    sp!, {r2-r7} @ 將r2~r7寄存器的值壓棧
 5     mov    r2, #0x200   @ 512個字節
 6 
 7 1:
 8     ldr    r4, [r1]
 9     ldr    r5, [r1]
10     ldr    r6, [r1]
11     ldr    r7, [r1]
12     stmia    r0!, {r4-r7}
13     ldr    r4, [r1]
14     ldr    r5, [r1]
15     ldr    r6, [r1]
16     ldr    r7, [r1]
17     stmia    r0!, {r4-r7}
18     ldr    r4, [r1]
19     ldr    r5, [r1]
20     ldr    r6, [r1]
21     ldr    r7, [r1]
22     stmia    r0!, {r4-r7}
23     ldr    r4, [r1]
24     ldr    r5, [r1]
25     ldr    r6, [r1]
26     ldr    r7, [r1]
27     stmia    r0!, {r4-r7}
28     subs    r2, r2, #64  @ 一次循環讀64個字節
29     bne    1b;
30     ldmfd    sp!, {r2-r7} @ 恢復r2~r7寄存器的值
31     mov    pc,lr @ 返回

挺好懂的,不多解析。再回到Main()函數的17行(最后一個函數調用)調用ReadImageFromNand():

 1 void ReadImageFromNand(void)
 2 {
 3     unsigned int Length;
 4     U8 *RAM;
 5     unsigned BlockNum;
 6     unsigned pos;
 7 
 8     Length = g_os_length;
 9     //內核的大小(單位:塊)
10     Length = (Length + BLOCK_SIZE - 1) >> (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT) << (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT); // align to Block Size
11     //內核在flash中的第幾塊
12     BlockNum = g_os_start >> (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT);
13     //要拷貝到的起始地址
14     RAM = (U8 *) g_os_ram_start;
15     for (pos = 0; pos < Length; pos += BLOCK_SIZE) {
16         unsigned int i;
17         // skip badblock
18         //壞塊檢測
19         for (;;) {
20             if (NandIsGoodBlock
21                 (BlockNum <<
22                  (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT))) {
23                 break;
24             }
25             BlockNum++;    //try next
26         }
27         for (i = 0; i < BLOCK_SIZE; i += SECTOR_SIZE) {
28             int ret =
29                 NandReadOneSector(RAM,
30                           (BlockNum <<
31                            (BYTE_SECTOR_SHIFT +
32                         SECTOR_BLOCK_SHIFT)) + i);
33             RAM += SECTOR_SIZE;
34             ret = 0;
35 
36         }
37 
38         BlockNum++;
39     }
40 
41     CallLinux();
42 }

主要是從nand flash里把內核映像一塊一塊地讀到ram里,每讀一塊之前先進行壞塊檢測,如果是壞塊就跳過,繼續讀下一塊(這里的壞塊檢測是一個比較粗略的檢測方法),直到把整個內核映像讀到ram里面。這里內核映像的大小設置為3M(實際上不到3M),因此讀也是讀3M大小到ram里面。最后該函數的第41行調用CallLinux():

 1 static void CallLinux(void)
 2 {
 3     struct param_struct {
 4         union {
 5             struct {
 6                 unsigned long page_size;    /*  0 */
 7                 unsigned long nr_pages;    /*  4 */
 8                 unsigned long ramdisk_size;    /*  8 */
 9                 unsigned long flags;    /* 12 */
10                 unsigned long rootdev;    /* 16 */
11                 unsigned long video_num_cols;    /* 20 */
12                 unsigned long video_num_rows;    /* 24 */
13                 unsigned long video_x;    /* 28 */
14                 unsigned long video_y;    /* 32 */
15                 unsigned long memc_control_reg;    /* 36 */
16                 unsigned char sounddefault;    /* 40 */
17                 unsigned char adfsdrives;    /* 41 */
18                 unsigned char bytes_per_char_h;    /* 42 */
19                 unsigned char bytes_per_char_v;    /* 43 */
20                 unsigned long pages_in_bank[4];    /* 44 */
21                 unsigned long pages_in_vram;    /* 60 */
22                 unsigned long initrd_start;    /* 64 */
23                 unsigned long initrd_size;    /* 68 */
24                 unsigned long rd_start;    /* 72 */
25                 unsigned long system_rev;    /* 76 */
26                 unsigned long system_serial_low;    /* 80 */
27                 unsigned long system_serial_high;    /* 84 */
28                 unsigned long mem_fclk_21285;    /* 88 */
29             } s;
30             char unused[256];
31         } u1;
32         union {
33             char paths[8][128];
34             struct {
35                 unsigned long magic;
36                 char n[1024 - sizeof(unsigned long)];
37             } s;
38         } u2;
39         char commandline[1024];
40     };
41     //啟動參數在內存的起始地址
42     struct param_struct *p = (struct param_struct *)0x30000100;
43     memset(p, 0, sizeof(*p));
44     memcpy(p->commandline, g_linux_cmd_line, sizeof(g_linux_cmd_line));
45     //內存頁的大小4K
46     p->u1.s.page_size = 4 * 1024;
47     //內存總共有多少頁
48     p->u1.s.nr_pages = 64 * 1024 * 1024 / (4 * 1024);
49 
50     {
51         unsigned int *pp = (unsigned int *)(0x30008024);
52         if (pp[0] == 0x016f2818) {  //zImage的魔數,在內核中定義
53             //Uart_SendString("\n\rOk\n\r");
54         } else {
55             Uart_SendString("\n\rWrong Linux Kernel\n\r");
56             for (;;) ;
57         }
58 
59     }
60      asm (
61         "mov    r5, %2\n"
62         "mov    r0, %0\n"
63         "mov    r1, %1\n"
64         "mov    ip, #0\n"
65         "mov    pc, r5\n"
66         "nop\n" "nop\n":    /* no outpus */
67         :"r"(0), "r"(782), "r"(g_os_ram_start)
68     );
69 }

首先定義了一個struct param_struct結構體變量,從這里就可以看出,vboot用的是舊的方式(新的是用tag方式),struct param_struct與內核里定義的一樣。第41~59行,看注釋可以明白,第60~67行,是內核的一些約定:

R0 = 0

R1 = 機器ID

.....

最后第65行,設置pc為內核映像在內存中的起始地址,直接跳到內核映像的入口,從而開始內核代碼的執行......

 

總結:

      vboot是一個十分精簡的bootloader,從nand flash啟動,目前只支持2440 Linux,只有引導內核的功能,它的編譯后的二進制文件不會超過4K(這是由2440從nand flash啟動所限制的),編譯vboot只需要在代碼目錄下執行make,便可生成vboot.bin文件,通過BIOS將它燒寫到nand flash里。強烈推薦想學習ARM bootloader的同學從vboot開始入手。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM