MTD(3)---nand flash的erase,read,write接口函数代码分析

标签:
it |
分类: Linux |
本来是想按照代码流程往下讲bbt的,但是写着写着,还是要先介绍下mtd的几个基本flash读写擦函数接口。那就调整下,先讲基本接口函数,再讲到bbt的时候,就不用回头来讲基本读写函数了,这样主线清楚些。
忽然觉得我讲的流程有些乱:)
还没有讲flash的具体操作命令,要是穿插在下来的章节里面讲,会更乱,那就在这里补充下吧:)
前面这章已经提到了一些东西,但我光顾着分解代码了,没有把他们关联起来。
我们知道,flash的基本操作就是erase、write、read。那么kernel是如何执行这些操作的呢?
首先我们要明确一点,CPU是通过flash控制器操作Flash芯片的,不同的芯片flash控制器不同,那么flash控制器有什么功能呢?硬件ECC校验,指令状态,工作时序等等;
http://my.csdn.net/uploads/201206/28/1340855605_3080.jpgflash的erase,read,write接口函数代码分析" />
http://my.csdn.net/uploads/201206/28/1340855811_5878.jpgflash的erase,read,write接口函数代码分析" />
上面是flash的读写擦通用操作流程。
以上的代码都是针对某个特点平台的flash底层信息,比如我们就是针对TI的DM368来讲的,它们既要遵循一般的flash操作规范,如读写擦的命令字,也会有自己chip的一些特性,比如IO管脚复用,时序控制等等。
那么kernel如何管理种类繁多的flash设备?就是依赖MTD抽象层来实现的。
MTD定义了通用的flash操作接口,也针对大多数nand flash定义了通用的操作流程(nand_base.c),各种不同的chip只需要实现自己直接操作flash设备的命令就好了。
dm368就是通过上面的几个接口函数来完成具体动作的。
MTD提供的底层flash操作接口如下:
提醒一点,到目前为止,我们还没有涉及到flash逻辑分区,所有的flash相关信息都是以单片flash为目标的,上面MTD提供的底层接口,也都是以chip为工作域的。
对flash进行操作,一定要指定要操作的区域属于哪个block,哪个page,而MTD的接口函数使用的偏移量参数通常是以字节为单位的,所以要经常在byte,page,block之间转换;
kernel里面大量使用了位移来替代乘除法,也许是为了提高效率,也许是因为历史原因,但对阅读代码来说要稍微绕下弯子,相关shift的赋值在nand_get_flash_type中。
//
有的flash封装了多个chip,比如2GB由2个1GB的chip组成。
我们先看下nand_erase,flash在写入数据之前必须先擦除(erase),擦除是以block为单位的,擦除成功则该block所有bit都为1,如果擦除失败,则需要更新bbt;
static int nand_erase(structmtd_info *mtd, struct erase_info *instr)
{
}
nand_erase要擦除的区域信息是通过struct erase_info *instr传入的,最重要的几个成员有addr,len,callback,state;
struct erase_info {
};
#define BBT_PAGE_MASK 0xffffff3f
int nand_erase_nand(struct mtd_info*mtd, struct erase_info *instr,
前面的判断边界条件的代码不讲了,略过;
这个函数实际上是给chip加锁,因为同一个chip不能同时做多个动作,比如,一个read完成了,才能开始下一个read的动作,这里面用的是spin_lock自旋锁,因为要考虑到多核CPU和SMP。
一个动作结束后,必须调用nand_release_device(mtd);释放自旋锁。
以上代码根据addr计算出要擦除的区域的起始page以及chip序号,并选中要操作的chip,要注意,有的flash封装了多个chip。
goto erase_exit;
kernel里面通常是不擦除以标记的坏块的;这里如何检查坏块的代码就跳过了,后面讲bbt的时候会详细解说,原理就是检查所在块的第一个page的oob里面的坏块标记是否为0xff,如果不是就是坏块。
下面分析下erase_cmd的代码;
static void single_erase_cmd(structmtd_info *mtd, int page)
{
}
从上面的硬件手册可知,擦除的流程主要是先写入60h,再写入page地址,再写入d0h,然后就是读取状态寄存器等待命令执行完毕,得到命令执行成功与否的返回值。
那这上面的2句cmdfunc就是发送erase命令了。
#define
NAND_CMD_ERASE1
#define
NAND_CMD_ERASE2
从宏定义可以看到,正是erase的2个命令字;
现在,是时候看下cmdfunc的代码了,我们针对large page看下nand_command_lp
上面的地址,被分成了2部分,page和column,column是page内部的偏移量,指定column是为了执行一些特殊命令,如随机读写page内部的某些区域,通常是为了读写oob的某些字节。
发送命令比我们在之前看到的流程图要复杂些,原因在于CLE和ALE,下面是DM368 EMIF硬件手册的说明;
Figure
8
However, it isrecommended, especially when booting from NAND Flash, that EM_A[2:1] be used.This is because these pins are not muxed with another peripheral and aretherefore always available.
http://my.csdn.net/uploads/201206/28/1340855902_3541.jpgflash的erase,read,write接口函数代码分析" />
从上面的结构图可以看到,命令、地址、数据是通过不同的引脚访问的。
nand_command_lp写入命令和地址时都用了相同的接口chip->cmd_ctrl,但是必须要区分命令和地址通过不同的IO地址写入才行,所以就增加了一个参数ctrl来告诉底层;
另外,就是对address的拆分,因为要写入address的IO端口是8bit的,所以要把address按照一定的规则分次写入IO端口,通常是先写入低字节,再写入高字节。
明白了上面讲的原理,下面的代码也就很好理解了,就是根据函数传入的ctr区分要写入的是命令、地址还是数据,来切换nand->IO_ADDR_W,然后写入命令或地址(如果有的话)。
static voidnand_davinci_hwcontrol(struct mtd_info *mtd, int cmd,
{
}
nand_command_lp的代码比较长,这里不详细列出了,说明下流程;
先写命令字,ctrl里面的NAND_CLE告诉底层要写的是命令;
接下来写地址,有的命令是没有地址的,如NAND_CMD_STATUS;
有的地址只有page,没有column,这都要上层调用者指定,没有地址请指定-1,要记住0也是合法的地址;
首先写入column,如果是16bit的flash,一次读写2个byte,这里要把column除以2;
目前column的有效bit不超过16,所以连续写入2个byte就可以了,先写入低字节要注意ctr用NAND_ALE注明要写入的是地址;
接着写入page,要根据芯片size确定page的有效范围,最少是2个字节,128MB以上的需要3个字节。
基本的命令字和地址写入后,一般的命令就结束了,接下来,上层可能要读写数据,所以要把nand->IO_ADDR_W恢复成IO端口,下面的函数调用就完成这个动作;
chip->cmd_ctrl(mtd,NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
有些命令还需要后续的处理才算完成,所以,代码还没完:)
举例,NAND_CMD_READ0命令在写入地址后,还要再写入一个命令NAND_CMD_READSTART,并读取寄存器NANDFSR_OFFSET等待命令执行完毕才能返回给上层,上层才可以开始读取数据。
我们已经把chip->cmdfunc分析完了,知道如何发送命令了,但是nand_erase_nand还没结束;
发送NAND_CMD_ERASE1命令后,我们还要读取状态寄存器等待erase命令执行完毕并得到返回值;
//chip->waitfunc = nand_wait;
nand_wait的流程就是间隔读取NANDFSR_OFFSET寄存器,直到读到非0值或超时为止,这里的超时设置,erase擦除是400ms,write是20ms;
需要注意的流程是,要先发送NAND_CMD_STATUS命令,等待命令完成后才能开始读取命令返回值status;
如果status不是NAND_STATUS_READY,就说明擦除失败了,这时候要进行一些错误处理,比如更新bbt坏块表;
如果接口传入的len包括了多个block,还要继续擦除下一个block;
如果erase失败,要调用mtd_erase_callback(instr);它会调用instr->callback(instr);
再分析下nand_read,nand_read_oob,这2个接口函数都是读取flash page的数据,区别在于nand_read只读取data区域,nand_read_oob可能会同时读取page的data和oob;
尽管从接口参数来看,可以读取page内部任意一段data,但是因为ECC校验,驱动程序还是要完整的读取全部data,然后再copy给上层;
flash支持NAND_CMD_READ0自动读取oob,有的flash提供了单独的命令NAND_CMD_READOOB来读取oob,请参考flash的硬件手册。
他们都调用到了nand_do_read_ops,这个函数根据传入的ops读取相应的data和oob;
首先还是要根据from计算出page和col,要注意我们一次只能读取一个page的data,所以ops->len不能越界否则要被截掉bytes = min(mtd->writesize - col, readlen);
如果上层不是要读取整个data,那驱动就要使用chip->buffers->databuf来读取整个data,再copy给上层传入的bug;
发送NAND_CMD_READ0命令后,就要读取data了,我们继续看下正常的hw ecc的流程,nand_read_page_hwecc;
ECC校验都是以512bytes为一组的,所以large page要分组分别操作;
对于dm368来说,采用的是HW 4bit/512的ECC校验,具体实现如下;
从上面的代码注释可以看到,HWecc是在读取data的时候硬件就自动完成了的,不再需要软件的干预;
接下来继续读取oob;
然后下面是ECC校验,对于硬件ECC来讲就是读取控制器的相应的寄存器,必须要看硬件手册的,这样就不讲了。
最后,还需要把前面读出的data和oob 复制给ops,这里要注意,如果ops->mode是MTD_OOB_AUTO,就只能复制oobfree给上层ops;
我们先看下MTD_OOB_RAW模式的处理流程;
在前面的代码里面,如果是MTD_OOB_RAW模式,就直接读取整个data和整个oob,注意,oob存放在chip->oob_poi里面;
复制oob的代码是
从下面的代码,我们可以看出,在MTD_OOB_RAW模式下,驱动是将oob复制到ops->datbuf里面的,而且要通过ops->ooboffs指定oob位置和长度,这种模式要谨慎使用。
static uint8_t*nand_transfer_oob(struct nand_chip *chip, uint8_t *oob,
下面再看下MTD_OOB_AUTO模式,上层应用程序使用这种模式比较多,比如yaffs文件系统。
MTD_OOB_AUTO模式就是只读写oobfree区域,也就是在free->offset的基础上再偏移ops->ooboffs,然后把数据复制给ops->oobbuf,所以上层应用程序就不能再自己计算free offset了。
nand_do_read_ops函数实现的功能比较丰富,可以一次读取多个连续的page,也可以只读取page的一部分,但上层调用者还是要注意自己的逻辑,尽量一次读取一个完整的page。
再看下mtd->read_oob
nand_read_oob - [MTD Interface] NANDread data and/or out-of-band
那么,我们到底是要读取data还是oob?要读取哪些内容?这就是ops的成员定义的了,下面看代码。
Oob mode支持3种方式,分别如下;
MTD_OOB_AUTO只允许读写oobfree区域,并且自动计算oobfree的起始位置;
MTD_OOB_RAW允许读写整个oob,调用者要通过ops.ooboffs和ops.ooblen自行确定起始位置和长度;
page分为data和oob 两个部分,如果datbuf为NULL,就只读取oob,否则有可能要读取data和oob.
最后再看下写flash的接口函数nand_write,nand_write_oob,同read接口一样,nand_write只写data,nand_write_oob可能会同时写data和oob,由ops指定;
如果ops->datbuf 为空,则是只写oob,会调用nand_do_write_oob函数;
函数开始的代码不列出了,就是边界条件判断,计算page,选中chip;
实际动作有下面三步,更新oob后,重新写入全部oob;
所以,如何更新oob,就是重点了,请看nand_fill_oob;
如果不是MTD_OOB_AUTO模式,就简单了,直接复制ops->oobbuf,复制目标偏移量是ops->ooboffs,长度是ops->ooblen;
如果是MTD_OOB_AUTO模式,复制ops->oobbuf的时候,复制目标偏移量是free->offset+ops->ooboffs,长度是ops->ooblen,这一点,要注意;
总之,MTD_OOB_AUTO模式下,只允许读写free区域,并且自动计算free的起始地址,这样对上层应用是有好处的,上层只需要知道oob里面有一段free区域可以存放私有数据就好了,不需要知道这段free在哪里。
如果ops->datbuf不为NULL,要写data,就会调用nand_do_write_ops函数;
这个函数里面有一段代码要注意,他的流程是,如果上层调用者只想写page data的一部分,那么驱动为了使用ECC校验,也必须要写整个page,驱动就会把data的其他区域填充为0xff;
所以,上层最好还是一次写完整的pagedata,以免逻辑不严谨造成错误发生。
更新chip->buffers->databuf后,要调用底层写flash了。
看下nand_write_page的处理流程;
如果是MTD_OOB_RAW模式,就直接写page的全部data和oob,不做ECC码计算;
否则,调用nand_write_page_hwecc,写入data的同时,要计算ECC码,最后将ECC码跟oob合并后写入oob。
最后,还是要调用status =chip->waitfunc(mtd, chip); 得到write命令的返回值,判断write成功与否。
Nand flash在mtd最基本的读写擦接口函数,就分析完了。
下一章要接着nand_scan_tail的代码,讲bbt了。