rk35xx 通过recovery升级问题
Firefly 的recovery库是一个核心组件,它构建了一个独立的微型 Linux 系统,专门用于在设备主系统之外执行高可靠性的固件升级。简单来说,它的工作流程是:主系统通过命令触发,将升级指令写入特定分区并重启;随后,引导程序(U-Boot)加载recovery系统;最后,recovery系统解析并执行升级包,完成固件更新。
整个流程与 Rockchip 平台深度集成,主要用于实现系统级(OTA)升级和恢复出厂设置。
🔄 OTA 升级原理与完整流程
Rockchip 平台的 OTA 升级流程可以清晰地拆分为以下几个步骤:
步骤 1: 升级触发与数据准备
用户触发升级后,升级程序 (update_engine或rkupdate) 会完成两项核心工作:
- 校验升级包:首先对 OTA 包(如
update.img或update.zip)进行签名校验,确保其完整性和来源可信。 - 写入 BCB 指令:校验通过后,会向
misc分区的启动控制块(BCB)写入特定指令。其中,command字段是关键,通常为boot-recovery,同时recovery字段会携带升级包的路径(例如--update_package=/udisk/update.img)。这个操作就是告诉引导程序下次启动时要做什么。
💡 关于
misc分区和 BCB 结构体
misc分区是一个数据中转站,在用户空间通常通过文件系统节点(例如/dev/block/by-name/misc)进行访问。BCB 结构体的核心内容定义在
bootable/recovery/bootloader.h中:structbootloader_message{charcommand[32];// 如 "boot-recovery"charstatus[32];// 升级进度状态charrecovery[1024];// 详细升级指令,如 "--update_package=/sdcard/update.zip"// ... 其他字段};
步骤 2: 重启并进入 Recovery 模式
- 主系统执行重启命令(
reboot)。硬件重启,控制权移交给引导程序(通常是 U-Boot)。 - U-Boot 在启动过程中会检查
misc分区的 BCB 数据。若读取到command字段包含boot-recovery,U-Boot 便会根据预设配置,加载recovery分区上的recovery.img镜像,而不是常规的boot.img。
步骤 3: Recovery 系统执行升级
recovery.img被加载到内存并启动,它实际上是一个包含精简内核和最小根文件系统(通常基于initramfs)的微型系统。其启动流程如下:
- 系统启动:内核启动,并挂载
initramfs。第一个执行的程序通常是/init。 - 启动核心程序:
init进程会根据配置文件(如init.rc)启动recovery主程序。recovery服务会解析 BCB 信息。 - 调用具体执行者:
recovery程序会解析 BCB 中的指令,识别升级包类型,并调用相应的升级后端:rkupdate后端:专门用于处理 Rockchip 私有格式的update.img完整镜像包。update_engine后端:功能更强大,支持update.zip、update.img等多种包格式,并兼容 A/B 分区升级。
- 写入固件:
rkupdate或update_engine根据指令,解析升级包文件,并将其中包含的各个分区镜像(如boot.img,system.img等)准确地写入到 Flash 存储的对应分区中。
🔬 关键代码模块与实现分析
recovery项目的源码目录通常位于bootable/recovery/。以下是其核心模块与代码实现的关键部分。
1.recovery主程序:流程控制中枢 (recovery.cpp)
recovery程序的入口在recovery.cpp的main()函数。它会解析 BCB,决定是执行升级还是显示菜单,是升级流程的控制中心。
// bootable/recovery/recovery.cpp (简化逻辑)intmain(intargc,char**argv){// 1. 加载并解析 BCB (Bootloader Control Block)std::string boot_command;std::string boot recovery;// ... 从 misc 分区读取数据,参考 bootloader.cpp 的 get_bootloader_message_mtd// 读取到的内容会被存入结构体 struct bootloader_message// 2. 解析命令行参数,例如 --update_package=/path/to/package// adb 等会将这些参数写入 /cache/recovery/command// get_args() 会合并 BCB 和 /cache/recovery/command 中的参数// update_package 变量最终会包含 OTA 包的路径,如 /sdcard/update.zip// 3. 根据命令执行相应操作// process_update_package(update_package) 是执行升级的核心函数if(!update_package.empty()){// 处理 OTA 升级包intstatus=install_package(update_package);// 升级成功则重启,失败则可能显示错误菜单}elseif(!wipe_data){// 显示 Recovery 主菜单ui->ShowMenu();}return0;}代码逻辑参考了 Android 原生 recovery 模块的实现方式。
updater机制:脚本驱动的升级逻辑当升级包为update.zip格式时,recovery会启动updater程序。updater的核心是执行包内的META-INF/com/google/android/updater-script脚本,该脚本使用 Edify 语言编写,定义了每一步升级操作,如分区擦除、文件写入等。
updater-script示例# 显示进度 ui_print("Starting update..."); # 擦除 system 分区 format("ext4", "EMMC", "/dev/block/by-name/system", "0"); # 将 new_system.img 写入 system 分区 block_image_update("/dev/block/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat"); # 写入 boot 分区 package_extract_file("boot.img", "/dev/block/by-name/boot"); ui_print("Update completed successfully.");
2.rkupdate后端:Rockchip 私有格式执行者 (external/rkupdate/)
rkupdate是处理 Rockchip 私有update.img固件的后端程序。update.img是一个包含多个分区镜像的集合包。rkupdate的核心功能是解析这个集合包,并将每个分区镜像烧录到 Flash 的正确位置。
下面是update.img解析的核心代码片段(简化逻辑):
// external/rkupdate/rkupdate.c (简化逻辑)intdo_rkupdate(constchar*package_path){FILE*fp=fopen(package_path,"rb");// 1. 解析 Rockchip 固件头部 (RC4)// 读取前几个字节,确认是 RC4 格式,并获取头部信息intfd=open(package_path,O_RDONLY);rc4_header header;read(fd,&header,sizeof(header));// 2. 循环处理每个分区镜像// 根据头部信息找到每个分区的起始地址和长度// 然后逐个分区进行烧写for(inti=0;i<header.num_images;i++){// 获取分区镜像的偏移和大小off_toffset=get_partition_offset(i);size_tsize=get_partition_size(i);constchar*name=get_partition_name(i);// 3. 烧写到对应分区// 调用 rkflash 相关函数写入 /dev/block/by-name/ 下的设备节点if(write_partition(name,fd,offset,size)!=0){LOGE("Failed to write partition %s",name);return-1;}// 写入进度更新update_progress(...);}fclose(fp);LOGI("rkupdate completed successfully!");return0;}3.update_engine后端:现代化升级方案 (external/recovery/update_engine/)
update_engine是一个设计上更现代化、功能更全面的升级后端。它是一个独立的守护进程,通过 D-Bus 通信,采用状态机管理升级任务,将升级过程抽象为IDLE->CHECKING->DOWNLOADING->UPDATING等状态。
- 架构优势:通过抽象接口(
PartitionWriter,FileSystem等)实现了高内聚低耦合,便于移植和扩展。 - 多格式支持:原生支持
update.zip和update.img。
update_engine的状态机迁移可以简化为:
IDLE -> CHECKING -> DOWNLOADING -> UPDATING -> IDLE | | | +------------+--------------+-> ERROR🛠️ 独立实现与进阶方案
除了使用 Rockchip 的官方方案,嵌入式 Linux 设备还有多种构建 OTA 能力的途径。
独立实现方案
要独立实现一套 OTA 系统,核心在于构建一个可靠、安全的升级环境。关键步骤和组件如下:
- 制作 Recovery 镜像 (
recovery.img):这个镜像本身是一个独立的 Linux 系统。你可以采用以下方式构建:- 基于 Buildroot:在 Buildroot 的配置中为另一个配置 (
output/recovery/) 选择最小化的 Linux 内核和 BusyBox,并加入升级程序源码。
- 基于 Buildroot:在 Buildroot 的配置中为另一个配置 (
- BCB 操作与系统服务:
- 编写一个独立程序(例如
my_ota_client)来操作/dev/block/by-name/misc分区,用于写入和读取 BCB 指令。 - 使用 Linux 的
inotify机制或实现一个简单的守护进程来监听升级事件。
- 编写一个独立程序(例如
- 引导程序适配:修改 U-Boot 源码,使其在启动流程中加入对
misc分区的检查逻辑。这通常涉及 Rockchip 特有的reboot mode驱动。 - 升级包生成脚本:编写一套脚本,用于生成最终可在设备上使用的
update.img或update.zip升级包。
进阶实现方案
使用开源框架 (SWUpdate/RAUC/Mender):这是实现专业级 OTA 功能的主流选择。它们功能完善,社区活跃,可极大缩短开发周期。
- SWUpdate:最灵活,支持本地、网络等多种升级方式,并可直接复用
update-engine。 - RAUC:在需要复杂的 A/B 分区更新和回滚机制的场景下,架构非常清晰。
- Mender:提供开箱即用的完整解决方案。
- SWUpdate:最灵活,支持本地、网络等多种升级方式,并可直接复用
引入 A/B (Seamless) 分区升级:这是提升用户体验的关键。系统拥有两套独立分区(A 和 B)。升级时,系统在后台更新非当前运行的那套分区,完成后只需简单重启即可切换,实现了真正的“无缝”升级,且升级失败能自动回滚。
两种方案对比
| 维度 | Rockchip 官方方案 | 开源方案 (SWUpdate 等) |
|---|---|---|
| 集成难度 | 低,与 SDK 深度集成 | 中高,需独立移植 |
| 灵活性 | 受限于 Rockchip 框架 | 非常高,可深度定制 |
| 社区生态 | 依赖 Rockchip 支持 | 全球开发者社区,活跃 |
| 维护成本 | 随 SDK 更新 | 需要持续关注上游 |
总的来说,Firefly 的recovery库展示了 Rockchip 平台实现系统升级的标准方法,其核心是围绕recovery系统这一独立环境来保证升级的高可靠性。在工程实践中,如果追求快速开发和与 SDK 的深度集成,Rockchip 的官方方案是首选;而当项目需要更灵活的升级策略、更强的社区支持或有特定的安全要求时,采用 SWUpdate 等成熟的开源框架将更具优势。
