RK3399 Android 10 系统OTA升级失败问题

为了远程升级现场机器的 Android10 系统固件,需要支持验证系统OTA升级功能,但是验证发现OTA升级会失败,这里记录下分析过程。

编译OTA包

source build/envsetup.sh
lunch

# compile android system
make installclean -j16
make -j16

# compile ota
make otapackage -j16

# optional
# ./mkimage.sh ota

说明:mkimage.sh ota 不是必须的,而且可以说是有点重复的,可以看到下面这段脚本。

if [ $TARGET == $BOOT_OTA ]
 then
 if [ "$PRODUCT_USE_DYNAMIC_PARTITIONS" = "true" ]; then
     make installclean && make -j4 && make dist -j4
     cp -rf  $OUT/obj/PACKAGING/super.img_intermediates/super.img  $IMAGE_PATH/
 fi
 echo -n "create system.img boot.img oem.img vendor.img dtbo.img vbmeta.img for OTA..."
 cp -rf  $OUT/obj/PACKAGING/target_files_intermediates/*-target_files*/IMAGES/*.img  $IMAGE_PATH/
 rm -rf  $IMAGE_PATH/cache.img
 rm -rf  $IMAGE_PATH/recovery-two-step.img
 if [ "$PRODUCT_USE_DYNAMIC_PARTITIONS" = "true" ]; then
     rm -rf  $IMAGE_PATH/super_empty.img
 fi
 echo "done."
 fi

mkimage.sh 会执行 make installclean && make -j4 && make dist -j4 ,相当于重新编译安卓系统和发布内容(也包含ota包)。

OTA 升级失败

将编译好的ota包(位于 out/target/product/PRODUCT_NAME/*ota*.zip), 放置到系统 /sdcard 目录,重启触发update service,弹框提示升级,点击升级后,发现系统升级失败。甚至都没有进入升级动画。

于是接上串口,查看升级日志,发现显示以下异常。

[    3.393649] healthd: BatteryTemperaturePath I:charge_status UNKNOWN, chanrged 1, status SUCCESS, capoacity 1
battery capacity is not enough for installing pafckage: 15% needed

从日志可以看到,recovery阶段检查当前电量为1,低于15%,导致升级失败。

recovery 源码分析

查找recovery相关代码如下:

  • bootable/recovery/recovery.cpp
     if (retry_count == 0 && !is_battery_ok(&required_battery_level)) {
       ui->Print("battery capacity is not enough for installing package: %d%% needed\n",
                 required_battery_level);
       // Log the error code to last_install when installation skips due to
       // low battery.
       log_failure_code(kLowBattery, update_package);
       status = INSTALL_SKIPPED;
     }

recovery 会判断电量信息capacity ,如果电量低于15%,则会跳过升级。

由于我们的系统不是直接通过内核上报电量,而是需要系统启动后通过应用层串口通信获取电量,所以使用了一个fake_battery 驱动,该驱动默认电量为1,导致recovery阶段直接跳过OTA升级。

  • is_battery_ok
     Result res = Result::UNKNOWN;
     int32_t capacity = INT32_MIN;
     if (health != nullptr) {
       health
           ->getCapacity([&res, &capacity](auto out_res, auto out_capacity) {
             res = out_res;
             capacity = out_capacity;
           })
           .isOk();  // should not have transport error
     }

     LOG(INFO) << "charge_status " << toString(charge_status) << ", charged " << charged
               << ", status " << toString(res) << ", capacity " << capacity;
     // At startup, the battery drivers in devices like N5X/N6P take some time to load
     // the battery profile. Before the load finishes, it reports value 50 as a fake
     // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected
     // to finish loading the battery profile earlier than 10 seconds after kernel startup.
     if (res == Result::SUCCESS && capacity == 50) {
       if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) {
         sleep(1);
         wait_second++;
         continue;
       }
     }
     // If we can't read battery percentage, it may be a device without battery. In this
     // situation, use 100 as a fake battery percentage.
     if (res != Result::SUCCESS) {
       capacity = 100;
     }

分析代码可知,当系统可以获取电量时,会以读取到的电量为准,如果获取失败会认为该系统无法获取电量,直接将电量设置为 100.

解决方案

根据源码分析结果,可以考虑以下几种解决方案:

  1. 更新recovery,将电量的判断逻辑去掉
  2. 更新内核驱动,去掉fake_battery功能
  3. 更新内核驱动,fake_battery 默认电量调整为15以上的数值

经验证,第2种方案可行,理论上第3种方案也可以(未验证),但是综合考虑,由于电量上报功能不能去除,只能考虑第一种方案,将recovery的判断逻辑去掉。

--- a/bootable/recovery/recovery.cpp
+++ b/bootable/recovery/recovery.cpp
@@ -721,7 +721,8 @@ static bool is_battery_ok(int* required_battery_level) {
     static constexpr int BATTERY_OK_PERCENTAGE = 20;
     static constexpr int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15;
     *required_battery_level = charged ? BATTERY_WITH_CHARGER_OK_PERCENTAGE : BATTERY_OK_PERCENTAGE;
-    return capacity >= *required_battery_level;
+    // return capacity >= *required_battery_level;
+    return true;
   }
 }

远程更新 recovery

将更新的recovery镜像远程到设备,然后通过指令刷新镜像。之后就可以通过ota包进行升级。

dd if=recovery.img of=/dev/block/by-name/recovery bs=1024k

注意事项

  • OTA升级包的制作时间需要比待升级的系统固件编译时间更新,否则也会升级失败。