进过前面对NANDFLASH的一些硬件特性以及MTD的上层操作已经有了一个大体概念,这些东西的重要性就像你要吃饭那么你首先得学会拿筷子道理一样吧,应该一样的。
五、MTD原始设备层和硬件驱动层的桥梁:
熟悉这几个重要的结构体:linux/mtd/mtd.h
struct mtd_info {
u_char type; /**内存技术类型(包括MTD_RAM,MTD_ROM,MTD_NANDFLASH等)**/
uint32_t flags; /*MTD设备属性标志*/
uint64_t size; // Total size of the MTD
uint32_t erasesize;//MTD设备的擦除单元大小,对于NandFlash来说就是Block的大小
uint32_t writesize;//最小的可写单元的字节数
uint32_t oobsize; // Amount of OOB data per block (e.g. 16)
uint32_t oobavail; // Available OOB bytes per block
unsigned int erasesize_shift;
unsigned int writesize_shift;
/* Masks based on erasesize_shift and writesize_shift */
unsigned int erasesize_mask;
unsigned int writesize_mask;
// Kernel-only stuff starts here.
const char *name;
int index;
/* ecc layout structure pointer - read only ! */
struct nand_ecclayout *ecclayout;
/* Data for variable erase regions. If numeraseregions is zero,
* it means that the whole device has erasesize as given above.
*一般为1吧
*/
int numeraseregions;
//擦除区域的指针
struct mtd_erase_region_info *eraseregions;
//擦除函数将一个erase_info结构放入擦除队列中
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
/* This stuff for eXecute-In-Place */
/* phys is optional and may be set to NULL */
int (*point) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys);
/* We probably shouldn't allow XIP if the unpoint isn't a NULL */
void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
/* Allow NOMMU mmap() to directly map the device (if not NULL)
* - return the address to which the offset maps
* - return -ENOSYS to indicate refusal to do the mapping
*/
unsigned long (*get_unmapped_area) (struct mtd_info *mtd,
unsigned long len,
unsigned long offset,
unsigned long flags);
/* Backing device capabilities for this device
* - provides mmap capabilities
*/
struct backing_dev_info *backing_dev_info;
//read和write分别用于MTD设备的读和写
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);
int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
//读写MTD设备的OOB区域的数据
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);
//访问一些受保护的寄存器
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);
/* kvec-based read/write methods.
NB: The 'count' parameter is the number of _vectors_, each of
which contains an (ofs, len) tuple.
*/
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
/* Sync *//*同步*/
void (*sync) (struct mtd_info *mtd);
/* Chip-supported device locking */
int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
/* Power Management functions */
int (*suspend) (struct mtd_info *mtd);
void (*resume) (struct mtd_info *mtd);
/* Bad block management functions */
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; /* default mode before reboot */
/* ECC status information */
struct mtd_ecc_stats ecc_stats;
/* Subpage shift (NAND) */
int subpage_sft;
//私有数据 指向map_info结构
void *priv;
struct module *owner;
struct device dev;
int usecount;
//设备驱动回调函数
int (*get_device) (struct mtd_info *mtd);
void (*put_device) (struct mtd_info *mtd);
};
上面的read()、write()、read_oob()、等都是MTD设备驱动要实现的主要函数,不过这些函数都是透明的不需要我们自己去实现,因为Linux在MTD的下层实现了针对NORFLASH和NANDFLASH的通用mtd_info成员函数。
感觉没什么可写的了,因为这些都不是我要关注的东西,但是又不能不知道有这么回事
这些结构体还是得了解了解
driver/mtd/mtdpart.c
/* Our partition node structure */
struct mtd_part {
struct mtd_info mtd; //分区信息
struct mtd_info *master; //该分区的主分区
uint64_t offset; //该分区的偏移量
struct list_head list;
};
mtd_partition会在MTD原始设备调用add_mtd_partitions()的时候传递分区参数/linux/mtd/partition.h
struct mtd_partition {
char *name; /* identifier string */
uint64_t size; /* partition size */
uint64_t offset; /* offset within the master MTD space */
uint32_t mask_flags; /* master MTD flags to mask out for this partition */
struct nand_ecclayout *ecclayout; /* out of band layout for this partition (NAND only)*/
};
一个MTD原始设备可以通过mtd_part分割成数个MTD原始设备注册进mtd_table,mtd_table中的每个MTD原始设备都可以被注册成一个MTD设备,有两个函数可以完成这个工作,即add_mtd_device函数和add_mtd_partitions函数。
int add_mtd_device(struct mtd_info *mtd)
int del_mtd_device(struct mtd_info *mtd)
int add_mtd_partitions(struct mtd_info *master,structmtd_partitions *parts,int nbparts)
当MTD原始设备调用add_mtd_partitions()的时候它会对每一个新建分区建立一个struct mtd_part 结构将其加入mtd_partitions中并调用add_mtd_device()将此分区做为MTD设备注册进MTD Core,成功的时返回0
重点关注一下add_mtd_partitions(struct mtd_info *master,struct mtd_partitions*parts,int nbparts)其中master就是这个MTD原始设备,parts即NAND的分区信息,nbparts指有几个分区。那么parts和nbparts怎么来的呢,其实上面有一句话已经说了。。举个例子在我们移植Linux内核到我们的开发板的时候我们会对NANDFLASH进行分区 这eilian240_default_nand_part就是起到上面这两个参数的作用如下:
static struct mtd_partition eilian240_default_nand_part[] = {
[0] = {
.name = "bootloader",/*uboot存放的地址对应dev/mtdblock0*/
.size = 0x00040000, /*大小256KB=((D)(0x00040000))/1024*/
.offset = 0,
},
[1] = {
.name = "param",
.offset = 0x00040000,/***如果UBOOT比较大就放在这个区域可以将前面的覆盖掉**//**0x00040000是偏移量**/
.size = 0x00020000, /**128KB**/
},
[2] = {
.name = "Linux Kernel",/********用于存放Linux内核镜像dev/mtdblock3*/
.offset = 0x00060000,
.size = 0x00500000, /*5M*/
},
[3] = {
.name = "rootfs",
.offset = 0x00560000,
.size = 1024 * 1024 * 1024, //1G因为该移植同时支持大容量的NAND
},
[4] = {
.name = "nand",
.offset = 0x00000000,
.size = 1024 * 1024 * 1024, //
}
};
/*arch/arm/plat-samsung/include/plat/nand.h **/
Linux内核在MTD的下层实现了通用的NAND驱动(/driver/mtd/nand/nand_base.c)因此芯片级的驱动实现不再需要我们关心mtd中的那些成员函数了主题转移到nand_chip数据结构中
先了解了解nand_chip结构体
struct nand_chip {
void __iomem *IO_ADDR_R; //读8位I/O线的地址
void __iomem *IO_ADDR_W; //写8位I/O线的地址
uint8_t (*read_byte)(struct mtd_info *mtd);//从芯片读一个字节
u16 (*read_word)(struct mtd_info *mtd);//从芯片读一个字
void (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);//将缓冲区的数据写入芯片
void (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);//将芯片中的数据独到缓冲区中
int (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len); //验证芯片和写入缓冲区中的数据
int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);//检查是否坏块
int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);//标记坏块
void (*select_chip)(struct mtd_info *mtd, int chip); //实现选中芯片
void (*cmd_ctrl)(struct mtd_info *mtd, int dat,
unsigned int ctrl);//控制ALE/CLE/nCE,也用于写命令和地址
int (*dev_ready)(struct mtd_info *mtd);//设备就绪
void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr); //实现命令发送
int (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);
void (*erase_cmd)(struct mtd_info *mtd, int page);//擦除命令的处理
int (*scan_bbt)(struct mtd_info *mtd);//扫描坏块
int (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);
int (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,
const uint8_t *buf, int page, int cached, int raw);//写一页
unsigned int options;//与具体的NAND芯片相关的一些选项,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等,至于这些选项具体表示什么含义,可以参考<linux/mtd/nand.h>,那里有较为详细的说明;
int page_shift;//用位表示的NAND芯片的page大小,如某片NAND芯片的一个page有512个字节,那么page_shift就是9;
int phys_erase_shift;//用位表示的NAND芯片的每次可擦除的大小,如某片NAND芯片每次可擦除16K字节(通常就是一个block的大小),那么phys_erase_shift就是14;
int bbt_erase_shift;//用位表示的bad block table的大小,通常一个bbt占用一个block,所以bbt_erase_shift通常与phys_erase_shift相等;
int chip_shift;用位表示的NAND芯片的容量;
int numchips;表示系统中有多少片NAND芯片;
uint64_t chipsize;//NAND芯片的大小;
int pagemask;//计算page number时的掩码,总是等于chipsize/page大小- 1;
int pagebuf;用来保存当前读取的NAND芯片的page number,这样一来,下次读取的数据若还是属于同一个page,就不必再从NAND芯片读取了,而是从data_buf中直接得到;
int subpagesize;
uint8_t cellinfo;
int badblockpos;
int badblockbits;
flstate_t state;
uint8_t *oob_poi;
struct nand_hw_control *controller;
struct nand_ecclayout *ecclayout;
struct nand_ecc_ctrl ecc;
struct nand_buffers *buffers;
struct nand_hw_control hwcontrol;
struct mtd_oob_ops ops;
uint8_t *bbt;
struct nand_bbt_descr *bbt_td;
struct nand_bbt_descr *bbt_md;
struct nand_bbt_descr *badblock_pattern;
void *priv;
};
这上面有一个与ECC相关的结构体 struct nand_ecc_ctrl
struct nand_ecc_ctrl {
nand_ecc_modes_t mode;
int steps;
int size;
int bytes;
int total;
int prepad;
int postpad;
struct nand_ecclayout *layout;
void (*hwctl)(struct mtd_info *mtd, int mode);/**控制硬件ECC*/
int (*calculate)(struct mtd_info *mtd,
const uint8_t *dat,
uint8_t *ecc_code);/**根据data计算ecc值**/
int (*correct)(struct mtd_info *mtd, uint8_t *dat,
uint8_t *read_ecc,
uint8_t *calc_ecc);
int (*read_page_raw)(struct mtd_info *mtd,
struct nand_chip *chip,
uint8_t *buf, int page);/**向NANDFLASH芯片读一个页的原始数据*/
void (*write_page_raw)(struct mtd_info *mtd,
struct nand_chip *chip,
const uint8_t *buf);
int (*read_page)(struct mtd_info *mtd,
struct nand_chip *chip,
uint8_t *buf, int page);/**读一个页但包含ecc校验*/
int (*read_subpage)(struct mtd_info *mtd,
struct nand_chip *chip,
uint32_t offs, uint32_t len,
uint8_t *buf);
void (*write_page)(struct mtd_info *mtd,
struct nand_chip *chip,
const uint8_t *buf);
int (*read_oob)(struct mtd_info *mtd,
struct nand_chip *chip,
int page,
int sndcmd);/**读OOB但不包含MAIN部分**/
int (*write_oob)(struct mtd_info *mtd,
struct nand_chip *chip,
int page);
};
下面这个结构体用来ECC在oob中布局的一个结构体。
struct nand_ecclayout {
__u32 eccbytes;//ecc字节数,对于512字节/page的NANDflash,eccbytes=3,如果需要额外用到oob中的数据,那么也可以大于3.
__u32 eccpos[64];ECC数据在oob中的位置,这里之所以是个64字节的数组,是因为对于2K/页的NAND来说,它的oob有64个字节。而对于512字节/page的NAND来说,可以而且只可以定义它的前16个字节。
__u32 oobavail;OOB中可用的字节数,不需要对该成员赋值,MTD会根据其他三个成员计算出来
struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];//显示定义空闲的OOB字节
};
大家都知道OOB但是OOB里面究竟存了些什么,OOB主要用来存储两种信息:坏块信息和ECC数据,对于小页的NANDFLASH一般坏块占据一个字节(并且是在第6个字节),ECC占3个字节,上面这个结构体就是起到了这个作用告诉那些与操作ECC无关的函数,在OOB区域里那部分是来存储ECC的(不可他用),那些字节是空闲的。