我现在用的板子是Realtek产商的,flash是nornand的,有两个分区,分别为rootfs和rootfs_1
板子切换分区的功能是由环境变量priority_root=rootfs来控制的
通过fw_printenv来查看环境变量,如下:
root@:/# fw_printenv
flash_type=0
hw_mac_addr=00:A0:11:22:33:44
.......省略
netmask=255.255.255.0
stderr=serial
stdin=serial
stdout=serial
priority_root=rootfs
当priority_root=rootfs_1时,再重启,板子就会从rootfs_1这个分区启动进入。
那么现在有个问题:这两个分区中有一个损坏了,如何自动切换到另外一个好的分区呢?
例如现在我的分区是rootfs,但是这个rootfs分区损坏了,进不去文件系统了,怎么切换到rootfs_1分区?
(注意,进不去文件系统证明系统已经无法正常工作!如果你的板子只有一个分区且分区又损坏了,那么已经GG了,重新烧code或者换另外一块板子继续工作吧。
因为一般分区损坏便是flash出问题,硬件出问题的机会更大,如果软件有问题,那么这版软件是不允许发布的!所以能发布的软件必须是OK的!)
那么针对上面提出的问题,提供两种解决方法!
一种是通过检查分区的CRC值来检验,二是初始化出两个分区信息
先看下效果图:
怕图片不清晰,附上信息
flash_type: 0
Hit any key to stop autoboot: 0
=========support upload error code off============
Support Dual Image
Start to check crc...
current is rootfs.now get rootfs_crc #可以看到现在的分区是rootfs
NAND read: device 1 offset 0x0, size 0x14c0000
21757952 bytes read: OK
env_crc:[123456] #这个env_crc的值是之前就获取出来保存好的,但这里我为了测试这个机制,便直接修改此值,让这个机制生效!
CRC for 84000000 ... 854bffff ==> ded0e7c7 #这个值ded0e7c7,是对当前的整个分区做crc校验的返回值,此值每次重启系统都会重新检验一次分区
#接下来的工作就是检查两个值是否一致,不一致那么证明系统挂掉了,于是切换分区
Check crc fail!try to switch to another image...
switch rootfs_1
check crc done
#至此,已经切换分区了,下面开始跑另外一个分区rootfs_1
priority_root=[rootfs_1]
prepare_start_rootfs_1
Check partition 2 image kernel&rootfs
Check partition 2 image kernel
check kernel cmd: nand device 1 && set mtdids nand1=nand1 && set mtdparts mtdparts=nand1:0x3f00000@0x4000000(fs),${msmparts} && ubi part fs && ubi read 0x84000000 kernel &&
Creating 1 MTD partitions on "nand1":
上面图片简单解释:文件系统损坏时,那么里面的东西就发生变化了,所以对文件系统再次做crc的检验时,得到的crc值和正常时是绝对不会一样的!
第一种方法便是根据crc校验值来判断系统是否损坏!
下面附上系统正常时的情况:
可以看到,正常时两个值都是ded0e7c7!
CRC值检验成功,那么便不会做任何事情,正常跑原来的rootfs_1分区!
说下需要用到的工具:
1、nanddump命令,#此命令用来讲所要校验的分区导出到文件
2、crcsum命令 #此命令用来校验分区信息
说下大概思路:
1、在系统升级的时候,将要升级的当前分区信息用nanddump命令导出到文件里面,然后使用crcsum命令对导出的分区镜像文件做一个crc的校验,返回值就是当前分区的校验值,然后保存到环境变量。
2、然后在重启系统的时候,在uboot层就做crc的校验,成功则运行,失败则切换到另外一个分区启动!
注意:大概说下系统启动顺序:内核启动->uboot启动->文件系统启动->服务程序配置。
机制流程
1、首先用nanddump命令将分区的信息导出来,目的是为了做crc的检验
//在upgrade/common.sh脚本中增加导出分区信息的机制
//我的是comman.sh脚本,该机制要加在系统升级的机制函数do_upgrade()里面,不清楚升级机制的,去搜下openwrt的do_upgrade()升级机制了解下
//系统升级的时候完成后会重启,这个机制就是在系统升级完之后,重启之前,对当前分区的crc校验完再进行重启。
do_upgrade() {
echo "Performing system upgrade..." > /dev/console
if type 'platform_do_upgrade' >/dev/null 2>/dev/null; then
platform_do_upgrade "$ARGV"
else
default_do_upgrade "$ARGV"
fi
sleep 1
if [ "$SAVE_CONFIG" -eq 1 ] && type 'platform_copy_config' >/dev/null 2>/dev/null; then
platform_copy_config
fi
##########上面的是openwrt自身的升级机制,到这里,系统已经完成升级了,下面开始对分区做CRC校验##################################
echo "nanddump start" > /dev/console
mtdname=`fw_printenv |grep priority_root|cut -d "=" -f2` #判断当前的那个分区
if [ "$mtdname" == "rootfs" ]; then
echo "nanddump mtd11"
nanddump /dev/mtd11 -a -f /tmp/image.bin -l 0x014c0000 #导出分区信息到tmp下的/image.bin文件中
rootfs1_crc=`crcsum /tmp/image.bin | awk -F' ' '{print $1}'` #做crc的校验,得到一个校验值
fw_setenv rootfs1_crc $rootfs1_crc #CRC的校验值保存到环境变量里面,现在CRC校验值已经保存到rootfs1_crc 变量里面了
else
echo "nanddump mtd10"
nanddump /dev/mtd10 -a -f /tmp/image.bin -l 0x014c0000
rootfs_crc=`crcsum /tmp/image.bin | awk -F' ' '{print $1}'`
fw_setenv rootfs_crc $rootfs_crc
fi
fw_size=`crcsum /tmp/image.bin | awk -F' ' '{print $2}'`
fw_setenv fw_size "0x$fw_size"
rm /tmp/image.bin #对分区做CRC的校验得到的值保存进环境变量之后,导出的文件便不需要了
echo "nanddump completed" > /dev/console
该功能一句话概括就是:升级时nanddump出当前分区mtd的crc值,保存到环境变量
2、在uboot中一个檢查crc的機制:检查现在的crc值和env环境变量中的crc值并进行比较,crc值一样证明分区信息没有损坏,正常往下启动系统,失敗就切換到另外一个分區
我的项目uboot启动流程主要由cmd_bootqca.c控制
该文件在openwrt架构的build_dir/目录下的uboot相关目录里面
不清楚的先去了解下openwrt架构的uboot结构
//这个是我写的一个16进制的字符串转换成整形的函数,下面的check_crc函数有用到
//该函数只是为了我的这个功能而写的,可能不具有通用性
static int htoi(char s[])
{
int i;
int n = 0;
if (s[0] == '0' && (s[1]=='x' || s[1]=='X'))
i = 2;
else
i = 0;
for (; (s[i] >= '0' && s[i] <= '9') || (s[i] >= 'a' && s[i] <= 'z') || (s[i] >='A' && s[i] <= 'Z');++i)
{
if (s[i] > '9')
n = 16 * n + (10 + s[i] - 'a');
else
n = 16 * n + (s[i] - '0');
}
return n;
}
static int check_crc(char* fw_size){
ulong addr, length;
ulong crc,num_crc;
char *new_crc;
char *addr_crc="0x84000000";
//char *length_crc="0x14c0000";
char runcmd[256];
//我们之前已经把分区的crc检验值保存到环境变量了,现在把它获取出来
printf("Start to check crc...\n");
if(!strcmp(getenv("priority_root"), "rootfs"))
{
printf("current is rootfs.now get rootfs_crc\n");
new_crc=getenv("rootfs_crc");
snprintf(runcmd, sizeof(runcmd),"nand read 0x84000000 0 %s",fw_size);
run_command(runcmd, 0);
}
else
{
printf("current is rootfs_1.now get rootfs1_crc\n");
new_crc=getenv("rootfs1_crc");
snprintf(runcmd, sizeof(runcmd),"nand read 0x84000000 0x4000000 %s",fw_size);
run_command(runcmd, 0);
}
//获取到crc值后,要做一个转换,因为获取到的是0x00000000这种16进制形式的,并且是字符串,我们需要转换为Int数据
num_crc=htoi(new_crc);
printf("env_crc:[%x]\n",num_crc);
//下面获取当前的mtd分区的crc值,与环境变量里的crc值做比较
addr = simple_strtoul (addr_crc, NULL, 16);
length = simple_strtoul (fw_size, NULL, 16);
crc = crc32_wd (0, (const uchar *) addr, length, CHUNKSZ_CRC32);
printf ("CRC for %08lx ... %08lx ==> %08lx\n",
addr, addr + length - 1, crc);
//check crc,如果两个相等,那么分区正常,不需要做什么
if(crc == num_crc)
{
printf("Check crc pass!\n");
}
else
{
printf("Check crc fail!try to switch to another image...\n");
if(!strcmp(getenv("priority_root"), "rootfs"))
{
if(getenv("rootfs_crc") != NULL)
{
setenv("priority_root","rootfs_1");//所有的东西都是为了这句话,切换分区
printf("switch rootfs_1\n");
}
else
{
printf("rootfs_crc is null ! do nothing\n");
}
}
else
{
if(getenv("rootfs1_crc") != NULL)
{
setenv("priority_root","rootfs");//所有的东西都是为了这句话,切换分区
printf("switch rootfs\n");
}
else
{
printf("rootfs1_crc is null!do nothing\n");
}
}
}
printf("check crc done\n");
return 0;
}
//这个函数就是我的项目uboot启动的检查机制入口,在这个函数开始的地方增加crc的检验机制
static int dual_image_start(void){
//........................
if( getenv("fw_size") != NULL )
{
fw_size = getenv("fw_size");
check_crc(fw_size);
//调用check CRC的检查机制,对当前正在运行的分区做crc的检验,然后和之前在环境变量里面保存好的crc值比较,看是否一致
}
//..........................
}
我这个功能还需要做一个产测的接口,给工厂调用,这里不再细言。
注意事项:
项目不同,控制分区启动的方法就不同。
1、首先我的分区信息可以用nanddump命令来获取,如果你没有类似功能的命令或函数,那么该方法GG。
2、其次我的分区启动方式是由环境变量控制,如果你的启动方式不是这种控制方式,那么核心的切换分区动作需要变更。
3、该方法仅供参考,至于第二种方式需要详细的研究内核、uboot层、mtd系统以及系统启动流程这些,有兴趣自行研究吧☺。
建议:凡是涉及到uboot层的功能修改,最好先做测试,测试OK了再编译源码烧写镜像,否则uboot的损坏很容易让系统崩溃并且有无法修复的可能。