Chinaunix首页 | 论坛 | 博客
  • 博客访问: 516256
  • 博文数量: 87
  • 博客积分: 4086
  • 博客等级: 上校
  • 技术积分: 900
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-23 15:55
文章分类

全部博文(87)

文章存档

2012年(3)

2010年(13)

2009年(7)

2008年(64)

我的朋友

分类: LINUX

2010-06-28 19:33:30

这次移植U-Boot,花了一些时间来完成yaffs烧写功能,网上这方面的文章是挺多,但是做的过程中还是遇到了一些问题,特记录下来,供大家参考。

本次使用的U-Boot版本为2010.06-rc2,硬件是三星的2440。目前U-Boot中NAND等驱动绝大部分来自于linux的mtd,本次读U-Boot的NAND驱动代码也就顺便了解了linux的mtd实现,可以说是一举两得O(∩_∩)O~以后有时间就写一篇这几天跟踪这段代码的心得。

好了,言归正传。U-Boot烧写yaffs功能是属于nand命令的子命令,在文件common/cmd_nand.c的do_nand函数中找到如下代码:

if (!s || !strcmp(s, ".jffs2") ||
            !strcmp(s, ".e") || !strcmp(s, ".i")) {
            if (read)
                ret = nand_read_skip_bad(nand, off, &size,
                             (u_char *)addr);
            else
            {
                ret = nand_write_skip_bad(nand, off, &size,
                              (u_char *)addr);
            }
        }

从上述代码可以知道,nand flash的write功能是通过函数nand_write_skip_bad完成的,因此我们要实现yaffs的烧写,为简单起见也选择这个函数来完成。因此在上面的代码中添加下列代码来进入yaffs的烧写处理流程:


else if (!strcmp(s, ".yaffs")) {
            if (read)
                printf("Nand read yaffs not supportted yet!\n");
            else
            {
                writeYaffs = 1;
                ret = nand_write_skip_bad(nand, off, &size,
                              (u_char *)addr);
            }

        }

这样添加以后,烧写yaffs的命令就为:nand write.yaffs mem_addr nand_off img_size,其中第一个参数是yaffs镜像在内存中的地址,第二个参数是要烧写到nand的开始位置,第三个参数是要烧写的yaffs镜像的大小。

需要注意的是,在上面这段代码中使用了writeYaffs这个变量,它是我新增的一个全局变量,在common/cmd_nand.c中定义,以后会在其他地方引用。它的作用是用来区别是烧写yaffs还是烧写普通的数据到nand中,后续分析中将会提及。

可以看到我们烧写yaffs还是使用的nand_write_skip_bad这个函数,这个函数定义在drivers/mtd/nand/nand_utils.c文件中。这个函数首先会检查传入的参数:一是offset,表示要烧写到nand的偏移量;二是length,表示要烧写的长度。

如果我们要烧写的是yaffs文件系统,那么要烧写的长度必须是整数个page,比如我使用的flash为K9F1208,一个page就是512Bytes,其中每个page还有另外16Bytes的OOB数据。由于后面函数中使用到的length这个参数都表示的是data的长度,即不包括oob数据的长度;而我们输入的时候为了方便还是直接输入整个镜像文件的长度,因此为了使后面的代码happy,我们这里对length进行一下调整,在nand_write_skip_bad的最前面添加下面一段代码:

if (writeYaffs)
    {
        *length -= (*length / (nand->writesize + nand->oobsize)) * nand->oobsize;
        left_to_write = *length;
    }

该代码的功能是把要写的总长度减去yaffs镜像中的oob数据的长度。这里的writeYaffs就是在刚才的commom/cmd_nand.c文件中定义的变量,在本文件中需要声明一下这个外部变量,不然编译的时候会出现错误。

然后,该文件中还有一处需要修改。还是在刚才提到的nand_write_skip_bad函数中,找到下面一段代码:


rval = nand_write (nand, offset, &write_size, p_buffer);
        if (rval != 0) {
            printf ("NAND write to offset %llx failed %d\n",
                offset, rval);
            *length -= left_to_write;
            return rval;
        }

这段代码的下面有一行代码是:

p_buffer += write_size;

把这行代码修改为:

if (writeYaffs)
        {
            p_buffer += (write_size + write_size / nand->writesize * nand->oobsize);
        }
        else
        {
            p_buffer += write_size;
        }

表示如果是写入yaffs文件系统,那么偏移量应该多加一个oob数据的大小。然后在该函数(nand_write_skip_bad)的return这一行之前加入下面一行代码:

writeYaffs = 0;

到这里,这个文件就修改完成了。

然后还需要修改drivers/mtd/nand/nand_base.c这个函数。在这个文件中找到nand_do_write_ops函数,然后找到这个函数中的下面几行代码:

if (unlikely(oob))
            oob = nand_fill_oob(chip, oob, ops);


        ret = chip->write_page(mtd, chip, wbuf, page, cached,
                       (ops->mode == MTD_OOB_RAW));

这里就是在完成oob数据的填充后调用底层的函数把数据写入代码中,而这个oob不是我们想要的,我们要写入的oob数据在制作yaffs根文件系统镜像的时候已经填充好了(除了ecc部分),因此我们就在写入之前强制把oob数据放到指定的地方,写入的时候将写入我们的oob数据。需要修改的就是把上面那几行代码中插入一段拷贝oob数据的代码,上面几行代码修改后为如下几行:

if (unlikely(oob))
            oob = nand_fill_oob(chip, oob, ops);

        if (writeYaffs)
            memcpy(chip->oob_poi, wbuf + bytes, mtd->oobsize);

        ret = chip->write_page(mtd, chip, wbuf, page, cached,
                       (ops->mode == MTD_OOB_RAW));

表示如果写的是yaffs数据,那么就拷贝oob到chip->oob_poi指向的地方,底层函数在写入数据时会将这个地方的数据写入到oob中。

然后在这几行代码后找到下面这行代码:

buf += bytes;

把上面这行代码修改为:

if (writeYaffs)
            buf += (bytes + mtd->oobsize);
        else
            buf += bytes;

表示如果是写的yaffs文件系统,那么要把buf的偏移量再加上oobsize。
到这里,需要修改的文件就修改完成了。

另外,制作yaffs镜像的工具mkyaffsimage已经跟不上时代了,呵呵!为了得到正确的镜像文件需要修改一下。

在下载的yaffs2的源码包中有一个utils目录,该目录中的mkyaffsimage.c即是mkyaffsimage的源代码。修改这个文件中的write_chunk函数如下:

static int write_chunk(__u8 *data, __u32 objId, __u32 chunkId, __u32 nBytes)
{
yaffs_PackedTags1 pt1;
    yaffs_ExtendedTags etags;
    __u8 ecc_code[6];
    __u8 oobbuf[16];


    error = write(outFile,data,512);
    if(error < 0) return error;


    etags.chunkId = chunkId;
    etags.serialNumber = 0;
    etags.byteCount = nBytes;
    etags.objectId = objId;
    etags.chunkDeleted = 0;

    yaffs_PackTags1(&pt1, &etags);

    yaffs_CalcTagsECC((yaffs_Tags *)&pt1);

    memset(oobbuf, 0xff, 16);
    memcpy(oobbuf+8, &pt1, 8);
    
    nPages++;


    return write(outFile, oobbuf, 16);

}

这个函数就是写一个page的数据(包括oob)到yaffs镜像文件中。从代码中可以知道,在写入的page的oob中,oob的16个Bytes的前8个Bytes为0xff,后8个Bytes是经过pack过的tags。而前8个Bytes,从linux的MTD层代码可以知道,它的第0,1,2,3,6,7个Bytes放的是这个page的ECC数据,而第4,5个Bytes是固定的0xff。从这里可以知道我们这里并没有计算每个page的ECC数据,我们把这个工作交给U-Boot的MTD层去完成。

为了让U-Boot帮我们来计算这个ECC数据,在我们的NAND驱动中必须设置NAND数据的校验方式为软件校验。在U-Boot的源码中找到drivers/mtd/nand/s3c2410_nand.c文件中的board_nand_init函数的下面几行代码:

#ifdef CONFIG_S3C2410_NAND_HWECC
    nand->ecc.hwctl = s3c2410_nand_enable_hwecc;
    nand->ecc.calculate = s3c2410_nand_calculate_ecc;
    nand->ecc.correct = s3c2410_nand_correct_data;
    nand->ecc.mode = NAND_ECC_HW;
    nand->ecc.size = CONFIG_SYS_NAND_ECCSIZE;
    nand->ecc.bytes = CONFIG_SYS_NAND_ECCBYTES;
#else
    nand->ecc.mode = NAND_ECC_SOFT;
#endif

确保最后一个#endif前面那行代码是把ecc.mode设置成了NAND_ECC_SOFT,即使用软件校验的方式。同理,在所配套的linux驱动中也要保证所使用的驱动是使用软件校验方式。例如我所使用的内核为linux-2.6.30,保证它是软件校验方式的代码drivers/mtd/nand/s3c2410.c文件中的第752行:

chip->ecc.mode = NAND_ECC_SOFT;


在烧写之前,我们一般需要把yaffs的镜像文件下载到内存当中,下载后一般会提示下载长度,比如说我把我的根文件系统镜像下载到0x31000000地址处,下载完成后会提示下载长度为0x293190,因此我使用如下命令将yaffs根文件系统镜像烧写到nand flash当中:
nand write.yaffs 0x31000000 0x240000 0x293190
烧写完成后启动linux内核,将成功挂载上yaffs根文件系统,第一次挂载可能会有一长串的提示,如:
page xx in gc has no object: 0 0 0
之类的,我也不大清楚出现这个问题的原因,重启以后这种现象将消失。有兴趣的朋友可以试着解决或者解释一下这个问题,找到答案了别忘记分享哈!

Good Luck!

阅读(4726) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~