本来是想按照代码流程往下讲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设备的命令就好了。
info-&d_ctrl = nand_davinci_hwcontrol;
info->chip.dev_ready = nand_davinci_dev_ready;
info-&ad_buf = nand_davinci_read_buf;
info->chip.write_buf = nand_davinci_write_buf;
dm368就是通过上面的几个接口函数来完成具体动作的。
MTD提供的底层flash操作接口如下:
mtd->erase= nand_erase;
write的返回值 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; //write size相当与page size,对应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; // chip size=1GB, pagemask = 0x7ffff
chip->bbt_erase_shift= chip->phys_erase_shift =
ffs(mtd->erasesize)- 1; // erasesize 等于block size,对应K9K8G08U0A,1 block = 64 page = 128KB,bbt_erase_shift = 17;
if(chip->chipsize & 0xffffffff)
chip->chip_shift= ffs((unsigned)chip->chipsize) - 1;// chipsize=1GB, chip_shift=3
0
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;
struct erase_info {
structmtd_info *mtd;
uint64_taddr; // 要擦除区域的起始位置,以byte为单位
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
int nand_erase_nand(struct mtd_info*mtd, struct erase_info *instr,
int allowbbt)
前面的判断边界条件的代码不讲了,略过;
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))
goto erase_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;
static void single_erase_cmd(structmtd_info *mtd, int page)
{
structnand_chip *chip = mtd->priv;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论