vboot完全解读

来源:互联网 发布:地物波谱数据库 编辑:程序博客网 时间:2024/06/11 10:52

载自:http://www.cnblogs.com/lknlfy/archive/2012/08/25/2655743.html


上半个月在学习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    UndefEntryPoint11 12 @ 0x08: Software interrupt exception13 SWIEntryPoint:14     b    SWIEntryPoint15 16 @ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort)17 PrefetchAbortEnteryPoint:18     b    PrefetchAbortEnteryPoint19 20 @ 0x10: Data Access Memory Abort21 DataAbortEntryPoint:22     b    DataAbortEntryPoint23 24 @ 0x14: Not used25 NotUsedEntryPoint:26     b    NotUsedEntryPoint27 28 @ 0x18: IRQ(Interrupt Request) exception29 IRQEntryPoint:30     b    IRQHandle31 32 @ 0x1c: FIQ(Fast Interrupt Request) exception33 FIQEntryPoint:34     b    FIQEntryPoint35 36 @0x20: Fixed address global value. will be replaced by downloader.37 38     .long ZBOOT_MAGIC39     .byte OS_TYPE, HAS_NAND_BIOS, (LOGO_POS & 0xFF), ((LOGO_POS >>8) &0xFF)40     .long OS_START41     .long OS_LENGTH42     .long OS_RAM_START43     .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_BASE10     mov    r2, #0xffffffff11     str    r2, [r1, #oINTMSK]12     ldr    r2, =0x7ff13     str    r2, [r1, #oINTSUBMSK]    14 15     @ 初始化系统时钟16     mov    r1, #CLK_CTL_BASE17     mvn    r2, #0xff00000018     str    r2, [r1, #oLOCKTIME]   @设置LOCKTIME寄存器19     20     mov    r1, #CLK_CTL_BASE21     ldr    r2, clkdivn_value22     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 register27 28     mov    r1, #CLK_CTL_BASE29     ldr r2, =S3C2440_UPLL_48MHZ_Fin12MHz30     str r2, [r1, #oUPLLCON]31 32     nop33     nop34     nop35     nop36     nop37     nop38     nop39     nop40     nop41     42     ldr    sp, DW_STACK_START           @ setup stack pointer43 44     ldr     r2, mpll_value_USER         @ clock user set 12MHz45     str    r2, [r1, #oMPLLCON]46     bl    memsetup47 48     @ set GPIO for UART49     mov    r1, #GPIO_CTL_BASE50     add    r1, r1, #oGPIO_H51     ldr    r2, gpio_con_uart    52     str    r2, [r1, #oGPIO_CON]53     ldr    r2, gpio_up_uart54     str    r2, [r1, #oGPIO_UP]    55     bl    InitUART56 57 58     @ get read to call C functions59     mov    fp, #0            @ no previous frame, so fp=060     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], #410     cmp    r1, r311     bne    1b12     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 functions12     mov    fp, #0            @ no previous frame, so fp=013     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 Size11     //内核在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 badblock18         //坏块检测19         for (;;) {20             if (NandIsGoodBlock21                 (BlockNum <<22                  (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT))) {23                 break;24             }25             BlockNum++;    //try next26         }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     //内存页的大小4K46     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开始入手。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 淘宝买家确认收货超时怎么办 淘宝没收到货退款卖家不处理怎么办 微博红包都是字怎么办 500个访客没转化怎么办 店铺动态评分是0怎么办 京东店铺评分低怎么办 被淘宝主播屏蔽怎么办 在淘宝客推广后退款怎么办 生产出现异常时你应该怎么办 违规后的店铺没访客怎么办 淘宝少发货店家不承认怎么办 淘宝买东西店家不发货怎么办 淘宝店家拒绝同意退款怎么办 被淘宝店家骂了怎么办 淘宝买家骂店家骚扰店家怎么办? 不想开淘宝店了怎么办 我是客服经常有客户骂人怎么办 淘宝直播前期没人看怎么办 淘宝被投诉商标侵权怎么办 淘宝后商家页面打不开了怎么办 淘宝遇到职业打假人怎么办 发票被复写上字怎么办 淘宝直播广告图片的商品怎么办 美团商家排名低怎么办 想成为淘宝主播怎么办 用移动流量很卡怎么办 淘宝店铺被恶意刷流量怎么办 一个想要公司权利的人怎么办 淘宝商家短信推广告怎么办 在群里乱发信息怎么办 支付宝扫不了码怎么办 淘宝商家收款不发货怎么办 淘宝申请退款商家拒绝怎么办 一件代发找不到供货商怎么办 淘宝京东兼职上当怎么办 退货时快递丢件怎么办 淘宝店铺代销1688有订单怎么办 供应商已解除合作无法代销怎么办 被代运营骗了怎么办 被淘宝运营骗了怎么办 淘宝运营公司骗了怎么办