Chinaunix首页 | 论坛 | 博客
  • 博客访问: 209867
  • 博文数量: 112
  • 博客积分: 275
  • 博客等级: 二等列兵
  • 技术积分: 565
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-20 10:52
文章分类

全部博文(112)

文章存档

2014年(3)

2013年(2)

2012年(64)

2011年(43)

分类:

2012-09-10 07:18:12

      在Linux 内核中,MTD 源代码放在/driver/mtd 目录中,该目录中包含chips 、devices 、maps 、nand 、onenand 和ubi 六个子目录。其中只有nand 和onenand 目录中的代码才与NAND 驱动相关,不过nand 目录中的代码比较通用,而onenand 目录中的代码相对于nand 中的代码而言则简化了很多,它是针对三星公司开发的另一类Flash芯片,即OneNAND Flash,是一种较常用NAND先进的FLASH吧,只是目前似乎普及率并不高,本文也将不做讨论。 
      因此,若只是开发基于MTD 的NAND 驱动程序,那么我们需要关注的代码就基本上全在drivers/mtd/nand 目录中了,而该目录中也不是所有的代码文件都与我们将要开发的NAND 驱动有关,除了Makefile 和Kconfig 之外,其中真正与NAND 驱动有关的代码文件只有6 个,即: 
1)nand_base.c : 
      定义了NAND 驱动中对NAND 芯片最基本的操作函数和操作流程,如擦除、读写page 、读写oob 等。当然这些函数都只是进行一些default 的操作,若你的系统在对NAND 操作时有一些特殊的动作,则需要在你自己的驱动代码中进行定义,然后Replace 这些default 的函数。
2)nand_bbt.c : 
定义了NAND 驱动中与坏块管理有关的函数和结构体。
3)nand_ids.c : 
      定义了两个全局类型的结构体:struct nand_flash_dev nand_flash_ids[ ] 和struct nand_manufacturers nand_manuf_ids[ ] 。其中前者定义了一些NAND 芯片的类型,后者定义了NAND 芯片的几个厂商。NAND 芯片的ID 至少包含两项内容:厂商ID 和厂商为自己的NAND 芯片定义的芯片ID 。当NAND 驱动被加载的时候,它会去读取具体NAND 芯片的ID ,然后根据读取的内容到上述定义的nand_manuf_ids[ ] 和nand_flash_ids[ ] 两个结构体中去查找,以此判断该NAND 芯片是那个厂商的产品,以及该NAND 芯片的类型。若查找不到,则NAND 驱动就会加载失败,因此在开发NAND 驱动前必须事先将你的NAND 芯片添加到这两个结构体中去(其实这两个结构体中已经定义了市场上绝大多数的NAND 芯片,所以除非你的NAND 芯片实在比较特殊,否则一般不需要额外添加)。

     值得一提的是,nand_flash_ids[ ] 中有三项属性比较重要,即pagesize 、chipsize 和erasesize ,驱动就是依据这三项属性来决定对NAND 芯片进行擦除,读写等操作时的大小的。其中pagesize 即NAND 芯片的页大小,一般为256 、512 或2048 ;chipsize 即NAND 芯片的容量;erasesize 即每次擦除操作的大小,通常就是NAND 芯片的block 大小。 
4)nand_ecc.c : 
      定义了NAND 驱动中与softeware ECC 有关的函数和结构体,若你的系统支持hardware ECC ,且不需要software ECC ,则该文件也不需理会。 
5)nandsim.c : 
      定义了Nokia 开发的模拟NAND 设备,默认是Toshiba NAND 8MiB 1,8V 8-bit (根据ManufactureID ),开发普通NAND 驱动时不用理会。 
6)diskonchip.c : 
      定义了片上磁盘(DOC) 相关的一些函数,开发普通NAND 驱动时不用理会。
除了上述六个文件之外,nand 目录中其他文件基本都是特定系统的NAND 驱动程序例子,但看来真正有参考价值的还有cafe_nand.c 和s3c2410.c 两个,而其中又尤以cafe_nand.c 更为详细,另外,nand 目录中也似乎只有cafe_nand.c 中的驱动程序在读写NAND 芯片时用到了DMA 操作。

在我们开始NAND 驱动编写之前,至少应该知道:数据在NAND 中是怎样存储的,以及以怎样的方式从NAND 中读写数据的。

                             
1,NAND 的存储结构和操作方式 
      这方面的资料可以从任意一种NAND 的datasheet 中得到,而且事实上,大多数的NAND datasheet 都大同小异,所不同的大概只是该NAND 芯片的容量大小和读写速度等基本特性。 
      这里以每页512 字节的NAND FLASH 为例简单说明一下:每一块NAND 芯片由n 个block 组成-> 每一个block 由m 个page 组成-> 每一个page 由256 字节大小的column1( 也称1st half page) 、256 字节大小的column2( 也称2nd half page) 和16 字节大小的oob(out-of-band ,也称spare area) 组成。至于m 和n 的大小可以查看特定NAND 的datasheet 。相应的,若给定NAND 中的一个字节的地址,我们可以根据这个地址算出block 地址( 即第几个block) 、page 地址( 即该block 中的第几个page) 和column 地址( 即1st half page ,或2nd half page ,或oob 中的第几个字节) 。 
       在擦除NAND 时,必须每次至少擦除1 个block ;在写NAND 时,必须每次写1 个page( 有些NAND 也支持写不足一个page 大小的数据) ;在读NAND 时,分为三种情况( 对应三种不同的NAND 命令) ,即读column1 、读column2 和读oob ,那么为什么要分这三种情况呢?这是因为:基于MTD 的NAND 驱动在读写NAND 时,可以分两种情况,即:(1) 不进行ECC 检测时,一次读写一整个page 中的MAIN 部分( 也就是那真实存储数据的512 字节) ;(2) 进行ECC 检测时( 不管是hardware ECC 还是software ECC) ,一次读写一整个page( 包括16 字节的oob 部分) 。所以部分NAND 所支持的写不足一个page 大小数据的功能,对MTD 来说是用不着的。 
      那么,如果只需要读写不足一个page 大小的数据怎么办?这是MTD 更上层的部分需要处理的事。也就是说,对于NAND 驱动来说,它只会读写整整一个page 的数据! 
      最后值得一提的是,NAND 驱动有可能只去读oob 部分,这是因为除了ECC 信息之外,坏块信息也存储在oob 之中,NAND 驱动需要读取oob 中描述坏块的那个字节( 通常是每个block 的第一个page 的oob 中的第六个字节) 来判断该block 是不是一个坏块。所以,我们只有在读oob 时,才需要实现精确到读某一个byte 地址的函数。由此,我们也可以额外知道一件事,那就是NAND 驱动中用到的column 地址只在读oob 时才有用,而在其他情况下,column 地址都为0 。

2,ECC 相关的结构体 
struct nand_ecclayout { 
           uint32_t eccbytes; 
           uint32_t eccpos[64]; 
           uint32_t oobavail; 
           struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES]; 
}; 
这是用来定义ECC 在oob 中布局的一个结构体。 
      前面已经提及过,oob 中主要存储两种信息:坏块信息和ECC 数据。对与small page 的NAND 芯片来说,其中坏块信息占据1 个字节( 一般固定在第六个字节) ,ECC 数据占据三个字节。所以sturct nand_ecclayout 这个结构体,也就是用来告诉那些与ECC 操作无关的函数,Nand 芯片的oob 部分中,哪些字节是用来存储ECC 的( 即不可用作它用的) ,哪些字节是空闲的,即可用的。 
      其实之所以有这个结构体,主要是因为硬件ECC 的缘故。以写数据为例,在使用硬件ECC 的情况下,那三个字节的ECC 数据是由硬件计算得到,并且写到NAND 芯片的oob 中去的,同时也是由硬件决定写到oob 的哪三个字节中去。这些都是由硬件做的,而NAND 驱动并不知道,所以就需要用这个结构体来告诉驱动了。 
      所以,在写NAND 驱动时,就有可能需要对这个结构体进行赋值。这里说“有可能”,是因为MTD 对这个结构体有一个默认的赋值,假如这个赋值所定义的ECC 位置与你的硬件一致的话,那就不必在你的驱动中手动赋值了。其实对大多数硬件( 这里所说的硬件,不是指NAND 芯片,而是NAND 控制器) 来说,是不必手动赋值的,但也有许多例外。 
      现在对struct nand_ecclayout 这个结构体进行一下说明。 
uint32_t eccbytes :ECC 的字节数,对于512B-per-page 的NAND 来说,eccbytes = 3 ,如果你需要额外用到oob 中的数据,那么也可以大于3. 
uint32_t eccpos[64] :ECC 数据在oob 中的位置,这里之所以是个64 字节的数组,是因为对于2048-per-page 的NAND 来说,它的oob 有64 个字节。而对于512B-per-page 的NAND 来说,可以而且只可以定义它的前16 个字节。 
uint32_t oobavail :oob 中可用的字节数,这个值不用赋值,MTD 会根据其它三个变量自动计算得到。 
struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES] :显示定义空闲的oob 字节。

1,platform_driver 的定义和注册

      在s3c_nand.c中,

static struct platform_driver s3c6410_nand_driver = {
                .probe  = s3c6410_nand_probe,
                .remove  = s3c_nand_remove,
                .suspend = s3c_nand_suspend,
                .resume  = s3c_nand_resume,
                .driver  = {
                       .name = "s3c6410-nand",
                       .owner = THIS_MODULE,
                 },
      };

      static int __init s3c_nand_init(void)
     {
             printk("S3C NAND Driver, (c) 2008 Samsung Electronics\n");
             return platform_driver_register(&s3c6410_nand_driver);
      }

     module_init(s3c_nand_init);
     module_exit(s3c_nand_exit);

与大多数嵌入式Linux 驱动一样,NAND 驱动也是从module_init 宏开始。s3c_nand_init 是驱动初始化函数,在此函数中注册platform driver 结构体,platform driver 结构体中自然需要定义probe 和remove 函数。其实在大多数嵌入式Linux 驱动中,这样的套路基本已经成了一个定式。
      至于module_init 有什么作用,s3c_nand_init 又是何时调用的,以及这个driver 是怎么和NAND 设备联系起来的,就不再多说了,这里只提三点:
A)以上代码只是向内核注册了NAND 的platform_driver ,即s3c_nand_init ,我们当然还需要一个NAND 的platform_device ,要不然s3c_nand_init的probe 函数就永远不会被执行,因为没有device 需要这个driver 。

B)向Linux 内核注册NAND 的platform_device 有两种方式:其一是直接定义一个NAND 的platform_device 结构体,然后调用platform_device_register 函数注册。其二是用platform_device_alloc 函数动态分配一个platform_device ,然后再用platform_device_add 函数把这个platform_device 加入到内核中去。相对来说,第一种方式更加方便和直观一点,而第二种方式则更加灵活一点。

C)在加载NAND 驱动时,我们还需要向MTD Core 提供一个信息,那就是NAND 的分区信息。

2,platform_device 的定义和注册

     在本地代码上,platform_device是这样注册的:
     A)定义了分区表:

struct mtd_partition s3c_partition_info[] = {
       {
                .name  = "misc",
                .offset  = (768*SZ_1K),  /* for bootloader */
                .size  = (256*SZ_1K),
                .mask_flags = MTD_CAP_NANDFLASH,
        },
        {
                .name  = "recovery",
                .offset  = MTDPART_OFS_APPEND,
                .size  = (5*SZ_1M),
//                .mask_flags = MTD_CAP_NANDFLASH,
        },
        {
                .name  = "kernel",
                .offset  = MTDPART_OFS_APPEND,
                .size  = (3*SZ_1M),
        },
        {
                .name  = "ramdisk",
                .offset  = MTDPART_OFS_APPEND,
                .size  = (1*SZ_1M),
        },
        {
                .name  = "system",
                .offset  = MTDPART_OFS_APPEND,
                .size  = (67*SZ_1M),
        },
        {
                .name  = "cache",
                .offset  = MTDPART_OFS_APPEND,
                .size  = (67*SZ_1M),
        },
        {
                .name  = "userdata",
                .offset  = MTDPART_OFS_APPEND,
                .size  = MTDPART_SIZ_FULL,
        }

};

      其中offset是分区开始的偏移地址,MTDPART_OFS_APPEND,表示紧接着上一个分区,MTD Core会自动计算和处理分区地址;size是分区的大小,在最后一个分区我们设为MTDPART_SIZ_FULL,表示这个NAND剩下的所有部分。这样配置NAND的分区并不是唯一的,需要视具体的系统而定,我们可以在kernel中这样显式的指定,也可以使用bootloader传给内核的参数进行配置。
     B)struct s3c_nand_mtd_info s3c_nand_mtd_part_info = {
             .chip_nr = 1,
             .mtd_part_nr = ARRAY_SIZE(s3c_partition_info),
             .partition = s3c_partition_info,
          };
     C)填充s3c_device_nand.dev.platform_data = &s3c_nand_mtd_part_info;
     D)s3c_device_nand是struct platform_device *smdk6410_devices[]的一个成员,然后platform_add_devices(smdk6410_devices, ARRAY_SIZE(smdk6410_devices));一起加载全部的platform_device(与platform_device_add相比前者的好处是可以一次加载多个device)

3,对分区的加载应用
      一个MTD原始设备可以通过mtd_part分割成数个MTD原始设备注册进mtd_table,mtd_table中的每个MTD原始设备都可以被注册成一个MTD设备,有两个函数可以完成这个工作,即 add_mtd_device函数和add_mtd_partitions函数。其中add_mtd_device函数是把整个NAND FLASH注册进MTD Core,而add_mtd_partitions函数则是把NAND FLASH的各个分区分别注册进MTD Core。其中master就是这个MTD原始设备,parts即NAND的分区信息,nbparts指有几个分区。
      这个可以从s3c_nand_probe中的add_mtd_partitions(s3c_mtd, partition_info, plat_info->mtd_part_nr);获得,其中的partition_info就是3c_partition_info,第三个参数就是ARRAY_SIZE(s3c_partition_info)。

MTD对NAND芯片的读写主要分三部分:

                  
    A、struct mtd_info中的读写函数,如read,write_oob等,这是MTD原始设备层与FLASH硬件层之间的接口; 
    B、struct nand_ecc_ctrl中的读写函数,如read_page_raw,write_page等,主要用来做一些与ecc有关的操作; 
    C、struct nand_chip中的读写函数,如read_buf,cmdfunc等,与具体的NAND controller相关,就是这部分函数与硬件交互,通常需要我们自己来实现。值得一提的是,struct nand_chip中的读写函数虽然与具体的NAND controller相关,但是MTD也为我们提供了default的读写函数,如果你的NAND controller比较通用(使用PIO模式),对NAND芯片的读写与MTD提供的这些函数一致,就不必自己实现这些函数了。

      本地代码上我们所有的实现函数都在s3c_nand.c中,

  nand->cmd_ctrl  = s3c_nand_hwcontrol;

  nand->ecc.hwctl  = s3c_nand_enable_hwecc;
  nand->ecc.calculate = s3c_nand_calculate_ecc;
  nand->ecc.correct = s3c_nand_correct_data;

  nand->ecc.read_page = s3c_nand_read_page_4bit;
  nand->ecc.write_page = s3c_nand_write_page_4bit;
       以读NAND芯片为例,讲解一下这三部分读写函数的工作过程。首先,MTD上层会调用struct mtd_info中的读page函数,即nand_read函数。

(1)接着nand_read函数会调用struct nand_chip中cmdfunc函数,这个cmdfunc函数与具体的NAND controller相关,它的作用是使NAND controller向NAND 芯片发出读命令,NAND芯片收到命令后,就会做好准备等待NAND controller下一步的读取。 
(2)或者nand_read函数又会调用struct nand_ecc_ctrl中的read_page函数,而read_page函数又会调用struct nand_chip中read_buf函数,从而真正把NAND芯片中的数据读取到buffer中(所以这个read_buf的意思其实应该是read into buffer,另外,这个buffer是struct mtd_info中的nand_read函数传下来的)。 

      read_buf函数返回后,read_page函数就会对buffer中的数据做一些处理,比如校验ecc,以及若数据有错,就根据ecc对数据修正之类的,最后read_page函数返回到nand_read函数中。  
      对NAND芯片的其它操作,如写,擦除等,都与读操作类似。

阅读(876) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~