在这里补充这一片是为了更好的理解上一篇,补充的内容包括:MTD下NAND的布局中几个重要文件的诠释和MTD涉及的几个重要的结构体(更好的理解接口)
一、内核中的NAND代码布局
在Linux 内核中,MTD 源代码放在/driver/mtd 目录中,该目录中包含chips 、devices 、maps 、nand 、onenand 和ubi 六个子目录。其中只有nand 和onenand 目录中的代码才与NAND 驱动相关,不过nand 目录中的代码比较通用,而onenand 目录中的代码相对于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 和jz4780_nand.c 两个,而其中又尤以cafe_nand.c 更为详细,另外,nand 目录中也似乎只有cafe_nand.c 中的驱动程序在读写NAND 芯片时用到了DMA 操作。
芯片级驱动需要实现nand_chip结构体
MTD使用nand_chip来表示一个NAND FLASH芯片, 该结构体包含了关于Nand Flash的地址信息,读写方法,ECC模式,硬件控制等一系列底层机制。
二、MTD下NAND所涉及的几个重要的结构体
1、mtd_info数据结构。(重要的结构体之一)mtd_info结构是MTD原始设备层的一个重要结构,表示MTD原始设备的结构,该结构定义了大量的关于MTD的数据和操作,定义在include/linux/mtd/mtd.h头文件。mtd_info结构成员主要由数据成员和操作函数两部分组成。
每个分区也被实现为一个mtd_info,如果有两个MTD原始设备,每个上有三个分区,在系统中就一共有6个mtd_info结构,这些mtd_info的指针被存放在名为mtd_table的数组里.
要强调的是:包含的这些函数指针指向的函数是MTD驱动提供的接口函数(每一个驱动程序的最终目的就是提供一些接口函数实现对底层硬件设备的操作),这些函数是在整个MTD驱动框架中层次最高的函数,他们可以在应用程序中直接调用实现对MTD设备底层硬件的操作。如:static int nand_read_ecc ()。它实现了对NAND flash的读操作。这些函数一般是都在u-boot源码目录下的driver/nand/nand_base.c中实现的通用操作函数。MTD驱动保证这些通用的操作函数支持对各种NAND flash芯片的操作。
struct mtd_info {
u_char type;
//内存技术类型,例如MTD_RAM,MTD_ROM,MTD_NORFLASH,MTD_NAND_FLASH等
u_int32_t flags;
//标志位,MTD设备的性能描述
u_int32_t size;
//MTD设备的大小
u_int32_t erasesize;
//最小的擦除块大小
u_int32_t writesize;
//编程大小
u_int32_t oobsize;
//oob(Out of band)块大小
u_int32_t ecctype;
//ECC校验类型
u_int32_t eccsize;
#define MTD_PROGREGION_CTRLMODE_VALID(mtd) (mtd)->oobsize
#define MTD_PROGREGION_CTRLMODE_INVALID(mtd) (mtd)->ecctype
char *name;
int index;
struct nand_ecclayout *ecclayout;
//eec布局结构
int numeraseregions;
//擦除区域个数,通常为1
struct mtd_erase_region_info *eraseregions;
//擦除区域的区域信息地址
u_int32_t bank_size;
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
//函数指针,erase函数的功能是将一个erase_info加入擦除队列
int (*point) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf);
//point函数功能是允许片内执行(XIP)
void (*unpoint) (struct mtd_info *mtd, u_char * addr, loff_t from, size_t len);
//unpoint函数与point函数相反,是禁止片内执行(XIP)
int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
//MTD设备的读写函数
int (*read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);
//用于MTD设备的OBB数据读写
int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
//访问保护寄存器区
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
//基于kevc的读写方法
void (*sync) (struct mtd_info *mtd);
//MTD设备的同步函数
int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len);
int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len);
//芯片的加锁和解锁
int (*suspend) (struct mtd_info *mtd);
void (*resume) (struct mtd_info *mtd);
//支持电源管理函数
int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);
//坏块管理函数
struct notifier_block reboot_notifier;
struct mtd_ecc_stats ecc_stats; //ECC状态信息
void *priv;
//私有数据指针 ,这个指针指向MTD驱动中另一个重要的数据结构struct nand_chip
struct module *owner;
int usecount;
};
2、mtd_part结构体信息
mtd_part(mtd_part.c)是用于表示 MTD 原始设备分区的结构,其中包含了 mtd_info,因为每一个分区都是被看成一个MTD 原始设备加在 mtd_table 中的,mtd_part.mtd_info中的大部分数据都从该分区的主分区 mtd_part->master中获得。
static LIST_HEAD(mtd_partitions);
//分区链表
/* Our partition node structure */
//分区结构信息
struct mtd_part {
struct mtd_info mtd; //mtd_info数据结构体,加入到mtd_table中
struct mtd_info *master;//该分区的主分区
uint64_t offset;//该分区的偏移量
struct list_head list;
};
3、mtd_notifier结构体
mtd_notifier:MTD通知器,加入/删除MTD设备和原始设备时调用的函数,在设备层,当MTD字符设备或块设备注册时,如果定义了CONFIG_DEVFS_FS,则会将一个mtd_notifier加入MTD原始设备层的mtd_notifiers链表,其中的函数会在两种情况下被调用,一是加入/删除新的MTD字符/块设备时,此时调用该MTD字符/块设备的notifier对下层所有的MTD原始设备操作一遍,二是加入/删除新的MTD原始设备时,此时调用所有的notifier对该原始设备执行一遍。
//MTD设备通知结构体
struct mtd_notifier {
void (*add)(struct mtd_info *mtd);
//加入MTD原始/字符/块设备时执行
void (*remove)(struct mtd_info *mtd);
//移除MTD原始/字符/块设备时执行
struct list_head list;
//list是双向链表,定义在include/linux/list.h
};
4、mtd_fops 结构体
在/drivers/mtdchar.c 字符型mtd设备中:
字符设备中定义了mtd_fops字符类的文件指针操作函数,完成字符设备读、写、打开、关闭等功能。
static const struct file_operations mtd_fops = {
.owner = THIS_MODULE,
.llseek = mtdchar_lseek,
.read = mtdchar_read,
.write = mtdchar_write,
.unlocked_ioctl = mtdchar_unlocked_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = mtdchar_compat_ioctl,
#endif
.open = mtdchar_open,
.release = mtdchar_close,
.mmap = mtdchar_mmap,
#ifndef CONFIG_MMU
.get_unmapped_area = mtdchar_get_unmapped_area,
#endif
};
5、mtd_table
mtd_table(mtdcore.c)则是所有 MTD 原始设备的列表
struct mtd_info *mtd_table[MAX_MTD_DEVICES];
最多可以有MAX_MTD_DEVICES(默认定义为16)个设备,每个MTD 分区也算一个MTD 设备。
add_mtd_device添加MTD原始设备到mtd_table中
del_mtd_device从mtd_table中删除MTD原始设备
上面两个函数是底层驱动(nand flash)用来添加/删除其设置好的mtd_info结构。
6、nand_chip(重要结构体之二)
这个数据结构从结构名nand_chip就可以知道它主要针对MTD设备中NAND flash的描述。在这个数据结构中包含了许多参数和函数指针。其中的成员大致可以分为以下几类:
1、 与芯片有关的参数
如:page_shift、phys_erase_shift、bbt_erase_shift、chip_shift、chipsize、numchips
2、与坏块管理、ECC校验及oob区管理有关的参数:
如:eccmode、eccsize、eccbyte、eccsteps、calculate_ecc、oob_buf、oobdirty、autooob、bbt、badblockpos、bbt_td、bbt_md、badblock_pattern
3、与NAND flash控制器寄存器操作有关的参数
如:IO_ADDR_R、IO_ADDR_W
4、与NAND flash控制器寄存器操作有关的函数指
如:read_byte、write_byte、read_word、write_word、hwcontrol、dev_ready、cmdfunc、select_chip
5、与NAND flash操作功能有关的函数指针
如:write_buf、read_buf、verify_buf、waitfunc、erase_cmd
6、与坏块管理、ECC校验及oob区管理有关的函数指针:
如:block_bad、block_markbad、calculate_ecc、correct_data、enable_hwecc、scan_bbt
综上,nand_chip主要包括了MTD驱动的低层和底层硬件操作函数的函数指针以及坏块管理、ECC校验和oob区管理需要相关函数的函数指针,这些指针指向的函数一部分在源码目录下的driver/nand/nand_base.c定义为通用函数,一部分需要用户在移植时自行编写。那些需要用户自行编写在后续的分析将会提到。nand_chip还包括了一些参数,这些参数与具体芯片型号及坏块管理策略有关,它们会在nand_scan()函数中被初始化。
struct nand_chip的结构声明
它在include/linux/mtd/nand.h中被声明定义,去掉一些编译选项、无关成员及原有注释,添加一些注释后如下;
struct nand_chip {
void __iomem *IO_ADDR_R; //NAND flash控制器的寄存器读访问指针,
void __iomem *IO_ADDR_W; //NAND flash控制器的寄存器写访问指针,
u_char (*read_byte)(struct mtd_info *mtd);
//写一字节到NAND flash控制器的寄存器函数的函数指针
void (*write_byte)(struct mtd_info *mtd, u_char byte);
//从NAND flash控制器的寄存器读一字节函数的函数指针
u16 (*read_word)(struct mtd_info *mtd); //读字函数指针
void (*write_word)(struct mtd_info *mtd, u16 word); //写字函数指针
//读写缓冲区函数指针,所谓缓冲区无非就是自行定义的一个数组
void (*write_buf)(struct mtd_info *mtd, const u_char *buf, int len);
void (*read_buf)(struct mtd_info *mtd, u_char *buf, int len);
int (*verify_buf)(struct mtd_info *mtd, const u_char *buf, int len);
//缓存校验韩式指针,验证从Flash中读取的内容是否与缓存中一致
void (*select_chip)(struct mtd_info *mtd, int chip);
//芯片片选函数指针,由于涉及底层寄存器操作该函数在移植时一般要自己编写
int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
//读取坏块标记函数指针
int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
//标记坏块函数指针
void (*hwcontrol)(struct mtd_info *mtd, int cmd);
//寄存器访问控制函数指针,该函数根据第二个参数cmd使O_ADDR_R/W指向不同的寄存器
int (*dev_ready)(struct mtd_info *mtd);
//设备状态读取函数指针,该函数读取NAND flash控制器的NFSTAT的bit0,获取R/B状态
void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);
//向NAND flash写一个命令字,如果形参column、page_addr不都为-1则还向NAND flash写一个地址
int (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this, int state);
//操作超时处理函数指针,该函数根据命令的不同设定不同的超时时间并时刻检查R/B位直到R/B状态进入Ready状态否则为操作超时。
int (*calculate_ecc)(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code);
//ECC编码计算函数指针,该函数通过软件算法计算256字节的3字节ECC编码
int (*correct_data)(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc);
//ECC检测纠正函数,该函数检测并纠正256字节块中的1位错误。
void (*enable_hwecc)(struct mtd_info *mtd, int mode);
//使能硬件计算ECC码函数指针
void (*erase_cmd)(struct mtd_info *mtd, int page);
//擦除指定块函数的函数指针
int (*scan_bbt)(struct mtd_info *mtd);
//坏块标记表创建函数指针
int eccmode;// ECC的计算方法, NAND_ECC_SOFT时意指软件运算,即采用calculate_ecc进行运算,利用correct_data进行校正
int eccsize;// ECC校准的数据长度,用calculate_ecc进行校准时,数据长度固定为256字节
int eccbytes;// ECC校验码字节数,软件校准时为3字节
int eccsteps;// ECC校验的步数,当FLASH每页的字节数为512字节,而calculate_ecc每次仅能校验256字节,则需要两步才能校验完成。
int chip_delay;// 等待时间,一般用于等待Flash的R/B管脚
int page_shift;// 页的地址移位数,当为512(2^9)字节的空间,其该值为9
int phys_erase_shift;// 块的地址移位数
int bbt_erase_shift;// 在bbt表中,相间隔两个内容之间的地址差的位数,也即是块的地址位数
int chip_shift;// 芯片总的地址位数
u_char *data_buf; //数据缓冲区指针
u_char *oob_buf;//oob缓冲区指针
int oobdirty;// 表示oob_buf内是否有内容,如为0表示oob_buf为空(0xFF),为1表示oob_buf中已有内容
u_char *data_poi;// 指向一个临时使用的数据区
unsigned int options;
//MTD驱动中配置NAND FALSH芯片的扩展功能,如NAND_IS_AND、NAND_USE_FLASH_BBT、NAND_COPYBACK等等和重要参数,如位宽NAND_BUSWIDTH_16。上述的这些宏在include/linux/mtd/nand.h均有定义,这些宏的定义能保证当它们进行或运算是能保证options能同时接受这些扩展功能和参数。及options某bit位为1时则使用对应的扩展功能。如有:
#define NAND_COPYBACK 0x00000010
那么如果options的bit4位为1则使用copyback功能,在MTD中大量使用了这种技巧,需要注意
int badblockpos;// 坏块标记的位置。当芯片为小页面NAND flashs时值为NAND_SMALL_BADBLOCK_POS(即为5);为大页面时值为NAND_BIG _BADBLOCK_POS(即为0)
int numchips;//开发板上NAND flash芯片的片数
unsigned long chipsize;// 芯片的容量
int pagemask;//页地址掩码
int pagebuf;// 存储Data_buf内的相关的页号
struct nand_oobinfo *autooob;
// oob区布局设计结构体指针。从功能上看,oob区和数据存储区一样可以存储数据,但是oob区一般不会用于存储数据,而是做为坏块标记和ECC校验数据存储。其中坏块标记位置一般有约定俗成的位置,而oob区的布局设计有很多不同的设计,MTD使用一个结构体来描述这种设计,oob区布局设计结构体在include/linux/mtd/mtd_abi.h中定义如下:
其中useecc 为使用ECC校验标志,eccbyte为ECC校验的位数,eccpos为ECC校验所存的位号,oobfree为OOB中ECC未使用到的字节(起始地址+长度)
uint8_t *bbt;// 指向内存中坏块表的指针
struct nand_bbt_descr *bbt_td;// 用以Flash中坏块表搜索的相关描述
struct nand_bbt_descr *bbt_md;// 上述描述的镜像
struct nand_hw_control *controller;// 用以实现互锁操作,此处并未使用
void *priv;//用途不明
};
MTD 使用 nand_chip 数据结构表示一个NAND Flash 芯片,这个结构体中包含了关于 NAND Flash 的地址信息、读写方法、ECC模式、硬件控制等一系列底层机制。
read_byte 和read_word :从NAND 芯片读一个字节或一个字,通常MTD 会在读取NAND 芯片的ID ,STATUS 和OOB 中的坏块信息时调用这两个函数,具体是这样的流程,首先MTD 调用cmdfunc 函数,发起相应的命令,NAND 芯片收到命令后就会做好准备,最后MTD 就会调用read_byte 或read_word 函数从NAND 芯片中读取芯片的ID ,STATUS 或者OOB ;
read_buf 、write_buf 和verify_buf:分别是从NAND 芯片读取数据到buffer、把buffer 中的数据写入到NAND 芯片、和从NAND 芯片中读取数据并验证。调用read_buf 时的流程与read_byte 和read_word 类似,MTD 也是先调用cmdfunc 函数发起读命令( 如NAND_CMD_READ0 命令) ,接着NAND 芯片收到命令后做好准备,最后MTD 再调用read_buf 函数把NAND 芯片中的数据读取到buffer 中。调用write_buf 函数的流程与read_buf 相似;
select_chip :因为系统中可能有不止一片NAND 芯片,所以在对NAND 芯片进行操作前,需要这个函数来指定一片NAND 芯片
cmdfunc :向NAND 芯片发起命令;
waitfunc :NAND 芯片在接收到命令后,并不一定能立即响应NAND controller 的下一步动作,对有些命令,比如erase ,program 等命令,NAND 芯片需要一定的时间来完成,所以就需要这个waitfunc 来等待NAND 芯片完成命令,并再次进入准备好状态;
write_page :把一个page 的数据写入NAND 芯片,这个函数一般不需我们实现,因为它会调用struct nand_ecc_ctrl 中的write_page_raw 或者write_page 函数,关于这两个函数将在稍后介绍。
以上提到的这些函数指针,都是REPLACEABLE 的,也就是说都是可以被替换的,根据你的NAND controller ,如果你需要自己实现相应的函数,那么只需要把你的函数赋值给这些函数指针就可以了,如果你没有赋值,那么MTD 会把它自己定义的default 的函数赋值给它们。在本地代码上,以上函数指针都采用的默认的方式,通过s3c_nand_probe-》nand_scan-》nand_scan_ident-》nand_set_defaults,在该函数中以上的函数指针都被nand_base.c定义的默认函数赋值。
八、数据结构struct nand_flash_dev分析
这个数据结构主要描述的是具体的NAND flash芯片型号。这个数据结构比较简单,它在include/linux/mtd/nand.h中被声明定义为一个结构体:
struct nand_flash_dev {
char *name; //NAND flash的名称
int id; //NAND flashd 的设备ID
unsigned long pagesize;//页面大小,单位为KB
unsigned long chipsize;//芯片容量,单位为MB
unsigned long erasesize;//擦除单位大小
unsigned long options;// NAND flashd的扩展功能配置及位宽配置
};
结构体struct nand_flash_dev在drivers/mtd/nand_ids.c中这个数据结构被定义为一个结构体数组并被初始化。其中包含的每一个数组元素即为MTD驱动支持的NAND flashd芯片型号。初始化如下:
struct nand_flash_dev nand_flash_ids[] = {
………………………………………………………….
{"NAND 128MiB 1,8V 16-bit", 0x72, 512, 128, 0x4000, NAND_BUSWIDTH_16},
{"NAND 128MiB 3,3V 16-bit", 0x74, 512, 128, 0x4000, NAND_BUSWIDTH_16},
/* 1 Gigabit */
{"NAND 128MiB 1,8V 8-bit", 0xA1, 0, 128, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
{"NAND 128MiB 3,3V 8-bit", 0xF1, 0, 128, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
/* 2 Gigabit */
{"NAND 256MiB 1,8V 8-bit", 0xAA, 0, 256, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
{"NAND 256MiB 3,3V 8-bit", 0xDA, 0, 256, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
/* 4 Gigabit */
{"NAND 512MiB 1,8V 8-bit", 0xAC, 0, 512, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
/* 8 Gigabit */
{"NAND 1GiB 1,8V 8-bit", 0xA3, 0, 1024, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
/* 16 Gigabit */
{"NAND 2GiB 1,8V 8-bit", 0xA5, 0, 2048, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
{NULL,}
};
要强调的是:移植MTD驱动时一定要确定要移植的NAND flash芯片在这里有对应的nand_flash_dev结构体,没有的话要自行添加。在nand_scan()中,会先读出芯片的设备ID再到该结构体数组中逐个对比,如果没有找到匹配项则会导致在nand_scan()初始化失败返回。