u-boot移植--5、支持烧写yaffs文件系统映像

常常说自己是老年人记忆,因为看过的东西很快就忘。秉承着看自己写的东西肯定要比看别人写东西要好理解的原则。写下这个系列的u-boot移植,同时也加深自己的理解。其实到现在网上大神很多,给出了很多的解决方案。我也以他们为参考。与之不同我会加上一些我自己对某些地方的理解来进行处理。希望对大家有帮助。
开发板:天嵌TQ2440
u-boot:u-boot-1.1.6

手里的开发板是天嵌的TQ2440,看的书是韦东山老师的《嵌入式Linux开发完全手册》,有些许不同的地方,增加了移植困得,当应该也更加深自己的理解。


为什么要支持烧写yaffs文件系统映像呢?(摘抄一段书上的内容)

在实际生产中,可以通过烧片器等手段将内核、文件系统映像烧入固态存储设备中,bootloader不需要具备烧写功能。但是为了方便开发,通常在bootloader中增加烧写内核、文件系统映像文件的功能。

增加了nand flash功能的u-boot1.1.6已经可以通过“nand write...”、“nand write.jaffs2...”等命令来烧写内核,烧写cramfs、jffs2文件系统映象文件,但是在nand flash上,yaffs文件系统的性能更佳,下面增加“nand write.yaffs...”命令来烧写yaffs文件系统映象文件。


要增加命令需要使用U-BOOT-CMD函数,我们先来了解一下这个函数。关于这个函数的定义是在include/Command.h中实现的。我们来一步一步看。关于U-BOOT-CMD的定义如下所示。根据是否定义CFG_LONGHELP来确定是否包含help项。

#ifdef  CFG_LONGHELP

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

#else	/* no long help info */

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}

#endif	/* CFG_LONGHELP */

我们以第二行的定义来进行详细的说明

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)    cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

可以看到U_BOOT_CMD使用宏定义的形式定义,它包含6个参数。它实际上指向的是cmd_tbl_t结构体变量,即使用定义U_BOOT_CMD命令就是定义填充一个完整的cmd_tbl_t结构体的过程。上面的这条指令我们有三个地方需要解释一下:##name、Struct_Section、#name。

首先Struct_Section的定义是是本源文件中(include/Command.h)已经定义好了,如下所示。

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

这表明被填充的结构体是放入到了一个.u_boot_cmd 段中。

##name将字符直接跟在后面,

#name会将name这个字符中以“..."的形式放置,即表示为一个字符串。

以一个例子来说明,

U_BOOT_CMD(boot, 0, 0, fun, "boot xxx");
它展开之后为的结果为

cmd_tbl_t __u_boot_cmd_boot __attribute___((unused, section(".u_boot_cmd"))) = {"boot", 0, 0, fun, "boot xxx"}
(这里是使用的__attribute__是属性的说明,什么意思?GNU C 允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代码检查。要指定一个声明的属性,在声明后写__attribute__ (( ATTRIBUTE ))其中 ATTRIBUTE 是属性说明,多个属性以逗号分隔。

简而言之: 声明 + 属性,拿我们上面的例子来说明即:
  cmd_tbl_t __u_boot_cmd_boot + 属性 = {"boot", 0, 0, fun, "boot xxx"}
  cmd_tbl_t __u_boot_cmd_boot __attribute___((unused, section(".u_boot_cmd"))) = {"boot", 0, 0, fun, "boot xxx"}

所以这句话的意思就是定义了一个结构体__u_boot_cmd_boot,这个结构体被放入.u_boot_cmd段中,结构体的各部分属性为{"boot", 0, 0, fun, "boot xxx"}

接下来我们贴一下cmd_tbl_t的定义,省的去翻代码.。这个结构体的定义同样是在include/Command.h函数中实现的。具体如下:

struct cmd_tbl_s {
	char		*name;		/* Command Name			*/
	int		maxargs;	/* maximum number of arguments	*/
	int		repeatable;	/* autorepeat allowed?		*/
					/* Implementation function	*/
	int		(*cmd)(struct cmd_tbl_s *, int, int, char *[]);
	char		*usage;		/* Usage message	(short)	*/
#ifdef	CFG_LONGHELP
	char		*help;		/* Help  message	(long)	*/
#endif
#ifdef CONFIG_AUTO_COMPLETE
	/* do auto completion on the arguments */
	int		(*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};

typedef struct cmd_tbl_s	cmd_tbl_t;
可以看到,溯源到根本,实际上cmd_tbl_t是由cmd_tbl_s结构体定义而来,另外我们上面提到的说明结构体的属性{"boot", 0, 0, fun, "boot xxx"}的次序和属性内容也都是根据这个结构体决定的。这里我们关注一个点,那就是第四个参数cmd,它实际上指向的是一个函数,而这个函数就是我们要执行命令的真正的处理函数。

好,最后来我们看看.u_boot_cmd这个段,这个段是在/board/TQ2440/u-boot.lds中有说明,如下所示。

	. = .;
	__u_boot_cmd_start = .;
	.u_boot_cmd : { *(.u_boot_cmd) }
	__u_boot_cmd_end = .;

即他定义一个__u_boot_cmd_start作为开始,一个__u_boot_cmd_end作为结束。中间就是.u_boot_cmd中存放的命令。

对于u-boot.lds这个文件主要用于定义代码段的存放地址,关于这个文件的具体解释,大家可以去看参考文献9和10.


ok,跑题这么久,我们还是回来看看如何增加“nand write.yaffs...”命令来烧写yaffs文件系统映象文件。

“nand write.yaffs...”字样的命令中,“nand”是具体命令,“write.yaffs...”是参数。nand命令在common/cmd_nand.c文件中实现(关于文件的命令规则我们会发现,所有的U-BOOT-CMD命令的定义文件都是以cmd开头的,例如cmd_nand.c),在cmd_nand.c文件中已经有nand命令了他主要是用来烧写jffs2、cramfs文件系统映像文件的,我们只需要稍加修改,添加支持。这里有一个问题需要注意一下,我们在上一讲对于nand flash的移植中使用了不宏定义CFG_NAND_LEGACY的模式。因此我们在修改代码使也是针对没有定义CFG_NAND_LEGACY的代码。

在U_BOOT_CMD中,我们在help位置增加对应的说明字符串。增加后的U_BOOT_CMD如下所示

U_BOOT_CMD(nand, 5, 1, do_nand,
	"nand    - NAND sub-system\n",
	"info                  - show available NAND devices\n"
	"nand device [dev]     - show or set current device\n"
	"nand read[.jffs2]     - addr off|partition size\n"
	"nand write[.jffs2]    - addr off|partiton size - read/write `size' bytes starting\n"
	"nand read.yaffs addr off size - read the 'size' byte yaffs image starting\n"
	"at offset 'off' to memory address 'addr'\n"	
	"nand write.yaffs addr off size - write the 'size' byte yaffs image starting\n"
	"at offset 'off' to memory address 'addr'\n"
	"    at offset `off' to/from memory address `addr'\n"
	"nand erase [clean] [off size] - erase `size' bytes from\n"
	"    offset `off' (entire device if not specified)\n"
	"nand bad - show bad blocks\n"
	"nand dump[.oob] off - dump page\n"
	"nand scrub - really clean NAND erasing bad blocks (UNSAFE)\n"
	"nand markbad off - mark bad block at offset (UNSAFE)\n"
	"nand biterr off - make a bit error at offset (UNSAFE)\n"
	"nand lock [tight] [status] - bring nand to lock state or display locked pages\n"
	"nand unlock [offset] [size] - unlock section\n");

其实我们可以看到nand还是支持比较多的命令的,比如nand bad、nand lock等等。上面我们在help位置增加了说明字符串nand read.yaffs和nand write.yaffs的字符串说明。接下来我们要真正的实现这两个命令。通过上面的U_BOOT_CMD我们知道,这个实现函数为do_nand函数。那我们接下啦就继续修改do_nand函数吧。在do_nand函数中,增加读写yaffs文件系统命令后的代码段如下所示。其中else if段即为增加的对于yaffs的支持。

/* read write */
	if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {
		int read;

		if (argc < 4)
			goto usage;

		addr = (ulong)simple_strtoul(argv[2], NULL, 16);

		read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
		printf("\nNAND %s: ", read ? "read" : "write");
		if (arg_off_size(argc - 3, argv + 3, nand, &off, &size) != 0)
			return 1;

		s = strchr(cmd, '.');
		if (s != NULL &&
		    (!strcmp(s, ".jffs2") || !strcmp(s, ".e") || !strcmp(s, ".i"))) {
			if (read) {
				/* read */
				nand_read_options_t opts;
				memset(&opts, 0, sizeof(opts));
				opts.buffer	= (u_char*) addr;
				opts.length	= size;
				opts.offset	= off;
				opts.quiet      = quiet;
				ret = nand_read_opts(nand, &opts);
			} else {
				/* write */
				nand_write_options_t opts;
				memset(&opts, 0, sizeof(opts));
				opts.buffer	= (u_char*) addr;
				opts.length	= size;
				opts.offset	= off;
				/* opts.forcejffs2 = 1; */
				opts.pad	= 1;
				opts.blockalign = 1;
				opts.quiet      = quiet;
				ret = nand_write_opts(nand, &opts);
			}
		}
		else if (s != NULL && (!strcmp(s, ".yaffs"))) {
			if (read) {
				/* read */
				nand_read_options_t opts;
				memset(&opts, 0, sizeof(opts));
				opts.buffer	= (u_char*) addr;
				opts.length	= size;
				opts.readoob    = 1;
				opts.offset	= off;
				opts.quiet      = quiet;
				ret = nand_read_opts(nand, &opts);
			} else {
				/* write */
				nand_write_options_t opts;
				memset(&opts, 0, sizeof(opts));
				opts.buffer	= (u_char*) addr;    /* yaffs 文件系统映像存放的地址 */
				opts.length	= size;              /* 长度 */
				opts.offset	= off;               /* 要烧写到nand flash的偏移地址 */
				/* opts.forcejffs2 = 1; */           /* 计算ecc码的方法,没有用 */
				opts.noecc      = 1;                 /* 不要计算ecc,yaffs映像中有oob数据 */
				opts.writeoob   = 1;                 /* 写oob区 */
				opts.blockalign = 1;                 /* 每个逻辑上的块大小为1个物理块 */
				opts.quiet      = quiet;             /* 是否打印提示信息 */
				opts.skipfirstblk = 1;               /* 跳过第一个可用块 */
				ret = nand_write_opts(nand, &opts);
			}
		} else {
			if (read)
				ret = nand_read(nand, off, &size, (u_char *)addr);
			else
				ret = nand_write(nand, off, &size, (u_char *)addr);
		}

		printf(" %d bytes %s: %s\n", size,
		       read ? "read" : "written", ret ? "ERROR" : "OK");

		return ret == 0 ? 0 : 1;
	}

所谓oob区就是out of band的意思。nand flash每一页大小为(2K+64)字节(这个我们在nand flash移植一文中有介绍,还有其他格式的nand flash比如每页大小为(256+8)(512+16))其中2k字节是存储数据的区域。16字节就是oob区主要存放坏块标记,前面2k字节的ecc校验码。cramfs、jffs2文件系统映象一文件中并没有oob的内容如果将他们烧录到nor flash中的“平铺”关系;如果将它们烧入nand flash中,则先根据OOB的标记略过坏块,然后将2k数据写入后还会计算他们的ecc校验最后将他们写入它们烧入oob区,如此循环。cramfs、jffs2文件系统映像文件通常是512的整数倍。

而yaffs文件系统映像文件的格式则跟他们不相同。为念本省就包含了OOB区的数据。(里面通常有坏块标记。ECC校验码、其他yaffs相关的信息,所以烧写时,不需要计算ECC值,首先检查是否有坏块(是则跳过),然后写入2k字节的数据,最后写入64字节的OOB数据。如此循环,yaffs文件系统映像文件的大小是(2k+64)的整数倍。

注意:这里在烧写yaffs文件系统映像时分区上的第一个可用的(不是坏块)块也要跳过。

这在我们的代码中也有体现,即前面代码中提到的opts.skipfirstblk = 1;               /* 跳过第一个可用块 */

不过由于u-boot中编写的nand_write_options本身是没有考虑yaffs的,所以nand_write_options中并没有skipfirstblk项,所以需要我们手动的添加。所以我们需要在incclude/nand.h中进行如下修改,增加skipfirstblk成员。修改之后的代码如下所示。

struct nand_write_options {
        u_char *buffer;         /* memory block containing image to write */
        ulong length;           /* number of bytes to write */
        ulong offset;           /* start address in NAND */
        int quiet;              /* don't display progress messages */
        int autoplace;          /* if true use auto oob layout */
        int forcejffs2;         /* force jffs2 oob layout */
        int forceyaffs;         /* force yaffs oob layout */
        int noecc;              /* write without ecc */
        int writeoob;           /* image contains oob data */
        int pad;                /* pad to page size */
        int blockalign;         /* 1|2|4 set multiple of eraseblocks
                                 * to align to */
        int skipfirstblk        /* new add, to skip the first blk*/
};
接下来是修改nand_write_opts函数,增加对skipfirstblk成员的支持。它在drivers/nand/nand_util.c文件中,下面的第301行,第430~434行为新加的。
285 int nand_write_opts(nand_info_t *meminfo, const nand_write_options_t *opts)
286 {
287         int imglen = 0;
288         int pagelen;
289         int baderaseblock;
290         int blockstart = -1;
291         loff_t offs;
292         int readlen;
293         int oobinfochanged = 0;
294         int percent_complete = -1;
295         struct nand_oobinfo old_oobinfo;
296         ulong mtdoffset = opts->offset;
297         ulong erasesize_blockalign;
298         u_char *buffer = opts->buffer;
299         size_t written;
300         int result;
301         int skipfirstblk = opts->skipfirstblk;
302 
303         if (opts->pad && opts->writeoob) {
...
397                 while (blockstart != (mtdoffset & (~erasesize_blockalign+1))
...
430                 if(skipfirstblk) {
431                         mtdoffset += erasesize_blockalign;
432                         skipfirstblk = 0;
433                         continue;
434                 }


然后我们就可以编译喽,make all!



下载后,我们可以看到我们的命令已经被成功识别。但是由于我对于yaffs了解不深。所以就没有做后续操作。这篇博文就到这里吧。







参考文献:3、http://www.360doc.com/content/12/0905/20/4186481_234492334.shtml

                    4、http://blog.csdn.net/shengzhadon/article/details/52766263

                    5、http://blog.csdn.net/yuzeze/article/details/51754541

                    6、http://blog.csdn.net/tongdh/article/details/24471325

                    7、http://blog.sina.com.cn/s/blog_6276db0e0101b1qs.html

                    8、http://blog.sina.com.cn/s/blog_89fa41ef0100xysl.html

    9、http://www.linuxidc.com/Linux/2011-08/41809.htm

    10、http://blog.csdn.net/qiaoliang328/article/details/5891913

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值