introduction

MTD(Memory Technology Device)即内存技术设备,在Linux内核中, 引入MTD层为NOR FLASH和NAND FLASH设备提供统一接口. MTD将文件系统与底层FLASH存储器进行了隔离.

skeleton

其中 :

  • Flash硬件驱动层 : Flash硬件驱动层负责对Flash硬件的读,写和擦除操作.MTD设备的Nand Flash芯片的驱动则drivers/mtd/nand/子目录下,Nor Flash芯片驱动位于drivers/mtd/chips/子目录下.
  • MTD原始设备层 : 用于描述MTD原始设备的数据结构是mtd_info,它定义了大量的关于MTD的数据和操作函数.其中mtdcore.c MTD原始设备接口相关实现,mtdpart.c: MTD分区接口相关实现.
  • MTD设备层: 基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90).其中mtdchar.c : MTD字符设备接口相关实现,mtdblock.c : MTD块设备接口相关实现.

Data Structure

mtd_info

mtd_info用于抽象mtd原始设备 , 其定义如下 :

struct mtd_info {
	u_char type;	     /* MTD类型,包括MTD_NORFLASH,MTD_NANDFLASH等(可参考mtd-abi.h) */
	uint32_t flags;	     /* MTD属性标志,MTD_WRITEABLE,MTD_NO_ERASE等(可参考mtd-abi.h) */
	uint64_t size;	     /* mtd设备的大小 */
	uint32_t erasesize;	 /* MTD设备的擦除单元大小,对于NandFlash来说就是Block的大小 */
	uint32_t writesize;	 /* 写大小, 对于norFlash是字节,对nandFlash为一页 */
	uint32_t oobsize;    /* OOB字节数 */
	uint32_t oobavail;   /* 可用的OOB字节数 */
	unsigned int erasesize_shift;	/* 擦除单元大小的2进制偏移 */
	unsigned int writesize_shift;	/* 写大小的2进制偏移 */
	unsigned int erasesize_mask;	/* 擦除大小的mask */
	unsigned int writesize_mask;	/* 写大小的mask */
	const char *name;				/* 名字 */
	int index;						/* 索引号 */
	int numeraseregions;			/* 通常为1 */
	struct mtd_erase_region_info *eraseregions;	/* 可变擦除区域 */
	
	void *priv;		/* 设备私有数据指针,对于NandFlash来说指nand_chip结构体 */
	struct module *owner;	/* 一般设置为THIS_MODULE */
	
	/* 擦除函数 */
	int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
 
	/* 读写flash函数 */
	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);
 
	/* 带oob读写Flash函数 */
	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);
 
	int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
	int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
	/* 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);
 
	/* 电源管理函数 */
	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);
 
	void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
	unsigned long (*get_unmapped_area) (struct mtd_info *mtd,
					    unsigned long len,
					    unsigned long offset,
					    unsigned long flags);
	struct backing_dev_info *backing_dev_info;
	struct notifier_block reboot_notifier;  /* default mode before reboot */
 
	/* ECC status information */
	struct mtd_ecc_stats ecc_stats;
	int subpage_sft;
	struct device dev;
	int usecount;
	int (*get_device) (struct mtd_info *mtd);
	void (*put_device) (struct mtd_info *mtd);
};

所有的mtd_info都通过 add_mtd_devicedel_mtd_device 接口向系统添加和删除 . 所有的mtd_info都通过 index 作为id在全局的 mtd_idr 中进行查找 .

mtd_part

如果一个mtd设备下挂载多个分区 , 每个分区由 mtd_part 进行抽象 , 其定义如下 :

struct mtd_partition {
	struct mtd_info mtd;
	struct mtd_info *master;
	uint64_t offset;
	struct list_head list;
};

其实每个分区也是一个 mtd_info , 而所有的分区都被挂在全局的 mtd_partitions 双向链表中 . 同时 , 为了表示设备和其下的分区 , 用于表示整个设备的 mtd_info 称为master , 而表示该设备下分区的 mtd_info 称为slave , 可以看到每个slave都有一个master指针 , 用于标示它所属的master设备 .

所有的分区都通过 mtd_add_partitionmtd_del_partition 接口向系统添加和删除 .

mtd char

drivers/mtd/mtdchar.c文件实现了MTD字符设备接口,通过它,可以直接访问Flash设备, 通过file_operations结构体里面的open()、read()、write()、ioctl()可以读写Flash, 通过一系列IOCTL 命令可以获取Flash 设备信息、擦除Flash、读写NAND 的OOB、获取OOB layout 及检查NAND 坏块等(MEMGETINFO、MEMERASE、MEMREADOOB、MEMWRITEOOB、MEMGETBADBLOCK IOCRL)

mtd block

drivers/mtd/mtdblock.c文件实现了MTD块设备接口,主要原理是将Flash的erase block 中的数据在内存中建立映射,然后对其进行修改,最后擦除Flash 上的block, 将内存中的映射块写入Flash 块.整个过程被称为read/modify/erase/rewrite 周期. 但是,这样做是不安全的,当下列操作序列发生时,read/modify/erase/poweroff,就会丢失这个block 块的数据.

flash driver

Linux内核在MTD层下实现了通用的NAND驱动(driver/mtd/nand/nand_base.c), 因此芯片级的NAND驱动不再需要实现mtd_info结构体中的read()、write()、read_oob()、write_oob()等成员函数.

MTD使用nand_chip来表示一个NAND FLASH芯片, 该结构体包含了关于Nand Flash的地址信息,读写方法,ECC模式,硬件控制等一系列底层机制.

其结构如下 :

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);
	/* 选中芯片 */
	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	(*cmd_ctrl)(struct mtd_info *mtd, int dat,unsigned int ctrl);
	/* 设备是否就绪 */
	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;			/* 由板决定的延迟时间 */
	/* 与具体的NAND芯片相关的一些选项,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等 */
	unsigned int	options;	
 
	/* 用位表示的NAND芯片的page大小,如某片NAND芯片
	 * 的一个page有512个字节,那么page_shift就是9 
	 */
	int		 page_shift;
	/* 用位表示的NAND芯片的每次可擦除的大小,如某片NAND芯片每次可
	 * 擦除16K字节(通常就是一个block的大小),那么phys_erase_shift就是14
	 */
	int		 phys_erase_shift;
	/* 用位表示的bad block table的大小,通常一个bbt占用一个block,
	 * 所以bbt_erase_shift通常与phys_erase_shift相等
 	 */
	int		 bbt_erase_shift;
	/* 用位表示的NAND芯片的容量 */
	int		 chip_shift;
	/* NADN FLASH芯片的数量 */
	int		 numchips;
	/* NAND芯片的大小 */
	uint64_t chipsize;
	int		 pagemask;
	int		 pagebuf;
	int		 subpagesize;
	uint8_t	 cellinfo;
	int		 badblockpos;
	nand_state_t	state;
	uint8_t		*oob_poi;
	struct nand_hw_control  *controller;
	struct nand_ecclayout	*ecclayout;	/* ECC布局 */
	
	struct nand_ecc_ctrl ecc;	/* ECC校验结构体,里面有大量的函数进行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;
};

initialization

flash驱动的初始化 , 主要分为两个阶段 , 第一个阶段主要用于探测颗粒 , 确定其型号 , block大小 , 页大小等一些基本信息 , 该阶段主要由 nand_scan_ident 接口实现 .

第二阶段主要是初始化一些驱动未定义的操作函数 (read_page, write_page等) , 同时扫描并初始化坏块表 , 该阶段主要由 nand_scan_tail 接口实现 .

nand_scan_ident

为了确定flash颗粒的型号 , 首先会读取该颗粒的id信息(总共8个字节) ,

static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
						  struct nand_chip *chip,
						  int *maf_id, int *dev_id,
						  struct nand_flash_dev *type)
{
	u8 id_data[8];
	
	/* Select the device */
	chip->select_chip(mtd, 0);

	/*
	 * Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx)
	 * after power-up.
	 */
	chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);

	/* Send the command for reading device ID */
	chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);

	...

	/* Read entire ID string */
	for (i = 0; i < 8; i++)
		id_data[i] = chip->read_byte(mtd);

接着 , 会在驱动支持的颗粒列表中查找 , 每个表项包含如下信息 :

struct nand_flash_dev {
	char *name;
	union {
		struct {
			uint8_t mfr_id;
			uint8_t dev_id;
		};
		uint8_t id[NAND_MAX_ID_LEN];
	};
	unsigned int pagesize;
	unsigned int chipsize;
	unsigned int erasesize;
	unsigned int options;
	uint16_t id_len;
	uint16_t oobsize;
	struct {
		uint16_t strength_ds;
		uint16_t step_ds;
	} ecc;
	int onfi_timing_mode_default;
};

当然 , 如果驱动程序不指定其支持的颗粒列表 , 那么会从kernel默认支持的列表( nand_flash_ids )中查找 :

if (!type)
	type = nand_flash_ids;

for (; type->name != NULL; type++) {
	if (is_full_id_nand(type)) {
		if (find_full_id_nand(mtd, chip, type, id_data, &busw))
			goto ident_done;
	} else if (*dev_id == type->dev_id) {
			break;
	}
}

如果成功找到对应的颗粒信息 , 则会初始化chip中的相关字段

nand_scan_tail

如果第一阶段成功 , 则会进入第二阶段 . 首先 , 会检查驱动是否初始化用于ecc计算和数据缓存的buffer , 如果没有 , 这里会申请对应大小的buffer :

int nand_scan_tail(struct mtd_info *mtd)
{
	struct nand_buffers *nbuf;

	if (!(chip->options & NAND_OWN_BUFFERS)) {
		nbuf = kzalloc(sizeof(*nbuf) + mtd->writesize
				+ mtd->oobsize * 3, GFP_KERNEL);
		if (!nbuf)
			return -ENOMEM;
		nbuf->ecccalc = (uint8_t *)(nbuf + 1);
		nbuf->ecccode = nbuf->ecccalc + mtd->oobsize;
		nbuf->databuf = nbuf->ecccode + mtd->oobsize;

		chip->buffers = nbuf;
	} else {
		if (!chip->buffers)
			return -ENOMEM;
	}

之后 ,如果驱动没有指定ecc的布局信息 , 这里会使用默认的布局信息 :

if (!ecc->layout && (ecc->mode != NAND_ECC_SOFT_BCH)) {
	switch (mtd->oobsize) {
	case 8:
		ecc->layout = &nand_oob_8;
		break;
	case 16:
		ecc->layout = &nand_oob_16;
		break;
	case 64:
		ecc->layout = &nand_oob_64;
		break;
	case 128:
		ecc->layout = &nand_oob_128;
		break;
	default:
		pr_warn("No oob scheme defined for oobsize %d\n",
			   mtd->oobsize);
		BUG();
	}
}

接着 , 根据ecc模式初始化未定义的函数操作指针 :

	switch (ecc->mode) {
	case NAND_ECC_HW_OOB_FIRST:
		...
		if (!ecc->read_page)
			ecc->read_page = nand_read_page_hwecc_oob_first;
	case NAND_ECC_HW:
		/* Use standard hwecc read page function? */
		if (!ecc->read_page)
			ecc->read_page = nand_read_page_hwecc;
		if (!ecc->write_page)
			ecc->write_page = nand_write_page_hwecc;
		...
	case NAND_ECC_HW_SYNDROME:
		...
		break;
	case NAND_ECC_SOFT:
		...
		break;
	case NAND_ECC_SOFT_BCH:
		...
		break;

	case NAND_ECC_NONE:
		pr_warn("NAND_ECC_NONE selected by board driver. This is not recommended!\n");
		ecc->read_page = nand_read_page_raw;
		ecc->write_page = nand_write_page_raw;
		ecc->read_oob = nand_read_oob_std;
		ecc->read_page_raw = nand_read_page_raw;
		ecc->write_page_raw = nand_write_page_raw;
		ecc->write_oob = nand_write_oob_std;
		ecc->size = mtd->writesize;
		ecc->bytes = 0;
		ecc->strength = 0;
		break;
	default:
		pr_warn("Invalid NAND_ECC_MODE %d\n", ecc->mode);
		BUG();
	}

之后还会初始化一些 mtd_info 操作函数 , 以及ecc信息 :

mtd->type = nand_is_slc(chip) ? MTD_NANDFLASH : MTD_MLCNANDFLASH;
mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :
					MTD_CAP_NANDFLASH;
mtd->_erase = nand_erase;
mtd->_point = NULL;
mtd->_unpoint = NULL;
mtd->_read = nand_read;
mtd->_write = nand_write;
mtd->_panic_write = panic_nand_write;
mtd->_read_oob = nand_read_oob;
mtd->_write_oob = nand_write_oob;
mtd->_sync = nand_sync;
mtd->_lock = NULL;
mtd->_unlock = NULL;
mtd->_suspend = nand_suspend;
mtd->_resume = nand_resume;
mtd->_reboot = nand_shutdown;
mtd->_block_isreserved = nand_block_isreserved;
mtd->_block_isbad = nand_block_isbad;
mtd->_block_markbad = nand_block_markbad;
mtd->writebufsize = mtd->writesize;

mtd->ecclayout = ecc->layout;
mtd->ecc_strength = ecc->strength;
mtd->ecc_step_size = ecc->size;

最后 , 如果驱动没有指定跳过坏块表扫描 , 则会扫描这个颗粒 , 建立坏块表 :

/* Check, if we should skip the bad block table scan */
if (chip->options & NAND_SKIP_BBTSCAN)
	return 0;

/* Build bad block table */
return chip->scan_bbt(mtd);
}

scan_bbt

坏块表保存在 nand_chip 的bbt字段中 , 其中 , 每个block的状态由2个bit表示 :

#define BBT_BLOCK_GOOD		0x00 // 好块
#define BBT_BLOCK_WORN		0x01 // 由于长时间使用导致的坏块
#define BBT_BLOCK_RESERVED	0x02
#define BBT_BLOCK_FACTORY_BAD	0x03 // 出厂时,厂商标记的坏块

如果一个block是坏块 , 会由一个坏块标记来标示 , 该标记一般每个block中第一个 (或最后一个)page的oob区域中 , 在软件层面是使用 nand_bbt_descr 来描述 :

struct nand_bbt_descr {
	int options;
	int offs; // pattern 在oob区域内的偏移
	int len; // pattern 的长度
	uint8_t *pattern;
	...
};

static uint8_t scan_ff_pattern[] = { 0xff, 0xff };

由于坏块表的建立需要扫描整个flash颗粒 , 为了加快该过程 , 软件可以将之前建立的坏块表存在flash中的固定位置 , 这样 , 下次扫描的时候就可以直接使用上次保存的坏块信息 , 这样就避免了 每次都扫描整个颗粒 . 这里我们暂且不考虑这个情况 , 默认每次都扫描整个可以.

下面我们来看默认的坏块扫描函数(nand_default_bbt). 首先 , 初始化bbt :

int nand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd)
{
	len = mtd->size >> (this->bbt_erase_shift + 2);
	/*
	 * Allocate memory (2bit per block) and clear the memory bad block
	 * table.
	 */
	this->bbt = kzalloc(len, GFP_KERNEL);
	if (!this->bbt)
		return -ENOMEM;

可以看到 , 这里默认所有的block都是好块 . 之后便是扫描整个所有的block的坏块标记 , 如果存在则将对应的标记位置为 BBT_BLOCK_FACTORY_BAD

for (i = startblock; i < numblocks; i++) {
	int ret;

	BUG_ON(bd->options & NAND_BBT_NO_OOB);

	ret = scan_block_fast(mtd, bd, from, buf, numpages);
	if (ret < 0)
		return ret;

	if (ret) {
		bbt_mark_entry(this, i, BBT_BLOCK_FACTORY_BAD);
		pr_warn("Bad eraseblock %d at 0x%012llx\n",
			i, (unsigned long long)from);
		mtd->ecc_stats.badblocks++;
	}

	from += (1 << this->bbt_erase_shift);
}

static inline void bbt_mark_entry(struct nand_chip *chip, int block,
		uint8_t mark)
{
	uint8_t msk = (mark & BBT_ENTRY_MASK) << ((block & BBT_ENTRY_MASK) * 2);
	chip->bbt[block >> BBT_ENTRY_SHIFT] |= msk;
}

read operation

flash驱动层对于read_page和read_oob封住了对应的接口, 分别为 nand_readnand_read_oob , 下面我们来看下这两个接口 .

首先 , 不管是对于read还是write , flash驱动层都需要相关操作的具体信息 , 包括从哪个位置read/write多少内容到哪里 , 这些信息都被封装到 mtd_oob_ops 结构体中 :

struct mtd_oob_ops {
	unsigned int	mode; // 
	size_t		len; // 期望read/write的数据的长度
	size_t		retlen; // 实际read/write的数据的长度
	size_t		ooblen; // 期望读取read/write oob数据的长度
	size_t		oobretlen; // 实际read/write oob数据的长度
	uint32_t	ooboffs; // read/write oob数据在oob区域内的偏移
	uint8_t		*datbuf; // read/write 数据的buffer
	uint8_t		*oobbuf; // read/write oob数据的buffer
}

当然 , 为了考虑接口在多线程坏境下的使用, 每次read/write操作之前都会同步 颗粒的状态 , 只有在ready状态下,才会进行后续的操作 , 否则一直等待其状态变为ready 这部分工作在 nand_get_device 接口中实现 :

static int nand_get_device(struct mtd_info *mtd, int new_state)
{
	struct nand_chip *chip = mtd->priv;
	spinlock_t *lock = &chip->controller->lock;
	wait_queue_head_t *wq = &chip->controller->wq;
	DECLARE_WAITQUEUE(wait, current);
retry:
	spin_lock(lock);

	...
	if (chip->controller->active == chip && chip->state == FL_READY) {
		chip->state = new_state;
		spin_unlock(lock);
		return 0;
	}
	...
	set_current_state(TASK_UNINTERRUPTIBLE);
	add_wait_queue(wq, &wait);
	spin_unlock(lock);
	schedule();
	remove_wait_queue(wq, &wait);
	goto retry;
}

当本次操作结束时 , 会调用 nand_release_device 将颗粒状态置为ready :

static void nand_release_device(struct mtd_info *mtd)
{
	struct nand_chip *chip = mtd->priv;

	/* Release the controller and the chip */
	spin_lock(&chip->controller->lock);
	chip->controller->active = NULL;
	chip->state = FL_READY;
	wake_up(&chip->controller->wq);
	spin_unlock(&chip->controller->lock);
}

nand_read_oob

read oob数据时会有三种操作模式 :

enum {
	MTD_OPS_PLACE_OOB = 0, // read/write oob数据在oob区域的指定位置
	MTD_OPS_AUTO_OOB = 1, // read/write oob数据在oob中的free areas区域
	MTD_OPS_RAW = 2, // 关闭ecc read/write oob数据在oob区域的指定位置
};

最终会根据不同的模式 , 选择对应的驱动实现的read oob接口 :

static int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
			    struct mtd_oob_ops *ops)
{
	...

	while (1) {
		if (ops->mode == MTD_OPS_RAW)
			ret = chip->ecc.read_oob_raw(mtd, chip, page);
		else
			ret = chip->ecc.read_oob(mtd, chip, page);

		if (ret < 0)
			break;

		len = min(len, readlen);
		buf = nand_transfer_oob(chip, buf, ops, len);

		...
		readlen -= len;
		if (!readlen)
			break;

		/* Increment page address */
		realpage++;

		page = realpage & chip->pagemask;
	}

	ops->oobretlen = ops->ooblen - readlen;
	...
	return  mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0;
}

这里需要注意 , read_oob/read_oob_raw接口只是将oob的数据读到 chip->oob_poi 这个内部的buffer中 , 之后需要根据具体的mode , 将其中的数据copy到调用者提供的buffer中 , 这部分工作由 nand_transfer_oob 完成 :

static uint8_t *nand_transfer_oob(struct nand_chip *chip, uint8_t *oob,
				  struct mtd_oob_ops *ops, size_t len)
{
	switch (ops->mode) {

	case MTD_OPS_PLACE_OOB:
	case MTD_OPS_RAW:
		memcpy(oob, chip->oob_poi + ops->ooboffs, len);
		return oob + len;

	case MTD_OPS_AUTO_OOB: {
		struct nand_oobfree *free = chip->ecc.layout->oobfree;
		uint32_t boffs = 0, roffs = ops->ooboffs;
		size_t bytes = 0;

		for (; free->length && len; free++, len -= bytes) {
			/* Read request not from offset 0? */
			if (unlikely(roffs)) {
				if (roffs >= free->length) {
					roffs -= free->length;
					continue;
				}
				boffs = free->offset + roffs;
				bytes = min_t(size_t, len,
						  (free->length - roffs));
				roffs = 0;
			} else {
				bytes = min_t(size_t, len, free->length);
				boffs = free->offset;
			}
			memcpy(oob, chip->oob_poi + boffs, bytes);
			oob += bytes;
		}
		return oob;
	}
	default:
		BUG();
	}
	return NULL;
}

nand_read

当读取flash中数据取得内容时 , 考虑到性能的因素 , 驱动内部会缓存当前 页的数据在 chip->buffers->databuf 中 , 如果当前读取的内容和之前的 读取的内容在同一页内 , 那么直接从当前缓存的buffer中直接copy :

while (1) {
	if (realpage != chip->pagebuf || oob) {
	...
	} else {
		memcpy(buf, chip->buffers->databuf + col, bytes);
		buf += bytes;
		...
	}

否则 , 就需要根据mode选择不同的驱动接口将数据读取到内部buffer中 :

if (realpage != chip->pagebuf || oob) {
	bufpoi = use_bufpoi ? chip->buffers->databuf : buf;

	if (use_bufpoi && aligned)
		pr_debug("%s: using read bounce buffer for buf@%p\n",
				 __func__, buf);

read_retry:
	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);

	/*
	 * Now read the page into the buffer.  Absent an error,
	 * the read methods return max bitflips per ecc step.
	 */
	if (unlikely(ops->mode == MTD_OPS_RAW))
		ret = chip->ecc.read_page_raw(mtd, chip, bufpoi,
						  oob_required,
						  page);
	else if (!aligned && NAND_HAS_SUBPAGE_READ(chip) &&
		 !oob)
		ret = chip->ecc.read_subpage(mtd, chip,
					col, bytes, bufpoi,
					page);
	else
		ret = chip->ecc.read_page(mtd, chip, bufpoi,
					  oob_required, page);

write operation

write操作的逻辑和read类似 , 这里就不在赘述 . 这里有一点需要注意 , 但写入的内容不满一页时 , 驱动实际还是写入一页数据 , 只不过未改变的部分会用0xff填充 , 这样当硬件将0xff写入到对应颗粒中时 , 并不会改变其之前的内容 .

FIN.