在上一章节《Openwrt sysupgrade系统升级》中,我们描述了sysupgrade升级系统的过程,这种升级过程会直接firmware分区进行写入,无法保证系统的安全性,只要在写入过程突然断电就会出现系统写入失败,升级失败无法启动系统的问题。
为了解决该问题一般会使用双固件升级的方式,有一个主分区firmware和一个备份分区firmware_backup,常见的有双固件升级方式有很多种,这边只介绍一种通用方式
1.升级流程
- 1.根据sysupgrade的过程,将固件进行校验写入,不过写入的时候将升级文件写入到备份分区firmware_backup,不直接写主分区firmware。
- 2.写完备份分区后,设置备份分区写入完成标志位(一般会开辟一块很小的分区用来写标志位),然后重启系统
- 3.uboot启动的时候,检测到备份分区标志位被置位,则读取备份分区firmware_backup的固件内容。
- 4.对firmware_backup的内容进行校验,校验一切正常后,将firmware_backup的内容写入到firmware分区。
- 5.firmware写入完成后,启动系统,系统启动完成后将备份分区标志位清零,这样下次启动后就不会再次升级系统。
通过双固件的方式就不会出现升级失败系统启动不了的问题。
- 如果步骤1写入过程被断电也不会出问题,因为主分区正常启动,只是没有升级到最新的分区而已。
- 如果是步骤4升级过程被断电也不会出问题,再次启动的时候会检测到主分区有问题会被再次写入。
2.修改内容
2.1 sysupgrade写入备份分区
PART_NAME修改位备份分区
PART_NAME=firmware_backup
写入固件完成重启前,设置备份分区标志位
//set backup_flg=1
v "Upgrade completed"
[ -n "$DELAY" ] && sleep "$DELAY"
v "Rebooting system..."
upgrade_log_end
reboot -f
sleep 2
force_reboot
2.2 uboot对双系统的支持
双固件最主要的工作都在uboot下面进行,一些mtk提供的新版本uboot一般会有支持部分双固件功能,位于dual_image.c文件中。
uboot添加双固件支持的配置
CONFIG_MTK_DUAL_IMAGE_SUPPORT=y
CONFIG_MTK_DUAL_IMAGE_PARTNAME_MAIN="firmware"
CONFIG_MTK_DUAL_IMAGE_PARTNAME_BACKUP="firmware_backup"
CONFIG_MTK_DUAL_IMAGE_SQUASHFS_DATA_CHECK=y
# CONFIG_MTK_DUAL_IMAGE_RESTORE_KERNEL_ONLY is not set
CONFIG_MTDPARTS_DEFAULT="mtdparts=raspi:576k(u-boot),7360k(firmware),7360k(firmware_backup)"
dual_image_check()函数里面就是校验firmware合法性的内容。
firmware为kernel+rootfs,所以校验的时候两块都会进行验证
printf("Verifying main image at 0x%llx...\n", image1_off);
ret = verify_image(flash, image1_off, image1_partsize, &image1_size);
if (ret < 0) {
printf("Dual image checking is bypassed\n");
return 0;
}
if (ret == 0) {
ret = verify_rootfs(flash, image1_off + image1_size,
image1_partsize - image1_size,
&image1_padding_bytes, &rootfs1_size);
}
image1_ok = ret == 0;
2.2.1 kernel校验
kernel的校验有两种格式
- 一种是老版本的kernel,我们会把它称作LEGACY
- 另一种是支持最新设备树的Flattened uImage Tree,会把它称作FIT
校验函数位于verify_image 中
case IMAGE_FORMAT_LEGACY:
return verify_legacy_image(flash, offset, maxsize, load_addr,
image_size);
#if defined(CONFIG_FIT)
case IMAGE_FORMAT_FIT:
return verify_fit_image(flash, offset, maxsize, load_addr,
image_size);
#endif
default:
printf("Invalid image format\n");
return 1;
}
里面的具体校验内容,查看代码细究,里面对于几种类型的image都有对于的校验函数。
image-fdt.c
image-fit.c
image-sig.c
image.c
2.2.2 rootfs校验
现在一般使用的都是squashfs文件系统,校验函数为verify_squashfs
static int verify_rootfs(void *flash, uint64_t offset, uint64_t maxsize,
size_t *header_prefix_bytes, size_t *rootfs_size)
{
uint64_t end = offset + maxsize, leading, extra_bytes, tmp;
int ret;
ret = verify_squashfs(flash, offset, end, rootfs_size);
if (!ret) {
if (header_prefix_bytes)
*header_prefix_bytes = 0;
return 0;
}
tmp = offset;
leading = do_div(tmp, mtk_board_get_flash_erase_size(flash));
if (!leading)
return 1;
extra_bytes = mtk_board_get_flash_erase_size(flash) - leading;
offset += extra_bytes;
ret = verify_squashfs(flash, offset, end, rootfs_size);
if (!ret) {
if (header_prefix_bytes)
*header_prefix_bytes = extra_bytes;
return 0;
}
printf("No SquashFS found\n");
return 1;
}
squashfs文件系统头部有专门的结构信息,如下
#define SQUASHFS_MAGIC 0x73717368
struct squashfs_super_block {
__le32 s_magic;
__le32 pad0[9];
__le64 bytes_used;
};
校验内容
if (le32_to_cpu(sb.s_magic) != SQUASHFS_MAGIC)
return 1;
size = le64_to_cpu(sb.bytes_used);
if (offset + size >= end) {
printf("RootFS is truncated\n");
return 1;
}
2.2.3 根据校验结果进行操作
- 如果两个分区的固件都有问题,则报错
- 如果主分区和备份分区都没有问题,判断备份分区是否有置位,置位则将备份分区拷贝到主分区;
- 如果主分区正常,备份分区异常,则将主分区拷贝到备份分区
- 如果主分区异常,备份分区正常,则将备份分区拷贝到主分区
2.2.4 注意事项
spi flash的块大小为64K,有时候为了节省空间把设置标志位的分区设置成4k,它不是64k的倍数,这样mtd 读写的时候也会报错
Detected w25q128bv with page size 256 Bytes, erase size 64 KiB, total 16 MiB
另外mount的时候,一般要大于64k的5倍,不然也会出现一直挂载不上
2.3 系统标志位清零
系统启动成功后,在init之后的某个脚本处,将标志位清零即可。后面再次启动的时候就不会重复烧录
|