20、modem与bootloader升级:固件分区升级、fastboot模式交互、基带版本管理

各位做系统升级的同行,今天我们来聊聊两个“硬骨头”——modem和bootloader的升级。说实话,这两块要是没处理好,轻则手机没信号,重则变砖。我自己就吃过这个亏,所以今天把经验掰开揉碎了讲给你听。

20.1 固件分区升级:modem和bootloader住在哪?

先搞清楚一件事:modem和bootloader不是普通的app,它们有自己专属的物理分区。在Android分区表里,通常长这样:

# 典型的AB分区布局(以高通平台为例)
/dev/block/bootdevice/by-name/modem_a
/dev/block/bootdevice/by-name/modem_b
/dev/block/bootdevice/by-name/bluetooth_a
/dev/block/bootdevice/by-name/bluetooth_b
/dev/block/bootdevice/by-name/abl_a       # 应用启动加载器
/dev/block/bootdevice/by-name/abl_b
/dev/block/bootdevice/by-name/xbl_a       # 扩展启动加载器
/dev/block/bootdevice/by-name/xbl_b

嗯,这里要注意:modem分区里装的是基带固件,也叫“modem.img”或“NON-HLOS.bin”。bootloader则更复杂,它通常由多个子分区组成——abl、xbl、tz(TrustZone)等等。我见过不少新手只刷了abl,结果xbl版本不匹配,直接卡死在fastboot界面。

核心原则:modem和bootloader的升级必须遵循“分区对分区”的写入方式,不能像普通系统分区那样直接挂载文件系统。

20.2 fastboot模式交互:你手里的“最后一道防线”

fastboot是bootloader暴露给PC端的协议。说白了,它就是手机在变砖前最后的“救命通道”。我个人习惯在开发阶段把fastboot命令背得滚瓜烂熟,因为关键时刻能救命。

常见的交互流程是这样的:

  1. 进入fastboot模式:按住音量减+电源键,或者通过adb reboot bootloader
  2. 检查设备连接:fastboot devices 确认手机被识别
  3. 刷写分区:fastboot flash modem modem.img
  4. 擦除与重启:fastboot erase modem_a 或 fastboot reboot

我曾经在项目里遇到一个坑:某款手机在fastboot模式下刷写modem分区时,如果电池电量低于30%,刷到一半就会断电。嗯,从那以后我每次刷基带前都会先检查电量——fastboot getvar battery-voltage。

小技巧:用 fastboot getvar all 可以查看当前设备的所有变量,包括分区大小、slot状态、安全标志等。我每次拿到新样机都会先跑一遍这个命令,心里有个底。

20.3 基带版本管理:版本号里藏着什么秘密?

基带版本号不是随便写的。以高通平台为例,版本号通常长这样:

MPSS.HA.1.0.c2-00045-M8974AAAAANAZM-1
# 解析:
# MPSS —— 调制解调器子系统
# HA   —— 硬件架构代号
# 1.0  —— 主版本
# c2   —— 客户定制版本
# 00045 —— 构建序号
# M8974 —— 芯片型号
# AAAAANAZM —— 内部追踪码

你想想看,如果系统版本和基带版本不匹配,会出现什么情况?我遇到过最典型的问题是:升级Android 13后,基带还是Android 12的版本,结果VoLTE功能直接失效。因为新系统里的RIL(无线接口层)和旧基带的协议栈对不上。

避坑指南:我曾经在量产前发现,某批次手机的modem版本号末尾是“-test”,这其实是工程固件。赶紧全部召回重新刷写——测试版固件在商用网络下可能触发基站兼容性问题。

20.4 升级流程实战:从OTA包到分区写入

在Android OTA升级中,modem和bootloader的升级通常放在“firmware”阶段。我画了一张流程图,帮你理清整个逻辑:

Modem & Bootloader 升级流程 OTA包解析 校验签名与版本兼容性 选择非活跃slot 写入 modem / abl / xbl 分区 重启并切换slot 注意: modem分区写入时 不能断电! 版本号必须 与系统匹配

实际代码里,升级脚本(updater-script)会这样写:

# 典型的modem升级片段
ui_print("正在更新基带固件...");
package_extract_file("firmware-update/modem.img", "/dev/block/bootdevice/by-name/modem_b");
ui_print("基带固件写入完成");

# bootloader升级(以abl为例)
ui_print("正在更新启动加载器...");
package_extract_file("firmware-update/abl.elf", "/dev/block/bootdevice/by-name/abl_b");
ui_print("启动加载器更新完成");

这里有个细节:为什么先写modem_b而不是modem_a?因为当前系统运行在slot_a,我们要升级的是非活跃slot。等重启后切换slot,新固件才会生效。我见过有人直接写活跃slot,结果刷到一半系统崩溃,两个slot都坏了——那才叫真正的“双砖”。

20.5 基带版本管理的工程实践

在项目里,我建议用版本号做强制校验。具体做法是在升级脚本里读取当前基带版本,和目标版本做比较:

# 读取当前modem版本(高通平台示例)
getprop("ro.baseband.version");
# 返回类似 "MPSS.HA.1.0.c2-00045"

# 在updater-script中做版本检查
if getprop("ro.baseband.version") == "MPSS.HA.1.0.c2-00045" then
  ui_print("基带版本已是最新,跳过升级");
else
  ui_print("基带版本需要更新");
endif;
避坑指南:我曾经遇到一个case——某款手机在升级modem后,基带版本号显示正确,但实际功能异常。后来发现是modem分区里的“NV items”(非易失性参数)没有同步更新。从那以后,我每次升级modem都会顺带检查NV版本,必要时一起刷写。

嗯,最后说一句:modem和bootloader升级,本质上是在和硬件直接打交道。你写的每一行代码,都可能影响到用户的通信质量。所以,多测试、多验证,别怕麻烦。

个人习惯:每次发布新版本前,我会在10台不同批次的样机上做modem升级测试,然后连续通话2小时、切换基站10次以上。只有全部通过,我才敢签release。

公众号:蓝海资料掘金营,微信deep3321