5 NAND Flash 启动支持
对于老版的u-boot, 由于Nor flash支持读取代码执行,所以u-boot 默认是烧写进Nor flash启动的。想要在Nand flash中启动u-boot,需要在启动阶段将u-boot拷贝到内存中执行才行。
而最新版本的u-boot在启动第一阶段时,会将u-boot代码重定向到sdram里运行,具体过程如下:
① 在start.S中设置完CPU后,接着调用arch/arm/lib/board.c中的board_init_f做第一阶段平台的初始化工作(timer_init, env_init, ram_init... 计算堆栈addr_sp,设置gd结构体,计算出重定向u-boot的位置addr)。
② 执行完board_init_f后,会调用relocate_code(addr_sp, id, addr)返回start.S, 执行relocate_code的代码,拷贝u-boot代码到内存addr位置。
对于这个版本的u-boot,我实现同时支持Nor flash和Nand flash启动的方法是:在启动的开始阶段,初始化SDRAM后,通过CopyCode2Ram函数(CopyCode2Ram能够分辨代码是在Nor flash中还是在Nand flash中,从而采用不同的拷贝方法,具体实现在board/samsung/micro2440/nand.c中),将u-boot代码拷贝到TEXT_BASE指定的内存区域。然后再执行board_init_f和relocate_code。此时relocate_code不再是从flash中拷贝u-boot代码到内存了,而是从内存中TEXT_BASE处将代码拷贝到board_init_f中计算的addr位置。
这样,不管是从Nor flash还是Nand flash启动,一开始时就通过Copycode2Ram将u-boot代码复制到内存中,然后就可以顺利的执行后面的board_init_f和relocate_code了,简单方便的实现了同时支持Nor flash和Nand flash启动的功能。
u-boot第一阶段启动时,重定向代码示意图
下面是具体实现过程:
1) 修改include/configs/micro2440.h,设置TEXT_BASE
#define CONFIG_SYS_TEXT_BASE 0x33D80000
这里要注意,board_init_f计算出来的relocate addr一般是内存的末尾,所以此处的TEXT_BASE定义得不能太靠后,不然会出现代码拷贝过程中的overlap。(以前老版本的u-boot在内存中运行时,习惯将u-boot拷贝到0x33F80000处,这次我刚开始也设定的是这个值,结果第二次relocate_code拷贝代码时,就覆盖了先前的代码,造成系统错误。)
2) 修改arch/arm/cpu/u-boot.lds,将Copycode2Ram的代码放在.text段的最前面:
由于micro2440在通过Nand flash启动时,只会将flash中前4k的代码复制到内部ram中执行,所以我们在第一阶段拷贝代码时,必须保证执行拷贝操作的代码在.text段的前4k。这个可以通过修改u-boot.lds文件实现。
.text :{
__image_copy_start = .;
CPUDIR/start.o (.text)
board/samsung/micro2440/libmicro2440.o (.text)
*(.text)
}
3) 修改arch/arm/cpu/arm920t/start.S:
这里,我们可以把设置系统时钟,初始化MPLL的操作提到前面来做,设置完时钟后再执行代码复制的操作,这样可以加快代码复制的速度。
在start.S的call_board_init_f前添加下面一段代码:
#ifdef CONFIG_MICRO2440
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
bl board_early_init_f /* configure MPLL UPLL */
ldr r0, =0x0
ldr r1, =(CONFIG_SYS_TEXT_BASE)
ldr r2, _end_ofs
bl CopyCode2Ram /* r0: source, r1: dest, r2: size */
ldr pc, =call_board_init_f /* jump to SDRAM */
#endif
6 NAND Flash 启动支持 ⇒ 改进
按照上节描述的方法实现同时支持从Nor flash 和Nand flash启动后,在后面做usbslave download时,发现这种实现方法有缺陷(这个缺陷应该是u-boot官方code自带的?)。问题出现在中断处理过程里,我们先来看一下start.S中的中断向量和中断处理例程(假设链接地址_TEXT_BASE=0x0):
.globl _start
_start:bstart_code
ldr pc, _undefined_instruction /* 当产生中断或异常时,Arm内核会跳转到0x0地址
ldr pc, _software_interrupt * 开始处按照中断(或异常)的类型偏移一定地址执行
ldr pc, _prefetch_abort * 跳转指令。
ldr pc, _data_abort */
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used:. word not_used
_irq: .word irq /*此处,编译时确定_irq的值:irq = _TEXT_BASE +irq_ofs*/
_fiq: .word fiq
需要注意的是,此处_irq的值是编译时确定的,为_TEXT_BASE加上irq函数相对于_TEXT_BASE的偏移。当u-boot执行relocate函数,把u-boot代码重定向到内存中后,若有中断发生,会执行0x0开始的中断跳转指令。由于重定向时,并未更改0x0开始处的_irq, _fiq等label的值,此处的跳转仍然会跳转到重定向前的地址执行。(执行relocate时,在relocate .rel.dyn段时,会重新计算.rel.dyn段内汇编代码中Label的值。参见http://blog.csdn.net/caiyuqing2001/article/details/7377925)
所以在这种情况下执行中断处理例程时,执行的是_TXET_BASE为0x0,重定向前的代码,引用的汇编代码内的Label和C语言的全局变更也是重定向前的。而我们在初始化中断时,初始化的Label和全局变量是重写向后的变量(中断初始化时,代码已经重定向了),所以这种情况下执行的中断处理例程用到的变量全是未初始化的,会导致系统出错。
解决这个问题有两个方法:
1) 运行时根据重定向后中断处理例程的实际地址,更改0x0开始处的中断跳转地址。
若u-boot从Nand flash启动,0x0开始的4K的地址是在内部ram里的,此处的中断向量地址可以直接修改;但若是从Nor flash启动时,0x0开始处的地址是在Nor flash中的,此处的中断向量不能直接修改,得按照Nor flash的写时序来操作。所以想要同时支持Nor flash和Nand flash 启动,这种方法不可行。
2) 在运行过程中指定中断向量跳转地址。
中断向量跳转可以写成这种形式:ldr pc, =CONFIG_VECTOR_TABLE+0x08*irq_no。其中CONFIG_VECTORY_TABLE是设定的内存中的中断向量基地址,这样,relocate代码后在初始化中断过程中,可以根据中断处理函数的最终地址来设置中断向量表:
#define CONFIG_VECTOR_TABLE0x33FFFFC0
#define INSTRUCTION_LDR_PC0xE51FF004 /*ldr pc, [pc, #4]的指令代码*/
unsigned long int p_vect_table;
p_vect_table = (unsigned long int *) (CONFIG_VECTOR_TABLE + irq_no<<3);
p_vect_table++ = INSTRUCTION_LDR_PC;
p_vect_table = irq_handle;
3) 移除掉u-boot的重定向代码的功能,初始化SDRAM后,直接把u-boot代码拷贝至_TEXT_BASE处(假设_TEXT_BASE=0x33F40000),不再进行代码的重定向。这样发生中断时,跳转到的就是代码在内存中的最终地址,便没有上述问题了。
我采用的是第3种方法,这样在启动开始时,只需要复制一次代码就行了,实现也较简单。
① 修改arch/arm/config.mk,去除掉-pie链接选项,这样就不会生成.rel.dyn段了
#needed for relocation
#ifndef CONFIG_NAND_SPL
#LDFLAGS_u-boot += -pie
#endif
② 移除掉arch/arm/cpu/arm920t/start.S中的relocate代码和clear_bss代码:
#ifndef CONFIG_MICRO2440
adrr0, _start
cmpr0, r6
beqclear_bss/* skip relocation */
movr1, r6 /* r1 <- scratch for copy_loop */
ldrr3, _bss_start_ofs
addr2, r0, r3 /* r2 <- source end address */
copy_loop:
ldmiar0!, {r9-r10} /* copy from source address [r0] */
stmiar1!, {r9-r10} /* copy to target address [r1] */
cmpr0, r2 /* until source end address [r2] */
blocopy_loop
…
fixnext:
strr1, [r0]
addr2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmpr2, r3
blofixloop
#endif
#endif /* !CONFIG_MICRO2440 */
…
clear_bss:
#ifndef CONFIG_SPL_BUILD
ldrr0, _bss_start_ofs
ldrr1, _bss_end_ofs
ldrr4, =_TEXT_BASE /* reloc addr */
addr0, r0, r4
addr1, r1, r4
…
ldrr0, _board_init_r_ofs
adrr1, _start
addlr, r0, r1
#ifndef CONFIG_MICRO2440
addlr, lr, r9
#endif
/* setup parameters for board_init_r */
movr0, r5 /* gd_t */
③ 修改arch/arm/lib/board.c的board_init_f函数:
…
gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE;
#endif
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;
#ifdef CONFIG_MICRO2440
addr -= CONFIG_SYS_U_BOOT_SIZE;
#endif
#ifdef CONFIG_LOGBUFFER
#ifndef CONFIG_ALT_LB_ADDR
/* reserve kernel log buffer */
addr -= (LOGBUFF_RESERVE);
debug("Reserving %dk for kernel logbuffer at %08lx\n", LOGBUFF_LEN,
addr);
#endif
#endif
...
#ifdef CONFIG_LCD
#ifdef CONFIG_FB_ADDR
gd->fb_base = CONFIG_FB_ADDR;
#else
/* reserve memory for LCD display (always full pages) */
addr = lcd_setmem(addr);
gd->fb_base = addr;
#endif /* CONFIG_FB_ADDR */
#endif /* CONFIG_LCD */
#ifndef CONFIG_MICRO2440
/*
* reserve memory for U-Boot code, data & bss
* round down to next 4 kB limit
*/
addr -= gd->mon_len;
addr &= ~(4096 - 1);
debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr);
#else
addr &= ~(4096 - 1);
#endif
…
gd->bd->bi_baudrate = gd->baudrate;
/* Ram ist board specific, so move it to board code ... */
dram_init_banksize();
display_dram_config();/* and display it */
#ifdef CONFIG_MICRO2440
gd->relocaddr = CONFIG_SYS_TEXT_BASE;
gd->start_addr_sp = addr_sp;
gd->reloc_off = 0;
debug("relocation Offset is: %08lx\n", gd->reloc_off);
memcpy(id, (void *)gd, sizeof(gd_t));
relocate_code(addr_sp, id, addr);
#else
gd->relocaddr = addr;
gd->start_addr_sp = addr_sp;
gd->reloc_off = addr - _TEXT_BASE;
debug("relocation Offset is: %08lx\n", gd->reloc_off);
memcpy(id, (void *)gd, sizeof(gd_t));
relocate_code(addr_sp, id, addr);
#endif