本来是想按照代码流程往下讲bbt的,但是写着写着,还是要先介绍下mtd的几个基本flash读写擦函数接口。那就调整下,先讲基本接口函数,再讲到bbt的时候,就不用回头来讲基本读写函数了,这样主线清楚些。 忽然觉得我讲的流程有些乱:) 还没有讲flash的具体操作命令,要是穿插在下来的章节里面讲,会更乱,那就在这里补充下吧:) 前面这章已经提到了一些东西,但我光顾着分解代码了,没有把他们关联起来。 我们知道,flash的基本操作就是erase、write、read。那么kernel是如何执行这些操作的呢? 首先我们要明确一点,CPU是通过flash控制器操作Flash芯片的,不同的芯片flash控制器不同,那么flash控制器有什么功能呢?硬件ECC校验,指令状态,工作时序等等;
上面是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.
从上面的结构图可以看到,命令、地址、数据是通过不同的引脚访问的。 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了。 |
|
来自: ningmei0424 > 《NAND flash有关》