
1. 项目缘起为什么需要同时搞定三大平台的OTA在嵌入式设备特别是智能硬件和物联网终端的开发与维护中OTAOver-The-Air空中升级功能是产品生命线的核心保障。它直接关系到用户体验、安全漏洞修复和功能迭代的效率。我最近接手了一个项目需要为一款出货量不小的智能设备提供OTA支持而这款设备的硬件方案覆盖了高通Qualcomm、联发科MediaTek MTK和展锐Unisoc三大主流移动平台。这听起来像是一个“三合一”的挑战但实际上它暴露了当前硬件方案多样化背景下软件开发和维护的一个典型痛点如何在保持功能一致性的前提下高效、稳定地适配不同芯片平台的底层差异。这个需求绝非个例。很多公司为了供应链安全、成本控制或功能差异化会在不同批次、不同型号甚至同一型号的不同版本中采用不同的主控芯片。如果每个平台都独立开发一套OTA流程那后续的测试、维护和版本管理将是一场噩梦。我们的目标很明确构建一套统一的、可配置的OTA升级框架能够屏蔽底层平台差异向上提供一致的升级接口和体验。这不仅仅是写几个脚本那么简单它涉及到引导程序Bootloader、分区管理、升级包制作、安全校验、回滚机制等一整套复杂逻辑在不同芯片架构上的实现与适配。2. 三大平台OTA的底层差异与核心挑战在开始设计统一方案之前必须深入理解这三个平台在启动、分区和系统更新层面的根本不同。这些差异是设计抽象层和通用接口的出发点。2.1 引导程序与刷机模式的入口差异这是第一个拦路虎。OTA的本质是更新存储设备通常是eMMC或UFS上的固件分区而引导程序是控制这一切的“守门人”。高通平台其Bootloader通常基于Little KernelLK深度定制我们最常打交道的模式是EDLEmergency Download模式也就是常说的9008模式。在EDL模式下通过高通的专属工具如QPST、QFIL和协议可以直接与设备存储的底层分区表进行交互进行读写操作。对于OTA我们通常不进入完整的EDL而是利用Bootloader中实现的fastboot协议。高通的fastboot命令集通常比较丰富支持flash、erase、getvar等并且其分区名称如boot、system、recovery有相对标准的定义。安全启动Secure Boot链条也较为严格从PBLPrimary Boot Loader到ABLApplication Boot Loader再到系统每一级都会验证下一级镜像的签名。MTK平台MTK的Bootloader同样基于LK或U-Boot。其对应的底层刷机模式是BROMBoot ROM模式。通过短接测试点或特定按键组合设备可以进入BROM此时可以通过MTK的专属工具如SP Flash Tool进行读写。在正常启动的Bootloader中MTK也支持fastboot协议但其命令和分区命名可能与高通有细微差别。MTK平台有一个显著特点是MTK DWSDriver Working Sheet文件它定义了GPIO、I2C等引脚配置OTA升级包有时需要包含或考虑这些配置的更新否则可能导致升级后外设如双喇叭mtk双喇叭工作异常。此外MTK相机相关的PDAF OTP数据也可能存储在独立分区OTA时需要小心处理避免丢失校准数据。展锐平台展锐平台的Bootloader方案多样可能是U-Boot或其定制版本。其底层下载模式通常称为ResearchDownload模式。与之配套的工具有ResearchDownload Tool等。在fastboot支持上展锐平台可能不如高通和MTK那么“标准”部分自定义分区或命令可能需要通过其他机制如通过updater-script脚本调用底层工具来操作。展锐平台在多媒体特别是相机方面有其独特的Metadata架构相机的HAL层与这些metadata紧密耦合OTA升级相机相关组件时必须同步考虑metadata的兼容性否则会出现相机无法启动或功能异常的问题。注意直接操作底层刷机模式9008/BROM/ResearchDownload风险极高通常仅用于工厂烧录或救砖。生产环境下的OTA必须基于运行中的系统或Recovery模式中的标准接口如fastboot、A/B系统更新来进行以确保过程可控、可回滚。2.2 分区布局与动态分区管理分区表定义了存储空间的划分。统一的OTA方案必须能适应不同的分区布局。传统静态分区这是最常见的方式例如boot、system、userdata、cache等分区大小固定。制作OTA包时需要针对每个分区生成独立的镜像文件如boot.img、system.img。这种方式简单直接但不够灵活无法在后期调整分区大小。动态分区这是Android 10以后推广的机制特别是与A/B无缝更新结合时。它将system、vendor、product等分区合并到一个大的super分区中并在其中动态分配空间。OTA包中不再包含完整的system.img而是包含一个super_empty.img和一系列system、vendor等的增量内容.new.dat.br等。Bootloader需要支持动态分区表的读取和更新。高通、MTK、展锐的新平台尤其是搭载Android 10的都已支持动态分区但具体实现和工具链可能有差异。例如生成super.img的工具lpmake参数各平台可能需要不同的配置。A/B分区无缝更新这是实现用户无感升级的关键。设备有两套完整的槽位slot_a, slot_b。OTA时后台将新系统更新到非活动槽位重启时Bootloader切换槽位。这对Bootloader的要求更高需要支持读取A/B状态、设置启动优先级等。三个平台在实现A/B时其Bootloader中读取的misc分区信息结构或用于切换槽位的fastboot命令如set_active可能需要平台特定的适配。2.3 升级包格式与更新脚本OTA升级包ZIP格式的核心是META-INF/com/google/android/目录下的两个文件updater-binary可执行文件和updater-scriptEdify脚本。updater-binary这个二进制文件负责解析和执行updater-script脚本。它需要与平台紧密集成因为它内部会调用底层函数来操作分区、挂载文件系统、进行哈希校验等。不同芯片平台甚至不同Android版本都需要编译对应的updater-binary。通常它来自AOSP源码中bootable/recovery的编译产物。updater-script这是我们用类JavaScript语法编写的更新逻辑。一个统一的OTA方案理想情况下希望updater-script是通用的。但现实很骨感我们经常需要针对不同平台进行条件判断和分支处理。例如一个典型的updater-script可能需要处理如下差异# 1. 设备断言确保升级包适用于本设备 getprop(“ro.product.device”) “my_device” || abort(“不支持的设备”); # 2. 分区映射 - 这里开始出现差异 # 假设我们有一个函数来获取平台类型 ifelse(is_mtk(), ( # MTK平台可能将persist分区用于特殊数据 map_partition(“persist”); ), is_unisoc(), ( # 展锐平台可能有独立的metadata分区 map_partition(“metadata”); ), ( # 默认为高通或其他平台 map_partition(“persist”); )); # 3. 刷写镜像 - 动态分区处理逻辑不同 ifelse(is_dynamic_partition(), ( # 动态分区更新 block_image_update(“super”, package_extract_file(“super.transfer.list”), “super.new.dat.br”, “super.patch.dat”); ), ( # 静态分区更新 package_extract_file(“boot.img”, “/dev/block/bootdevice/by-name/boot”); package_extract_file(“system.img”, “/dev/block/bootdevice/by-name/system”); )); # 4. 平台特定的后处理脚本 # 例如MTK可能需要更新DWS数据展锐可能需要处理相机metadata run_program(“/tmp/update_platform_specific.sh”);修改和调试updater-script是OTA开发中的关键环节需要深入理解Recovery模式下的环境限制和可用函数。3. 构建统一OTA框架的核心策略面对这些差异我们的策略不是写三套代码而是设计一个“核心通用框架 平台适配层”的架构。3.1 抽象硬件抽象层首先我们需要定义一个硬件抽象层接口将平台相关的操作封装起来。这个接口可以包含以下功能get_platform_type(): 返回当前运行平台QCOM, MTK, UNISOC。get_partition_path(const char* name): 根据分区名如”boot”, “system”返回该分区在设备上的实际块设备路径如/dev/block/bootdevice/by-name/boot。这是因为不同平台的分区命名前缀可能不同。execute_fastboot_command(const char* cmd): 封装fastboot命令的执行。对于不支持标准fastboot的命令如某些展锐平台可以在适配层内转换为其他工具调用。update_platform_specific_data(): 执行平台特定的数据更新如MTK的DWS、展锐的相机metadata。get_boot_slot()/set_active_slot(int slot): 对于A/B系统抽象槽位操作。在Android系统中这个HAL层可以实现在一个独立的本地库如libotaplatform.so中由updater-binary或Recovery中的服务调用。3.2 统一升级包制作流程升级包的制作端服务器或编译服务器也需要统一。我们可以基于AOSP的ota_from_target_files工具链但为其注入平台感知能力。输入统一要求每个平台的固件编译产出物都遵循相同的目录结构包含IMAGES/,META/等即使底层内容不同。构建系统扩展在设备的BoardConfig.mk或类似配置文件中定义平台类型和特性。# BoardConfig.mk TARGET_BOARD_PLATFORM : msm8953 # 高通 # TARGET_BOARD_PLATFORM : mt6765 # MTK # TARGET_BOARD_PLATFORM : ums512 # 展锐 # 自定义变量 OTA_PLATFORM_TYPE : qcom # OTA_PLATFORM_TYPE : mtk # OTA_PLATFORM_TYPE : unisoc HAS_DYNAMIC_PARTITIONS : true HAS_AB_OTA : true生成差异化脚本在生成OTA包时构建系统根据OTA_PLATFORM_TYPE等变量选择或生成对应的updater-script模板片段并将其整合到最终的升级脚本中。同时将平台特定的二进制工具如处理DWS或metadata的小工具打包进OTA包的/tmp目录。安全签名无论哪个平台都必须对完整的OTA包进行签名如RSA SHA256。私钥由公司严格保管公钥内置在设备的Bootloader或系统信任库中。这是防止恶意升级的最后防线。3.3 Recovery模式的定制与统一Recovery是执行OTA更新的主战场。AOSP提供了标准的Recovery UI和更新逻辑但我们需要对其进行定制。统一UI/UX无论底层平台如何用户看到的Recovery界面、升级进度条、错误提示语都应该保持一致。这需要修改bootable/recovery的UI资源。集成平台HAL在Recovery的源码中调用我们实现的libotaplatform.so中的函数来执行分区操作而不是硬编码路径或命令。增强日志与调试在Recovery中增加详细的日志输出记录每一步操作和平台函数的调用结果这些日志可以通过adb sideload模式或特定的日志分区获取对于排查跨平台问题至关重要。4. 实战一次完整的三平台OTA流程推演假设我们要为设备推送一个版本号为V2.0的安全更新。4.1 服务器端升级包生成代码与配置管理三个平台的代码在各自的Git分支中维护但共享同一套OTA框架代码和updater-script模板。编译分别编译高通、MTK、展锐平台的V2.0固件。编译系统会自动根据BoardConfig.mk中的配置将平台特定的二进制工具和配置如MTK的DWS更新脚本、展锐的metadata工具打包进target_files.zip。生成OTA包调用统一的包装脚本传入平台标识和target_files.zip。# 伪代码示例 ./build_ota_package.sh --platformqcom --target-filetarget_files_qcom.zip --outputota_qcom_v2.0.zip ./build_ota_package.sh --platformmtk --target-filetarget_files_mtk.zip --outputota_mtk_v2.0.zip ./build_ota_package.sh --platformunisoc --target-filetarget_files_unisoc.zip --outputota_unisoc_v2.0.zip脚本内部会调用ota_from_target_files。根据--platform参数插入对应的updater-script逻辑片段。对生成的ota_*.zip进行公司统一的密钥签名。发布将三个签名后的OTA包上传到OTA服务器。服务器根据设备上报的型号和平台信息通常通过ro.product.board或ro.boot.platformtype等属性区分向设备推送对应的升级包。4.2 设备端升级执行检测与下载设备上的OTA客户端一个系统应用或服务定期查询服务器发现V2.0更新并下载适用于自己平台的升级包。预验证客户端在用户数据分区验证升级包的签名。验证通过后将升级包复制到/cache或/data分区并设置一个标志通知系统需要更新。进入Recovery设备重启进入Recovery模式。标准的Recovery会读取misc分区中的指令找到OTA包的位置。执行升级脚本Recovery加载并运行OTA包中的updater-binary。updater-binary读取updater-script并逐行执行。脚本首先调用HAL函数get_platform_type()确认当前是MTK平台。脚本执行通用的分区检查、空间验证。当需要更新boot分区时脚本调用get_partition_path(“boot”)HAL层返回MTK平台正确的路径如/dev/block/platform/bootdevice/by-name/boot。脚本执行动态分区更新逻辑block_image_update(“super”, …)。这个函数是AOSP提供的但其底层会通过HAL与MTK的存储驱动交互。更新完成后脚本调用run_program(“/tmp/update_mtk_specific.sh”)这个脚本是打包时放进去的它可能会处理DWS文件的更新。脚本最后调用HAL的set_active_slot()如果是A/B系统并完成收尾工作。重启与验证设备重启到新系统。系统启动后OTA客户端向服务器上报升级成功状态。如果启动失败A/B机制会自动回滚到上一个槽位静态分区设备则可能进入Recovery显示错误。5. 避坑指南与经验总结在实现这个三平台统一OTA框架的过程中我们踩了无数坑也积累了一些宝贵的经验。坑一fastboot命令的“方言”。高通的fastboot getvar all输出非常详细MTK和展锐的可能省略了一些信息。在编写用于验证分区状态的脚本时不能依赖某个特定的变量。解决方案是统一通过读取/proc/cmdline或/dev/block下的by-name/by-num链接来获取核心分区信息fastboot命令仅作为辅助和刷写工具。坑二动态分区super的布局差异。虽然都叫动态分区但各平台对super分区内system、vendor等逻辑分区的大小和排列顺序可能有默认偏好。在从静态分区迁移到动态分区时必须仔细核对各平台编译生成的super.img的布局信息使用lpdump工具确保OTA更新前后布局一致否则会导致更新后无法启动。坑三平台特定数据的备份与恢复。像MTK的DWS、展锐的相机OTP/Calibration数据、高通的某些射频校准数据NV项通常存放在persist、misc或独立的分区。OTA更新system或vendor分区时这些数据分区必须被排除在擦除列表之外。更好的做法是在updater-script中在更新前将这些关键数据备份到临时位置如/tmp更新后再写回。这需要与硬件和驱动团队紧密合作明确哪些数据是“黄金数据”。坑四Recovery模式下的驱动兼容性。Recovery是一个极简的Linux环境。有些平台为了缩小Recovery体积会裁剪掉部分驱动比如某些显示驱动或触摸屏驱动。这可能导致Recovery UI显示异常或无法操作。必须确保Recovery内核配置包含了所有必要的驱动特别是对于带屏设备。测试时不仅要测试升级功能还要测试Recovery界面本身是否正常。经验建立完善的自动化测试桩。我们搭建了一套自动化测试框架可以模拟不同平台设备的行为。在生成OTA包后会自动在虚拟机或真机测试桩上执行“千次刷写”压力测试、断电测试、异常包测试等。对于三平台支持自动化测试是保证质量、提升效率的唯一途径。经验版本号与兼容性管理。我们制定了严格的版本命名规则其中就包含了平台标识符。例如HW01_QCOM_V2.0.0、HW01_MTK_V2.0.0。在OTA服务器端数据库会严格记录每个设备ID对应的硬件版本和平台确保不会推错包。同时对于Bootloader和底层固件如Modem、DSP的更新我们采取极其保守的策略通常不通过用户OTA推送而是通过线下工具升级因为风险太高。最终我们成功部署了这套框架。对于上层应用和用户而言他们感知到的只是一个流畅、统一的升级体验。而对于我们开发团队来说虽然前期投入了大量精力进行抽象和适配但带来的收益是巨大的新平台的接入时间从数月缩短到数周OTA功能的回归测试用例可以高度复用版本发布和问题排查的效率得到了质的提升。在嵌入式开发日益复杂的今天这种面向差异化的设计思维和架构能力正变得越来越重要。