红联Linux门户
Linux帮助

关于linux下mtd_oobtest.ko的一点点分析

发布时间:2017-01-08 09:27:37来源:linux网站作者:jackyard
最近在学习nand,写完nand controller的驱动后,随手也作了一下测试,发现mtd_oobtest老是出错,从这里开始,对它做一点点的分析:
当我们加载mtd_oobtest.ko模块时,下面的函数就会调用:
insmod mtd_oobtest.ko dev=0
 
static int __init mtd_oobtest_init(void)  
{  
int err = 0;  
unsigned int i;  
uint64_t tmp;  
struct mtd_oob_ops ops;  
loff_t addr = 0, addr0;
printk(KERN_INFO "\n");  
printk(KERN_INFO "=================================================\n");
if (dev < 0) {  
pr_info("Please specify a valid mtd-device via module parameter\n");  
pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n");  
return -EINVAL;  
}
pr_info("MTD device: %d\n", dev);
mtd = get_mtd_device(NULL, dev);//这里是通过我们在加载时传入的mtd设备的id号来获取对应的mtd_info,而这里的dev是一个全局的变量,针对于上面  
//命令来说,dev就等于0.这里要说明一点的是,怎么通过一个ID来获取对应的mtd_info呢,其实这里用到了linux中的另一个知识点,就是IDR,关于这一块,  
// 有兴趣的,可以在网上找找,已经有好多人发表了自己的看法。  
if (IS_ERR(mtd)) {  
err = PTR_ERR(mtd);   
pr_err("error: cannot get MTD device\n");  
return err;  
}
if (!mtd_type_is_nand(mtd)) {//判断是不是针对nand的测试,因为oob只是针对nand来说的。  
pr_info("this test requires NAND flash\n");  
goto out;  
}
tmp = mtd->size;  
do_div(tmp, mtd->erasesize);//其实这里就是一个除法操作,完成之后tmp就是这个mtd设备所有的block数  
ebcnt = tmp;  
pgcnt = mtd->erasesize / mtd->writesize;//计算一个block有多少个page
pr_info("MTD device size %llu, eraseblock size %u, "  
"page size %u, count of eraseblocks %u, pages per "  
"eraseblock %u, OOB size %u\n",  
(unsigned long long)mtd->size, mtd->erasesize,  
mtd->writesize, ebcnt, pgcnt, mtd->oobsize);
err = -ENOMEM;  
readbuf = kmalloc(mtd->erasesize, GFP_KERNEL);  
if (!readbuf)  
goto out;  
writebuf = kmalloc(mtd->erasesize, GFP_KERNEL);//这时分调两个buff  
if (!writebuf)  
goto out;  
bbt = kzalloc(ebcnt, GFP_KERNEL);  
if (!bbt)  
goto out;
err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt);//扫描整个device,看一没有坏块,当然有坏块也是正常的。  
if (err)  
goto out;
use_offset = 0;  
use_len = mtd->ecclayout->oobavail;//这里获取oob的用户可用大小,因为oob有一部分是  
用来保存ecc的,注意,这里是从mtd的nand_base.c中所初始化的信息中获取的。  
use_len_max = mtd->ecclayout->oobavail;  
vary_offset = 0;
/* First test: write all OOB, read it back and verify */// 第一步测试,  
pr_info("test 1 of 5\n");  
  
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);//这时是erase所有好的block.  
if (err)  
goto out;
prandom_seed_state(&rnd_state, 1);//设置ramdom的种子。  
err = write_whole_device();//向nand所有的oob区写入数据。  
if (err)  
goto out;
prandom_seed_state(&rnd_state, 1);//重新设备ramdom种子  
err = verify_all_eraseblocks();//验证,其实就是把每一个page的oob中的数据读出来,与ramdom产生的数据进行比较。  
if (err)  
goto out; 
/* 
* Second test: write all OOB, a block at a time, read it back and 
* verify. 
*///从这里可以看到第二步的测试是,一次读取一个block的整个oob区,再验证。  
pr_info("test 2 of 5\n");
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);  
if (err)  
goto out;
prandom_seed_state(&rnd_state, 3);  
err = write_whole_device();//写整个MTD的oob区,不过也是一个page一个page的写  
if (err)  
goto out;
/* Check all eraseblocks */  
prandom_seed_state(&rnd_state, 3);  
pr_info("verifying all eraseblocks\n");  
for (i = 0; i < ebcnt; ++i) {  
if (bbt[i])  
continue;  
err = verify_eraseblock_in_one_go(i);//这里与第一步不同的是,一次一下子全部读取一个block的所有oob数据,而不是一次只读一个page的  
if (err)  
goto out;  
if (i % 256 == 0)  
pr_info("verified up to eraseblock %u\n", i);  
cond_resched();  
}  
pr_info("verified %u eraseblocks\n", i);
/* 
* Third test: write OOB at varying offsets and lengths, read it back 
* and verify. 
*///这一步的测试,通过写每一个oob用不同offset和len  
pr_info("test 3 of 5\n");
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);  
if (err)  
goto out;
/* Write all eraseblocks */  
use_offset = 0;  
use_len = mtd->ecclayout->oobavail;  
use_len_max = mtd->ecclayout->oobavail;  
vary_offset = 1;//这里可以看到vary_offset是1,在每一次写之前len和offset会变。  
prandom_seed_state(&rnd_state, 5);
err = write_whole_device();  
if (err)  
goto out;
/* Check all eraseblocks */  
use_offset = 0;  
use_len = mtd->ecclayout->oobavail;  
use_len_max = mtd->ecclayout->oobavail;  
vary_offset = 1;  
prandom_seed_state(&rnd_state, 5);  
err = verify_all_eraseblocks();  
if (err)  
goto out;
use_offset = 0;  
use_len = mtd->ecclayout->oobavail;  
use_len_max = mtd->ecclayout->oobavail;  
vary_offset = 0;
/* Fourth test: try to write off end of device */  
pr_info("test 4 of 5\n");//这个是第四步测试,是只针对每一个block的最后一个page写oob,然后再进行验证
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);  
if (err)  
goto out;
addr0 = 0;  
for (i = 0; i < ebcnt && bbt[i]; ++i)  
addr0 += mtd->erasesize;//写的地址是以一个block为单位的,也就是会写到每一个block的最后一下page的oob区
/* Attempt to write off end of OOB */  
ops.mode  = MTD_OPS_AUTO_OOB;  
ops.len   = 0;  
ops.retlen = 0;  
ops.ooblen = 1;  
ops.oobretlen = 0;  
ops.ooboffs = mtd->ecclayout->oobavail;  
ops.datbuf = NULL;  
ops.oobbuf = writebuf;  
pr_info("attempting to start write past end of OOB\n");  
pr_info("an error is expected...\n");  
err = mtd_write_oob(mtd, addr0, &ops);  
if (err) {  
pr_info("error occurred as expected\n");  
err = 0;  
} else {  
pr_err("error: can write past end of OOB\n");  
errcnt += 1;  
}
/* Attempt to read off end of OOB */  
ops.mode  = MTD_OPS_AUTO_OOB;  
ops.len   = 0;  
ops.retlen = 0;  
ops.ooblen = 1;  
ops.oobretlen = 0;  
ops.ooboffs   = mtd->ecclayout->oobavail;  
ops.datbuf = NULL;  
ops.oobbuf = readbuf;  
pr_info("attempting to start read past end of OOB\n");  
pr_info("an error is expected...\n");  
err = mtd_read_oob(mtd, addr0, &ops);  
if (err) {  
pr_info("error occurred as expected\n");  
err = 0;  
} else {  
pr_err("error: can read past end of OOB\n");  
errcnt += 1;  
}
if (bbt[ebcnt - 1])  
pr_info("skipping end of device tests because last "  
"block is bad\n");  
else {  
/* Attempt to write off end of device */  
ops.mode  = MTD_OPS_AUTO_OOB;  
ops.len   = 0;  
ops.retlen= 0;  
ops.ooblen= mtd->ecclayout->oobavail + 1;  
ops.oobretlen = 0;  
ops.ooboffs   = 0;  
ops.datbuf= NULL;  
ops.oobbuf= writebuf;  
pr_info("attempting to write past end of device\n");  
pr_info("an error is expected...\n");  
err = mtd_write_oob(mtd, mtd->size - mtd->writesize, &ops);  
if (err) {  
pr_info("error occurred as expected\n");  
err = 0;  
} else {  
pr_err("error: wrote past end of device\n");  
errcnt += 1;  
}
/* Attempt to read off end of device */  
ops.mode  = MTD_OPS_AUTO_OOB;  
ops.len   = 0;  
ops.retlen = 0;  
ops.ooblen = mtd->ecclayout->oobavail + 1;  
ops.oobretlen = 0;  
ops.ooboffs   = 0;  
ops.datbuf = NULL;  
ops.oobbuf = readbuf;  
pr_info("attempting to read past end of device\n");  
pr_info("an error is expected...\n");  
err = mtd_read_oob(mtd, mtd->size - mtd->writesize, &ops);  
if (err) {  
pr_info("error occurred as expected\n");  
err = 0;  
} else {  
pr_err("error: read past end of device\n");  
errcnt += 1;  
}
err = mtdtest_erase_eraseblock(mtd, ebcnt - 1);  
if (err)  
goto out;
/* Attempt to write off end of device */  
ops.mode  = MTD_OPS_AUTO_OOB;  
ops.len   = 0;  
ops.retlen = 0;  
ops.ooblen = mtd->ecclayout->oobavail;  
ops.oobretlen = 0;  
ops.ooboffs   = 1;  
ops.datbuf = NULL;  
ops.oobbuf = writebuf;  
pr_info("attempting to write past end of device\n");  
pr_info("an error is expected...\n");  
err = mtd_write_oob(mtd, mtd->size - mtd->writesize, &ops);  
if (err) {  
pr_info("error occurred as expected\n");  
err = 0;  
} else {  
pr_err("error: wrote past end of device\n");  
errcnt += 1;  
}
/* Attempt to read off end of device */  
ops.mode  = MTD_OPS_AUTO_OOB;  
ops.len   = 0;  
ops.retlen = 0;  
ops.ooblen = mtd->ecclayout->oobavail;  
ops.oobretlen = 0;  
ops.ooboffs   = 1;  
ops.datbuf = NULL;  
ops.oobbuf = readbuf;  
pr_info("attempting to read past end of device\n");  
pr_info("an error is expected...\n");  
err = mtd_read_oob(mtd, mtd->size - mtd->writesize, &ops);  
if (err) {  
pr_info("error occurred as expected\n");  
err = 0;  
} else {  
pr_err("error: read past end of device\n");  
errcnt += 1;  
}  
}  
/* Fifth test: write / read across block boundaries */  
pr_info("test 5 of 5\n");//最后一步是,对每一个block的第一个page和最后一个page进行写操作。
/* Erase all eraseblocks */  
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);  
if (err)  
goto out;
/* Write all eraseblocks */  
prandom_seed_state(&rnd_state, 11);  
pr_info("writing OOBs of whole device\n");  
for (i = 0; i < ebcnt - 1; ++i) {  
int cnt = 2;  
int pg;  
size_t sz = mtd->ecclayout->oobavail;  
if (bbt[i] || bbt[i + 1])  
continue;  
addr = (loff_t)(i + 1) * mtd->erasesize - mtd->writesize;  
prandom_bytes_state(&rnd_state, writebuf, sz * cnt);  
for (pg = 0; pg < cnt; ++pg) {  
ops.mode  = MTD_OPS_AUTO_OOB;  
ops.len   = 0;  
ops.retlen = 0;  
ops.ooblen = sz;  
ops.oobretlen = 0;  
ops.ooboffs   = 0;  
ops.datbuf = NULL;  
ops.oobbuf = writebuf + pg * sz;  
err = mtd_write_oob(mtd, addr, &ops);  
if (err)  
goto out;  
if (i % 256 == 0)  
pr_info("written up to eraseblock %u\n", i);  
cond_resched();  
addr += mtd->writesize;  
}  
}  
pr_info("written %u eraseblocks\n", i);
/* Check all eraseblocks */  
prandom_seed_state(&rnd_state, 11);  
pr_info("verifying all eraseblocks\n");  
for (i = 0; i < ebcnt - 1; ++i) {  
if (bbt[i] || bbt[i + 1])  
continue;  
prandom_bytes_state(&rnd_state, writebuf,  
mtd->ecclayout->oobavail * 2);  
addr = (loff_t)(i + 1) * mtd->erasesize - mtd->writesize;  
ops.mode  = MTD_OPS_AUTO_OOB;  
ops.len   = 0;  
ops.retlen = 0;  
ops.ooblen = mtd->ecclayout->oobavail * 2;  
ops.oobretlen = 0;  
ops.ooboffs   = 0;  
ops.datbuf = NULL;  
ops.oobbuf = readbuf;  
err = mtd_read_oob(mtd, addr, &ops);  
if (err)  
goto out;  
if (memcmp(readbuf, writebuf, mtd->ecclayout->oobavail * 2)) {  
pr_err("error: verify failed at %#llx\n",  
(long long)addr);  
errcnt += 1;  
if (errcnt > 1000) {  
pr_err("error: too many errors\n");  
goto out;  
}  
}  
if (i % 256 == 0)  
pr_info("verified up to eraseblock %u\n", i);  
cond_resched();  
}  
pr_info("verified %u eraseblocks\n", i);
pr_info("finished with %d errors\n", errcnt);  
out:  
kfree(bbt);  
kfree(writebuf);  
kfree(readbuf);  
put_mtd_device(mtd);  
if (err)  
pr_info("error %d occurred\n", err);  
printk(KERN_INFO "=================================================\n");  
return err;  
}
 
下面就怎么把数据写到oob中去的,分析一下代码的整个过程。我们可以从代码中看到,在oobtest.c的最后都 是调用mtd_write_oob(mtd, addr0, &ops);这个函数来写的。
这里的三个参数分别是:mtd_info,要写入的地址,oob的操作信息。
其中mtd就是我们在函数开头通过ID获取到的mtd,而addr0就是一以page为单位的起始地址,ops是比较重要的:
 
struct mtd_oob_ops {  
unsigned intmode;//操作模式  
size_t  len;//要读或写的数据长度  
size_t  retlen;//已经写入或者读出的数据长度  
size_t  ooblen;//要写,读的oob数据长度  
size_t  oobretlen;//已经写或读出的OOB数据长度  
uint32_tooboffs;//在oob区中要偏移  
uint8_t *datbuf;//数据buff,如果为null表示,只有oob区数据有写入。  
uint8_t *oobbuf;//oob数据buff  
};
 
下面就其中一须写的代码为例,说说是怎么调用的:
ops.mode  = MTD_OPS_AUTO_OOB;//在这里指定,oob区的分布方式,这里用的auto,也就是会把用户的数据区写在oob的前,ecc放在oob区的后面  
ops.len   = 0;//用户数据为0  
ops.retlen= 0;  
ops.ooblen= mtd->ecclayout->oobavail + 1;//指定要写的oob数据的长度  
ops.oobretlen = 0;  
ops.ooboffs   = 0;//偏移为0  
ops.datbuf= NULL;//这里为null,意思就是说只写oob区  
ops.oobbuf= writebuf;  
pr_info("attempting to write past end of device\n");  
pr_info("an error is expected...\n");  
err = mtd_write_oob(mtd, mtd->size - mtd->writesize, &ops);//调用写入。  
 
下面我们来看下这个函数的内部:
static inline int mtd_write_oob(struct mtd_info *mtd, loff_t to,  
struct mtd_oob_ops *ops)  
{  
ops->retlen = ops->oobretlen = 0;//可以看到这个函数一进来,就把返回的数据长度指定为0.  
if (!mtd->_write_oob)//如果mtd层没有指定写oob的函数,则直接返回。  
return -EOPNOTSUPP;  
if (!(mtd->flags & MTD_WRITEABLE))//如果没有使能  
return -EROFS;  
return mtd->_write_oob(mtd, to, ops);//调用mtd层的函数写入。  
}
 
上面我们看,到最后,是调用mtd->_wirte_oob来写的,那这个函数是在哪里被指定的呢?
这个是在nand_base.c中的nand_scan_tail函数中指定的,那这个函数又是在什么时候被调用的呢?这个是在初始化你的nandflash时调用的,这里就不说了,太绕了。
在这个函数中,我们可以看到给_write_oob指定的是下面的一个函数:
 
static int nand_write_oob(struct mtd_info *mtd, loff_t to,  
struct mtd_oob_ops *ops)  
{  
int ret = -ENOTSUPP; 
ops->retlen = 0;
/* Do not allow writes past end of device */  
if (ops->datbuf && (to + ops->len) > mtd->size) {  
pr_debug("%s: attempt to write beyond end of device\n",  
__func__);  
return -EINVAL;  
}//这时百参数检测
nand_get_device(mtd, FL_WRITING);//操作之前先要获取这个device,注意这个会引起休眠的
switch (ops->mode) {  
case MTD_OPS_PLACE_OOB:  
case MTD_OPS_AUTO_OOB:  
case MTD_OPS_RAW:  
break;//可以这看到,这里支持的写oob的布局配置
default:  
goto out;  
}
if (!ops->datbuf)  
ret = nand_do_write_oob(mtd, to, ops);//如果没有用户数据区的数据写入,则调用这个函数。  
else  
ret = nand_do_write_ops(mtd, to, ops);//如果有用户数数据区的数据要写入,则调用这个。  
out:  
nand_release_device(mtd);//释放这个device  
return ret;  
}
 
下面我们具体来看一下写oob的这个函数的实现:
static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,  
struct mtd_oob_ops *ops)  
{  
int chipnr, page, status, len;  
struct nand_chip *chip = mtd->priv;
pr_debug("%s: to = 0x%08x, len = %i\n",  
__func__, (unsigned int)to, (int)ops->ooblen);
if (ops->mode == MTD_OPS_AUTO_OOB)//我们在写时,设置的是这个属性,获取整个oob驱动可供用户使用的availabe的整个长度  
len = chip->ecc.layout->oobavail;  
else  
len = mtd->oobsize;
/* Do not allow write past end of page */  
if ((ops->ooboffs + ops->ooblen) > len) {//检测是否超出了整个oob availabe区的长度,  
pr_debug("%s: attempt to write past end of page\n",  
__func__);  
return -EINVAL;  
}
if (unlikely(ops->ooboffs >= len)) {//如果你在oob availabe区的偏移超出了oob available的长度,也是错误的。  
pr_debug("%s: attempt to start write outside oob\n",  
__func__);  
return -EINVAL;  
}
/* Do not allow write past end of device */  
if (unlikely(to >= mtd->size ||  
ops->ooboffs + ops->ooblen >  
((mtd->size >> chip->page_shift) -  
(to >> chip->page_shift)) * len)) {  
pr_debug("%s: attempt to write beyond end of device\n",  
__func__);  
return -EINVAL;  
}
chipnr = (int)(to >> chip->chip_shift);//这里是计算你所要操作的是哪块nand,这里的chip_shift是表示的nand 的地址所用的bit的个数,  
chip->select_chip(mtd, chipnr);//选择这个nand,也就是把nand的cs拉低。
/* Shift to get page */  
page = (int)(to >> chip->page_shift);计算是写的page
/* 
* Reset the chip. Some chips (like the Toshiba TC5832DC found in one 
* of my DiskOnChip 2000 test units) will clear the whole data page too 
* if we don't do this. I have no clue why, but I seem to have 'fixed' 
* it in the doc2000 driver in August 1999.  dwmw2. 
*/  
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);//关于这里,上面的comment已经说的很清楚了。
/* Check, if it is write protected */  
if (nand_check_wp(mtd)) {  
chip->select_chip(mtd, -1);  
return -EROFS;  
}
/* Invalidate the page cache, if we write to the cached page */  
if (page == chip->pagebuf)  
chip->pagebuf = -1;
nand_fill_oob(mtd, ops->oobbuf, ops->ooblen, ops);//这一句其实是根据oob的分布来给mtd->chip下的oob_poi copy要写的数据。
if (ops->mode == MTD_OPS_RAW)  
status = chip->ecc.write_oob_raw(mtd, chip, page & chip->pagemask);  
else  
status = chip->ecc.write_oob(mtd, chip, page & chip->pagemask);//调用这函数,写入oob,注意第三个参数为page的地址,也就是在  
//整个device中,这个page的地址。  
chip->select_chip(mtd, -1);
if (status)  
return status;
ops->oobretlen = ops->ooblen;
return 0;  
}
 
在上面的函数最后,是调用ecc.write_oob来完成写入操作的,那它又是在哪里指定的呢?
上面的函数也是在nand_base.c中的nand_scan_tail完成的。
 
它所指定的是下面的函数:
static int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip,  
int page)  
{  
int status = 0;  
const uint8_t *buf = chip->oob_poi;  
int length = mtd->oobsize;
chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);//写入地址,这里的,我们可以看到这里的第三个参数据colum的值,这里指定的是整个  
//page的大小,所以这里可以看到,写oob就是写在了整个page的结尾,也就是写oob区。  
chip->write_buf(mtd, buf, length);//这里是调用nand controller驱动中指定的写函数,写入数据  
/* Send command to program the OOB data */  
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);//发送写命令。
status = chip->waitfunc(mtd, chip);
return status & NAND_STATUS_FAIL ? -EIO : 0;  
}
 
上面我们可以看到发送命令和地址是用的是chip->cmdfunc这个函数,而这个就是
static void nand_command_lp(struct mtd_info *mtd, unsigned int command,  
int column, int page_addr)  
{  
register struct nand_chip *chip = mtd->priv;
/* Emulate NAND_CMD_READOOB */  
if (command == NAND_CMD_READOOB) {  
column += mtd->writesize;//如果是读oob,则column就是整个page的长度,也就是从page的结尾起开始读  
command = NAND_CMD_READ0;  
}
/* Command latch cycle */  
chip->cmd_ctrl(mtd, command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
if (column != -1 || page_addr != -1) {  
int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;
/* Serially input address */  
if (column != -1) {  
/* Adjust columns for 16 bit buswidth */  
if (chip->options & NAND_BUSWIDTH_16 &&  
!nand_opcode_8bits(command))  
column >>= 1;  
chip->cmd_ctrl(mtd, column, ctrl);  
ctrl &= ~NAND_CTRL_CHANGE;  
chip->cmd_ctrl(mtd, column >> 8, ctrl);  
}  
if (page_addr != -1) {  
chip->cmd_ctrl(mtd, page_addr, ctrl);  
chip->cmd_ctrl(mtd, page_addr >> 8,  
NAND_NCE | NAND_ALE);  
/* One more address cycle for devices > 128MiB */  
if (chip->chipsize > (128 << 20))  
chip->cmd_ctrl(mtd, page_addr >> 16,  
NAND_NCE | NAND_ALE);  
}  
}  
chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
/* 
* Program and erase have their own busy handlers status, sequential 
* in, and deplete1 need no delay. 
*/  
switch (command) {
case NAND_CMD_CACHEDPROG:  
case NAND_CMD_PAGEPROG:  
case NAND_CMD_ERASE1:  
case NAND_CMD_ERASE2:  
case NAND_CMD_SEQIN:  
case NAND_CMD_RNDIN:  
case NAND_CMD_STATUS:  
return;
case NAND_CMD_RESET:  
if (chip->dev_ready)  
break;  
udelay(chip->chip_delay);  
chip->cmd_ctrl(mtd, NAND_CMD_STATUS,  
NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);  
chip->cmd_ctrl(mtd, NAND_CMD_NONE,  
NAND_NCE | NAND_CTRL_CHANGE);  
while (!(chip->read_byte(mtd) & NAND_STATUS_READY))  
;  
return;
case NAND_CMD_RNDOUT:  
/* No ready / busy check necessary */  
chip->cmd_ctrl(mtd, NAND_CMD_RNDOUTSTART,  
NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);  
chip->cmd_ctrl(mtd, NAND_CMD_NONE,  
NAND_NCE | NAND_CTRL_CHANGE);  
return;
case NAND_CMD_READ0:  
chip->cmd_ctrl(mtd, NAND_CMD_READSTART,  
NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);  
chip->cmd_ctrl(mtd, NAND_CMD_NONE,  
NAND_NCE | NAND_CTRL_CHANGE);
/* This applies to read commands */  
default:  
/* 
* If we don't have access to the busy pin, we apply the given 
* command delay. 
*/  
if (!chip->dev_ready) {  
udelay(chip->chip_delay);  
return;  
}  
}
/* 
* Apply this short delay always to ensure that we do wait tWB in 
* any case on any machine. 
*/  
ndelay(100);
nand_wait_ready(mtd);  
}
 
本文永久更新地址:http://www.linuxdiyf.com/linux/27613.html