最近在学习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);
}
本文永久更新地址://m.ajphoenix.com/linux/27613.html