在上一章節《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之後的某個腳本處,將標誌位清零即可。後面再次啓動的時候就不會重複燒錄