2410 bootloader分析(二)

来源:互联网 发布:大智慧崩溃数据丢失 编辑:程序博客网 时间:2024/04/29 02:09

转载请注明出处

作者:小马


接下来几行一直到while循环就是打印公司log,设置led闪烁等操作,难度不大, 不分析.

下面就直接进入bootloader菜单功能分析了. 如下图所示:

 

 Bootloader共有七个功能, 每一个功能都涉及到很多知识, 比如0号功能usb下载文件, 就涉及到usb的很多知识,如描述符,枚举等,每一个都可以专门写一篇文章来分析了. 我这里只分析加载操作系统这个功能. 以 加载wince举例.用到的函数是void NandLoadRunW(void).

下面就一步一步跟进去这个函数去剖析操作系统的加载机制.

void NandLoadRunW(void){         printf("Now boot Wince/n");         ClearMemory();         InitNandFlash();         LoadRun(3);}


ClearMemory是把wince本身运行用的内存空间清0, 我觉得这个操作意义不大,因为我试过不要这个操作,系统运行也没有问题, 这段内存应该会被覆盖的,清空也无意义. 不知道我的说法对不对.

 

InitNandFlash的实现如下:

static void InitNandFlash(void){                U32 i;         InitNandCfg();         i = ReadChipId();         if((i==0x9873)||(i==0xec75))                     NandAddr = 0;         else if(i==0xec76)         {                          support=1;                   NandAddr = 1;         }         else {                           puts("Chip id error!!!/n");                   return;         }//      printf("Nand flash status = %x/n", ReadStatus());}


 InitNandCfg初始化nandflash寄存器,它的实现非常简单,就一行.

rNFCONF = (1<<15)|(1<<12)|(1<<11)|(7<<8)|(7<<4)|(7);    

操作的结果是使能nand flash 控制器, 初始化ecc校验.

 

ReadChipId读nand flash芯片的ID值. 这块板子所用的nand flash型号是K9F1208U0B, 打开它的datasheet , 从下图我们可以获得两个信息, 一是读ID的指令流, 二是芯片ID的组成.

ReadChipId的实现,可以参考K9F1208U0B,难度不大,但是比较繁琐, 不做分析. 另外,根据上图可知, 只有0xec76是合法的可识别的ID号.

 

下面是重头戏LoadRun函数, 该函数承担了加载wince的主要工作, 它的实现如下(把和linux相关的代码去掉了):

static void LoadRun(int part_sel)//part_sel = 1或3.{         U32 i, ram_addr, buf = 0x30200000;         int size;         StartPage = NandPart[part_sel].offset>>9;         size = NandPart[part_sel].size;         ram_addr = buf;               for(i=0; size>0; )         {                   if(!(i&0x1f))                   {                            if(CheckBadBlk(i+StartPage))                            {                                     printf("Skipped bad block at 0x%x/n", i+StartPage);                                     i += 32;                                     size -= 32<<9;                                     continue;                            }                   }                   ReadPage((i+StartPage), (U8 *)ram_addr);                   i++;                   size -= 512;                   ram_addr += 512;         }         DsNandFlash();         rBWSCON |= 0x0000d000;         rBWSCON &= ~0x00040000;         putch('/n');         call_linux(0, 193, buf);}


先看下面两行

StartPage = NandPart[part_sel].offset>>9;size = NandPart[part_sel].size;


part_sel是传来的参数, 这里是3. NandPart的定义如下:

static struct Partition NandPart[] = {         {0,             0x00040000, "bootloader"},          //256K         {0x00040000, 0x001c0000, "zImage"},//0.75M         {0x00200000, 0x01e00000, "cramfs"},            //30M         {0x02000000, 0x02000000, "WinCE"},    //32M.         {0,                       0         , 0}};


很明显这里说明, nandFlash被分成了四区,第0个分区是存bootloader, 1,2分区是linux相关的, 3区是放wince映象, 最后一组是一个结束标志. 3区的两个0x02000000分别表示起始地址和大小(offset和size).这里表示留给3区的大小为0x02000000(32M),所以你编译的wince映象文件不能大于这个值,否则bootloader将无法加载.

 

现在有个问题是NandPart[3].offset为什么要左移9位呢. 要弄清这个问题,得看这个值用在了哪里.

这个值赋给StartPage, 然后作为参数传进去了CheckBadBlk函数里(参见上段LoadRun实现代码).

CheckBadBlk函数是检查flash芯片坏块的, 查看K9F1208U0B的文档,我们知道, page的地址是A9~A25, 低9位是列字节位置标识, 如下图所示:

 

至于CheckBadBlk的实现原理这里不分析了,K9F1208U0B的文档里有详细的流程说明, 只说明两点:

1 flash芯片是允许坏块存在的, 厂家只要保证低于某一个坏快量就可以了.

2 一个健壮的程序是应该识别所用芯片的坏块的.

 

所以, 很明显, 上面一段代码中的for循环是读整个nand flash的3号分区, 如果读到坏块就跳过当前的page继续读,否则就据读到的信息存放到ram_addr里(ReadPage((i+StartPage), (U8 *)ram_addr)实现). 而ram_addr指向了0x30200000这个地址. 而这个地址正是wince系统运行所用的RAM的地址(你可以理解为内存). 这块优龙的板子, SDRAM的大小是64M,起始地址是0x30000000, 结束地址是0x34000000.注意我这里说的SDRAM地址全部是物理地址, 操作系统都还没加载,哪里会有虚拟地址呢?

 

其实 bootloader存在的真正意义就两个, 一是初始化CPU相关硬件,如cache,MMU等, 二是完成将操作系统映象文件加载到RAM中(上段LoadRun的实现代码中for循环就是做这个事情). 然后,它就失去了存在的价值,不起任何作用了.

 

继续看loadRun的实现代码, 下面三行:

DsNandFlash();rBWSCON |= 0x0000d000;rBWSCON &= ~0x00040000;


第一行使nandFlash 无效.

二三两行的是用来的操作2410的memory data bus width的, 这里是操作 bank3和bank4, 优龙的板子, 两块SDRAM是接到bank6的, 用到bank3的是cs900(这是一个ethernet 驱动IC), 不明白为什么要把bank3的设置放在这里,我估计是要用到ethernet下载调试系统时才会用到,因为我这里用的是USB进行调试, 应该可以去掉这两行.

 

还有一个问题,我们已经把wince的映像加载到RAM中指定的地址了, 得去执行它,否则它怎么运行. 这就是call_linux的任务了. 它的实现代码如下:

void call_linux(U32 a0, U32 a1, U32 a2){         int i, j;         void (*goto_start)(U32, U32);         cache_clean_invalidate();         tlb_invalidate();                disable_irq();         //If write-back is used,the DCache should be cleared.         for(i=0; i<64; i++)                   for(j=0; j<8; j++)                            MMU_CleanInvalidateDCacheIndex((i<<26)|(j<<5));         __asm {                   mov r0, #0                   mcr  p15, 0, r0, c7, c10, 4        // drain WB         }         MMU_DisableDCache();         MMU_DisableICache();         MMU_InvalidateICache();         MMU_DisableMMU();         MMU_InvalidateTLB();         goto_start = (void (*)(U32, U32))a2;         (*goto_start)(a0, a1);     }


其实要跳转指定的地址去执行, 只要下面三行语句就可以了.

void (*goto_start)(U32, U32);goto_start = (void (*)(U32, U32))a2;(*goto_start)(a0, a1);     


中间的就是一些关闭MMU和TLB等操作, 这是系统运行对硬件的要求, 这里主要想说一下对a0, a1这两个参数的疑问. 表面看起来,这两个参数是传给系统内核的, 我在CSND上看到有人问这个问题,  googleman回复的帖子说是没什么用,我写成下面这种形式测试了一下,  

void (*goto_start)(void);goto_start = (void (*)(void))a2;(*goto_start)();


Wince启动也是没有问题的.这就说明一个问题, 参数肯定不是传给wince的. 因为我对linux不熟,不知道是不是传给linux内核的呢?而且call_linux 在usb 的WaitDownload函数也被调用了, 这又是为什么呢? 另外,为什么参数是0 和193呢?希望知道答案的朋友可以不吝赐教,小弟在这里多谢了.
原创粉丝点击