基于at91sam9x5ek嵌入式系统的内核和文件系统双备份实现

来源:互联网 发布:通达信软件使用教程 编辑:程序博客网 时间:2024/05/18 22:11

前言前阵子公司使用的基于at91sam9x5ek的板子出现了好几块系统无法启动的问题,用串口打印显示要不就是文件系统损坏,要不就是内核损坏了,排除了人为误操作的原因几乎就可以断定应该是存放内核或是文件系统的nand flash出现了坏块所致。鉴于此,想重新规划nand flash分区,给内核和文件系统都规划一个备份分区,使得在内核或是文件系统损坏时可以从备份分区中修复。

概述:众所周知,at91一般是由bootstrap先开始引导(当然其实前面还有一点固化在rom中的引导代码由于这块不能更改这里就不提及了),bootstrap初始化一部分硬件之后会把下一级的引导镜像拷贝在SDRAM中的一个固定地址上(对于at91sam9x5ek这个地址是0x22000000),这个镜像可以是uboot镜像也可以是kernel镜像,也就是说bootstrap可以跳过uboot直接引导内核,公司之前的板子就是这样,直接用bootstrap引导内核,虽然这样引导速度略有提升,然而却不好维护,因为没有uboot引导这一项,bootstrap功能太过单一,所以板子一出问题只能更换或是用samba重新烧写,很是麻烦。若想实现备份自动恢复的功能,那么就必须要借助uboot的强大功能。

先说一下实现原理吧,首先规划一下nand flash分区,如下所示:
<span style="font-size:18px;color:#ff9966;"><strong>mtdnameoffsetsizeblocksmtd0bootstrap:0x0256k2mtd1uboot0x40000512k4mtd2env0xc0000256k2mtd3judge0x1000001M8mtd4user0x2000001M8mtd5bak_kernel0x3000005M40mtd6kernel0x8000005M40mtd7rootfs0xd00000143M1144mtd8data0x9c0000040M320mtd9bak_rootfs0xc40000060M480</strong></span>
各分区的作用如name所示,里面最核心的一个分区就是judge分区,自动恢复的实现全依赖于它里面存放的数据,其实它存放的数据很简单,只有两行数据"kernel_boot_succ=yes(or no)\nrootfs_boot_succ=yes(or no)",uboot启动后会去读取judge分区里面的这两个值,其中kernel_boot_succ存放是上一次内核是否启动成功,而rootfs_boot_succ则存放的是上一次文件系统是否启动成功。如果检查到kernel_boot_succ=no则使用nand read命令从bak_kernel分区中拷贝出内核镜像再次nand write写入到kernel分区(写入之前当然要用nand erase擦除一下了),同理rootfs_boot_succ是针对文件系统的备份恢复,uboot在启动内核之前会把这两个变量设置为"kernel_boot_succ=no\nrootfs_boot_succ=yes\n\n"随后启动内核,内核若是启动成功便会再把这两个值改为"kernel_boot_succ=yes\nrootfs_boot_succ=no\n\n",显而易见文件系统启动成功后把这两个值全部设置为yes。若是内核或是文件系统没有启动成功,那么他们对应的标志就会是no,由于任一启动失败最终都会导致watchdog重启机制,所以下次重启后uboot便可以通过判断这两个变量来决定是否需要进行备份恢复了,当然了,前提是要你在bootstrap和uboot阶段把watchdog打开才行。

原理大致如此,下面说说具体的实现吧。

首先第一步去at官网下载bootstrap、uboot、kernel、还有buildroot的源码包,网址如下:http://www.at91.com/linux4sam/bin/view/Linux4SAM,at91的官网有详细介绍下载源码的路径以及每一项的编译方法,这里就不再赘述。

第二步修改源码
1.修改bootstrap(本文使用的bootstrap为at91bootstrap-at91bootstrap-3.5.x)
前面说过需要使用watchdog重启机制,然而默认的bootstrap配置是没有使能watchdog的,因此需要做一修改,有两种方式,第一种通过make menuconfig配置去掉disable watchdog的选项,第二种直接修改源码,由于之前的同事直接在源码中进行更改的,所以我也没有再使用make menuconfig进行配置,直接参照他的了,在driver/at91_wdt.c中增加两个函数,具体如下:
void at91_enable_wdt(void){    writel(AT91C_WDTC_WDV | AT91C_WDTC_WDD | AT91C_WDTC_WDRSTEN , AT91C_BASE_WDT + WDTC_MR);}void at91_reset_wdt(void){    writel(AT91C_WDTC_WDRSTT | AT91C_WDTC_KEY, AT91C_BASE_WDT + WDTC_CR);}
显然一个是使能watchdog的函数,一个是喂狗函数,然后在board/at91sam9x5ek/at91sam9x5ek.c的hw_init()函数中注释掉"at91_disable_wdt();"这一行,然后添加"at91_enable_wdt();"也就是在硬件初始化时打开watchdog开关,至于喂狗函数几乎用不上,除非你想在bootstrap中做一些其他比较耗时的操作,那么这时你便需要在适当的位置添加"at91_reset_wdt();"然后执行:
make at91sam9x5eknf_uboot_defconfig
其实就是把board/at91sam9x5ek/at91sam9x5eknf_uboot_defconfig这个文件作为.config而已,这是at91给出的默认bootstrap引导uboot的配置,然后执行:
<span style="font-size:18px;">make CROSS_COMPILE=arm-none-linux-gnueabi-</span>
既可以在binaries目录下生成at91sam9x5ek-nandflashboot-uboot-3.5.4.bin(也就是bootstrap的镜像文件),这个文件可以直接用samba工具烧写到板子上作为引导文件。
由于at91sam9x5ek对nand flash增加了PMECC的校验,如果想使用uboot进行烧写,在at91官网中你可以看到在使用uboot烧写bootstrap之前需要增加以下PMECC校验的头以及长度等等,如果不想麻烦在uboot敲那么多命令,可以直接编写一个程序给上述的bin文件增加上这个头部校验信息,我的代码实现如下:
经此代码生成的bootstrap文件便可以直接通过uboot烧写到nand flash的0x0地址上

2.修改uboot(本文使用的uboot为u-boot-at91-u-boot-2015.01-at91)
首先需要配置uboot,at91sam9x5ek默认的配置大都位于include/configs/at91sam9x5ek.h文件中,我需要添加一些其他的配置,例如:watchdog的使能,环境变量的重写还有一些其他额外的配置等等,具体如下所示:
#define CONFIG_HW_WATCHDOG<span style="white-space:pre"></span>/*使能watchdog的两个配置选项*/#define CONFIG_AT91SAM9_WATCHDOG#define CONFIG_ENV_OVERWRITE<span style="white-space:pre"></span>/*环境变量可以重新修改,不配此项有些环境变量无法进行修改*/#define CONFIG_LIB_RAND<span style="white-space:pre"></span>/*使用随机数的库,获取随机的mac地址会用到*/#if 0<span style="white-space:pre"></span>/*以下是eth0和eth1配置默认mac地址,由于板子要量产所以不能把所有的板子全配置为一样的mac地址,调试的时候会用到,发布时注释掉使用随机mac*/#define CONFIG_ETHADDR0c:9b:2f:ce:01:d8#define CONFIG_ETH1ADDR 6e:3c:2a:fe:c2:b1#endif#define CONFIG_CMD_NET<span style="white-space:pre"></span>/*配置网络相关的uboot命令,实际上如果配置了下面几项之后默认会配置此项的*/#define CONFIG_IPADDR172.7.18.99<span style="white-space:pre"></span>/*默认的ip地址*/#define CONFIG_NETMASK255.255.255.0<span style="white-space:pre"></span>/*默认的子网掩码*/#define CONFIG_GATEWAYIP172.7.18.1<span style="white-space:pre"></span>/*默认网关*/#define CONFIG_SERVERIP172.7.18.200<span style="white-space:pre"></span>/*默认的远程tftp或是nfs服务器的ip*/#define CONFIG_EXTRA_ENV_SETTINGS \<span style="white-space:pre"></span>/*额外的环境变量设置,这里主要用来启动nfs文件系统*/"nfsbootargs=noinitrd init=/linuxrc root=/dev/nfs rw " \"nfsroot=${serverip}:/ubifs,nolock " \"ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}::eth1:off " \"console=ttyS0,115200 earlyprintk"
/* PMECC & PMERRLOC */<span style="white-space:pre"></span>/*nand flash PMECC相关的配置*/#define CONFIG_ATMEL_NAND_HWECC<span style="white-space:pre"></span>1#define CONFIG_ATMEL_NAND_HW_PMECC<span style="white-space:pre"></span>1#define CONFIG_PMECC_CAP<span style="white-space:pre"></span>4<span style="white-space:pre"></span>/*changed by Jicky, the origin is 2*/#define CONFIG_PMECC_SECTOR_SIZE<span style="white-space:pre"></span>512
#define CONFIG_PREBOOT "mtdparts default"<span style="white-space:pre"></span>/*uboot启动预先执行的命令*/
/*默认分区的相关配置*/#define MTDPARTS_DEFAULT "mtdparts=atmel_nand:256k(bootstrap)ro,512k(uboot)ro,"<span style="white-space:pre"></span>\<span style="white-space:pre"></span>"256k(env)ro,1M(judge),1M(user),5M(bak_kernel)ro,"<span style="white-space:pre"></span>\<span style="white-space:pre"></span>"5M(kernel)ro,143M(rootfs),40M(data),-(bak_rootfs)ro"#define MTD_ACTIVE_PART "nand0,0"#define MTDIDS_DEFAULT "nand0=atmel_nand"
/*uboot环境变量的偏移地址大小以及judge的偏移地址大小*/
/* bootstrap + u-boot + env + linux in nandflash */#define CONFIG_ENV_IS_IN_NAND#define CONFIG_ENV_OFFSET<span style="white-space:pre"></span>0xc0000#define CONFIG_ENV_SIZE<span style="white-space:pre"></span>0x20000<span style="white-space:pre"></span>/* 1 sector = 128 kB */#define CONFIG_ENV_JUDGE_OFFSET<span style="white-space:pre"></span>0x100000#define CONFIG_ENV_JUDGE_SIZE<span style="white-space:pre"></span>0x100000#define CONFIG_ENV_USER_OFFSET<span style="white-space:pre"></span>0x200000#define CONFIG_ENV_USER_SIZE<span style="white-space:pre"></span>0x100000#define CONFIG_RECOVER_KERNEL<span style="white-space:pre"></span>/*恢复内核*/#define CONFIG_RECOVER_ROOTFS<span style="white-space:pre"></span>/*恢复文件系统*/<span style="white-space:pre"></span>/*uboot引导内核相关的两个参数*/#define CONFIG_BOOTCOMMAND<span style="white-space:pre"></span>"nand read " \<span style="white-space:pre"></span>"0x22000000 kernel 0x300000; " \<span style="white-space:pre"></span>"bootm 0x22000000"
#define CONFIG_BOOTARGS<span style="white-space:pre"></span>\<span style="white-space:pre"></span>"console=ttyS0,115200 earlyprintk "<span style="white-space:pre"></span>\<span style="white-space:pre"></span>"mtdparts=atmel_nand:256k(bootstrap)ro,512k(uboot)ro,"<span style="white-space:pre"></span>\<span style="white-space:pre"></span>"256k(env)ro,1M(judge),1M(user),5M(bak_kernel)ro,"<span style="white-space:pre"></span>\<span style="white-space:pre"></span>"5M(kernel)ro,143M(rootfs),40M(data),-(bak_rootfs)ro "<span style="white-space:pre"></span>\<span style="white-space:pre"></span>"rootfstype=ubifs ubi.mtd=7 root=ubi0:rootfs rw"
以上只是列出了部分修改的配置,还有一些其他默认的配置请查看源文件。

(1)修改网络部分
也不知道是由于at91sam9x5ek本身的问题还是公司硬件组同事的设计问题,在at91sam9x5ek启动uboot后,eth0总是不能ping通远程的nfs服务器,然而把环境变量"ethact"设置为"macb1"再配置"eth1addr"就可以ping通了,也就是说使用eth1可以ping通网络,很奇怪,也懒得去深究了,总之网络能通就行。这块主要在使用uboot调试的时候会用到,等内核和文件系统启动后eth0和eth1仍然都是好用的。只是在调试的时候为了避免每次启动后还要重新去修改环境变量,因此我就直接更改了uboot对于网络初始化的一些代码。其实主要是设置"ethact"这个环境变量,这个变量默认是uboot初始化第一块网卡的name,另外要提出一点,即使你在at91sam9x5ek.h中默认配置了"#define CONFIG_ETHACT "macb1" "等网卡初始化后还是会改为"macb0"的,除非系统初始化的第一块网卡就是macb1(也就是eth1,uboot中称之为macb1),所以需要调整网卡初始化的顺序,在board/atmel/at91sam9x5ek/at91sam9x5ek.c的board_eth_init函数中可以看到如下所示:
int board_eth_init(bd_t *bis){    int rc = 0;#ifdef CONFIG_MACB    if (has_emac0())        rc = macb_eth_initialize(0,            (void *)ATMEL_BASE_EMAC0, 0x00);
<pre name="code" class="csharp" style="font-size: 18px;">    if (has_emac1())        rc = macb_eth_initialize(1,            (void *)ATMEL_BASE_EMAC1, 0x00);
#endif return rc; }
很明显系统先初始化eth0再初始化eth1,要想先初始化eth1,只需要把二者互换位置即可,这里就不再贴出代码了。(如果无需使用uboot网络调试那么这块可以不用修改的)。
另外一个对于网络的修改就是mac地址这块,也不知道咋回事,at91sam9x5ek这板子一重启后mac就丢失了,每次启动都只能随机生成一个,经常由于mac地址的变动导致系统启动后要过好久网络才能通(若想了解为何mac变动会导致网络好久才通请去了解一下arp协议你就懂了,这里不做解释)。因此为了有一个固定的mac地址,我打算在uboot中生成随机的mac地址,并把生成的mac地址保存在env分区的ethaddr或是eth1addr环境变量中,下次重启直接读取环境变量即可,免得每次都要随机生成。
修改很简单,把net/eth.c中的eth_getenv_enetaddr函数改为如下即可:
int eth_getenv_enetaddr(const char *name, uchar *enetaddr){    char *addr = getenv(name);/* added by Jicky for set mac address, in case the kernel use random when boot every time */    if (addr == NULL)    {   /*set mac addr */        eth_random_addr(enetaddr);        eth_setenv_enetaddr(name, enetaddr);        saveenv();    }       else/* add over */        eth_parse_enetaddr(addr, enetaddr);    return is_valid_ether_addr(enetaddr);}
注意以上改动使用了随机数的lib,因此我在include/configs/at91sam9x5ek.h中配置了CONFIG_LIB_RAND这个选项,目的是使能随机数的库,不然编译会报错的。

(2)修改uboot环境变量
实现原理中曾提到要使用uboot去读取judge分区存放的kernel_boot_succ和rootfs_boot_succ变量值,因此需要在uboot中添加对于judge分区的读写。我这里把judge分区作为uboot环境变量的另一份存放区,因此只需仿照uboot对于env分区的读写以及一些命令行操作即可。这里我实际上用uboot又额外读取了两个分区的信息,一个是前面提到的judge分区,另外一个是user分区,user分区里面存放是内核、文件系统以及用户存放在data分区的相关大小。
judge分区存放的数据格式为:
kernel_boot_succ=yesrootfs_boot_succ=yes
user分区存放的数据格式为:
datasize=2048kernelsize=300000rootfssize=2800000
其中datasize存放的用户存放入data分区的十进制文件大小,而kernelsize和rootfssize分区对应于内核和文件系统的十六进制镜像文件的大小,uboot在恢复内核和文件系统时会通过kernelsize和rootfssize从备份分区中读取相应大小的数据。
对于这两个分区大小都为1M(即8个块,每块128K),它们的读取方式与读取env一样,只不过在读取时我循环了8次,每次读取一个块(128K),若是读取成功就跳出循环,这样可以在有坏块的时候跳过坏块去读取下一块,当然写的时候亦是如此,避免了由于坏块出现而导致不能读取或是写入数据到nand flash中。

uboot对于环境变量的读取大致是先分配一个CONFIG_ENV_SIZE的buf(这里为128K),然后从配置的相关偏移地址中读取一块数据存放到buf中,最后把buf按照'\0'为分割点导入到一个env_htab中,这样env_htab中存放的便是环境变量的每个键值对,后续的查询和修改都是通过操作env_htab来完成,最后在保存环境变量时,把env_htab再按照'\0'为分割点导出到一个buf,然后把buf写入到对于的flash中即可。
因此,我仿照这些定义了一个env_htab_judge,然后把judge分区和user分区的数据分区读取之后分别导入到env_htab_judge中,最后再仿照getenv、setenv、printenv等函数的实现,额外实现了getenv_judge、setenv_judge、printenv_judge等函数,这样便可以在uboot中通过getenv_judge去获取judge和user分区的环境变量了。有一点需要注意,uboot对于env分区的环境变量是按照\0进行分割的,而我实现的judge和user分区很显然应当是按照换行符'\n'来进行分割,所以对于himport_r和hexport_r函数中的sep参数我填的都是'\n'。另外在导入user分区的环境变量到env_htab_judge时,使用himport_r函数的第5个参数flag需要设置为H_NOCLEAR,因为在导入user分区变量之前已经导入了judge分区的变量,如果不指定这个flag,那么hinport_r函数会摧毁之前的env_htab_judge然后再重新创建一个的,而我是要两个分区共用一个htab,所以这个flag必须指定,目的就是告诉himport函数不需要摧毁之前的htab,接着之前的htab继续创建一些entry,具体代码实现如下:

首先需要在common目录的env_nand.c文件中添加如下代码:
<pre name="code" class="cpp">/* added by Jicky */int saveenv_judge(void){intret = 1;ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);int i, num = CONFIG_ENV_JUDGE_SIZE / CONFIG_ENV_SIZE;ssize_tlen;memset(buf, 0, CONFIG_ENV_SIZE);len = hexport_r(&env_htab_judge, '\n', 0, &buf, ENV_SIZE, 0, NULL);if (len < 0) {error("Cannot export environment: errno = %d\n", errno);return 1;}for (i = 0; i < num; i++){struct env_location location = {.name = "JUDGE",.erase_opts = {.length = CONFIG_ENV_RANGE * (i + 1),.offset = CONFIG_ENV_JUDGE_OFFSET,},};ret = erase_and_write_env(&location, (u_char *)buf);if (ret == 0)break;}return ret;}void env_relocate_spec_user(void){#if defined(CONFIG_ENV_USER_OFFSET) && defined(CONFIG_ENV_USER_SIZE)int ret = 1;ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);const char *default_environment_user = "kernelsize=300000\nrootfssize=1e00000\n\n";int i, num = CONFIG_ENV_USER_SIZE / CONFIG_ENV_SIZE;for (i = 0; i < num; i++){ret = readenv(CONFIG_ENV_USER_OFFSET + i * CONFIG_ENV_SIZE, (u_char *)buf);if (ret == 0)break;}if (ret) {himport_r(&env_htab_judge, (char *)default_environment_user,sizeof(default_environment_user), '\n', H_NOCLEAR, 0,0, NULL);return;}if (himport_r(&env_htab_judge, (char *)buf, ENV_SIZE, '\n', H_NOCLEAR, 0,0, NULL) == 0) {himport_r(&env_htab_judge, (char *)default_environment_user,sizeof(default_environment_user), '\n', H_NOCLEAR, 0,0, NULL);}#endif}void env_relocate_spec_judge(void){#if defined(CONFIG_ENV_JUDGE_OFFSET) && defined(CONFIG_ENV_JUDGE_SIZE)int ret;ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
int i, num = CONFIG_ENV_JUDGE_SIZE / CONFIG_ENV_SIZE;for (i = 0; i < num; i++){ret = readenv(CONFIG_ENV_JUDGE_OFFSET + i * CONFIG_ENV_SIZE, (u_char *)buf);if (ret == 0)break;}if (ret) {set_default_env_judge("!readenv() in judge partition failed");return;}if (himport_r(&env_htab_judge, (char *)buf, ENV_SIZE, '\n', 0, 0, 0, NULL) == 0|| getenv_judge("kernel_boot_succ") == NULL|| getenv_judge("rootfs_boot_succ") == NULL) {set_default_env_judge("!import env in judge partition to env_htab_judge failed");}#endif}
在common/env_common.c中添加:
/* added by Jicky */struct hsearch_data env_htab_judge = {.change_ok = env_flags_validate,};void set_default_env_judge(const char *s){int flags = 0;const char *default_environment_judge = "kernel_boot_succ=yes\nrootfs_boot_succ=yes\n\n";if (s) {if (*s == '!') {printf("*** Warning - %s, ""using default environment in judge\n\n",s + 1);} else {flags = H_INTERACTIVE;puts(s);}} else {puts("Using default environment in judge\n\n");}if (himport_r(&env_htab_judge, (char *)default_environment_judge,sizeof(default_environment_judge), '\n', flags, 0,0, NULL) == 0)error("Environment in judge partition import failed: errno = %d\n", errno);}并在env_relocate函数中对于env_relocate_spec调用的后面添加
<span style="white-space:pre"></span>env_relocate_spec();<span style="white-space:pre"></span>/* added by Jicky */<span style="white-space:pre"></span>env_relocate_spec_judge();<span style="white-space:pre"></span>env_relocate_spec_user();
并在include/environment.h中添加相关的函数声明,如下:
extern struct hsearch_data env_htab_judge;void set_default_env_judge(const char *s);extern void env_relocate_spec_judge(void);extern void env_relocate_spec_user(void);
最后在common目录下新建一个cmd_nvedit_judge.c添加如下代码:
并把此文件添加到makefile的编译中。

最后修改common/autoboot.c中的autoboot_command函数如下所示:
void autoboot_command(const char *s){#if defined(CONFIG_RECOVER_KERNEL) && defined(CONFIG_RECOVER_ROOTFS)/* changed by Jicky */char bootcmd[512] = {0};int ret1 = strcmp(getenv_judge("kernel_boot_succ"), "yes");int ret2 = strcmp(getenv_judge("rootfs_boot_succ"), "yes");char *kernelsize = getenv_judge("kernelsize");char *rootfssize = getenv_judge("rootfssize");int len = 0;if (ret1 != 0 && ret2 != 0) /*当内核和文件系统都损坏时把stored_bootdelay设置为1s使得可以进入uboot命令行*/stored_bootdelay = 1;#endifdebug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)int prev = disable_ctrlc(1);/* disable Control C checking */#endif#if defined(CONFIG_RECOVER_KERNEL) && defined(CONFIG_RECOVER_ROOTFS)/* changed by Jicky */if (simple_strtoul(kernelsize, NULL, 16) < 0x200000)/*内核大小最小2M*/kernelsize = "300000";if (simple_strtoul(rootfssize, NULL, 16) < 0x1e00000)/*文件系统大小最小30M*/rootfssize = "2800000";if (ret1 != 0 && ret2 == 0) {/*说明内核损坏需要从备份中恢复*/len = sprintf(bootcmd, "nand read 0x22000000 bak_kernel %s; "\"nand erase.part kernel; nand write 0x22000000 kernel %s; ", kernelsize, kernelsize);printf("^^^^^^^ the kernel is damaged, recover it! ^^^^^^^\n");} else if (ret1 == 0 && ret2 != 0) {/*说明文件系统损坏需要从备份中恢复*/len = sprintf(bootcmd, "nand read 0x22000000 bak_rootfs %s; "\"nand erase.part rootfs; nand write.trimffs 0x22000000 rootfs %s; ", rootfssize, rootfssize);printf("^^^^^^^ the rootfs is damaged, recover it! ^^^^^^^\n");} else if (ret1 != 0 && ret2 != 0) {/*说明内核和文件系统全都损坏了需要在启动之前恢复备份*/len = sprintf(bootcmd, "nand read 0x22000000 bak_rootfs %s; "\"nand erase.part rootfs; nand write.trimffs 0x22000000 rootfs %s; "\"nand read 0x22000000 bak_kernel %s; "\"nand erase.part kernel; nand write 0x22000000 kernel %s; ",rootfssize, rootfssize, kernelsize, kernelsize);printf("^^^^^^^ both the kernel and rootfs are damaged, recover them! ^^^^^^^\n");} else {//printf("^^^^^^^ both the kernel and rootfs are good! ^^^^^^^\n");}if (ret1 != 0)/*说明内核需要恢复,由于之前已经把内核拷贝到0x22000000,所以此处直接使用bootm启动*/sprintf(bootcmd + len, "bootm 0x22000000");elsesprintf(bootcmd + len, "nand read 0x22000000 kernel %s; bootm 0x22000000", kernelsize);setenv_judge("kernel_boot_succ", "no");/* 内核启动成功会恢复为"yes" */setenv_judge("rootfs_boot_succ", "yes"); /* 内核启动成功后会设置为"no",文件系统启动成功会恢复为"yes" *//*删除kernelsize,rootfssize,datasize这几个环境变量,避免下次重启时环境变量重复,因为这些变量每次启动时都是从user分区导出来的*/setenv_judge("kernelsize", NULL);setenv_judge("rootfssize", NULL);setenv_judge("datasize", NULL);saveenv_judge();run_command_list(bootcmd, -1, 0);#elserun_command_list(s, -1, 0);#endif#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)disable_ctrlc(prev);/* restore Control C checking */#endif}#ifdef CONFIG_MENUKEYif (menukey == CONFIG_MENUKEY) {s = getenv("menucmd");if (s)run_command_list(s, -1, 0);}#endif /* CONFIG_MENUKEY */}
执行:make at91sam9x5ek_nandflash_defconfig && make
即可编译出u-boot.bin

3.修改内核(本文内核使用的是linux-at91-linux-2.6.39-at91)
前面实现原理中曾提到内核启动成功后会把judge分区中的环境变量分别设置为"kernel_boot_succ=yes\nrootfs_boot_succ=no\n\n"表明内核启动成功,文件系统尚未启动成功,因此需要在内核引导文件系统之前添加对于judge分区这两个变量的设置,即对judge分区进行处理。
在driver/mtd/mtdcore.c中添加如下代码:
/* 内核启动成功后,在尚未加载文件系统之前, * 把judge分区的内核启动成功(kernel_boot_succ)环境变量设置为"yes" * judge分区存放的环境变量为:"kernel_boot_succ=no\nrootfs_boot_succ=no\n\n"由uboot设置 * added by Jicky */void mtd_judge_part_deal(void){    struct mtd_info *mtd = get_mtd_device_nm("judge");    struct erase_info erase;    u_char *buf = NULL;    size_t len = 0;    int i;    if (IS_ERR(mtd)) {        printk(KERN_WARNING "the judge mtd partition isn't exist!\n");        return;    }       buf = kzalloc(mtd->writesize, GFP_KERNEL);    if (IS_ERR_OR_NULL(buf)) {        printk(KERN_ERR "kzalloc for judge partition to store env fail!\n");        put_mtd_device(mtd);        return;    }    strcpy(buf, "kernel_boot_succ=yes\nrootfs_boot_succ=no\n\n");    memset(&erase, 0, sizeof(erase));    erase.mtd = mtd;    erase.addr = 0;    erase.len = mtd->erasesize;    for (i = 0; i < 8; i++, erase.addr += mtd->erasesize)   {   /*judge分区总共8个块*/        if (mtd->block_isbad(mtd, erase.addr) == 0)            continue;        if (mtd->erase(mtd, &erase) == 0) {            mtd->block_markbad(mtd, erase.addr);            continue;        }        if (mtd->write(mtd, 0, mtd->writesize, &len, buf) != 0) {            printk(KERN_ERR "write judge partition env fail!\n");            mtd->block_markbad(mtd, erase.addr);        } else {            printk(KERN_INFO "write judge partition env succ!\n");            break;        }    }    kfree(buf);    put_mtd_device(mtd);}
此函数先获取judge的mtd_info结构体,然后通过mtd_info擦除judge的一个块再写入"kernel_boot_succ=yes\nrootfs_boot_succ=no\n\n"字符串,最后释放mtd_info给内核。
随后在init/main.c中的kernel_init函数中的sys_dup(0)调用之后添加mtd_judge_part_deal()函数的调用:
#if 1 /* added by Jicky */extern void mtd_judge_part_deal(void);#endifstatic int __init kernel_init(void * unused)
{
<span style="white-space:pre"></span>............
     (void) sys_dup(0);     (void) sys_dup(0);    /*       * check if there is an early userspace init.  If yes, let it do all     * the work     */#if 1   /* added by Jicky for set env 'kernel_boot_succ=yes' */    mtd_judge_part_deal();#endif
最后编译内核
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-mkimage -A arm -O linux -C none -T kernel -a 20008000 -e 20008000 -n linux-2.6 -d arch/arm/boot/zImage uImage-2.6.39-at91sam9x5ek.bin#更新模块之间的依赖关系depmod -b $PWD -e -F $PWD/System.map -v 2.6.39 -A -a

4.修改文件系统启动脚本(本文使用的文件系统是buildroot-2015.08.1)
在生成文件系统output/target/etc/init.d/目录下任意一个启动脚本中添加如下内容:
set_judge(){    if grep -q 'mtd3: 00100000 00020000 "judge"' /proc/mtd; then        echo -ne "kernel_boot_succ=yes\nrootfs_boot_succ=yes\n\n" > /tmp/judge        flash_erase /dev/mtd3 0 1 && nandwrite -m -p /dev/mtd3 /tmp/judge        if [ $? -eq 0 ]; then            right "set judge partition successful!"        else            error "set judge partition fail!"        fi        rm /tmp/judge &> /dev/null    else        error "judge partition isn't exist!"    fi}
并把set_judge函数加入到start函数中,这段脚本就是在文件系统启动后擦除judge分区,然后把两个启动成功的标志改为"yes"再写入到judge分区中。
注意:上述脚本函数中使用到了flash_erase、nandwrite等命令,这些命令需要在buildroot中编译mtd-tools才能得到。

至此,整个BSP的修改结束,剩下的便是烧写镜像了,另外在我的实现中,如果后续板子需要更换内核或是文件系统,只需要在系统中使用flash_erase和nandwrite等命令把新的内核或是文件系统写入到bak_kernel或是bak_rootfs分区中,然后把judge分区的相应标志设置为"no"即可在下次重启后自动更新。不过有一点需要注意,如果需要直接在系统中使用命令进行升级更新内核和文件系统,默认是不允许的,因为我规划的bak_kernel和bak_rootfs分区是只读的,这个在bootargs的启动参数里有定义。那么如果你真的想在系统中直接升级更新可以使用我额外编写的一个小驱动,它的目的就是用来设置相应的分区为可写,代码如下:
执行insmod writeable.ko mtdname=bak_kernel或是bak_rootfs便可以设置相应的分区为可写,即使驱动加载是失败的。
随后便可以使用flash_erase和nandwrite对此mtd分区进行擦除和写入了。

另外在我的设计中,data分区存放的是用户常用目录的生成的压缩包,datasize存放在user分区上文已经提到过,系统启动后如果在常用目录没有找到相应的可执行程序则会从data分区中进行恢复,例如文件系统损坏恢复后常用目录里面肯定都是空的,这时就需要从data分区中恢复用户数据了,而且在用户对系统的某个控制程序进行升级时也会备份一份到data分区,这样其实也实现了用户数据的备份机制。这些操作也需要添加了文件系统的启动脚本中,这里就不再展示具体内容实现了。

最后给出制作ubifs文件系统的脚本,因为我觉得文件系统通过buildroot编译好之后,还需要对新生成的文件系统做一些额外的调整或是裁剪,例如一些不用的命令要删除,修改一些脚本之内的,每次修改完还是再次通过buildroot来编译太过麻烦,因此我直接做了一个脚本方便生成ubifs镜像,脚本如下:

PS:第一次写博客,花了一下午的时间,如有描述不对之处,欢迎各位大神来拍砖,也希望可以对后续同行有所帮助!

0 0
原创粉丝点击