uboot移植中对NAND Flash读数据时地址移位的疑惑

时间:2021-06-11 04:52:43

移植环境:

Linux系统:Fedora9

交叉编译环境:arm-linux-gcc4.4.3

目标板:华中科技大学惠世科技S3C2440实验箱,采用SAMSUNG公司型号为K9F2G08U0B256M*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的操作。

uboot移植中对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的流程图如下所示:

uboot移植中对NAND Flash读数据时地址移位的疑惑

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读取数据函数如下:

点击(此处)折叠或打开

  1. static int nand_read_page_ll(struct boot_nand_t * nand, unsigned char *buf, 
  2.      unsigned long addr)
  3. {
  4. unsigned short *ptr16 = (unsigned short *)buf;
  5. unsigned int i, page_num;
  6. nand_clear_RnB();
  7. NFCMD = NAND_CMD_READ0;//发送读数据命令
  8. if (nand->page_size == 512) {
  9. /* Write Address */
  10. NFADDR = addr & 0xff;
  11. NFADDR = (addr >> 9) & 0xff;
  12. NFADDR = (addr >> 17) & 0xff;
  13. NFADDR = (addr >> 25) & 0xff;
  14. else if (nand->page_size == 2048) {
  15. page_num = addr >> 11; /* addr / 2048 */
  16. /* Write Address */
  17. NFADDR = 0;
  18. NFADDR = 0;
  19. NFADDR = page_num & 0xff;
  20. NFADDR = (page_num >> 8) & 0xff;
  21. NFADDR = (page_num >> 16) & 0xff;
  22. NFCMD = NAND_CMD_READSTART;
  23. } else {
  24. return -1;
  25. }
  26. nand_wait();
  27. #if defined(CONFIG_S3C2410)
  28. for (i = 0; i < nand->page_size; i++) {
  29. *buf = (NFDATA & 0xff);
  30. buf++;
  31. }
  32. #elif defined(CONFIG_S3C2440) || defined(CONFIG_S3C2442)
  33. for (i = 0; i < (nand->page_size>>1); i++) {//读2K字节数据
  34. *ptr16 = NFDATA16;
  35. ptr16++;
  36. }
  37. #endif
  38. return nand->page_size;
  39. }

参照K9F2G08U0B芯片手册,发送地址的时序如下图所示:

uboot移植中对NAND Flash读数据时地址移位的疑惑

前两个周期发送A0~A11,为列地址,即页内地址。后三个周期发送A12~A28,为行地址,即页地址。但是程序中获取页地址是把地址addr右移11位,而不是12位。开始一直很困惑。经过一番探索之后才理解了其中缘由。首先需要了解NAND Flash页内地址的结构,它分为Main Field(2K)和Spare Field(64Byte)两部分,而我们的数据是存放在2K当中,普通的数据读写不对后面64Byte操作,使用A0~A10地址就可实现对2K数据存储区的寻址。下图为K9F2G08U0B页内结构:

uboot移植中对NAND Flash读数据时地址移位的疑惑

函数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如下:

点击(此处)折叠或打开

  1. static int is_bad_block(struct boot_nand_t * nand, unsigned long i)
  2. {
  3. unsigned char data;
  4. unsigned long page_num;
  5. nand_clear_RnB();
  6. if (nand->page_size == 512) {
  7. NFCMD = NAND_CMD_READOOB; /* 0x50 */
  8. NFADDR = nand->bad_block_offset & 0xf;
  9. NFADDR = (i >> 9) & 0xff;
  10. NFADDR = (i >> 17) & 0xff;
  11. NFADDR = (i >> 25) & 0xff;
  12. } else if (nand->page_size == 2048) {     //K9F2G08U0B执行这里
  13. page_num = i >> 11; /* addr / 2048 */
  14. NFCMD = NAND_CMD_READ0;
  15. NFADDR = nand->bad_block_offset & 0xff;
  16. NFADDR = (nand->bad_block_offset >> 8) & 0xff;
  17. NFADDR = page_num & 0xff;
  18. NFADDR = (page_num >> 8) & 0xff;
  19. NFADDR = (page_num >> 16) & 0xff;
  20. NFCMD = NAND_CMD_READSTART;
  21. } else {
  22. return -1;
  23. }
  24. nand_wait();
  25. data = (NFDATA & 0xff);    //读2048处一个字节数据
  26. if (data != 0xff)
  27. return 1;
  28. return 0;
  29. }

在这里只需把列地址的值设为2048,即0x80。此时就需要用到A11来寻址了。这里只需要读取第一个字节就可以了。

小结:

这里开始无法理解的原因是不知道传递进来的地址addr不包括对Spare Feild的寻址,只有对2K数据的寻址。检查是否是坏块,是直接给列地址赋值为0x80,并没有用到传递进来的地址参数。

附nand_read.c源码(摘自Mini2440之U-boot使用及移植详细手册.pdf):

点击(此处)折叠或打开

  1. /*
  2. * nand_read.c: Simple NAND read functions for booting from NAND
  3. *
  4. * This is used by cpu/arm920/start.S assembler code,
  5. * and the board-specific linker script must make sure this
  6. * file is linked within the first 4kB of NAND flash.
  7. *
  8. * Taken from GPLv2 licensed vivi bootloader,
  9. * Copyright (C) 2002 MIZI Research, Inc.
  10. *
  11. * Author: Hwang, Chideok <hwang@mizi.com>
  12. * Date : $Date: 2004/02/04 10:37:37 $
  13. *
  14. * u-boot integration and bad-block skipping (C) 2006 by OpenMoko, Inc.
  15. * Author: Harald Welte <laforge@openmoko.org>
  16. */
  17. #include <common.h>
  18. #include <linux/mtd/nand.h>
  19. #define __REGb(x) (*(volatile unsigned char *)(x))
  20. #define __REGw(x) (*(volatile unsigned short *)(x))
  21. #define __REGi(x) (*(volatile unsigned int *)(x))
  22. #define NF_BASE 0x4e000000
  23. #if defined(CONFIG_S3C2410)
  24. #define NFCONF __REGi(NF_BASE + 0x0)
  25. #define NFCMD __REGb(NF_BASE + 0x4)
  26. #define NFADDR __REGb(NF_BASE + 0x8)
  27. #define NFDATA __REGb(NF_BASE + 0xc)
  28. #define NFSTAT __REGb(NF_BASE + 0x10)
  29. #define NFSTAT_BUSY 1
  30. #define nand_select() (NFCONF &= ~0x800)
  31. #define nand_deselect() (NFCONF |= 0x800)
  32. #define nand_clear_RnB() do {} while (0)
  33. #elif defined(CONFIG_S3C2440) || defined(CONFIG_S3C2442)
  34. #define NFCONF __REGi(NF_BASE + 0x0)
  35. #define NFCONT __REGi(NF_BASE + 0x4)
  36. #define NFCMD __REGb(NF_BASE + 0x8)
  37. #define NFADDR __REGb(NF_BASE + 0xc)
  38. #define NFDATA __REGb(NF_BASE + 0x10)
  39. #define NFDATA16 __REGw(NF_BASE + 0x10)
  40. #define NFSTAT __REGb(NF_BASE + 0x20)
  41. #define NFSTAT_BUSY 1
  42. #define nand_select() (NFCONT &= ~(<< 1))
  43. #define nand_deselect() (NFCONT |= (<< 1))
  44. #define nand_clear_RnB() (NFSTAT |= (<< 2))
  45. #endif
  46. static inline void nand_wait(void)
  47. {
  48.     int i;
  49.     while (!(NFSTAT & NFSTAT_BUSY))
  50.     for (i=0; i<10; i++);
  51. }
  52. struct boot_nand_t {
  53.     int page_size;
  54.     int block_size;
  55.     int bad_block_offset;
  56.     // unsigned long size;
  57. };
  58. #if 0
  59. #if defined(CONFIG_S3C2410) || defined(CONFIG_MINI2440)
  60. /* configuration for 2410 with 512byte sized flash */
  61. #define NAND_PAGE_SIZE 512
  62. #define BAD_BLOCK_OFFSET 5
  63. #define NAND_BLOCK_MASK (NAND_PAGE_SIZE - 1)
  64. #define NAND_BLOCK_SIZE 0x4000
  65. #else
  66. /* configuration for 2440 with 2048byte sized flash */
  67. #define NAND_5_ADDR_CYCLE
  68. #define NAND_PAGE_SIZE 2048
  69. #define BAD_BLOCK_OFFSET NAND_PAGE_SIZE
  70. #define NAND_BLOCK_MASK (NAND_PAGE_SIZE - 1)
  71. #define NAND_BLOCK_SIZE (NAND_PAGE_SIZE * 64)
  72. #endif
  73. /* compile time failure in case of an invalid configuration */
  74. #if defined(CONFIG_S3C2410) && (NAND_PAGE_SIZE != 512)
  75. #error "S3C2410 does not support nand page size != 512"
  76. #endif
  77. #endif
  78. static int is_bad_block(struct boot_nand_t * nand, unsigned long i)
  79. {
  80.     unsigned char data;
  81.     unsigned long page_num;
  82.     nand_clear_RnB();
  83.     if (nand->page_size == 512) {
  84.         NFCMD = NAND_CMD_READOOB; /* 0x50 */
  85.         NFADDR = nand->bad_block_offset & 0xf;
  86.         NFADDR = (>> 9) & 0xff;
  87.         NFADDR = (>> 17) & 0xff;
  88.         NFADDR = (>> 25) & 0xff;
  89.     } else if (nand->page_size == 2048) {
  90.         page_num = i >> 11; /* addr / 2048 */
  91.         NFCMD = NAND_CMD_READ0;
  92.         NFADDR = nand->bad_block_offset & 0xff;
  93.         NFADDR = (nand->bad_block_offset >> 8) & 0xff;
  94.         NFADDR = page_num & 0xff;
  95.         NFADDR = (page_num >> 8) & 0xff;
  96.         NFADDR = (page_num >> 16) & 0xff;
  97.         NFCMD = NAND_CMD_READSTART;
  98.     } else {
  99.         return -1;
  100.     }
  101.     nand_wait();
  102.     data = (NFDATA & 0xff);
  103.     if (data != 0xff)
  104.         return 1;
  105.     return 0;
  106. }
  107. static int nand_read_page_ll(struct boot_nand_t * nand, unsigned char *buf, unsigned long addr)
  108. {
  109.     unsigned short *ptr16 = (unsigned short *)buf;
  110.     unsigned int i, page_num;
  111.     nand_clear_RnB();
  112.     NFCMD = NAND_CMD_READ0;
  113.     if (nand->page_size == 512) {
  114. /* Write Address */
  115.     NFADDR = addr & 0xff;
  116.     NFADDR = (addr >> 9) & 0xff;
  117.     NFADDR = (addr >> 17) & 0xff;
  118.     NFADDR = (addr >> 25) & 0xff;
  119.     } else if (nand->page_size == 2048) {
  120.     page_num = addr >> 11; /* addr / 2048 */
  121. /* Write Address */
  122.     NFADDR = 0;
  123.     NFADDR = 0;
  124.     NFADDR = page_num & 0xff;
  125.     NFADDR = (page_num >> 8) & 0xff;
  126.     NFADDR = (page_num >> 16) & 0xff;
  127.     NFCMD = NAND_CMD_READSTART;
  128.     } else {
  129.         return -1;
  130.     }
  131.     nand_wait();
  132. #if defined(CONFIG_S3C2410)
  133.     for (= 0; i < nand->page_size; i++) {
  134.         *buf = (NFDATA & 0xff);
  135.         buf++;
  136.     }
  137. #elif defined(CONFIG_S3C2440) || defined(CONFIG_S3C2442)
  138.     for (= 0; i < (nand->page_size>>1); i++) {
  139.         *ptr16 = NFDATA16;
  140.         ptr16++;
  141.     }
  142. #endif
  143.     return nand->page_size;
  144. }
  145. static unsigned short nand_read_id()
  146. {
  147.     unsigned short res = 0;
  148.     NFCMD = NAND_CMD_READID;
  149.     NFADDR = 0;
  150.     res = NFDATA;
  151.     res = (res << 8) | NFDATA;
  152.     return res;
  153. }
  154. extern unsigned int dynpart_size[];
  155. /* low level nand read function */
  156. int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
  157. {
  158.     int i, j;
  159.     unsigned short nand_id;
  160.     struct boot_nand_t nand;
  161.     /* chip Enable */
  162.     nand_select();
  163.     nand_clear_RnB();
  164.     for (= 0; i < 10; i++)
  165.         ;
  166.     nand_id = nand_read_id();
  167.     if (0) { /* dirty little hack to detect if nand id is misread */
  168.         unsigned short * nid = (unsigned short *)0x31fffff0;
  169.         *nid = nand_id;
  170.     }
  171.     if (nand_id == 0xec76 || /* Samsung K91208 */
  172.         nand_id == 0xad76 ) { /*Hynix HY27US08121A*/
  173.         nand.page_size = 512;
  174.         nand.block_size = 16 * 1024;
  175.         nand.bad_block_offset = 5;
  176.         // nand.size = 0x4000000;
  177.     } else if (nand_id == 0xecf1 || /* Samsung K9F1G08U0B */
  178.         nand_id == 0xecda || /* Samsung K9F2G08U0B */
  179.         nand_id == 0xecd3 ) { /* Samsung K9K8G08 */
  180.         nand.page_size = 2048;
  181.         nand.block_size = 128 * 1024;
  182.         nand.bad_block_offset = nand.page_size;
  183.         // nand.size = 0x8000000;
  184.     } else {
  185.         return -1; // hang
  186. }
  187.     if ((start_addr & (nand.block_size-1)) || (size & (nand.block_size-1)))
  188.         return -1; /* invalid alignment */
  189.     for (i=start_addr; i < (start_addr + size);) {
  190.     #ifdef CONFIG_S3C2410_NAND_SKIP_BAD
  191.         if (& (nand.block_size-1)== 0) {
  192.             if (is_bad_block(&nand, i) ||
  193.             is_bad_block(&nand, i + nand.page_size)) {
  194.             /* Bad block */
  195.             i += nand.block_size;
  196.             size += nand.block_size;
  197.             continue;
  198.             }
  199.         }
  200.     #endif
  201.         j = nand_read_page_ll(&nand, buf, i);
  202.         i += j;
  203.         buf += j;
  204.     }
  205. /* chip Disable */
  206.     nand_deselect();
  207.     return 0;
  208. }