MTD3 nand flash的erasereadwrite接口函数代码分析.docx
《MTD3 nand flash的erasereadwrite接口函数代码分析.docx》由会员分享,可在线阅读,更多相关《MTD3 nand flash的erasereadwrite接口函数代码分析.docx(12页珍藏版)》请在冰豆网上搜索。
MTD3nandflash的erasereadwrite接口函数代码分析
本来是想按照代码流程往下讲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操作接口,也针对大多数nandflash定义了通用的操作流程(nand_base.c),各种不同的chip只需要实现自己直接操作flash设备的命令就好了。
info->chip.cmd_ctrl =nand_davinci_hwcontrol;
info->chip.dev_ready =nand_davinci_dev_ready;
info->chip.read_buf =nand_davinci_read_buf;
info->chip.write_buf =nand_davinci_write_buf;
dm368就是通过上面的几个接口函数来完成具体动作的。
MTD提供的底层flash操作接口如下:
mtd->erase=nand_erase;
mtd->read=nand_read;
mtd->write=nand_write;
mtd->read_oob=nand_read_oob;
mtd->write_oob=nand_write_oob;
mtd->sync=nand_sync;
mtd->suspend=nand_suspend;
mtd->resume=nand_resume;
mtd->block_isbad=nand_block_isbad;
mtd->block_markbad=nand_block_markbad;
提醒一点,到目前为止,我们还没有涉及到flash逻辑分区,所有的flash相关信息都是以单片flash为目标的,上面MTD提供的底层接口,也都是以chip为工作域的。
对flash进行操作,一定要指定要操作的区域属于哪个block,哪个page,而MTD的接口函数使用的偏移量参数通常是以字节为单位的,所以要经常在byte,page,block之间转换;
kernel里面大量使用了位移来替代乘除法,也许是为了提高效率,也许是因为历史原因,但对阅读代码来说要稍微绕下弯子,相关shift的赋值在nand_get_flash_type中。
chip->page_shift=ffs(mtd->writesize)-1; //writesize相当与pagesize,对应2KB的lp来说,chip->page_shift=ffs(2048)-1=11
// 顺便说下,ffs(x)这个函数把我搞晕了一下,ffs(2048)开始我以为是11,但后面计算不对,我只好加了调试语句,结果等于12.也就是说,ffs(0)=0;ffs
(1)=1;ffs
(2)=2;
chip->pagemask=(chip->chipsize>>chip->page_shift)-1; //chipsize=1GB,pagemask=0x7ffff
chip->bbt_erase_shift=chip->phys_erase_shift=
ffs(mtd->erasesize)-1; //erasesize 等于blocksize,对应K9K8G08U0A,1block=64page=128KB,bbt_erase_shift=17;
if(chip->chipsize&0xffffffff)
chip->chip_shift=ffs((unsigned)chip->chipsize)-1;//chipsize=1GB,chip_shift=30
else
chip->chip_shift=ffs((unsigned)(chip->chipsize>>32))+32-1;
有的flash封装了多个chip,比如2GB由2个1GB的chip组成。
我们先看下nand_erase,flash在写入数据之前必须先擦除(erase),擦除是以block为单位的,擦除成功则该block所有bit都为1,如果擦除失败,则需要更新bbt;
staticintnand_erase(structmtd_info*mtd,structerase_info*instr)
{
returnnand_erase_nand(mtd,instr,0);
}
nand_erase要擦除的区域信息是通过structerase_info*instr传入的,最重要的几个成员有addr,len,callback,state;
structerase_info{
structmtd_info*mtd;
uint64_taddr; //要擦除区域的起始位置,以byte为单位
uint64_tlen; //要擦除区域的长度
uint64_tfail_addr;
u_longtime;
u_longretries;
unsigneddev;
unsignedcell;
void(*callback)(structerase_info*self); //擦除成功,会调用此回调函数;
u_longpriv;
u_charstate; //擦除动作的结果
structerase_info*next;
};
#defineBBT_PAGE_MASK0xffffff3f
intnand_erase_nand(structmtd_info*mtd,structerase_info*instr,
intallowbbt)
前面的判断边界条件的代码不讲了,略过;
nand_get_device(chip,mtd,FL_ERASING);
这个函数实际上是给chip加锁,因为同一个chip不能同时做多个动作,比如,一个read完成了,才能开始下一个read的动作,这里面用的是spin_lock自旋锁,因为要考虑到多核CPU和SMP。
一个动作结束后,必须调用nand_release_device(mtd);释放自旋锁。
page=(int)(instr->addr>>chip->page_shift);
chipnr=(int)(instr->addr>>chip->chip_shift);
pages_per_block=1<<(chip->phys_erase_shift-chip->page_shift);
chip->select_chip(mtd,chipnr);
以上代码根据addr计算出要擦除的区域的起始page以及chip序号,并选中要操作的chip,要注意,有的flash封装了多个chip。
if(nand_block_checkbad(mtd,((loff_t)page)<<
chip->page_shift,0,allowbbt))
gotoerase_exit;
kernel里面通常是不擦除以标记的坏块的;这里如何检查坏块的代码就跳过了,后面讲bbt的时候会详细解说,原理就是检查所在块的第一个page的oob里面的坏块标记是否为0xff,如果不是就是坏块。
chip->erase_cmd(mtd,page&chip->pagemask);
下面分析下erase_cmd的代码;
if(chip->options&NAND_4PAGE_ARRAY)
chip->erase_cmd=multi_erase_cmd;
else
chip->erase_cmd=single_erase_cmd;
staticvoidsingle_erase_cmd(structmtd_info*mtd,intpage)
{
structnand_chip*chip=mtd->priv;
chip->cmdfunc(mtd,NAND_CMD_ERASE1,-1,page);
chip->cmdfunc(mtd,NAND_CMD_ERASE2,-1,-1);
}
从上面的硬件手册可知,擦除的流程主要是先写入60h,再写入page地址,再写入d0h,然后就是读取状态寄存器等待命令执行完毕,得到命令执行成功与否的返回值。
那这上面的2句cmdfunc就是发送erase命令了。
#defineNAND_CMD_ERASE1 0x60
#defineNAND_CMD_ERASE2 0xd0
从宏定义可以看到,正是erase的2个命令字;
现在,是时候看下cmdfunc的代码了,我们针对largepage看下nand_command_lp
上面的地址,被分成了2部分,page和column,column是page内部的偏移量,指定column是为了执行一些特殊命令,如随机读写page内部的某些区域,通常是为了读写oob的某些字节。
发送命令比我们在之前看到的流程图要复杂些,原因在于CLE和ALE,下面是DM368EMIF硬件手册的说明;
Figure8 showstheEMIFexternalpinsusedtointerfacewithaNANDFlashdevice.EMIFaddresslinesareusedtodrivetheNANDFlashdevice'scommandlatchenable(CLE)andaddresslatchenable(ALE)signals.AnyEMIFaddresslinemaybeusedtodrivetheCLEandALEsignalsoftheNANDFlash.
However,itisrecommended,especiallywhenbootingfromNANDFlash,thatEM_A[2:
1]beused.Thisisbecausethesepinsarenotmuxedwithanotherperipheralandarethereforealwaysavailable.
info->mask_ale =pdata->mask_ale?
:
MASK_ALE;
info->mask_cle =pdata->mask_cle?
:
MASK_CLE;
从上面的结构图可以看到,命令、地址、数据是通过不同的引脚访问的。
nand_command_lp写入命令和地址时都用了相同的接口chip->cmd_ctrl,但是必须要区分命令和地址通过不同的IO地址写入才行,所以就增加了一个参数ctrl来告诉底层;
另外,就是对address的拆分,因为要写入address的IO端口是8bit的,所以要把address按照一定的规则分次写入IO端口,通常是先写入低字节,再写入高字节。
明白了上面讲的原理,下面的代码也就很好理解了,就是根据函数传入的ctr区分要写入的是命令、地址还是数据,来切换nand->IO_ADDR_W,然后写入命令或地址(如果有的话)。
staticvoidnand_davinci_hwcontrol(structmtd_info*mtd,intcmd,
unsignedintctrl)
{
structdavinci_nand_info *info=to_davinci_nand(mtd);
uint32_t addr=info->current_cs;
structnand_chip *nand=mtd->priv;
if(ctrl&NAND_CTRL_CHANGE){
if((ctrl&NAND_CTRL_CLE)==NAND_CTRL_CLE)
addr|=info->mask_cle;
elseif((ctrl&NAND_CTRL_ALE)==NAND_CTRL_ALE)
addr|=info->mask_ale;
nand->IO_ADDR_W=(void__iomem__force*)addr;
}
if(cmd!
=NAND_CMD_NONE)
iowrite8(cmd,nand->IO_ADDR_W);
}
nand_command_lp的代码比较长,这里不详细列出了,说明下流程;
chip->cmd_ctrl(mtd,command&0xff,
NAND_NCE|NAND_CLE|NAND_CTRL_CHANGE);
先写命令字,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命令执行完毕并得到返回值;
status=chip->waitfunc(mtd,chip);
//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个接口函数都是读取flashpage的数据,区别在于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了,我们继续看下正常的hwecc的流程,nand_read_page_hwecc;
ECC校验都是以512bytes为一组的,所以largepage要分组分别操作;
for(i=0;eccsteps;eccsteps--,i+=eccbytes,p+=eccsize){
chip->ecc.hwctl(mtd,NAND_ECC_READ);
chip->read_buf(mtd,p,eccsize);
chip->ecc.calculate(mtd,p,&ecc_calc[i]);
}
对于dm368来说,采用的是HW4bit/512的ECC校验,具体实现如下;
info->chip.ecc.calculate=nand_davinci_calculate_4bit;
info->chip.ecc.correct=nand_davinci_correct_4bit;
info->chip.ecc.hwctl=nand_davinci_hwctl_4bit;
info->chip.ecc.bytes=10;
从上面的代码注释可以看到,HWecc是在读取data的时候硬件就自动完成了的,不再需要软件的干预;
接下来继续读取oob;
chip->read_buf(mtd,chip->oob_poi,mtd->oobsize);
然后下面是ECC校验,对于硬件ECC来讲就是读取控制器的相应的寄存器,必须要看硬件手册的,这样就不讲了。
最后,还需要把前面读出的data和oob复制给ops,这里要注意,如果ops->mode是MTD_OOB_AUTO,就只能复制oobfree给上层ops;
我们先看下MTD_OOB_RAW模式的处理流程;
if(unlikely(ops->mode==MTD_OOB_RAW))
ret=chip->ecc.read_page_raw(mtd,chip,
bufpoi,page);
在前面的代码里面,如果是MTD_OOB_RAW模式,就直接读取整个data和整个oob,注意,oob存放在chip->oob_poi里面;
复制oob的代码是 buf=nand_transfer_oob(chip,buf,ops,mtd->oobsize);
从下面的代码,我们可以看出,在MTD_OOB_RAW模式下,驱动是将oob复制到ops->datbuf里面的,而且要通过ops->ooboffs指定oob位置和长度,这种模式要谨慎使用。
staticuint8_t*nand_transfer_oob(structnand_chip*chip,uint8_t*oob,
structmtd_oob_ops*ops,size_tlen)
caseMTD_OOB_RAW:
memcpy(oob,chip->oob_poi+ops->ooboffs,len);
retur