前言:前阵子公司使用的基于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>mtd name offset size blocks mtd0 bootstrap: 0x0 256k 2 mtd1 uboot 0x40000 512k 4 mtd2 env 0xc0000 256k 2 mtd3 judge 0x100000 1M 8 mtd4 user 0x200000 1M 8 mtd5 bak_kernel 0x300000 5M 40 mtd6 kernel 0x800000 5M 40 mtd7 rootfs 0xd00000 143M 1144 mtd8 data 0x9c00000 40M 320 mtd9 bak_rootfs 0xc400000 60M 480</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文件增加上这个头部校验信息,我的代码实现如下:
#include #include #include #include #include #include #include #define PMECC_HEADER 0xc0902405 //correct bits: 4bits, PMECC sector size: 512 int main(int argc, char **argv) { int fd; char buf[0x10000]; unsigned int *pmecc_header = (unsigned int *)buf; struct stat stat; int i; fd = open((argc == 2) ? argv[1] : "binaries/at91sam9x5ek-nandflashboot-uboot-3.5.4.bin", O_RDONLY); if (fd == -1) { perror("open binaries/at91sam9x5ek-nandflashboot-linux-3.5.4.bin fail"); return -1; } memset(buf, 0xff, sizeof(buf)); for (i = 0; i < 0x34; i++) pmecc_header[i] = PMECC_HEADER; memset(&stat, 0, sizeof(stat)); if (-1 == fstat(fd, &stat)) { close(fd); perror("fstat error"); return -1; } read(fd, &buf[0xd0], stat.st_size); memcpy(&buf[0xe4], &stat.st_size, 4); close(fd); fd = creat("bootstrap", 0666); if (fd == -1) { perror("create bootstrap fail"); return -1; } write(fd, buf, sizeof(buf)); fdatasync(fd); close(fd); return 0; }
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_ETHADDR 0c: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_IPADDR 172.7.18.99<span style="white-space:pre"> </span>/*默认的ip地址*/ #define CONFIG_NETMASK 255.255.255.0<span style="white-space:pre"> </span>/*默认的子网掩码*/ #define CONFIG_GATEWAYIP 172.7.18.1<span style="white-space:pre"> </span>/*默认网关*/ #define CONFIG_SERVERIP 172.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=yes rootfs_boot_succ=yesuser分区存放的数据格式为:
datasize=2048 kernelsize=300000 rootfssize=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) { int ret = 1; ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE); int i, num = CONFIG_ENV_JUDGE_SIZE / CONFIG_ENV_SIZE; ssize_t len; 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添加如下代码:
/* * (C) Copyright 2000-2013 * Wolfgang Denk, DENX Software Engineering, wd@denx.de. * * (C) Copyright 2001 Sysgo Real-Time Solutions, GmbH * Andreas Heppel * * Copyright 2011 Freescale Semiconductor, Inc. * * SPDX-License-Identifier: GPL-2.0+ */ /* * Support for persistent environment data * * The "environment" is stored on external storage as a list of '\0' * terminated "name=value" strings. The end of the list is marked by * a double '\0'. The environment is preceeded by a 32 bit CRC over * the data part and, in case of redundant environment, a byte of * flags. * * This linearized representation will also be used before * relocation, i. e. as long as we don't have a full C runtime * environment. After that, we use a hash table. */ #include #include #include #include #include #include #include #include #include #include #include DECLARE_GLOBAL_DATA_PTR; #if !defined(CONFIG_ENV_IS_IN_EEPROM) && \ !defined(CONFIG_ENV_IS_IN_FLASH) && \ !defined(CONFIG_ENV_IS_IN_DATAFLASH) && \ !defined(CONFIG_ENV_IS_IN_MMC) && \ !defined(CONFIG_ENV_IS_IN_FAT) && \ !defined(CONFIG_ENV_IS_IN_NAND) && \ !defined(CONFIG_ENV_IS_IN_NVRAM) && \ !defined(CONFIG_ENV_IS_IN_ONENAND) && \ !defined(CONFIG_ENV_IS_IN_SPI_FLASH) && \ !defined(CONFIG_ENV_IS_IN_REMOTE) && \ !defined(CONFIG_ENV_IS_IN_UBI) && \ !defined(CONFIG_ENV_IS_NOWHERE) # error Define one of CONFIG_ENV_IS_IN_{EEPROM|FLASH|DATAFLASH|ONENAND|\ SPI_FLASH|NVRAM|MMC|FAT|REMOTE|UBI} or CONFIG_ENV_IS_NOWHERE #endif #ifndef CONFIG_SPL_BUILD /* * Command interface: print one or all environment variables * * Returns 0 in case of error, or length of printed string */ static int env_print_judge(char *name, int flag) { char *res = NULL; ssize_t len; if (name) { /* print a single name */ ENTRY e, *ep; e.key = name; e.data = NULL; hsearch_r(e, FIND, &ep, &env_htab_judge, flag); if (ep == NULL) return 0; len = printf("%s=%s\n", ep->key, ep->data); return len; } /* print whole list */ len = hexport_r(&env_htab_judge, '\n', flag, &res, 0, 0, NULL); if (len > 0) { puts(res); free(res); return len; } /* should never happen */ printf("## Error: cannot export environment\n"); return 0; } static int do_env_print_judge(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { int i; int rcode = 0; int env_flag = H_HIDE_DOT; if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'a') { argc--; argv++; env_flag &= ~H_HIDE_DOT; } if (argc == 1) { /* print all env vars */ rcode = env_print_judge(NULL, env_flag); if (!rcode) return 1; printf("\nEnvironment size: %d/%ld bytes\n", rcode, (ulong)ENV_SIZE); return 0; } /* print selected env vars */ env_flag &= ~H_HIDE_DOT; for (i = 1; i < argc; ++i) { int rc = env_print_judge(argv[i], env_flag); if (!rc) { printf("## Error: \"%s\" not defined\n", argv[i]); ++rcode; } } return rcode; } #endif /* CONFIG_SPL_BUILD */ /* * Set a new environment variable, * or replace or delete an existing one. */ static int _do_env_set_judge(int flag, int argc, char * const argv[]) { int i, len; char *name, *value, *s; ENTRY e, *ep; int env_flag = H_INTERACTIVE; debug("Initial value for argc=%d\n", argc); while (argc > 1 && **(argv + 1) == '-') { char *arg = *++argv; --argc; while (*++arg) { switch (*arg) { case 'f': /* force */ env_flag |= H_FORCE; break; default: return CMD_RET_USAGE; } } } debug("Final value for argc=%d\n", argc); name = argv[1]; value = argv[2]; if (strchr(name, '=')) { printf("## Error: illegal character '='" "in variable name \"%s\"\n", name); return 1; } /* Delete only ? */ if (argc < 3 || argv[2] == NULL) { int rc = hdelete_r(name, &env_htab_judge, env_flag); return !rc; } /* * Insert / replace new value */ for (i = 2, len = 0; i < argc; ++i) len += strlen(argv[i]) + 1; value = malloc(len); if (value == NULL) { printf("## Can't malloc %d bytes\n", len); return 1; } for (i = 2, s = value; i < argc; ++i) { char *v = argv[i]; while ((*s++ = *v++) != '\0') ; *(s - 1) = ' '; } if (s != value) *--s = '\0'; e.key = name; e.data = value; hsearch_r(e, ENTER, &ep, &env_htab_judge, env_flag); free(value); if (!ep) { printf("## Error inserting \"%s\" variable, errno=%d\n", name, errno); return 1; } return 0; } int setenv_judge(const char *varname, const char *varvalue) { const char * const argv[4] = { "setenv", varname, varvalue, NULL }; /* before import into hashtable */ if (!(gd->flags & GD_FLG_ENV_READY)) return 1; if (varvalue == NULL || varvalue[0] == '\0') return _do_env_set_judge(0, 2, (char * const *)argv); else return _do_env_set_judge(0, 3, (char * const *)argv); } #ifndef CONFIG_SPL_BUILD static int do_env_set_judge(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { if (argc < 2) return CMD_RET_USAGE; return _do_env_set_judge(flag, argc, argv); } #endif /* CONFIG_SPL_BUILD */ /* * Look up variable from environment, * return address of storage for that variable, * or NULL if not found */ char *getenv_judge(const char *name) { if (gd->flags & GD_FLG_ENV_READY) { /* after import into hashtable */ ENTRY e, *ep; WATCHDOG_RESET(); e.key = name; e.data = NULL; hsearch_r(e, FIND, &ep, &env_htab_judge, 0); return ep ? ep->data : NULL; } return NULL; } #ifndef CONFIG_SPL_BUILD #if defined(CONFIG_CMD_SAVEENV) && !defined(CONFIG_ENV_IS_NOWHERE) static int do_env_save_judge(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { printf("Saving Environment to judge partition\n"); return saveenv_judge() ? 1 : 0; } U_BOOT_CMD( saveenv_judge, 1, 0, do_env_save_judge, "save environment variables to judge partition", "" ); #endif U_BOOT_CMD_COMPLETE( printenv_judge, CONFIG_SYS_MAXARGS, 1, do_env_print_judge, "print environment variables of judge partition", "[-a]\n - print [all] values of all environment variables\n" "printenv name ...\n" " - print value of environment variable 'name'", var_complete ); U_BOOT_CMD_COMPLETE( setenv_judge, CONFIG_SYS_MAXARGS, 0, do_env_set_judge, "set environment variables of judge partition", "[-f] name value ...\n" " - [forcibly] set environment variable 'name' to 'value ...'\n" "setenv [-f] name\n" " - [forcibly] delete environment variable 'name'", var_complete ); #endif /* CONFIG_SPL_BUILD */
最后修改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; #endif debug("### 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"); else sprintf(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); #else run_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_MENUKEY if (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); #endif static 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的启动参数里有定义。那么如果你真的想在系统中直接升级更新可以使用我额外编写的一个小驱动,它的目的就是用来设置相应的分区为可写,代码如下:
#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static char *mtdname = "bootstrap/kernel"; module_param(mtdname, charp, 0644); static uint offset = 0; module_param(offset, uint, 0644); static uint size = 0; module_param(size, uint, 0644); static int __init io_init(void) { struct mtd_info *mtd = get_mtd_device_nm(mtdname); struct erase_info erase; uint left = 0; if (IS_ERR(mtd)) { printk(KERN_WARNING "the judge mtd partition isn't exist!"); return - 1; } printk(KERN_INFO "get %s mtd device info succ!\n", mtdname); mtd->flags |= MTD_WRITEABLE; //设置为可写 if (size >= 128 * 1024) { printk(KERN_INFO "erase %s mtd device, offset: %u, size: %u\n", mtdname, offset, size); left = size % mtd->erasesize; if (left) size += mtd->erasesize - left; memset(&erase, 0, sizeof(erase)); erase.mtd = mtd; erase.addr = offset; erase.len = size; mtd->erase(mtd, &erase); } put_mtd_device(mtd); return -1; } static void __exit io_exit(void) { } module_init(io_init); module_exit(io_exit); MODULE_AUTHOR("Hikvision Corporation"); MODULE_DESCRIPTION("Signal machine main controller board erase flash driver"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_VERSION("1.0.0"); ifneq ($(KERNELRELEASE),) obj-m := writeable.o else #KERNELDIR ?= /lib/modules/$(shell uname -r)/build KERNELDIR ?= /usr/jay/kernel_code/linux-at91-linux-2.6.39-at91 PWD := $(shell pwd) default: clean make -C $(KERNELDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- rm -rf *.o .*.cmd .tmp_versions *.mod.c modules.order Module.symvers *.ko.unsigned clean: $(RM) -r *.ko *.o .*.cmd .tmp_versions *.mod.c modules.order Module.symvers *.ko.unsigned endif
随后便可以使用flash_erase和nandwrite对此mtd分区进行擦除和写入了。
另外在我的设计中,data分区存放的是用户常用目录的生成的压缩包,datasize存放在user分区上文已经提到过,系统启动后如果在常用目录没有找到相应的可执行程序则会从data分区中进行恢复,例如文件系统损坏恢复后常用目录里面肯定都是空的,这时就需要从data分区中恢复用户数据了,而且在用户对系统的某个控制程序进行升级时也会备份一份到data分区,这样其实也实现了用户数据的备份机制。这些操作也需要添加了文件系统的启动脚本中,这里就不再展示具体内容实现了。
最后给出制作ubifs文件系统的脚本,因为我觉得文件系统通过buildroot编译好之后,还需要对新生成的文件系统做一些额外的调整或是裁剪,例如一些不用的命令要删除,修改一些脚本之内的,每次修改完还是再次通过buildroot来编译太过麻烦,因此我直接做了一个脚本方便生成ubifs镜像,脚本如下:
#!/bin/sh dir=`pwd` #逻辑擦除块大小 UBIFS_LEBSIZE=0x1f000 #最小的输入输出大小 UBIFS_MINIOSIZE=0x800 #最大逻辑擦除块数量 UBIFS_MAXLEBCNT=2048 # -x 的相关选项 UBIFS_RT_NONE= UBIFS_RT_ZLIB= UBIFS_RT_LZO=y #物理擦除块大小 UBI_PEBSIZE=0x20000 #子页面大小 UBI_SUBSIZE=2048 #制作ubifs的其他配置选项 UBIFS_OPTS= #使用ubinize制作rootfs.ubi的选项 UBI_OPTS= MKFS_UBIFS_OPTS="-e $UBIFS_LEBSIZE -c $UBIFS_MAXLEBCNT -m $UBIFS_MINIOSIZE" if [ -n "$UBIFS_RT_NONE" ]; then MKFS_UBIFS_OPTS="$MKFS_UBIFS_OPTS -x none" fi if [ -n "$UBIFS_RT_ZLIB" ]; then MKFS_UBIFS_OPTS="$MKFS_UBIFS_OPTS -x zlib" fi if [ -n "$UBIFS_RT_LZO" ]; then MKFS_UBIFS_OPTS="$MKFS_UBIFS_OPTS -x lzo" fi MKFS_UBI_OPTS="-m $UBIFS_MINIOSIZE -p $UBI_PEBSIZE" if [ -n "$UBI_OPTS" -a "$UBI_OPTS" != '""' ]; then MKFS_UBI_OPTS="$MKFS_UBI_OPTS $UBI_OPTS" fi if [ -n "$UBI_SUBSIZE" ]; then MKFS_UBI_OPTS="$MKFS_UBI_OPTS -s $UBI_SUBSIZE" fi MKFS_UBIFS_CMD=$dir/tools/mkfs.ubifs UBINIZE_CMD=$dir/tools/ubinize UBINIZE_CFG_PATH=$dir/tools/ubinize.cfg TARGET_FILE_DIR=$dir/target ROOTFS_UBIFS_PATH=$dir/images/rootfs.ubifs ROOTFS_UBI_PATH=$dir/images/rootfs.ubi rm $ROOTFS_UBIFS_PATH $ROOTFS_UBI_PATH &> /dev/null #首先生成可供uboot中使用ubi write烧写的rootfs.ubifs $MKFS_UBIFS_CMD -d $TARGET_FILE_DIR $MKFS_UBIFS_OPTS -o $ROOTFS_UBIFS_PATH if [ $? -ne 0 ]; then echo 'generate rootfs.ubifs fail' exit 1 else echo 'generate rootfs.ubifs succ' fi #接着使用ubinize生成可直接使用nand write烧写的rootfs.ubi /bin/cp $UBINIZE_CFG_PATH . && echo "image=$ROOTFS_UBIFS_PATH" >> ubinize.cfg $UBINIZE_CMD -o $ROOTFS_UBI_PATH $MKFS_UBI_OPTS ubinize.cfg if [ $? -ne 0 ]; then echo 'generate rootfs.ubi fail' exit 1 else echo 'generate rootfs.ubi succ' fi rm -f ubinize.cfg[ubifs] mode=ubi vol_id=0 vol_type=dynamic vol_name=rootfs vol_alignment=1 vol_flags=autoresize
PS:第一次写博客,花了一下午的时间,如有描述不对之处,欢迎各位大神来拍砖,也希望可以对后续同行有所帮助!