下面以笔记的形式讲述调试uboot内存的方法,分别以ARM芯片和MIPS芯片为基础进行,大家可以将其作为bringup板子时的参考。
一 分类:内存与CPU的连接方式分为两类,一类是Static OnBoard, 表示板载内存颗粒;一类是DIMM 双列直插模式连接;两者在硬件连接上有不同的地方,这里不是重点,但这两类配置内存的方式是有差别的,前者由启动代码对映射好的关于内存的寄存器进行直接配置,后者是由启动代码读取DIMM上的iic spd数据配置内存的。
二 内存的配置代码涉及到启动过程,因此我饶有兴趣地向大家讲讲启动过程。
uboot的开源性会提供一个启动汇编程序start.S, 对于一个具体的芯片,会由此start.S调用分支,执行关于它的一个具体平台platform.S, platform.S当中正包含了如何初始化内存的整个过程。
1. arm方式的过程,针对static OnBoard方式,当然它也支持DIMM方式。
uboot的第一个阶段也正是执行该汇编程序的地方,包含start.S, platform.S, ddr2BasicInit/ddr2StaticInit.S....。
1) 首先它要设置好文本段TEXT_BASE起始值,BSS段起止start/end,为后续定位执行做好准备。
2) 然后刷新芯片内部I/D-Cache, 该Cache一般均为32K大小,由协处理器CP15管理,关闭MMU和TLB,刷新和关闭的目的是防止cpu使用这些硬件取不对应的数据值,比如没有刷新cache,新的存取操作已经发生,但不是cache原来的命中值,数据已经发生变化但cache仍然保留原样,这样就取出错误的数据和指令。
3) 然后进入到cpu_init_crit,这个函数通常映射了具体平台platform.S, 执行标签为lowlevel_init,它完成的工作包括:映射CPU寄存器地址,使用CPU寄存器地址配置CPU关于总线的初始化(如MBUS),配置cpu的时钟和MPP调试GPIO口,再转入到内存的调试标签入口_mvDramIfStaticInit/_mvDramIfBasicInit, 这个文件在ddr2/mvDramIfBasicInit.S目录下,如果为static执行_mvDramIfStaticInit直接写配置字就完成,如果为DIMM,则初始化IIC即TWSI,_i2cRead读SPD,最后用读出的值来配置内存_mvDramIfConfig。这部分的执行流程过程如下图1-1所示:
图1-1 内存配置的启动代码流程图
下面列出一个关于Marvell 512M static 内存的配置情况如图1-2(这是俺熟读CPU关于内存寄存器配置很多遍得到的结果哦)
1 | #define STATIC_SDRAM0_BANK0_SIZE 0x1FFFFFF1 /* 0x1504 *//*0 --> 16*16, 1-->32*16*< BR > #define STATIC_SDRAM_CONFIG 0x43008c30 /* 0x1400 */< BR > #define STATIC_SDRAM_MODE 0x00000C52 /* 0x141c */< BR > #define STATIC_DUNIT_CTRL_LOW 0x39543000 /* 0x1404 */< BR > #define STATIC_DUNIT_CTRL_HI 0x0000F1FF /* 0x1424 */< BR > #define STATIC_SDRAM_ADDR_CTRL 0x0000000d /* 0x1410 */ /*512M*/< BR > #define STATIC_SDRAM_TIME_CTRL_LOW 0x22125451 /* 0x1408 */< BR > #define STATIC_SDRAM_TIME_CTRL_HI 0x00000833 /* 0x140c */< BR > #define STATIC_SDRAM_ODT_CTRL_LOW 0x003C0000 /* 0x1494 */< BR > #define STATIC_SDRAM_ODT_CTRL_HI 0x00000000 /* 0x1498 */< BR > #define STATIC_SDRAM_DUNIT_ODT_CTRL 0x0000F80F /* 0x149c */< BR > #define STATIC_SDRAM_EXT_MODE 0x00000042 /* 0x1420 */< BR > #define STATIC_SDRAM_DDR2_TIMING_LO 0x00085520 /* 0x1428 */< BR > #define STATIC_SDRAM_DDR2_TIMING_HI 0x00008552 /* 0x147C */< BR > |
图1-2 板载内存配置
4) 完成上面的3)过程,喘口气,它还没跳出第一阶段的圈子,还在汇编程序当中,但它准备好了最关键的设备--内存,后面的程序就可以使用它了。下面就进入uboot引导的关键一步----重定位,代码截图1-3如下:
1 | relocate: /* relocate U-Boot to RAM */< BR > adr r0, _start /* r0 <- current position of code */< BR > ldr r1, _TEXT_BASE /* test if we run from flash or RAM */< BR > cmp r0, r1 /* don't reloc during debug */< BR > beq stack_setup< BR >< BR > ldr r2, _armboot_start< BR > ldr r3, _bss_start< BR > sub r2, r3, r2 /* r2 <- size of armboot */< BR > add r2, r0, r2 /* r2 <- source end address */< BR >< BR >copy_loop:< BR > ldmia r0!, {r3-r10} /* copy from source address [r0] */< BR > stmia r1!, {r3-r10} /* copy to target address [r1] */< BR > cmp r0, r2 /* until source end addreee [r2] */< BR > ble copy_loop< BR >< BR > |
图1-3 start.S中重定位
重定位的主要作用是将Nand/Nor flash上的代码拷贝到内存上来使用,当然,我们不可以简单理解这种拷贝是由读写flash函数调用来实现的,毕竟此时flash还没有被初始化,一般的理解是:CPU在出厂的时候会烧固件给内部ROM,它是上电可执行代码,像PC机的bios一样会自检等等。一般来讲,CPU内部也是有小部分RAM,当CPU的boot_cs选中外界flash的时候,CPU的flash controller会根据这个片选信号倾泻数据给RAM。重定位的作用正是这部分flash对RAM的倾泻转移到物理内存上,最终交由内存来存放uboot源码。1)步骤正是为了配合重定位来是指示bss和起始执行位置TEXT_BASE的,在Marvell芯片内部,uboot在物理内存重定位的值为0x600000 6M处位置,可用命令md 0x600000查看。
5) start.S阶段当然还有一些工作需要处理,比如关闭中断,设置异常中断指向重启复位,重新安排stack的位置等等,这些都不是重点,了解即可。
到此 终于完成了它的第一阶段,现在进入uboot的第二步,我浮光掠影地讲解下第二阶段过程,重点放在内存上。
1) 第一阶段调用start_armboot指向C语言执行代码区,首先它要从内存上的重定位数据获得不完全配置的全局数据表格和板级信息表格,即获得gd_t和bd_t,这两个类型变量记录了刚启动时的信息,并将要记录作为引导内核和文件系统的参数,如bootargs等等,并且将来还会在启动内核时,由uboot交由kernel时会有所用。
2) 下面就是各个sequence的初始化,重点关注cpuMapInit和dram_init,对于Marvell芯片源码,cpuMapInit函数是要读取一张表格,这个表格用来映射CPU的地址空间,它包含内存的,设备的,启动boot_cs的,以及PCI设备的映射,由于uboot不分所谓的内核空间用户空间,也就无所谓权限保护的问题,这张表格指定是多少地址就是实际使用该设备的CPU空间地址,可以理解为直接映射,对于Marvell设备来讲,内存是零偏移位置,重要的是不要冲突占用即可。
dram_init的初始化,实际上由于Static OnBoard原因简化了操作,只是获取该内存的映射地址以及内存的大小,这些映射的设置亦有cpuMapInit完成,然后将内存的一些信息填充到gd_t和bd_t当中。
3) 补充啰嗦一点,uboot第二阶段最初会关中断初始化的中断计数,待关键的内容初始化完成后(如cpu_init,cpuMap,env),会同GPIO一同打开中断功能。当然uboot的初始化远不止这些,还包括诸如flash初始化,scsi初始化,以太网初始化,定时器初始化等等。
第二阶段工作的完成,可以告知系统内存是线性而且是直接的映射,CPU可以在内存上做各种简单的应用,可以使用malloc函数分配,当然无法执行繁重的工作,因为它没有回收和分配管理能力。
2。MIPS方式过程:以spd为例,当然也支持OnBoard方式。
若以SPD方式来调试板子的bringup,其实相对来说是比较容易的,因为它的大部分信息已经记录在spd内部,读出来配置就可以了,能否成功配置同cpu的内存访问源码有关,通常要检查CPU是否正确指定了spd iic地址,其次,如果源码的健壮性比较差,还要根据CPU的关于内存的片选,内存的延迟机时做适当的调整,因为某些内存是双CS的,内存的rank是偶数,同样,layout会影响信号失真,配置芯片CPU的信号增强也可以提高稳定性。
下面列出一些我在MIPS平台下的数据如图1-4,大家可以作为参考,至于MIPS的bootloader,与ARM的大同小异,跟着上面一样的思路就可以完整找到具体的步骤,在此不啰嗦了。