接vboot详解一…

第二个阶段主要是C语言编写的程序,主要文件为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(
    unsupport NAND
    );   
  11.         for(;;);   
  12.     }   
  13.   
  14.     GetParameters();   
  15.   
  16.     Uart_SendString(“load Image of Linux…

    );   

  17.     ReadImageFromNand();   
  18. }  

其中第3行、第4行是为了使能ICache和DCache,这是在MMU中学到的,之前在学习的过程中由于没有对MMU进行学习,暂且没有进行更深入的研究。通过Source Insight可以看出详细代码为内嵌GNU汇编。

第6行,初始化了一些IO口。

第7行,初始化了Nand Flash存储器。主要实现都在nand.c文件中。

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

主要是设置了Nand Flash的时序参数,页的大小,位宽等,这样初始化以后才可以读写Nand Flash。
接下来返回到Main函数中的GetParameters函数中

  1. static inline void GetParameters(void)   
  2. {   
  3.     U32 Buf[2048];   
  4.     g_os_type = OS_LINUX;   //内核操作系统类型
  5.     g_os_start = 0×60000;   //内核在Flash中的起始地址
  6.     g_os_length = 0×500000;   //内核影像的大小
  7.   
  8.     g_os_ram_start = 0×30008000;   //内核拷贝SDRAM中的地址
  9.   
  10.     // vivi LINUX CMD LINE   从flash的参数分区中读取命令行参数,8个字节。
  11.     NandReadOneSector((U8 *)Buf, 0×48000);   
  12.     if (Buf[0] == 0×49564956 && Buf[1] == 0x4C444D43) {   
  13.         memcpy(g_linux_cmd_line, (char *)&(Buf[2]), sizeof g_linux_cmd_line);   
  14.     }   
  15. }  

上述代码主要设置了内核映像在nand flash的起始地址和大小,还有设置内核映像被拷贝到ram的起始地址。其中g_os_type都是一个宏定义,最终都是赋值给了first_sector这样一个结构体,具体可以从源码中看到,在Parameters.h中。

命令行参数是通过BIOS(nor flash里的supervivi)写到nand flash的0×40000地址处,通过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. }  

这里读取一个扇区是根据page的类型来的,由于我学习采用的板子是友善之臂提供的Mini2440,Nand flash大小为256MB,所以看第9行,PAGE2048,即每一页为2048个字节。

  1. static inline int NandReadOneSectorP2048(U8 * buffer, U32 addr)   
  2. {   
  3.     U32 sector;   
  4.     sector = addr >> 11;   
  5.   
  6.     delay();   
  7.     NandReset();   
  8. #if 0  
  9.     NF_RSTECC();   
  10.     NF_MECC_UnLock();   
  11. #endif   
  12.     NF_nFCE_L();   
  13.   
  14.     NF_CLEAR_RB();   
  15.     NF_CMD(0×00);   
  16.   
  17.     NF_ADDR(0×00);   
  18.     NF_ADDR(0×00);   
  19.     NF_ADDR(sector & 0xff);   
  20.     NF_ADDR((sector >> 8) & 0xff);   
  21.     NF_ADDR((sector >> 16) & 0xff);   
  22.     NF_CMD(0×30);   
  23.   
  24.     delay();   
  25.     NF_DETECT_RB();   
  26.   
  27.     ReadPage512(buffer + 0 * 512, &NFDATA);   
  28.     ReadPage512(buffer + 1 * 512, &NFDATA);   
  29.     ReadPage512(buffer + 2 * 512, &NFDATA);   
  30.     ReadPage512(buffer + 3 * 512, &NFDATA);   
  31.   
  32. #if 0  
  33.     NF_MECC_Lock();   
  34. #endif   
  35.     NF_nFCE_H();   
  36.   
  37.     return 1;   
  38. }  

这个函数,先是设置了对nandflash的读使能操作,并且设置了读的起始地址,最后调用4个ReadPage512函数,根据定义可以到它是由汇编实现的,在head.S中如下:

  1. ReadPage512:   
  2.     stmfd   sp!, {r2-r7}   //将r2-r7寄存器入栈
  3.     mov r2, #0×200  
  4. 1:   
  5.     ldr r4, [r1]        @every execute r1 will +4  
  6.     ldr r5, [r1]   
  7.     ldr r6, [r1]   
  8.     ldr r7, [r1]   
  9.     stmia   r0!, {r4-r7}   
  10.     ldr r4, [r1]   
  11.     ldr r5, [r1]   
  12.     ldr r6, [r1]   
  13.     ldr r7, [r1]   
  14.     stmia   r0!, {r4-r7}   
  15.     ldr r4, [r1]   
  16.     ldr r5, [r1]   
  17.     ldr r6, [r1]   
  18.     ldr r7, [r1]   
  19.     stmia   r0!, {r4-r7}   
  20.     ldr r4, [r1]   
  21.     ldr r5, [r1]   
  22.     ldr r6, [r1]   
  23.     ldr r7, [r1]   
  24.     stmia   r0!, {r4-r7}
  25.     subs    r2, r2, #64  //每次读64个字节=4*4*4
  26.     bne 1b;   
  27.     ldmfd   sp!, {r2-r7}   //恢复r2-r7寄存器的值
  28.     mov pc,lr  //函数返回

接下来返回到Main函数中的,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 *)0×30000100;   
  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 *)(0×30008024);   
  52.          if (pp[0] == 0x016f2818) {  //zImage的魔数,在内核中定义   
  53.              //Uart_SendString(“

    Ok

    ”);   

  54.          } else {   
  55.              Uart_SendString(

    Wrong Linux Kernel

    );   

  56.              for (;;) ;   
  57.          }   
  58.     
  59.      }   
  60.       asm (   
  61.          “mov    r5, %2
      
  62.          “mov    r0, %0
      
  63.          “mov    r1, %1
      
  64.          “mov    ip, #0
      
  65.          “mov    pc, r5
      
  66.          “nop
     “nop
    :    /* no outpus */  
  67.          :“r”(0), “r”(782), “r”(g_os_ram_start)   
  68.      );   
  69.  }  

这段代码我们可以看到是定义了一个struct param_struct结构体,可以得出,vboot采用的是旧的引导方式,新的是采用tag方式,具体可以看一下相关的书籍。

其中61-67行是内核的一些规定,第65行设置了pc为内核映像的起始地址,然后短延迟,直接跳入到内核映像中执行,开始引导内核。

最后总结,vboot是一个十分精简的bootloader程序,只能在nand flash中启动,目前只支持S3C2440的芯片,只有内核功能,没有uboot那样强大的命令,推荐和我一样的新手朋友可以学习一下.

使用vboot可以执行采用arm-linux-gcc编译器,交叉编译即可,make之后生成vboot.bin,通过supervivi等BIOS烧到Nand flash中。最后附上,vboot的下载地址:文件名:vboot-src-20100727.tar.gz, 访问地址:http://www.kuaipan.cn/file/id_22375181884326190.htm

参考资料:友善之臂文档以及http://www.cnblogs.com/lknlfy/archive/2012/08/25/2655743.html

Bootloader之vboot详解(二)

发表评论

电子邮件地址不会被公开。 必填项已用*标注