1 概述
OTA 是Over The Air 的簡稱,顧名思義就是通過無線網絡從服務器上下載更新文件對本地系統或文件進行升級,便于客戶為其用戶及時更新系統和應用以提供更
好的產品服務,這對于客戶和消費者都極其重要。
1.1 編寫目的
本文主要服務于使用Tina 軟件平臺的廣大客戶,以冀幫助客戶使用Tina 平臺的OTA 升級系統并做二次開發。
1.2 適用范圍
Allwinner 軟件平臺Tina。
1.3 相關人員
適用Tina 平臺的廣大客戶和關心OTA 的相關人員。
1.4 OTA 方案
1.4.1 recovery 系統方案
recovery 系統方案,是在主系統之外,增加一個recovery 系統。升級時,主系統負責升級recovery系統,recovery 系統負責升級主系統。
這樣如果升級中途發生掉電,也不會影響當前正在使用的這個系統。重啟后仍可正常進入系統,繼續完成升級。
一般recovery 系統會使用intiramfs 功能,并大量裁剪不必要的應用,只保留OTA 必需的功能,把size 盡量減小。
recovery 系統方案優點:
recovery 系統可以做得比較小,省flash 空間。
recovery 系統方案缺點:
recovery 系統一般不包含主應用,所以OTA 期間,處于recovery 系統中時,無法為用戶正常提供服務。
需要重啟兩次。
需要維護兩份系統配置,即主系統和recovery 系統。
1.4.2 AB 系統方案
AB 系統方案,是將原有的系統,增加一份。即flash 上總共有AB 兩套系統。兩套系統互相升級。OTA 時,若當前運行的是A 系統,則升級B 系統,升級完成后,
設置標志,重啟切換到B系統。OTA 時,若當前運行的是B 系統,則升級A 系統,升級完成后,設置標志,重啟切換到A 系統。
AB 系統方案優點:
更新過程是在完整系統中進行的,更新期間可正常提供服務,用戶無感知。最終做一次重啟即可。
邏輯簡單,只重啟一次。
只維護一套系統配置。
AB 系統方案缺點:
flash 占用較大。
2 ota-burnboot 介紹
2.1 文檔說明
此文檔主要介紹如何在OTA 時升級boot0/uboot。
升級工具包含兩個方面內容:
OTA 命令升級boot0 和uboot。
OTA 升級boot0 和uboot 的C/C++ APIs。
2.2 概念說明
?
表2-1: ota-burnboot 相關概念說明表
?
概念 | 說明 |
---|---|
boot0 | 較為簡單, 主要作用是初始化dram 并加載運行uboot。一般不需修改。 |
uboot | 功能較豐富, 支持燒寫, 啟動內核, 燒key 及其他一些定制化的功能。 |
sys_config | 全志特有的配置文件, 對于使用linxu3.4/uboot2011 的平臺, 在打包之 后sys_config 會跟uboot 拼接到一起。對于使用 linux3.10/uboot2014 及更高版本的平臺,sys_config 會在打包階段, 跟設備樹的配置合并, 生成最終的dtb。linux5.4 開始不再合并到 dtb。 |
dtb | 設備樹, 由dts 配置和sys_config 配置綜合得到。 |
u-boot.fex | 使用linxu3.4/uboot2011 的平臺最終用到的uboot, 其實是 uboot+sys_config。 |
boot_package.fex | 使用linux3.10/uboot2014 及更高版本的平臺最終用到的uboot, 其 實包含的文件由配置文件boot_package.cfg 決定, 一般至少包含了 uboot 和dtb, 安全方案會包含一些安全所需文件文件, 可能還有 bootlogo 等文件。 |
toc0.fex | 安全方案使用的boot0。 |
toc1.fex | 安全方案使用的uboot, 類似boot_package.fex 說明, 其中實際也包 含了dts 等多個文件。 |
即, 本文介紹的升級uboot, 其實是升級uboot+dtb 這樣的一個整體文件。后文不再區分更新uboot, 更新sys_config, 更新dtb。這幾個打包完畢是合成一個文件的,
暫不支持單獨更新其中一個, 需整體更新。
2.3 用于更新的bin 文件
獲取用于OTA 的boot0 與uboot 的bin 文件, 用于加入OTA 包中。
2.3.1 編譯boot0 uboot
如果原本的固件生成流程已經包含編譯uboot, 則正常編譯固件即可。
否則可按照如下步驟編譯生成uboot
$ source build/envsetup.sh
=> 設置環境變量。
$ lunch
=> 選擇方案。
$ muboot
=> 編譯uboot。
$ mboot0
=> 編譯boot0 (注意,此命令在大多數平臺無效,因為boot0不開源,SDK中提供了編譯好的bin文件)。
編譯后會自行拷貝bin 文件到該平臺的目錄下, 即:
對于tina3.5.0 及之前版本,路徑為:
target/allwinner/xxx-common/bin
對于tina3.5.1 及之后版本,路徑為:
device/config/chips/${CHIP}/bin
編譯出的boot0/uboot 還不能直接用于OTA, 請繼續編譯和打包固件, 如執行:
$ make -j
=> 編譯命令,若只修改boot0/uboot/sys_config 無需重新編譯,可跳過。
=> 若修改了dts 則需要執行,重新編譯。
$ pack [-d]。
=> 非安全方案的打包命令。
$ pack -s [-d]
=> 安全方案的打包命令。
2.3.2 關于更新boot0
大多數平臺,代碼環境中并不包含boot0 相關代碼, 因此無法編譯boot0。
一般情況下并不需要修改boot0, 而是直接使用提供的boot0 的bin 文件即可。少部分平臺提供了可編譯的boot0 代碼,可使用mboot0 編譯。
2.3.3 Bin 文件路徑
2.3.3.1 使用uboot2011 的非安全方案
以R16 的astar-parrot 方案為例。
根據對應存儲介質選擇bin。
boot0:
out/astar-parrot/image/boot0_nand.fex :nand 方案使用的boot0。
out/astar-parrot/image/boot0_sdcard.fex :mmc 方案使用的boot0。
out/astar-parrot/image/boot0_spinor.fex :nor 方案使用的boot0。
uboot:
out/astar-parrot/image/u-boot.fex :nand/mmc 方案使用的uboot。
out/astar-parrot/image/u-boot-spinor.fex :nor 方案使用的uboot。
2.3.3.2 使用uboot2014 及更高版本的非安全方案
以R6 的sitar-evb 方案為例。
根據對應存儲介質選擇bin。
boot0:
out/sitar-evb/image/boot0_nand.fex :nand 方案使用的boot0。
out/sitar-evb/image/boot0_sdcard.fex :mmc 方案使用的boot0。
out/sitar-evb/image/boot0_spinor.fex :nor 方案使用的boot0。
uboot:
out/sitar-evb/image/boot_package.fex :nand/mmc 方案使用的uboot。
out/sitar-evb/image/boot_package_nor.fex :nor 方案使用的uboot。
2.3.3.3 安全方案
以R18 的tulip-noma 方案為例。
boot0:
out/tulip-noma/image/toc0.fex :安全方案使用的boot0。
uboot:
out/tulip-noma/image/toc1.fex :安全方案使用的uboot。
2.4 OTA 升級命令
2.4.1 支持OTA 升級命令
升級boot0 與uboot 分別使用ota-burnboot0 與ota-burnuboot 命令。
兩個命令都是OTA 升級boot0 和uboot 的C/C++ APIs 的封裝。
要支持本功能, 需要選中ota-burnboot 的包, 即:
Make menuconfig --> Allwinner ---> <*>ota-burnboot
2.4.2 ota-burnboot0
2.4.2.1 命令說明
$ Usage: ota-burnboot0
升級boot0, 其中boot0-image 是鏡像的路徑。
請注意, 安全和非安全方案所使用的boot0-image 是不同的, 具體見“用于更新的bin 文件” 章節。
2.4.2.2 使用示例
root@TinaLinux:/# ota-burnboot0 /tmp/boot0_nand.fex Burn Boot0 Success
2.4.3 ota-burnuboot
2.4.3.1 命令說明
$ Usage: ota-burnuboot
升級uboot, 其中uboot-image 是鏡像的路徑。請注意, 安全和非安全方案, 不同的uboot 版本,所使用的uboot-image 是不同的,具體見第二章。
2.4.3.2 使用示例
root@TinaLinux:/# ota-burnuboot /tmp/u-boot.fex Burn Uboot Success
2.5 OTA 升級C/C++ APIs
包含頭文件OTA_BurnBoot.h,使用庫libota-burnboot.so
2.5.1 int OTA_burnboot0(const char *img_path)
?
表2-2: OTA_burnboot0 函數說明表
?
函數原型 | int OTA_burnboot0(const char *img_path); |
---|---|
參數說明 | img_path:boot0 鏡像路徑 |
返回說明 | 0:成功; 非零:失敗 |
功能描述 | 燒寫boot0 |
2.5.2 int OTA_burnuboot(const char *img_path)
?
表2-3: OTA_burnuboot 函數說明表
?
函數原型 | int OTA_burnuboot(const char *img_path); |
---|---|
參數說明 | img_path:uboot 鏡像路徑 |
返回說明 | 0:成功; 非零:失敗 |
功能描述 | 燒寫uboot |
2.6 底層實現
2.6.1 如何保證安全更新boot0/uboot
前提條件是,flash 中存有不止一份boot0/uboot。在這個基礎上, 啟動流程需支持校驗并選擇完整的boot0/uboot 進行啟動, 更新流程需保證任意時刻掉電,flash 上
總存在至少一份可用的boot0/uboot。
2.6.2 Nand Flash NFTL 方案實現
在nand nftl 方案中,boot0 和uboot 是由nand 驅動管理, 保存在物理地址中, 邏輯分區不可見。
Nand 驅動會保存多份boot0 和uboot, 啟動時, 從第一份開始依次嘗試, 直到找到一份完整的boot0/uboot 進行使用。
更新boot0/uboot 時, 上層調用nand 驅動提供的接口, 驅動中會從第一份開始依次更新, 多份全部更新完畢后返回。因此可保證在OTA 過程中任意時刻掉電,flash
中均有至少一份完整的boot0/uboot 可用。再次啟動后, 只需重新調用更新接口進行更新, 直到調用成功返回即可。
目前nand 中的多份boot0/uboot 是由nand 驅動管理的, 只能整體更新, 暫不支持單獨更新其中的一份。
2.6.3 Nand Flash UBI 方案實現
在nand ubi 方案中, boot0 一般存放于mtd0 中,uboot 存放于mtd1 中。
與nftl 方案一樣,底層實際是保存多份boot0 和uboot。啟動時, 從第一份開始依次嘗試, 直到找到一份完整的boot0/uboot 進行使用。對上提供多份統一的更新接
口,軟件包會通過對mtd的iotcl 接口發起更新。
注:用戶空間直接讀寫/dev/mtdx 節點,需要內核使能CONFIG_MTD_CHAR=y。
2.6.4 MMC Flash 實現
在mmc 方案中, boot0 和uboot 各有兩份, 存在mmc 上的指定偏移處, 邏輯分區不可見。需要讀寫可直接操作/dev/mmcblk0 節點的指定偏移。
具體位置:
1 sector = 512 bytes = 0.5k。 boot0/toc0 保存了兩份,offset1: 16 sector, offset2: 256 sector。 uboot/toc1 保存了兩份,offset1: 32800 sector, offset2: 24576 sector。
啟動時會先讀取offset1,如果完整性校驗失敗,則讀取offset2。
更新時, 默認只更新offset1, 而offset2 是保持在出廠狀態的。只要offset1 正常更新了, 則啟動時會優先使用。如果在更新offset1 的過程中掉電導致數據損壞, 則自
動使用offset2 進行啟動。
如需定制策略,例如改成每次offset1 和offset2 均更新,可自行修改ota-burnboot 代碼。
2.6.5 NOR Flash 實現
nor 方案中, 只保存一份boot0 和uboot, 更新過程中掉電可能導致無法啟動, 只能進行刷機。故目前未實現ota 更新, 需后續擴展。
3 Tina SWUpdate OTA 介紹
3.1 swupdate 介紹
3.1.1 簡介
SWUpdate 是一個開源的OTA 框架,提供了一種靈活可靠的方式來更新嵌入式系統上的軟件。
官方源碼:
https://github.com/sbabic/swupdate
官方文檔:
http://sbabic.github.io/swupdate/
非官方翻譯的中文文檔:
https://zqb-all.github.io/swupdate/
源碼自帶文檔:
解壓tina/dl/swupdate-xxx.tar.xz ,解壓后的doc 目錄下即為此版本源碼附帶的文檔。
社區論壇:
https://groups.google.com/forum/#!forum/swupdate
3.1.2 移植到tina 的改動
移植到tina 主要做了以下修改:
? 位置在package/allwinner/swupdate。
? 仿照busybox,添加了配置項,可通過make menuconfig 直接配置。
? 添加patch,支持了更新boot0,uboot。
? 添加了自啟動腳本。
? 默認啟動progress 在后臺,輸出到串口。這樣升級時會打印進度條。實際方案不需要的話,可去除。客戶應用可參考progress 源碼,自行獲取進度信息。
? 默認啟動一個腳本swupdate_cmd.sh,負責完善參數,最終調用swupdate。腳本介紹詳見后續章節。
3.2 配置
3.2.1 recovery 系統介紹
若選用主系統+recovery 系統的方式,則需要一個recovery 系統。
recovery 系統是一個帶initramfs 的kernel。對應的配置文件是target/allwinner/xxx/defconfig_ota。
如果沒有此文件,可以拷貝defconfig 為defconfig_ota,再做配置裁剪。
3.2.2 系統配置命令
對于主系統,使用:
make menuconfig
配置結果保存在:
target/allwinner/xxx/defconfig
對于recovery 系統,使用:
make ota_menuconfig
配置結果保存在:
target/allwinner/xxx/defconfig_ota
3.2.3 主系統和recovery 都需要的swupdate 包
選上swupdate 包。
Allwinner --> [*]swupdate
swupdate 中還有很多細分選項,一般用默認配置即可。需要的話可以做一些調整,比如裁剪掉網絡部分。
swupdate 會依賴選中uboot-envtools 包,以提供用戶空間讀寫env 分區的功能。
3.2.4 主系統和recovery 都需要的wifimanager daemon
如果想從網絡升級,則需要啟動系統自動聯網。
一種實現方式是,使用wifimanager daemon 。當然,如果用戶自己在腳本或應用中去做聯網,則不需要此選項。
Allwinner ---> <*> wifimanager ---> [*] Enable wifimanager daemon support ---> <*> wifimanager-daemon-demo..................... Tina wifimanager daemon demo
3.2.5 配置主系統
就以上提到的幾個包,暫時沒有只針對主系統的需要選的包。
3.2.6 編譯主系統
正常make 即生成主系統。
make
3.2.7 配置recovery 系統
對于recovey 系統,需要選上ramdisk,同時建議使用xz 壓縮方式以節省flash 空間。
make ota_menuconfig ---> Target Images ---> [*] ramdisk ---> Compression (xz)
選上recovery 后綴,避免編譯recovery 系統時,影響到主系統。
make ota_menuconfig ---> Target Images ---> [*] customize image name ---> Boot Image(kernel) name suffix (boot_recovery.img/boot_initramfs_recovery.img) ---> Rootfs Image name suffix (rootfs_recovery.img)
3.2.8 編譯recovery 系統
要編譯生成recovery 系統,可使用: swupdate_make_recovery_img 或手工調用: make -j16 TARGET_CONFIG=./target/allwinner/xxx/defconfig_ota 編譯得到: out/xxx/boot_initramfs_recovery.img
3.2.9 配置env
本方案推薦使用env 來保存信息,不使用misc 分區。
uboot 會從env 分區讀取啟動命令,并根據啟動命令來啟動系統。只要我們能在用戶空間改動到env,即可控制下次啟動的系統。
3.2.9.1 boot_partition 變量
增加一個boot_partition 變量,用于指定要啟動的內核所在分區。
配置env 主要是修改boot_normal 命令,將要啟動的分區獨立成boot_partition 變量。
即從:
boot_normal=fatload sunxi_flash boot 40007fc0 uImage;bootm 40007fc0
改成:
boot_partition=boot boot_normal=fatload sunxi_flash ${boot_partition} 40007fc0 uImage;bootm 40007fc0
這樣可以通過控制boot_partition 來直接選擇下次要啟動的系統,無需uboot 介入。uboot 只需按照boot_normal 啟動即可。
對于recovery 方案,可設置boot_partition 為boot 或recovery。OTA 切換系統時,只需要改變此變量即可達到切換主系統和recovery 系統的目的。
對于AB 系統方案,可設置為boot_partition 為bootA 或bootB。OTA 切換系統時,只需要改變此變量即可達到切換kernel 的目的。
3.2.9.2 root_partition 變量
增加一個root_partition 變量,用于指定要啟動的rootfs 所在分區。
uboot 會解析分區表,找出此變量指定的分區并在cmdline 中指定root 參數。
例如,在env 中設置:
root_partition=rootfs
則啟動時uboot 會遍歷分區表,找到名字為rootfs 的分區,假設找到的分區為/dev/nand0p4,則在cmdline 中增加root=/dev/nand0p4。
kernel 需要掛載rootfs 時,取出root 參數,則得知需要掛載/dev/nand0p4 分區。
對于recovery 方案,就一直設置root_partition 為rootfs 即可。主系統需要從rootfs 分區讀取數據,而recovery 系統使用initramfs,無需從rootfs 分區讀取數據
即可正常運行OTA 應用等。當然,recovery 系統中要更新rootfs 的話,還是會訪問(寫入)rootfs 分區的,但這個動作就跟env 的root_partition 無關了。
對于AB 系統方案,可設置root_partition 為rootfsA 或rootfsB,以匹配不同的系統。OTA切換系統時,只需要改變此變量即可達到切換rootfs 的目的。
3.2.10 配置備份env
由于寫入env 時斷電,可能導致env 的數據被破壞,因此需要支持備份env。
3.2.10.1 方式一:env 分區擴展為存放兩份env
此方式是在uboot 中進行定制實現,非社區原生方案。
可在uboot源碼中搜索CONFIG_SUNXI_ENV_NOT_BACKUP, 若存在則說明支持此功能。
支持此功能后,只要uboot不配置CONFIG_SUNXI_ENV_NOT_BACKUP,則此功能默認開啟。uboot會將env數據在同一分區中進行備份。
啟用方法:
修改分區表,將env 分區擴大到128k*2=256k。
工作方式: uboot 檢測到env 分區足夠大,則激活env 備份功能,認為0-128k 存放第一份env 數據,128k-256k 存放第二份env 數據。由于env 數據本身帶有CRC 校驗,所
以可判斷一份env 是否完整。啟動時,uboot 會對兩份env 進行同步,若某一份損壞則取另一份進行覆蓋,若兩份均完整,則以第一份為準。
對于用戶空間的fw_printenv,默認只會更新第一份env,即執行fw_setenv 之后兩份env 就有差異了,要到下次啟動才由uboot 進行同步。若更新env 的過程中發
生掉電,則第一份env不完整,重新啟動時,uboot 會識別到并用第二份env 覆蓋第一份。
確認是否生效:
1.直接觀察。 用戶空間控制臺執行:
hexdump -C /dev/by-name/env
確認是否存在兩份。
2.嘗試破壞env。
dd if=/dev/zero of=/dev/by-name/env bs=1k count=1
損壞第一份env,重啟看能否正常啟動,并嘗試
fw_printenv hexdump -C /dev/by-name/env
確認env 分區數據是否正常。
3.2.10.2 方式二:增加env-redund 分區
此方式是uboot 原生功能。雖然也需要修改sunxi 的env 讀取代碼進行適配,但總體讀寫邏輯是社區原生的。 啟用方法:
1.增加env-redund 分區。
將env 分區復制一份,分區名改為env-redund。
注意只是分區名修改為env-redund,其downloadfile 仍然指定為env.fex
2.支持在打包時制作冗余env。
make menuconfig --> Global build settings --> [*] sunxi make redundant env data
注意事項: 啟用上述選項之后,打包時會調用mkenvimage 工具來制作env,對env 的格式有一定要求。
如注釋和有效配置不能合并在一行。
若env-x.x.cfg 中存在類似如下配置
bootcmd=run setargs_nand boot_normal#default nand boot
則需要改成:
#default nand boot bootcmd=run setargs_nand boot_normal
3.配置uboot 并重新編譯uboot bin。
以r328 spinand 方案為例。
在
lichee/brandy-2.0/u-boot-2018/configs/sun8iw18p1_defconfig
中增加配置:
CONFIG_SUNXI_REDUNDAND_ENVIRONMENT=y
重新編譯uboot。
4.修改fw_env.config
拷貝
package/utils/uboot-envtools/files/fw_env.config
到
target/allwinner//base-files/etc/ (若使用procd-init) target/allwinner//busybox-init-base-files/etc/ (若使用busybox-init)
修改拷貝后的fw_env.config,增加備份env 的配置。
例如原本最后一行為:
/dev/by-name/env 0x0000 0x20000
則增加一行:
/dev/by-name/env-redund 0x0000 0x20000
這樣用戶空間的fw_printenv 和fw_setenv 即可正確處理兩份env。
3.2.11 配置啟動腳本
procd-init 是默認配置好的。
busybox-init 需要手工配置下。
參考《Tina System init 使用說明文檔》, 拷貝
/package/busybox-init-base-files/files/etc/init.d/load_script.conf
到
/target/allwinner//busybox-init-base-files/etc/init.d/
并在其中添加一行:
swupdate_autorun
3.3 OTA 包
OTA 包中,需要包含sw-description 文件,以及本次升級會用到的各個文件,例如kernel,rootfs。
整個OTA 包是cpio 格式,且要求sw-description 文件在第一個。
3.3.1 OTA 策略描述文件:sw-description
sw-description 文件是swupdate 官方規定的,OTA 策略的描述文件,具體語法可參考swupdate官方文檔。
tina 提供了幾個示例:
target/allwinner/generic/swupdate/sw-description-ab target/allwinner/generic/swupdate/sw-description
也可以自行為具體的方案編寫描述文件:
target/allwinner//swupdate/sw-description
本文件在SDK 中的存放路徑和名字沒有限定,只要最終打包進OTA 包中,重命名為swdescription并放在第一個文件即可。
3.3.2 OTA 包配置文件:sw-subimgs.cfg
sw-subimgs.cfg 是tina 提供的,用于指示如何生成OTA 包。
基本格式為
swota_file_list=( #表示把文件xxx拷貝到swupdate目錄下,重命名為yyy,并把yyy打包到最終的OTA包中 xxx:yyy ) swota_copy_file_list=( #表示把文件xxx拷貝到swupdate目錄下,重命名為yyy,但不把yyy打包到最終的OTA包中 xxx:yyy )
swota_copy_file_list 存在的原因是,有一些文件我們只需要其sha256 值,而不需要文件本身。例如使用差分包配合readback handler 時,readback handler 需
要原始鏡像的sha256值用于校驗。
例子:
swota_file_list=( #將target/allwinner/generic/swupdate/sw-description-ab-sign拷貝成sw-description,后續同理。 target/allwinner/generic/swupdate/sw-description-ab-sign:sw-description out/${TARGET_BOARD}/uboot.img:uboot out/${TARGET_BOARD}/boot0.img:boot0 out/${TARGET_BOARD}/image/boot.fex.gz:kernel.gz out/${TARGET_BOARD}/image/rootfs.fex.gz:rootfs.gz out/${TARGET_BOARD}/image/rootfs.fex.zst:rootfs.zst )
tina 提供了幾個示例:
target/allwinner/generic/swupdate/sw-subimgs.cfg # 普通系統,recovery系統,整包升級。這是其余demo的基礎版本。 target/allwinner/generic/swupdate/sw-subimgs-ab.cfg # 改為AB系統。 target/allwinner/generic/swupdate/sw-subimgs-secure.cfg # 改為安全系統。 target/allwinner/generic/swupdate/sw-subimgs-ab-rdiff.cfg # 改為AB方案,差分方案。 target/allwinner/generic/swupdate/sw-subimgs-readback.cfg # 增加回讀校驗。 target/allwinner/generic/swupdate/sw-subimgs-sign.cfg # 增加簽名校驗。 target/allwinner/generic/swupdate/sw-subimgs-ubi.cfg # 改為ubi方案。
也可以自行為具體的方案編寫描述文件。
target/allwinner//swupdate/sw-subimgs.cfg
本文件在SDK 中的路徑需位于target/allwinner//swupdate 目錄下,或target/allwinner/generic/swupdate 目錄下。
名字需要命名為sw-subimg.cfg 或sw-subimgsxxx.cfg,其中xxx 可自定義。
這個限定主要是為了方便打包函數處理。在打包時,命令行傳入參數xxx,則會使用swsubimgsxxx.cfg 進行打包。
3.3.3 OTA 包生成:swupdate_pack_swu
在build/envsetup.sh 中提供了一個swupdate_pack_swu 函數。 可以參考該函數,自行實現一套打包swupdate 升級包的腳本。也可以直接使用,使用方式如下。
準備好sw-descrition 文件,具體作用和語法請參考swupdate 說明文檔。
準備好sw-subimgs.cfg 文件,里面需要每一行列出一個打包需要的子鏡像文件,即內核,rootfs 等。可以使用冒號分隔,前面為SDK 中的文件,后面為打包進OTA 包的文件名。若沒有冒號則使用原文件名字。使用相對于tina 根目錄的相對路徑進行描述。其中第一個必須為swdescription。
編譯好所需的子鏡像,例如主系統的內核和rootfs,recovery 系統等。
執行swupdate_pack_swu 生成swupdate 升級包。不帶參數執行,則會在特定路徑下尋找sw-subimgs.cfg,解析配置生成OTA 包。帶參數-xx 執行,則會在特定路徑下尋找swsubimgs-xx.cfg,解析配置生成OTA 包。例如執行swupdate_pack_swu -sign,則會尋找sw-subimgs-sign.cfg,如此方便配置多個不同用途的sw-subimgs-xx.cfg。
注:不同介質使用的boot0/uboot 鏡像不同,swupate_pack_swu 需要sys_config.fex 中的storage_type 配置明確指出介質類型,才能取得正確的boot0.img 和
uboot.img 具體可直接查看build/envsetup.sh 中swupdate_pack_swu 的實現。
3.4 recovery 系統方案舉例
3.4.1 配置分區和env
在分區表中,增加一個recovery 分區,用于保存recovery 系統。
size 根據實際recovery 系統的大小,再加點裕量。
download_file 可以留空,因為OTA 第一步就是寫入一個recovery 系統。
當然也可以配置上download_file,并在打包固件之前先編譯好recovery 系統,一并打包到固件中,這樣出廠就帶recovery 系統,后續的OTA 執行過程,可以考
慮不寫入recovert 系統,用現成的,直接重啟并升級主系統。
在env 中指定:
boot_partition=boot root_partition=rootfs
并配置boot_normal 命令,從$boot_partition 變量指定的分區加載系統。
3.4.2 配置主系統
lunch 選擇方案后, make menuconfig, 選上swupdate。
3.4.3 配置recovery 系統
假設沒有現成的recovery 系統配置,則我們從主系統配置修改得到。lunch 選擇方案后, 拷貝配置文件。
cdevice cp defconfig defconfig_ota
根據上文介紹,make ota_menuconfig 選上swupdate, ramdisk, recovery 后綴等必要的配置。
recovery 系統整個運行在ram 中,如果系統過大會無法啟動,所以需要進行裁剪。make ota_menuconfig, 將不必要的包盡量從recovery 系統中去掉。
3.4.4 準備sw-description
這里我們直接使用:
target/allwinner/generic/swupdate/sw-description
內容如下,中文部分是注釋,原文件中沒有。
/* 固定格式,最外層為software = { } */ software = { /* 版本號和描述*/ version = "0.1.0"; description = "Firmware update for Tina Project"; /* * 外層tag,stable, * 沒有特殊含義,就理解為一個字符串標志即可。 * 可以修改,調用的時候傳入匹配的字符串即可 */ stable = { /* * 內層tag,upgrade_recovery, * 當調用swupdate xxx -e stable,upgrade_recovery時,就會匹配到這部分,執行{}內的動作, * 可以修改,調用的時候傳入匹配的字符串即可 */ /* upgrade recovery,uboot,boot0 ==> change swu_mode,boot_partition ==> reboot */ upgrade_recovery = { /* 這部分是為了在主系統中,升級recovery系統,升級uboot和boot0 */ /* upgrade recovery */ images: ( /* 處理各個image */ { filename = "recovery"; /* 源文件是OTA包中的recovery文件*/ device = "/dev/by-name/recovery"; /* 要寫到/dev/by-name/recovery節點中, 這 個節點在tina上就對應recovery分區*/ installed-directly = true; /* 流式升級,即從網絡升級時邊下載邊寫入, 而不是先完 整下載到本地再寫入,避免占用額外的RAM或ROM */ }, { filename = "uboot"; /* 源文件是OTA包中的uboot文件*/ type = "awuboot"; /* type為awuboot,則swupdate會調用對應的handler做處理*/ }, { filename = "boot0"; /* 源文件是OTA包中的boot0文件*/ type = "awboot0"; /* type為awuboot,則swupdate會調用對應的handler做處理*/ } ); /* image處理完之后,需要設置一些標志,切換狀態*/ /* change swu_mode to upgrade_kernel,boot_partition to recovery & reboot*/ bootenv: ( /* 處理bootenv,會修改uboot的env分區*/ { /* 設置env:swu_mode=upgrade_kernel, 這是為了記錄OTA進度*/ name = "swu_mode"; value = "upgrade_kernel"; }, { /* 設置env:boot_partition=recovery, 這是為了切換系統,下次uboot就會啟動 recovery系統(kernel位于recovery分區) */ name = "boot_partition"; value = "recovery"; }, { /* 設置env:swu_next=reboot, 這是為了跟外部腳本配合,指示外部腳本做reboot動作*/ name = "swu_next"; value = "reboot"; } /* 實際有什么其他需求,都可以靈活增刪標志來解決, 外部腳本和應用可通過fw_setenv/ fw_printenv操作env */ /* 注意,以上幾個env,是一起在ram中修改好再寫入的, 不會出現部分寫入部分未寫入的情況*/ ); }; /* * 內層tag,upgrade_kernel, * 當調用swupdate xxx -e stable,upgrade_kernel時,就會匹配到這部分,執行{}內的動作, * 可以修改,調用的時候傳入匹配的字符串即可。 */ /* upgrade kernel,rootfs ==> change sw_mode */ upgrade_kernel = { /* upgrade kernel,rootfs */ /* image部分,不贅述*/ images: ( { filename = "kernel"; device = "/dev/by-name/boot"; installed-directly = true; }, { filename = "rootfs"; device = "/dev/by-name/rootfs"; installed-directly = true; } ); /* change sw_mode to upgrade_usr,change boot_partition to boot */ bootenv: ( { /* 設置env:swu_mode=upgrade_usr, 這是為了記錄OTA進度*/ name = "swu_mode"; value = "upgrade_usr"; }, { /* 設置env:boot_partition=boot, 這是為了切換系統,下次uboot就會啟動主系統( kernel位于boot分區) */ name = "boot_partition"; value = "boot"; } ); }; /* 內層tag,upgrade_usr, 當調用swupdate xxx -e stable,upgrade_usr時,就會匹配到這部分,執行{}內的動作, 可以修改,調用的時候傳入匹配的字符串即可*/ /* upgrade usr ==> clean ==> reboot */ upgrade_usr = { /* * misc-upgrade的小容量方案,將usr拆成獨立分區了。 * 這里我們不需要,如果保留的話,不做任何image操作即可。 * 也可以徹底刪除這一部分,并將上面的upgrade_usr改掉。 */ /* upgrade usr */ /* OTA結束,清空各種標志*/ /* clean swu_param,swu_software,swu_mode & reboot */ bootenv: ( { name = "swu_param"; value = ""; }, { name = "swu_software"; value = ""; }, { name = "swu_mode"; value = ""; }, { name = "swu_next"; value = "reboot"; } ); }; }; /* 當沒有匹配上面的tag,進入對應的處理流程時,則運行到此處。我們默認清除掉一些狀態*/ /* when not call with -e xxx,xxx just clean */ bootenv: ( { name = "swu_param"; value = ""; }, { name = "swu_software"; value = ""; }, { name = "swu_mode"; value = ""; }, { name = "swu_version"; value = ""; } ); }
說明:
升級過程會進行兩次重啟。具體的: (1)升級recovery 分區(recovery),uboot(uboot),boot0(boot0) 。設置boot_partition為recovery。 (2)重啟,進入recovery 系統。 (3)升級內核(kernel) 和rootfs(rootfs) 。設置boot_partition 為boot。 (4)重啟,進入主系統,升級完成。
3.4.5 準備sw-subimgs.cfg
我們直接看下tina 默認的:
target/allwinner/generic/swupdate/sw-subimgs.cfg
內容如下,中文部分是注釋,原文件中沒有。
swota_file_list=( #取得sw-description,放到OTA包中。 #注意第一行必須為sw-description。如果源文件不叫sw-description,可在此處加:sw-description做一次重命名 target/allwinner/generic/swupdate/sw-description #取得boot_initramfs_recovery.img,重命名為recovery,放到OTA包中。以下雷同 out/${TARGET_BOARD}/boot_initramfs_recovery.img:recovery #uboot.img和boot0.img是執行swupdate_pack_swu時自動拷貝得到的,需配置sys_config.fex中的 storage_type out/${TARGET_BOARD}/uboot.img:uboot #注:boot0沒有修改的話,以下這行可去除,其他雷同,可按需升級 out/${TARGET_BOARD}/boot0.img:boot0 out/${TARGET_BOARD}/boot.img:kernel out/${TARGET_BOARD}/rootfs.img:rootfs #下面這行是給小容量方案預留的,目前注釋掉 #out/${TARGET_BOARD}/usr.img:usr )
說明: 指明打包swupdate 升級包所需的各個文件的位置。這些文件會被拷貝到out 目錄下,再生成swupdate OTA 包。
3.4.6 編譯OTA 包所需的子鏡像
編譯kernel 和rootfs。
make
編譯recovery 系統。
swupdate_make_recovery_img
編譯uboot。
muboot
打包,若需要升級boot0/uboot,則是必要步驟,打包會將boot0 和uboot 拷貝到out 目錄下,并對頭部參數等進行修改。生成的固件也可用于測試。注:如果希
望生成的固件的recovery分區是有系統的,則需要先編譯recovery 系統,再打包。
pack / pack -s
生成OTA 包。因為我們使用的就是sw-subimgs.cfg,所以不同帶參數。
注意,如果方案目錄下存在sw-subimgs.cfg,則優先用方案目錄下的。沒有方案特定配置才用generic 下的。如果需要升級boot0/uboot,需要配置好
sys_config.fex 中的storage_type參數,swupdate_pack_swu 才能正確拷貝對應的boot0/uboot。
swupdate_pack_swu
3.4.7 執行OTA
3.4.7.1 準備OTA 包
對于測試來說,直接推入。
adb push out//swupdate/.swu /mnt/UDISK
實際應用時, 可從先從網絡下載到本地, 再調用swupdate, 也可以直接傳入url 給swupdate。
3.4.7.2 調用swupdate
若使用原生的swupdate,則調用:
swupdate -i /mnt/UDISK/.swu -e stable,upgrade_recovery
但這樣不會在自啟動的時候幫我們準備好swupdate 所需的-e 參數。
我們可以使用輔助腳本:
swupdate_cmd.sh -i /mnt/UDISK/.swu -e stable,upgrade_recovery
3.5 AB 系統方案舉例
3.5.1 配置分區和env
在分區表中,將原有的boot 分區和rootfs 分區,分區名改為bootA 和rootfsA。
將這兩個分區配置拷貝一份,即新增兩個分區,并把名字改為bootB 和rootfsB。
這樣flash 中就存在A 系統(bootA+rootfsA) 和B 系統(bootB+rootfsB)。
一般是一個系統燒錄兩份。即分區表中的bootA 和bootB 都指定的boot.fex,rootfsA 和rootfsB 都指定的rootfs.fex。
在env 中,指定:
boot_partition=bootA root_partition=rootfsA
并配置boot_normal 命令,從$boot_partition 變量指定的分區加載系統。
3.5.2 配置主系統
lunch 選擇方案后, make menuconfig, 選上swupdate。
3.5.3 配置recovery 系統
AB 系統方案沒有使用recovery 系統,無需配置和生成。
3.5.4 準備sw-description
這里我們直接使用:
target/allwinner/generic/swupdate/sw-description-ab
內容如下,中文部分是注釋,原文件中沒有。
/* 固定格式,最外層為software = { } */ software = { /* 版本號和描述*/ version = "0.1.0"; description = "Firmware update for Tina Project"; /* * 外層tag,stable, * 沒有特殊含義,就理解為一個字符串標志即可。 * 可以修改,調用的時候傳入匹配的字符串即可。 */ stable = { /* * 內層tag,now_A_next_B, * 當調用swupdate xxx -e stable,now_A_next_B時,就會匹配到這部分,執行{}內的動作, * 可以修改,調用的時候傳入匹配的字符串即可。 */ /* now in systemA, we need to upgrade systemB(bootB, rootfsB) */ now_A_next_B = { /* 這部分是描述,當前處于A系統,需要更新B系統,該執行的動作。執行完后下次啟動為B系統*/ images: ( /* 處理各個image */ { filename = "kernel"; /* 源文件是OTA包中的kernel文件*/ device = "/dev/by-name/bootB"; /* 要寫到/dev/by-name/bootB節點中, 這個節點 在tina上就對應bootB分區*/ installed-directly = true; /* 流式升級,即從網絡升級時邊下載邊寫入, 而不是先完 整下載到本地再寫入,避免占用額外的RAM或ROM */ }, { filename = "rootfs"; /* 同上,但處理rootfs,不贅述*/ device = "/dev/by-name/rootfsB"; installed-directly = true; }, { filename = "uboot"; /* 源文件是OTA包中的uboot文件*/ type = "awuboot"; /* type為awuboot,則swupdate會調用對應的handler做處理*/ }, { filename = "boot0"; /* 源文件是OTA包中的boot0文件*/ type = "awboot0"; /* type為awuboot,則swupdate會調用對應的handler做處理*/ } ); /* image處理完之后,需要設置一些標志,切換狀態*/ bootenv: ( /* 處理bootenv,會修改uboot的env分區*/ { /* 設置env:swu_mode=upgrade_kernel, 這是為了記錄OTA進度, 對于AB系統來說,此時 已經升級完成,置空*/ name = "swu_mode"; value = ""; }, { /* 設置env:boot_partition=bootB, 這是為了切換系統,下次uboot就會啟動B系統( kernel位于bootB分區) */ name = "boot_partition"; value = "bootB"; }, { /* 設置env:root_partition=rootfsB, 這是為了切換系統,下次uboot就會通過cmdline 指示掛載B系統的rootfs */ name = "root_partition"; value = "rootfsB"; }, { /* 兼容另外的切換方式,可以先不管*/ name = "systemAB_next"; value = "B"; }, { /* 設置env:swu_next=reboot, 這是為了跟外部腳本配合,指示外部腳本做reboot動作*/ name = "swu_next"; value = "reboot"; } ); }; /* * 內層tag,now_B_next_A, * 當調用swupdate xxx -e stable,now_B_next_A時,就會匹配到這部分,執行{}內的動作, * 可以修改,調用的時候傳入匹配的字符串即可 */ /* now in systemB, we need to upgrade systemA(bootA, rootfsA) */ now_B_next_A = { /* 這里面就不贅述了, 跟上面基本一致,只是AB互換了*/ images: ( { filename = "kernel"; device = "/dev/by-name/bootA"; installed-directly = true; }, { filename = "rootfs"; device = "/dev/by-name/rootfsA"; installed-directly = true; }, { filename = "uboot"; type = "awuboot"; }, { filename = "boot0"; type = "awboot0"; } ); bootenv: ( { name = "swu_mode"; value = ""; }, { name = "boot_partition"; value = "bootA"; }, { name = "root_partition"; value = "rootfsA"; }, { name = "systemAB_next"; value = "A"; }, { name = "swu_next"; value = "reboot"; } ); }; }; /* 當沒有匹配上面的tag,進入對應的處理流程時,則運行到此處。我們默認清除掉一些狀態*/ /* when not call with -e xxx,xxx just clean */ bootenv: ( { name = "swu_param"; value = ""; }, { name = "swu_software"; value = ""; }, { name = "swu_mode"; value = ""; }, { name = "swu_version"; value = ""; } ); }
說明:
升級過程會進行一次重啟。具體的: (1)升級kernel 和rootfs 到另一個系統所在分區,升級uboot(uboot),boot0(boot0) 。設置boot_partition 為切換系統。 (2)重啟,進入新系統。
3.5.5 準備sw-subimgs.cfg
我們直接看下tina 默認的:
target/allwinner/generic/swupdate/sw-subimgs-ab.cfg
內容如下,中文部分是注釋,原文件中沒有。
swota_file_list=( #取得sw-description-ab, 重命名成sw-description, 放到OTA包中。 #注意第一行必須為sw-description target/allwinner/generic/swupdate/sw-description-ab:sw-description #取得uboot.img,重命名為uboot,放到OTA包中。以下雷同 #uboot.img和boot0.img是執行swupdate_pack_swu時自動拷貝得到的,需配置sys_config.fex中的 storage_type out/${TARGET_BOARD}/uboot.img:uboot #注:boot0沒有修改的話,以下這行可去除,其他雷同,可按需升級 out/${TARGET_BOARD}/boot0.img:boot0 out/${TARGET_BOARD}/boot.img:kernel out/${TARGET_BOARD}/rootfs.img:rootfs )
說明: 指明打包swupdate 升級包所需的各個文件的位置。這些文件會被拷貝到out 目錄下,再生成 swupdate OTA 包。
3.5.6 編譯OTA 包所需的子鏡像
編譯kernel 和rootfs。
make
編譯uboot。
muboot
打包,若需要升級boot0/uboot,則是必要步驟,打包會將boot0 和uboot 拷貝到out 目錄下,并對頭部參數等進行修改。生成的固件也可用于測試。
pack / pack -s
生成OTA 包。因為我們使用的是sw-subimgs-ab.cfg,所以調用時帶參數-ab。
注意,如果方案目錄下存在sw-subimgs-ab.cfg,則優先用方案目錄下的。沒有方案特定配置才用generic 下的。
swupdate_pack_swu -ab
3.5.7 執行OTA
3.5.7.1 準備OTA 包
對于測試來說,直接推入。
adb push out//swupdate/.swu /mnt/UDISK
實際應用時, 可從先從網絡下載到本地, 再調用swupdate, 也可以直接傳入url 給swupdate。
3.5.7.2 判斷AB 系統
對于AB 系統方案來說,必須判斷當前所處系統,才能知道需要升級哪個分區的數據。
判斷當前是處于A 系統還是B 系統。
方式一:直接使用fw_printenv 讀取判斷當前的boot_partition 和root_partition 的值。
3.5.7.3 調用swupdate
若使用原生的swupdate,則調用:
當前處于A系統: swupdate -i /mnt/UDISK/.swu -e stable,now_A_next_B 當前處于B系統: swupdate -i /mnt/UDISK/.swu -e stable,now_B_next_A
但這樣不會在自啟動的時候幫我們準備好swupdate 所需的-e 參數。
我們可以使用輔助腳本:
當前處于A系統: swupdate_cmd.sh -i /mnt/UDISK/.swu -e stable,now_A_next_B 當前處于B系統: swupdate_cmd.sh -i /mnt/UDISK/.swu -e stable,now_B_next_A
3.6 輔助腳本swupdate_cmd.sh
為什么需要輔助腳本?
因為我們需要啟動時能自動調用swupdate,自動傳遞合適的-e 參數給swupdate, 需要在合適的時候調用重啟。
具體可直接看下腳本內容。
其基本思路是,當帶參數調用時,腳本從傳入的參數中,取出”-e xxx,yyy” 部分,將其余參數原樣保存為env 的swu_param 變量。
取出的”-e xxx,yyy” 中的xxx 保存到env 的swu_software 變量, yyy 保存為env 的swu_mode變量。
然后就取出變量,循環調用。
swupdate $swu_param -e "$swu_software,$swu_mode"
sw-description 中可以通過改變env 的swu_software 和swu_mode 變量,來影響下次的調用參數。
實際應用時,可不使用此腳本,直接在主應用中,調用swupdate 即可。但要自行做好-e 參數的處理。
3.7 版本號
3.7.1 使用方式
在sw-descriptionwen 文件中,會配置一個版本號字符串,如:
software = { version = "1.0.0"; ... }
如果需要在升級時檢查版本號,則可使用-N 參數,傳入的參數代表小機端當前的版本號。如果不需要,則不傳遞-N 參數,忽略版本號即可。
swupdate 會進行比較,如果OTA 包中sw-descriptionwen 文件配置的版本號小于當前版本號,則不允許升級。
如何在小機端保存,獲取,更新版本號,需要自定義, swupdate 沒有規定具體的方式。
3.7.2 實現例子
應用可以按自己的邏輯維護版本號,不依賴系統env 等,只需按照swupate 要求傳遞參數即可。
此處提供一種依賴系統env 的實現方式供參考。
1.初始化設備端版本號。
首先需要定義設備端的版本號存放在哪,如何獲取。
本方法定義設備端的版本號保存于env 之中,用swu_version 記錄。則在SDK 中,需在env-x.x.cfg 中添加一行:
swu_version=1.0.0
表示此時版本為1.0.0,燒錄固件后可執行fw_printenv 查看。
此步驟如果不做,則第一次燒錄固件后env 中不存在swu_version,調用swupdate 時也無法傳入獲得并版本號,則第一次升級時不會檢查版本。
注:這是tina 自定義的,可修改。只要讀寫這個版本號的地方均配套修改即可。實際應用時版本號可以存在任意分區中,或者存放在文件系統的文件中,或者硬編
碼在系統和應用的二進制中,swupdate 未做限制。
2.在sw-description 中,設置OTA 包版本號。
升級時如果檢查到OTA 包的sw-description 中的version,小于通過-N 參數傳入的版本號,則不允許升級。
software = { version = "2.0.0"; ...
例如當設備端的env 中設置了swu_version=2.0.0, 則調用swupdate_cmd.sh 時,會自動獲取此參數并在調用swupdate 時傳入-N 2.0.0。
此時若OTA 包中定義了version = “1.0.0” , 則此時升級會降低版本號,拒絕升級。
此時若OTA 包中定義了version = “2.0.0” , 則此次升級不會降低版本號,可以升級。
此時若OTA 包中定義了version = “3.0.0” , 則此次升級不會降低版本號,可以升級。
注:這是swupdate 原生的OTA 包版本號規則,不是tina 自定義的。
3.更新設備端版本號。
本方式版本號定義在env 中,則升級kernel 和rootfs 分區不會自動更新版本號,需要主動修改env。
若版本號是記錄于rootfs 的某個文件,則不必在sw_description 中添加這種操作,因為更新rootfs 時版本號就自然更新了。但缺點是版本號跟rootfs 綁定了,每
次OTA 必須升級rootfs 才能更新版本號。
添加一個設置version 代表swu_version 的env 操作, 在OTA 時自動更新版本號。
software = { #表示這個OTA包的版本號,給swupdate讀取檢查的。原生規定的。 version = "2.0.0"; ... bootenv: ( ... { #表示這個OTA包的版本號,OTA時會寫入env分區,用于在下次OTA時讀出作為-N參數的值。 Tina自定義的。 name = "swu_version"; value = "2.0.0"; } ... ); ... }
注意,這么做的話,更新版本時需要修改env 中的版本號,以使得新的固件包擁有新的版本號,以及更新sw-description 的兩個位置,一處是最上面的version =
xxx 的版本號,一處是bootenv 操作中的版本號,以使得OTA 包擁有新的版本號,以及能在OTA 時寫入新版本號。
讀取設備端版本號傳給swupdate。
假如小機端是用腳本調用,則可用如下方式讀取并傳給swupdate:
swu_version=$(fw_printenv -n swu_version) swupdate ... -N $swu_version
更好的方式是判斷非空才傳入,如此可支持不在env 中提前配置好swu_version。
check_version_para="" [ x"$swu_version" != x"" ] && { echo "now version is $swu_version" check_version_para="-N $swu_version" } swupdate ... $check_version_para
注: 如果不使用版本號,則不在env 中設置swu_version,也不在bootenv 中寫swu_version 即可。
如果sw_description 中的版本號一直保持v1.0.0,也總是能升級。
3.8 簽名校驗
3.8.1 檢驗原理
OTA 包中包含了sw-decsription 文件和各個具體的鏡像,如kernel,rootfs。
如果對整個OTA 包進行完整校驗,則會對流式升級造成影響,要求必須把整個OTA 包下載下來,才能判斷出校驗是否通過。
為了避免上述問題,swupdate 的校驗是分鏡像的,首先從OTA 包最前面取出兩個文件,即swdescription和sw-description.sig,使用傳入的公鑰校驗sw-
description,校驗通過則認為sw-description 可信,則說明其中描述的image 和sha256 也是可信的。
后續無需再使用公鑰,直接校驗每個鏡像的sha256 即可。因此可以逐個鏡像處理,無需全部下載完畢再處理。
3.8.2 配置
swupdate 支持使用簽名校驗功能,需要在編譯時選中對應功能。
出于安全考慮,一旦使能了校驗,則swupdate 不再支持不使用簽名的更新調用。
make menuconfig ---> Allwinner ---> <*> swupdate ---> [*] Enable verification of signed images Signature verification algorithm (RSA PKCS#1.5) --->(選擇校驗算法,此處以RSA為例)
注意,recovery 系統也需要對應進行配置,即:
make ota_menuconfig ---> ...(重復以上配置)
3.8.3 使用方法
在PC 端使用私鑰簽名OTA 包。
在小機端調用swupdate 時,使用-k 參數傳入公鑰。
3.8.4 初始化key
Tina 封裝了一條命令,生成默認的密鑰對。執行:
swupdate_init_key
執行后會使用默認密碼生成密鑰對并拷貝到指定目錄:
密碼/私鑰/公鑰:
password:tina/target/allwinner/方案名/swupdate/swupdate_priv.password private key:tina/target/allwinner/方案名/swupdate/swupdate_priv.pem public key:tina/target/allwinner/方案名/swupdate/swupdate_public.pem 公鑰拷貝到base-files中,供使用procd-init的方案使用 public key:tina/target/allwinner/方案名/base-files/swupdate_public.pem 公鑰拷貝到busybox-init-base-files中,供使用busybox-init的方案使用 public key:tina/target/allwinner/方案名/busybox-init-base-files/swupdate_public.pem
此步驟僅為方便調試使用,只需要做一次。
用戶也可使用自己的密碼自行生成密鑰,生成密鑰的具體命令可參考build/envsetup.sh 中swupdate_init_key 的實現:
local password="swupdate"; echo "$password" > swupdate_priv.password; echo "-------------------- init priv key --------------------"; openssl genrsa -aes256 -passout file:swupdate_priv.password -out swupdate_priv.pem; echo "-------------------- init public key --------------------"; openssl rsa -in swupdate_priv.pem -passin file:swupdate_priv.password -out swupdate_public.pem -outform PEM -pubout;
生成的密鑰如swupdate_init_key 一般放到tina/target/allwinner/方案名/swupdate/ 中,即可在打包OTA 包時自動使用。
主要就是調用openssl 生成,私鑰拷貝到SDK 指定目錄,供生成OTA 包時使用。公鑰放到設備端,供設備端執行OTA 時使用。
密鑰的作用是校驗OTA 包,意味著拿到密鑰的人即可生成可通過校驗的OTA 包,因此正式產品中一般密鑰只掌握在少數人手中,并采取適當措施避免泄漏或丟失。
一種可參考的實踐方式是,正式密鑰做好備份,并僅部署在有權限管控的服務器上,只能代碼入庫后通過自動構建生成OTA 包,普通工程師無法拿到密鑰自行本地
生成用于正式產品的OTA包。
3.8.5 修改sw-description
如上文所述,每個image 在使用時會校驗sha256,因此需要在為每個更新文件在swdesctiption中添加sha256 屬性,指定sha256 的值供更新過程校驗。
有獨立鏡像的文件才需要sha256 屬性,例如images 中配置的文件。而bootenv 等直接寫在sw-description 中的,則無需sha256 屬性。
目前腳本支持自動在生成OTA 包時,更新sha256 的值。但需要在sw-description 中,手工添加:
sha256 = @文件名
如:
$ git diff sw-description diff --git a/allwinner/cowbell-perf1/configs/sw-description b/allwinner/cowbell-perf1/ configs/sw-description index ed04b64..467ac3b 100644 --- a/allwinner/cowbell-perf1/configs/sw-description +++ b/allwinner/cowbell-perf1/configs/sw-description @@ -9,14 +9,17 @@ software = { filename = "boot_initramfs_recovery.img" device = "/dev/by-name/recovery"; + sha256 = "@boot_initramfs_recovery.img" }, { filename = "boot_package.fex" type = "awuboot"; + sha256 = "@boot_package.fex" }, { filename = "boot0_nand.fex" type = "awboot0"; + sha256 = "@boot0_nand.fex" } ); @@ -34,10 +37,12 @@ software = { filename = "boot.img"; device = "/dev/by-name/boot"; + sha256 = "@boot.img" }, { filename = "rootfs.img"; device = "/dev/by-name/rootfs"; + sha256 = "@rootfs.img" } ); bootenv: (
在打包OTA 包時,腳本自動算出sha256 的值,并替換到上述位置,再完成OTA 包的生成。
可參考:
target/allwinner/generic/swupdate/sw-description-sign target/allwinner/generic/swupdate/sw-subimgs-sign.cfg 注: 調用swupdate_pack_swu 則會使用sw-subimgs.cfg,其中默認指定了使用sw-description做為最終的sw-description。 調用swupdate_pack_swu -sign則會使用sw-subimgs-sign.cfg,其中默認指定了使用sw-description-sign做為 最終的sw-description。 即關鍵還是看使用哪份sw-subimgs.cfg,以及sw-subimgs.cfg中如何指定。
3.8.6 添加sw-description.sig
簽名的OTA 包,需要生成簽名文件sw-description.sig,并使其在OTA 包中,緊隨在swdescription后面。
目前腳本中自動處理。
3.8.7 生成OTA 包
方法不變,腳本中會檢測defconfig 的配置,并自動完成簽名等動作。
3.8.8 將公鑰放置到小機端
目前腳本中生成key 的時候,自動拷貝了。如需手工處理,可參考如下方式。
對于procd-init:
cdevice mkdir -p ./base-files cp swupdate_public.pem ./base-files/etc/
對于busybox-init
cdevice mkdir -p ./busybox-init-base-files/ cp swupdate_public.pem ./busybox-init-base-files/etc/
3.8.9 在小機端調用
在原本的命令基礎上,加上-k /etc/swupdate_public.pem 即可, 如:
swupdate_cmd.sh -v -i /mnt/UDISK/tina-cowbell-perf1.swu -k /etc/swupdate_public.pem
3.9 壓縮
swupdate 支持對鏡像先解壓,再寫入目標位置,當前支持gzip 和zstd 兩種壓縮算法。
3.9.1 配置
使用gzip 壓縮無需配置,使用zstd 則需選上
make menuconfig --> Allwinner ---> <*> swupdate --> [*] Zstd compression support
3.9.2 生成壓縮鏡像
如果希望每次打包固件自動生成,則可修改scripts/pack_img.sh, 在function do_pack_tina()函數的最后加上壓縮的動作。
生成gz鏡像: gzip -k -f boot.fex gzip -k -f rootfs.fex gzip -k -f recovery.fex #如果使用AB方案,則無需recovery 生成lzma鏡像: zstd -k -f boot.fex -T0 zstd -k -f rootfs.fex -T0 zstd -k -f recovery.fex -T0
對于lzma,若需要調整壓縮率,可指定0-19 的數字(數字越大,壓縮率越高,耗時越長),如
zstd -19 -k -f boot.fex -T0 zstd -19 -k -f rootfs.fex -T0 zstd -19 -k -f recovery.fex -T0
如果不希望每次打包固件多耗時間,則需自行在生成OTA 包之前,使用上述命令制作好壓縮鏡像。原始的boot.fex,rootfs.fex, recovery.fex 在out/方案/image/目錄下。
3.9.3 sw-subimgs.cfg 配置壓縮鏡像
以rootfs 為例,將原本未壓縮的版本
out/${TARGET_BOARD}/rootfs.img:rootfs
改成壓縮的
out/${TARGET_BOARD}/image/rootfs.fex.gz:rootfs.gz
或
out/${TARGET_BOARD}/image/rootfs.fex.zst:rootfs.zst
注:為了方便差分包的處理,此處約定壓縮鏡像需以.gz 或.zst 結尾,生成差分項的腳本會檢查后綴名,并自動解壓。
3.9.4 sw-description 配置壓縮鏡像
以rootfs 為例,將原本未壓縮的版本
{ filename = "rootfs"; device = "/dev/by-name/rootfs"; installed-directly = true; sha256 = "@rootfs"; },
換成
{ filename = "rootfs.gz"; device = "/dev/by-name/rootfs"; installed-directly = true; sha256 = "@rootfs.gz"; compressed = "zlib"; },
或
{ filename = "rootfs.zst"; device = "/dev/by-name/rootfsB"; installed-directly = true; sha256 = "@rootfs.zst"; compressed = "zstd"; },
3.10 調用OTA
swupdate_cmd.sh , 用于給swupdate 傳入相關參數,切換更新狀態,以及不斷重試。
3.10.1 進度條
swupdate 提供了progress 程序,該程序會在后臺運行,從socket 獲取進度信息,打印進度條到串口。
具體方案可參考其實現(在swupdate 源碼中搜索progress),自行在應用中獲取進度,通過屏 幕等其他方式進行指示。
3.10.2 重啟
1.調用swupdate 的時候加上-p reboot , 則swupdate 更新完畢后,會執行reboot。
2.swupdate_cmd.sh 支持檢測env 中的swu_next 變量,如果為reboot,則腳本中執行reboot。可在sw-description 中設置此變量。
3.如果調用progress 的時候加上-r 參數,則progress 會在檢測到更新完成后,執行reboot。
3.10.3 本地升級示例
將生成的OTA 包推送到小機端,如放在/mnt/UDISK 目錄下。
PC 端執行:
adb push out/cowbell-perf1/swupdate/tina-cowbell-perf1.swu /mnt/UDISK
小機端執行(不帶簽名校驗版本):
swupdate_cmd.sh -i /mnt/UDISK/tina-cowbell-perf1.swu -e stable,upgrade_recovery
小機端執行(帶簽名校驗版本):
swupdate_cmd.sh -i /mnt/UDISK/tina-cowbell-perf1.swu -k /etc/swupdate_public.pem -e stable,upgrade_recovery
3.10.4 網絡升級示例
啟動服務器:
cd out/cowbell-perf1/swupdate/ sudo python -m SimpleHTTPServer 80 #啟動一個服務器
小機端命令,使用-d -uxxx,xxx 為url。
例如(不帶簽名校驗版本):
swupdate_cmd.sh -d -uhttp://192.168.35.112/tina-cowbell-perf1.swu -e stable,upgrade_recovery
例如(帶簽名校驗版本):
swupdate_cmd.sh -d -uhttp://192.168.35.112/tina-cowbell-perf1.swu -k /etc/swupdate_public.pem -e stable,upgrade_recovery
注:需依賴外部程序,提供自動聯網支持。OTA 本身不處理聯網。
3.10.5 錯誤處理
如何判斷swupdate 升級出錯?
1.調用swupdate 時獲得并判斷返回值是否為0。
2.讀取env 變量recovery_status。根據swupdate 官方文檔,swupdate 開始執行時,會設置recovery_status=“progress”,升級完成會清除這個變量,升級失敗則設置recovery_status=“failed”。
3.11 裁剪
swupdate 本身是可配置的, 不需要某些功能時,可將其裁剪掉。
make menuconfig ---> Allwinner ---> <*> swupdate --->
例如,不需要使用swupdate 來從網絡下載OTA 包的話,則可將
[*] Enable image downloading
取消掉。 不需要更新boot0/uboot 的話,則將
Image Handlers ---> [*] allwinner boot0/uboot
取消掉
3.12 調試
3.12.1 直接調用swupdate
目前swupdate_cmd.sh 主要有兩個作用:
1.自啟動,無限重試。
2.在主系統和recovery 系統中,傳入不同的-e 參數給swupdate。
出問題時,可以不使用swupdate_cmd.sh,手工直接調用swupdate,在后面加上合適的-e 參數,觀察輸出log。如:
swupdate -v -i xxx.swu -e stable,boot swupdate -v -i xxx.swu -e stable,recovery
3.12.2 手工切換系統
按上述env 的配置,啟動的系統,是由boot_partition 變量控制的。
注意,需要/var/lock 目錄存在且可寫。
切換到主系統:
fw_setenv boot_partition boot reboot
切換到recovery 系統:
fw_setenv boot_partition recovery reboot
觀察當前變量:
fw_printenv
3.12.3 更新boot0/uboot
目前更新boot0,uboot 實際功能是由另一個軟件包ota-burnboot 完成的,swupdate 只是準備數據,并調用ota-burnboot 提供的動態庫。
如果更新失敗,先嘗試手工使用ota-burnboot0 xxx 和ota-burnuboot xxx 能否正常更新。以確定是ota-burnboot 的問題,還是swupdate 的問題。
3.12.4 解壓OTA 包
swupdate 的OTA 包,本質上是一個cpio 格式的包,直接使用通用的cpio 解包命令即可。
cpio -idv < xxx.swu
3.12.5 校驗OTA 包
當使能了簽名校驗,會對sw-description 簽名生成sw-description.sig,如果校驗失敗,可以在PC 端手工驗證下:
使用RSA 時, build/envsetup.sh 中調用的命令是:
openssl dgst -sha256 -sign "$priv_key_file" $password_para "$SWU_DIR/sw-description" > " $SWU_DIR/sw-description.sig"
則對應的密鑰驗證簽名命令為:
openssl dgst -prverify swupdate_priv.pem -sha256 -signature sw-description.sig swdescription
公鑰驗證簽名的命令為:
openssl dgst -verify swupdate_public.pem -sha256 -signature sw-description.sig swdescription
3.13 測試固件示例
3.13.1 生成方式
一般而言,測試需要兩個有差異的OTA 包,如,uboot 和kernel 的log 有差異,rootfs 的文件有差異。
這樣方法測試人員根據log 判斷是否升級成功。
3.13.1.1 準備工作
如果需要網絡更新,OTA 不負責聯網,所以需要選上wifimanager-daemon。
Allwinner ---> <*> wifimanager ---> [*] Enable wifimanager daemon support ---> <*> wifimanager-daemon-demo..................... Tina wifimanager daemon demo
3.13.1.2 生成固件1 和OTA 包1
重新編譯boot,使得編譯時間更新:
mboot
創建文件以標記rootfs:
cd target/allwinner/cowbell-ailabs_c1c/base-files rm -f OTA1 OTA2 echo OTA1 > OTA1
重新編譯recovery 系統:
swupdate_make_recovery_img
重新編譯打包,使得編譯時間更新:
mp -j32
生成OTA 包1:
swupdate_pack_swu
得到產物:
cp out/cowbell-perf1/tina_cowbell-perf1_uart0.img tina_cowbell-perf1_uart0_OTA1.img cp out/cowbell-perf1/swupdate/tina-cowbell-perf1.swu tina-cowbell-perf1_OTA1.swu
3.13.1.3 生成固件2 和OTA 包2
重新編譯uboot,使得編譯時間更新:
muboot
創建文件以標記rootfs:
cd target/allwinner/cowbell-ailabs_c1c/base-files rm -f OTA1 OTA2 echo OTA2 > OTA2
重新編譯recovery 系統:
swupdate_make_recovery_img
重新編譯打包,使得編譯時間更新:
mp -j32
生成OTA 包2:
swupdate_pack_swu
得到產物:
cp out/cowbell-perf1/tina_cowbell-perf1_uart0.img tina_cowbell-perf1_uart0_OTA2.img cp out/cowbell-perf1/swupdate/tina-cowbell-perf1.swu tina-cowbell-perf1_OTA2.swu
3.13.2 使用方式
任意選擇一個OTA 固件燒錄后,可在此基礎上進行本地升級或網絡升級。
3.13.2.1 本地升級方式
PC 端執行:
adb push out/astar-parrot/swupdate/tina-cowbell-perf1_OTA1.swu /mnt/UDISK
小機端執行:
swupdate_cmd.sh -i /mnt/UDISK/tina-cowbell-perf1_OTA1.swu
3.13.2.2 網絡升級方式
PC 端搭建服務器:
sudo python -m SimpleHTTPServer 80
小機端聯網:
wifi_connect_ap_test SSID 密碼
小機端執行:
swupdate_cmd.sh -d -uhttp://192.168.xxx.xxx/tina-cowbell-perf1_OTA1.swu
注明:啟動后會自動聯網,連網后等待OTA 后臺腳本嘗試更新。中途掉電重啟后,正常會在啟動后幾十秒內,成功聯網并開始繼續更新。
3.13.2.3 升級過程
升級過程會進行兩次重啟。具體的: (1)升級recovery 分區(boot_initramfs_recovery.img),uboot(boot_package.fex),boot0(boot0_nand.fex)。 (2)重啟,進入recovery 系統。 (3)升級內核(boot,img) 和rootfs(rootfs.img)。 (4)重啟,進入主系統,升級完成。
3.13.2.4 判斷升級
從log 中的時間和rootfs 文件可以判斷當前運行的版本。
例如:
OTA_1: boot0:[66]HELLO! BOOT0 is starting Dec 29 2018 16:15:59! uboot:U-Boot 2018.05-00015-g5068c23-dirty (Dec 29 2018 - 16:15:41 +0800) Allwinner Technology kernel:[ 0.000000] Linux version 4.9.118 (zhuangqiubin@Exdroid5) (gcc version 6.4.1 ( OpenWrt/Linaro GCC 6.4-2017.11 2017-11) ) #189 SMP Sat Dec 29 08:13:38 UTC 2018 (注,進入控制臺cat /proc/version 也可看到) rootfs:ls查看下,在根目錄下有一個文件OTA1 OTA_2: boot0: [66]HELLO! BOOT0 is starting Dec 29 2018 16:17:35! uboot: U-Boot 2018.05-00015-g5068c23-dirty (Dec 29 2018 - 16:17:17 +0800) Allwinner Technology kernel:[ 0.000000] Linux version 4.9.118 (zhuangqiubin@Exdroid5) (gcc version 6.4.1 ( OpenWrt/Linaro GCC 6.4-2017.11 2017-11) ) #190 SMP Sat Dec 29 08:18:33 UTC 2018(注,進入控制臺cat /proc/version 也可看到) rootfs:ls查看下,在根目錄下有一個文件OTA2
3.14 升級定制分區
如果定制了一個分區,并需要對此分區進行OTA,則需要:
1.確認需不需要備份。
2.將分區文件加入OTA 包。
3.確定升級策略,在sw-description 中增加對此分區的處理。
3.14.1 備份
在OTA 過程隨時可能掉電,如果掉電時正在升級分區mypart ,則重啟后mypart 的數據是不完整的。
是否需要備份主要取決于mypart 所保存的文件是否影響繼續進行OTA。
例如mypart 中保存的是開機音樂,被損壞的結果只是開機無聲音,但開機后能正常繼續OTA,則無需備份。
例如mypart 中保存的是應用,且OTA 中途掉電后重啟,需要應用負責聯網從網絡重新下載OTA 包,則需要備份,否則無法繼續OTA,設備就無法恢復了。
例如mypart 中保存的是dsp,啟動過程沒有有效的dsp 鏡像會無法啟動,則需要備份,否則無法啟動也就無法繼續OTA,設備就無法恢復了。
3.14.2 無需備份
假設分區表中定義了:
[partition] name = mypart size = 512 downloadfile = "mypart.fex" user_type = 0x8000
文件放在:
out/${TARGET_BOARD}/image/mypart.fex
則在sw-subimg.cfg 中增加一行(注意要放到sw-description 之后,因為sw-description 必須是第一個文件),將mypart.fex 打包到OTA 包中,重命名為mypart:
out/${TARGET_BOARD}/image/mypart.fex:mypart
在sw-description 中增加升級動作的定義。例如可以在升級rootfs 之后,升級該分區:
software = { ... stable = { ... upgrade_kernel = { images: ( ... { filename = "rootfs"; device = "/dev/by-name/rootfs"; installed-directly = true; }, { filename = "mypart"; device = "/dev/by-name/mypart"; installed-directly = true; } ... ); ...
3.14.3 需要備份
在分區表中定義好兩個分區,這樣升級過程掉電,總有一份是完整的。
[partition] name = mypart size = 512 downloadfile = "mypart.fex" user_type = 0x8000 [partition] name = mypart-r size = 512 downloadfile = "mypart.fex" user_type = 0x8000
文件放在:
out/${TARGET_BOARD}/image/mypart.fex
則在sw-subimg.cfg 中增加一行(注意要放到sw-description 之后,因為sw-description 必
須是第一個文件),將mypart.fex 打包到OTA 包中,重命名為mypart:
out/${TARGET_BOARD}/image/mypart.fex:mypart
有兩個分區,則需要條件決定使用哪一個,可考慮在env 中定義一個變量:
mypart_partition=mypart
使用mypart 時,要先讀取env 的mypart_partition 的值來決定要使用哪個分區。
在sw-description 中,定義好要升級的分區和bootenv,保證每次升級那個未在使用的分區。
這樣即使掉電也無妨。
software = { ... stable = { upgrade_recovery = { images: { filename = "recovery"; device = "/dev/by-name/recovery"; installed-directly = true; }, /* 更新mypart-r, 此時掉電,mypart分區是完整的*/ { filename = "mypart-r"; device = "/dev/by-name/mypart-r"; installed-directly = true; } ); bootenv: ( { name = "swu_mode"; value = "upgrade_kernel"; }, { name = "boot_partition"; value = "recovery"; }, /* 設置這個env,指示下次啟動,即啟動到recovery分區時,配套使用mypart-r */ { name = "mypart_partition"; value = "mypart-r"; }, { name = "swu_next"; value = "reboot"; } ); }; upgrade_kernel = { images: ( { filename = "kernel"; device = "/dev/by-name/boot"; installed-directly = true; }, { filename = "rootfs"; device = "/dev/by-name/rootfs"; installed-directly = true; }, /* 更新mypart, 此時掉電,mypart分區還是完整的*/ { filename = "mypart-r"; device = "/dev/by-name/mypart-r"; installed-directly = true; } ); bootenv: ( { name = "swu_mode"; value = "upgrade_usr"; }, /* 設置這個env,指示下次啟動,即啟動到正常系統時,使用mypart */ { name = "mypart_partition"; value = "mypart"; }, { name = "boot_partition"; value = "boot"; } ); }; ...
3.15 handler 說明
此處只對一些handler 做簡介。
具體的handler 的用法,請參考swupdate 官方文檔說明。
3.15.1 awboot
3.15.1.1 nand/emmc
全志拓展的handler,用于支持升級全志boot0 和uboot,實質上會調用外部的ota-burnboot包完成升級。
目前只支持nand/emmc,詳見ota-burnboot 章節。
使用方式:
選上handler 支持:
make menuconfig ---> Allwinner ---> <*> swupdate ---> Image Handlers --> [*] allwinner boot0/uboot
注意,recovery 系統也需要對應進行配置,即:
make ota_menuconfig ---> ...(重復以上配置)
在sw-description 中指定type 即可:
{ filename = "uboot"; /* 源文件是OTA包中的uboot文件*/ type = "awuboot"; /* type為awuboot,則swupdate會調用對應的handler做處理*/ }, { filename = "boot0"; /* 源文件是OTA包中的boot0文件*/ type = "awboot0"; /* type為awuboot,則swupdate會調用對應的handler做處理*/ }
3.15.1.2 nor
對于nor 方案,升級boot0/uboot 有掉電風險。如確需升級,可直接配置合適偏移即可,不使用awboot handler。
例如已知boot0 存放在偏移為0 處,uboot 存放在偏移為24k 處,則配置:
{ filename = "boot0"; device = "/dev/mtdblock0"; installed-directly = true; }, { filename = "uboot"; device = "/dev/mtdblock0"; offset = "24k" installed-directly = true; }
3.15.2 readback
用于支持在sw-description 中配置sha256,在升級后讀出數據進行校驗。
一種應用場景是,在AB 系統差分升級時,應用差分包后讀出校驗,以確認差分得到的結果是對的,再切換系統。
需選上對應handler。
make menuconfig/make ota_menuconfig --> Allwinner --> swupdate --> <*> Allow to add sha256 hash to each image make menuconfig/make ota_menuconfig --> Allwinner --> swupdate --> Image Handlers --> [*] readback
3.15.2.1 示例
target/allwinner/generic/swupdate/sw-description-readback target/allwinner/generic/swupdate/sw-subimgs-readback.cfg
3.15.3 ubi
用于ubi 方案。 需先選上MTD 支持:
make menuconfig/make ota_menuconfig --> swupdate --> Swupdate Settings ---> General Configuration ---> [*] MTD support
再選上對應handler:
make menuconfig/make ota_menuconfig --> swupdate --> Image Handlers --> [*] ubivol
3.15.3.1 示例
請參考:
target/allwinner/generic/swupdate/sw-subimgs-ubi.cfg target/allwinner/generic/swupdate/sw-description-ubi
3.15.4 rdiff
rdiff handle 用于差分包升級。
需選上對應handler:
make menuconfig/make ota_menuconfig --> swupdate --> Image Handlers --> [*] rdiff #若使用AB系統方案,則無recovery系統,則make ota_menuconfig的配置可不做。
3.15.4.1 特性
在https://librsync.github.io/page_rdiff.html 中指出
rdiff cannot update files in place: the output file must not be the same as the input file.
即不支持原地更新,即應用差分包將A0 更新成A1,需要有足夠空間存儲A0 和A1,不能直接對A0 進行改動。
rdiff does not currently check that the delta is being applied to the correct file. If a delta is applied to the wrong basis file, the results will be garbage.
不校驗原文件,如果將差分包應用于錯誤的文件,則會得到無效的輸出文件,但不會報錯。
The basis file must allow random access. This means it must be a regular file rather than apipe or socket.
原文件必須支持隨機訪問,因此不能從管道或socket 中獲取原文件。 更多介紹請參考:https://librsync.github.io/
3.15.4.2 示例
首先需要將方案修改為AB 系統的方案,請參考上文的“AB 系統方案舉例”。
swupdate 的配置文件請參考:
target/allwinner/generic/swupdate/sw-description-ab-rdiff target/allwinner/generic/swupdate/sw-subimgs-ab-rdiff.cfg
差分文件的生成使用rdiff:
$ rdiff -h Usage: rdiff [OPTIONS] signature [BASIS [SIGNATURE]] [OPTIONS] delta SIGNATURE [NEWFILE [DELTA]] [OPTIONS] patch BASIS [DELTA [NEWFILE]]
tina 封裝了一條命令,用于解出兩個swu 包并生成各個子鏡像的差分文件。
一種參考的差分包生成方式是,先按普通的升級方式生成整包。
假設舊固件對應V1.swu,新固件對應V2.swu,則可使用:
swupdate_make_delta V1.swu V2.swu
生成差分文件。
再將生成的差分文件,用于生成差分的OTA 包。
例如:
make && pack #生成V1固件 swupdate_pack_swu -ab #得到out/r328s2-perf1/swupdate/tina-r328s2-perf1-ab.swu cp out/r328s2-perf1/swupdate/tina-r328s2-perf1-ab.swu V1.swu #保存V1的OTA包 #進行一些修改 make && pack #生成V2固件 swupdate_pack_swu -ab #得到out/r328s2-perf1/swupdate/tina-r328s2-perf1-ab.swu cp out/r328s2-perf1/swupdate/tina-r328s2-perf1-ab.swu V2.swu #保存V2的OTA包 swupdate_make_delta V1.swu V2.swu #生成差分鏡像 swupdate_pack_swu -ab-rdiff #生成差分OTA包,這一步用到了剛剛生成的差分鏡像
3.15.4.3 開銷問題
差分升級的主要好處在于節省傳輸的文件大小。而不是節省ram 和rom 的占用。
設備端在進行差分升級時,需要使用版本匹配的舊版本鏡像,加上差分包,生成新版本鏡像。
rsync 不支持原地更新,必須有額外的空間保存新生成的鏡像。
從掉電安全的角度考慮,在新版本鏡像完整保存到flash 之前,舊版本鏡像不能破壞,否則一旦中途掉電,將無法再次使用舊鏡像+ 差分包生成新鏡像,只能聯網
下載完整的OTA 包。
以上限制,導致flash 必須在舊鏡像之外,有足夠flash 空間用于存放新的鏡像。
對于recovery 方案,原本的:
從OTA包獲取新recovery寫入recovery分區--> reboot --> 從OTA包獲取新kernel寫入boot分區--> 從OTA包獲取新rootfs升級rootfs分區--> reboot
就需要變成:
從OTA包獲取recovery差分包,讀recovery分區,生成新recovery暫存到文件系統中--> 從文件系統獲取新recovery寫入recovery分區--> reboot --> ...
總體較為麻煩,且需要文件系統足夠大。 對于AB 方案,原本的:
從OTA包獲取新kernel寫入bootB分區--> 從OTA包獲取新rootfs寫入rootfsB分區--> reboot
就需要變成:
從OTA包獲取kernel差分包,讀出bootA分區,合并生成新kernel寫入bootB分區--> 從OTA包獲取rootfs差分包,讀出rootfsA分區,合并生成新rootfs寫入bootB分區--> reboot
不需要依賴額外的文件系統空間。
因此,若希望節省ram/rom 占用,差分包并非解決的辦法。若希望使用差分包,建議配合AB 系統使用。
3.15.4.4 管理問題
差分包的一個麻煩問題在于,差分包必須跟設備端的版本匹配。而出廠之后的設備,可能存在多種版本。
例如出廠為V1,當前最新為V4,則設備可能處于V1,V2,V3。此時若使用整包升級,則無需區分。
若使用差分升級,一種策略是為每個舊版本生成一個差分包,則需要制作三個差分包V1_4,V2_4,V3_4,并在OTA 時先判斷設備端和云端版本,再使用對應的差
分包。
另一種策略是,只為上一個版本生成差分包V3_4,并額外準備一個整包。在OTA 時先判斷設備端版本和云端版本,若可相差一個版本則使用差分包,若跨版本則
使用整包。不管哪一種,都需要應用做出額外的判斷。這一點需要主應用和云端服務器做好處理。
3.15.4.5 校驗問題
目前社區支持的rdiff 本身并不包含較好的校驗機制,即應用一個版本不對的差分包,也能跑完升級流程。這樣一旦出錯,就會導致機器變磚。
一種可考慮的方式是搭配readback 使用,即在應用差分包,寫入目標分區之后,將更新后的目標分區數據讀出,校驗其sha256 是否符合預期,校驗成功才切換系
統,校驗失敗則報錯。
可參考
target/allwinner/generic/swupdate/sw-description-ab-rdiff-sign target/allwinner/generic/swupdate/sw-subimgs-ab-rdiff-sign.cfg
其中增加了readback 的處理。
具體的,sw-subimgs-xxx.cfg 中可以配置swota_copy_file_list,指定一些文件只拷貝到swupdate 目錄,不打包到最終的OTA 包(swu 文件)中。因為在這個場
景下,我們需要原始的kernel, rootfs 等文件來計算sha256,但并不需要將其加入最終的OTA 包中。
3.15.4.6 跟ubi 的配合問題
swupdate 官方目前沒有支持rdiff 用于ubi 卷。
目前采用增加預處理腳本的方式來兼容,即在執行rdiff handler 之前,先調用腳本使用ubiupdatevol創建一個可用于升級目標ubi 卷的fifo。隨后rdiff handler 即
可將此fifo 當作目標裸設備,無需特殊處理ubi 卷。在后處理腳本中,再通過填0 的方式,結束所有的ubiupdatevol。
具體可參考如下文件夾中的配置和腳本:
target/allwinner/r329-evb5/swupdate
前提仍然是配置好AB 系統,使用方式:
# 編譯系統,得到要燒錄的固件,記為V1 make && pack # 生成OTA包,此OTA包的各個分區鏡像跟V1固件的鏡像一致 swupdate_pack_swu -ab cp out/r329-evb5/swupdate/tina-r329-evb5-ab.swu tina-r329-evb5-ab_V1.swu # 進行修改,編譯出V2的系統 make && pack # 生成OTA包,此OTA包的各個分區鏡像跟V2固件的鏡像一致 swupdate_pack_swu -ab cp out/r329-evb5/swupdate/tina-r329-evb5-ab.swu tina-r329-evb5-ab_V2.swu # 此時tina-r329-evb5-ab_V2.swu 可用于正常的OTA升級 # 生成各個鏡像的差分文件 swupdate_make_delta ./tina-r329-evb5-ab_V1.swu ./tina-r329-evb5-ab_V2.swu # 基于上一步得到的差分文件,生成差分OTA包 swupdate_pack_swu -ab-rdiff
4 Tina misc-upgrade 介紹(建議改用swupdate)
4.1 方案選擇
misc-upgrade 只支持recovery 系統方案。
由于在實際應用中,存儲操作系統和持久文件的存儲介質(如nand、emmc、spinor)大小各異,在OTA 中需要單獨在存儲介質上開辟recovery 分區,以防備在
更新中意外斷電,造成系統更新失敗無法重啟的問題。
所以在選擇OTA 方案時一定要考慮到recovery 分區大小對分區規劃的影響,避免在小容量時recovery 分區太大導致分區規劃難題。
綜上因素,我們在Tina 上針對大容量和小容量設計了不同的方案。
4.1.1 小容量方案
小容量介質一般指存儲介質容量小于32M(一般為spi nor)。
在命令行中進入Tina 根目錄,執行命令進入配置主界面:
source build/envsetup.sh (見詳注1) lunch (見詳注2) make menuconfig (見詳注3)
詳注: 1 加載環境變量及tina提供的命令 2 輸入編號,選擇方案 3 進入內核配置主界面(對一個shell而言,前兩個命令只需要執行一次)
配置路徑:
Target Image └─> *** Image Options *** [*] For storage less than 32M, enable this when using ota
選中該配置項后,rootfs 的/usr 會被分拆出一部分生成usr.squashfs(usr.img),并建立軟鏈接usr.fex。通過配置分區表將usr.fex 放在extend 分區,開機后自動
掛載到usr 目錄。這種設置的目的是可與recovery 鏡像(boot_initramfs)復用該分區,以此起到節省存儲空間的作用。
因此小容量方案中,并無單獨的recovery 分區,而是在OTA 升級時與extend 分區復用。
為了達到啟動后自動掛載extend 分區的效果。需要進行配置。
對于busybox-init,默認在pseudo_init 中會嘗試掛載usr。如果發現沒有正常掛載,請檢查/pseudo_init 中的mount_usr。
對于procd-init,可在
target/allwinner//base-files/etc/config/fstab
配置文件中,增加:
config 'mount' option target '/usr' option device '/dev/by-name/extend' option options 'ro,sync' option enabled '1'
4.1.2 大容量方案
大容量介質一般指存儲介質容量大于32M(一般是nand、emmc)。
對于大容量方案,不建議選中小容量方案中所述配置項,即不需要usr.img 和extend 分區,而只需要添加recovery 分區,這樣在OTA 升級時會省去很多麻煩。
4.1.3 misc-upgrade
不管是小容量還是大容量,都要在make 之前選中應用包misc-upgrade:
make menuconfig └─> Allwinner └─> <*> misc-upgrade...... read and write the misc partition
misc-upgrade 包主要功能是從指定服務器下載更新鏡像到本地,然后升級相應分區,期間會向misc 分區寫入升級階段的標志,出現意外無法重啟時uboot 或內核
(如果能夠啟動)可以根據misc 分區的狀態標志進行下一步的決策。
4.1.4 OTA 的升級流程
4.1.4.1 基本步驟
Tina3.0 及之前版本處理流程:
1. 備份busybox等資源到ram中, 使得OTA過程不依賴rootfs 2. 設置開始OTA的標志,upgrade_pre 3. 將recovery系統寫入recovery分區 4. 設置標志boot-recovery 5. 更新boot和rootfs分區 6. 設置標志upgrade_post 7. 對于小容量方案,更新usr分區 8. 設置標志upgrade_end 9. 重啟,重啟后為新系統
最新版本處理流程:
1. 設置開始OTA的標志,upgrade_pre 2. 將recovery系統寫入recovery分區 3. 設置標志boot-recovery 4. 主動重啟,重啟后進入recovery系統 5. 更新boot和rootfs分區 6. 設置標志upgrade_boot0 7. 如果存在boot0鏡像,則更新boot0 8. 設置標志upgrade_uboot 9. 如果存在uboot鏡像,則更新uboot 10. 設置標志upgrade_post 11. 對于小容量方案,更新usr分區 12. 設置標志upgrade_end 13. 重啟,重啟后為新系統
4.1.4.2 中途掉電
OTA 中途掉電后,下次啟動會根據標志,繼續完成OTA。
當設置upgrade_pre 標志之后掉電,重啟后仍是舊系統,從該標志之后的步驟開始執行。
當設置boot-recovery 標志之后掉電,重啟時,uboot 判斷到這個標志,并直接啟動recovery系統,啟動后,從該標志之后的步驟開始執行。
當設置upgrade_post 標志之后掉電,重啟時后為新系統,從該標志之后的步驟開始執行。
4.2 分區處理
4.2.1 分區定義
?
表4-1: misc-upgrade 升級分區說明表
?
升級分區 | |
---|---|
boot 分區 | 基礎系統鏡像分區,即/lib,/bin,/etc,/sbin 等非/usr,非掛載其他分 區的路徑,wifi 支持環境,alsa 支持環境、OTA 環境。 |
extend 分區 | 擴展系統鏡像分區,即/usr 應用分區,僅小容量方案有。 |
recovery | 分區存放恢復系統鏡像,僅大容量方案有。 |
private 分區 | 存儲SN 號分區。 |
misc 分區 | 系統狀態、刷機狀態分區。 |
UDISK 分區 | 用戶數據分區,一般掛載在/mnt/UDISK。 |
overlayfs 分區 | 存儲overlayfs 覆蓋數據。 |
4.2.2 分區大小配置
4.2.2.1 配置boot 分區大小
boot 分區鏡像的大小依賴內核配置,必須小于等于sys_partition.fex/sys_partition_nor.fex中定義的boot 分區大小。
boot 分區鏡像大小設定:
make menuconfig └─> Target Images └─> *** Image Options *** (4) Boot (SD Card) filesystem partition size (in MB)
boot 分區大小設定:
[partition] name =boot size =8192 downloadfile ="boot.fex" user_type =0x8000
對于大容量方案,需要在sys_partition.fex 中添加recovery 分區:
recovery 分區說明 如果啟用了OTA 升級,需要去掉下面recovery 分區的注釋以提供恢復分區存儲恢復系統鏡像, 默認以boot_initramfs.img 作為recovery.fex: [partition] name =recovery size =32768 downloadfile ="recovery.fex" user_type =0x8000
其中recovery.fex 是生成的boot_initramfs.img 軟鏈接而成。
4.2.2.2 rootfs 分區的大小
rootfs 分區不需要通過make menuconfig 去設定,直接根據鏡像大小修改分區文件即可。
4.2.2.2.1 小容量對于一些小容量flash 的方案(如16M),需在/bin 下存放聯網邏輯程序、
版本控制程序、下載鏡像程序、播報語音程序以及語音文件(這些文件在編譯時應該install 到/bin 或者/lib 下),可以在固件編譯完后,查看rootfs.img 的大小再決
定sys_partition.fex 中rootfs 分區的大小。
4.2.2.2.2 大容量對于大容量flash 的方案(如128M 以上,或者有足夠的flash 空間存相
關鏡像),不需要小容量中那些OTA 額外的程序,直接查看rootfs.img 的大小設定分區文件即可。
4.2.2.3 extend 分區的大小
extend 分區用于小容量方案下boot_initramfs.img 和usr.img 的復用,其大小需要考慮多個方面: 1 . 編譯后usr.img 的大小 2 . make_ota_image 后initramfs 鏡像的大小 因此,extend 分區略大于boot_initramfs.img 和usr.img 兩個的最大值,并把extend 分區的大小值設置為initramfs 鏡像的大小:
make menuconfig └─> Target Images └─> *** Image Options *** (8) Boot-Recovery initramfs filesystem partition size (in MB)
4.2.2.4 其他分區
如private 、misc 等使用默認的大小即可。
4.2.2.5 UDISK 分區
sys_partition.fex 中不指定UDISK 分區大小,則剩下的空間全部自動分配進入UDISK 分區。
需要注意的是,因為OTA 過程會在里面寫一些中間文件,所以一定要留取一定空間給UDISK 分區,至少可以格式化掛載,而小容量flash 的方案,也要保證有256K~512K 的空間。
4.2.2.6 其他說明
在OTA 升級過程中并不能修改上述分區的大小,因此應在滿足分區大小條件限制( 如3.2.1-3.2.3) 的情況下盡可能留有足夠的空余空間,以滿足OTA 升級添加內容
的需求。
修改分區大小時,盡量對齊到所用存儲介質的塊大小。
對于大容量,使用recovery 分區,對應的,其env 定義中,boot_recovery 命令需定義為從recovery 分區啟動系統。對于小容量,使用extend 分區,對應的,其
env 定義中,boot_recovery 命令需定義為從extend 分區啟動系統。
4.3 misc-upgrade 升級
4.3.1 misc-upgrade 構成
misc-upgrade 是Tina 下的一個應用,其路徑為:
tina/package/allwinner//misc-upgrade
Makefile 符合tina 安裝包的書寫規范。
misc-upgrade 目錄結構如下:
├── aw_fstab.init #小容量方案掛載用。 ├── aw_reboot.sh ├── aw_upgrade_autorun.init #自啟動腳本。 ├── aw_upgrade_image.sh ├── aw_upgrade_lite.sh ├── aw_upgrade_log.sh ├── aw_upgrade_normal.sh #大容量方案。 ├── aw_upgrade_plus.sh ├── aw_upgrade_process.sh #小容量方案。 ├── aw_upgrade_utils.sh ├── aw_upgrade_vendor_default.sh ├── Makefile ├── readme.txt └── tools #編譯出write_misc和read_misc應用。 ├── Makefile ├── misc_message.c ├── misc_message.h ├── read_misc.c └── write_misc.c
4.3.2 OTA 鏡像包編譯
在命令行中進入Tina 根目錄,執行命令進入配置主界面(環境配置):
source build/envsetup.sh ( 詳見1 ) lunch ( 詳見2 ) make ota_menuconfig (可選) ( 詳見3 ) 詳注: 1 加載環境變量及tina 提供的命令。 2 輸入編號,選擇方案。 3 進入OTA config 配置界面。直接保存退出即可。 此步驟可選,目的是解決開發過程中defconfig_ota 未能及時更新而可能引發的編譯問題。
編譯命令:
make_ota_image ( 詳見1 ) make_ota_image --force ( 詳見2 ) 詳注: 1 在新版本代碼已經成功編譯出燒錄固件的環境的基礎上,打包OTA 鏡像。 2 重新編譯新版本代碼,然后再打包OTA 鏡像。 注:不同介質使用的boot0/uboot鏡像不同, make_ota_image需要sys_config.fex中的storage_type配置明確指出介質類型, 才能取得正確的boot0.img和uboot.img。 具體可直接查看build/envsetup.sh中make_ota_image的實現。
執行make_ota_image 之前,可通過make ota_menuconfig 對ota 的恢復系統鏡像
boot_initramfs.img 進行配置,可根據實際情況,配置ota 恢復系統包含的功能。
如以下配置支持ramdisk 并選用xz 壓縮cpio :
make ota_menuconfig └─> target Images └─> [*] ramdisk └─> --- ramdisk └─> Compression (xz) --->
OTA 鏡像包路徑為:tina/out/xxx/ota/
目錄結構如下:
├── boot0_sys #boot0升級文件。 │ ├── boot0.img │ └── boot0.img.md5 ├── boot0_sys.tar.gz #boot0升級文件的壓縮包。 ├── package_sys #某定制版OTA腳本使用,不在本文檔描述范圍。 │ ├── boot0.img │ ├── boot0.img.md5 │ ├── boot.img │ ├── boot.img.md5 │ ├── ota.tar │ ├── recovery.img │ ├── recovery.img.md5 │ ├── rootfs.img │ ├── rootfs.img.md5 │ ├── uboot.img │ └── uboot.img.md5 ├── ramdisk_sys #recovery系統升級文件。 │ ├── boot_initramfs.img │ └── boot_initramfs.img.md5 ├── ramdisk_sys.tar.gz #recovery系統升級壓縮包。 ├── target_sys #kernel和rootfs升級文件。 │ ├── boot.img │ ├── boot.img.md5 │ ├── rootfs.img │ └── rootfs.img.md5 ├── target_sys.tar.gz #kernel和rootfs升級壓縮包。 ├── uboot_sys #uboot升級文件。 │ ├── uboot.img │ └── uboot.img.md5 └── uboot_sys.tar.gz #uboot升級壓縮包。
其中” .img.md5 “是” .img “的校驗值文件。
升級腳本不帶-n 參數,則使用壓縮包,帶-n 則直接使用不壓縮的升級文件。
4.3.3 小機端OTA 升級命令
必選參數:-f -p 二選一。
aw_upgrade_process.sh -f 升級完整系統(內核分區、rootfs 分區、extend 分區)。
aw_upgrade_process.sh -p 升級應用分區( extend 分區)。
可選參數:-l , -d -u, -n。
注:對于大容量,用aw_upgrade_normal.sh 替代aw_upgrade_process.sh ,且一般用-f參數而不用-p。
4.3.3.1 大容量flash 方案
可以使用本地鏡像測試,如主程序下載校驗好鏡像后,存在/mnt/UDISK/misc-upgrade 中,調用如下命令。
OTA 升級期間掉電,重啟后升級程序也能自動完成燒寫,不需要依賴聯網重新下載鏡像。
4.3.3.1.1 -l 選項-l < 路徑>
如:
aw_upgrade_normal.sh -f -l /mnt/UDISK/misc-upgrade
( 注:mnt 前的根目錄“/” 最好帶上,misc-upgrade 后不要帶“/”) ( -l 參數,默認使用壓縮鏡像包)。
不使用-n 參數的方案需要部署上服務器上的鏡像是:
? recovery 系統和主系統(必選):ramdisk_sys.tar.gz 、target_sys.tar.gz。 ? uboot 和boot0(可選, 調用腳本時鏡像不存在則自動跳過):uboot_sys.tar.gz,boot0_sys.tar.gz。
4.3.3.1.2 -n 選項如:
aw_upgrade_normal.sh -f -n -l /mnt/UDISK/misc-upgrade
一般情況下不使用-n 選項,而是下載ramdisk_sys.tar.gz 、target_sys.tar.gz 及可選的uboot_sys.tar.gz、boot0_sys.tar.gz。
但對于某些ram 較小的平臺,如R6 spinand 的情況,flash 的容量足夠放下大容量的OTA 包,但升級過程可能因為ram 不足而失敗。
對于這種情況,可以選擇下載未壓縮的數據,即上述xxx.tar.gz 解壓出來的所有內容。
將多個分區數據文件下載到/mnt/UDISK/misc-upgrade 中,調用上述命令。
使用-n 參數的方案需要部署上服務器上的鏡像是: ? recovery 系統和主系統(必選):boot_initramfs.img 、boot.img 、rootfs.img 及其對應的md5 后綴的校驗文件。 ? uboot 和boot0(可選,調用腳本時鏡像不存在則自動跳過):uboot.img、boot0.img 及其對應的md5 后綴的校驗文件。
4.3.3.2 小容量flash 方案
4.3.3.2.1 -l 選項原始的設計是用于網絡升級,不能使用-l 參數,升級區間出錯重啟后,需
要聯網下載程序獲取鏡像。 后續考慮存在小容量設備插SD 卡升級的情況,支持了-l 參數,命令類似上述的大容量方案,不贅述。
4.3.3.2.2 -d -u -n 選項
-d arg1 -u arg2
同時使用,-d 參數為可以ping 通的OTA 服務器的地址、-u 參數為鏡像的下載地址。
-n
-n 一些小ddr 的方案(如剩余可使用內存在20m 以下的方案),可以使用這個參數,shell 會直接下載不壓縮的4 個img 文件,這樣子設備下載后不需要tar 解壓,減少內存使用。
如:
aw_upgrade_process -f -d 192.168.1.140 -u http://192.168.1.140/
升級shell 會先ping -d 參數( ping 192.168.1.140 ),ping 通過后,會根據升級命令和系統當前場景請求下載: 無-n 參數:
http://192.168.1.140/ramdisk_sys.tar.gz http://192.168.1.140/target_sys.tar.gz http://192.168.1.140/usr_sys.tar.gz
有-n 參數:
http://192.168.1.140/boot_initramfs.img http://192.168.1.140/boot.img http://192.168.1.140/rootfs.img http://192.168.1.140/usr.img
使用-n 參數的方案需要部署上服務器上的鏡像是:boot_initramfs.img 、boot.img 、rootfs.img 、usr.img 及其對應的md5 后綴的校驗文件。
不使用-n 參數的方案需要部署上服務器上的鏡像是:ramdisk_sys.tar.gz 、target_sys.tar.gz、usr_sys.tar.gz。
注:若由misc-upgrade 自行下載鏡像,當前實現暫不支持可選的boot0/uboot 鏡像,即不會自動從服務器下載升級boot0/uboot。
4.4 腳本接口說明
對于小容量flash 的方案,沒有空間存儲鏡像,相關鏡像只會存在ram 中,斷電就會丟失。假如升級過程斷電,需要在重啟后重新下載鏡像。
aw_upgrade_vendor.sh 設計為各個廠家實現的鉤子,SDK 上只是個demo 可以隨意修改。
4.4.1 實現聯網邏輯
check_network_vendor(){ return 0 聯網成功(如:可以ping 通OTA 鏡像服務器)。 return 1 聯網失敗。 }
4.4.2 請求下載目標鏡像
$1 : ramdisk_sys.tar.gz $2 : /tmp
download_image_vendor(){ # $1 image name $2 DIR $@ others rm -rf $2/$1 echo "wget $ADDR/$1" wget $ADDR/$1 -P $2 }
4.4.3 開始燒寫分區狀態
aw_upgrade_process.sh -p 主動升級應用分區的模式下,返回0 開始寫分區1 不寫分區。 aw_upgrade_process.sh -f 不理會這個返回值。 upgrade_start_vendor(){ # $1 mode: upgrade_pre,boot-recovery,upgrade_post #return 0 -> start upgrade; 1 -> no upgrade #reutrn value only work in nornal mode #nornal mode: $NORMAL_MODE echo upgrade_start_vendor $1 return 0 }
4.4.4 寫分區完成
upgrade_finish_vendor(){ #set version or others reboot -f }
4.4.5 -f (-n) 調用順序
1 . check_network_vendor -> 2 . upgrade_start_vendor -> 3 . download_image_vendor (ramdisk_sys.tar.gz, -n 為boot_initramfs.img)-> 4 .內部燒寫、清除鏡像邏輯(不讓已經使用鏡像占用內存) -> 5 . download_image_vendor(target_sys.tar.gz, -n 為boot.img rootfs.img) -> 6 .內部燒寫、清除鏡像邏輯(不讓已經使用鏡像占用內存) -> 7 . download_image_vendor(usr_sys.tar.gz, -n 為usr.img) -> 8 .內部燒寫、清除鏡像邏輯(不讓已經使用鏡像占用內存) -> 9 . upgrade_finish_vendor
4.4.6 -p 調用順序
1 . check_network_vendor -> 2 . download_image_vendor (usr_sys.tar.gz) -> 3 . upgrade_start_vendor -> 4 . 檢測返回值,燒寫-> 5 . upgrade_finish_vendor
4.5 相關系統狀態讀寫
相關的信息存儲在misc 分區,OTA 升級不會清除這個分區(重新燒寫鏡像會擦除)。
讀:
read_misc [command] [status] [version]
其中:
command 表示升級的系統狀態( shell 腳本處理使用)。 status 自由使用,表示用戶自定義狀態。 version 自由使用,表示用戶自定義狀態。
寫:
write_misc [ -c command ] [ -s status ] [ -v version ]
其中:
-c 不能隨意修改,只能由aw-upgrade shell 修改。 -s -v 自定義使用。
4.6 OTA 配置
4.6.1 recovery 系統生成
一般來說,默認方案目錄下會有一份defconfig_ota 配置文件,該文件用于編譯生成一個帶ramdisk 的kernel ,即boot_initramfs.img ,作為OTA 期間燒寫到
recovery 分區或extend 分區的備份系統。
用戶可以基于原有的defconfig_ota 進行配置,也可以自行拷貝defconfig 為新的defconfig_ota ,然后進行適當修改和配置。
執行make ota_menuconfig 可進行OTA 配置。
選上recovery 后綴,避免編譯recovery 系統時,影響到主系統。
make ota_menuconfig ---> Target Images ---> [*] customize image name ---> Boot Image(kernel) name suffix (boot_recovery.img/boot_initramfs_recovery.img) ---> Rootfs Image name suffix (rootfs_recovery.img)
如果方案進行了較多的修改,建議刪除原本的defconfig_ota ,然后拷貝defconfig 為新的defconfig_ota ,再進行配置。
如上文所述,必須選上misc-upgrade 包,以及ramdisk 選項,保留wifi 功能。其他選項可以關掉,以對生成的boot_initramfs.img 進行裁剪。
4.6.2 recovery 系統切換
4.6.2.1 切換方式1:基于misc 分區
對于存在misc 分區的方案,可以在misc 分區中設置boot-recovery 標志。啟動時,uboot 會檢測misc 分區,如果為boot-recovery,則執行env 中配置的
boot_recovery 命令啟動內核。否則執行boot_normal 命令啟動內核。
因此,env 中需要正確配置boot_normal 和boot_recovery。類似:
boot_normal=sunxi_flash read 40007800 boot;bootm 40007800 boot_recovery=sunxi_flash read 40007800 extend;bootm 40007800
對于大容量方案boot_recovery 配置為recovey 分區啟動,對于小容量方案,boot_recovery配置為從extend 分區啟動。
OTA 過程需要正確設置misc 分區的值。
4.6.2.2 切換方式2:基于env 分區
可以去除misc 分區,直接基于env 進行系統切換。OTA 過程需修改env 分區中的值。
一般是將env 的boot_normal 配置為從$boot_partition 分區啟動,即將要使用的分區抽離成一個變量。
類似:
boot_partition=boot boot_normal=sunxi_flash read 40007800 ${boot_partition};bootm 40007800
env 中默認boot_partition=boot。需要切換到recovery 系統時,在用戶空間直接設置env,設置boot_partition=recovery。需要切換回主系統時,設置
boot_recovery=boot。
OTA 過程需要正確設置env 中的值。
4.7 對overlayfs 的處理
Tina 默認使用overlayfs ,則用戶對原rootfs 的修改會記錄在rootfs_data 分區中。而OTA更新的是rootfs 分區,默認不會修改rootfs 分區。則用戶對rootfs 文件
的增加,刪除,修改操作都會保留,假如原本的rootfs 中有文件A ,用戶將其修改為A1 ,而OTA 更新將該文件修改為B ,則最終看到的仍然是A1 。這是由
overlayfs 的特性決定的,上層目錄的文件會屏蔽下層目錄。
如果希望OTA 之后,以OTA 更新的文件為準,移除所有用戶的修改。則可以在OTA 之后,重新格式化rootfs_data 分區。
4.8 對busybox-init 的處理
4.8.1 upgrade_etc 標志(不再使用)
有些平臺使用了busybox-init ,以R6 為例子: R6 方案將啟動方式從procd 修改為busybox-init ,不再使用procd 和overlayfs ,由此帶來一系列變化。
OTA 腳本通過1 號進程的名字來判斷啟動方式。并根據結果。在后續腳本中做區別處理。
對于busybox-init ,在第一次啟動之后,會將etc 的文件拷貝到rootfs_data 分區中,并在后續掛載該分區作為etc 目錄。OTA 的過程不會更新rootfs_data 分區。
為了支持更新etc 目錄,增加了一個系統狀態,upgrade_etc ,并在原本設置upgrade_end 的地方,改為設置成upgrade_etc 。而busybox-init 的啟動腳本會判斷
此標志,如果啟動是標志為upgrade_etc,則進行etc 分區文件的更新,更新后設置系統狀態為upgrade_end 。
主系統指定了啟動腳本init=/pseudo_init. 對于OTA 使用的recovery 系統也需要指定啟動腳本,若所使用的方案未配置,可自行修改env ,在cmdline 中傳遞
rdinit=/pseudo_init 進行指定。若文件系統中已經有rdinit 文件(例如是一個到pesudo_init 的軟鏈接),則可使用rdinit=/rdinit。
4.8.2 etc_need_update 文件
rootfs_data 有兩種可能的用法
1.保存rootfs /etc 的副本,掛載到/etc,以支持/etc 可寫
2.用作overlayfs,以支持全目錄可寫,類似procd 方案 對于早期版本,僅支持1。對于當前最新版本,/pseudo_init 的最上方可以配置,當配置MOUNT_ETC=1, MOUNT_OVERLAY=0 時,即為1,跟早期版本相同,此處只討論上述的1。 其行為如下:
燒錄后第一次啟動,rootfs_data 分區為空,則/pesudo_init 會格式化rootfs_data,將/etc文件拷貝一份到rootfs_data 的文件系統,將rootfs_data 掛載為/etc,對用戶來說看到的/etc 就是rootfs_data 中的
第二次啟動,rootfs_data 中存在有效的文件系統,直接掛載為/etc,對用戶來說看到的/etc就是rootfs_data 中的
此時若直接更新rootfs 分區中的rootfs,重啟,用戶看到的/etc 仍為rootfs_data 中的,不會改變
若創建/etc/etc_need_update 文件(OTA 之后應該創建它),則下次啟動/pseudo_init 腳本會將rootfs 分區中的/etc 進行一次同步
同步/etc 的具體行為: 5.1. 將rootfs_data 掛載到/mnt 5.2. 將rootfs 分區的/etc 拷貝到/tmp/etc 5.3. 將rootfs_data 中不希望被覆蓋的文件,拷貝到/tmp/etc。例如wpa_supplocant.conf。如果有其他特殊文件不希望被OTA 覆蓋,可參考/pseudo_init 中的wpa_supplicant 的處理方法,修改/pseudo_init。 5.4. 將/tmp/etc 的內容,拷貝回/mnt/etc(即rootfs_data 分區) 5.5. 創建標志文件etc_complete,刪除標志文件etc_need_update。若在此步驟完成前掉電,則下次啟動會從步驟1 重新同步etc 5.6 將rootfs_data 的掛載點從/mnt 改為/etc, 繼續啟動
4.9 常見問題
4.9.1 OTA 時出現SQUASHFS ERROR
此問題是由于Tina3.0 及更早版本的OTA ,在更新rootfs 分區時,只是將busybox 等備份到ram 中,rootfs,分區尚處于掛載狀態,此時如果有進程訪問rootfs ,
則會出現錯誤。
解決方式:
OTA 之前,關閉其他進程,避免有進程訪問rootfs 。如果確有進程需要保留,將其依賴的資源提前備份到ram 中(相關代碼可參考OTA 腳本中的prepare_env 函數)。
參照最新版本的做法,在更新完recovery 分區后,主動reboot ,進入recovery 系統,在recovery 系統中更新boot 和rootfs 分區。
參考原生openwrt 的做法,在內存中構建ram 文件系統后,執行切換根文件系統的操作,再進行更新。
4.9.2 編譯OTA 包之后,正常編譯出錯
出現編譯問題,可能是由于defconfig 被替換了。
請檢查下target 倉庫中方案對應目錄的defconfig 和defconfig_ota 的狀況。當前的OTA 包編譯過程,實質上是備份defconfig ,使用defconfig_ota 替換defconfig
,執行make ,最終還原defconfig。
如果此過程中斷,則defconfig 未被還原,會導致下次正常編譯出問題。
解決方式:
還原方案目錄下的defconfig 文件。
建議make menuconfig 和make ota_menuconfig 之后,及時在target 倉庫下將修改提交入git 倉庫中,避免修改丟失,方便在必要時進行還原。
(此問題為Tina3.0 之前的問題,最新版本沒有此問題。)
4.9.3 是否可更新boot0/uboot
misc-upgrade 原設計流程中未包含此功能
當前版本中,本地升級支持可選地更新boot0/uboot(存在鏡像則更新,不存在則跳過),網絡升級暫未支持。
具體升級功能由ota-burnboot 軟件包支持,細節請參考相關章節。
判斷是否支持的方式:搜索腳本中是否有地方調用了ota-burnboot0 和ota-burnuboot。
5 Tina upgrade app 介紹(建議改用swupdate)
5.1 功能簡介
以前Tina 只有misc-upgrade,為了特殊需求,創建了本腳本。現在建議直接使用swupdate。
有些客戶,需要單獨更新應用程序。一種解決方式是,將應用程序單獨放到一個分區中,并在啟動時掛載該分區。為了保證更新過程掉電重啟,仍有可用的應用程
序,可設置兩個應用分區,并配合環境變量等選擇掛載。
5.2 應用源碼
需修改應用源碼的Makefile,將應用涉及文件,全部安裝到/mnt/app 路徑。
如果應用是二進制形式,則請放到:
target/allwinner/xxx/busybox-init-base-files/mnt/app
5.3 menuconfig 設置
選中:
make menuconfig ---> Target Images ---> [*] Separate /mnt/app from rootfs
使得/mnt/app 目錄, 從rootfs 中分離出來。打包的時候, 會被制作成一個單獨的文件app.fex。 選中:
make menuconfig ---> Allwinner ---> <*> tina-app-upgrade
選中后,可使用
/sbin/aw_upgrade_dual_app.sh
進行OTA。
5.4 分區設置
增加兩個分區,名字固定為app 和app_sub,downloadfile 固定為app.fex,size 根據實際情況調整。
[partition] name = app size = 51200 downloadfile = "app.fex" user_type = 0x8000 [partition] name = app_sub size = 51200 downloadfile = "app.fex" user_type = 0x8000
5.5 env 設置
對于tina3.5.0 及之前版本,位于:
target/allwinner/${board}/configs/env-xxx.cfg
對于tina3.5.1 及之后版本,位于:
device/config/chips/${chip}/configs/${board}/linux/env-xxx.cfg
增加配置:
appAB=A #set applimit=0 to disable appcount check applimit=0 appcount=0
其中appAB 指定要啟動時要掛載哪個分區:
appAB=A 掛載/dev/by-name/app到/mnt/app appAB=B 掛載/dev/by-name/app到/mnt/app
applimit 和appcount 用于支持應用的自動回退功能。
5.6 自動回退
applimit=0 時,沒有任何作用。
applimit 非0 時,會在每次啟動,遞增appcount 值,并檢測appcount 是否大于applimit,若大于,則切換掛載另一個app 分區。
例如,當前掛載/dev/by-name/app,設置applimit=2,appcount=0。
則每次啟動,appcount 加一,兩次啟動后,appcount =2,再次重啟,appcount+1=3>applimit,超過限制,自動改成掛載/dev/by-name/app_sub。
這個功能主要是要解決,更新了一個有問題的應用,導致無法正常啟動應用的問題。例如:
當前處于app,OTA 更新了一個有問題的新應用到app_sub 分區,重啟,重啟后無法正常啟動app_sub 中的新應用。則applimit 次重啟后,會自動改回使用app
分區中,舊的可用的應用。 使用此功能,需要應用在正常啟動后,主動使用 fw_setenv appcount 0 清空計數值,避免累積到applimit 切換分區。
5.7 OTA 步驟
5.7.1 生成OTA 包
tina 目錄下,執行
make_ota_package_for_dual_app
生成OTA 包
out/xxx/ota_dual_app/app_ota.tar
5.7.2 下載OTA 包
通過網絡或ADB 等方式,將app_ota.tar 放到小機端/mnt/UDISK/app_ota.tar。
5.7.3 執行OTA
執行腳本:
aw_upgrade_dual_app.sh
5.8 調試
生成OTA 包的函數,位于:
build/envsetup.sh function make_ota_package_for_dual_app()
可從生成的tar 文件中,將app.fex 解出來:
tar -xf out/xxx/ota_dual_app/app_ota.tar
解壓得到的app.fex,是一個ext4 文件系統,可在linux 主機,或推送到小機端,掛載,查看文件系統中的內容:
mkdir app mount app.fex app ls aaa
6 其他功能
6.1 在用戶空間操作env
Tina 中支持了uboot-envtools 軟件包。
make menuconfig --> Utilities ---> <*> uboot-envtools
選上后即可在用戶空間使用fw_printenv 和fw_setenv 來讀寫env 分區的變量。
6.2 AB 系統切換
6.2.1 uboot 原生啟動計數機制
uboot 原生支持了啟動計數功能。該功能主要涉及三個變量。
upgrade_available=0/1 #總開關,設置1才會進行bootcount++及檢查切換,設為0則表示關閉此功能 bootcount=N #啟動計數,若upgrade_available=1,則每次啟動bootcount++,并用于跟bootlimit比較 bootlimit=5 #配置判定啟動失敗的閾值
即編譯支持該功能后,當upgrade_available=1,則uboot 會維護一個bootcount 計數值,每次啟動自動加一,并判斷bootcount 是否超過bootlimit。如果未超
過,則正常啟動,執行env中配置的bootcmd 命令。如果超過,則執行env 中的altbootcmd 命令。
支持此功能后,啟動腳本或主應用需要在合適的時機清除bootcount 計數值,表示已經正常啟動。
配置:
使能:配置CONFIG_BOOTCOUNT_LIMIT 選擇bootcount存放位置, 如存放在env分區(掉電不丟失):配置CONFIG_BOOTCOUNT_ENV 如存放在RTC寄存器(掉電丟失):配置CONFIG_BOOTCOUNT_RTC 如需存放在其他位置可自行拓展
在用戶空間可配置upgrade_available 的值對此功能進行動態開關。例如可在OTA 之前打開,確認OTA 成功后關閉。也可一直保持打開。在用戶空間,需要清空
bootcount,否則多次重啟就會導致bootcount 超過bootlimit。
6.2.2 全志定制系統切換
全志自己添加了CONFIG_SUNXI_SWITCH_SYSTEM 功能進行系統切換。目前尚未大量使用,供參考。
對上接口:
在flash 的env 分區中設置了2 個標志變量systemAB_now,systemAB_next。systemAB_now:由uboot 階段進行設置, 該標志位系統應用只讀不能寫(通過工具
fw_printenv,或其他工具)。systemAB_next:無論是uboot 還是系統應用都可讀可寫(一般uboot 不會主動修改,除非系統已經損壞需要切換)初始值一般設
置為
systemAB_next=A #表示下次啟動A系統 systemAB_now=A #表示當前為A系統,不設置也可以,uboot會自動設置
當前系統應用可以通過systemAB_now 標志識別當前系統是A 系統還是B 系統, 系統應用可以把標志變量systemAB_next=A/B 寫到env 分區里, 然后重啟. 重啟后
uboot 會去檢查systemAB_next 這個標志, 如果標志是systemAB_next=A,uboot 會啟動A 系統, 如果是systemAB_next=B,uboot 會啟動B 系統。同時uboot 會根
據本次systemAB_next 的值設置systemAB_now 供系統讀取。
底層設置:
對上接口定義的是systemA/B,以系統為單位。目前的系統組成定義為包含kernel 和rootfs 分區。考慮不同方案,分區名可能不同,因此支持用環境變量指定具體
分區名,而不是hardcode為某個分區名。
systemA=boot A系統kernel分區名 rootfsA=rootfs A系統rootfs分區名 systemB=boot_B B系統kernel分區名 rootfsB=rootfs_B B系統rootfs分區名
底層實現: 此機制會動態修改boot_partition 和root_partition 的值。具體boot_partition 和root_partition 變量的作用,請參考本文檔其他章節的介紹。
7 注意事項
7.1 Q & A
Q:系統的哪些部分是可以升級的? A:kernel 和rootfs 是可以升級的,但為了掉電安全,需要搭配一個recovery 系統或者做AB 系統。對于nand 和emmc 來說,boot0/uboot 存在備份,可以升
級。對于nor來說,boot0/uboot 沒有備份,不能升級。或者說升級有風險,中途掉電會導致無法啟動。boot0/uboot 的升級具體可參考本文檔中的ota-burnboot
部分。對于swupdate 升級方案,可以自行在sw-description 中配置策略,升級自己的定制分區和文件,但務必考慮升級中途掉電的情況,必要的話需要做備份和
恢復機制。
Q:系統的哪些部分是不能升級的? A:分區表是不可升級的,因為改動分區表后,具體分區對應的數據也要遷移。建議在量產前規劃好分區,為每個可能升級的分區預留部分空間,防止后續升級空
間不足。nor 方案的boot0/uboot 是不可升級的,因為沒有備份。其余不確定是否能夠升級的,請向開發人員確認。
Q:dts/sys_config 如何升級? A:默認dts 和sys_config,會跟uboot 綁定生成一個bin 文件。因此升級uboot 實質上是升級了uboot+dts/sys_config。
Q:能否單獨升級dts? A:目前默認跟uboot 綁定,需要跟開發人員確認如何將dts 獨立出來,放到獨立分區或者跟kernele 綁定到一起。如果是dts 位于獨立分區,那么就需要修改配
置,將dts 放置到OTA 包中,OTA 時寫入到對應分區。
Q:升級過程掉電重啟后,是從斷點繼續升級還是從頭升級? A:從頭開始升級,例如定義了在recovery 系統中升級boot 分區和rootfs 分區,則在升級boot 或rootfs 過程中斷電,重啟后均是從boot 重新開始升級。
8 升級失敗問題排查
凡是遇到升級失敗問題先看串口log,如果不行再看/mnt/UDISK/swupdate.log 文件
8.1 分區比鏡像文件小引起的失敗
log 大概如下。找error 的地方,可以看到recovery 分區比其鏡像小,所以報錯。
[ERROR] : SWUPDATE failed [0] ERROR handlers/ubivol_handler.c : update_volume : 171 : " recovery" will not fit volume "recovery"
解決方法: 增加對應方案tina/device/config/chips/< 芯片編號>/configs/< 方案>/sys_partition.fex 文件的對應分區的大小
8.2 校驗失敗
差分有嚴格的版本控制,當出現checksum 有問題時,基本可以歸類為這種問題。
解決方法:重新燒錄固件,制作差分包
-
Linux
+關注
關注
87文章
11229瀏覽量
208928 -
無線網絡
+關注
關注
6文章
1426瀏覽量
65884 -
OTA
+關注
關注
7文章
568瀏覽量
35144 -
開發指南
+關注
關注
0文章
34瀏覽量
7531 -
Tina
+關注
關注
2文章
45瀏覽量
16957
發布評論請先 登錄
相關推薦
評論