2410 bootloader分析(二)
接下来几行一直到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呢? 希望知道答案的朋友可以不吝赐教,小弟在这里多谢了.
相关文章
- java中文GBK和UTF-8编码转换乱码的分析
- 从源码分析 MGR 的新主选举算法
- Hacking Team安卓浏览器攻击过程中的漏洞分析 Stage4
- 深度分析CVE-2017-0007是如何绕过防护措施的
- 有道词典 Andriod 版本数据格式分析
- Splunk:大数据智能分析平台&全能日志分析利器
- 分析js跳出循环的几种方法
- 计算机视觉系列-MMCV 组件分析: Hook
- SAP借助HANA应对大数据时代的实时分析
- 【Bootloader】bootloader启动过程分析
- 入门视频采集与处理(学会分析YUV数据)
- lightdb/postgresql中的MemoryContext out of memory原因分析及解决思路
- 需求分析