加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

基于MTD的NANDFLASH设备驱动底层实现原理分析 二

发布时间:2020-12-15 18:03:50 所属栏目:百科 来源:网络整理
导读:进过前面对NANDFLASH的一些硬件特性以及MTD的上层操作已经有了一个大体概念,这些东西的重要性就像你要吃饭那么你首先得学会拿筷子道理一样吧,应该一样的。 五、MTD原始设备层和硬件驱动层的桥梁: ???? 熟悉这几个重要的结构体:linux/mtd/mtd.h struct mtd_

进过前面对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,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,size_t *retlen,u_char *buf);
?? ?int (*write) (struct mtd_info *mtd,loff_t to,const u_char *buf);

?? ?int (*panic_write) (struct mtd_info *mtd,const u_char *buf);
??? //读写MTD设备的OOB区域的数据
?? ?int (*read_oob) (struct mtd_info *mtd,
?? ??? ??? ? struct mtd_oob_ops *ops);
?? ?int (*write_oob) (struct mtd_info *mtd,
?? ??? ??? ? 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,u_char *buf);
?? ?int (*get_user_prot_info) (struct mtd_info *mtd,size_t len);
?? ?int (*read_user_prot_reg) (struct mtd_info *mtd,u_char *buf);
?? ?int (*write_user_prot_reg) (struct mtd_info *mtd,u_char *buf);
?? ?int (*lock_user_prot_reg) (struct mtd_info *mtd,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,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,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函数。

其中add_mtd_device函数是把整个NAND?FLASH注册进MTD Core,而add_mtd_partitions函数则是把NAND?FLASH的各个分区分别注册进MTD Core。

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)

int del_mtd_ partitions(struct mtd_info *master ) 当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,//
?? ?}
};
/*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,int len); //验证芯片和写入缓冲区中的数据

?? int ? ? ? ? (*block_bad)(struct mtd_info *mtd,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);//写一页

??? int?? ??? ?chip_delay;//有板决定的延迟时间
?? 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,
?? ??? ??? ??? ??? ????? 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,
?? ??? ??? ??? ??? ????? int page);
};


hwctl:这个函数用来控制硬件产生ecc,其实它主要的工作就是控制NAND controller向NAND芯片发出NAND_ECC_READ、NAND_ECC_WRITE和NAND_ECC_READSYN等命令,与struct nand_chip结构体中的cmdfunc类似,只不过发起的命令是ECC相关的罢了;
?
calculate:根据data计算ecc值;
correct:根据ecc值,判断读写数据时是否有错误发生,若有错,则立即试着纠正,纠正失败则返回错误;
read_page_raw和write_page_raw:从NAND芯片中读取一个page的原始数据和向NAND芯片写入一个page的原始数据,所谓的原始数据,即不对读写的数据做ecc处理,该读写什么值就读写什么值。另外,这两个函数会读写整个page中的所有内容,即不但会读写一个page中MAIN部分,还会读写OOB部分。
read_page和write_page:与read_page_raw和write_page_raw类似,但不同的是,read_page和write_page在读写过程中会加入ecc的计算,校验,和纠正等处理。
read_oob和write_oob:读写oob中的内容,不包括MAIN部分。
其实,以上提到的这几个read_xxx和write_xxx函数,最终都会调用struct nand_chip中的read_buf和write_buf这两个函数,所以如果没有特殊需求的话,我认为不必自己实现,使用MTD提供的default的函数即可。

下面这个结构体用来ECC在oob中布局的一个结构体。

struct nand_ecclayout {
?? ?__u32 eccbytes;//ecc字节数,对于512字节/page的NANDflash,eccbytes=3,如果需要额外用到oob中的数据,那么也可以大于3.
?? ?__u32 eccpos[64];ECC数据在oob中的位置,这里之所以是个64字节的数组,是因为对于2K/页NAND来说,它的oob64个字节。而对于512字节/pageNAND来说,可以而且只可以定义它的前16个字节。
?? ?__u32 oobavail;OOB中可用的字节数,不需要对该成员赋值,MTD会根据其他三个成员计算出来
?? ?struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];//显示定义空闲的OOB字节
};

大家都知道OOB但是OOB里面究竟存了些什么,0)">OOB主要用来存储两种信息:坏块信息和ECC数据,对于小页的NANDFLASH一般坏块占据一个字节(并且是在第6个字节),ECC占3个字节,上面这个结构体就是起到了这个作用告诉那些与操作ECC无关的函数,在OOB区域里那部分是来存储ECC的(不可他用),那些字节是空闲的。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读