开发板:天嵌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