赞
踩
tar xjf u-boot-2012.04.01.tar.bz2
cd u-boot-2012.04.01
find -name "*2440*"
find -name "*2410*"
,搜索的结果如下:在uboot启动过程分析中的可知uboot 运行的第一个C函数是 board_init_f,单片机/SOC在执行C 函数之前,必须设置栈。
/* Set stackpointer in internal RAM to call board_init_f */
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) // sp = 0x30000000 + 0x1000 - 128 = 0x3000 0F80
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
bl board_init_f
从以上代码可知,在执行C函数 board_init_f 之前设置 sp =CONFIG_SYS_INIT_SP_ADDR,那么CONFIG_SYS_INIT_SP_ADDR等于多少呢?在include/configs/smdk2410.h中 由以下代码:
/*-----------------------------------------------------------------------
* Physical Memory Map
*/
#define CONFIG_NR_DRAM_BANKS 1 /* we have 1 bank of DRAM */
#define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */
/* additions for new relocation code, must be added to all boards */
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_1
#define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_SDRAM_BASE + 0x1000 - \
GENERATED_GBL_DATA_SIZE)
可知:CONFIG_SYS_INIT_SP_ADDR = CONFIG_SYS_SDRAM_BASE + 0x1000 - GENERATED_GBL_DATA_SIZE
= 0x30000000 + 0x1000 - GENERATED_GBL_DATA_SIZE
此时,GENERATED_GBL_DATA_SIZE 就是 gd_t 结构体占的内存大小。这里可通过u-boot 的反 汇编可直接知道 CONFIG_SYS_INIT_SP_ADDR 的值。
(在uboot顶层目录下输入命令:arm-linux-objdump -D u-boot > u-boot.dis )
由上图可知,sp的值存储在0x498 这个地址里, 由下图可知,这个地址存储的值是0x30000f80,
总结:sp = 0x30000f80;GENERATED_GBL_DATA_SIZE = 128,即 gd_t 结构体的大小是128B。
设置好sp后,对sp进行8字节对齐(sp对齐后仍然是0x30000f80),然后把r0 = 0作为参数传入函数board_init_f,下面分析board_init_f函数。
void board_init_f(ulong bootflag) { bd_t *bd; init_fnc_t **init_fnc_ptr; gd_t *id; ulong addr, addr_sp; #ifdef CONFIG_PRAM ulong reg; #endif bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f"); /* Pointer is writable since we allocated a register for it */ gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07); // gd = (gd_t *)0x30000f80 /* compiler optimization barrier needed for GCC >= 3.4 */ __asm__ __volatile__("": : :"memory"); memset((void *)gd, 0, sizeof(gd_t)); gd->mon_len = _bss_end_ofs; // _bss_end_ofs = __bss_end__ - _start,从反汇编里可知:_bss_end_ofs = 0x000ae4e0 = 697KB #ifdef CONFIG_OF_EMBED /* Get a pointer to the FDT */ gd->fdt_blob = _binary_dt_dtb_start; #elif defined CONFIG_OF_SEPARATE /* FDT is at end of image */ gd->fdt_blob = (void *)(_end_ofs + _TEXT_BASE); #endif /* Allow the early environment to override the fdt address */ gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16, (uintptr_t)gd->fdt_blob); for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } } #ifdef CONFIG_OF_CONTROL /* For now, put this check after the console is ready */ if (fdtdec_prepare_fdt()) { panic("** CONFIG_OF_CONTROL defined but no FDT - please see " "doc/README.fdt-control"); } #endif debug("monitor len: %08lX\n", gd->mon_len); /* * Ram is setup, size stored in gd !! */ debug("ramsize: %08lX\n", gd->ram_size); #if defined(CONFIG_SYS_MEM_TOP_HIDE) /* * Subtract specified amount of memory to hide so that it won't * get "touched" at all by U-Boot. By fixing up gd->ram_size * the Linux kernel should now get passed the now "corrected" * memory size and won't touch it either. This should work * for arch/ppc and arch/powerpc. Only Linux board ports in * arch/powerpc with bootwrapper support, that recalculate the * memory size from the SDRAM controller setup will have to * get fixed. */ gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE; #endif addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size; // gd->ram_size = 64MB,所以此时的 addr = 0x30000000 + 0x04000000 = 0x34000000 #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_PRAM /* * reserve protected RAM */ reg = getenv_ulong("pram", 10, CONFIG_PRAM); addr -= (reg << 10); /* size is in kB */ debug("Reserving %ldk for protected RAM at %08lx\n", reg, addr); #endif /* CONFIG_PRAM */ #if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) /* reserve TLB table */ addr -= (4096 * 4); // 保留16kB 给 TLB table,所以 addr = 0x34000000 -16k = 0x33ffc000 /* round down to next 64 kB limit */ addr &= ~(0x10000 - 1); // 64kB对齐 addr = 0x33ffc000 & ~0xFFFF = 0x33ff0000 gd->tlb_addr = addr; // gd->tlb_addr = 0x33ff0000 debug("TLB table at: %08lx\n", addr); #endif /* round down to next 4 kB limit */ addr &= ~(4096 - 1); // 4kq对齐: addr仍然等于 0x33ff0000 debug("Top of RAM usable for U-Boot at: %08lx\n", addr); #ifdef CONFIG_LCD //没有配置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 */ /* * reserve memory for U-Boot code, data & bss * round down to next 4 kB limit */ addr -= gd->mon_len; // addr = 0x33ff0000 - 0x000ae4e0 = 0x33F41B20 addr &= ~(4096 - 1); // 4k对齐 addr = 0x33F41B20 & ~0xfff = 0x33F41000 debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr); #ifndef CONFIG_SPL_BUILD /* * reserve memory for malloc() arena */ addr_sp = addr - TOTAL_MALLOC_LEN; // addr_sp = 0x33F41000 - 4MB = 0x33B41000 debug("Reserving %dk for malloc() at: %08lx\n", TOTAL_MALLOC_LEN >> 10, addr_sp); /* * (permanently) allocate a Board Info struct * and a permanent copy of the "global" data */ addr_sp -= sizeof (bd_t);//存放bd_t(bd_info) 结构体 bd = (bd_t *) addr_sp; gd->bd = bd; // debug("Reserving %zu Bytes for Board Info at: %08lx\n", sizeof (bd_t), addr_sp); #ifdef CONFIG_MACH_TYPE gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */ #endif addr_sp -= sizeof (gd_t);//存放gd_t(global_data)结构体 id = (gd_t *) addr_sp; debug("Reserving %zu Bytes for Global Data at: %08lx\n", sizeof (gd_t), addr_sp); /* setup stackpointer for exeptions */ gd->irq_sp = addr_sp; #ifdef CONFIG_USE_IRQ addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ); debug("Reserving %zu Bytes for IRQ stack at: %08lx\n", CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp); #endif /* leave 3 words for abort-stack */ addr_sp -= 12; //addr_sp = addr_sp -12 /* 8-byte alignment for ABI compliance */ addr_sp &= ~0x07; // 8字节对齐 #else addr_sp += 128; /* leave 32 words for abort-stack */ gd->irq_sp = addr_sp; #endif debug("New Stack Pointer is: %08lx\n", addr_sp); #ifdef CONFIG_POST post_bootmode_init(); post_run(NULL, POST_ROM | post_bootmode_get(0)); #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 */ gd->relocaddr = addr; // addr = 0x33F41000 gd->start_addr_sp = addr_sp; // addr_sp = 预留12字节内存后的地址 gd->reloc_off = addr - _TEXT_BASE; debug("relocation Offset is: %08lx\n", gd->reloc_off); memcpy(id, (void *)gd, sizeof(gd_t));// 把全局变量gd 复制到Id这个地址里 relocate_code(addr_sp, id, addr); /* NOTREACHED - relocate_code() does not return */ }
(1) 从上面代码的14行gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07) 可知,gt结构体存放在0x30000f80这个地址里,大小128B;board_init_f函数对这块内存的操作:先清0,然后设置gd 结构体的各个成员;
(2)上面的32~36行代码的作用是:各种硬件的初始化;硬件的初始化函数保存在init_sequence这个指针数组里,假如初始化失败,则进入一个死循环函数hang;
以下是init_sequence里存放的各个硬件初始化函数:
init_fnc_t *init_sequence[] = { #if defined(CONFIG_ARCH_CPU_INIT) arch_cpu_init, /* basic arch cpu dependent setup */ #endif #if defined(CONFIG_BOARD_EARLY_INIT_F) board_early_init_f, //单板早期初始化 #endif #ifdef CONFIG_OF_CONTROL fdtdec_check_fdt, #endif timer_init, /* initialize timer */ #ifdef CONFIG_FSL_ESDHC get_clocks, #endif env_init, /* initialize environment */ init_baudrate, /* initialze baudrate settings */ serial_init, /* serial communications setup */ console_init_f, /* stage 1 init of console */ display_banner, /* say that we are here */ #if defined(CONFIG_DISPLAY_CPUINFO) print_cpuinfo, /* display cpu info (and speed) */ #endif #if defined(CONFIG_DISPLAY_BOARDINFO) checkboard, /* display board info */ #endif #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C) init_func_i2c, #endif dram_init, /* configure available RAM banks */ NULL, };
(3) 上面的65行:addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size,把 addr设置为SDRAM(64MB)的最高地址0x34000000;
(4) 86~92行:然后给TLB table 分配16kB的内存空间(64kB对齐后,实际给TLB table分配了64kB空间)此时的 addr = 0x33ff0000;
(5) 114~115行:从20行可知gd->mon_len = _bss_end_ofs,_bss_end_ofs = __bss_end__ - _start
( 即uboot代码的大小 ) 从反汇编可知:_bss_end_ofs = 0x000ae4e0 = 697KB
。所以,144~115行代码的作用是为之后的uboot重定位分配内存空间,此时的addr = 0x33f41000;
(6) 123行:给 malloc() 函数分配4MB的堆空间,此时 addr_sp = 0x33B41000;
(7) 130~132行:为 bd_t结构体分配内存空间,并且把该结构体的地址保存在 gd->bd 中;
(8) 140~146行:为 gd_t结构体分配内存空间,并且把用id记录该结构体的地址,同时也把结构的地址保存在gd->irq_sp 中;
(9) 153~156行:为abort(数据访问终止模式)分配 12 字节的内存空间,并且进行8字节对齐;
(10) 174~180行:把addr、addr_sp等板寸到gd结构体,同时使用memcpy函数,把gd结构体复制到id这个地址里面;接着把 addr_sp、id、addr作为参数传入代码重定位函数relocate_code中;
总结:① 经过上面的一步步分析,可得出下图的uboot内存分布图,其中下图红色字体addr、id、addr_sp所指的内存的地址值就是传入
elocate_code函数的值,用于代码的重定位。
② 对于relocate_code函数在汇编代码中:addr_sp 保存在 r0,id 保存在 r1,addr 保存在 r2(r2是目标地址)。
1. 代码重定位原理:
由前面的分析可知,uboot的连接地址为0,所以对于Jz2440开发板而言,uboot只能从NOR Flash运行。由NOR Flash可以像内存一样读,但不能像内存一样写,如果有某个全局变量存储在NOR Flash中,执行程序时只能对该变量进行读操作,不能进行写操作,此时程序就好出现问题,因此需代码重定位(即把NOR Flash的代码复制到SDRAM)。uboot程序的连接地址为0,复制到SDRAM中为何能执行?因为把程序复制到SDRAM后还需要修改代码,改变里面变量、函数的地址。例如,访问nor flash上某个变量的地址是0x100,把涉及的程序复制到0x32000000里面去,想访问同一个变量,使用0x32000100来访问。因此,如果某段程序涉及多个变量,就需要在把这段程序从nor flash复制到SDRAM后,修改程序里变量、函数的地址。
3. 重定位的旧地址在哪里?
在链接时加上pie选项(arm-linux-ld -pie)地址信息就会生成,然后存在段里面。在链接脚本里面存储地址信息的段是.rel.dyn和.dynsym,如下图所示:
uboot在代码重定位时会根据存储在.rel.dyn和.dynsym段里的旧地址信息修改代码,把旧地址改为新地址,程序就可以复制到任何地址位置去运行了。
3. 重定位代码分析:
从前面对 board_init_f 函数的分析可知,代码重定位的函数是 relocate_code,传入该函数的参数是addr_sp、id、addr,在u-boot-2012.04.01内存分布图可知他们指向内存的位置,当调用汇编函数时这三个参数分别对应r0、r1、r2,代码如下:
.globl relocate_code
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */
4. 代码重定位步骤:
(1)从NOR FLASH把代码复制到SDRAM;
(2)程序的链接地址是0,访问全局变量、静态变量、调用函数时是使"基于0地址编译得到的地址",
现在把程序复制到SDRAM,需要把"基于0地址编译得到的地址"修改为新地址。
(3)程序里有些地址在链接时不能确定,要到运行前才能确定:fixabs
5.重定位的实现:
(1)保存传入relocate_code函数的参数,把栈地址r0保存到r4,gd结构体的地址r1保存到r5,uboot重定位的地址r2保存到r6;
/*
* void relocate_code (addr_sp, gd, addr_moni)
*
* This "function" does not return, instead it continues in RAM
* after relocating the monitor code.
*
*/
.globl relocate_code
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */
(2)重新设置栈,把r4的值赋给sp (在执行board_init函数之前设置的栈为0x30000f80,现重定位重新设置sp);adr r0,_start
里面_start是第
一条语句的当前地址,当前地址和r6目的地址比较,如果相等,就不需要拷贝代码(重定位),直接跳过清除bss段;如果不相等,就把代
码从flash拷贝到内存里,从r0这个地地址拷贝到r6这个地址;add r2,r0,r3
的意思是r2=r0+r3,r2
是源代码的结束地址,而r0是源代码的
开始地址,而r3是代码段的长度 (_bss_start_ofs = __bss_start - _start
,在二进制文件里面不包含bss段)
/* Set up the stack */ stack_setup: mov sp, r4 /* adr 与 ldr伪指令的区别: * adr r0, _start 得到的是 _start 的当前执行位置,由 pc+offset 决定 * ldr r0, =_start 得到的是绝对的地址,链接时决定 */ adr r0, _start cmp r0, r6 beq clear_bss /* skip relocation */ mov r1, r6 /* r1 <- scratch for copy_loop */ ldr r3, _bss_start_ofs add r2, r0, r3 /* r2 <- source end address */ copy_loop: ldmia r0!, {r9-r10} /* copy from source address [r0] */ //将r0处的两个32位数据拷到r9-r10中,然后r0+=8 stmia r1!, {r9-r10} /* copy to target address [r1] */ //将拷出来的两个数据放入r1(重定位地址)处,然后r1+=8 cmp r0, r2 /* until source end address [r2] */ // uboot 重定位到0x33F41000这个地址 blo copy_loop
(3)拷贝代码到重定位地址后修改代码 (把基于0地址编译得到的地址修改为新地址)
#ifndef CONFIG_SPL_BUILD /* * fix .rel.dyn relocations */ ldr r0, _TEXT_BASE /* r0 <- Text base */ //r0=text(代码)段基地址=0 sub r9, r6, r0 /* r9 <- relocation offset */ //r9= 重定位后的偏移值=0x33F41000 ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */ //_dynsym_start_ofs =__dynsym_start - _start=0x73608 add r10, r10, r0 /* r10 <- sym table in FLASH */ //所以r10=动态符号表的起始偏移值=0x73608 ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */ //r2=__rel_dyn_start - _start=0x6b568 add r2, r2, r0 /* r2 <- rel dyn start in FLASH */ //所以r2=相对动态信息的起始偏移值=0x6b568 ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */ // _rel_dyn_end_ofs=__rel_dyn_end - _start=0x73608 add r3, r3, r0 /* r3 <- rel dyn end in FLASH */ //所以r3=相对动态信息的结束偏移值=0x00073608 fixloop: ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */ //从反汇编可知,r2=0x6b568地址处的内容= 0x20,所以r0 = 0x20 add r0, r0, r9 /* r0 <- location to fix up in RAM */ //r0=0x33F41000+0x20=0x33F41020 ldr r1, [r2, #4] // r2 + 4 =0x6b56C ,地址0x6b56C的内容是0x17,所以r1 = 0x17 and r7, r1, #0xff // r7 = 0x17& 0xff = 0x17 cmp r7, #23 /* relative fixup? */ // 0x 17的十进制就是 23 ,因此r7与23 相等,下一条指令将跳转到fixabs beq fixrel cmp r7, #2 /* absolute fixup? */ beq fixabs /* ignore unknown type of fixup */ b fixnext fixabs: /* absolute fix: set location to (offset) symbol value */ mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */ add r1, r10, r1 /* r1 <- address of symbol in table */ ldr r1, [r1, #4] /* r1 <- symbol value */ add r1, r1, r9 /* r1 <- relocated sym addr */ b fixnext fixrel: /* relative fix: increase location by offset */ ldr r1, [r0] // r0 = 0x33F41020, 可知知道这个地址的内容即是反汇编0x20地址的内容0x000001e0 add r1, r1, r9 // r1 = r1 + r9 = 0x33F41000 + 0x000001e0 = 0x33F411e0 fixnext: str r1, [r0] //改变链接地址里的内容, [0x33F41020]=0x33F411e0 (之前为0x1e0) add r2, r2, #8 /* each rel.dyn entry is 8 bytes */ //r2等于下一个相对动态信息(0x24)的地址 cmp r2, r3 //若没到尾部__rel_dyn_end,便继续执行: fixloop blo fixloop #endif
总结:(图解代码重定位过程)
在nor flash上存储的程序有如图右所示的rel_dyn_start这一段,里面的6b568地址存储的值是0x20,在nor flash上的0x20地址里存放的是某个地址值。rel_dyn_start这一段表明你想让程序能够运行,需要修改像0x20地址里面的地址值。nor flash上0地址的代码要复制到SDRAM的33f41000地址来,SDRAM地址33f41000偏移值为0x20的地方存储的值跟nor flash上0x20地址的值完全一样,我们把代码复制到SDRAM后,要去修改SDRAM上的代码,把里面用到的地址全部改为新地址,rel_dyn_start这一段表明用到哪些地址,表明要修改地址0x20这个地址里的值( 地址值),设这个值在nor flash的0x20地址上是V,在SDRAM 的33f4,1000地址的偏移值0x20的地址值改为v+0x33f41000,下面的0x00000017是标志位,表示是直接把0x20里面的值取出来,加上一个偏移,得到一个新的值后,写到SDRAM的33f41000地址的偏移值0x20的地址里面去。
这样修改的作用:
假设程序从0x04这个地址开始运行,即执行ldr pc, _undefined_instruction
这条指令,它先去_undefined_instruction
这个地址里取一个值undefined_instruction
,然后赋值给PC跳转到undefined_instruction
这个地址里执行程序。
.globl _start _start: b start_code ldr pc, _undefined_instruction 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 _fiq: .word fiq .balignl 16,0xdeadbeef
在反汇编可知:ldr pc, _undefined_instruction
对应的指令是ldr pc, [pc, #20]
,0x20 这个地址里存储的值是0x000001e0,也就是undefined_instruction这个函数的地址;在SDRAM中,假设执行到地址0x33f41000偏移值为0x04的地方,它就会先去0x33f41000偏移值为0x20的地方取值,然后跳转到0x33f41000偏移值为0x1e0的地方(0x33f41000+0x1e0)执行undefined_instruction函数。
程序里有些地址在链接时不能确定,要到运行前才能确定的情况:例如:printf
写mian
函数时用到printf
,假若程序是动态链接,printf
是在库函数里面实现的,运行时才知道printf
函数的地址,汇编上会写成ldr pc ,[addr]
去某个地方(addr
)读值,这个地方本来的值应该是printf
的地址,但是一开始不知道,就写为0
,在运行前确定printf
地址(是操作系统帮我们做的),把地址写入addr
,再赋值给pc
,从而执行程序。
uboot重定位之后,清空bss段,然后调用C函数board_init_r
进入uboot的第二阶段。
1. 新建一个单板:
(1) 在board/samsung/目录下新建一个单板:
make distclean
cd board/samsung/
cp -rf smdk2410 smdk2440
(2) 修改smdk2440的目录下的Makefile,把smdk2410.c 重命名为smdk2440.c
a.把Makefile 里的smdk2410.o 改为 smdk2440.o,如下图所示:
改为
b. 重命名smdk2410.c为 smdk2440.c:mv smdk2410.c smdk2440.c
(3) 进入include/configs/目录下,添加smdk2440.h 配置文件:
cd include/configs/
cp smdk2410.h smdk2440.h
(4) 修改boards.cfg:
仿照
smdk2410 arm arm920t - samsung s3c24x0
添加:
smdk2440 arm arm920t - samsung s3c24x0
(5) 配置、编译、烧写:
//在顶层目录下
make smdk2440_config
make
这样编译出来的u-boot烧写到开发板上,串口肯定不会有输出,因为相比于之前,只是添加、修改了文件名,并没有修改代码.。
1.阅读代码分析,在start.S启动文件中发现不足,如下图所示:
从图中可知:uboot里先以60MHZ的时钟计算参数来设置内存控制器,但是MPLL 还未设置;
处理措施: 把MPLL的设置放到start.S里,取消board_early_init_f里对MPLL的设置,同时根据新设置的时钟参数设置内存控制器。
(1) 把上图设置时钟比例参数的代码删掉,添加如下代码:
/* 2. 设置时钟 */ ldr r0, =0x4c000014 // mov r1, #0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1 mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8 str r1, [r0] /* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */ mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */ orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */ mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */ #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01)) /* MPLLCON = S3C2440_MPLL_200MHZ */ ldr r0, =0x4c000004 ldr r1, =S3C2440_MPLL_400MHZ str r1, [r0] /* 启动ICACHE */ mrc p15, 0, r0, c1, c0, 0 @ read control reg orr r0, r0, #(1<<12) mcr p15, 0, r0, c1, c0, 0 @ write it back
(2) 同时修改设置内存控制器参数的代码,在lowlevel_init.S中最后有如下代码:
SMRDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0x32
.word 0x30
.word 0x30
更改为如如下代码:
SMRDATA:
.long 0x22011110 //BWSCON
.long 0x00000700 //BANKCON0
.long 0x00000700 //BANKCON1
.long 0x00000700 //BANKCON2
.long 0x00000700 //BANKCON3
.long 0x00000740 //BANKCON4
.long 0x00000700 //BANKCON5
.long 0x00018005 //BANKCON6
.long 0x00018005 //BANKCON7
.long 0x008C04F4 // REFRESH
.long 0x000000B1 //BANKSIZE
.long 0x00000030 //MRSRB6
.long 0x00000030 //MRSRB7
(3) 取消board_early_init_f里对MPLL的设置,修改/board/samsung/smdk2440/smdk2440.c中board_early_init_f函数代码如下:
int board_early_init_f(void) { struct s3c24x0_clock_power * const clk_power = s3c24x0_get_base_clock_power(); struct s3c24x0_gpio * const gpio = s3c24x0_get_base_gpio(); #if 0 //注释对MPLL的设置 /* to reduce PLL lock time, adjust the LOCKTIME register */ writel(0xFFFFFF, &clk_power->locktime); /* configure MPLL */ writel((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV, &clk_power->mpllcon); /* some delay between MPLL and UPLL */ pll_delay(4000); #endif /* configure UPLL */ writel((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV, &clk_power->upllcon); /* some delay between MPLL and UPLL */ pll_delay(8000); /* set up the I/O ports */ writel(0x007FFFFF, &gpio->gpacon); writel(0x00044555, &gpio->gpbcon); writel(0x000007FF, &gpio->gpbup); writel(0xAAAAAAAA, &gpio->gpccon); writel(0x0000FFFF, &gpio->gpcup); writel(0xAAAAAAAA, &gpio->gpdcon); writel(0x0000FFFF, &gpio->gpdup); writel(0xAAAAAAAA, &gpio->gpecon); writel(0x0000FFFF, &gpio->gpeup); writel(0x000055AA, &gpio->gpfcon); writel(0x000000FF, &gpio->gpfup); writel(0xFF95FFBA, &gpio->gpgcon); writel(0x0000FFFF, &gpio->gpgup); writel(0x002AFAAA, &gpio->gphcon); writel(0x000007FF, &gpio->gphup); return 0; }
(4) 做了以上修改后,重新make编译成功,烧写u-boot.bin到Nor Flash,重启开发板,串口输出如下:
从上图可知,串口输出的是乱码,究其原因应该是波特率设置的问题。
2. 查看波特率的设置,解决乱码的问题
(1) 查看串口波特率的设置,在arch\arm\lib中有函数board_init_f,其中有一个结构体init_sequence:
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
source insight 里跳转找到这个数组:
init_fnc_t *init_sequence[] = { #if defined(CONFIG_ARCH_CPU_INIT) arch_cpu_init, /* basic arch cpu dependent setup */ #endif #if defined(CONFIG_BOARD_EARLY_INIT_F) board_early_init_f, #endif #ifdef CONFIG_OF_CONTROL fdtdec_check_fdt, #endif timer_init, /* initialize timer */ #ifdef CONFIG_FSL_ESDHC get_clocks, #endif env_init, /* initialize environment */ init_baudrate, /* initialze baudrate settings */ serial_init, /* serial communications setup */ console_init_f, /* stage 1 init of console */ display_banner, /* say that we are here */ #if defined(CONFIG_DISPLAY_CPUINFO) print_cpuinfo, /* display cpu info (and speed) */ #endif #if defined(CONFIG_DISPLAY_BOARDINFO) checkboard, /* display board info */ #endif #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C) init_func_i2c, #endif dram_init, /* configure available RAM banks */ NULL, };
init_sequence里是各种的初始化,找到串口初始化函数 serial_init
,跳转到/drivers/serial/serial_s3c24x0.c里:
int serial_init(void)
{
return serial_init_dev(UART_NR);
}
接着跳转到函数:serial_init_dev
static int serial_init_dev(const int dev_index) { struct s3c24x0_uart *uart = s3c24x0_get_base_uart(dev_index); #ifdef CONFIG_HWFLOW hwflow = 0; /* turned off by default */ #endif /* FIFO enable, Tx/Rx FIFO clear */ writel(0x07, &uart->ufcon); writel(0x0, &uart->umcon); /* Normal,No parity,1 stop,8 bit */ writel(0x3, &uart->ulcon); /* * tx=level,rx=edge,disable timeout int.,enable rx error int., * normal,interrupt or polling */ writel(0x245, &uart->ucon); #ifdef CONFIG_HWFLOW writel(0x1, &uart->umcon); /* rts up */ #endif /* FIXME: This is sooooooooooooooooooo ugly */ #if defined(CONFIG_ARCH_GTA02_v1) || defined(CONFIG_ARCH_GTA02_v2) /* we need auto hw flow control on the gsm and gps port */ if (dev_index == 0 || dev_index == 1) writel(0x10, &uart->umcon); #endif _serial_setbrg(dev_index); return (0); }
接着跳转到:_serial_setbrg
void _serial_setbrg(const int dev_index)
{
struct s3c24x0_uart *uart = s3c24x0_get_base_uart(dev_index);
unsigned int reg = 0;
int i;
/* value is calculated so : (int)(PCLK/16./baudrate) -1 */
reg = get_PCLK() / (16 * gd->baudrate) - 1;
writel(reg, &uart->ubrdiv);
for (i = 0; i < 100; i++)
/* Delay */ ;
}
在这里终于看到有时钟相关的函数:get_PCLK()
,其中通过get_HCLK()函数获得PLCK时钟,然后跳转到 arch/arm/cpu/arm920t/s3c24x0/speed.c 文件的get_HCLK()
:
/* return HCLK frequency */ ulong get_HCLK(void) { struct s3c24x0_clock_power *clk_power = s3c24x0_get_base_clock_power(); #ifdef CONFIG_S3C2440 switch (readl(&clk_power->clkdivn) & 0x6) { default: case 0: return get_FCLK(); case 2: return get_FCLK() / 2; case 4: return (readl(&clk_power->camdivn) & (1 << 9)) ? get_FCLK() / 8 : get_FCLK() / 4; case 6: return (readl(&clk_power->camdivn) & (1 << 8)) ? get_FCLK() / 6 : get_FCLK() / 3; } #else return (readl(&clk_power->clkdivn) & 2) ? get_FCLK() / 2 : get_FCLK(); #endif }
从source insight中可以发现#ifdef CONFIG_S3C2440
这一句是黑色的,说明没有定义这个CONFIG_S3C2440
;
处理措施:在include/configs/smdk2440.h: 去掉CONFIG_S3C2410 ,换成CONFIG_S3C2440,如下所示:
然后,make重编译,发现编译有错误如下:
(2) 分析图错误,使代码编译通过以测试串口是否可以正常输出:
错误原因:应该是此时的uboot 还不支持Nand Flash,所以此时暂时去掉对Nand Flash的支持
解决措施:把关于nand的编译选项去掉,查看drivers/mtd/nand/Makefile,找到只一句:
说明编译s3c2410_nand.c的前提是定义CONFIG_NAND_S3C2410,在源码中搜索grep -nR "CONFIG_NAND_S3C2410"
,
发现在smdk2440.h中找到:
上图说明只有定义了CONFIG_CMD_NAND
,CONFIG_NAND_S3C2410
才被定义;因此,让CONFIG_CMD_NAND
不被定义即刻。
从smdk2440.h搜索到在101行有: #define CONFIG_CMD_NAND
,把它注释掉即可。
(3) make重新编译,发现有如下错误:
同样的方法,去掉u-boot对yaffs文件系统的支持,另外为了减少编译后u-boot的大小,这里我们去掉对所有文 件系统的支持,把smdk2440.h对文件系统的配置全部注释掉,如下图所示:
(4) 重编译 :
make distclean (注:不清理工程,重新配置编译,直接make编译会出错)
make smdk2440_config
make
(5) ls u-boot.bin
由上图可知编译出来的u-boot变小了很多,只有大约213kB。
(6) 把编译出来的u-boot.bin重新烧写到开发板,串口输出正常,如下图所示:
上图可知,串口输出虽然正常了,但u-boot还无法正常启动,下一步继续修改代码完善uboot。
原来的代码在链接时加了”-pie”选项, 使得u-boot.bin里多了”(.rel)”, “*(.dynsym)”使得程序非常大,不利于从NAND启动(重定位之前的启动代码应该少于4K)。
1. 去"-pie"选项:
在文件:arch/arm/config.mk的第75行,去掉:LDFLAGS_u-boot += -pie
;
2.把之前编写好的init.c文件放到board/samsung/smdk2440目录,init.c文件的代码如下:
/*那么如何判断是NOR启动还是NAND启动呢? 可以利用NOR的特点: *1.NOR可以像内存一样读,但不可以像内存一样写; *2.可以通过向0地址写数据已区分是NAND启动还是NOR启动; *(因为NAND启动时,0地址是RAM,可读可写;但NOR启动时,0地址是NOR FLASH,可读不可写) *返回值:1:Nor启动; 0:NAND启动 */ #define NFCONF (*((volatile unsigned long *)0x4E000000)) #define NFCONT (*((volatile unsigned long *)0x4E000004)) #define NFCMMD (*((volatile unsigned char *)0x4E000008)) #define NFADDR (*((volatile unsigned char *)0x4E00000c)) #define NFDATA (*((volatile unsigned char *)0x4E000010)) #define NFSTAT (*((volatile unsigned long *)0x4E000020)) /* GPIO */ #define GPHCON (*(volatile unsigned long *)0x56000070) #define GPHUP (*(volatile unsigned long *)0x56000078) /* UART registers*/ #define ULCON0 (*(volatile unsigned long *)0x50000000) #define UCON0 (*(volatile unsigned long *)0x50000004) #define UFCON0 (*(volatile unsigned long *)0x50000008) #define UMCON0 (*(volatile unsigned long *)0x5000000c) #define UTRSTAT0 (*(volatile unsigned long *)0x50000010) #define UTXH0 (*(volatile unsigned char *)0x50000020) #define URXH0 (*(volatile unsigned char *)0x50000024) #define UBRDIV0 (*(volatile unsigned long *)0x50000028) #define TXD0READY (1<<2) /* NAND flash 设置时序(需要对比s3c2440与NAND手册的NAND时序) * */ void nand_init_ll(void) { /*设置NANDFlash时序*/ #define TACLS 0 #define TWRPH0 1 #define TWRPH1 0 NFCONF &= ~((3<<12)|(7<<8)|(7<<4)); NFCONF |= (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4); /*使能NANDflash 控制器, *禁止片选(开发板没正式使用片选,避免错误的操作) *初始化 ECC 编码器/译码器*/ NFCONT = (1<<0)|(1<<1)|(1<<4); } static void nand_cmd(unsigned char cmd) { volatile int i; NFCMMD = cmd; for(i = 0; i < 10; i++);/*等待*/ } static void nand_addr(unsigned int addr) { unsigned int col = addr % 2048; unsigned int page = addr / 2048; volatile int i; NFADDR = col & 0xff; for(i = 0; i < 10; i++);/*等待*/ NFADDR = (col >> 8) & 0xff; for(i = 0; i < 10; i++);/*等待*/ NFADDR = page & 0xff; for(i = 0; i < 10; i++);/*等待*/ NFADDR = (page >> 8) & 0xff; for(i = 0; i < 10; i++);/*等待*/ NFADDR = (page >> 16) & 0xff; for(i = 0; i < 10; i++);/*等待*/ } static void nand_wait_ready(void) { while(!(NFSTAT & 0x01)); } static unsigned char nand_data(void) { return NFDATA; } static void nand_select(void) { NFCONT &= ~(1<<1); } static void nand_deselect(void) { NFCONT |= (1<<1); } void nand_read_ll(unsigned int addr, unsigned char *buf,unsigned int len) { int col = addr % 2048; int i = 0; /*1.片选*/ nand_select(); while(i < len) { /*2.发出读命令00h*/ nand_cmd(0x00); /*3.发出地址(分5个地址周期)*/ nand_addr(addr); /*4.发出读命令30h*/ nand_cmd(0x30); /*5.等待就绪(判断状态)*/ nand_wait_ready(); /*6.读数据*/ for(; (col < 2048) && (i < len); col++) { buf[i] = nand_data(); i++; addr++; } col = 0; } /*7.取消选中*/ nand_deselect(); } static int isBootFromNorFlash(void) { volatile unsigned int *p = (volatile unsigned int*)0; unsigned int val; val = *p; *p = 0x12345678; if(*p == val) /*写前写后值一样,证明无法向0地址写数据,是NOR启动*/ { return 1; }else /*否则是NAND启动*/ { *p = val; /*恢复数据*/ return 0; } } void copy_code_to_sdram(unsigned char *src, unsigned char *dest,unsigned int len) { unsigned int i=0; /*如果是NOR启动*/ if(isBootFromNorFlash()) /*那么如何判断是NOR启动还是NAND启动呢? */ { while(i < len) { dest[i] = src[i]; i++; } }else /*否则是NAND启动*/ { nand_init_ll();/*初始化NAND*/ nand_read_ll((unsigned int )src,dest,len); } } /*c语言如何引用连接脚本的 __bss_start、__bss_end等变量呢? * extern int __bss_start,__bss_end; * int *p = &__bss_start; * */ void clean_bss(void) { extern int __bss_start,__bss_end__; int *p = &__bss_start; for(;p < &__bss_end__;p++) { *p = 0; } }
3. 修改board/samsung/smdk2440目录下的Makefile,把init.c 文件编译进uboot:
4. 修改start.S文件
由前面的分析可知,代码重定位函数在board_init_f
函数中,board_init_f
做了一系列的硬件初始化后最后才执行代码重定位函数relocate_code
,这样relocate_code
函数很有可能不在代码的前4k范围内(NAND Flash启动硬件自动把NAND Flash的前4k代码复制到SRAM中),无法重定位。所以必须确保代码重定位函数在代码的前4k范围内。
(1) 因此,在u-boot添加自己的代码重定位函数,在start.S 有如下代码:
/* Set stackpointer in internal RAM to call board_init_f */
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
bl board_init_f
更改为:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) // 0x30000000 + 0x1000 - 128 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ bl nand_init_ll mov r0,#0 ldr r1, _TEXT_BASE ldr r2, _bss_start_ofs bl copy_code_to_sdram bl clean_bss ldr pc,=call_board_init_f /* Set stackpointer in internal RAM to call board_init_f */ call_board_init_f: ldr r0,=0x00000000 bl board_init_f
(2) 注释掉u-boot自身的重定位代码:
/*------------------------------------------------------------------------------*/ /* * void relocate_code (addr_sp, gd, addr_moni) * * This "function" does not return, instead it continues in RAM * after relocating the monitor code. * */ #if 0 .globl relocate_code relocate_code: mov r4, r0 /* save addr_sp */ mov r5, r1 /* save addr of gd */ mov r6, r2 /* save addr of destination */ /* Set up the stack */ stack_setup: mov sp, r4 /* adr 与 ldr伪指令的区别: * adr r0, _start 得到的是 _start 的当前执行位置,由 pc+offset 决定 * ldr r0, =_start 得到的是绝对的地址,链接时决定 */ adr r0, _start cmp r0, r6 beq clear_bss /* skip relocation */ mov r1, r6 /* r1 <- scratch for copy_loop */ ldr r3, _bss_start_ofs add r2, r0, r3 /* r2 <- source end address */ copy_loop: ldmia r0!, {r9-r10} /* copy from source address [r0] *///将r0处的两个32位数据拷到r9-r10中,然后r0+=8 stmia r1!, {r9-r10} /* copy to target address [r1] */ //将拷出来的两个数据放入r1(重定位地址)处,然后r1+=8 cmp r0, r2 /* until source end address [r2] */// uboot 重定位到0x33F41000这个地址 blo copy_loop #ifndef CONFIG_SPL_BUILD /* * fix .rel.dyn relocations */ ldr r0, _TEXT_BASE /* r0 <- Text base *///r0=text段基地址=0 sub r9, r6, r0 /* r9 <- relocation offset */ //r9= 重定位后的偏移值=0x33F41000 ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */ //_dynsym_start_ofs =__dynsym_start - _start=0x73608 add r10, r10, r0 /* r10 <- sym table in FLASH */ //所以r10=动态符号表的起始偏移值=0x73608 ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs *///r2=__rel_dyn_start - _start=0x6b568 add r2, r2, r0 /* r2 <- rel dyn start in FLASH */ //所以r2=相对动态信息的起始偏移值=0x6b568 ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */ // _rel_dyn_end_ofs=__rel_dyn_end - _start=0x73608 add r3, r3, r0 /* r3 <- rel dyn end in FLASH */ //所以r3=相对动态信息的结束偏移值=0x00073608 fixloop: ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */ //从反汇编可知,r2=0x6b568地址处的内容= 0x20,所以r0 = 0x20 add r0, r0, r9 /* r0 <- location to fix up in RAM */ //r0=0x33F41000+0x20=0x33F41020 ldr r1, [r2, #4] // r2 + 4 =0x6b56C ,地址0x6b56C的内容是0x17,所以r1 = 0x17 and r7, r1, #0xff // r7 = 0x17& 0xff = 0x17 cmp r7, #23 /* relative fixup? */ // 0x 17的十进制就是 23 ,因此r7与23 相等,下一条指令将跳转到fixabs beq fixrel cmp r7, #2 /* absolute fixup? */ beq fixabs /* ignore unknown type of fixup */ b fixnext fixabs: /* absolute fix: set location to (offset) symbol value */ mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */ add r1, r10, r1 /* r1 <- address of symbol in table */ ldr r1, [r1, #4] /* r1 <- symbol value */ add r1, r1, r9 /* r1 <- relocated sym addr */ b fixnext fixrel: /* relative fix: increase location by offset */ ldr r1, [r0] // r0 = 0x33F41020, 可知知道这个地址的内容即是反汇编0x20地址的内容0x000001e0 add r1, r1, r9 // r1 = r1 + r9 = 0x33F41000 + 0x000001e0 = 0x33F411e0 fixnext: str r1, [r0] //改变链接地址里的内容, 0x33F41020=0x33F411e0 (之前为0x1e0) add r2, r2, #8 /* each rel.dyn entry is 8 bytes */ //r2等于下一个相对动态信息(0x24)的地址 cmp r2, r3 //若没到尾部__rel_dyn_end,便继续执行: fixloop blo fixloop #endif clear_bss: #ifndef CONFIG_SPL_BUILD ldr r0, _bss_start_ofs ldr r1, _bss_end_ofs mov r4, r6 /* reloc addr */ add r0, r0, r4 add r1, r1, r4 mov r2, #0x00000000 /* clear */ clbss_l:str r2, [r0] /* clear loop... */ add r0, r0, #4 cmp r0, r1 bne clbss_l bl coloured_LED_init bl red_led_on #endif
5. 修改代码重定位地址CONFIG_SYS_TEXT_BASE为0x33f00000
:
在前面的u-boot重定位函数copy_code_to_sdram
中,使用到_TEXT_BASE
作为copy_code_to_sdram
函数的目的地址,那么它在哪里被定义呢?
在代码中跳转到 :_TEXT_BASE
bl nand_init_ll
mov r0,#0
ldr r1, _TEXT_BASE
跳转到lowlevel_init.S中的这里:
_TEXT_BASE:
.word CONFIG_SYS_TEXT_BASE
然后点击:CONFIG_SYS_TEXT_BASE
,跳转到smdk2440.h中的这里:
#define CONFIG_SYS_TEXT_BASE 0x0
然后把CONFIG_SYS_TEXT_BASE
的值改为0x33f00000
#define CONFIG_SYS_TEXT_BASE 0x33f00000
6. 修改board_init_f(arch/arm/lib/board.c中), 把relocate_code去掉:
//relocate_code(addr_sp, id, addr);
同时去掉:
// addr -= gd->mon_len; // addr = 0x33ff0000 - 0x000ae4e0 = 0x33F41B20
// addr &= ~(4096 - 1); // 4k对齐 addr = 0x33F41B20 & ~0xfff = 0x33F41000
改为:
addr = CONFIG_SYS_TEXT_BASE;
因为在调用uboot第二阶段的函数void board_init_r(gd_t *id, ulong dest_addr)
之前需要重新设置栈,栈空间等内存分布在board_init_f
中设置,在start.S中把值赋给sp
;第二阶段函数所用得到的参数id也在board_init_f
设置,而参数dest_addr就是uboot代码重定位的目的地址等于_TEXT_BASE
。所以,需要在start.S 定义存储sp的全局变量base_sp
和存储id的全局变量gd_addr
:
.globl base_sp
base_sp:
.long 0
.globl gd_addr
gd_addr:
.long 0
同时,在start.S 中进入
同时在board_init_f
函数的开头,引入这两个变量,
extern unsigned long base_sp;
extern unsigned long gd_addr;
最后,board_init_f
函数修改如下:
void board_init_f(ulong bootflag) { bd_t *bd; init_fnc_t **init_fnc_ptr; gd_t *id; ulong addr, addr_sp; extern unsigned long base_sp; extern unsigned long gd_addr; #ifdef CONFIG_PRAM ulong reg; #endif bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f"); /* Pointer is writable since we allocated a register for it */ gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07); /* compiler optimization barrier needed for GCC >= 3.4 */ __asm__ __volatile__("": : :"memory"); memset((void *)gd, 0, sizeof(gd_t)); gd->mon_len = _bss_end_ofs; #ifdef CONFIG_OF_EMBED /* Get a pointer to the FDT */ gd->fdt_blob = _binary_dt_dtb_start; #elif defined CONFIG_OF_SEPARATE /* FDT is at end of image */ gd->fdt_blob = (void *)(_end_ofs + _TEXT_BASE); #endif /* Allow the early environment to override the fdt address */ gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16, (uintptr_t)gd->fdt_blob); for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } } #ifdef CONFIG_OF_CONTROL /* For now, put this check after the console is ready */ if (fdtdec_prepare_fdt()) { panic("** CONFIG_OF_CONTROL defined but no FDT - please see " "doc/README.fdt-control"); } #endif debug("monitor len: %08lX\n", gd->mon_len); /* * Ram is setup, size stored in gd !! */ debug("ramsize: %08lX\n", gd->ram_size); #if defined(CONFIG_SYS_MEM_TOP_HIDE) /* * Subtract specified amount of memory to hide so that it won't * get "touched" at all by U-Boot. By fixing up gd->ram_size * the Linux kernel should now get passed the now "corrected" * memory size and won't touch it either. This should work * for arch/ppc and arch/powerpc. Only Linux board ports in * arch/powerpc with bootwrapper support, that recalculate the * memory size from the SDRAM controller setup will have to * get fixed. */ gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE; #endif addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size; #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_PRAM /* * reserve protected RAM */ reg = getenv_ulong("pram", 10, CONFIG_PRAM); addr -= (reg << 10); /* size is in kB */ debug("Reserving %ldk for protected RAM at %08lx\n", reg, addr); #endif /* CONFIG_PRAM */ #if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) /* reserve TLB table */ addr -= (4096 * 4); /* round down to next 64 kB limit */ addr &= ~(0x10000 - 1); gd->tlb_addr = addr; debug("TLB table at: %08lx\n", addr); #endif /* round down to next 4 kB limit */ addr &= ~(4096 - 1); debug("Top of RAM usable for U-Boot at: %08lx\n", addr); #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 */ /* * reserve memory for U-Boot code, data & bss * round down to next 4 kB limit */ //addr -= gd->mon_len; //addr &= ~(4096 - 1); addr = CONFIG_SYS_TEXT_BASE; debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr); #ifndef CONFIG_SPL_BUILD /* * reserve memory for malloc() arena */ addr_sp = addr - TOTAL_MALLOC_LEN; debug("Reserving %dk for malloc() at: %08lx\n", TOTAL_MALLOC_LEN >> 10, addr_sp); /* * (permanently) allocate a Board Info struct * and a permanent copy of the "global" data */ addr_sp -= sizeof (bd_t); bd = (bd_t *) addr_sp; gd->bd = bd; debug("Reserving %zu Bytes for Board Info at: %08lx\n", sizeof (bd_t), addr_sp); #ifdef CONFIG_MACH_TYPE gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */ #endif addr_sp -= sizeof (gd_t); id = (gd_t *) addr_sp; debug("Reserving %zu Bytes for Global Data at: %08lx\n", sizeof (gd_t), addr_sp); /* setup stackpointer for exeptions */ gd->irq_sp = addr_sp; #ifdef CONFIG_USE_IRQ addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ); debug("Reserving %zu Bytes for IRQ stack at: %08lx\n", CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp); #endif /* leave 3 words for abort-stack */ addr_sp -= 12; /* 8-byte alignment for ABI compliance */ addr_sp &= ~0x07; #else addr_sp += 128; /* leave 32 words for abort-stack */ gd->irq_sp = addr_sp; #endif debug("New Stack Pointer is: %08lx\n", addr_sp); #ifdef CONFIG_POST post_bootmode_init(); post_run(NULL, POST_ROM | post_bootmode_get(0)); #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 */ 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)); gd_addr = (unsigned long)id; base_sp = addr_sp; // relocate_code(addr_sp, id, addr); /* NOTREACHED - relocate_code() does not return */ }
7. 修改链接脚本: 把start.S, init.c, lowlevel.S等文件放在最前面:
链接脚本为:arch/arm/cpu/u-boot.lds 最前面有下面一段代码:
.text :
{
__image_copy_start = .;
CPUDIR/start.o (.text)
*(.text)
}
将其改为:
.text :
{
__image_copy_start = .;
CPUDIR/start.o (.text)
board/samsung/smdk2440/libsmdk2440.o (.text)
*(.text)
}
libsmdk2440.o
包含了start.S, init.c, lowlevel.S,所以只需要把它放前。
8. 编译,烧写到NAND Flash,设置为NAND 启动观察能否正常启动:
从上图可知,u-boot已经支持从NAND Flash 启动了,但是在打印DRAM后就卡死了,经检查发现,卡死在board_init_f
函数里无法继续执行。在board_init_f
函数最后添加puts("test1\r\n")
,编译烧写后打印如下:
把u-boot烧写到开发板后,会一直重复打印上图的内容。找了很久也找不出原因,最终从admithhq 的博客找到了解决办法:在include/common.h中的276行,unsigned int board_init_f (ulong) __attribute__ ((noreturn));
把__attribute__ ((noreturn))
去掉,否则board_init_r
无法取得需要的参数,导致程序不能正常运行。(同时也把· void board_init_r (gd_t *, ulong) __attribute__ ((noreturn));
的__attribute__ ((noreturn))
去掉)
去掉后,重新编译uboot并烧写到开发板,可以正常的往下执行,如下图所示:
1.从上一节把uboot烧写到NAND启动后,最后打印出Flash: *** failed ***
,然后提示### ERROR ### Please RESET the board ###
,让我们重启开发板。
(1)我们在source insight中搜索“Flash:”这个字符串出现在哪里,在board.c中的board_init_r函数中,有以下代码:
#if !defined(CONFIG_SYS_NO_FLASH) puts("Flash: "); flash_size = flash_init(); if (flash_size > 0) { # ifdef CONFIG_SYS_FLASH_CHECKSUM char *s = getenv("flashchecksum"); print_size(flash_size, ""); /* * Compute and print flash CRC if flashchecksum is set to 'y' * * NOTE: Maybe we should add some WATCHDOG_RESET()? XXX */ if (s && (*s == 'y')) { printf(" CRC: %08X", crc32(0, (const unsigned char *) CONFIG_SYS_FLASH_BASE, flash_size)); } putc('\n'); # else /* !CONFIG_SYS_FLASH_CHECKSUM */ print_size(flash_size, "\n"); # endif /* CONFIG_SYS_FLASH_CHECKSUM */ } else { puts(failed); hang(); } #endif
上面代码有以下两行:
puts(failed);
hang();
其中:failed
是一个指针,它的定义是:static char *failed = "*** failed ***\n";
hang()
函数的定义为:
void hang(void)
{
puts("### ERROR ### Please RESET the board ###\n");
for (;;);
}
结合前面NAND启动的串口输出信息可知,Flash初始化失败,程序执行puts(failed)
后进入hang()
死循环。
Flash初始化失败,回看Flash初始化函数:flash_size = flash_init();
, flash_init()的代码如下(在drivers/mtd/cfi_flash.c中):
unsigned long flash_init (void) { unsigned long size = 0; int i; #ifdef CONFIG_SYS_FLASH_PROTECTION /* read environment from EEPROM */ char s[64]; getenv_f("unlock", s, sizeof(s)); #endif /* Init: no FLASHes known */ for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; ++i) { flash_info[i].flash_id = FLASH_UNKNOWN; /* Optionally write flash configuration register */ cfi_flash_set_config_reg(cfi_flash_bank_addr(i), cfi_flash_config_reg(i)); if (!flash_detect_legacy(cfi_flash_bank_addr(i), i)) flash_get_size(cfi_flash_bank_addr(i), i); size += flash_info[i].size; if (flash_info[i].flash_id == FLASH_UNKNOWN) { #ifndef CONFIG_SYS_FLASH_QUIET_TEST printf ("## Unknown flash on Bank %d " "- Size = 0x%08lx = %ld MB\n", i+1, flash_info[i].size, flash_info[i].size >> 20); #endif /* CONFIG_SYS_FLASH_QUIET_TEST */ } #ifdef CONFIG_SYS_FLASH_PROTECTION else if ((s != NULL) && (strcmp(s, "yes") == 0)) { /* * Only the U-Boot image and it's environment * is protected, all other sectors are * unprotected (unlocked) if flash hardware * protection is used (CONFIG_SYS_FLASH_PROTECTION) * and the environment variable "unlock" is * set to "yes". */ if (flash_info[i].legacy_unlock) { int k; /* * Disable legacy_unlock temporarily, * since flash_real_protect would * relock all other sectors again * otherwise. */ flash_info[i].legacy_unlock = 0; /* * Legacy unlocking (e.g. Intel J3) -> * unlock only one sector. This will * unlock all sectors. */ flash_real_protect (&flash_info[i], 0, 0); flash_info[i].legacy_unlock = 1; /* * Manually mark other sectors as * unlocked (unprotected) */ for (k = 1; k < flash_info[i].sector_count; k++) flash_info[i].protect[k] = 0; } else { /* * No legancy unlocking -> unlock all sectors */ flash_protect (FLAG_PROTECT_CLEAR, flash_info[i].start[0], flash_info[i].start[0] + flash_info[i].size - 1, &flash_info[i]); } } #endif /* CONFIG_SYS_FLASH_PROTECTION */ } flash_protect_default(); #ifdef CONFIG_FLASH_CFI_MTD cfi_mtd_init(); #endif return (size); }
上面的程序有一个if判断语句:
if (!flash_detect_legacy(cfi_flash_bank_addr(i), i))
flash_get_size(cfi_flash_bank_addr(i), i);
从字面意思看出flash_detect_legacy
为旧的检测flash,flash_get_size
就应该为新的检测flash机制,先看一下旧的,没看出什么,再看flash_get_size
,发现有很多debug调试信息,有这么多调试信息,那就应该用起来:
debug ("manufacturer is %d\n", info->vendor);
debug ("manufacturer id is 0x%x\n", info->manufacturer_id);
debug ("device id is 0x%x\n", info->device_id);
debug ("device id2 is 0x%x\n", info->device_id2);
debug ("cfi version is 0x%04x\n", info->cfi_version);
搜索debug 查到:
在include/common.h中有如下代码:
#define debug(fmt, args...) \
debug_cond(_DEBUG, fmt, ##args)
很明显应该是用的_DEBUG,搜索_DEBUG,在include/common.h中有:
#ifdef DEBUG
#define _DEBUG 1
#else
#define _DEBUG 0
#endif
那么我们就把#define _DEBUG 1
给加上,在cfi_flash.c
中定义如下两行:
#define DEBUG 1 //不确定是哪个就都定义,反正也不会出错
#define _DEBUG 1
重新编译u-boot烧写到NOR Flash,并设置为NOR Flash启动,启动结果如下图:
从打印的这句话:JEDEC PROBE: ID c2 2249 0
可知厂家ID是c2
,设备ID是2249
,查看NOR Flash(MX29LV160DBT)的数据手册有:
从数据手册可知,MX29LV160DBT的设备厂家ID是C2
,设备ID是2249
,与uboot读出的厂家ID、设备ID相符。
注:当把上面编译好的uboot烧写到NAND Flash,并设置为NAND启动时,uboot打印的信息如下图:
从上图可知,从NAND Flash启动读出的厂家ID是f0
,设备ID是ea00
,这明显与从NOR Flash启动读厂家ID、设备ID不一样。为什么同一个uboot在NOR Flash启动与NAND 启动读出的NOR flash厂家ID、设备ID不一样呢?(当时被这个问题卡了好久,最终在视频听到韦老师的一句话:“如果从NAND启动,跟本就没有NOR flash …”,恍然大悟。)
原因:当Jz2440开发板从NAND启动时,CPU根本看不到NOR Flash,此时的NOR Flash是不存在的。从s3c2440 的数据手册可找到它的地址空间分布如下图:
从上面的两张图可知,从NOR Flash启动时,NOR Flash 的地址范围是0 ~ 0x00200000
( NOR Flahs 2MB,并且接到nGCS0),从NAND Flash启动时片内的4kB RAM 的地址范围是:0 ~ 0x00001000
,当从NAND启动时,CPU是看不到NOR Flash的。
那么当从NAND 启动时读出的NOR flash厂家ID、设备ID为什么是f0、ea00呢?
原因:从上面的NOR Flash数据手册的读ID指令表可知,复位时会向0x00地址写0xF0
,当读ID时往0x555
、0x2AA
等地址写数据后,最后从
0x00地址读出厂家ID、从0x01地址读设备ID(注:Jz2440开发板的NOR Flash的位宽是16bit,所以读取NOR Flash某个地址上的数据
时,需要把NOR Flash对应的地址左移一位(地址乘以2),因此读设备ID时读的是内存0x02(0x01<<1)地址的内容)。NAND Flash启动时,
s3c2440会自动不u-boot 的前4kB内容复制到片内RAM,当读厂家ID、设备ID时,读到的内容就是片内RAM的0x00、0x02地址里的数据。
用UltraEdit软件打开u-boot.bin文件里的数据如下图,0x00地址的内容复位NOR Flash是被改为0xF0,从0x02地址读出的16位数据是
0xEA00 (s3c2440的存储方式是小端模式,高字节存储在高地址、低字节存储在低地址)
言归正传,继续记录uboot移植,前面说uboot从NOR Flash启动正确读出了NOR Flash的厂家ID、设备ID。根据打印信息,在源码中搜索字符串“JEDEC PROBE:”在drivers/mtd/cfi_flash.c中的flash_detect_legacy
函数中有如下代码片段:
debug("JEDEC PROBE: ID %x %x %x\n",
info->manufacturer_id,
info->device_id,
info->device_id2);
if (jedec_flash_match(info, info->start[0]))
break;
else
unmap_physmem((void *)info->start[0],
MAP_NOCACHE);
从上面的代码可知厂家ID、设备ID是如何打印的;同时还需要通过jedec_flash_match
函数进行匹配,jedec_flash_match
函数代码如下:(在drivers/mtd/jedec_flash.c)
int jedec_flash_match(flash_info_t *info, ulong base) { int ret = 0; int i; ulong mask = 0xFFFF; if (info->chipwidth == 1) mask = 0xFF; for (i = 0; i < ARRAY_SIZE(jedec_table); i++) { if ((jedec_table[i].mfr_id & mask) == (info->manufacturer_id & mask) && (jedec_table[i].dev_id & mask) == (info->device_id & mask)) { fill_info(info, &jedec_table[i], base); ret = 1; break; } } return ret; }
从jedec_flash_match
发现一个数组jedec_table
,匹配设备的ID用的应该就是这个数组里的内容了,查看数组如下:
static const struct amd_flash_info jedec_table[] = { #ifdef CONFIG_SYS_FLASH_LEGACY_256Kx8 { .mfr_id = (u16)SST_MANUFACT, .dev_id = SST39LF020, .name = "SST 39LF020", .uaddr = { [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ }, .DevSize = SIZE_256KiB, .CmdSet = P_ID_AMD_STD, .NumEraseRegions= 1, .regions = { ERASEINFO(0x01000,64), } }, #endif ......
这个结构体里的内容,定义了许多类型的flash,每一个定义就是一个flash芯片。我们在里面自己定义我们的芯片结构项:
{ .mfr_id = (u16)0x00C2,/*厂家ID*/ .dev_id = 0x2249, /*设备ID*/ .name = "MXIC MX29LV160DB", .uaddr = { [1] = MTD_UADDR_0x0555_0x02AA /* x16 *//*NOR FLASH看到的解锁地址*/ }, .DevSize = SIZE_2MiB, .CmdSet = P_ID_AMD_STD, .NumEraseRegions = 4, /* 擦除区域的数目 */ .regions = { ERASEINFO(16*1024, 1), ERASEINFO(8*1024, 2), ERASEINFO(32*1024, 1), ERASEINFO(64*1024, 31), } },
同时把board_init_r
函数中的以下两行代码注释掉,让u-boot继续运行,避免u-boot卡死在死循环里。
//puts(failed);
//hang();
然后,重新编译u-boot,烧写到NOR Flash并启动运行,打印信息如下:
从上图可知,已经成功打印出Flash的容量:2MiB;但是还显示错误ERROR: too many flash sectors
(2) 从源码中搜索错误ERROR: too many flash sectors
,发现在drivers/mtd/cfi_flash.c有如下代码:
if (sect_cnt >= CONFIG_SYS_MAX_FLASH_SECT) {
printf("ERROR: too many flash sectors\n");
break;
}
跳转到CONFIG_SYS_MAX_FLASH_SECT
这个定义(在smdk2440.h中),有:
#define CONFIG_SYS_MAX_FLASH_SECT (19)
从下图的NOR Flash的数据手册可知,Secter总共有35个;因此,把CONFIG_SYS_MAX_FLASH_SECT
宏定义改为::
#define CONFIG_SYS_MAX_FLASH_SECT (35)
此外,在cfi_flash.c
中把Debug调试信息去掉:
//#define DEBUG 1 //不确定是哪个就都定义,反正也不会出错
//#define _DEBUG 1
重新编译u-boot,烧写到NOR Flash并启动运行,打印信息如下:
从上图可知,已经可以正常启动uboot了,那么现在测试一下NOR FLASH能否擦除与读写。
① 先解除写保护,在串口中输入:protect off all
② 输入:flinfo
;打印正常:
③ 输入:erase 80000 8ffff
④ 输入:cp.b 30000000 80000 10000
从上图可知,NOR Flash 写成功。
(2) 测试norflash写功能的完整性:
在串口输入以下命令:
protect off all //关闭写保护
erase 80000 8ffff
cp.b 30000000 80000 10000
md.b 80000 //显示0x80000地址的内容
md.b 30000000
cmp.b 30000000 80000 10000 //比较0x30000000地址的内容与0x80000地址的内容,比较数据长度0x10000字节
结果如下图:
由上图可知,NOR Flash读写测试成功。
1.在之前为让uboot编译通过,我们把NAND FLASH 给屏蔽掉了,现在把它添加回来,把include/configs/smdk2440.h
里的#define CONFIG_CMD_NAND
取消注释,重新编译,错误如下:
遇到错误要按照从第一个错误开始解决的办法,因为有时候第一个错误解决了,后面的错误可能就没有了。我们这里第一个错误是:
s3c2410_nand.c:72: error: dereferencing pointer to incomplete type
显示大概的意思是不完整的类型,我们去s3c2410_nand.c
中72行去寻找,是如下代码:
if (ctrl & NAND_NCE)
72行: writel(readl(&nand->nfconf) & ~S3C2410_NFCONF_nFCE,
&nand->nfconf);
调用了一个nand结构体:
struct s3c2410_nand *nand = s3c2410_get_base_nand();
结构体如下:
#ifdef CONFIG_S3C2410
/* NAND FLASH (see S3C2410 manual chapter 6) */
struct s3c2410_nand {
u32 nfconf;
u32 nfcmd;
u32 nfaddr;
u32 nfdata;
u32 nfstat;
u32 nfecc;
};
#endif
从上面的#ifdef CONFIG_S3C2410
可知,我们应该是没有定义CONFIG_S3C2410
,因为之前是我们在smdk2440.h中注释掉了2410的定义,换成了2440:
//#define CONFIG_S3C2410 /* specifically a SAMSUNG S3C2410 SoC */
#define CONFIG_S3C2440 /* specifically a SAMSUNG S3C2440 SoC */
所以,现在我们只能修改代码支持2440的部分了,操作步骤:
① 把drivers/mtd/nand/s3c2410_nand.c
复制为s3c2440_nand.c
;
② 修改drivers/mtd/nand/
目录下的Makefile
,在Makefile
里搜索CONFIG_NAND_S3C2410
,有如下代码:
COBJS-$(CONFIG_NAND_S3C2410) += s3c2410_nand.o
在这行代码后面添加下面的代码,把s3c2440_nand.c
编译进uboot
;
COBJS-$(CONFIG_NAND_S3C2440) += s3c2440_nand.o
③ 在include/configs/smdk2440.h
中添加支持s3c2440_nand
,在smdk2440.h
中搜索CONFIG_NAND_S3C2410
,有如下代码:
#define CONFIG_NAND_S3C2410
#define CONFIG_SYS_S3C2410_NAND_HWECC
改为:
#ifdef CONFIG_S3C2410
#define CONFIG_NAND_S3C2410
#define CONFIG_SYS_S3C2410_NAND_HWECC
#else
#define CONFIG_NAND_S3C2440
#define CONFIG_SYS_S3C2440_NAND_HWECC
#endif
2.首先去看一下NAND Flash 初始化的相关函数,在board.c
中board_init_r
函数中有:
void board_init_r(gd_t *id, ulong dest_addr)
{
......
......
nand_init(); /* go init the NAND */
......
......
}
跳转到nand.c
文件中,有如下代码:
void nand_init(void) { #ifdef CONFIG_SYS_NAND_SELF_INIT board_nand_init(); #else int i; for (i = 0; i < CONFIG_SYS_MAX_NAND_DEVICE; i++) nand_init_chip(i); #endif printf("%lu MiB\n", total_nand_size / 1024); #ifdef CONFIG_SYS_NAND_SELECT_DEVICE /* * Select the chip in the board/cpu specific driver */ board_nand_select_device(nand_info[nand_curr_device].priv, nand_curr_device); #endif }
关于上面代码#ifdef CONFIG_SYS_NAND_SELF_INIT
中的CONFIG_SYS_NAND_SELF_INIT
,在nand.h
中的代码如下图所示:(source insight截图),从图可知CONFIG_NAND_FSL_ELBC
是黑色的,说明没有定义;因此,上面的代码不会执行board_nand_init()
函数。
跳过board_nand_init()
函数,执行nand_init_chip()
函数:
static void nand_init_chip(int i) { struct mtd_info *mtd = &nand_info[i]; struct nand_chip *nand = &nand_chip[i]; ulong base_addr = base_address[i]; int maxchips = CONFIG_SYS_NAND_MAX_CHIPS; if (maxchips < 1) maxchips = 1; mtd->priv = nand; nand->IO_ADDR_R = nand->IO_ADDR_W = (void __iomem *)base_addr; if (board_nand_init(nand)) return; if (nand_scan(mtd, maxchips)) return; nand_register(i); }
接下来看board_nand_init
函数:(在drivers/mtd/nand/s3c2440_nand.c
)
int board_nand_init(struct nand_chip *nand) { u_int32_t cfg; u_int8_t tacls, twrph0, twrph1; struct s3c24x0_clock_power *clk_power = s3c24x0_get_base_clock_power(); struct s3c2410_nand *nand_reg = s3c2410_get_base_nand(); debug("board_nand_init()\n"); writel(readl(&clk_power->clkcon) | (1 << 4), &clk_power->clkcon); /* initialize hardware */ #if defined(CONFIG_S3C24XX_CUSTOM_NAND_TIMING) tacls = CONFIG_S3C24XX_TACLS; twrph0 = CONFIG_S3C24XX_TWRPH0; twrph1 = CONFIG_S3C24XX_TWRPH1; #else tacls = 4; twrph0 = 8; twrph1 = 8; #endif cfg = S3C2410_NFCONF_EN; cfg |= S3C2410_NFCONF_TACLS(tacls - 1); cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1); cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1); writel(cfg, &nand_reg->nfconf); /* initialize nand_chip data structure */ nand->IO_ADDR_R = (void *)&nand_reg->nfdata; nand->IO_ADDR_W = (void *)&nand_reg->nfdata; nand->select_chip = NULL; /* read_buf and write_buf are default */ /* read_byte and write_byte are default */ #ifdef CONFIG_NAND_SPL nand->read_buf = nand_read_buf; #endif /* hwcontrol always must be implemented */ nand->cmd_ctrl = s3c2410_hwcontrol; nand->dev_ready = s3c2410_dev_ready; #ifdef CONFIG_S3C2410_NAND_HWECC nand->ecc.hwctl = s3c2410_nand_enable_hwecc; nand->ecc.calculate = s3c2410_nand_calculate_ecc; nand->ecc.correct = s3c2410_nand_correct_data; nand->ecc.mode = NAND_ECC_HW; nand->ecc.size = CONFIG_SYS_NAND_ECCSIZE; nand->ecc.bytes = CONFIG_SYS_NAND_ECCBYTES; #else nand->ecc.mode = NAND_ECC_SOFT; #endif #ifdef CONFIG_S3C2410_NAND_BBT nand->options = NAND_USE_FLASH_BBT; #else nand->options = 0; #endif debug("end of nand_init\n"); return 0; }
将上面代码的第6行:
struct s3c2440_nand *nand_reg = s3c2440_get_base_nand();
改为:
struct s3c2440_nand *nand_reg = s3c2440_get_base_nand();
然后往下看代码,看到设置时序的时候,与2440手册对比发现,发现时序设置不对,它是适用于2410的,并不适用于2440,将以下代码:
cfg = S3C2410_NFCONF_EN;
cfg |= S3C2410_NFCONF_TACLS(tacls - 1);
cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);
cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);
writel(cfg, &nand_reg->nfconf);
改为:
/* 初始化时序 */
cfg = ((tacls-1)<<12)|((twrph0-1)<<8)|((twrph1-1)<<4);
writel(cfg, &nand_reg->nfconf);
去掉ECC的代码,因为这一部分太复杂,但是对我们的程序又没有影响太大,所以删掉下面的代码:
#ifdef CONFIG_S3C2410_NAND_HWECC void s3c2410_nand_enable_hwecc(struct mtd_info *mtd, int mode) { struct s3c2410_nand *nand = s3c2410_get_base_nand(); debug("s3c2410_nand_enable_hwecc(%p, %d)\n", mtd, mode); writel(readl(&nand->nfconf) | S3C2410_NFCONF_INITECC, &nand->nfconf); } static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) { struct s3c2410_nand *nand = s3c2410_get_base_nand(); ecc_code[0] = readb(&nand->nfecc); ecc_code[1] = readb(&nand->nfecc + 1); ecc_code[2] = readb(&nand->nfecc + 2); debug("s3c2410_nand_calculate_hwecc(%p,): 0x%02x 0x%02x 0x%02x\n", mtd , ecc_code[0], ecc_code[1], ecc_code[2]); return 0; } static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc) { if (read_ecc[0] == calc_ecc[0] && read_ecc[1] == calc_ecc[1] && read_ecc[2] == calc_ecc[2]) return 0; printf("s3c2410_nand_correct_data: not implemented\n"); return -1; } #endif
然后把s3c2410_hwcontrol, s3c2410_dev_ready, s3c2410_nand, s3c2410_get_base_nand
,替换成s3c2440_hwcontrol, s3c2440_dev_ready, s3c2440_nand, s3c2440_get_base_nand
;
接下来分析s3c2440_hwcontrol
函数,它最后面有这样几行代码:
if (ctrl & NAND_NCE) /*使能片选*/
writel(readl(&nand->nfconf) & ~S3C2410_NFCONF_nFCE,
&nand->nfconf);
else /*取消片选*/
writel(readl(&nand->nfconf) | S3C2410_NFCONF_nFCE,
&nand->nfconf);
上的代码是2410的配置,我们的Jz2440开发板是2440的,需要查看2440手册查看相关寄存器,我们的2440需要配置的是NFCONT寄存器,查看寄存器配置后,将上面的代码修改为:
if (ctrl & NAND_NCE) /*使能片选*/
writel(readl(&nand->nfcont) & ~(1<<1),
&nand->nfcont);
else /*取消片选*/
writel(readl(&nand->nfcont) | (1<<1),
&nand->nfcont);
此外,还需要使能NAND Flash控制器, 初始化ECC, 禁止片选 ,因此在s3c2440_nand.c
中的board_nand_init
函数中加入下代码:
/* 初始化时序 */
cfg = ((tacls-1)<<12)|((twrph0-1)<<8)|((twrph1-1)<<4);
writel(cfg, &nand_reg->nfconf);
/*使能NAND Flash控制器, 初始化ECC, 禁止片选*/ /*在初始化时序后面添加*/
writel((1<<4)|(1<<1)|(1<<0),&nand_reg->nfcont);
同时还需要在board_nand_init
函数里加上nand->select_chip = s3c2440_nand_select;
所以,将
nand->select_chip = NULL;
修改为:
nand->select_chip = s3c2440_nand_select;
然后编写s3c2440_nand_select
函数:
static void s3c2440_nand_select(struct mtd_info *mtd, int chipnr) { struct s3c2440_nand *nand = s3c2440_get_base_nand(); switch (chipnr) { case -1: /* 取消选中 */ nand->nfcont |= (1<<1); break; case 0: /* 选中 */ nand->nfcont &= ~(1<<1); break; default: BUG(); } }
通过分析知道,需要修改s3c2440_hwcontrol函数重新修改为如下函数:
static void s3c2440_hwcontrol(struct mtd_info *mtd, int dat, unsigned int ctrl) { struct nand_chip *chip = mtd->priv; struct s3c2440_nand *nand = s3c2440_get_base_nand(); if (ctrl & NAND_CLE) { /* 发命令 */ writeb(dat, &nand->nfcmd); } if (ctrl & NAND_ALE) { /* 发地址 */ writeb(dat, &nand->nfaddr); } }
重新编译,把uboot烧写到NOR Flash并启动,串口打印信息如下:
从上图打印信息可知,已经识别出NAND: 256 MiB
,说明我们的uboot已经支持NAND Flash。
下面从NAND Flash启动看看能否识别,首先我们从NOR启动的uboot直接把uboot拷贝到NAND,输入以下命令:
nand erase 0 80000
nand write 0 0 80000
然后测试烧写到NAND Flash中的uboot是否正常,输入以下命令:
nand read 30000000 0 80000
cmp.b 0 30000000 80000 //比较拷贝的代码是否全部一样
比较代码的结果如下,从下图可知,代码拷贝完全正确;
然后,设置为NAND Flash启动,并重启开发板,打印结果如下图,从图中可知,成功识别出NAND: 256 MiB
。
3.总结分析过程(涉及修改的三个文件:s3c2440_nand.c,smdk2440.h,Makefile
)
nand_init nand_init_chip board_nand_init 设置nand_chip结构体, 提供底层的操作函数 nand_scan nand_scan_ident nand_set_defaults chip->select_chip = nand_select_chip; chip->cmdfunc = nand_command; chip->read_byte = busw ? nand_read_byte16 : nand_read_byte; nand_get_flash_type chip->select_chip chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); nand_command() // 即可以用来发命令,也可以用来发列地址(页内地址)、行地址(哪一页) chip->cmd_ctrl s3c2440_hwcontrol chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); *maf_id = chip->read_byte(mtd); *dev_id = chip->read_byte(mtd);
现在的开发板大多使用DM9000网卡了,我们的uboot源码里面也是有DM9000网卡的驱动程序,在drivers/net/目录下,文件名是dm9000x.c
。
1.去drivers/net/目录下的Makefile搜索dm9000字符串,搜索结果如下:
从图中可知,定义了CONFIG_DRIVER_DM9000
,dm9000.c才会被编译进uboot里(同时去掉CONFIG_CS8900
)
2.去到uboot的配置文件smdk2440.h,找到下面的宏定义:
/*
* Hardware drivers
*/
#define CONFIG_CS8900 /* we have a CS8900 on-board */
#define CONFIG_CS8900_BASE 0x19000300
#define CONFIG_CS8900_BUS16 /* the Linux driver does accesses as shorts */
这是cs8900网卡的定义,把它去掉,加上dm9000所需要的宏:
#ifdef 0
#define CONFIG_CS8900 /* we have a CS8900 on-board */
#define CONFIG_CS8900_BASE 0x19000300
#define CONFIG_CS8900_BUS16 /* the Linux driver does accesses as shorts */
#else
#define CONFIG_DRIVER_DM9000
#endif
重新编译uboot,发现如下错误:
上图的第二行dm9000x.c:156: error: 'DM9000_DATA' undeclared (first use in this function)
显示dm9000x.c
没有定义DM9000_DATA
这个宏参数。在经验不足,对驱动程序了解不多的情况下,我们没有办法知道这个宏参数是干什么的,但是我们可以仿照uboot中现有的程序中相同的地方模仿。我们在uboot源码中搜索DM9000_DATA这个字符串:
grep "DM9000_DATA" * -nR
搜索结果如下:
我们随便找一个配置文件进去看看别人是怎么配置的,假如找的是这个吧:
include/configs/at91sam9261ek.h:159:#define DM9000_DATA (CONFIG_DM9000_BASE + 4)
进入该文件,DM9000网卡的配置是这样的:
#define CONFIG_DRIVER_DM9000
#define CONFIG_DM9000_BASE 0x30000000
#define DM9000_IO CONFIG_DM9000_BASE
#define DM9000_DATA (CONFIG_DM9000_BASE + 4)
原来是我们少定义了一些参数。那么我们来看看如何取值CONFIG_DM9000_BASE 与DM9000_DATA。
3.DM9000属于内存类接口,操作内存类接口需要知道它的访问地址,同时还需要根据DM9000网卡的实际情况设置内存控制器的时序和位宽:
(1) 设置访问地址:
首先是 CONFIG_DM9000_BASE ,这个是DM9000的基地址,是CPU用来识别网卡的一个基地址。 查看DM9000网卡原理图:它的片选引脚是:nGCS4,那么我们查看2440手册,因为网卡是内存类的接口,所以我们看2440手册内存控制器那一章,找到片选信号的地址分配表:
从上图可以看出:nGCS4片选管脚对应的基地址是0x20000000
,所以CONFIG_DM9000_BASE
的值是0x20000000
,内存控制器把nGCS4管脚设为低电平,从而选中DM9000网卡。然后是 DM9000_DATA的地址取值。查看原理图知,cpu只发出了LADDR2给DM9000网卡内的CMD引脚, 说明CPU只关心访问地址的第2位,所以我们把基地址加4,因此#define DM9000_DATA (CONFIG_DM9000_BASE + 4)
。
关于DM9000网卡总体的配置如下:
#define CONFIG_DRIVER_DM9000
#define CONFIG_DM9000_BASE 0x20000000
#define DM9000_IO CONFIG_DM9000_BASE
#define DM9000_DATA (CONFIG_DM9000_BASE + 4)
(2) 设置内存控制器的时序以及位宽:
uboot中lowlevel_init.S
中有设置内存控制器寄存器的相关的代码:
SMRDATA:
.long 0x22011110 //BWSCON
.long 0x00000700 //BANKCON0
.long 0x00000700 //BANKCON1
.long 0x00000700 //BANKCON2
.long 0x00000700 //BANKCON3
.long 0x00000700 //BANKCON4
.long 0x00000700 //BANKCON5
.long 0x00018005 //BANKCON6
.long 0x00018005 //BANKCON7
.long 0x008C04F4 // REFRESH
.long 0x000000B1 //BANKSIZE
.long 0x00000030 //MRSRB6
.long 0x00000030 //MRSRB7
.long 0x22011110 //BWSCON
这个寄存器应该是设置的位宽,.long 0x00000700 //BANKCON4(nGCS4)
这个应该是设置时序的寄存器。
① 在s3c2440手册中找到BWSCON
这个寄存器,关于nGCS4对应的BANK4的设置如下图的bit[19:16]
:
从上图可知,设置位宽的相关位是DW4[17:16]
。
bit[17:16]
位对应的是01,BANK4的位宽是16bit
,查看原理图得知,DM9000网卡的位宽刚好是16bit
,所以,不需要修改了。bit[18]
的等待信号DM9000上没有,不需要设置。bit[19]
也没有用到,不需要设置。
② 设置时序(设置BANKCON4寄存器):
对比s3c2440与DM9000的时序图可知,
a. 地址和片选信号可以同时发出:Tacs = 0
;
b. 发出片选信号之后多长时间发读写信号:写:Tcos = T1 = 0ns
;
c. 读写信号需要维持的时间(读写脉冲):Tacc = T2 = 10ns
(Tacp DM9000没有,不需要设置);
d. 读写信号取消之后,片选信号还需要维持多长时间:写:Tcoh = T4 = 3ns
,读:Tcoh = T4 = 6ns
;
e. 片选信号取消之后,地址信号还需要维持多长时间:Tcah = T5 =0ns
;
所以,BANKCON4的设置如下图,BANKCON4 = 0x00000040
将BANKCON4 修改为0x00000040,然后从新编译uboot,编译顺利通过,把uboot烧写到NOR Flash并重新启动,打印信息如下:
上图显示Net: No ethernet found
,那么我们去源码中搜索它在哪个地方,在源码中搜索Net:
,在board.c
中有:
#if defined(CONFIG_CMD_NET)
puts("Net: ");
eth_initialize(gd->bd);
跳转到eth_initialize
,里面有一个函数board_eth_init
:
int board_eth_init(bd_t *bis)
{
int rc = 0;
#ifdef CONFIG_CS8900
rc = cs8900_initialize(0, CONFIG_CS8900_BASE);
#endif
return rc;
}
发现没有初始化DM9000网卡。我们在DM9000网卡驱动程序中找到:dm9000_initialize
,应该是DM9000的初始化函数,在uboot的顶层目录搜索:grep "dm9000_initialize" * -nR
,看看有没有类似的初始化,发现多是这样:
return dm9000_initialize(bis);
所以我们可以把board_eth_init
函数改成:(在board/samsung/smdk2440/smdk2410.c
的136行)
int board_eth_init(bd_t *bis)
{
int rc = 0;
#ifdef CONFIG_CS8900
rc = cs8900_initialize(0, CONFIG_CS8900_BASE);
#endif
#ifdef CONFIG_DRIVER_DM9000
rc = dm9000_initialize(bis);
#endif
return rc;
}
重新编译,启动开发板:
上图显示:Net: dm9000
,说明已经成功识别DM9000网卡。
(3) ping 电脑测试:
① 设置开发板的IP地址:
set ipaddr 192.168.0.200
② ping测试(ubuntu虚拟机IP是192.168.0.103)
ping 192.168.0.103
结果如下:
重复ping了很多次,仍然显示:ping failed
,查找原因突然想起BANKCON4的bit[10:8]=000,这个时间设置的太临界了(Tacc = T2 = 10ns
),那么干脆把它设置为最大bit[10:8]=111;(注:如果bit[10:8]=001,虽然能解决ping failed
的问题,但在后面使用tftp 30000000 uImage_4.3
命令下载的内核,bootm 30000000
启动内核时有CRC校验错误,导致内核启动失败,原因应该是下载内核时数据传输出了错误,后面把bit[10:8]改为111,完美解决内核启动出现CRC检验错误的问题。)
所以,将BANKCON4 修改为0x00000740。
重新编译uboot,烧写到NOR Flash,启动开发板,重复①②步骤,打印结果如下:
上图显示:
*** ERROR: `ethaddr' not set
说明没有设置MAC地址。
③ 设置MAC 地址
set ethaddr 00:0c:29:4d:e4:f4 //MAC可以随便设置
然后重新ping,打印结果如下:(注:ping一次可能不成功,需要多ping几次)
上图显示:host 192.168.0.103 is alive
,说明已经ping成功。
④ 下面我们试一下看看能不能用tftp下载。因为uboot没有集成TFTP工具,那么我们就用windows上的tftp工具进行下载内核:
由上图可知服务器的IP地址是:192.168.0.106
所以需要在开发板设置服务器IP地址:
set serverip 192.168.0.106
然后使用fttp命令从window下载内核:
tftp 30000000 uImage_4.3
⑤ 启动内核:
bootm 30000000
打印信息如下,从图中可知,内核启动成功,同时也说明uboot支持了DM9000网卡。
这一节的目标:裁剪U-BOOT,使其更加易用,修改默认参数,以及制作最终修改好得补丁文件方便以后的快速移植。
在裁剪修改之前呢,我们先来了解一下uboot的环境参数(环境变量):uboot在启动的时候首先会读取环境参数,然后判断环境参数是否有效,如果设置的环境参数是无效的,那么就使用默认的参数。
Linux系统在硬盘中(FLASH)的分区如下图所示:
上图显示:uboot放到第一个分区,环境参数放到第二个分区,内核放到第三个分区,文件系统放到第四个分区。
之前我们修改的U-BOOT启动后,一直有一个警告,如下图所示:
上图显示:CRC的参数错误,使用默认的环境变量。那么我们就从这个问题引入,在uboot中搜索:using default environment
,在common/env_common.c
中可找到:
const uchar default_environment[] = { #ifdef CONFIG_BOOTARGS "bootargs=" CONFIG_BOOTARGS "\0" #endif #ifdef CONFIG_BOOTCOMMAND "bootcmd=" CONFIG_BOOTCOMMAND "\0" #endif #ifdef CONFIG_RAMBOOTCOMMAND "ramboot=" CONFIG_RAMBOOTCOMMAND "\0" #endif #ifdef CONFIG_NFSBOOTCOMMAND "nfsboot=" CONFIG_NFSBOOTCOMMAND "\0" #endif #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) "bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0" #endif #if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0) "baudrate=" MK_STR(CONFIG_BAUDRATE) "\0" #endif #ifdef CONFIG_LOADS_ECHO "loads_echo=" MK_STR(CONFIG_LOADS_ECHO) "\0" #endif #ifdef CONFIG_ETHADDR "ethaddr=" MK_STR(CONFIG_ETHADDR) "\0" #endif #ifdef CONFIG_ETH1ADDR "eth1addr=" MK_STR(CONFIG_ETH1ADDR) "\0" #endif #ifdef CONFIG_ETH2ADDR "eth2addr=" MK_STR(CONFIG_ETH2ADDR) "\0" #endif #ifdef CONFIG_ETH3ADDR "eth3addr=" MK_STR(CONFIG_ETH3ADDR) "\0" #endif #ifdef CONFIG_ETH4ADDR "eth4addr=" MK_STR(CONFIG_ETH4ADDR) "\0" #endif #ifdef CONFIG_ETH5ADDR "eth5addr=" MK_STR(CONFIG_ETH5ADDR) "\0" #endif #ifdef CONFIG_IPADDR "ipaddr=" MK_STR(CONFIG_IPADDR) "\0" #endif #ifdef CONFIG_SERVERIP "serverip=" MK_STR(CONFIG_SERVERIP) "\0" #endif #ifdef CONFIG_SYS_AUTOLOAD "autoload=" CONFIG_SYS_AUTOLOAD "\0" #endif #ifdef CONFIG_PREBOOT "preboot=" CONFIG_PREBOOT "\0" #endif #ifdef CONFIG_ROOTPATH "rootpath=" CONFIG_ROOTPATH "\0" #endif #ifdef CONFIG_GATEWAYIP "gatewayip=" MK_STR(CONFIG_GATEWAYIP) "\0" #endif #ifdef CONFIG_NETMASK "netmask=" MK_STR(CONFIG_NETMASK) "\0" #endif #ifdef CONFIG_HOSTNAME "hostname=" MK_STR(CONFIG_HOSTNAME) "\0" #endif #ifdef CONFIG_BOOTFILE "bootfile=" CONFIG_BOOTFILE "\0" #endif #ifdef CONFIG_LOADADDR "loadaddr=" MK_STR(CONFIG_LOADADDR) "\0" #endif #ifdef CONFIG_CLOCKS_IN_MHZ "clocks_in_mhz=1\0" #endif #if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0) "pcidelay=" MK_STR(CONFIG_PCI_BOOTDELAY) "\0" #endif #ifdef CONFIG_EXTRA_ENV_SETTINGS CONFIG_EXTRA_ENV_SETTINGS #endif "\0" };
bootargs
是传给内核的启动参数,可以设置文件系统的相关分区等。我们在配置文件smdk2440.h中定义一下:
#define CONFIG_BOOTARGS "console=ttySAC0 root=dev/mtdblock3" //代表内核从串口0启动,文件系统放到第3个分区
bootcmd
是uboot用来启动内核的参数,我们也在配置文件中定义(bootargs定义的下面)定义一下:
#define CONFIG_BOOTCOMMAND "nand read 30000000 0xbac 0x200000;bootm 30000000"
//因为现在还无得知内核的分区的具体地址,先随便设置一个值,用于演示,等我们把整个FLASH分区规划好了之后,再来设置
bootdelay
是uboot启动后的那个倒数计时的参数,当uboot启动后,进入倒计时启动,我们按下任意键,进入uboot交互界面开始进行一些设置等操作。这里我们就去默认值。
ipaddr
是uboot单板的ip地址,可以定义:
#define CONFIG_IPADDR 192.168.0.200
ethaddr
表示网卡的MAC地址,可以定义:
#define CONFIG_ETHADDR 00:0c:29:4d:e4:f4
设置好我们常用的参数后,我们先来裁剪一下uboot。我们在uboot命令行输入help,发现有各种命令,有些命令根本不需要,那么我们就需要把相关的宏定义给去掉。这里我们可以把去掉uboot对usb、文件系统的支持。(因为太分散了,就不做记录了)
配置好后,如果直接make
编译,可能会出现错误,解决办法:
make distclean
make smdk2440_config
make
然后,我们对uboot进行分区,在分区之前我们来看看之前是怎分区的:
0x00000000-0x00040000 : “bootloader” (0~256k)
0x00040000-0x00060000 : “params”
0x00060000-0x00260000 : “kernel”
0x00260000-0x10000000 : “root”
我们先来设置一下我们的参数的存放地址吧,因为之前我们设置好参数之后,每次都不敢save,就是怕破坏FLASH;
在smdk2440.h配置文件中,找到如下环境变量的相关定义:
#define CONFIG_ENV_ADDR (CONFIG_SYS_FLASH_BASE + 0x070000)
#define CONFIG_ENV_IS_IN_FLASH
#define CONFIG_ENV_SIZE 0x10000
/* allow to overwrite serial and ethaddr */
#define CONFIG_ENV_OVERWRITE
那么我们重新定义这些宏,该怎么定义呢?
在uboot命令行使用help命令查看save相关的信息:
然后我们在uboot源码中搜索字符串:saveenv
找到了类似这样的语句:common/env_nand.c:174:int saveenv(void)
我们去common目录下看看Makefile,看看env_nand.c
的编译依赖哪个宏?找到了下面这句:COBJS-$(CONFIG_ENV_IS_IN_NAND) += env_nand.o
,说明编译env_nand.c
依赖的是CONFIG_ENV_IS_IN_NAND
这个宏的定义。那么我们就定义这个宏CONFIG_ENV_IS_IN_NAND
:
所以我们将上面环境变量的宏重新定义如下:
#define CONFIG_ENV_IS_IN_NAND
#define CONFIG_ENV_OFFSET 0x00040000
#define CONFIG_ENV_SIZE 0x20000
#define CONFIG_ENV_RANGE CONFIG_ENV_SIZE
然后重新编译,没有错误。
先设置好ip等,使用tftp命令下载uboot:
tftp 30000000 u-boot.bin
protect off all
erase 0 3ffff
cp.b 30000000 0 40000
重启开发板:
由启动知,已经有了倒数的命令,以及我们设置的ip等,都直接就存在了。
但是还是有呢个关于CRC的警告信息,那是因为,那个CRC的变量没有写到FLSAH中,我们执行save命令,再重启:
此时,就没有那个警告信息了,说明参数已经成功被写进flash,说明上面修改的的参数保存的地址的区域,也设置成功了。
烧写内核:
tftp 30000000 uImage_4.3
nand erase 60000 200000
nand write 30000000 60000 200000
从这里,就可以感觉到,烧写个程序要这么麻烦。下面我们就来修改代码,让烧写程序变得简单:
在配置文件中定义宏:CONFIG_CMD_MTDPARTS
:
#define CONFIG_CMD_MTDPARTS
#define MTDIDS_DEFAULT "nand0=jz2440-0" /* 表示哪一个设备 */
#define MTDPARTS_DEFAULT "mtdparts=jz2440-0:256k(u-boot)," \
"128k(params)," \
"2m(kernel)," \
"-(rootfs)" \
在board.c中654行添加:
run_command("mtdparts default",0);
重新编译,出现错误:
通过查找,发现mtdcore.c
中(在drivers/mtd目录下)定义了get_mtd_device_nm
这个函数,应该是没有被编译进内核,所以才显示错误,去Makefile中查看,发现,还需要定义这个宏:CONFIG_MTD_DEVICE
,那么我们在配置文件中再定义这个宏:
#define CONFIG_CMD_MTDPARTS
#define CONFIG_MTD_DEVICE
#define MTDIDS_DEFAULT "nand0=jz2440-0" /* 表示哪一个设备 */
#define MTDPARTS_DEFAULT "mtdparts=jz2440-0:256k(u-boot)," \
"128k(params)," \
"2m(kernel)," \
"-(rootfs)" \
再重新编译,没有错误。烧写uboot,重启,在uboot命令行输入:mtdparts
,显示如下:
上图可知,uboot支持了mtdparts
命令,之前启动时出现的:Unknown command 'mtdparts' - try 'help'
也消失了。(之前一直出现这提示,是因为在board.c
中添加了run_command("mtdparts default",0);
;为什么会添加了呢?因为board.c是直接从之前移植好的uboot直接复制过来的)
再来烧写内核时,就可以这样烧写内核了:
tftp 30000000 uImage_4.3
nand erase.part kernel
nand write 30000000 kernel
然后我们再回过头把配置文件中的这句话:
#define CONFIG_BOOTCOMMAND "nand read 30000000 0xbac 0x200000;bootm 30000000"
修改为:
#define CONFIG_BOOTCOMMAND "nand read 30000000 kernel 0x200000;bootm 30000000"
重新编译,烧写uboot,烧写内核。重启,发现可以直接启动内核了,如下图所示:
前面我们通过修改uboot的代码让它支持串口、nand flash,nor flash,网络下载文件等功能,现在我们来实现最后一个功能,实现Uboot烧写YAFFS文件系统,同时制作uboot补丁方便以后的移植,避免重复造轮子。
关于JFFS和YAFFS文件系统的区别可以参考这篇文章:JFFS和YAFFS文件系统
该uboot已经支持JFFS2文件系统的烧写了,我们先来烧写JFFS2文件系统:
tftp 30000000 fs_mini_mdev.jffs2
nand erase.part rootfs
nand write.jffs2 30000000 0x00260000 5b89a8
烧写完后,需要先设置文件系统的类型,在uboot中输入命令:
set bootargs console=ttySAC0 root=/dev/mtdblock3 rootfstype=jffs2
然后输入命令启动linux:
boot
启动后,打印的信息如下:
从上图串口的输出信息可知,linux成功挂载了文件系统。
那么我们的uboot支持烧写YAFFS文件系统吗?
可以验证一下:
reboot
tftp 30000000 fs_mini_mdev.yaffs2
nand erase.part rootfs
nand write.yaffs 30000000 260000 889bc0
打印信息如下:
上图显示:Unknown nand command suffix '.yaffs'.
,说明此时的uboot
还没有支持yaffs文件系统是烧写。
在uboot源码目录中搜索:Unknown nand command suffix
,在common/cmd_nand.c
的608行有:
#ifdef CONFIG_CMD_NAND_YAFFS
} else if (!strcmp(s, ".yaffs")) {
if (read) {
printf("Unknown nand command suffix '%s'.\n", s);
return 1;
}
ret = nand_write_skip_bad(nand, off, &rwsize,
(u_char *)addr, WITH_YAFFS_OOB);
#endif
从上面的代码可知,如果需要支持YAFFS文件系统,必须定义宏:CONFIG_CMD_NAND_YAFFS
,所以需要在配置文件smdk2440.h
添加宏定义:
#define CONFIG_CMD_NAND_YAFFS
重新编译uboot,输入以下命令烧写uboot:
tftp 30000000 u-boot.bin; protect off all; erase 0 3ffff; cp.b 30000000 0 40000
(注:上面每条指令用英文的分号隔开)
然后输入命令重启开发板:reset
然后重新烧写YAFFS文件系统:
tftp 30000000 fs_mini_mdev.yaffs2
nand erase.part rootfs
nand write.yaffs 30000000 260000 889bc0
从下图的打印信息可知,现在的uboot已经支持了yaffs文件系统的烧写。
启动之前把之前的设置:
set bootargs console=ttySAC0 root=/dev/mtdblock3 rootfstype=jffs2
改为:
set bootargs console=ttySAC0 root=/dev/mtdblock3 rootfstype=yaffs2
然后输入命令:boot
,启动linux,最后的打印信息如下:
很显然,没有完全挂载上。
yaffs文件系统包含两部分数据,页数据,和OOB数据。我们现在来对比一下我们少烧写的yaffs文件系统的数据与原有的数据的差别:
nand dump 260000
显示的就是yaffs文件系统的的16进制数据。通过对比,发现OOB数据区的数据不一样。
在drivers/mtd/nand/nand_util.c
中的nand_write_skip_bad
函数中有:(在drivers/mtd/nand/nand_util.c
的567行)
if (!rval)
break;
这个地方写错了,导致只写了一页的OOB数据,应该是:
if (rval)
break;
还有一个地方需要修改,在drivers/mtd/nand/nand_util.c
的518行有:
if (!need_skip && !(flags & WITH_DROP_FFS)) {
改为:
if (!need_skip && !(flags & WITH_DROP_FFS) && !(flags & WITH_YAFFS_OOB)) {
否则:如果分区中没有坏块(need_skip=0)时, 将执行这个分支, 导致oob区烧写不正确。
同时,还需要把drivers/mtd/nand/nand_util.c
的556行的:
ops.mode = MTD_OOB_AUTO;
改为:
ops.mode = MTD_OOB_RAW; //烧写时使用原本的OOB
重新编译uboot
,更新uboot,重新烧写文件系统:
① 更新uboot:
tftp 30000000 u-boot.bin; protect off all; erase 0 3ffff; cp.b 30000000 0 40000
② 重启开发板,重新烧写文件系统:
tftp 30000000 fs_mini_mdev.yaffs2;nand erase.part rootfs;nand write.yaffs 30000000 260000 889bc0
在uboot中输出命令:boot
,启动linux,从下图的打印信息可知,linux启动成功,uboot支持了yaffs文件系统的烧写。
最后,制作uboot补丁:
在uboot目录下,分别输入以下命令:
make distclean
rm u-boot.dis
cd ..
mv u-boot-2012.04.01 u-boot-2012.04.01_100ask
tar jxf u-boot-2012.04.01.tar.bz2 (解压未经修改的uboot源文件)
diff -urN u-boot-2012.04.01 u-boot-2012.04.01_100ask > u-boot-2012.04.01_100ask.patch (生成补丁)
补丁文件生成后,以后换电脑或者换系统之类的,就可以直接给uboot打补丁了:
cd u-boot-2012.04.01
patch -p1 < …/u-boot-2012.04.01_100ask.patch
至此,uboot移植已完成。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。