MTD3 nand flash的erasereadwrite接口函数代码分析.docx

上传人:b****6 文档编号:7950224 上传时间:2023-01-27 格式:DOCX 页数:12 大小:24.14KB
下载 相关 举报
MTD3 nand flash的erasereadwrite接口函数代码分析.docx_第1页
第1页 / 共12页
MTD3 nand flash的erasereadwrite接口函数代码分析.docx_第2页
第2页 / 共12页
MTD3 nand flash的erasereadwrite接口函数代码分析.docx_第3页
第3页 / 共12页
MTD3 nand flash的erasereadwrite接口函数代码分析.docx_第4页
第4页 / 共12页
MTD3 nand flash的erasereadwrite接口函数代码分析.docx_第5页
第5页 / 共12页
点击查看更多>>
下载资源
资源描述

MTD3 nand flash的erasereadwrite接口函数代码分析.docx

《MTD3 nand flash的erasereadwrite接口函数代码分析.docx》由会员分享,可在线阅读,更多相关《MTD3 nand flash的erasereadwrite接口函数代码分析.docx(12页珍藏版)》请在冰豆网上搜索。

MTD3 nand flash的erasereadwrite接口函数代码分析.docx

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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > PPT模板 > 动态背景

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1