📄 nand.txt
字号:
从NAND闪存中启动U-BOOT的设计
引言
随着嵌入式系统的日趋复杂,它对大容量数据存储的需求越来越紧迫。而嵌入式设备低功耗、小体积以及低成本的要求,使硬盘无法得到广泛的应用。NAND闪存设备就是为了满足这种需求而迅速发展起来的。目前关于U-BOOT的移植解决方案主要面向的是微处理器中的NOR 闪存,如果能在微处理器上的NAND 闪存中实现U-BOOT的启动,则会给实际应用带来极大的方便。
U-BOOT简介
U-BOOT 支持ARM、 PowerPC等多种架构的处理器,也支持Linux、NetBSD和VxWorks等多种操作系统,主要用来开发嵌入式系统初始化代码 bootloader。bootloader是芯片复位后进入操作系统之前执行的一段代码,完成由硬件启动到操作系统启动的过渡,为运行操作系统提供基本的运行环境,如初始化CPU、堆栈、初始化存储器系统等,其功能类似于PC机的BIOS。U-BOOT执行流程图如图1所示。
NAND闪存工作原理
S3C2410开发板的 NAND闪存由NAND闪存控制器(集成在S3C2410 CPU中)和NAND闪存芯片(K9F1208U0A)两大部分组成。当要访问NAND闪存芯片中的数据时,必须通过NAND闪存控制器发送命令才能完成。所以, NAND闪存相当于S3C2410的一个外设,而不位于它的内存地址区。
NAND闪存(K9F1208U0A)的数据存储结构分层为:1设备(Device) = 4096 块(Block);1块= 32页/行(Page/row);1页= 528B = 数据块 (512B) + OOB块 (16B)
在每一页中,最后16个字节(又称OOB)在NAND闪存命令执行完毕后设置状态,剩余512个字节又分为前半部分和后半部分。可以通过NAND闪存命令00h/01h/50h分别对前半部、后半部、OOB进行定位,通过NAND闪存内置的指针指向各自的首地址。
NAND 闪存的操作特点为:擦除操作的最小单位是块;NAND闪存芯片每一位只能从1变为0,而不能从0变为1,所以在对其进行写入操作之前一定要将相应块擦除; OOB部分的第6字节为坏快标志,即如果不是坏块该值为FF,否则为坏块;除OOB第6字节外,通常用OOB的前3个字节存放NAND闪存的硬件ECC (校验寄存器)码;
从NAND闪存启动U-BOOT的设计思路
如果S3C2410被配置成从NAND闪存启动,上电后,S3C2410的NAND闪存控制器会自动把NAND闪存中的前4K数据搬移到内部RAM中, 并把0x00000000设置为内部RAM的起始地址, CPU从内部RAM的0x00000000位置开始启动。因此要把最核心的启动程序放在NAND闪存的前4K中。
由于NAND闪存控制器从NAND闪存中搬移到内部RAM的代码是有限的,所以, 在启动代码的前4K里,必须完成S3C2410的核心配置,并把启动代码的剩余部分搬到RAM中运行。在U-BOOT中, 前4K完成的主要工作就是U-BOOT启动的第一个阶段(stage1)。
根据U-BOOT的执行流程图,可知要实现从NAND闪存中启动U-BOOT,首先需要初始化NAND闪存,并从NAND闪存中把U-BOOT搬移到RAM中,最后需要让U-BOOT支持NAND闪存的命令操作。
开发环境
本设计中目标板硬件环境如下:CPU为S3C2410,SDRAM为HY57V561620,NAND闪存为64MB的K9F1208U0A。
主机软件环境为Redhat9.0、 u-boot-1.1.3、gcc 2.95.3。修改U-BOOT的Makefile,加入:
wch2410_config : unconfig
@./mkconfig $(@:_config=) arm arm920t wch2410 NULL s3c24x0
即将开发板起名为wch2410,接下来依次进行如下操作:
mkdir board/wch2410
cp board/smdk2410 board/wch2410
mv smdk2410.c wch2410.c
cp include/configs/smdk2410.h include/configs/wch2410.h
export PATH=/usr/local/arm/2.95.3/bin:$PATH
最后执行:
make wch2410_config
make all ARCH=arm
生成u-boot.bin,即通过了测试编译。
具体设计
支持NAND闪存的启动程序设计
因为U-BOOT的入口程序是/cpu/arm920t/start.S,故需在该程序中添加NAND闪存的复位程序,以及实现从NAND闪存中把U-BOOT搬移到RAM中的功能程序。
首先在/include/configs/wch2410.h中加入CONFIG_S3C2410_NAND_BOOT, 如下:
#define CONFIG_S3C2410_NAND_BOOT 1?? @支持从NAND 闪存中启动
然后在/cpu/arm920t/start.S中添加
#ifdef CONFIG_S3C2410_NAND_BOOT
copy_myself:
mov r10, lr
ldr sp, DW_STACK_START @安装栈的起始地址
mov fp, #0 @初始化帧指针寄存器
bl nand_reset @跳到复位C函数去执行,执行NAND闪存复位
.......
/*从NAND闪存中把U-BOOT拷贝到RAM*/
ldr r0, =UBOOT_RAM_BASE @ 设置第1个参数: UBOOT在RAM中的起始地址
mov r1, #0x0 @ 设置第2个参数:NAND闪存的起始地址
mov r2, #0x20000 @ 设置第3个参数: U-BOOT的长度(128KB)
bl nand_read_whole @ 调用nand_read_whole(),把NAND闪存中的数据读入到RAM中
tst r0, #0x0 @ 如果函数的返回值为0,表示执行成功
beq ok_nand_read @ 执行内存比较,把RAM中的前4K内容与NAND闪存中的前4K内容进行比较, 如果完全相同, 则表示搬移成功
其中,nand_reset (),nand_read_whole()被加在/board/wch2410/wch2410.c中。
支持U-BOOT命令设计
在U -BOOT下对nand闪存的支持主要是在命令行下实现对nand闪存的操作。对nand闪存实现的命令为:nand info(打印nand Flash信息)、nand device(显示某个nand闪存设备)、nand read(读取nand闪存)、nand write(写nand闪存)、nand erease(擦除nand闪存)、nand bad(显示坏块)等。
用到的主要数据结构有:struct nand_flash_dev、struct nand_chip。前者包括主要的芯片型号、存储容量、设备ID、I/O总线宽度等信息;后者是具体对NAND闪存进行操作时用到的信息。
a. 设置配置选项
修改/include/configs/wch2410.h,主要是在CONFIG_COMMANDS中打开CFG_CMD_NAND选项。定义NAND闪存控制器在SFR区中的起始寄存器地址、页面大小,定义NAND闪存命令层的底层接口函数等。
b. 加入NAND闪存芯片型号
在/include/linux/mtd/ nand_ids.h中对如下结构体赋值进行修改:
static struct nand_flash_dev nand_flash_ids[] = {
......
{"Samsung K9F1208U0A", NAND_MFR_SAMSUNG, 0x76, 26, 0, 3, 0x4000, 0},
.......
}
这样对于该款NAND闪存芯片的操作才能正确执行。
c. 编写NAND闪存初始化函数
在/board/wch2410/wch2410.c中加入nand_init()函数。
void nand_init(void)
{
/* 初始化NAND闪存控制器, 以及NAND闪存芯片 */
nand_reset();
/* 调用nand_probe()来检测芯片类型 */
printf ("%4lu MB\n", nand_probe(CFG_NAND_BASE) >> 20);
}
该函数在启动时被start_armboot()调用。
最后重新编译U-BOOT并将生成的u-boot.bin烧入NAND闪存中,目标板上电后从串口输出如下信息:
U-Boot 1.1.3 (Nov 14 2006 - 11:29:50)
U-Boot code: 33F80000 -> 33F9C9E4 BSS: -> 33FA0B28
RAM Configuration:
Bank #0: 30000000 64 MB
## Unknown Flash on Bank 0: ID 0xffff, Size = 0x00000000 = 0 MB
Flash: 0 kB
NAND: 64 MB
In: serial
Out: serial
Err: serial
Hit any key to stop autoboot: 0
wch2410 #
结语
以往将U-BOOT移植到ARM9平台中的解决方案主要针对的是ARM9中的NOR闪存,因为NOR闪存的结构特点致使应用程序可以直接在其内部运行,不用把代码读到RAM中,移植过程相对简单。从NAND闪存中启动U-BOOT的设计难点在于NAND闪存需要把U-BOOT的代码搬移到RAM中,并要让U-BOOT支持NAND闪存的命令操作。本文介绍了实现这一设计的思路及具体程序。移植后,U-BOOT在嵌入式系统中运行良好。
参考文献
1 杜春雷. ARM体系结构与编程[M]. 北京:清华大学出版社,2003
2 S3C2410 User's Mannual[Z].Samsung
经过了一个月的学习和实践,终于能够在终端上看到”samsung nand ……“的字样。原本打算半个月完成的事情,被多拖沓了半个月,我想这会是影响我今后发展的大忌,一定要好好克服。也许很多朋友会说我刚刚说的是一些废话,但是我觉得学习某一样东西,最重要的是在过程中总结出一些有助于我们今后发展的经验,所以刚开头我先费了半天的话。
现在言归正传,移植u-boot的文章在网上有很多,大家可以去看看。当然我在作的过程中也看了一些,对我最有帮助的是chinaunix论坛中的一个贴子,作者比较系统地阐述了移植Linux-2.6内核的一些关键步骤,当然也包括u-boot。作者还制作了pdf,方便大家下载。在此感谢这位无私的作者。利用Linux,大家就应该发扬开源的精神。
1.u-boot-1.1.6与以前版本的不同
但是按照网上一些中文贴的办法来移植u-boot-1.1.6,是不能够在自己的板子上让u-boot跑起来的。因为1.1.6与前面版本的代码有了一些改进,开发人员加入了自己编写的一些与nand相关的代码,大家可以看看../u-boot-1.1.6/drivers文件夹下的内容,它比以前版本多出了nand和nand_legacy两个文件夹。nand文件夹下是开发人员自己编写的从nand启动的代码。如果我们不满意开发人员写的代码,我们可以自己加入与nand相关的代码,这样nand_legacy下的文件就起作用了。如果要使用nand_legacy就必须在../u-boot/include/configs下自己开发板的头文件里面添加宏 #define CFG_NAND_LEGACY。
2.u-boot-1.1.6的整体结构
|-- board
|-- common
|-- cpu
|-- disk
|-- doc
|-- drivers
|-- dtt
|-- examples
|-- fs
|-- include
|-- lib_arm
|-- lib_generic
|-- lib_i386
|-- lib_m68k
|-- lib_microblaze
|-- lib_mips
|-- lib_nios
|-- lib_nios2
|-- lib_ppc
|-- net
|-- post
|-- rtc
`-- tools
board:和一些已有开发板有关的文件. 每一个开发板都以一个子目录出现在当前目录中,
比如说:SMDK2410,子目录中存放与开发板相关的配置文件。
common:实现 U-BOOT 命令行下支持的命令,每一条命令都对应一个文件。例如 bootm 命令对应就是 cmd_bootm.c。
cpu:与特定 CPU 架构相关目录,每一款 U-BOOT 下支持的 CPU 在该目录下对应一个子目录,比如有子目录 arm920t 等。
disk:对磁盘的支持。
doc:文档目录。U-BOOT 有非常完善的文档,推荐大家参考阅读。
drivers:U-BOOT 支持的设备驱动程序都放在该目录,比如各种网卡、支持 CFI 的 Flash、
串口和 USB 等。
fs: 支持的文件系统,U-BOOT 现在支持 cramfs、fat、fdos、jffs2 和 registerfs。
include:U-BOOT 使用的头文件,还有对各种硬件平台支持的汇编文件,系统的配置文件
和对文件系统支持的 文件。该目录下 configs 目录有与开发板相关的配置头文件,如
smdk2410.h。该目录下的 asm 目录有与 CPU 体 系结构相关的头文件,asm 对应的是 asm-arm.
lib_xxxx: 与体系结构相关的库文件。如与 ARM 相关的库放在 lib_arm 中。
net:与网络协议栈相关的代码,BOOTP 协议、TFTP 协议、RARP 协议和 NFS 文件系统
的实现。
tools:生成 U-BOOT 的工具,如:mkimage, crc 等等。
作移植只有关注与开发平台有关的代码,主要是:
(1)board下的smdk2410.c与lowlevel_init.S,smdk2410.c主要是对IO口初始化、时钟的设置、设置dram机构的的长度和起始地址;lowlevel_init.s对dram寄存器的初始化,我们只要知道自己板子的相关配置就可以了。
(2)cpu/arm920t下start.s文件,这个文件是u-boot的启动代码。一般从网上下载下来的u-boot默认在nor flash里启动,我们要让它从nand里面启动就要改这部分的内容。因为nandflash启动方式与nor不同,只有nand前4KB的代码才可以放到sdram中,所以我们要在前4KB的代码中完成主要的初始化和把nand中的内容移到sdram中的操作。
(3)include中的smdk2410.h中的要定义
#define CONFIG_S3C2410_NAND_BOOT,还要把CFG_CMD_NAND的注释去掉。
(4)lib_arm的board.c文件中有函数start_armboot(),start.s启动基本的硬件后,就跳到start_armboot()函数的入口。它主要完成系统初始化,并进入主循环,进入用户命令行。
(5)drivers/nand中的nand.c中是对nand进行初始化的一些代码,nand_base.c中的nand_scan函数是对nandflash的型号进行检测。其中nand.c函数中的board_nand_init要对nand_chip结构进行初始化,这部分需要我们来写,主要要对hwcontrol、eccmode项进行设置
对于uboot的移植请参考我之前写的《U-Boot的编译与移植到QT-S3C44B0X开发板上》http://bbs.chinaunix.net/viewthr ... p;highlight=pywj777
非常感谢dozec的《基于S3C2410的Linux全线移植文档》http://bbs.chinaunix.net/viewthr ... 的Linux全线移植文档我的nand flash移植大部分是参考这个文档移植成功的。
下面对nand flash的初始化代码nand_init()进行分析:
1.如果定义(CONFIG_COMMANDS & CFG_CMD_NAND)没定义(CFG_NAND_LEGACY) 则start_armboot()调用driver/nand/nand.c中的nand_init(),否则如果定义(CONFIG_COMMANDS & CFG_CMD_NAND)并且有定义了CFG_NAND_LEGACY,则调用自己定义的nand_init()。在我当前的情景中是使用driver/nand/nand.c中的nand_init()。
2.nand_init()调用本文件中的nand_init_chip()对nand进行初始化。
3.nand_init_chip()首先调用board_nand_init()。
4.board_nand_init()是需要自己添加的函数,这个函数的主要功能是对struct nand_chip结构体的函数指针赋值,让它们指向自己为nand驱动编写的一些函数,对未赋值的指针,uboot会在后面为其赋上通用nand驱动函数指针。
5.nand_init_chip()接着调用nand_scan().
6.nand_scan()定义在drivers/nand/nand_base.c文件中。它首先对struct nand_chip结构体中在board_nand_init()函数中未赋值的指针赋上通用nand驱动函数指针。
7.通用nand驱动函数nand_select_chip()赋值给struct nand_chip结构体的函数指针用于打开或关闭nand芯片,0为打开,1为关闭。在这个函数中会调用nand_chip结构体中的hwcontrol函数指针,这个指针指向的函数是需要自己编写的。这个函数指针在board_nand_init()函数中被赋值。主要作用是向nand flash发送一些nand flash开启与关闭命令。
8.nand_scan()剩余部分初始化nand_chip和mtd_info结构体。
9.nand_scan()最后在返回时调用drivers/nand/nand_bbt.c文件中的nand_default_bbt()。
10.nand_default_bby()选择一个坏块描述表,返回时调用本文件中的nand_scan_bbt()。
11.nand_scan_bbt()寻找建立一个坏块描述表。
12.最后返回到nand_init(),这样nand驱动的初始化完成了。
下面对命令nand read addr ofs size的执行流程进行分析:
1.nand read addr ofs size命令的作用是从nand flash地址的偏移量ofs处读取长度为size字节的数据存储到内存地址addr处。
2.common/main.c文件中的main_loop()主要执行read_line()读取命令行。
3.read_line()读取到命令行后会调用common/main.c文件中的run_command()。
4.run_command()调用common/command.c文件中的find_cmd()在.u_boot_cmd段中寻找该命令的cmd_tbl_t结构,找到后返回该结构。该命令的结构是通过定义在include/command.h中的宏定义U_BOOT_CMD登记进.u_boot_cmd段中的。
5.run_command()找到该命令的cmd_tbl_t结构后则执行该命令对应的函数。对于本情景是nand命令对应的函数do_nand()。
6.do_nand()有两个版本,一个是定义了CFG_NAND_LEGACY。另一个是未定义CFG_NAND_LEGACY。这两个版本都定义在common/cmd_nand.c文件中。对于本情景使用未定义CFG_NAND_LEGACY的do_nand()函数。要使用do_nand()还必须定义宏CONFIG_COMMANDS&CFG_CMD_NAND。(若未定义CFG_NAND_LEGACY则在这个情景中的do_nand()函数调用的函数都定义在drivers/nand_legacy/nand_legacy.c文件中)。
7.对于我们的情景do_nand()会调用定义在include/nand.h文件中的nand_read()。
8.nand_read()则调用本nand芯片对应的nand_info_t结构的read指针。而read指针在nand_scan()中被指向了同文件(drivers/nand/nand_base.c)中的nand_read()函数。
9.nand_read()函数最终会调用nand_chip结构中的cmdfunc指针,通过这个指针指向的函数向nand flash芯片发送命令。最终完成整个命令的执行。
为了让uboot支持自己QT板子的nand flash而进行修改的部分
1.前面的移植请参考我写的一篇《U-Boot的编译与移植到QT-S3C44B0X开发板上》,现在在board/51EDA/QT/目录下建立nand.c文件。
2.在nand.c中添加自己的board_nand_init()函数。设定nand_chip结构中的hwcontrol和dev_ready指针指向自己的函数QT_hwcontrol和QT_device_ready。并建立自己的QT_hwcontrol和QT_device_ready函数。
3.由于自己板子的nand flash的命令发送方式与uboot提供的通用nand flash命令发送方式不同,所以在nand.c文件中建立自己的命令发送函数QT_nand_command(),并在board_nand_init()函数中将nand_chip结构中的cmdfunc指针指向QT_nand_command()函数,使其使用自己定义的发送命令函数。
4.在include/configs/QT.h中定义CFG_NAND_BASE用于指定自己板子nand flash的I/O地址。
5.在CONFIG_COMMANDS中打开CFG_CMD_NAND选项。
6.在include/configs/QT.h中定义NAND_MAX_CHIPS指定自己板子的nand flash芯片数。
7.在include/configs/QT.h中定义CFG_MAX_NAND_DEVICE指定想要支持的nand flash设备数。
dozec的《基于S3C2410的Linux全线移植文档》在U-BOOT对Nand Flash的支持中的移植方法是在定义了CFG_NAND_LEGACY情况下移植的。而我的是在未定义CFG_NAND_LEGACY的情况下移植的。
--------------------------------------------------------------------------------
作者: pywj777 时间: 2006-11-15 18:22
下面是我修改的代码:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -