OTA升级流程初稿

一 、OTA应用升级API

RecoverySystem.installPackage(this, new File("/data/ota_package/update.zip"));

将OTA升级包的固件传入应用调用的api接口

@RequiresPermission(android.Manifest.permission.RECOVERY)
    public static void installPackage(Context context, File packageFile)
            throws IOException {
        installPackage(context, packageFile, false);
    }

在一层封装后多了一个false的参数

@SystemApi
    @RequiresPermission(android.Manifest.permission.RECOVERY)
    public static void installPackage(Context context, File packageFile, boolean processed)
            throws IOException {
        synchronized (sRequestLock) {
            LOG_FILE.delete();
            UNCRYPT_PACKAGE_FILE.delete();
            
            String filename = packageFile.getCanonicalPath();
 
            if (filename.startsWith("/data/")) {
                if (processed) {
                } else {
                    FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);  \\uncrypt_file
                    try {
                        uncryptFile.write(filename + "\n");
                    } finally {
                        uncryptFile.close();
                    }
                    
                    if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
                            || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
                        Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
                    }

                    BLOCK_MAP_FILE.delete();
                }
                filename = "@/cache/recovery/block.map";
            }

            final String filenameArg = "--update_package=" + filename + "\n";
            final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";

            String command = filenameArg + localeArg;

            RecoverySystem rs = (RecoverySystem) context.getSystemService(
                    Context.RECOVERY_SERVICE);
                    
            if (!rs.setupBcb(command)) {
                throw new IOException("Setup BCB failed");
            }

            PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);     \\获取权限
            String reason = PowerManager.REBOOT_RECOVERY_UPDATE;

            pm.reboot(reason);     \\执行uncrypt
        }
    }

从上面看到api传入false参数,意味着系统未作处理,从而创建了uncrypt_file文件,然后将–update_package等参数传递到Bcb(Bootloader Control Block)中,然后执行 reboot,在reboot过程中会执行uncrypt创建block.map文件,并且进入到Bcb中。

上面的流程实际上做的事情和我之前总结的Android adb升级OTA是一样的流程。

android 内核实现otg android ota_System


Main System 如何进入Recovery 模式:当我们在Main System 使用update.zip 包进行升级时,

系统会重启并进入recovery 模式。在系统重启前,我们可以看到Main System 定会向recovery 域写入

boot-recovery(粉红色线),用来告知bootloader 重启后进入Rcovery 模式。这一步是必须的,至于

Main System 是否会向recovery 域写入值我们在源码中不能肯定这一点。即便如此,重启进入Recovery

模式后,Bootloader 会从/cache/recovery/command 中读取值并放入到BCB 的recovery 域。而Main

System 在重启之前肯定会向/cache/recovery/command 中写入Recovery 将要进行的操作命令。

二、Recovery服务流程

recovery源代码在./bootable/recovery中,该文件会被编译为recovery可执行文件放在/sbin/下,并在init.rc中注册这个服务

service recovery /sbin/recovery
    console
    seclabel u:r:recovery:s0

recovery服务的入口在recovery.cpp中的main中,main函数的开始对log进行了初始化

android::base::InitLogging(argv, &UiLogger);
    __android_log_pmsg_file_read(
        LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
        logbasename, &doRotate);
    // Take action to refresh pmsg contents
    __android_log_pmsg_file_read(
        LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
        logrotate, &doRotate);

然后初始化了串口用于调试

char *SerialName = getSerial();
	//ui->Print("SerialName: %s\n", SerialName);
    freopen(SerialName, "a", stdout); setbuf(stdout, NULL);
    freopen(SerialName, "a", stderr); setbuf(stderr, NULL);
    free(SerialName);

在main函数中调用 load_volume_table(),读取/fstab.rk30board文件内容,并填充fstab结构体,但是并没有执行挂载操作;

load_volume_table();
45 void load_volume_table()  
 46 {  
 47     int i;
 48     int ret;
 49    
 50     fstab = fs_mgr_read_fstab_default(); 
 51     if (!fstab) {         
 52         LOG(ERROR) << "failed to read default fstab";
 53         return;
 54     }
 55                           
 56     ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk"); 
 57     if (ret < 0 ) {       
 58         LOG(ERROR) << "failed to add /tmp entry to fstab";
 59         fs_mgr_free_fstab(fstab);      
 60         fstab = NULL;
 61         return;
 62     }
 63 
 64     printf("recovery filesystem table\n");
 65     printf("=========================\n");
 66     for (i = 0; i < fstab->num_entries; ++i) { 
 67         Volume* v = &fstab->recs[i];   
 68         printf("  %d %s %s %s %lld\n", i, v->mount_point, v->fs_type, 
 69                v->blk_device, v->length);     
 70     }
 71     printf("\n");
 72 }

接着是挂载cache分区

has_cache = volume_for_path(CACHE_ROOT) != nullptr;

然后是读取参数并且设置相应的标志位。

std::vector<std::string> args;
    if(rksdboot.isSDboot() || rksdboot.isUSBboot()){
        args = rksdboot.get_args(argc, argv);
    }else{
        args = get_args(argc, argv);
    }

    std::vector<char*> args_to_parse(args.size());
    std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),
                   [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
                              &option_index)) != -1) {
        switch (arg) {
        case 'n': android::base::ParseInt(optarg, &retry_count, 0); break;
        case 'u': update_package = optarg; break;
        case 'w': should_wipe_data = true; break;
        case 'c': should_wipe_cache = true; break;
        case 't': show_text = true; break;
        case 's': sideload = true; break;
        case 'a': sideload = true; sideload_auto_reboot = true; break;
        case 'x': just_exit = true; break;
        case 'l': locale = optarg; break;
        case 'p': shutdown_after = true; break;
        case 'r': reason = optarg; break;
        case 'e': security_update = true; break;
        case 'w'+'a': { should_wipe_all = true; should_wipe_data = true; should_wipe_cache = true;} break;
        case 'f': factory_mode = optarg; break;
        case 'p'+'t': factory_mode = optarg; break;
        case 'r'+'p': { resize_partition = 1; printf("resize_partition = 1!\n");} break;
        case 0: {
            std::string option = OPTIONS[option_index].name;
            if (option == "wipe_ab") {
                should_wipe_ab = true;
            } else if (option == "wipe_package_size") {
                android::base::ParseUint(optarg, &wipe_package_size);
            } else if (option == "prompt_and_wipe_data") {
                should_prompt_and_wipe_data = true;
            }
            break;
        }
        case 'f'+'w': //fw_update
            if((optarg)&&(!sdboot_update_package)){
                sdboot_update_package = strdup(optarg);
            }
            break;
        case '?':
            LOG(ERROR) << "Invalid command argument";
            continue;
        }
    }

显示UI界面fb显示使用的库是minui,该库的方法还是比较好用的,网上也能查到相应的方法说明。

Device* device = make_device();
    if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
        printf("Quiescent recovery mode.\n");
        ui = new StubRecoveryUI();
    } else {
        ui = device->GetUI();

        if (!ui->Init(locale)) {
            printf("Failed to initialize UI, use stub UI instead.\n");
            ui = new StubRecoveryUI();
        }
    }

    // Set background string to "installing security update" for security update,
    // otherwise set it to "installing system update".
    ui->SetSystemUpdateText(security_update);

    int st_cur, st_max;
    if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
        ui->SetStage(st_cur, st_max);
    }

    ui->SetBackground(RecoveryUI::NONE);
    if (show_text) ui->ShowText(true);

    sehandle = selinux_android_file_context_handle();
    selinux_android_set_sehandle(sehandle);
    if (!sehandle) {
        ui->Print("Warning: No file_contexts\n");
    }

    device->StartRecovery();

挂载matedata及uniubidata分区

SureMetadataMount();
	SureuniubidataMount();

根据之前获取参数的标志位进行格式化及升级等操作,install_package是升级的代码。

if (update_package != NULL) {
        strcpy(updatepath, update_package);
        // It's not entirely true that we will modify the flash. But we want
        // to log the update attempt since update_package is non-NULL.
        modified_flash = true;

        if (!is_battery_ok()) {
            ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
                      BATTERY_OK_PERCENTAGE);
            // Log the error code to last_install when installation skips due to
            // low battery.
            log_failure_code(kLowBattery, update_package);
            status = INSTALL_SKIPPED;
        } else if (bootreason_in_blacklist()) {
            // Skip update-on-reboot when bootreason is kernel_panic or similar
            ui->Print("bootreason is in the blacklist; skip OTA installation\n");
            log_failure_code(kBootreasonInBlacklist, update_package);
            status = INSTALL_SKIPPED;
        } else {
            const char *reallyPath = check_media_package(update_package);
            if(reallyPath == NULL)
                reallyPath = update_package;
            status = install_package(reallyPath, &should_wipe_cache,
                                     TEMPORARY_INSTALL_FILE, true, retry_count);
            if (status == INSTALL_SUCCESS && should_wipe_cache) {
                wipe_cache(false, device);
            }
            if (status != INSTALL_SUCCESS) {
                ui->Print("Installation aborted.\n");
                // When I/O error happens, reboot and retry installation RETRY_LIMIT
                // times before we abandon this OTA update.
                if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
                    copy_logs();
                    set_retry_bootloader_message(retry_count, args);
                    // Print retry count on screen.
                    ui->Print("Retry attempt %d\n", retry_count);

                    // Reboot and retry the update
                    if (!reboot("reboot,recovery")) {
                        ui->Print("Reboot failed\n");
                    } else {
                        while (true) {
                            pause();
                        }
                    }
                }
                // If this is an eng or userdebug build, then automatically
                // turn the text display on if the script fails so the error
                // message is visible.
                if (is_ro_debuggable()) {
                    ui->ShowText(true);
                }
            }else if(status == INSTALL_SUCCESS){
                bAutoUpdateComplete = true;
            }
        }
    } else if (sdboot_update_package != NULL) {
        printf("bSDBoot = %d, sdboot_update_package=%s\n", rksdboot.isSDboot(), sdboot_update_package);
        status = rksdboot.do_rk_mode_update(sdboot_update_package);
    } else if (should_wipe_data || resize_partition) {
        if (resize_partition !=1) {
            if (!wipe_data(device)) {
                status = INSTALL_ERROR;
            }
        } else {
            printf("resize /data \n");
            ui->Print("resize /data \n");
            Volume* v11 = volume_for_path("/data");
            if(rk_check_and_resizefs(v11->blk_device)) {
                ui->Print("check and resize /data failed!\n");
                status = INSTALL_ERROR;
            }
        }
        if (should_wipe_all) {
            printf("begin to wipe frp partion!\n");
            int ret;
            ret = format_volume("/frp");
            if(ret<0){
               printf("wiping frp failed!\n");
            } else {
                printf("wiping frp success!\n");
            }
        }
    } else if (factory_mode != NULL){
        status = rksdboot.do_rk_factory_mode();
		printf("do_factory_mode status=%d factory_mode=%s \n", status, factory_mode);
		exit_from_factory = 1;
    } else if (should_prompt_and_wipe_data) {
        ui->ShowText(true);
        ui->SetBackground(RecoveryUI::ERROR);
        if (!prompt_and_wipe_data(device)) {
            status = INSTALL_ERROR;
        }
        ui->ShowText(false);
    } else if (should_wipe_cache) {
        if (!wipe_cache(false, device)) {
            status = INSTALL_ERROR;
        }
    } else if (should_wipe_ab) {
        if (!wipe_ab_device(wipe_package_size)) {
            status = INSTALL_ERROR;
        }
    } else if (sideload) {
        // 'adb reboot sideload' acts the same as user presses key combinations
        // to enter the sideload mode. When 'sideload-auto-reboot' is used, text
        // display will NOT be turned on by default. And it will reboot after
        // sideload finishes even if there are errors. Unless one turns on the
        // text display during the installation. This is to enable automated
        // testing.
        if (!sideload_auto_reboot) {
            ui->ShowText(true);
        }
        status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE);
        if (status == INSTALL_SUCCESS && should_wipe_cache) {
            if (!wipe_cache(false, device)) {
                status = INSTALL_ERROR;
            }
        }
        ui->Print("\nInstall from ADB complete (status: %d).\n", status);
        if (sideload_auto_reboot) {
            ui->Print("Rebooting automatically.\n");
        }
    } else if (!just_exit) {
      // If this is an eng or userdebug build, automatically turn on the text display if no command
      // is specified. Note that this should be called before setting the background to avoid
      // flickering the background image.

	  //try to do sdcard boot
	  if (try_do_sdcard_boot(&status)){
	  	printf("try_do_sdcard_boot is actually do sdupdate status=%d \n", status);
	  }
	  else
	  {
	      if (is_ro_debuggable()) {
	        ui->ShowText(true);
	      }
	      status = INSTALL_NONE;  // No command specified
	      ui->SetBackground(RecoveryUI::NO_COMMAND);
	  }
    }

接着分析install_package

int install_package(const std::string& path, bool* wipe_cache, const std::string& install_file,
                    bool needs_mount, int retry_count) {
	...........
  if (setup_install_mounts() != 0) {
    LOG(ERROR) << "failed to set up expected mounts for install; aborting";
    result = INSTALL_ERROR;
  } else {
    result = really_install_package(path, wipe_cache, needs_mount, &log_buffer, retry_count,
                                    &max_temperature);
  }
	.............
  }

really_install_package做了以下几件事
1、若是需要格式化,则格式化分区(这部分对自己创建的但烧录包中没有相应img的分区有用)

if (needs_mount) {
    if (path[0] == '@') {
      ensure_path_mounted(path.substr(1).c_str());
    } else {
      ensure_path_mounted(path.c_str());
    }
  }

获取升级包地址

MemMapping map;
  if (!map.MapFile(path)) {
    LOG(ERROR) << "failed to map file";
    return INSTALL_CORRUPT;
  }

验证升级包签名

// Verify package.
  if (!verify_package(map.addr, map.length)) {
    log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
    return INSTALL_CORRUPT;
  }

打开升级包

// Try to open the package.
  ZipArchiveHandle zip;
  int err = OpenArchiveFromMemory(map.addr, map.length, path.c_str(), &zip);
  if (err != 0) {
    LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err);
    log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));

    CloseArchive(zip);
    return INSTALL_CORRUPT;
  }

验证软件包的兼容性。

// Additionally verify the compatibility of the package.
  if (!verify_package_compatibility(zip)) {
    log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
    CloseArchive(zip);
    return INSTALL_CORRUPT;
  }

执行升级脚本文件,开始升级

ui->SetEnableReboot(false);
  int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature);
  ui->SetEnableReboot(true);
  ui->Print("\n");

升级结束后重启

if(exit_from_factory)
	{
		exit_factory_mode_wipe_cmd_in_bcb();
	}

    switch (after) {
        case Device::SHUTDOWN:
            ui->Print("Shutting down...\n");
            android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
            break;

        case Device::REBOOT_BOOTLOADER:
            ui->Print("Rebooting to bootloader...\n");
            android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
            break;

        default:
            ui->Print("Rebooting...\n");
            reboot("reboot,");
            break;
    }
    while (true) {
        pause();
    }

三、RecoveryUI 定制

RecoveryUI是指进入recovery模式时的指引画面类似这种

android 内核实现otg android ota_android 内核实现otg_02

Device* device = make_device();
ui = device->GetUI();
if (!ui->Init(locale)) {
	printf("Failed to initialize UI, use stub UI instead.\n");
	ui = new StubRecoveryUI();
}

这个ui是从device/rockchip/common/recovery里的代码获取到的定制UI

#include <linux/input.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

#include "common.h"
#include "device.h"
#include "screen_ui.h"

const char* HEADERS[] = { "Volume up/down to move highlight;",
                         "power button to select.",
                         "",
                         NULL };

const char* ITEMS[] ={ "reboot system now",
                       "apply update from ADB",
                       "apply update from external storage",
                       "update rkimage from external storage",
                       "apply update from cache",
                       "wipe data/factory reset",
                       "wipe cache partition",
                       "recovery system from backup",
                       NULL };

class RkUI : public ScreenRecoveryUI {
  public:
	RkUI() :
        consecutive_power_keys(0) {
    }

    virtual KeyAction CheckKey(int key) {
        if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) {
            return TOGGLE;
        }
        if (key == KEY_POWER) {
            ++consecutive_power_keys;
            if (consecutive_power_keys >= 7) {
                return REBOOT;
            }
        } else {
            consecutive_power_keys = 0;
        }
        return ENQUEUE;
    }

  private:
    int consecutive_power_keys;
};


class RkDevice : public Device {
  public:
	RkDevice() :
        ui(new RkUI) {
    }

    RecoveryUI* GetUI() { return ui; }

    int HandleMenuKey(int key_code, int visible) {
        if (visible) {
            switch (key_code) {
              case KEY_DOWN:
              case KEY_VOLUMEDOWN:
                return kHighlightDown;

              case KEY_UP:
              case KEY_VOLUMEUP:
                return kHighlightUp;

              case KEY_ENTER:
              case KEY_POWER:
                return kInvokeItem;
            }
        }

        return kNoAction;
    }

    BuiltinAction InvokeMenuItem(int menu_position) {
        switch (menu_position) {
          case 0: return REBOOT;
          case 1: return APPLY_ADB_SIDELOAD;
          case 2: return APPLY_EXT;
          //case 3: return APPLY_INT_RKIMG;
          case 4: return APPLY_CACHE;
          case 5: return WIPE_DATA;
          case 6: return WIPE_CACHE;
          //case 7: return RECOVER_SYSTEM;
          default: return NO_ACTION;
        }
    }

    const char* const* GetMenuHeaders() { return HEADERS; }
    const char* const* GetMenuItems() { return ITEMS; }

  private:
    RecoveryUI* ui;
};

Device* make_device() {
    return new RkDevice;
}

这里定义了一个继承了Device类的RkDevice类,以及实现了GetUI、InvokeMenuItem、HandleMenuKey等方法的实例。这些方法皆可自定义实现。

四、recovery logo定制

recovery logo是指升级过程中的动画界面,原生的系统是一个安卓小机器人的动图,动图的制作可以参考之前的博文Android recovery图片资源制作 做好的图片资源放在此处bootable\recovery\res-mdpi\images

五、minui库的方法说明

int gr_init(void);             /* 初始化图形显示,主要是打开设备、分配内存、初始化一些参数 */  
void gr_exit(void);            /* 注销图形显示,关闭设备并释放内存 */  

int gr_fb_width(void);         /* 获取屏幕的宽度 */  
int gr_fb_height(void);        /* 获取屏幕的高度 */  
gr_pixel *gr_fb_data(void);    /* 获取显示数据缓存的地址 */  
void gr_flip(void);            /* 刷新显示内容 */  
void gr_fb_blank(bool blank);  /* 清屏 */  

void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);  /* 设置字体颜色 */  
void gr_fill(int x, int y, int w, int h);  /* 填充矩形区域,参数分别代表起始坐标、矩形区域大小 */  
int gr_text(int x, int y, const char *s);  /* 显示字符串 */  
int gr_measure(const char *s);             /* 获取字符串在默认字库中占用的像素长度 */  
void gr_font_size(int *x, int *y);         /* 获取当前字库一个字符所占的长宽 */  

void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy);  /* 填充由source指定的图片 */  
unsigned int gr_get_width(gr_surface surface);   /* 获取图片宽度 */  
unsigned int gr_get_height(gr_surface surface);  /* 获取图片高度 */  
/* 根据图片创建显示资源数据,name为图片在mk文件指定的相对路径 */  
int res_create_surface(const char* name, gr_surface* pSurface);  
void res_free_surface(gr_surface surface);       /* 释放资源数据 */