main 函数

从main入口函数分析recovery的主要源码:

输出重定向


1
2
3
4
5
6
redirect_stdio(TEMPORARY_LOG_FILE);
//redirect log to serial output
#ifdef LogToSerial
freopen("/dev/ttyFIQ0", "a", stdout); setbuf(stdout, NULL);
freopen("/dev/ttyFIQ0", "a", stderr); setbuf(stderr, NULL);
#endif

这部分代码很容易理解,主要作用是输出log到/tem/recovery.log文件中

执行adb sideload分支


1
2
3
4
if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
adb_main(0, DEFAULT_ADB_PORT);
return 0;
}

判断命令行参数是否为–adbd,并执行adb_main函数,这部分代码在后续adb_install.cpp中分析;

填充fstab结构体


在main函数中调用 load_volume_table(),读取/etc/recovery.emmc.fstab文件内容,并填充fstab结构体,但是并没有执行挂载操作:
load_volume_table函数在roots.cpp文件中,也是很容易理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void load_volume_table()
{
...
int emmcState = getEmmcState();//判断是否为emmc设备
if(emmcState) {
fstab = fs_mgr_read_fstab("/etc/recovery.emmc.fstab");
}else {
fstab = fs_mgr_read_fstab("/etc/recovery.fstab");
}
...
//读取文件中每个条目内容,填充fstab结构体
ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
...
//日志打印fstable信息
printf("recovery filesystem table\n");
printf("=========================\n");
for (i = 0; i < fstab->num_entries; ++i) {
Volume* v = &fstab->recs[i];
printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type,
v->blk_device, v->length);
}
printf("\n");
}

读取控制参数
recovery 和 bootloader 必须通过内存的一个特定分区,才能进行相互的通信,这个分区一般是/misc;
对应的信息数据结构体为bootloader_message;
参照源码中bootloader_message 的注释

1
2
3
4
5
6
7
struct bootloader_message {
char command[32];//bootloader 启动时读取改数据,决定是否进入recovery模式
char status[32];//由bootloader进行更新,标识升级的结果;
char recovery[768];//由Android系统进行写入,recovery从中读取信息;
char stage[32];
char reserved[224];
};

recovery 根据命令行参数,再从/misc分区中解析出对应的参数,进行后续的操作,具体的调用函数为get_args(&argc, &argv);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static void
get_args(int *argc, char ***argv) {
struct bootloader_message boot;//参数结构体
memset(&boot, 0, sizeof(boot));
get_bootloader_message(&boot); // 具体的读取信息的函数,可能为空的情况
stage = strndup(boot.stage, sizeof(boot.stage));
...
// 如果上述情况为空,则从/cache/recovery/command获取参数,其中COMMAND_FILE=/cache/recovery/command
if (*argc <= 1) {
FILE *fp = fopen_path(COMMAND_FILE, "r");
if (fp != NULL) {
char *token;
char *argv0 = (*argv)[0];
*argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
(*argv)[0] = argv0; // use the same program name
char buf[MAX_ARG_LENGTH];
for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
if (!fgets(buf, sizeof(buf), fp)) break;
token = strtok(buf, "\r\n");
if (token != NULL) {
(*argv)[*argc] = strdup(token); // Strip newline.
} else {
--*argc;
}
}
check_and_fclose(fp, COMMAND_FILE);
LOGI("Got arguments from %s\n", COMMAND_FILE);
}
}
//把从/cache/recovery/command获取参数重新写回到/misc分区
// --> write the arguments we have back into the bootloader control block
// always boot into recovery after this (until finish_recovery() is called)
strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
int i;
for (i = 1; i < *argc; ++i) {
strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
strlcat(boot.recovery, "\n", sizeof(boot.recovery));
}
set_bootloader_message(&boot);
}

解析命令行参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
switch (arg) {
case 'f': factory_mode = optarg; bFactoryMode = true; break;
case 'i': send_intent = optarg; break;
case 'u': update_package = optarg; break;
case 'w': should_wipe_data = true; break;
case 'k': update_rkimage = optarg;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 'g': {
if (stage == NULL || *stage == '\0') {
char buffer[20] = "1/";
strncat(buffer, optarg, sizeof(buffer)-3);
stage = strdup(buffer);
}
break;
}
case 'f'+'w': //fw_update
if((optarg)&&(!sdboot_update_package)){
sdboot_update_package = strdup(optarg);
}
break;
case 'd': //demo_copy
if((optarg)&&(! demo_copy_path)){
demo_copy_path = strdup(optarg);
}
break;
case 'p': shutdown_after = true; break;
case 'r': reason = optarg; break;
case 'w'+'a': { should_wipe_all = should_wipe_data = should_wipe_cache = true;show_text = true;} break;
case '?':
LOGE("Invalid command argument\n");
continue;
}
}

这部分代码很简单,就是通过getopt_long进行命令行参数的解析并赋值;

显示界面和功能选项

接下来就是创建device,显示对应UI界面和功能选项;

1
2
3
4
5
6
7
8
Device* device = make_device();//可以自己实现一个设备
ui = device->GetUI();
gCurrentUI = ui;//赋值ui界面
ui->SetLocale(locale);//获取归属地信息
ui->Init();//初始化,可以重载,在init中实现相应功能
ui->SetStage(st_cur, st_max);
ui->SetBackground(RecoveryUI::NONE);

进行分区挂载操作
ensure_path_mounted

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
int ensure_path_mounted(const char* path) {
...
Volume* v = volume_for_path(path);//根据路径名获取分区信息
...
int result;
result = scan_mounted_volumes();
const MountedVolume* mv =
find_mounted_volume_by_mount_point(v->mount_point);//根据挂载点,获取已挂载分区的信息,如果不为空,说明已经成功挂载
if (mv) {
// volume is already mounted
return 0;
}
result = mkdir(v->mount_point, 0755); // 创建对应目录,确保目录存在,也有可能目录已经存在
if (result!=0)
{
printf("failed to create %s dir,err=%s!\n",v->mount_point,strerror(errno));
}
// 根据文件系统类型,执行mount操作
if (strcmp(v->fs_type, "yaffs2") == 0) {
// mount an MTD partition as a YAFFS2 filesystem.
mtd_scan_partitions();
const MtdPartition* partition;
partition = mtd_find_partition_by_name(v->blk_device);
if (partition == NULL) {
LOGE("failed to find \"%s\" partition to mount at \"%s\"\n",
v->blk_device, v->mount_point);
return -1;
}
return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0);
} else if (strcmp(v->fs_type, "ext4") == 0 ||
strcmp(v->fs_type, "ext3") == 0) {
result = mount(v->blk_device, v->mount_point, v->fs_type,
MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
if (result == 0) return 0;
LOGE("failed to mount %s %s (%s)\n", v->mount_point, v->blk_device, strerror(errno));
return -1;
} else if (strcmp(v->fs_type, "vfat") == 0) {
result = mount(v->blk_device, v->mount_point, v->fs_type,
MS_NOATIME | MS_NODEV | MS_NODIRATIME, "shortname=mixed,utf8");
if (result == 0) return 0;
LOGW("trying mount %s to ntfs\n", v->blk_device);
result = mount(v->blk_device, v->mount_point, "ntfs",
MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
if (result == 0) return 0;
char *sec_dev = v->fs_options;
if(sec_dev != NULL) {
char *temp = strchr(sec_dev, ',');
if(temp) {
temp[0] = '\0';
}
result = mount(sec_dev, v->mount_point, v->fs_type,
MS_NOATIME | MS_NODEV | MS_NODIRATIME, "shortname=mixed,utf8");
if (result == 0) return 0;
LOGW("trying mount %s to ntfs\n", sec_dev);
result = mount(sec_dev, v->mount_point, "ntfs",
MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
if (result == 0) return 0;
}
LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));
return -1;
}else if (strcmp(v->fs_type, "ntfs") == 0) {
LOGW("trying mount %s to ntfs\n", v->blk_device);
result = mount(v->blk_device, v->mount_point, "ntfs",
MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
if (result == 0) return 0;
LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));
return -1;
}
LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point);
return -1;
}

界面定制

实现Recovery UI

在自己的设备目录下:device/vendor/recovery/recovery_ui.cpp

1
2
3
4
5
6
7
8
#include <linux/input.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include "common.h"
#include "device.h"
#include "screen_ui.h"

实现头部显示和列表项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 };

实现ScreenRecoveryUI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class DeviceUI : public ScreenRecoveryUI {
public:
DeviceUI () :
consecutive_power_keys(0) {
}
//实现自己的识别key类型的功能,可以为不同的输入设备适配recovery功能
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;
};

实现设备类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class MyDevice : public Device {
public:
RkDevice() :
ui(new DeviceUI ) {
}
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 1: return APPLY_EXT;
case 2: return APPLY_INT_RKIMG;
case 3: return APPLY_CACHE;
case 4: return WIPE_DATA;
case 5: return WIPE_CACHE;
case 6: 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 MyDevice ;
}

添加编译实现

主要是覆盖TARGET_RECOVERY_UI_LIB,输出到/out/…./recovery/root目录下:

Android.mk

1
2
3
4
5
6
7
8
9
10
11
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_C_INCLUDES += bootable/recovery
LOCAL_SRC_FILES := recovery_ui.cpp
# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk
LOCAL_MODULE := librecovery_ui_$(TARGET_PRODUCT)
include $(BUILD_STATIC_LIBRARY)

recovery 二次开发

代码结构:

  1. bootable/recovery
    Recovery主程序代码,其中recovery.cpp是入口,rkimage.cpp处理update.img升级流程,updater/install.c处理ota update.zip包升级流程。

  2. build/tools/releasetools/
    Ota升级包编译的python脚本,控制完整包与差异包的生成。

  3. build/tools/drmsigntool/
    如果开启drm,生成ota包时对boot.img进行签名,使用build/target/product/security/private.key,
    保证进行ota升级后drm功能还能正常使用。

  4. build/tools/mkparameter/
    编译ota包时打包可升级的parameter的工具。

  5. build/tools/remkloader/
    编译ota包时打包可升级的loader的工具。

  6. build/target/product/security/
    编译ota包时签名使用的密钥

  7. device/rockchip/rksdk/recovery/
    Recovery菜单及按键定制

  8. device/rockchip/rksdk/loader/
    将需要升级的loader放在该目录下,可以打包到ota升级包中。

  9. device/rockchip/rksdk/parameter/
    将需要升级的parameter放在该目录下,可以打包到ota升级包中。

  10. out/target/product/rk3188/obj/PACKAGING/target_files_intermediates/
    Ota升级包编译生成的素材包,要做差异包必须保存该素材包。

板级配置

Recovery 一些宏配置开关在device/rockchip/rk30sdk/BoardConfig.mk

  1. BOARD_HAS_FLIPPED_SCREEN := true

    针对lcd装反的情况可以强制recovery ui旋转180°

  2. TARGET_RECOVERY_UI_LIB ?= librecovery_ui_rk30sdk

    自定义recovery菜单与按键实现。

  3. TARGET_USERIMAGES_USE_EXT4 ?= true

    Recovery支持ext4 文件系统

  4. RECOVERY_UPDATEIMG_RSA_CHECK ?= false

    Update.img在升级时进行drm签名校验,防止第三方非法固件升级。Update.img必须使用secureboot工具签名,并勾选sign check复选框。

  5. RECOVERY_BOARD_ID ?= false

    一种根据烧录Boardid 串号实现一种固件支持不同硬件不同国家定制的one image方案,该方案支持google 的ota 完整升级和差异升级。

脚本文件命令以及解析

升级脚本文件解析,

try_update_binary

主要命令有

1
2
3
4
5
6
7
progress <frac> <secs>
set_progress <frac>
firmware <"hboot"|"radio"> <filename>
ui_print <string>
wipe_cache
clear_display
enable_reboot