接vboot详解一…
第二个阶段主要是C语言编写的程序,主要文件为main.c,入口代码如下:
- void Main(void)
- {
- MMU_EnableICache();
- MMU_EnableDCache();
- Port_Init();
- NandInit();
- if (g_page_type == PAGE_UNKNOWN) {
- Uart_SendString(“
unsupport NAND
”); - for(;;);
- }
- GetParameters();
- Uart_SendString(“load Image of Linux…
”
); - ReadImageFromNand();
- }
其中第3行、第4行是为了使能ICache和DCache,这是在MMU中学到的,之前在学习的过程中由于没有对MMU进行学习,暂且没有进行更深入的研究。通过Source Insight可以看出详细代码为内嵌GNU汇编。
第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函数中的GetParameters函数中
- static inline void GetParameters(void)
- {
- U32 Buf[2048];
- g_os_type = OS_LINUX; //内核操作系统类型
- g_os_start = 0×60000; //内核在Flash中的起始地址
- g_os_length = 0×500000; //内核影像的大小
- g_os_ram_start = 0×30008000; //内核拷贝SDRAM中的地址
- // vivi LINUX CMD LINE 从flash的参数分区中读取命令行参数,8个字节。
- NandReadOneSector((U8 *)Buf, 0×48000);
- if (Buf[0] == 0×49564956 && Buf[1] == 0x4C444D43) {
- memcpy(g_linux_cmd_line, (char *)&(Buf[2]), sizeof g_linux_cmd_line);
- }
- }
上述代码主要设置了内核映像在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()函数:
- int NandReadOneSector(U8 * buffer, U32 addr)
- {
- int ret;
- switch(g_page_type) {
- case PAGE512:
- ret = NandReadOneSectorP512(buffer, addr);
- break;
- case PAGE2048:
- ret = NandReadOneSectorP2048(buffer, addr);
- break;
- default:
- for(;;);
- }
- return ret;
- }
这里读取一个扇区是根据page的类型来的,由于我学习采用的板子是友善之臂提供的Mini2440,Nand flash大小为256MB,所以看第9行,PAGE2048,即每一页为2048个字节。
- static inline int NandReadOneSectorP2048(U8 * buffer, U32 addr)
- {
- U32 sector;
- sector = addr >> 11;
- delay();
- NandReset();
- #if 0
- NF_RSTECC();
- NF_MECC_UnLock();
- #endif
- NF_nFCE_L();
- NF_CLEAR_RB();
- NF_CMD(0×00);
- NF_ADDR(0×00);
- NF_ADDR(0×00);
- NF_ADDR(sector & 0xff);
- NF_ADDR((sector >> 8) & 0xff);
- NF_ADDR((sector >> 16) & 0xff);
- NF_CMD(0×30);
- delay();
- NF_DETECT_RB();
- ReadPage512(buffer + 0 * 512, &NFDATA);
- ReadPage512(buffer + 1 * 512, &NFDATA);
- ReadPage512(buffer + 2 * 512, &NFDATA);
- ReadPage512(buffer + 3 * 512, &NFDATA);
- #if 0
- NF_MECC_Lock();
- #endif
- NF_nFCE_H();
- return 1;
- }
这个函数,先是设置了对nandflash的读使能操作,并且设置了读的起始地址,最后调用4个ReadPage512函数,根据定义可以到它是由汇编实现的,在head.S中如下:
- ReadPage512:
- stmfd sp!, {r2-r7} //将r2-r7寄存器入栈
- mov r2, #0×200
- 1:
- ldr r4, [r1] @every execute r1 will +4
- ldr r5, [r1]
- ldr r6, [r1]
- ldr r7, [r1]
- stmia r0!, {r4-r7}
- ldr r4, [r1]
- ldr r5, [r1]
- ldr r6, [r1]
- ldr r7, [r1]
- stmia r0!, {r4-r7}
- ldr r4, [r1]
- ldr r5, [r1]
- ldr r6, [r1]
- ldr r7, [r1]
- stmia r0!, {r4-r7}
- ldr r4, [r1]
- ldr r5, [r1]
- ldr r6, [r1]
- ldr r7, [r1]
- stmia r0!, {r4-r7}
- subs r2, r2, #64 //每次读64个字节=4*4*4
- bne 1b;
- ldmfd sp!, {r2-r7} //恢复r2-r7寄存器的值
- mov pc,lr //函数返回
接下来返回到Main函数中的,ReadImageFromNand(),也就是最后一个函数调用。
- void ReadImageFromNand(void)
- {
- unsigned int Length;
- U8 *RAM;
- unsigned BlockNum;
- unsigned pos;
- Length = g_os_length;
- //内核的大小(单位:块)
- Length = (Length + BLOCK_SIZE - 1) >> (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT) << (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT); // align to Block Size
- //内核在flash中的第几块
- BlockNum = g_os_start >> (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT);
- //要拷贝到的起始地址
- RAM = (U8 *) g_os_ram_start;
- for (pos = 0; pos < Length; pos += BLOCK_SIZE) {
- unsigned int i;
- // skip badblock
- //坏块检测
- for (;;) {
- if (NandIsGoodBlock
- (BlockNum <<
- (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT))) {
- break;
- }
- BlockNum++; //try next
- }
- for (i = 0; i < BLOCK_SIZE; i += SECTOR_SIZE) {
- int ret =
- NandReadOneSector(RAM,
- (BlockNum <<
- (BYTE_SECTOR_SHIFT +
- SECTOR_BLOCK_SHIFT)) + i);
- RAM += SECTOR_SIZE;
- ret = 0;
- }
- BlockNum++;
- }
- CallLinux();
- }
主要是从nand flash里把内核映像一块一块地读到ram里,每读一块之前先进行坏块检测,如果是坏块就跳过,继续读下一块(这里的坏块检测是一个比较粗略的检测方法),直到把整个内核映像读到ram里面。这里内核映像的大小设置为3M(实际上不到3M),因此读也是读3M大小到ram里面。最后该函数的第41行调用CallLinux():
- static void CallLinux(void)
- {
- struct param_struct {
- union {
- struct {
- unsigned long page_size; /* 0 */
- unsigned long nr_pages; /* 4 */
- unsigned long ramdisk_size; /* 8 */
- unsigned long flags; /* 12 */
- unsigned long rootdev; /* 16 */
- unsigned long video_num_cols; /* 20 */
- unsigned long video_num_rows; /* 24 */
- unsigned long video_x; /* 28 */
- unsigned long video_y; /* 32 */
- unsigned long memc_control_reg; /* 36 */
- unsigned char sounddefault; /* 40 */
- unsigned char adfsdrives; /* 41 */
- unsigned char bytes_per_char_h; /* 42 */
- unsigned char bytes_per_char_v; /* 43 */
- unsigned long pages_in_bank[4]; /* 44 */
- unsigned long pages_in_vram; /* 60 */
- unsigned long initrd_start; /* 64 */
- unsigned long initrd_size; /* 68 */
- unsigned long rd_start; /* 72 */
- unsigned long system_rev; /* 76 */
- unsigned long system_serial_low; /* 80 */
- unsigned long system_serial_high; /* 84 */
- unsigned long mem_fclk_21285; /* 88 */
- } s;
- char unused[256];
- } u1;
- union {
- char paths[8][128];
- struct {
- unsigned long magic;
- char n[1024 - sizeof(unsigned long)];
- } s;
- } u2;
- char commandline[1024];
- };
- //启动参数在内存的起始地址
- struct param_struct *p = (struct param_struct *)0×30000100;
- memset(p, 0, sizeof(*p));
- memcpy(p->commandline, g_linux_cmd_line, sizeof(g_linux_cmd_line));
- //内存页的大小4K
- p->u1.s.page_size = 4 * 1024;
- //内存总共有多少页
- p->u1.s.nr_pages = 64 * 1024 * 1024 / (4 * 1024);
- {
- unsigned int *pp = (unsigned int *)(0×30008024);
- if (pp[0] == 0x016f2818) { //zImage的魔数,在内核中定义
- //Uart_SendString(“
Ok
”);
- } else {
- Uart_SendString(“
Wrong Linux Kernel
”
); - for (;;) ;
- }
- }
- asm (
- “mov r5, %2
” - “mov r0, %0
” - “mov r1, %1
” - “mov ip, #0
” - “mov pc, r5
” - “nop
” “nop
”: /* no outpus */ - :“r”(0), “r”(782), “r”(g_os_ram_start)
- );
- }
这段代码我们可以看到是定义了一个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