Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2872636
  • 博文数量: 674
  • 博客积分: 17881
  • 博客等级: 上将
  • 技术积分: 4849
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-17 10:15
文章分类

全部博文(674)

文章存档

2013年(34)

2012年(146)

2011年(197)

2010年(297)

分类: LINUX

2013-07-13 15:20:50

本来是想按照代码流程往下讲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设备的命令就好了。

       /*Set address of hardware control function */

       info->chip.cmd_ctrl      = nand_davinci_hwcontrol;

       info->chip.dev_ready    = nand_davinci_dev_ready;

       /*Speed up buffer I/O */

       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中。

       /*Calculate the address shift from the page size */

       chip->page_shift= ffs(mtd->writesize) - 1;  //write size相当与page size,对应2KBlp来说,chip->page_shift = ffs(2048)-1 = 11

// 顺便说下,ffs(x)这个函数把我搞晕了一下,ffs(2048)开始我以为是11,但后面计算不对,我只好加了调试语句,结果等于12.也就是说,ffs(0)=0;ffs(1)=1;ffs(2)=2;

       /*Convert chipsize to number of pages per chip -1. */

       chip->pagemask= (chip->chipsize >> chip->page_shift) - 1; // chip size=1GB, pagemask = 0x7ffff

       chip->bbt_erase_shift= chip->phys_erase_shift =

              ffs(mtd->erasesize)- 1; // erasesize 等于block size,对应K9K8G08U0A1 block = 64 page = 128KBbbt_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;

static int nand_erase(structmtd_info *mtd, struct erase_info *instr)

{

       returnnand_erase_nand(mtd, instr, 0);

}

nand_erase要擦除的区域信息是通过struct erase_info *instr传入的,最重要的几个成员有addr,len,callback,state;

/* If the erase fails, fail_addrmight indicate exactly which block failed. If

  fail_addr = MTD_FAIL_ADDR_UNKNOWN, the failure was not at the devicelevel or was not

  specific to any particular block. */

struct erase_info {

       structmtd_info *mtd;

       /* Start address must align on block boundary */

       uint64_taddr;        // 要擦除区域的起始位置,以byte为单位

       /* Length must align on block boundary */

       /* Do not allow erase past end of device */

       uint64_tlen;          // 要擦除区域的长度

       uint64_tfail_addr;

       u_longtime;

       u_longretries;

       unsigneddev;

       unsignedcell;

       void(*callback) (struct erase_info *self);  //擦除成功,会调用此回调函数;

       u_longpriv;

       u_charstate;        //擦除动作的结果

       structerase_info *next;

};

#define BBT_PAGE_MASK 0xffffff3f

/**

 * nand_erase_nand - [Internal] erase block(s)

 * @mtd:       MTDdevice structure

 * @instr:      eraseinstruction

 * @allowbbt: allowerasing the bbt area

 *

 * Erase one ore more blocks

 */

int nand_erase_nand(struct mtd_info*mtd, struct erase_info *instr,

                  int allowbbt)

前面的判断边界条件的代码不讲了,略过;

       /*Grab the lock and see if the device is available */

       nand_get_device(chip,mtd, FL_ERASING);

这个函数实际上是给chip加锁,因为同一个chip不能同时做多个动作,比如,一个read完成了,才能开始下一个read的动作,这里面用的是spin_lock自旋锁,因为要考虑到多核CPU和SMP。

一个动作结束后,必须调用nand_release_device(mtd);释放自旋锁。

       /*Shift to get first page */

       page= (int)(instr->addr >> chip->page_shift);

       chipnr= (int)(instr->addr >> chip->chip_shift);

       /*Calculate pages in each block */

       pages_per_block= 1 << (chip->phys_erase_shift - chip->page_shift);

       /*Select the NAND device */

       chip->select_chip(mtd,chipnr);

以上代码根据addr计算出要擦除的区域的起始page以及chip序号,并选中要操作的chip,要注意,有的flash封装了多个chip。

              /*

               * heck if we have a bad block, we do not erasebad blocks !

               */

              if(nand_block_checkbad(mtd, ((loff_t) page) <<

                                   chip->page_shift,0, allowbbt))

goto erase_exit;

kernel里面通常是不擦除以标记的坏块的;这里如何检查坏块的代码就跳过了,后面讲bbt的时候会详细解说,原理就是检查所在块的第一个page的oob里面的坏块标记是否为0xff,如果不是就是坏块。

              chip->erase_cmd(mtd,page & chip->pagemask);

下面分析下erase_cmd的代码;

       /*Check for AND chips with 4 page planes */

       if(chip->options & NAND_4PAGE_ARRAY)

              chip->erase_cmd= multi_erase_cmd;

       else

              chip->erase_cmd= single_erase_cmd;

static void single_erase_cmd(structmtd_info *mtd, int page)

{

       structnand_chip *chip = mtd->priv;

       /*Send commands to erase a block */

       chip->cmdfunc(mtd,NAND_CMD_ERASE1, -1, page);

       chip->cmdfunc(mtd,NAND_CMD_ERASE2, -1, -1);

}

从上面的硬件手册可知,擦除的流程主要是先写入60h,再写入page地址,再写入d0h,然后就是读取状态寄存器等待命令执行完毕,得到命令执行成功与否的返回值。

那这上面的2句cmdfunc就是发送erase命令了。

#define NAND_CMD_ERASE1           0x60

#define NAND_CMD_ERASE2           0xd0

从宏定义可以看到,正是erase的2个命令字;

 

现在,是时候看下cmdfunc的代码了,我们针对large page看下nand_command_lp

/**

 * nand_command_lp - [DEFAULT] Send command toNAND large page device

 * @mtd:       MTDdevice structure

 * @command:     thecommand to be sent

 * @column:  thecolumn address for this command, -1 if none

 * @page_addr:    thepage address for this command, -1 if none

 *

 * Send command to NAND device. This is theversion for the new large page

 * devices We dont have the separate regions aswe have in the small page

 * devices. We must emulate NAND_CMD_READOOB to keep the code compatible.

 */

上面的地址,被分成了2部分,page和column,column是page内部的偏移量,指定column是为了执行一些特殊命令,如随机读写page内部的某些区域,通常是为了读写oob的某些字节。

 

发送命令比我们在之前看到的流程图要复杂些,原因在于CLE和ALE,下面是DM368 EMIF硬件手册的说明;

Figure 8 shows the EMIF external pins used to interface with aNAND Flash device. EMIF address lines are used to drive the NAND Flash device'scommand latch enable (CLE) and address latch enable (ALE) signals. Any EMIFaddress line may be used to drive the CLE and ALE signals of the NAND Flash.

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.

/* use nandboot-capable ALE/CLEmasks by default */

       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,然后写入命令或地址(如果有的话)。

static voidnand_davinci_hwcontrol(struct mtd_info *mtd, int cmd,

                               unsigned int ctrl)

{

       structdavinci_nand_info      *info =to_davinci_nand(mtd);

       uint32_t                addr = info->current_cs;

       structnand_chip          *nand = mtd->priv;

 

       /*Did the control lines change? */

       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个接口函数都是读取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要分组分别操作;

       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来说,采用的是HW 4bit/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位置和长度,这种模式要谨慎使用。

static uint8_t*nand_transfer_oob(struct nand_chip *chip, uint8_t *oob,

                              struct mtd_oob_ops *ops, size_t len)

       caseMTD_OOB_RAW:

              memcpy(oob,chip->oob_poi + ops->ooboffs, len);

              returnoob + len;

 

下面再看下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  // mtd->read_oob = nand_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自行确定起始位置和长度;

       if(!ops->datbuf)

              ret= nand_do_read_oob(mtd, from, ops);

       else

              ret= nand_do_read_ops(mtd, from, ops);

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;

       memset(chip->oob_poi,0xff, mtd->oobsize);

       nand_fill_oob(chip,ops->oobbuf, ops);

       status= chip->ecc.write_oob(mtd, chip, page & chip->pagemask);

所以,如何更新oob,就是重点了,请看nand_fill_oob;

       size_tlen = ops->ooblen;

       switch(ops->mode){

       caseMTD_OOB_PLACE:

       caseMTD_OOB_RAW:

              memcpy(chip->oob_poi+ ops->ooboffs, oob, len);

              returnoob + len;

如果不是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;

              /*Partial page write ? */

              if(unlikely(column || writelen < (mtd->writesize - 1))) {

                     cached= 0;

                     bytes= min_t(int, bytes - column, (int) writelen);

                     chip->pagebuf= -1;

                     memset(chip->buffers->databuf,0xff, mtd->writesize);

                     memcpy(&chip->buffers->databuf[column],buf, bytes);

                     wbuf= chip->buffers->databuf;

              }

所以,上层最好还是一次写完整的pagedata,以免逻辑不严谨造成错误发生。

更新chip->buffers->databuf后,要调用底层写flash了。

              ret= chip->write_page(mtd, chip, wbuf, page, cached,

                                   (ops->mode == MTD_OOB_RAW));

看下nand_write_page的处理流程;

如果是MTD_OOB_RAW模式,就直接写page的全部data和oob,不做ECC码计算;

       if(unlikely(raw))

              chip->ecc.write_page_raw(mtd,chip, buf);

否则,调用nand_write_page_hwecc,写入data的同时,要计算ECC码,最后将ECC码跟oob合并后写入oob。

最后,还是要调用status =chip->waitfunc(mtd, chip); 得到write命令的返回值,判断write成功与否。

Nand flash在mtd最基本的读写擦接口函数,就分析完了。

下一章要接着nand_scan_tail的代码,讲bbt了。

http://blog.csdn.net/lidehua1975/article/details/7698879

阅读(4068) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~