Mini440之uboot移植之实践NAND启动(四)
在之前的章节我们已经介绍了u-boot如何支持我们的NOR FLASH,以及DM9000网卡的支持。
这一节我们将会在Young / s3c2440_project[u-boot-2016.05-nor-flash】代码的基础上新建u-boot-2016.05-nand-flash项目,使得我们项目支持NAND,由于u-boot默认采用的NOR启动,这里我们需要修改u-boot实现NAND启动。
如果想支持NAND启动,我们需要考虑以下问题,S3C2440在NAND启动时S3C2440的NAND 控制器会自动把NAND FLASH中的前4K代码数据搬到内部SRAM中(在S3C2440片内有一块被称为SteppingStone的片内SRAM,它的大小为4K),片内SRAM被映射到nGCS0片选的空间(nGCS0片选的空间,即0x00000000),CPU从0x00000000位置开始运行程序。
然而我们的uboot程序是远远大于4kb的,因此程序大于4kb的时候需要从 NAND FLASH中拷贝到 SDRAM中去运行。自然可以想到烧到 NAND FLASH中的程序前面一部分代码应该是初始化SDRAM(程序最终需要拷贝到SDRAM中去运行)和 将NAND FLASH中的剩余的程序拷贝到SDRAM中去(全考过去也行,方便点),然后跳转到SDRAM中执行。
既然我们已经有了思路,那么接下来就是如何去实现。
一、NAND支持
在分析Mini440之uboot移植之源码分析start.S(一)中我们介绍到CPU上点首先执行的是start.S文件,在内核级配置和初始化过程中会调用cpu_init_crit函数。
cpu_init_crit: /* * flush v4 I/D caches [1]. 清除cache */ mov r0, #0 mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ /* * disable MMU stuff and caches [2]. 禁止MMU */ mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) orr r0, r0, #0x00000002 @ set bit 1 (A) Align orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache mcr p15, 0, r0, c1, c0, 0/* * before relocating, we have to setup RAM timing * because memory timing is board-dependend, you will * find a lowlevel_init.S in your board directory. */ mov ip, lr bl lowlevel_init //[3]. 进入board/samsung/smdk2440/lowlevel_init.S执行, // 设置内存控制寄存器时序参数 mov lr, ip mov pc, lr
我们可以在cpu_init_crit函数执行完之后进行适当扩充,添加以下功能:
- NAND初始化;
- 代码重定位,将uboot拷贝到SDRAM,并进行跳转运行;
1.1 新建init.c文件
新建init.c文件并将文件放在board/samsung/smdk2440路径下,并修改当前路径下的Makefile:
obj-y := smdk2440.o init.o
obj-y += lowlevel_init.o
init.c代码如下:
/************************************************************************** * * FileName : init.c * Function : 一些初始化相关的函数 * Author : zy * *************************************************************************/ #include/************************************************************************** * * Function : nand flash读写相关函数 * *************************************************************************/ /* 等待NAND Flash就绪 */ void _nand_wait_idle(void) { int i; while(!(NFSTAT & BUSY)); for(i=0; i<10; i++); } /* 发出片选信号 */ void _nand_select_chip(void) { int i; NFCONT &= ~(1<<1); /* set CE=0 */ for(i=0; i<10; i++); } /* 取消片选信号 */ void _nand_deselect_chip(void) { NFCONT |= (1<<1); /* set CE=1 */ } /* 发出命令 */ void _nand_write_cmd(unsigned char cmd) { int i; NFCMMD = cmd; for(i=0; i<10; i++); } /* 发出地址 */ void _nand_write_addr_byte(unsigned char addr) { volatile int i; NFADDR = addr; for(i=0; i<10; i++); } /* 发出地址,5个周期来发送,前2个周期为col地址,后三个周期为row(page)地址 */ void _nand_write_addr(unsigned int addr) { int col, page; col = addr & NAND_PAGE_MASK_SIZE; page = addr / NAND_PAGE_SIZE; /* 块号 */ _nand_write_addr_byte(col & 0xff); /* Column Address A0~A7 */ _nand_write_addr_byte((col >> 8) & 0x0f); /* Column Address A8~A11 */ _nand_write_addr_byte(page & 0xff); /* Row Address A12~A19 */ _nand_write_addr_byte((page >> 8) & 0xff); /* Row Address A20~A27 */ _nand_write_addr_byte((page >> 16) & 0x01); /* Row Address A28 */ } /* 复位 */ void _nand_reset(void) { _nand_select_chip(); _nand_write_cmd(0xff); // 复位命令 _nand_wait_idle(); _nand_deselect_chip(); } /* 读取数据 */ unsigned char _nand_read_data(void) { return NFDATA; } /* 写入数据 */ void _nand_write_data(unsigned char data) { int i; NFDATA = data; for(i=0; i<10; i++); } /* 初始化and Flash */ void nand_init_ll(void) { #define TACLS 0 #define TWRPH0 1 #define TWRPH1 0 /* 设置时序 */ NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4); /* 使能NAND Flash控制器, 初始化ECC, 禁止片选, 使能NAND控制器 */ NFCONT = (1<<4)|(1<<1)|(1<<0); /* 复位NAND Flash */ _nand_reset(); } /* 发送读取数据命令,将nand start_addr处数据读取到buf中,读取长度为size, start_addr参数必须按页对齐 success:0 fail:-1 */ int nand_read_data(unsigned char *buf, unsigned int start_addr, unsigned int size) { int i = 0; int j = 0; if (start_addr & NAND_PAGE_MASK_SIZE ) { return -1; /* 地址或长度不对齐 */ } /* 选中芯片 */ _nand_select_chip(); /* 按页读取 */ while (i < size) { /* 发出READ命令 */ _nand_write_cmd(0); /* write address */ _nand_write_addr(start_addr+i); /* write cmd */ _nand_write_cmd(0x30); /* wait */ _nand_wait_idle(); /* 读取一页数据 */ for(; (j < NAND_PAGE_SIZE) && (i < size); j++) { buf[i++] = _nand_read_data(); } j = 0; } /* 取消片选信号 */ _nand_deselect_chip(); return 0; } /************************************************************************** * * Function : 自动区分是nand启动还是nor启动 * NAND启动,此时内部4k SRAM映射到0x00处,才可以访问该内存 * NOR启动,此时内部2M NOR FLASH映射到地址0x00处,此时无法写入内存,片内SRAM映射到0x40000000地址处 * *************************************************************************/ int is_boot_from_nor_flash(void) { volatile unsigned int *p = (volatile unsigned int *)0x00; unsigned int val = *p; /* get value from address 0x00 */ *p = 0x12345678; /* write value to address 0x00 */ /* 写成功, 对应nand启动 */ if(*p == 0x12345678){ *p = val; return 0; } return 1; } /************************************************************************** * * Function : 清除bss段 * *************************************************************************/ void clean_bss(void) { /* 要从lds文件中获得 __bss_start, __bss_end */ extern int __bss_end, __bss_start; int *p = &__bss_start; for (; p < &__bss_end; p++) *p = 0; } /************************************************************************** * * Function : 将代码从nand/nor falsh复制到sdram * *************************************************************************/ void copy_code_to_sdram(void) { /* 要从lds文件中获得 __image_copy_start, __image_copy_end 然后从0地址把数据复制到__image_copy_start */ extern int __image_copy_start, __image_copy_end; volatile unsigned int *dest = (volatile unsigned int *)&__image_copy_start; volatile unsigned int *end = (volatile unsigned int *)&__image_copy_end; volatile unsigned int *src = (volatile unsigned int *)0; unsigned int len = (unsigned int)(&__image_copy_end) - (unsigned int)(&__image_copy_start); /* nor falsh boot: nor flash address 0x00 */ if (is_boot_from_nor_flash()) { /* 把nor flash的内容全部copy到sdram */ while (dest < end) { *dest++ = *src++; } } else { // 将nand flash内容复制到SDRAM nand_read_data(dest, (unsigned int)src, len); } }
在include目录下新建init.h头文件,代码如下:
#ifndef __S3C2440_INIT__ #define __S3C2440_INIT__ #define GSTATUS1 (*(volatile unsigned long *)0x560000B0) #define BUSY 1 /* page size 2048byte */ #define NAND_PAGE_SIZE 2048 #define NAND_PAGE_MASK_SIZE (NAND_PAGE_SIZE - 1) #define NAND_BLOCK_SIZE (64*NAND_PAGE_SIZE) #define NAND_BLOCK_MASK_SIZE (NAND_BLOCK_SIZE-1) /* NAND FLASH (see S3C2440 manual chapter 6, www.100ask.net) */ #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 NFMECCD0 (*(volatile unsigned long *)0x4e000014) #define NFMECCD (*(volatile unsigned long *)0x4e000018) #define NFSECCD (*(volatile unsigned long *)0x4e00001C) #define NFSTAT (*(volatile unsigned long *)0x4e000020) #define NFESTAT0 (*(volatile unsigned long *)0x4e000024) #define NFESTAT1 (*(volatile unsigned long *)0x4e000028) #define NFMECC0 (*(volatile unsigned long *)0x4e00002C) #define NFMECC1 (*(volatile unsigned long *)0x4e000030) #define NFSECC (*(volatile unsigned long *)0x4e000034) #define NFSBLK (*(volatile unsigned long *)0x4e000038) #define NFEBLK (*(volatile unsigned long *)0x4e00003C) /* 函数声明 */ extern void nand_init_ll(void); extern void copy_code_to_sdram(); extern void clean_bss(void); #endif
修改include/configs/smdk2440.h文件,将CONFIG_SYS_TEXT_BASE宏改为0x33f00000,也就是uboot重定位后的位置, 这里没有设置成0x33f80000,而是留了1M空间供给uboot重定位,主要是因为我们的u-boot可能大于512kb。
具体可以看考Mini440之uboot移植之源码分析u-boot重定位(三):
1.2 start.S修改
lowlevel_init函数执行完之后就是执行bl _main(arch/arm/lib/crt0.S),因此我们可以在cpu_init_crit执行之后,并加入NAND初始化,以及重定位相关代码。修改之后的start.S代码:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) @1. 设置栈 0x30000f50 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ bl nand_init_ll @ 2. nand 初始化 bl copy_code_to_sdram @3.代码重定位 bl bss_init @4. 清除bss段(参考自制uboot章节) ldr pc,=_main @5. 绝对跳转,跳转到SDRAM运行 /* bss段初始化,该段内存清零 */ bss_init: ldr r0,=__bss_start @ 获取.bss段初始地址 在链接文件中定义 ldr r1,=__bss_end @ 获取.bss段的结束地址 在链接文件中定义 cmp r0,r1 moveq pc,lr @ 相等则返回 clear: mov r2,#0x00 str r2,[r0],#0x04 @ [r0] = 0x00 r0=r0+4 cmp r0,r1 bne clear @不相等跳转 mov pc,lr @ 返回
最后我们使用ldr进行了绝对跳转,跳转到SDRAM中运行。
1.3 屏蔽u-boot重定位代码
因为我们已经将代码从NAND FLASH拷贝到了SDRAM中,因此需要屏蔽掉u-boot的重定位、以及BSS清除相关的代码。屏蔽掉_main(arch/arm/lib/crt0.S)中如下代码:
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code
修改reserve_uboot(common/board_f.c):
static int reserve_uboot(void) { /* * reserve memory for U-Boot code, data & bss * round down to next 4 kB limit */ gd->relocaddr = CONFIG_SYS_TEXT_BASE; debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, gd->relocaddr); gd->start_addr_sp = gd->relocaddr; return 0; }
1.4 修改链接脚本
start.S, init.c(实现重定位), lowlevel.S(实现初始化SDRAM)等文件放在内存空间最前面。修改arch/arm/cpu/u-boot.lds部分信息如下:
.text : { *(.__image_copy_start) *(.vectors) CPUDIR/start.o (.text*) board/samsung/smdk2440/built-in.o (.text*) *(.text*) }
build-in.o是将smdk2440单板目录下的所有*.c,*S文件编译后,连接成一个库文件。
1.5、去掉 "-pie"选项
在u-boot源码分析重定位中,我们说过在arm-linux-ld时加了"-pie"选项, 使得u-boot.bin里多了"*(.rel*)", "*(.dynsym)",从而程序非常大,不利于从NAND启动,所以我们修改代码,并取消"-pie"选项。运行:
grep "\-pie" * -nR
我们修改arch/arm/config.mk,屏蔽掉下面代码:
LDFLAGS_u-boot += -pie
此外,我们还需要屏蔽掉顶层Makefile文件中的重定位规则检验的代码,否则编译汇报如下错误:
我们定位到1387行,修改为如下:
checkarmreloc: u-boot @RELOC="`$(CROSS_COMPILE)readelf -r -W $< | cut -d ' ' -f 4 | \ grep R_A | sort -u`"; \ # if test "$$RELOC" != "R_ARM_RELATIVE" -a \ # "$$RELOC" != "R_AARCH64_RELATIVE"; then \ # echo "$< contains unexpected relocations: $$RELOC"; \ # false; \ # fi
修改arch/arm/cpu/u-boot.lds,将如下内容:
.bss_start __rel_dyn_start (OVERLAY) : { KEEP(*(.__bss_start)); __bss_base = .; } .bss __bss_base (OVERLAY) : { *(.bss*) . = ALIGN(4); __bss_limit = .; } .bss_end __bss_limit (OVERLAY) : { KEEP(*(.__bss_end)); }
修改为:
. = ALIGN(4); /* 定义变量,保存.bss段起始地址,可以在汇编代码中之间使用 */ __bss_start = .; /* 全局的未初始化变量存在于.bss段中 */ .bss : { *(.bss*) } __bss_end = .;
为什么修改这个呢,主要是因为源代码配置的bss和rel.dyn使用的同一个段空间,而我们编译时取消了-pie选项,会导致bss段起始地址变成0x00。具体可以参考u-boot中bss段的使用。
二、 编译下载运行
2.1 配置编译
make distclean make smdk2440_config make ARCH=arm CROSS_COMPILE=arm-linux- V=1
2.2 查看内存分布
直接反汇编u-boot文件,可以得到反汇编代码:
arm-linux-objdump -D u-boot > u-boot.dis
查看u-boot.dis,可以看到代码的链接起始地址为0x33f00000:
继续查看u-boot.dis,找到NAND初始化代码:
可以看到NAND相关代码均没有超过4kb,执行完代码重定位后,执行了代码跳转,跳转到SDRAM中运行__main函数:
2.3 下载到NAND FALSH
我们将代码下载到NAND FLASH中,并从NAND启动,串口输出信息如下:
参考文章
[1]S3C2440移植uboot之支持NAND启动
[2]
[3]移植u-boot-2016.11到JZ2440(三:修改源码之实现NOR启动与NAND启动)