zl程序教程

您现在的位置是:首页 >  其它

当前栏目

2410 bootloader分析(二)

分析 BootLoader
2023-09-27 14:28:31 时间

接下来几行一直到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呢? 希望知道答案的朋友可以不吝赐教,小弟在这里多谢了.