移植环境:
Linux系统:Fedora9
交叉编译环境:arm-linux-gcc4.4.3
目标板:华中科技大学惠世科技S3C2440实验箱,采用SAMSUNG公司型号为K9F2G08U0B的256M*8Bit NAND Flash Memory
1. NAND Flash的组织结构
NAND Flash芯片的存储空间是按照块和页的概念来组织的。现在市面上的NAND主要按大页和小页两种存储类型来进行数据管理。以NAND型号K9F2G08U0B为例,该NAND为大页结构,即芯片每块(Block)有64页(Page),每页有2K Byte的数据存储区和64Byte的冗余数据区(用来存放ECC校验码)。2K Byte的数据存储区作为数据缓冲单元,用来实现I/0缓冲和存储器之间的数据传输。
NAND Flash的物理层驱动主要涉及到:NAND Flash的初始化(ID以及相关属性信息的读取)、擦除(以块作为单位),数据读写(以页作为基本单位)。在这一系列的过程中,会涉及到NAND Flash命令的发送以及 NAND Flash地址的发送。其中地址的发送会因NAND Flash型号的不同而有所区别。一般来说,小页的 NAND Flash其地址周期通常为4个:1个列地址(Column Address)和3个行地址(Row Address),而对大页的NAND Flash来说,列地址至少是2个周期,行地址会因芯片的容量大小而有所区别。本文采用的三星NAND Flash芯片是2个列地址和3个行地址(行地址就是页地址,列地址为页内地址)。对于 NAND FLASH 来讲,地址和命令只能在I/O[7:0]上并行传递。NAND Flash以页为单位读写数据,而以块为单位擦除数据。NAND的数据传输方式有基本的I/O传输方式,即在I/O[7:0]数据线上进行的数据传输。这种操作的缺点是系统CPU要频繁参与控制数据的传输,会影响到数据传输速度。S3C2440芯片集成了 NAND Flash控制寄存器,大大简化了对 NAND Flash的操作。
K9F2G08U0B Array Organization
2. NAND Flash 中的坏块(Bad Block)
NAND Flash 中,一个块中含有 1 个或多个位是坏的,就称为其为坏块 Bad Block。坏块的稳定性是无法保证的,也就是说,不能保证你写入的数据是对的,或者写入对了,读出来也不一定对的。与此对应的正常的块,肯定是写入读出都是正常的。坏块有两种:
(1) 出厂时就有存在的坏块:
一种是出厂的时候,也就是,你买到的新的,还没用过的 NAND Flash,就可以包含了坏块。此类出厂时就有的坏块,被称作 factory (masked) bad block 或 initial bad/invalid block,在出厂之前,就会做对应的标记,标为坏块。
(2) 使用过程中产生的坏块:
第二类叫做在使用过程中产生的,由于使用过程时间长了,在擦块除的时候,出错了,说明此块坏了,也要在程序运行过程中,发现,并且标记成坏块的。具体标记的位置,和上面一样。这类块叫做 worn-out bad block。即用坏了的块。
2.1坏块的标记
具体标记的地方是,对于现在常见的页大小为2K的 NAND Flash,是块中第一个页的 oob 起始位置的第1个字节(旧的小页面,pagesize是512B至256B的 NAND Flash,坏块标记是第 6 个字节)。如果不是 0xFF, 就说明是坏块。相对应的是,所有正常的块,好的块,里面所有数据都是 0xFF 的。不过,对于现在新出的有些 Nand Flash,很多标记方式,有些变化,有的变成该坏块的第一个页或者第二个页,也有的是,倒数最后一个或倒数第二个页,用于标记坏块的。具体的信息,请参考对应的 Nand Flash 的数据手册,其中会有说明。对于坏块的标记,本质上,也只是对应的 flash 上的某些字节的数据是非 0xFF 而已,所以,只要是数据,就是可以读取和写入的。也就意味着,可以写入其他值,也就把这个坏块标记信息破坏了。对于出厂时的坏块,一般是不建议将标记好的信息擦除掉的。uboot 中有个命令是“nand scrub”就可以将块中所有的内容都擦除了,包括坏块标记,不论是出厂时的,还是后来使用过程中出现而新标记的。一般来说,不建议用这个。其实最好的做法是,用“nand erase”只擦除好的块,对于已经标记坏块的块,不要轻易擦除掉,否则就很难区分哪些是出厂时就坏的,哪些是后来使用过程中用坏的了。
2.2坏块的管理
对于坏块的管理,在Linux系统中,叫做坏块管理(BBM,Bad Block Management),对应的会有一个表去记录好块,坏块的信息,以及坏块是出厂就有的,还是后来使用产生的,这个表叫做坏块表(BBT,Bad Block Table) 。在 Linux 内核 MTD 架构下的Nand Flash驱动,和 Uboot 中 NAND Flash 驱动中,在加载完驱动之后,如果你没有加入参数主动要求跳过坏块扫描的话,那么都会去主动扫描坏块,建立必要的 BBT 的,以备后面坏块管理所使用。K9F2G08U0B建立BBT的流程图如下所示:
2.3坏块的比例
而关于好块和坏块,Nand Flash 在出厂的时候,会做出保证:
1.关于好的,可以使用的块的数目达到一定的数目,比如三星的K9F2G08U0B,整个 Flash一共有2048个块,出厂的时候,保证好的块至少大于2008个,也就是意思是,你新买到这个型号的Nand Flash,最坏的可能, 有 2048-2008=40个坏块。不过,事实上,现在出厂时的坏块,比较少,绝大多数,都是使用时间长了,在使用过程中出现的。
2.保证第一个块是好的,并且一般相对来说比较耐用。做此保证的主要原因是,很多 NandFlash 坏块管理方法中,就是将第一个块,用来存储上面提到的 BBT,否则,都是出错几率一样的块,那么也就不太好管理了,连放 BBT 的地方,都不好找了。一般来说,不同型号的Nand Flash的数据手册中,也会提到自己的这个NAND Flash,最多允许多少个坏块。
3. 移植uboot NAND Flash代码的疑问
参考Mini2440之U-boot使用及移植详细手册.pdf,移植NAND Flash代码重定向时,新添加了nand_read.c源文件。摘录读取NAND Flash读取数据函数如下:
点击(此处)折叠或打开
- static int nand_read_page_ll(struct boot_nand_t * nand, unsigned char *buf,
- unsigned long addr)
- {
- unsigned short *ptr16 = (unsigned short *)buf;
- unsigned int i, page_num;
- nand_clear_RnB();
- NFCMD = NAND_CMD_READ0;//发送读数据命令
- if (nand->page_size == 512) {
- /* Write Address */
- NFADDR = addr & 0xff;
- NFADDR = (addr >> 9) & 0xff;
- NFADDR = (addr >> 17) & 0xff;
- NFADDR = (addr >> 25) & 0xff;
- } else if (nand->page_size == 2048) {
- page_num = addr >> 11; /* addr / 2048 */
- /* Write Address */
- NFADDR = 0;
- NFADDR = 0;
- NFADDR = page_num & 0xff;
- NFADDR = (page_num >> 8) & 0xff;
- NFADDR = (page_num >> 16) & 0xff;
- NFCMD = NAND_CMD_READSTART;
- } else {
- return -1;
- }
- nand_wait();
- #if defined(CONFIG_S3C2410)
- for (i = 0; i < nand->page_size; i++) {
- *buf = (NFDATA & 0xff);
- buf++;
- }
- #elif defined(CONFIG_S3C2440) || defined(CONFIG_S3C2442)
- for (i = 0; i < (nand->page_size>>1); i++) {//读2K字节数据
- *ptr16 = NFDATA16;
- ptr16++;
- }
- #endif
- return nand->page_size;
- }
参照K9F2G08U0B芯片手册,发送地址的时序如下图所示:
前两个周期发送A0~A11,为列地址,即页内地址。后三个周期发送A12~A28,为行地址,即页地址。但是程序中获取页地址是把地址addr右移11位,而不是12位。开始一直很困惑。经过一番探索之后才理解了其中缘由。首先需要了解NAND Flash页内地址的结构,它分为Main Field(2K)和Spare Field(64Byte)两部分,而我们的数据是存放在2K当中,普通的数据读写不对后面64Byte操作,使用A0~A10地址就可实现对2K数据存储区的寻址。下图为K9F2G08U0B页内结构:
函数nand_read_page_ll传递进来的addr地址没有包含对Sapre Field的寻址。所以只移11位,而不是移12位。NAND Flash读取数据是以页为单位,当NAND Flash接收到地址后,会把该页内addr&0x7F地址之后的数据依次输出包括Spare Field。在nand_read_page_ll函数里,列地址为0,读取此页所有数据。所以for循环读数据时,只读前2K的内容后就跳出循环。
那如何只读取Sapre Field中的内容呢?以检查K9F2G08U0B中某一块是否是坏块为例,我们需要读取该块第一页地址为2048处的数据是否为0xFF。摘录nand_read.c源文件中检查坏块函数is_bad_block如下:
点击(此处)折叠或打开
- static int is_bad_block(struct boot_nand_t * nand, unsigned long i)
- {
-
unsigned char data;
-
unsigned long page_num;
-
nand_clear_RnB();
-
if (nand->page_size == 512) {
-
NFCMD = NAND_CMD_READOOB; /* 0x50 */
-
NFADDR = nand->bad_block_offset & 0xf;
-
NFADDR = (i >> 9) & 0xff;
-
NFADDR = (i >> 17) & 0xff;
-
NFADDR = (i >> 25) & 0xff;
-
} else if (nand->page_size == 2048) { //K9F2G08U0B执行这里
-
page_num = i >> 11; /* addr / 2048 */
-
NFCMD = NAND_CMD_READ0;
-
NFADDR = nand->bad_block_offset & 0xff;
-
NFADDR = (nand->bad_block_offset >> 8) & 0xff;
-
NFADDR = page_num & 0xff;
-
NFADDR = (page_num >> 8) & 0xff;
-
NFADDR = (page_num >> 16) & 0xff;
-
NFCMD = NAND_CMD_READSTART;
-
} else {
-
return -1;
-
}
-
nand_wait();
-
data = (NFDATA & 0xff); //读2048处一个字节数据
-
if (data != 0xff)
-
return 1;
-
return 0;
- }
在这里只需把列地址的值设为2048,即0x80。此时就需要用到A11来寻址了。这里只需要读取第一个字节就可以了。
小结:
这里开始无法理解的原因是不知道传递进来的地址addr不包括对Spare Feild的寻址,只有对2K数据的寻址。检查是否是坏块,是直接给列地址赋值为0x80,并没有用到传递进来的地址参数。
附nand_read.c源码(摘自Mini2440之U-boot使用及移植详细手册.pdf):
点击(此处)折叠或打开
- /*
-
* nand_read.c: Simple NAND read functions for booting from NAND
-
*
-
* This is used by cpu/arm920/start.S assembler code,
-
* and the board-specific linker script must make sure this
-
* file is linked within the first 4kB of NAND flash.
-
*
-
* Taken from GPLv2 licensed vivi bootloader,
-
* Copyright (C) 2002 MIZI Research, Inc.
-
*
-
* Author: Hwang, Chideok <hwang@mizi.com>
-
* Date : $Date: 2004/02/04 10:37:37 $
-
*
-
* u-boot integration and bad-block skipping (C) 2006 by OpenMoko, Inc.
-
* Author: Harald Welte <laforge@openmoko.org>
-
*/
- #include <common.h>
- #include <linux/mtd/nand.h>
- #define __REGb(x) (*(volatile unsigned char *)(x))
- #define __REGw(x) (*(volatile unsigned short *)(x))
- #define __REGi(x) (*(volatile unsigned int *)(x))
- #define NF_BASE 0x4e000000
- #if defined(CONFIG_S3C2410)
- #define NFCONF __REGi(NF_BASE + 0x0)
- #define NFCMD __REGb(NF_BASE + 0x4)
- #define NFADDR __REGb(NF_BASE + 0x8)
- #define NFDATA __REGb(NF_BASE + 0xc)
- #define NFSTAT __REGb(NF_BASE + 0x10)
- #define NFSTAT_BUSY 1
- #define nand_select() (NFCONF &= ~0x800)
- #define nand_deselect() (NFCONF |= 0x800)
- #define nand_clear_RnB() do {} while (0)
- #elif defined(CONFIG_S3C2440) || defined(CONFIG_S3C2442)
- #define NFCONF __REGi(NF_BASE + 0x0)
- #define NFCONT __REGi(NF_BASE + 0x4)
- #define NFCMD __REGb(NF_BASE + 0x8)
- #define NFADDR __REGb(NF_BASE + 0xc)
- #define NFDATA __REGb(NF_BASE + 0x10)
- #define NFDATA16 __REGw(NF_BASE + 0x10)
- #define NFSTAT __REGb(NF_BASE + 0x20)
- #define NFSTAT_BUSY 1
- #define nand_select() (NFCONT &= ~(1 << 1))
- #define nand_deselect() (NFCONT |= (1 << 1))
- #define nand_clear_RnB() (NFSTAT |= (1 << 2))
- #endif
- static inline void nand_wait(void)
-
{
- int i;
- while (!(NFSTAT & NFSTAT_BUSY))
- for (i=0; i<10; i++);
-
}
- struct boot_nand_t {
- int page_size;
- int block_size;
- int bad_block_offset;
- // unsigned long size;
-
};
- #if 0
- #if defined(CONFIG_S3C2410) || defined(CONFIG_MINI2440)
-
/* configuration for 2410 with 512byte sized flash */
- #define NAND_PAGE_SIZE 512
- #define BAD_BLOCK_OFFSET 5
- #define NAND_BLOCK_MASK (NAND_PAGE_SIZE - 1)
- #define NAND_BLOCK_SIZE 0x4000
- #else
-
/* configuration for 2440 with 2048byte sized flash */
- #define NAND_5_ADDR_CYCLE
- #define NAND_PAGE_SIZE 2048
- #define BAD_BLOCK_OFFSET NAND_PAGE_SIZE
- #define NAND_BLOCK_MASK (NAND_PAGE_SIZE - 1)
- #define NAND_BLOCK_SIZE (NAND_PAGE_SIZE * 64)
- #endif
-
/* compile time failure in case of an invalid configuration */
- #if defined(CONFIG_S3C2410) && (NAND_PAGE_SIZE != 512)
- #error "S3C2410 does not support nand page size != 512"
- #endif
- #endif
- static int is_bad_block(struct boot_nand_t * nand, unsigned long i)
-
{
- unsigned char data;
- unsigned long page_num;
- nand_clear_RnB();
- if (nand->page_size == 512) {
- NFCMD = NAND_CMD_READOOB; /* 0x50 */
- NFADDR = nand->bad_block_offset & 0xf;
- NFADDR = (i >> 9) & 0xff;
- NFADDR = (i >> 17) & 0xff;
- NFADDR = (i >> 25) & 0xff;
- } else if (nand->page_size == 2048) {
- page_num = i >> 11; /* addr / 2048 */
- NFCMD = NAND_CMD_READ0;
- NFADDR = nand->bad_block_offset & 0xff;
- NFADDR = (nand->bad_block_offset >> 8) & 0xff;
- NFADDR = page_num & 0xff;
- NFADDR = (page_num >> 8) & 0xff;
- NFADDR = (page_num >> 16) & 0xff;
- NFCMD = NAND_CMD_READSTART;
- } else {
- return -1;
- }
- nand_wait();
- data = (NFDATA & 0xff);
- if (data != 0xff)
- return 1;
- return 0;
-
}
- static int nand_read_page_ll(struct boot_nand_t * nand, unsigned char *buf, unsigned long addr)
-
{
- unsigned short *ptr16 = (unsigned short *)buf;
- unsigned int i, page_num;
- nand_clear_RnB();
- NFCMD = NAND_CMD_READ0;
- if (nand->page_size == 512) {
-
/* Write Address */
- NFADDR = addr & 0xff;
- NFADDR = (addr >> 9) & 0xff;
- NFADDR = (addr >> 17) & 0xff;
- NFADDR = (addr >> 25) & 0xff;
- } else if (nand->page_size == 2048) {
- page_num = addr >> 11; /* addr / 2048 */
-
/* Write Address */
- NFADDR = 0;
- NFADDR = 0;
- NFADDR = page_num & 0xff;
- NFADDR = (page_num >> 8) & 0xff;
- NFADDR = (page_num >> 16) & 0xff;
- NFCMD = NAND_CMD_READSTART;
- } else {
- return -1;
- }
- nand_wait();
- #if defined(CONFIG_S3C2410)
- for (i = 0; i < nand->page_size; i++) {
- *buf = (NFDATA & 0xff);
- buf++;
- }
- #elif defined(CONFIG_S3C2440) || defined(CONFIG_S3C2442)
- for (i = 0; i < (nand->page_size>>1); i++) {
- *ptr16 = NFDATA16;
- ptr16++;
- }
- #endif
- return nand->page_size;
-
}
- static unsigned short nand_read_id()
-
{
- unsigned short res = 0;
- NFCMD = NAND_CMD_READID;
- NFADDR = 0;
- res = NFDATA;
- res = (res << 8) | NFDATA;
- return res;
-
}
- extern unsigned int dynpart_size[];
-
/* low level nand read function */
-
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
-
{
- int i, j;
- unsigned short nand_id;
- struct boot_nand_t nand;
- /* chip Enable */
- nand_select();
- nand_clear_RnB();
- for (i = 0; i < 10; i++)
- ;
- nand_id = nand_read_id();
- if (0) { /* dirty little hack to detect if nand id is misread */
- unsigned short * nid = (unsigned short *)0x31fffff0;
- *nid = nand_id;
- }
- if (nand_id == 0xec76 || /* Samsung K91208 */
- nand_id == 0xad76 ) { /*Hynix HY27US08121A*/
- nand.page_size = 512;
- nand.block_size = 16 * 1024;
- nand.bad_block_offset = 5;
- // nand.size = 0x4000000;
- } else if (nand_id == 0xecf1 || /* Samsung K9F1G08U0B */
- nand_id == 0xecda || /* Samsung K9F2G08U0B */
- nand_id == 0xecd3 ) { /* Samsung K9K8G08 */
- nand.page_size = 2048;
- nand.block_size = 128 * 1024;
- nand.bad_block_offset = nand.page_size;
- // nand.size = 0x8000000;
- } else {
- return -1; // hang
-
}
- if ((start_addr & (nand.block_size-1)) || (size & (nand.block_size-1)))
- return -1; /* invalid alignment */
- for (i=start_addr; i < (start_addr + size);) {
- #ifdef CONFIG_S3C2410_NAND_SKIP_BAD
- if (i & (nand.block_size-1)== 0) {
- if (is_bad_block(&nand, i) ||
- is_bad_block(&nand, i + nand.page_size)) {
- /* Bad block */
- i += nand.block_size;
- size += nand.block_size;
- continue;
- }
- }
- #endif
- j = nand_read_page_ll(&nand, buf, i);
- i += j;
- buf += j;
- }
-
/* chip Disable */
- nand_deselect();
- return 0;
- }