最近核心板进行了改版,以前两个bootloader放在dataflash里面,现在板子上只留了nand,所以bootstrap,u-boot,kernel,fs全存放在了nand上。同时,除了原有的256m,也新改出了一批64m nand的板子,针对这两种情况,需要对bsp进行修改。
同为256m的相对好改,只涉及到samba的烧写流程,偏移量,镜像生成等,没啥好说的。问题主要出在64m nand,由于原有的256使用的是yaffs2,而64m则是yaffs,所以有一定修改量,也出了些问题。我们主要靠u-boot烧写内核及fs的镜像,而问题就出现在fs的烧写上。最诡异的问题是,几块板子有的一直没问题,剩下的有时候有问题有时候又没问题,很难定性问题.
bootstrap的修改参考:
http://blog.csdn.net/kailan818/archive/2009/12/14/5004934.aspx
u-boot的镜像烧写修改参考:
http://blogold.chinaunix.net/u3/90065/showart_1780393.html
mkyaffsimage自然也不能用以前256m使用的工具,图省事,我拿了tq2440开发板自带的为64m内存提供的工具,谁知道其实给我添了不少麻烦。
bsp修改完,用samba把bootstrap,u-boot烧写到nand上,随后再用u-boot通过u盘烧写kernel,fs。完毕重启后进入系统发现,只要有写操作,yaffs就会报page xxx in gc has no object....的错误。跟踪发现这是yaffs损耗平衡出错信息。问题是将将写一点东西,怎么会需要做损耗均衡,并且出错呢?经过一长串痛苦的跟踪,发现最终是ecc对不上的问题。
问题看来出在镜像烧写上,那么我们来review一下后来添加的代码:
343 }else if ( s != NULL && !strcmp(s, ".yaffs")){
344 nand_write_options_t opts;
345 memset(&opts, 0, sizeof(opts));
346 opts.buffer = (u_char*) addr;
347 opts.length = size;
348 opts.offset = off;
349
350 opts.noecc = 1;
351 opts.writeoob = 1;
352 opts.blockalign = 1;
353 opts.quiet = quiet;
354 opts.skipfirstblk = 1;
355 ret = nand_write_opts(nand, &opts);
最终证明,第350行导致出错,那么一步步跟下去,看看为什么我这里无法采用这个选项。
进入nand_write_opts:
338 /* write without ecc? */
339 if (opts->noecc) {
340 memcpy(&meminfo->oobinfo, &none_oobinfo,
341 sizeof(meminfo->oobinfo));
342 oobinfochanged = 1;
343 }
这里使用none_oobinfo:
266 static struct nand_oobinfo none_oobinfo = {
267 .useecc = MTD_NANDECC_OFF,
268 };
可以看到,这里的ecc标志是eccoff。
继续往后走:
395 /* get data from input and write to the device */
396 while (imglen && (mtdoffset < meminfo->size)) {
。。。。。。
454 /* read page data from input memory buffer */
455 memcpy(data_buf, buffer, readlen);
456 buffer += readlen;
457
458 if (opts->writeoob) {
459 /* read OOB data from input memory block, exit
460 * on failure */
461 memcpy(oob_buf, buffer, meminfo->oobsize);
462 buffer += meminfo->oobsize;
463
464 /* write OOB data first, as ecc will be placed
465 * in there*/
466 result = meminfo->write_oob(meminfo,
467 mtdoffset,
468 meminfo->oobsize,
469 &written,
470 (unsigned char *)
471 &oob_buf);
。。。。。。
480
481 /* write out the page data */
482 result = meminfo->write(meminfo,
483 mtdoffset,
484 meminfo->oobblock,
485 &written,
486 (unsigned char *) &data_buf);
下面开始按页写数据,从内存地址内读镜像向nand里写。注意由于是64m,所以是512+16。第466行调用writeoob先写16字节的oob数据。在第482行调用write开始写页。注意这里每页写完也根据情况计算ecc并且重填前面的oob区,分析我们关心的write,其实是nand_write:
1620 static int nand_write (struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf)
1621 {
1622 return (nand_write_ecc (mtd, to, len, retlen, buf, NULL, NULL));
1623 }
再进入nand_write:
1679 /* if oobsel is NULL, use chip defaults */
1680 if (oobsel == NULL)
1681 oobsel = &mtd->oobinfo;
1682
1683 /* Autoplace of oob data ? Use the default placement scheme */
1684 if (oobsel->useecc == MTD_NANDECC_AUTOPLACE) {
1685 oobsel = this->autooob;
1686 autoplace = 1;
1687 }
1688 if (oobsel->useecc == MTD_NANDECC_AUTOPL_USR)
1689 autoplace = 1;
由于nand_write传入的oobsel是null,所以这里会选择我们以前设置的结构体,用的也是我们指定的校验方法,后面开始写页:
1706 /* Loop until all data is written */
1707 while (written < len) {
。。。。。。
1715 ret = nand_write_page (mtd, this, page, &oobbuf[oob], oobsel, (--numpages > 0));
再进去看一看:
903 int eccmode = oobsel->useecc ? this->eccmode : NAND_ECC_NONE;
。。。。。。
915 switch (eccmode) {
916 /* No ecc, write all */
917 case NAND_ECC_NONE:
918 // printk (KERN_WARNING "Writing data without ECC to NAND-FLASH is not recommended\n");
919 this->write_buf(mtd, this->data_poi, mtd->oobblock);
920 break;
。。。。。。
932 default:
933 eccbytes = this->eccbytes;
934 for (; eccsteps; eccsteps--) {
935 /* enable hardware ecc logic for write */
936 this->enable_hwecc(mtd, NAND_ECC_WRITE);
937 this->write_buf(mtd, &this->data_poi[datidx], this->eccsize);
938 this->calculate_ecc(mtd, &this->data_poi[datidx], ecc_code);
939 for (i = 0; i < eccbytes; i++, eccidx++)
940 oob_buf[oob_config[eccidx]] = ecc_code[i];
。。。。。。
955 this->write_buf(mtd, oob_buf, mtd->oobsize);
这里会根据我们选择的ecc方式来做。首先我们看到,不管是无ecc,还是注释掉noecc=1后走的default分支,都是先write_buf这个512字节的data区,如果是noecc,那么就打住了。由于镜像中本身就有mkyaffsimage时生成的ecc,所以这里只是单纯的拷贝。而下面的分支,则会在写完nand以后,再进行一个ecc计算,随后根据计算结果重填前面已写过的oob区的相关位。938行计算ecc,940重填,第955行重写oob。这里noecc也会重填,不过由于无修改,所以无所谓。
为什么u-boot在mkyaffsimage已经算过ecc的情况下还要再计算一次才正确呢?其实这里有一个ecc配合的问题。ecc有mtd及yaffs两种,算法各不相同,所以一定要对应上。内核里编译选项选择的是let yaffs do its own ecc,那么镜像制作也一定要按照这个格式。这里由于偷懒,直接拿别人的工具,没源码也不知道实现,u-boot烧写部分又照抄网上的文章没仔细研究,内核也按照以前的需求来配的,所以3个都没有对上,导致了错误。除了u-boot的noecc=1去掉,也可以保留这里不动,将内核里let yaffs do its own ecc去掉,
让mtd层来做,
一样可以。总之镜像的校验和内核使用的ecc一定要对上。
其实最终的问题解决,只是注释掉一个noecc,或者改一个内核选项,但足足调了n久。所以网络的方便使得问题的解决变的简单,但有时候也会带来麻烦。在可能的情况下,还是要对抄来的代码进行研究,了解背后的原因,知其所以然,否则不但学不到东西,还有可能导致莫名其妙或者难以解决的问题。