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是一样的流程。
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模式时的指引画面类似这种
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); /* 释放资源数据 */