最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【经验与教训】从Uboot中移植已经work的nand flash驱动到Kernel中,始终工作不正常

经验和教训 crifan 3041浏览 0评论

【现象】

已经在Uboot中实现了nand flash的驱动。

接下来要将其移植到kernel中,由于整体都是mtd的架构,所以,移植工作,相对不难,只是需要点细心和时间而已。

在移植之后,加入了yaffs2的支持,一切都很顺利,但是,在最后mount rootfs的时候,

yaffs2始终打印出这样的错误:

**>> Block 68 needs retiring

**>> Block 69 needs retiring

。。。

【解决过程】

1.开始以为是yaffs2的问题,后来在重新制作了一个可以用的makeyaffs2image工具,

用其制作了一个可以用的yaffs2的rootfs镜像,用已经加了write.yffs2支持的uboot烧写进去,

结果kernel挂载此yaffs2的rootfs,还是出现上面同样的错误。

2.然后就开始了漫长的调试,在yaffs2中,一点点加入打印代码,最后定位到问题出到,

static int yaffs_WriteNewChunkWithTagsToNAND(struct yaffs_DeviceStruct *dev,
const __u8 *data,
yaffs_ExtendedTags *tags,
int useReserve)
{
。。。

   writeOk = yaffs_WriteChunkWithTagsToNAND(dev, chunk,
data, tags);

   printk("After write chunk&tag: writeOk=%dn", writeOk);

   if(!bi->skipErasedCheck)
writeOk = yaffs_VerifyChunkWritten(dev, chunk, data, tags);
。。。

}

在yaffs_VerifyChunkWritten之后,writeOk的值就错了,

yaffs_VerifyChunkWritten() -> yaffs_ReadChunkWithTagsFromNAND() -> nandmtd2_ReadChunkWithTagsFromNAND() -> mtd->read_oob() -> nand_write_oob() -> nand_do_write_ops() -> chip->write_page() -> nand_write_page() -> chip->ecc.write_page() -> nand_write_page_hwecc() -> chip->write_buf() -> as353x_nand_write_buf_swbch4() ,

在这个写buf函数之后,yaffs_VerifyChunkWritten() 中,就会和之前写入的数据和tag进行对比,

发现每次写入函数都OK,但是读出来的都是空的0xFF,实际就是写入nand flash数据失败。

3.知道原因了,就一点点排除,将现在kernel里面的函数,和之前uboot中的对应的函数,一点点进行对比,但是始终没有解决问题。后来,无意间一次,发现数据读写对了,但是,在去除一堆垃圾的刚加入的debug后,数据写入又不对了,此时怀疑,是不是此bug,是随机出现的? 如果是,那么就难调试了。但是,好像就是,一直也找不到具体的原因。

4.在最后的错误排查完全确定没问题后,保留仅有的一些debug加入的代码后,此时write_buf函数工作正常。然后就单独将

static void as353x_nand_write_buf_swbch4(struct mtd_info *mtd,
const u_char *buf, int len)
{
int i;
u32 *p =(u32 *)buf;
struct as353x_nand_info *info = as353x_nand_mtd_toinfo(mtd);

if(len < 4)
len = 4;
len /=4;

writeb( NAF_CONTROL_RESET_FIFO, info->regs + NAF_CONTROL);
/* set to SLC mode */
naf_clr_bits_b(NAF_CONTROL_BCH_BIT, NAF_CONTROL);
/* do not remove this for write oob buf need this */
writew( NAF_MODE_DATA_WRITE_NO_ECC, info->regs + NAF_MODE );

writel( len, info->regs + NAF_WORDS );
for (i=0; i<len; i++){
while (as353x_fifo_isfull(mtd));
writel( p[i], info->regs + NAF_FIFODATA);
}

as353x_wait_fifo_got_empty_and_rdy(mtd);

if (need_show_write_buf_data)
{
show_data("write_buf_swbch4", buf, 32);
}
}
中的用于调试的,打印数据的函数show_data()注释掉,

以及read_buf中的show_data()也注释掉,此时发现数据写入就失败了,此时在一点点定位,单独保留read_buf中的show_data(),也还是有问题,而单独保write_buf中的show_data(),函数工作就正常。

此时,再仔细和uboot中的nand flash驱动相关函数对比发现,才明白根本的原因:

uboot中,当时觉得nand_base.c中的nand_wait()太麻烦,就自己单独实现了简单的等待函数:

static int as353x_nand_wait(struct mtd_info *mtd, struct nand_chip *this)
{
udelay(this->chip_delay);

/* wait until command is processed */
while (!this->dev_ready(mtd));

return 0;
}

然后初始化时候,挂上函数指针:

nand->waitfunc = as353x_nand_wait;

而kernel中,没有实现nand的wait函数,所以mtd架构中,nand_base.c中,就有用默认的nand_wait(),

而默认的mtd在调用底层write_buf函数,写了页数据之后,就会调用这个wait函数等待nand完成。

此处默认的nand_wait()很复杂,而且实际上对于我这里的工作不太正常,然后加了自己的nand_wait函数后,就可以了。

对应地,前面调试中出现的现象,多加了一个打印数据的函数,write_buf写就正常,否则就不正常,就是因为加了打印后, 打印许多数据需要很多时间,这个时间,对于写入数据之后所需要的等待时间来说,足够了,所以,就可以正常的写入数据了。而没加入的足够多的等待时间的话,nand 数据很可能还在芯片内部的页缓存中,而没写入到chip中呢。

【教训】

这个看似简单的bug,解决起来,真是令人头大。

以后,记住了,对于可以已经有了可以work的函数或驱动,在移植的时候,一定要尽可能地全部都对应地实现,而不能像这里漏了个别的函数,导致很隐晦的bug,实现很浪费时间和精力。

【后记】

后来又经过一段调试,结果测出来,真正的原因,并非完全是上面说的。

上面的结论正确的是,在write_buf之后,是要加入一个delay,延迟一定的时间,才能保证数据真正写入到Nand Flash里面去了。

但是,由于nand_base.c中的nand_wait()的实现的逻辑是,对于nand,先设定一个超时,然后判断,在这个时间之内,如果dev_ready()或通过发nand read staus命令 0x70然后读取nand的状态,来判断nand flash是否是已经ready了,最后总是通过发命令0x70,读status作为返回值。

此处我这里的的现象是,好像nand的controller或者chip有问题,发读状态命令去读取状态,结果返回的是0xFF,不太正常。而单独的通过dev_ready()去判断,其实通过读取nand controller的status状态寄存器,对应的位为ready,来判断设备是否ready,实际结果每次都是设备是ready了,但是实际flash chip好像没有真正操作(program page或者erase block等)完成,所以,数据没有真正写入。

此处最后用的解决办法,只有在write_buf()后,加了udelay(100);每写一此buf,延迟100微妙,而此处用的4K pagesize的nand,每页对应4K/512=8次write_buf的操作,一共相当于delay了100*8=800us。

这和对应的datasheet中的描述,倒是很接近,因为此nand flash的chip是MLC的,相近的datasheet中的关于program page的typical时间就是800us,最长时间好像是2ms。

说到这里,要说一下,见过别人的文章里提到,一般MLC的page program的时间,即写的时间,要比SLC慢不少。因此才清楚,为何同样的程序,去write_buf(),之前为何可以正常工作,而这里不能了。

因为之前用的是2K pagesize的nand,记得是SLC,其page program的时间要比现在用的4K pagesize的nand快不少,对应的等待时间就很短,而默认的我之前实现的那个nand_wait()中,udelay(20),左右,再加上其他程序执行的时间,就差不多正常够page program了,而对于4K的nand,就不够page program的时间,就要额外多等待一些时间。

至此,算是比较清楚了问题的原因了。

而对应的教训,也就没有太多可以总计的,毕竟还是由于自己对硬件特性等方面,考虑不全。

费了1,2天调试类似的bug:write_buf()工作不正常,实际没有去program page数据,

到此为止,也算有个了解,以后,会注意的。

【后记2】

然后,又经历了漫长的调试,将现在Uboot中的nand flash的驱动,和同样硬件情况下,别处的代码,进行了比较,最后调试的结果是,自己在write_buf之后,没有等待FIFO变成空,而直接就执行接下来的0x10命令(page program的命令是先0x80,然后写入数据,再0x10真正开始烧写)去烧写,因此FIFO中的数据,没有完全传输到nand chip中的那个page buffer,(估计chip自己判断,如果没有传送全部的数据进来,就不烧写数据)所以,没有写入数据,而在write_buf之后加入对应的函数,读取nand controller的status寄存器,判断对应的fifo empty and ready 位,为真,才继续执行接下来的代码,这样数据就真正的从FIFO传输到nand chip中的page buffer了,再发送0x10就可以真正写入数据了.
所以,所总结出来的教训就是,还是没有真正了解nand flash底层工作机制的具体细节,还有没真正看懂别人的代码,因此,逻辑没有完全理解,实现出来的代码自然有bug,却误以为是硬件的bug,呵呵。
不过,对于前面提到的,在page program之后,去发送0x70再读取chip状态,结果一直是0xFF,
这点看起来,应该是nand controller的硬件bug,但是上面提到的借鉴的那套代码,却是可以正常读取状态了。看来,很可能还是自己哪里没有完全注意到。等到以后再说了,暂时只是用判断flash ready 和flash got ready即可确定操作完全。

[后记3]

最后证实,的确是自己的硬件的bug,导致读取chip status有问题,后来加了一些辅助性的代码,弄了个workaround的版本的,至少可以暂时凑合用了。

不过,后来验证了那套software ECC BCH4的代码,在uboot中是可以正常write_buf去写入数据的的,但是在Linux Kernel中却必须像以前一样,要加入一定的延迟udelay(100),才可以正常工作,真是很奇怪,此问题,有待后续验证或解决。

【后记4】

后来,在kernel中,无意间发现,Linux kernel中的代码,在为nand flash设置期望的,和uboot中一样的,工作频率56MHz,但是实际却只设置了24MHz,然后去找原因,发现当前Nand 的频率是source是BOOT,即晶振的24MHz,而不是希望的PLL0或PLL1,然后最后找到原因,是移植ccu(chip control unit)驱动时候,作者没把某个宏设置为1,导致nand 去调用ccuSetClockSource设置频率时,发现没有设置nand的div,即没有用任何的divider,所以就直接从Boot中获得频率了,因此nand的频率的source就是boot了。

将那个宏去定义之后,再调用

ccuSetClockSource(CCU_NAND, CCU_SRC_PLL1);

设置nand的source为PLL1,然后nand驱动中,在去clk_enable(info->clk);,再去clk_set_rate(info->clk, NAF_DEF_FREQ);,其中

#define NAF_DEF_FREQ       (56 * 1000 * 1000)

就可以正常的设置nand的频率了,希望设置的是56MHz,实际底层驱动设置了一个最接近的值为54MHz,

这样,nand就可以正常工作了。对应地,上面的那个问题,即

在uboot中,设置为56MHz的nand驱动,SW BCH4,write buffer函数是工作正常的,可以将数据写入nand,而在Kernel中,同样的驱动,同样的设置的频率,SW BCH4,write buffer函数工作却不正常,虽然代码运行表现为已经将数据写入nand controller的data寄存器了,而且等到FIFO变空了,函数才返回,但是实际上,数据并没有真正写入nand flash里面。

此时,以为自己知道了原因,以为是:

kernel代码中,去设置了56MHz的频率,但是实际上,由于驱动没设置好,实际上nand的频率是24MHz,所以,在write buffer的时候,数据虽然写入nand controller的data寄存器,而且FIFO也为空了,但是由于频率太低,而导致数据没有及时真正的传送到nand flash芯片里面,所以,如果不加入等待时间,而函数直接返回,数据就丢失了,就没有真正的写入数据。而当nand的工作频率提高到56MHz(实际是54MHz),由于工作频率够快,所以,当nand controller的FIFO变空的时候,数据也真正的写入到nand chip了,就不需要添加额外的等待时间了。

经验和教训总结起来就是:在驱动移植的时候,还是需要再谨慎小心,找找两者之间的不同,及时发现区别,才更容易找到问题原因所在。

【后记5】

以为上面的推断是对的,结果实际测试后发现,不论是normal version还是speedup version的nand驱动,SW BCH4,对于write buffer,都还是要加那个delay,才能正常将数据写入nand,不加delay,还是不能工作,真是很无语,仍然是不知道此问题的根本原因。。。

转载请注明:在路上 » 【经验与教训】从Uboot中移植已经work的nand flash驱动到Kernel中,始终工作不正常

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (2)

  1. 博主强人啊,帮了我大忙。特地登录感谢!
    hacuna_matata13年前 (2011-11-22)回复
  2. 太好了,真的谢谢lz,牛
    XXWL7212201GCE13年前 (2011-06-25)回复
82 queries in 0.163 seconds, using 22.00MB memory