第二版主要是对于第一版的一些错误改正,之前nand flash的移植失败,特在此处使用另一种nand flash移植方法.另外在这里还介绍一下uboot支持yaffs烧写功能的移植(新版里已经支持了),还介绍一下yaffs2文件系统的制作与使用。
移植U-Boot 1.3.4到GT2440(第一版)下载地址
用户名与密码都是
具体下载目录在 /2011年资料/嵌入式Linux/移植U-Boot 1.3.4到GT2440/
硬件配置
1、 GT2440\其它开发板
2、 其中nand flash为2Kb一页(具体和512byte的有什么区别,参考nand元件手册,或者上网搜一下,对于uboot,主要是读nand时的写地址时序的不一样)
3、 串行线
4、 J-link\j-tag(笔者用的是jlink,主要用于uboot的调试,因为你移植马上能用的可能性不大,所以需要调试)
软件配置:
1、u-boot-1.3.4
2、- j-link或者H-jtag
一、从nandflash 启动uboot的原理
Uboot 源码是不支持从nand中启动的,但是2410 2440是支持的,而且对于闪存,nand比较大容量,比较便宜,所以使用nand启动uboot是比较需要的。那么它启动的原理是什么?
其实从nand flash 控制器有一个特殊功能,会自动把nand flash前4K内容复制到4K SRAM中(注意,是只有4k的SRAM而不是SDRAM,超过怎么办,所以要复制到SDRAM中,如下图)中,并把0x00000000设置成内存起始地址,cpu从这个地址开始运行。这个过程不需要程序干涉。在配置NAND启动模式之后,S3C2440上电会先将NAND中的0x0 - 0x1000共4096字节的数据拷贝到位于Bank0中的Boot Internal SRAM上
这4K的内容,主要是保存的uboot的部分功能(拷贝功能),执行后,再把nand里的内容拷贝到SDRAM中,原因有下:
1、 SDRAM运行速度快
2、 实际的uboot代码永远大于4Kb的空间,,所以要开辟一个新空间给uboot运行
这就是前4K所在的地方
二、uboot的运行流程
首先先大概分析一下uboot,这样有利于明白,移植的每一步是需要做些什么?
在讲uboot启动前,先讲一下arm启动流程,arm启动是先运行芯片厂家固化的boot block,这段程序是引导块,芯片厂家将boot block地址重映射到片内存储器空间最高处,接近接近2G(0x8000 0000靠MMU映射)。运行完这段程序后,就会运行0x0 地址。看回上面的2440存储映射图,假如是nor的话,就会直接运行Nor 里的内容,如果是nand的话,nand控制器先拷4k到0x0000 0000里的SRAM,然后0x4000000后的内容会被重映射到0x0000 0000的映射,这样就代表前4k直接操作的是bootinternal的SRAM,而不是nand的前4k,可能是因为SRAM速度快.
上个很好的图:
现在看回uboot启动流程:
像网上说的,uboot编译时首先编译的是u-boot-1.3.4\board\你的开发板文件夹\u-boot.lds,看一个2410的例子:
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu\arm920t\start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
(1) 从ENTRY(_start)可以看出u-boot的入口函数是_start,这个没错
(2) 从. = 0x00000000也许可以看出_start的地址是0x00000000,事实并不是这样的,
.text为代码段,可以看出cpu/arm920t/start.o 在代码段的最前面,所以会先执行start.o 中的代码, 如何设置从0x33f80000开始呢?~这是链接的时候指定的
在根目录下面的config.mk中有下面一句
LDFLAGS +=-Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
关键就是其中的-Ttext $(TEXT_BASE),这句指明了代码段的起始地址
而TEXT_BASE在 board/smdk2440/config.mk中定义 TEXT_BASE = 0x33F8 0000
为什么是0x33F80000呢?~
这是将NAND中Uboot拷贝到RAM中的起始地址,所以在代码拷贝到RAM之前不能使用绝对地址(链接后都会加上_TEXT_BASE)来寻址数据,只能用相对地址(相对于当前PC)(adr r0, _start可以确定程序在哪里运行,因为adr是基于pc当前值的,假如在内存中0x33f8000就应该是0x33f8000+_start,假如是在flash中,就应该是0+_start )
ARM汇编中,常有两种跳转方法:b跳转指令、ldr指令向PC赋值。 (参考《[NAND]UBOOT从NAND FLASH启动分析》)
要特别注意这两条指令的意思:
(a) b step:b跳转指令是相对跳转,依赖当前PC的值,偏移量是通过该
指令本身的 bit[23:0]算出来的,这使得使用b指令的程序不依赖于要跳到的代
码的位置,只看指令本身。具体是将这24位左移两位加上PC再赋给PC寄存器,得到目标地址
(b) ldr pc, =step :该指令是一个伪指令编译后会生成以下代码:
ldr pc, 0x30008000
<0x30008000 > step
是从内存中的某个位置(step)读出数据并赋给PC,同样依赖当前PC的值,但
是偏移量是step的连接地址(运行时的地址),所以可以用它实现从Flash到RAM的程
序跳转。
(c)此外,有必要回味一下adr伪指令,U-boot中那段relocate代码就是通过adr实
现当前程序是在RAM中还是flash中:
relocate: /* 把U-Boot
重新定位到RAM*/
adr r0, _start /* r0是代码的当
前位置 */
/* adr伪指令,汇编器自动通过当前PC的值算出这条指令中“_start"的值,执行到_
start时PC的值放到r0中:
当此段在flash中执行时r0 = _start = 0;当此段在RAM中执行时_start = _TEXT_B
ASE(在board/smdk2410/config.mk中指定的值为0x33F80000,即u-boot在把代码拷贝到RAM中去 执行的代码段的开始) */
ldr r1, _TEXT_BASE /* 测试判断是从Flash启
动,还是RAM */
/* 此句执行的结果r1始终是0x33F80000,因为此值是链接指定的 */
cmp r0, r1 /* 比较r0和r1,
调试的时候不要执行重定位 */(引用《u-boot.lds解析》)
所以说实际链接时,都是以_TEXT_BASE为基地址,但是运行时会根据现在代码所处的位置去复制uboot代码到内存(因为在复制到内存之前都是用相对地址的),如果在内存,就直接接着运行即可.具体请参考u-boot根目录下的config.mk.
顺着这个config.lds文件,就开始执行u-boot-1.3.4\cpu\arm920t\start.S
它里面大概执行流程是
(1) CPU为SVC模式
(2) 定义中断向量表
(3) 关闭中断 (因为uboot不需要使用中断)
(4) 底层初始化:两个函数cpu_init_crit和lowlevel_init
cpu_init_crit是禁止MMU和CACHE,为什么?因为如果你不禁止,有些数据会残留在cache上,会造成脏数据(《具体也可以参考ARM体系结构与编程》)
lowlevel_init主要是定义部分寄存器以及初始化SDRAM
(5) 然后把代码拷贝到内存中(没修改时为下面代码,修改后使用自己移植的)
#ifndefCONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM */
adr r0,_start /* r0 <- currentposition of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't relocduring debug */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /*CONFIG_SKIP_RELOCATE_UBOOT */
(6) 初始化堆栈(其实就是在内存中开辟一个区域,用于保存数据,其实是栈,经典映射图如下图)
(7) 调用C语言函数入口,start_armboot
到此第一阶段基本完成
注意b 跳转跟 ldr 跳转是不一样的
b跳转,依赖当前PC值,偏移量通过指令bit[23:0]算出
Ldr pc,=label
Ldr 伪指令,将内存值赋给PC,也是依赖于当前PC值,但是偏移量是label的运行时的地址
这就是创建的堆
这就是创建的栈