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

Linux嵌入式开发实战:如何用MTD工具精准测试NAND Flash读写性能(附实测数据对比) 本文详细介绍了在Linux嵌入式开发中,如何利用MTD工具对NAND Flash进行精准的读写速度测试。通过实战演示mtd_speedtest等工具的使用,并结合实测数据对比不同擦除块大小对性能的显著影响,为芯片选型、分区优化及系统性能评估提供了可靠的数据支撑和方法论。 阅读详情

最近在学习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 int	mode;//操作模式
	size_t		len;//要读或写的数据长度
	size_t		retlen;//已经写入或者读出的数据长度
	size_t		ooblen;//要写,读的oob数据长度
	size_t		oobretlen;//已经写或读出的OOB数据长度
	uint32_t	ooboffs;//在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);
}




【SigmaStar SSC335系列】开发环境搭建 0、前言 2020年对安防和泛智能设备圈而言,绝对是不平凡的一年,HW被制裁,海思安防芯片缺货,芯片价格飞涨,夸张时2包3559A顶二线城市一套房,第一的海思市场份额这块大蛋糕,让人起了无数想象,看看国内突然间冒出的一大堆芯片公司,20年选型选到眼花,也在试错,填过坑,走过弯路,最终用了SStar的方案,从产品的层次、产品丰富度、软件SDK稳定性各方面,都还不错。 现在最新的SDK已经到2021的版本了,不过我们20年开发的,当时版本是0902,只针对该版本的环境搭建。 本文源自老K(蓝海):http 阅读详情

相关推荐

VOWIFI--相关的协议文档

一、3GPP相关协议文档 3GPP 23.402 Architecture enhancements for non-3GPP accesse- //重要 整体框架结构和流程,3GPP和NON-3GPP切换 3GPP 36.304  User Equipment (UE) procedures in idle mode //重要,终端在空闲状态下3GPP与NON-3GPP之间的重选方法   3GP

时九博客 3580

老版本的linux内核中mtd test 出现 mtd_oobtest: error: verify failed at 0xXXXX 问题

最近在做parallel nor的东西,在用mtd/tests/下的mtd_oobtest时,老时有下面的打印: mtd_oobtest: error: verify failed at 0xXXX mtd_oobtest会对相关的device共作五步test,其中1,3,4过了,就是在2,5这两步是老是出现上面的错误, 后来发现,其实这个问题只是在老版本的内核存在,在新内核中没有。

Bean Huo 2606

二、基于C语言的JPEG解码代码详解

// JPEG解码 // 输入JPEG后输出Bitmap #include <stdio.h> #include <stdlib.h> #pragma warning(disable : 4996) unsigned int BuffIndex; // JPEG数据的位置 unsigned int BuffSize; // JPEG数据的大小 unsigned int BuffX; // 图像的横向尺寸 unsigned int BuffY; //

m0_37633745的博客 4455

使用linux的MTD tests support测试flash性能

在嵌入式linux开发过程中,经常会使用到nor flash,nand flash等存储设备,由于flash的芯片型号和接口类型较多,性能不一,我们需要对系统中使用的flash性能进行分析,并对设备工作的稳定性进行测试。 flash设备在嵌入式系统中,会被驱动为MTD分区,在linux kernel中,提供了针对MTD的测试模块,方面开发人员对flash的功能,性能,稳定性等进行测试。 使用m

gp_scorpius 6765

从零构建高精度温控系统:ADN8835的单电感拓扑与0.01℃稳定性实战解析

本文深入解析了如何利用ADN8835构建高精度温控系统,重点介绍了其创新的单电感拓扑架构如何实现0.01℃的长期稳定性。内容涵盖精密PCB布局、电源滤波设计,并详细阐述了针对不同热容负载的数字PID控制参数整定策略,为开发精密仪器和医疗设备提供了实战指南。

r2s3t4的博客 238

【深圳】IBM V3500存储维修 更换控制器故障

今天给大家分享的是深圳.福田区一个电商客户公司一台IBM Storwize v3500存储控制器故障从远程诊断 收集日志 分析结果的实际案例,如图所示通过存储管理口登陆进去后直接看到少了一个控制器,经过工程师分析日志后判定为该控制器损坏,接下来更换控制器配件,标准套件的控制器不带子卡和电池,所以需要把故障控制器上的子卡和电池拆下来装到新控制器上,这个电池拆还需要点技巧,接着把新的控制器插回V3500主机,等待大约十分钟后,新控制器指示灯全绿,管理界面看到节点已联机;冠峰科技-爱科技爱分享。

OuYangYuXi的博客 4579

linux 内核 mtd读取,MTD坏块管理(二)-内核获取Nandflash的参数过程

MTD坏块管理机制中,起着核心作用的数据结构是nand_chip,在此以TCC8900-Linux中MTD的坏块管理为例作一次介绍。MTD在Linux内核中同样以模块的形式被启用,TCC_MTD_IO_Init()函数完成了nand_chip初始化、mtd_info初始注册,坏块表的管理机制建立等工作。nand_chip在TCC_MTD_IO_Init函数中的实例名称是this,mtd_info ...

weixin_34093995的博客 1346

从数据到行为的可控暴露:SAP RAP 中的 BO Projection 与 BO Interface 全面实践指南

本文系统梳理了ABAP RAP中BO Projection与BO Interface的定位与实现方式。BO Projection面向业务服务(如UI/API),通过CDS投影视图(as projection on)和行为定义裁剪字段与操作,实现差异化暴露;BO Interface则为EML代码和跨组件复用提供稳定访问层,使用transactional_interface标识。两者均遵循C_/I_命名规范,通过分层设计实现封装性、安全性和灵活性。文中结合真实案例展示了如何通过不同投影满足多角色需求,并详细说明

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 251

MTD(3)---nand flash的erase,read,write接口函数代码分析 .

本来是想按照代码流程往下讲bbt的,但是写着写着,还是要先介绍下mtd的几个基本flash读写擦函数接口。那就调整下,先讲基本接口函数,再讲到bbt的时候,就不用回头来讲基本读写函数了,这样主线清楚些。 忽然觉得我讲的流程有些乱:) 还没有讲flash的具体操作命令,要是穿插在下来的章节里面讲,会更乱,那就在这里补充下吧:)   前面这章已经提到了一些东西,但我光顾着分解代码了,没有把他们

gjy938815的专栏 1799

[Dify实战] 合同审阅助手:识别风险条款、生成修改建议-带绑定资源

摘要: Dify合同审阅助手通过AI技术提升合同审阅效率,快速识别风险条款并输出结构化修改建议。方案包括合同输入、关键条款识别、风险评级和报告生成流程,重点关注付款、责任限制等核心条款。采用分层风险分级(高/中/低)和"红线清单"机制,确保审阅标准化。实施建议先建立条款库和模板,通过样本测试优化模型,定期更新规则库。该方案可缩短审阅时间50%以上,提高风险识别准确率,最终实现法务经验的可复用沉淀。

技术小甜甜:传播可落地技术,帮助个人和企业降低解决真实场景问题的成本 597

【YOLOv8科研级优化】基于YOLOv9的GELAN模块重构C2f结构,实现精度与效率的同步提升

打开确保模型解析器能识别GELANBlock模块。通过将YOLOv9的GELAN模块集成到YOLOv8的C2f模块中,我们实现了一个“强特征表达+高效计算”的目标检测模型。这个改进不仅在工程上能提升复杂场景下的检测效果,更在科研上提供了“多分支特征融合”的创新思路——这种思路可以延伸到其他检测网络(如Faster R-CNN、SSD)的改进中,帮你在论文中打造独特的研究点。

know_F0727的博客 203

linux中mtd动态加载,将新的Linux kernel中的MTD test加入到旧的kernel中

Linux Kernel自从2.6.29之后,就加入了MTD test模块,即几个测试模块,用于测试你的mtd的特性。分别是:mtd_oobtest.c //测试oob数据读写是否正常mtd_pagetest.c //测试page读写mtd_readtest.c //测试读功能mtd_speedtest.c //测试mtd的速度mtd_stresstest.c //压力测试mtd_subpaget...

weixin_36039921的博客 998

【目标检测】基于yolov5的安全帽佩戴检测系统(附代码和数据集)

【目标检测】基于yolov5的安全帽佩戴检测系统(附代码和数据集)

学无止境、积少成多、厚积薄发 3107

Linux内核4.14版本——mtd子系统(3)——字符mtd设备(mtdchar.c)

mtdchar.c简单分析

yangguoyu8023的博客 1948

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

squash文件系统挂载处理坏块内核5.10.0

squash linux5.10.0,nand 坏块

qq_18804879的博客 841

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

关于MTD你需要知道的几点?

关于MTD你需要知道的几点

Cdy126的博客 3539

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

spi nand MTD层代码流程分析

MTD层调用驱动代码流程     read mtd_read mtd-&gt;_read nand_read nand_do_read_ops nand-&gt;cmdfunc chip-&gt;ecc.read_page chip-&gt;read_buf     write mtd_write mtd-&gt;_write nand_...

TSZ0000的博客 2272

SylixOS中MTD调用底层接口流程分析

1. MTD设备与底层驱动的关系   MTD设备是一种特殊的抽象设备,它用于简化驱动开发。它是底层硬件和上层软件的桥梁,无论对Nand Flash或是Nor Flash,它都提供了统一的框架供上层文件系统使用。对于底层驱动,只需按照各自硬件差异实现MTD中要求的接口即可。  MTD设备同时实现了Nand驱动的通用访问流程,对于上层文件系统的读、写等访问,MTD设备中都封装了接口,对于Nand驱动的...

weixin_33920401的博客 246

linux 读写flash 测试,使用linux的MTD tests support测试flash性能

本文为gp_scorpius原创文章:http://blog.csdn.net/gp_scoprius/article/details/53257056在嵌入式Linux开发过程中,经常会使用到nor flash,nand flash等存储设备,由于flash的芯片型号和接口类型较多,性能不一,我们需要对系统中使用的flash性能进行分析,并对设备工作的稳定性进行测试。flash设备在嵌入式系统中...

weixin_39958138的博客 1791

linux下的nandflash驱动分析(3)——基于s3c6410平台

在上一篇中probe函数中的一个很重要的函数nand_scan函数,现在来说另外一个很重要的函数add_mtd_partitions函数,add_mtd_partitions()会对每一个新建分区建立一个新的mtd_part 结构体,将其加入mtd_ partitions中,并调用add_mtd_device()将此分区作为MTD设备加入mtd_table。成功时返回0,如果分配mtd_part时

思考的智慧,知识源于思考 3407

linux mtd 块设备,基于块设备子系统的MTD子系统(2.6.26)

基于块设备子系统的MTD子系统(2.6.26)作者:guoleleblog:http://blog.csdn.net/guolele2010这图说明了MTD子系统的架构,那么它跟块设备子系统有什么关系?其实MTD就是利用了块设备子系统,只是它中间多了一层MTD设备驱动层。在drivers/mtd/mtdblock.c里,static int__init init_mtdblock(void){re...

weixin_36269141的博客 363

linux编写驱动后write已杀死_【Linux Kernel】 读码时写的一些笔记,毕竟写给自己看,有些乱(好好理解kernel,能更好写出高效的驱动和应用代码)...

bh处理1.三种旧式的bottom half处理类型IMMEDIATE_BH:driver注册入tq_immediate队列,等待调度TQUEUE_BH:执行tq_timer,到系统tick产生时触发TIMER_BH:直接绑定到do_timer()处理函数2.mark_bh()将激活32个BH中的某一个,触发软中断来进入被调度状态此种软中断优先级为HI_SOFTIRQ3.tasklet此方法将触发...

weixin_39640262的博客 1909

Linux下读写FLASH驱动——MTD设备分析

  最近在学习驱动读写flash的代码部分。经历了可笑的过程:开始我知道flash用通过spi口来读写。所以就到了driver/spi 下面看相关代码。发现有个spidev.c里面有read/write/ioctl等函数。而且还有一个davinci_spi_master.以为调用spi驱动的时候会首先调用到这里,于是就想怎么在上层应用里将spidev.c里open调用到就可以了。最后修改了一些地方...

程序员「阿基米东」的编程之旅 1万+

uboot2021.10-nandflash-1.nand_fill_oob

本文只是对该函数的注释,方便之后查阅,可能有误,请高手指正。 /** * nand_fill_oob - [INTERN] Transfer client buffer to oob * @mtd: MTD device structure * @oob: oob data buffer * @len: oob data write length * @ops: oob ops structure */ static uint8_t *nand_fill_oob(struct mtd_inf

嵌入式 arm 单片机 入门 视频 培训 605

深入探讨MTD子系统的实现原理和相关技术

在中,MTD子系统是一种非常重要的子系统。它负责管理Flash存储设备并向用户空间提供接口。在本文中,我们将深入探讨MTD子系统的实现原理和相关技术。MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化硬件驱动框架。本文基于3.14内核,讨论MTD驱动框架。

shengyin714959的博客 2242
上一篇: uboot下关于quad spi nor 的一点点
下一篇: 关于nand flash 特殊 features的一点点
Bean Huo
博客等级 码龄19年 40粉丝 48原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值