Rockchip RK3588 - Rockchip Linux Recovery recovery源码分析
----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4
开发板
eMMC
:16GB
LPDDR3
:4GB
显示屏 :15.6
英寸HDMI
接口显示屏
uboot
:2017.09
linux
:4.19
----------------------------------------------------------------------------------------------------------------------------
在《Rockchip RK3588 - Rockchip Linux Recovery updateEngine
源码分析》中我们对updateEngine
源码进行了深入分析,并提到updateEngine
升级命令由两部分组成;
normal
系统下的升级:升级recovery
分区,并修改misc
分区,重启系统;recovery
系统下的升级:系统重启之后,会根据misc
分区存放的字段来判断将要引导的系统是normal
系统还是recovery
系统,这里会进入到recovery
系统,进行剩余分区的升级;
其中normal
系统下的升级是通过updateEngine
可执行程序完成的;
// 1. 在normal系统,updateEngine升级recovery分区;升级完函数就返回了,并不会升级其它分区
main(argc, argv)
MiscUpdate(image_url, partition, save_path)
// 对需要执行的升级命令打标记(这里标记了parameter、recovery升级命令)
RK_ota_set_partition(0x040000)
// 进行parameter、recovery分区升级
RK_ota_start(handle_upgrade_callback, handle_print_callback)
// 往misc偏移16k位置写入recovery信息,这样系统重启后会进入recovery系统执行剩余分区的升级
set_bootloader_message(&msg)
// 2. 触发系统重启
system(" echo b > /proc/sysrq-trigger ")
而recovery
系统下的升级,是通过recovery
可执行程序实现,具体命令如下:
/usr/bin/recovery --update_package=/userdata/update.img
recovery
二进制bin
程序部会根据编译配置调用updateEngine
或者rkupdate
进行升级,本章的目标就是对recovery
源码进行分析,实验基于《Rockchip RK3399
- 从零开始制作recovery
系统》中移植的recovery系统
。
一、recovery
目标分析
1.1 目标recovery
定位到<Rockchip Linux SDK>/external/recovery
目录下的Makefile
文件,找到目标recovery
;
OBJ = recovery.o \
default_recovery_ui.o \
rktools.o \
roots.o \
bootloader.o \
safe_iop.o \
strlcpy.o \
strlcat.o \
rkupdate.o \
sdboot.o \
usbboot.o \
mtdutils/mounts.o \
mtdutils/mtdutils.o \
mtdutils/rk29.o \
minzip/DirUtil.o \
update_engine/log.o
ifdef RecoveryNoUi
OBJ += noui.o # 不走这里
else
OBJ += ui.o\ # 走这里
minzip/Hash.o \
minzip/SysUtil.o \
minzip/Zip.o \
minui/events.o \
minui/graphics.o \
minui/resources.o \
minui/graphics_drm.o
endif
CFLAGS += -I$(PROJECT_DIR) -I/usr/include -I/usr/include/libdrm/ -lc -DUSE_UPDATEENGINE=ON
ifdef RecoveryNoUi
CFLAGS += -lpthread -lbz2 # 不走这里
else
CFLAGS += -lz -lpng -ldrm -lpthread -lcurl -lcrypto -lbz2 # 走这里
endif
$(PROM): $(OBJ)
$(CC) -o $(PROM) $(OBJ) $(CFLAGS)
可以看到目标recovery
是由若干个.o
文件通过aarch64-buildroot-linux-gnu-gcc
编译器链接生成的可执行文件。
而.o
文件实际上是由.c
文件通过aarch64-buildroot-linux-gnu-gcc
编译器编译生成的。
# build in buildroot, it need change work directory
recovery_version:
cd $(PROJECT_DIR)/../../../../../external/recovery && \
COMMIT_HASH=$$(git rev-parse --verify --short HEAD) && \
GIT_COMMIT_TIME=$$(git log -1 --format=%cd --date=format:%y%m%d) && \
GIT_DIRTY=$$(git diff-index --quiet HEAD -- || echo "-dirty") && \
commit_info=-g$${COMMIT_HASH}-$${GIT_COMMIT_TIME}$${GIT_DIRTY} && \
cd $(PROJECT_DIR) && \
echo "#define GIT_COMMIT_INFO $${commit_info}" > recovery_autogenerate.h
%.o: %.cpp
$(CC) -c $< -o $@ $(CFLAGS)
%.o: %.c recovery_version
$(CC) -c $< -o $@ $(CFLAGS)
其中:%.o
表示所有以.o
结尾的文件作为目标文件,%.cpp
表示所有以.c
结尾的文件作为依赖文件。
1.2 编译recovery
在《Rockchip RK3588 - Rockchip Linux SDK Buildroot
文件系统构建》中介绍过buildroot
编译命令;
root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ sudo make -j8
或者使用如下命令单独编译recovery
软件包:
root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ sudo make recovery-dirclean
root@ubuntu:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot$ sudo make recovery
其中recovery
编译日志如下:
>>> recovery develop Syncing from source dir /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/../external/recovery
rsync -au --chmod=u=rwX,go=rX --exclude .svn --exclude .git --exclude .hg --exclude .bzr --exclude CVS /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/../external/recovery/ /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop
>>> recovery develop Configuring
>>> recovery develop Building
PATH="/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/bin:/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin" /usr/bin/make -C /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop CC="/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/bin/aarch64-buildroot-linux-gnu-gcc" CFLAGS="-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Os -g0 -D_FORTIFY_SOURCE=1 -I. -fPIC -lpthread -lcurl -lssl -lcrypto -lbz2 -lpng -ldrm -lz -lm -I/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/aarch64-buildroot-linux-gnu/sysroot/usr/include/libdrm -DUSE_UPDATEENGINE=ON -DSUCCESSFUL_BOOT=ON"
make[1]: 进入目录“/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop”
cd /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop/../../../../../external/recovery && \
COMMIT_HASH=$(git rev-parse --verify --short HEAD) && \
GIT_COMMIT_TIME=$(git log -1 --format=%cd --date=format:%y%m%d) && \
GIT_DIRTY=$(git diff-index --quiet HEAD -- || echo "-dirty") && \
commit_info=-g${COMMIT_HASH}-${GIT_COMMIT_TIME}${GIT_DIRTY} && \
cd /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop && \
echo "#define GIT_COMMIT_INFO ${commit_info}" > recovery_autogenerate.h
/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/bin/aarch64-buildroot-linux-gnu-gcc -c recovery.c -o recovery.o -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Os -g0 -D_FORTIFY_SOURCE=1 -I. -fPIC -lpthread -lcurl -lssl -lcrypto -lbz2 -lpng -ldrm -lz -lm -I/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/aarch64-buildroot-linux-gnu/sysroot/usr/include/libdrm -DUSE_UPDATEENGINE=ON -DSUCCESSFUL_BOOT=ON
......
/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/bin/aarch64-buildroot-linux-gnu-gcc -o updateEngine mtdutils/mounts.o mtdutils/mtdutils.o mtdutils/rk29.o update_engine/rkbootloader.o update_engine/download.o update_engine/flash_image.o update_engine/log.o update_engine/main.o update_engine/md5sum.o update_engine/rkimage.o update_engine/rktools.o update_engine/rkboot.o update_engine/crc.o update_engine/update.o update_engine/do_patch.o -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Os -g0 -D_FORTIFY_SOURCE=1 -I. -fPIC -lpthread -lcurl -lssl -lcrypto -lbz2 -lpng -ldrm -lz -lm -I/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/host/aarch64-buildroot-linux-gnu/sysroot/usr/include/libdrm -DUSE_UPDATEENGINE=ON -DSUCCESSFUL_BOOT=ON
make[1]: 离开目录“/work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop”
>>> recovery develop Installing to target
/usr/bin/install -D -m 755 /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop/recovery /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target/usr/bin/
mkdir -p /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target/res/images
cp /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop/res/images/* /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target/res/images/
/usr/bin/install -D -m 755 /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/build/recovery-develop/updateEngine /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target/usr/bin/
/usr/bin/install -D -m 755 package/rockchip/recovery//S40recovery /work/sambashare/rk3588/armsom/armsom-rk3588-bsp/buildroot/output/rockchip_rk3588_recovery/target/etc/init.d/S40recovery
二、recovery
源码分析
recovery
程序的入口为recovery.c
文件。recovery.c
代码比较长,如下所示:
点开查看详情
int main(int argc, char **argv)
{
bool bSDBoot = false;
bool bUDiskBoot = false;
const char *sdupdate_package = NULL;
const char *usbupdate_package = NULL;
int previous_runs = 0;
const char *send_intent = NULL;
const char *update_package = NULL;
const char *encrypted_fs_mode = NULL;
int wipe_data = 0;
int wipe_all = 0;
int pcba_test = 0; // add for pcba test
int toggle_secure_fs = 0;
int arg;
bool isrkdebug = false;
int log_level = LOG_DEBUG;
encrypted_fs_info encrypted_fs_data;
struct timeval start_time, end_time;
long long elapsed_time;
gettimeofday(&start_time, NULL);
// 2.1 获取命令行参数:该函数的目的是从多个来源获取命令行参数,并将这些参数存储在argv数组中,同时更新misc分区
get_args(&argc, &argv);
strcpy(systemFlag, "false");
// 2.2 解析命令行参数:解析命令行参数
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
switch (arg) {
case 'p':
previous_runs = atoi(optarg);
break;
case 's':
send_intent = optarg;
break;
case 'u':
update_package = optarg;
break;
case 'w':
wipe_data = 1;
break;
case 'a':
wipe_all = 1;
break;
case 'e':
encrypted_fs_mode = optarg;
toggle_secure_fs = 1;
break;
case 't':
ui_show_text(1);
break;
case 'f':
pcba_test = 1;
break; // add for pcba test
case 'r':
isrkdebug = true;
break;
case 'i':
gr_set_rotate(atoi(optarg));
break;
case '?':
LOGE("Invalid command argument\n");
continue;
}
}
// 2.3 重定位标准输出和标准错误,并输出版本信息
time_t start = time(NULL);
if ((access("/.rkdebug", F_OK) != 0) && (isrkdebug != true)) {
// If these fail, there's not really anywhere to complain...
if (freopen(TEMPORARY_LOG_FILE, "a", stdout) == NULL) {
LOGW("freopen stdout error");
}
setbuf(stdout, NULL);
if (freopen(TEMPORARY_LOG_FILE, "a", stderr) == NULL) {
LOGE("freopen stderr error");
}
setbuf(stderr, NULL);
}
printf("\n");
printf("*********************************************************\n");
printf(" ROCKCHIP recovery system \n");
printf("*********************************************************\n");
printf("**** version : %s ****\n", recovery_version);
LOGI("Starting recovery on %s\n", ctime(&start));
while (access(coldboot_done, F_OK) != 0) {
LOGI("coldboot not done, wait...\n");
sleep(1);
}
#ifndef RecoveryNoUi
LOGI("Recovery System have UI defined.\n");
#endif
// 2.4 ui相关
ui_init();
ui_set_background(BACKGROUND_ICON_INSTALLING);
// 2.5 获取/etc/fstab中定义的系统启动时要挂在的文件系统,同时将要挂载的文件系统数量保存到全局变量num_volumes
load_volume_table();
// 2.6 获取获取MMC、SD、SDIO等类型的设备节点路径
setFlashPoint();
// 2.7 启动方式判定
bSDBoot = is_boot_from_SD();
bUDiskBoot = is_boot_from_udisk();
if (bSDBoot || bUDiskBoot) {
char imageFile[64] = {0};
if (bSDBoot) {
if (is_sdcard_update()) {
strlcpy(imageFile, EX_SDCARD_ROOT, sizeof(imageFile));
strlcat(imageFile, "/sdupdate.img", sizeof(imageFile));
if (access(imageFile, F_OK) == 0) {
sdupdate_package = strdup(imageFile);
bSDBootUpdate = true;
ui_show_text(1);
LOGI("sdupdate_package = %s\n", sdupdate_package);
}
}
}
if (bUDiskBoot) {
if (is_udisk_update()) {
strlcpy(imageFile, EX_UDISK_ROOT, sizeof(imageFile));
strlcat(imageFile, "/sdupdate.img", sizeof(imageFile));
if (access(imageFile, F_OK) == 0) {
usbupdate_package = strdup(imageFile);
bUdiskUpdate = true;
ui_show_text(1);
LOGI("usbupdate_package = %s\n", usbupdate_package);
}
}
}
}
// 函数什么也没有做,直接返回0
device_recovery_start();
// 输出命令:LOG_INFO: Command: "recovery" "--update_package=/userdata/update.img"
LOGI("Command:");
for (arg = 0; arg < argc; arg++) {
printf(" \"%s\"", argv[arg]);
}
printf("\n");
// update_package="/userdata/update.img"
if (update_package) {
// For backwards compatibility on the cache partition only, if
// we're given an old 'root' path "CACHE:foo", change it to
// "/cache/foo". 不会进入
if (strncmp(update_package, "CACHE:", 6) == 0) {
int len = strlen(update_package) + 10;
char* modified_path = malloc(len);
strlcpy(modified_path, "/cache/", len);
strlcat(modified_path, update_package + 6, len);
LOGI("(replacing path \"%s\" with \"%s\")\n",
update_package, modified_path);
update_package = modified_path;
}
}
printf("\n");
int status = INSTALL_SUCCESS;
// 不会进入
if (toggle_secure_fs) {
......
} else if (update_package != NULL) { // 进入 2.8 升级固件检测
int i, ret = 0;
const char* binary = "/usr/bin/rkupdate";
// 确保/oem、/userdata路径没有被挂载
rockchip_partition_check();
for (i = 0; i < 5; i++) {
// 确保/userdata/update.img已经被挂载
if (!ensure_path_mounted(update_package)) {
LOGI("mounted %s Success.\n", update_package);
break;
}
LOGW("mounted %s Failed. retry %d\n", update_package, i + 1);
sleep(1);
}
if (i != 5) {
LOGI(">>>rkflash will update from %s\n", update_package);
#ifdef USE_RKUPDATE
status = do_rk_update(binary, update_package);
#endif
#ifdef USE_UPDATEENGINE // 走这里 2.9 执行剩余分区的升级功能
const char* updateEnginebin = "/usr/bin/updateEngine";
status = do_rk_updateEngine(updateEnginebin, update_package);
#endif
if (status == INSTALL_SUCCESS) {
strcpy(systemFlag, update_package);
/* update success, delete update.img. */
if (access(update_package, F_OK) == 0)
remove(update_package);
ui_print("update.img images success!\n");
} else {
ui_print("update.img images failed!\n");
}
} else {
LOGE("mounted %s Failed.\n", update_package);
ui_print("mounted %s Failed.\n", update_package);
}
if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n");
ui_print("update.img Installation done.\n");
//ui_show_text(0);
} else if (sdupdate_package != NULL) {
......
} else if (usbupdate_package != NULL) {
.......
} else if (wipe_data) {
.......
} else if (wipe_all) {
.......
} else if (pcba_test) {
//pcba test todo...
printf("------------------ pcba test start -------------\n");
exit(EXIT_SUCCESS); //exit recovery bin directly, not start pcba here, in rkLanuch.sh
return 0;
} else {
if (argc == 1) { // No command specified
if (!bSDBootUpdate && !bUdiskUpdate && ui_text_visible())
prompt_and_wait();
finish_recovery(NULL);
reboot(RB_AUTOBOOT);
return 0;
}
status = INSTALL_ERROR; // No command specified
}
if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);
if (status != INSTALL_SUCCESS) {
LOGE("\n Install fail! \n");
if (!bSDBootUpdate && !bUdiskUpdate && ui_text_visible())
prompt_and_wait();
}
if (sdupdate_package != NULL && bSDBootUpdate) {
.......
} else if (usbupdate_package && bUdiskUpdate) {
.......
}
// Otherwise, get ready to boot the main system...
// 2.10 进行一些收尾工作
finish_recovery(send_intent);
gettimeofday(&end_time, NULL);
elapsed_time = (end_time.tv_sec - start_time.tv_sec) * 1000LL +
(end_time.tv_usec - start_time.tv_usec) / 1000LL;
LOGI("recovery usage time:%lld ms\n", elapsed_time);
ui_print("Rebooting...\n");
LOGI("Reboot...\n");
ui_show_text(0);
fflush(stdout);
sync();
// 系统重启
reboot(RB_AUTOBOOT);
return EXIT_SUCCESS;
}
2.1 获取命令行参数
首先调用get_args
函数,该函数的目的是从多个来源获取命令行参数,并将这些参数存储在 argv
数组中,同时更新misc
分区;
// command line args come from, in decreasing precedence:
// - the actual command line
// - the bootloader control block (one per line, after "recovery")
// - the contents of COMMAND_FILE (one per line)
static void
get_args(int *argc, char ***argv)
{
// 1. misc偏移16k位置存放的是recovery信息,因此recovery系统重启后会加载recovery信息
struct bootloader_message boot;
memset(&boot, 0, sizeof(boot));
get_bootloader_message(&boot); // this may fail, leaving a zeroed structure
// 输出命令:Boot command: boot-recovery
if (boot.command[0] != 0 && boot.command[0] != 255) {
LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command);
}
// 输出状态
if (boot.status[0] != 0 && boot.status[0] != 255) {
LOGI("Boot status: %.*s\n", (int)sizeof(boot.status), boot.status);
}
// --- if arguments weren't supplied, look in the bootloader control block
// 2. 如果argc小于或等于1(即没有提供命令行参数),函数会尝试从boot.recovery中提取参数
if (*argc <= 1) {
// 首先确保boot.recovery字符串以空字符结尾,然后使用strtok将boot.recovery字符串分割成以换行符\n为界的多个部分
// \n对应的ASCII值是0x0a
// boot.recovery="recovery\n--update_package=/userdata/update.img\n"
boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination
// arg=”recovery”
// boot.recovery="recovery\0--update_package=/userdata/update.img\n"
const char *arg = strtok(boot.recovery, "\n");
// 满足条件
if (arg != NULL && !strcmp(arg, "recovery")) {
*argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
// 保存参数arg
(*argv)[0] = strdup(arg);
for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
// 获取下一个子字符串
// arg=”--update_package=/userdata/update.img”
// boot.recovery="recovery\0--update_package=/userdata/update.img\0"
if ((arg = strtok(NULL, "\n")) == NULL) break;
(*argv)[*argc] = strdup(arg);
}
LOGI("Got arguments from boot message\n");
} else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) {
LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery);
}
}
// --- if that doesn't work, try the command file
if (*argc <= 1) {
......
}
// --> write the arguments we have back into the bootloader control block
// always boot into recovery after this (until finish_recovery() is called)
// 3. 设置参数
// 设置boot.command="boot-recovery"
// 设置boot.recovery="recovery"
strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
int i;
// 合并参数,最后boot.recovery="recovery\n--update_package=/userdata/update.img\n"
for (i = 1; i < *argc; ++i) {
strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
strlcat(boot.recovery, "\n", sizeof(boot.recovery));
}
// 往misc偏移16k位置写入recovery信息
set_bootloader_message(&boot);
}
有关struct bootloader_message
参考《Rockchip RK3588 - Rockchip Linux Recovery updateEngine
源码分析》第4.4节。比如misc
分区偏移16k
的数据如下:
10000000: 62 6f 6f 74 2d 72 65 63 6f 76 65 72 79 00 00 00 boot-recovery...
10000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10000040: 72 65 63 6f 76 65 72 79 0a 2d 2d 75 70 64 61 74 recovery.--updat
10000050: 65 5f 70 61 63 6b 61 67 65 3d 2f 75 73 65 72 64 e_package=/userd
10000060: 61 74 61 2f 75 70 64 61 74 65 2e 69 6d 67 0a 00 ata/update.img..
10000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
......
10000340: 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
......
2.2 解析命令行参数
由于recovery
是通过传参来实现固件升级的,因此不难猜出recovery
是基于命令行参数的程序。
接着使用了getopt_long
函数来解析命令行参数,并根据参数的不同执行相应的操作。
static const struct option OPTIONS[] = {
{ "send_intent", required_argument, NULL, 's' },
{ "update_package", required_argument, NULL, 'u' },
{ "wipe_data", no_argument, NULL, 'w' },
{ "wipe_all", no_argument, NULL, 'a' },
{ "set_encrypted_filesystems", required_argument, NULL, 'e' },
{ "show_text", no_argument, NULL, 't' },
{ "factory_pcba_test", no_argument, NULL, 'f' },
{ "rkdebug", no_argument, NULL, 'r'},
{ "ui_rotation", required_argument, NULL, 'i'},
{ NULL, 0, NULL, 0 },
};
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
switch (arg) {
case 'p':
previous_runs = atoi(optarg);
break;
case 's':
send_intent = optarg;
break;
case 'u': // --update_package=/userdata/update.img
update_package = optarg;
break;
case 'w':
wipe_data = 1;
break;
case 'a':
wipe_all = 1;
break;
case 'e':
encrypted_fs_mode = optarg;
toggle_secure_fs = 1;
break;
case 't':
ui_show_text(1);
break;
case 'f':
pcba_test = 1;
break; // add for pcba test
case 'r':
isrkdebug = true;
break;
case 'i':
gr_set_rotate(atoi(optarg));
break;
case '?':
LOGE("Invalid command argument\n");
continue;
}
}
getopt_long
这是一个用于获取命令行选项的函数。它可以处理长选项,并与短选项配合使用。
argc
和argv
是标准的命令行参数;OPTIONS
是定义选项的字符串或结构体(通常包含短选项及其参数信息);NULL
是指不使用长选项的返回值。
由于(*argv)[0]="recovery"
,(*argv)[1]="--update_package=/userdata/update.img"
,因此会进入u
分支,设置update_package="/userdata/update.img"
。
2.3 输出版本信息
此外如果创建了/.rkdebug
文件,则将将标准输出和标准错误重定向到一个临时日志文件/tmp/recovery.log
,,以便进行调试和记录;
if ((access("/.rkdebug", F_OK) != 0) && (isrkdebug != true)) {
// If these fail, there's not really anywhere to complain...
if (freopen(TEMPORARY_LOG_FILE, "a", stdout) == NULL) {
LOGW("freopen stdout error");
}
setbuf(stdout, NULL);
if (freopen(TEMPORARY_LOG_FILE, "a", stderr) == NULL) {
LOGE("freopen stderr error");
}
setbuf(stderr, NULL);
}
接着main
函数输出当前recovery
版本信息,比如:
*********************************************************
ROCKCHIP recovery system
*********************************************************
**** version : V1.0.1-g28f720bc5-240524-dirty ****
LOG_INFO: Starting recovery on Sun Oct 6 09:04:09 2024
如果没有配置RecoveryNoUi
,输出:
LOG_INFO: Recovery System have UI defined.
2.4 ui
相关
2.4.1 ui_init
ui_init
函数定义在ui.c
文件;
void ui_init(void)
{
int ret = 0;
ret = gr_init();
if (ret)
return;
ev_init((ev_callback)input_callback, NULL);
text_col = text_row = 0;
text_rows = gr_fb_height() / CHAR_HEIGHT;
if (text_rows > MAX_ROWS) text_rows = MAX_ROWS;
text_top = 1;
text_cols = gr_fb_width() / CHAR_WIDTH;
if (text_cols > MAX_COLS - 1) text_cols = MAX_COLS - 1;
int i;
for (i = 0; BITMAPS[i].name != NULL; ++i) {
int result = res_create_display_surface(BITMAPS[i].name, BITMAPS[i].surface);
if (result < 0) {
if (result == -2) {
LOGI("Bitmap %s missing header\n", BITMAPS[i].name);
} else {
LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result);
}
*BITMAPS[i].surface = NULL;
}
}
pthread_t t;
pthread_create(&t, NULL, progress_thread, NULL);
pthread_create(&t, NULL, input_thread, NULL);
}
2.4.2 ui_set_background
ui_set_background
函数定义在ui.c
文件;
void ui_set_background(int icon)
{
if (!gr_draw)
return;
pthread_mutex_lock(&gUpdateMutex);
gCurrentIcon = gBackgroundIcon[icon];
update_screen_locked();
pthread_mutex_unlock(&gUpdateMutex);
}
2.5 加载/etc/fstab
load_volume_table
函数用于获取/etc/fstab
中定义的系统启动时要挂在的文件系统,同时将要挂载的文件系统数量保存到全局变量num_volumes
;
rk3399 login: recovery filesystem table
=========================
0 (null) /tmp ramdisk (null) (null) (null)
1 /dev/root / ext2 rw,noauto 0 1
2 proc /proc proc defaults 0 0
3 devpts /dev/pts devpts defaults,gid=5,mode=620,ptmxmode=0666 0 0
4 tmpfs /dev/shm tmpfs mode=1777 0 0
5 tmpfs /tmp tmpfs mode=1777 0 0
6 tmpfs /run tmpfs mode=0755,nosuid,nodev 0 0
7 sysfs /sys sysfs defaults 0 0
8 configfs /sys/kernel/config configfs defaults 0 0
9 debugfs /sys/kernel/debug debugfs defaults 0 0
load_volume_table
函数定义在roots.c
:
void load_volume_table()
{
int alloc = 2;
// 保存块设备信息
device_volumes = malloc(alloc * sizeof(Volume));
// Insert an entry for /tmp, which is the ramdisk and is always mounted.
device_volumes[0].mount_point = "/tmp";
device_volumes[0].fs_type = "ramdisk";
device_volumes[0].device = NULL;
device_volumes[0].device2 = NULL;
device_volumes[0].option = NULL;
device_volumes[0].dump = NULL;
device_volumes[0].pass = NULL;
num_volumes = 1;
// 打开/etc/fstab文件
FILE* fstab = fopen("/etc/fstab", "r");
if (fstab == NULL) {
LOGE("failed to open /etc/fstab (%d)\n", errno);
return;
}
char buffer[1024];
char file_system[1024];
char mount_point[1024];
char fs_type[1024];
char option[1024];
char dump[1024];
char pass[1024];
char device[1024];
int i;
// 从文件中读取一行
while (fgets(buffer, sizeof(buffer) - 1, fstab)) {
// 将读取的行数据依次解析到file_system、mount_point、fs_type、option等
i = sscanf(buffer, "%s %s %s %s %s %s", file_system,
mount_point, fs_type, option, dump, pass);
if (file_system[0] == '#') continue;
//printf("load_volume_table file_system:%s, mount_point:%s, fs_type:%s, option:%s, dump:%s, pass:%s\n", file_system, mount_point, fs_type, option, dump, pass);
/* HACK: Convert *LABEL to "by-name" symlink */
// 如果配置的LABEL=xxx,则设置device="/dev/block/by-name/xxxx"
if (strstr(file_system, "LABEL="))
snprintf(device, sizeof(device), "/dev/block/by-name/%s",
strstr(file_system, "LABEL=") + strlen("LABEL="));
else
// 负责直接设置device=file_system
strcpy(device, file_system);
// 一行6个参数
if (i == 6) {
// 动态扩容
while (num_volumes >= alloc) {
alloc *= 2;
device_volumes = realloc(device_volumes, alloc * sizeof(Volume));
}
device_volumes[num_volumes].mount_point = strdup(mount_point);
device_volumes[num_volumes].fs_type = strdup(fs_type);
device_volumes[num_volumes].option = strdup(option);
device_volumes[num_volumes].dump = strdup(dump);
device_volumes[num_volumes].pass = strdup(pass);
device_volumes[num_volumes].device = strdup(device);;
device_volumes[num_volumes].device2 = NULL;
++num_volumes;
} else {
LOGE("skipping malformed recovery.fstab line: %s\n", buffer);
}
}
fclose(fstab);
printf("recovery filesystem table\n");
printf("=========================\n");
for (i = 0; i < num_volumes; ++i) {
Volume* v = &device_volumes[i];
printf(" %d %s %s %s %s %s %s\n", i, v->device, v->mount_point, v->fs_type, v->option, v->dump, v->pass);
}
printf("\n");
}
2.6 setFlashPoint
setFlashPoint
函数定义在rktools.c
,用于获取MMC
、SD
、SDIO
等类型的设备节点路径;
void setFlashPoint()
{
// 判断是不是MTD设备(Nor/Nand Flash设备),对于SD、eMMC不属于MTD设备
if (!isMtdDevice()) // 进入
// 等待设备节点/dev/block/by-name/misc可用
wait_for_device(MISC_PARTITION_NAME_BLOCK);
// 通过读取/sys/bus/mmc/devices/目录中的条目来查找SD或eMMC设备,并更新result_point
init_sd_emmc_point();
// 设置环境变量emmc_point_name="/dev/mmcblk2"
setenv(EMMC_POINT_NAME, result_point[MMC], 1);
// 判断SD类型设备是否存在,设备节点为/dev/mmcblk0
if (access(result_point[SD], F_OK) == 0)
// 设置环境变量sd_point_name_2
setenv(SD_POINT_NAME_2, result_point[SD], 1);
// /dev/mmcblk0p1
char name_t[22];
if (strlen(result_point[SD]) > 0) {
strcpy(name_t, result_point[SD]);
strcat(name_t, "p1");
}
if (access(name_t, F_OK) == 0)
// 设置环境变量sd_point_name
setenv(SD_POINT_NAME, name_t, 1);
// emmc_point_name
LOGI("emmc_point is %s\n", getenv(EMMC_POINT_NAME));
// sd_point_name
LOGI("sd_point is %s\n", getenv(SD_POINT_NAME));
// sd_point_name_2
LOGI("sd_point_2 is %s\n", getenv(SD_POINT_NAME_2));
}
函数执行完,输出信息大致如下;
LOG_INFO: devices is not MTD.
LOG_INFO: emmc_point is /dev/mmcblk2 # SDIO类型设备,在这里对应的是板载eMMC
LOG_INFO: sd_point is (null)
LOG_INFO: sd_point_2 is (null)
2.6.1 wait_for_device
wait_for_device
用于 确保某个文件(例如设备节点,挂载点或配置文件)可用;
static void wait_for_device(const char* fn)
{
int tries = 0;
int ret;
struct stat buf;
do {
++tries;
// 检查文件状态
ret = stat(fn, &buf);
// 回非零值,表示路径不可用
if (ret) {
LOGI("stat %s try %d: %s\n", fn, tries, strerror(errno));
sleep(1);
}
} while (ret && tries < 10);
if (ret) {
LOGI("failed to stat %s\n", fn);
}
}
2.6.2 init_sd_emmc_point
init_sd_emmc_point
通过读取/sys/bus/mmc/devices/
目录中的条目来查找SD
或eMMC
设备,并更新result_point
;
static const char *point_items[] = {
"/dev/mmcblk0", // MMC类型设备对应的设备节点
"/dev/mmcblk1", // SD类型设备对应的设备节点
"/dev/mmcblk2", // SDIO类型设备对应的设备节点
"/dev/mmcblk3", // SDcombo类型设备对应的设备节点
};
enum type {
MMC, // MMC类型
SD, // SD类型
SDIO, // SDIO类型
SDcombo, // .....
};
static const char *typeName[] = {
"MMC",
"SD",
"SDIO",
"SDcombo",
};
/**
* 设置flash 节点
*/
static char result_point[4][20] = {'\0'}; //0-->MMC, 1-->SD, 2-->SDIO, 3-->SDcombo
// 读取dir目录下的filename文件
int readFile(DIR* dir, char* filename)
{
char name[30] = {'\0'};
int i;
// 构建文件路径:mmc0:0001/type
// 构建文件路径:mmc1:0001/type
// 构建文件路径:mmc2:0001/type
strcpy(name, filename);
strcat(name, "/type");
// 打开文件
int fd = openat(dirfd(dir), name, O_RDONLY);
if (fd == -1) {
LOGE("Error: openat %s error %s.\n", name, strerror(errno));
return -1;
}
// 读取文件内容到resultBuf中
char resultBuf[10] = {'\0'};
if (read(fd, resultBuf, sizeof(resultBuf)) < 1) {
return -1;
}
// 将换行符替换为字符串结束符
for (i = 0; i < strlen(resultBuf); i++) {
if (resultBuf[i] == '\n') {
resultBuf[i] = '\0';
break;
}
}
// 比较读取的内容与预定义的类型名称typeName,如果匹配则返回索引
for (i = 0; i < 4; i++) {
if (strcmp(typeName[i], resultBuf) == 0) {
//printf("type is %s.\n", typeName[i]);
return i;
}
}
LOGE("Error:no found type!\n");
return -1;
}
// 通过读取/sys/bus/mmc/devices/目录中的条目来查找SD或eMMC设备
void init_sd_emmc_point()
{
DIR* dir = opendir("/sys/bus/mmc/devices/");
// 目录存在
if (dir != NULL) {
struct dirent* de;
// 循环读取目录中的每个条目,比如mmc0:0001、mmc1:0001、mmc2:0001
while ((de = readdir(dir))) {
// 排除当前目录 (.) 和父目录 (..)
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0 )
continue;
//if (de->d_type == 4) //dir
// printf("dir name : %s \n", de->d_name);
// 检查目录项名称是否以mmc开头,以确定是否是mmc设备。
if (strncmp(de->d_name, "mmc", 3) == 0) {
//printf("find mmc is %s.\n", de->d_name);
char flag = de->d_name[3];
int ret = -1;
// 读取dir目录下的mmc0:0001/type文件,获取设备类型
ret = readFile(dir, de->d_name);
// 设备类型存在
if (ret != -1) {
// mmc0:0001/type -> result_point[SDIO]="/dev/mmcblk2"
// mmc1:0001/type -> result_point[SD]="/dev/mmcblk1"
// mmc2:0001/type -> result_point[MMC]="/dev/mmcblk0"
strcpy(result_point[ret], point_items[flag - '0']);
} else {
// 无效
strcpy(result_point[ret], "");
}
}
}
}
closedir(dir);
}
以NanoPC-T4
开发板为例(未接入SD
卡):
pi@NanoPC-T4:~$ ls -l /sys/bus/mmc/devices
total 0
lrwxrwxrwx 1 root root 0 Oct 6 14:41 mmc0:0001 -> ../../../devices/platform/fe310000.mmc/mmc_host/mmc0/mmc0:0001
lrwxrwxrwx 1 root root 0 Oct 6 14:41 mmc2:0001 -> ../../../devices/platform/fe330000.sdhci/mmc_host/mmc2/mmc2:0001
pi@NanoPC-T4:~$ cat /sys/bus/mmc/devices/mmc0\:0001/type
SDIO
pi@NanoPC-T4:~$ cat /sys/bus/mmc/devices/mmc2\:0001/type
MMC
函数执行完毕:
result_point[MMC]="/dev/mmcblk0"
result_point[SD]=""
result_point[SDIO]="/dev/mmcblk2"
result_point[SDcombo]=""
如果插入SD
卡:
pi@NanoPC-T4:~$ ls -l /sys/bus/mmc/devices
total 0
lrwxrwxrwx 1 root root 0 Oct 6 14:41 mmc0:0001 -> ../../../devices/platform/fe310000.mmc/mmc_host/mmc0/mmc0:0001
lrwxrwxrwx 1 root root 0 Oct 6 15:02 mmc1:0001 -> ../../../devices/platform/fe320000.mmc/mmc_host/mmc1/mmc1:0001
lrwxrwxrwx 1 root root 0 Oct 6 14:41 mmc2:0001 -> ../../../devices/platform/fe330000.sdhci/mmc_host/mmc2/mmc2:0001
pi@NanoPC-T4:~$ cat /sys/bus/mmc/devices/mmc1\:0001/type
SD
2.7 启动方式判定
接着通过读取命令行信息判断启动方式,用于检测是否从SD
/USB
启动;
bSDBoot = is_boot_from_SD();
bUDiskBoot = is_boot_from_udisk();
if (bSDBoot || bUDiskBoot) {
char imageFile[64] = {0};
if (bSDBoot) {
if (is_sdcard_update()) {
strlcpy(imageFile, EX_SDCARD_ROOT, sizeof(imageFile));
strlcat(imageFile, "/sdupdate.img", sizeof(imageFile));
if (access(imageFile, F_OK) == 0) {
sdupdate_package = strdup(imageFile);
bSDBootUpdate = true;
ui_show_text(1);
LOGI("sdupdate_package = %s\n", sdupdate_package);
}
}
}
if (bUDiskBoot) {
if (is_udisk_update()) {
strlcpy(imageFile, EX_UDISK_ROOT, sizeof(imageFile));
strlcat(imageFile, "/sdupdate.img", sizeof(imageFile));
if (access(imageFile, F_OK) == 0) {
usbupdate_package = strdup(imageFile);
bUdiskUpdate = true;
ui_show_text(1);
LOGI("usbupdate_package = %s\n", usbupdate_package);
}
}
}
}
由于我们采用的eMMC
启动方式,所以输出如下日志:
LOG_INFO: read cmdline
LOG_INFO: >>> Boot from non-SDcard
LOG_INFO: read cmdline
LOG_INFO: >>> Boot from non-U-Disk
2.7.1 is_boot_from_SD
is_boot_from_SD
函数定义在sdboot.c
:
bool is_boot_from_SD(void)
{
bool bSDBoot = false;
char param[1024];
int fd, ret;
char *s = NULL;
LOGI("read cmdline\n");
memset(param, 0, 1024);
// 读取命令行
fd = open("/proc/cmdline", O_RDONLY);
ret = read(fd, (char*)param, 1024);
// 查找sdfwupdate字符串
s = strstr(param, "sdfwupdate");
if (s != NULL) {
bSDBoot = true;
LOGI(">>> Boot from SDcard\n");
} else {
bSDBoot = false;
LOGI(">>> Boot from non-SDcard\n");
}
close(fd);
return bSDBoot;
}
2.7.2 is_boot_from_udisk
is_boot_from_udisk
函数定义在usbboot.c
:
bool is_boot_from_udisk(void)
{
bool bUDisk = false;
char param[1024];
int fd, ret;
char *s = NULL;
LOGI("read cmdline\n");
memset(param, 0, 1024);
// 读取命令行
fd = open("/proc/cmdline", O_RDONLY);
ret = read(fd, (char*)param, 1024);
// 查找usbfwupdate字符串
s = strstr(param, "usbfwupdate");
if (s != NULL) {
bUDisk = true;
LOGI(">>> Boot from U-Disk\n");
} else {
bUDisk = false;
LOGI(">>> Boot from non-U-Disk\n");
}
close(fd);
return bUDisk;
}
2.8 升级固件检测
接着就是升级固件的检测,这里包含两步检测:
-
确保
/oem
、/userdata
路径没有被挂载; -
确保升级固件路径
/userdata/update.img
已经挂载;
源码如下:
int i, ret = 0;
const char* binary = "/usr/bin/rkupdate";
// 检测oem、userdata分区是否挂载
rockchip_partition_check();
for (i = 0; i < 5; i++) {
//update_package="/userdata/update.im", 函数内部确保/userdata路径已经挂载
if (!ensure_path_mounted(update_package)) {
LOGI("mounted %s Success.\n", update_package);
break;
}
LOGW("mounted %s Failed. retry %d\n", update_package, i + 1);
sleep(1);
}
比如我们在使用NanoPC-T4
开发板进行测试的时候,并没有在/etc/fstab
中配置/oem
、/userdata
路径的挂载信息;因此会输出如下信息:
# 输出下面这个错误是因为我们没有在/etc/fstab中配置/oem这个路径的挂载信息
LOG_ERROR: unknown volume for path [/oem]
LOG_ERROR:
=== umount oem fail ===
# 输出下面这个错误是因为我们没有在/etc/fstab中配置/userdata这个路径的挂载信息
LOG_ERROR: unknown volume for path [/userdata]
LOG_ERROR:
=== umount userdata fail ===
LOG_INFO: check userdata/oem partition success ...
# /userdata路径挂载成功
LOG_INFO: mounted /userdata/update.img Success.
我们采用了另外一种方式,即在制作的recovery
系统的/etc/inittab
文件中配置了oem
、userdata
分区的的挂载命令;
# 用于挂载oem、userdata分区(如果分区存在的话)
::sysinit:/bin/mount -t ext4 -r /dev/block/by-name/oem /oem
::sysinit:/bin/mount -t ext4 /dev/block/by-name/userdata /userdata
所以后面在使用updateEngine
命令进行剩余分区升级的时候,是可以找到升级固件/userdata/update.img
的。
2.8.1 rockchip_partition_check
rockchip_partition_check
函数用于确保/oem
、/userdata
路径没有被挂载;
// rockchip partition check (e.g: oem/userdata....)
static void
rockchip_partition_check()
{
// 确保/oem路径没有被挂载
if (ensure_path_unmounted("/oem") != 0)
LOGE("\n === umount oem fail === \n");
// 确保/userdata路径没有被挂载
if (ensure_path_unmounted("/userdata") != 0)
LOGE("\n === umount userdata fail === \n");
ui_print("check userdata/oem partition success ...\n");
LOGI("check userdata/oem partition success ...\n");
}
2.8.2 ensure_path_unmounted
ensure_path_unmounted
函数定义在roots.c
文件中,该函数用于确保路径path
没有被挂载。这里有一点需要注意:
- 如果输入
path="/oem"
,则判定的实际挂载点mount_point="/oem"
; - 如果输入
path="/userdata/update.img
,则判定的实际挂载点mount_point="/userdata"
; - 如果输入
path="/userdata/recovery/last_log
,则判定的实际挂载点mount_point="/userdata/recovery"
;
如果挂载了该路径文件系统,比如/dev/userdata
设备挂载到/userdata
,则取消挂载;
int ensure_path_unmounted(const char* path)
{
// 检测/etc/fstab文件中配置的若干文件系统挂载点是否包含了path这个路,如果包含,则返回匹配的文件系统挂载信息
Volume* v = volume_for_path(path);
// 如果找不到匹配项,返回-1; 在使用NanoPC-T4开发板进行测试时走到这一步就会返回
if (v == NULL) {
LOGE("unknown volume for path [%s]\n", path);
return -1;
}
// 如果文件系统类型为ramdisk,则返回-1
if (strcmp(v->fs_type, "ramdisk") == 0) {
// the ramdisk is always mounted; you can't unmount it.
return -1;
}
// 读取/proc/mount文件获取当前系统上真实挂载的所有文件系统的信息
int result;
result = scan_mounted_volumes();
if (result < 0) {
LOGE("failed to scan mounted volumes\n");
return -1;
}
// 从已经挂载的文件系统列表中查找挂载点为path`的文件系统
const MountedVolume* mv =
find_mounted_volume_by_mount_point(v->mount_point);
// 未挂载
if (mv == NULL) {
// volume is already unmounted
return 0;
}
// 如果mv文件系统已经挂载,取消挂载
return unmount_mounted_volume(mv);
}
/etc/fstab
: 这是一个静态文件,定义了系统启动时要挂载的文件系统,而 /proc/mounts
是动态的,反映当前的挂载状态。
通过检测/etc/fstab
文件中配置的要挂载的文件系统,如果挂载点为path
,则通过读取/proc/mount
来判定该文件系统是否真正挂载,如果真正挂载了则进行卸载。
2.8.2.1 volume_for_path
volume_for_path
定义在roots.c
文件中,用于检测/etc/fstab
文件中配置的若干文件系统挂载点是否包含了path
这个路,如果包含,则返回匹配的文件系统挂载信息;
static char mount_point[256] = {0};
// 如果输入linkpath="/oem",则返回buf="/oem"
// 如果输入linkpath="/userdata/update.img",则返回buf="/userdata"
// 如果输入linkpath="/userdata/recovery/last_log",则返回buf="/userdata/recovery"
char * get_link_path(const char* linkpath, char * buf="/oem", int count)
{
int i;
int rslt;
char path[256];
// 安全复制linkpath并限制长度
memset(path, 0, sizeof(path));
strcpy(path, linkpath);
// 查找最后一个'/'并将其替换为'\0',注意这里跳过了第一个字符
for (i = strlen(linkpath); i > 0; i--) {
if (path[i] == '/') {
path[i] = '\0';
break;
}
}
// 读取符号链接
rslt = readlink(path, buf, count - 1);
if (rslt < 0 || (rslt >= count - 1)) {
// printf("No link to path [%s]!!! \n", path);
return NULL;
}
// 确保buf以'/'开头
if (buf[0] != '/') {
char tmp[256];
memset(tmp, 0, sizeof(tmp));
memcpy(tmp, buf, strlen(buf));
memset(buf, 0, sizeof(buf));
memcpy(buf + 1, tmp, strlen(tmp));
buf[0] = '/';
}
// printf("buf = %s \n", buf);
return buf;
}
// path传入/oem
// path传入/userdata
// 返回/etc/fstab中配置的文件系统挂载信息
Volume* volume_for_path(const char* path)
{
int i;
// 填充0
memset(mount_point, 0, sizeof(mount_point));
// 这里path指向的实际路径,保存到mount_point
char* tmp = get_link_path(path, mount_point, 1024);
// if ( tmp != NULL)
// printf(" ### get mount_ponit = %s ### \n", mount_point);
// 遍历/etc/fstab中定义的文件系统挂载点和挂载选项
for (i = 0; i < num_volumes; ++i) {
// 获取第i个挂载信息
Volume* v = device_volumes + i;
int len = strlen(v->mount_point);
// 判断/oem、/userdata目录是否挂载
if (strncmp(path, v->mount_point, len) == 0 &&
(path[len] == '\0' || path[len] == '/')) {
//printf(" ===path = %s, v-mount_point = %s ===\n",path, v->mount_point);
return v;
} else {
//add by chad.ma for symbol link file. eg. sdcard/ --->mnt/sdcard
if (!tmp)
continue;
//printf(" # v->mount_point = %s\n", v->mount_point);
if (strncmp(mount_point, v->mount_point, len) == 0 &&
(path[len] == '\0' || path[len] == '/')) {
return v;
}
}
}
return NULL;
}
2.8.2.1 scan_mounted_volumes
scan_mounted_volumes
函数定义在mounts.c
文件,通过读取/proc/mount
文件获取当前系统上真实挂载的所有文件系统的信息;
typedef struct {
MountedVolume *volumes;
int volumes_allocd;
int volume_count;
} MountsState;
static MountsState g_mounts_state = {
NULL, // volumes
0, // volumes_allocd
0 // volume_count
};
int scan_mounted_volumes()
{
char buf[2048];
const char *bufp;
int fd;
ssize_t nbytes;
if (g_mounts_state.volumes == NULL) {
const int numv = 32;
MountedVolume *volumes = malloc(numv * sizeof(*volumes));
if (volumes == NULL) {
errno = ENOMEM;
return -1;
}
g_mounts_state.volumes = volumes;
g_mounts_state.volumes_allocd = numv;
memset(volumes, 0, numv * sizeof(*volumes));
} else {
/* Free the old volume strings.
*/
int i;
for (i = 0; i < g_mounts_state.volume_count; i++) {
free_volume_internals(&g_mounts_state.volumes[i], 1);
}
}
g_mounts_state.volume_count = 0;
/* Open and read the file contents.
* 读取/proc/mounts文件,获取真实挂载的文件系统信息
*/
fd = open(PROC_MOUNTS_FILENAME, O_RDONLY);
if (fd < 0) {
goto bail;
}
nbytes = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (nbytes < 0) {
goto bail;
}
buf[nbytes] = '\0';
/* Parse the contents of the file, which looks like:
*
* # cat /proc/mounts
* rootfs / rootfs rw 0 0
* /dev/pts /dev/pts devpts rw 0 0
* /proc /proc proc rw 0 0
* /sys /sys sysfs rw 0 0
* /dev/block/mtdblock4 /system yaffs2 rw,nodev,noatime,nodiratime 0 0
* /dev/block/mtdblock5 /data yaffs2 rw,nodev,noatime,nodiratime 0 0
* /dev/block/mmcblk0p1 /sdcard vfat rw,sync,dirsync,fmask=0000,dmask=0000,codepage=cp437,iocharset=iso8859-1,utf8 0 0
*
* The zeroes at the end are dummy placeholder fields to make the
* output match Linux's /etc/mtab, but don't represent anything here.
*/
bufp = buf;
while (nbytes > 0) {
char device[64];
char mount_point[64];
char filesystem[64];
char flags[128];
int matches;
/* %as is a gnu extension that malloc()s a string for each field.
*/
matches = sscanf(bufp, "%63s %63s %63s %127s",
device, mount_point, filesystem, flags);
if (matches == 4) {
device[sizeof(device) - 1] = '\0';
mount_point[sizeof(mount_point) - 1] = '\0';
filesystem[sizeof(filesystem) - 1] = '\0';
flags[sizeof(flags) - 1] = '\0';
MountedVolume *v =
&g_mounts_state.volumes[g_mounts_state.volume_count++];
v->device = strdup(device);
v->mount_point = strdup(mount_point);
v->filesystem = strdup(filesystem);
v->flags = strdup(flags);
} else {
printf("matches was %d on <<%.40s>>\n", matches, bufp);
}
/* Eat the line.
*/
while (nbytes > 0 && *bufp != '\n') {
bufp++;
nbytes--;
}
if (nbytes > 0) {
bufp++;
nbytes--;
}
}
return 0;
bail:
//TODO: free the strings we've allocated.
g_mounts_state.volume_count = 0;
return -1;
}
2.8.2.3 find_mounted_volume_by_mount_point
``find_mounted_volume_by_mount_point函数定义在
mounts.c文件,实用于从已经挂载的文件系统列表中查找挂载点为
mount_point`的文件系统;
const MountedVolume *
find_mounted_volume_by_mount_point(const char *mount_point)
{
// 真实已经挂载的文件系统列表
if (g_mounts_state.volumes != NULL) {
int i;
// 遍历每一个挂载的文件系统,查找挂载点为mount_point的文件系统
for (i = 0; i < g_mounts_state.volume_count; i++) {
MountedVolume *v = &g_mounts_state.volumes[i];
/* May be null if it was unmounted and we haven't rescanned.
*/
if (v->mount_point != NULL) {
if (strcmp(v->mount_point, mount_point) == 0) {
return v;
}
}
}
}
return NULL;
}
2.8.2.4 unmount_mounted_volume
unmount_mounted_volume
函数定义在mounts.c
文件,用于取消文件系统volume
的挂载;
int
unmount_mounted_volume(const MountedVolume *volume)
{
/* Intentionally pass NULL to umount if the caller tries
* to unmount a volume they already unmounted using this
* function.
* 等价于执行命令 umount <挂载点或设备>
*/
int ret = umount(volume->mount_point);
if (ret == 0) {
// 释放volume成员
free_volume_internals(volume, 1);
return 0;
}
return ret;
}
2.8.3 ensure_path_mounted
ensure_path_mounted
函数定义在roots.c
文件中,该函数用于确保路径path
被挂载。这里有一点需要注意:
- 如果输入
path="/oem"
,则判定的实际挂载点mount_point="/oem"
; - 如果输入
path="/userdata/update.img
,则判定的实际挂载点mount_point="/userdata"
; - 如果输入
path="/userdata/recovery/last_log
,则判定的实际挂载点mount_point="/userdata/recovery"
;
这里实际传入的参数为/userdata/update.img
;
int ensure_path_mounted(const char* path)
{
if (access(path, F_OK) == 0)
return 0;
// 检测/etc/fstab文件中配置的若干文件系统挂载点是否包含了path这个路,如果包含,则返回匹配的文件系统挂载信息
// 同时设置mount_point="/userdata"
Volume* v = volume_for_path(path);
// 如果找不到匹配项,返回-1; 在使用NanoPC-T4开发板进行测试时走到这一步就会返回
if (v == NULL) {
if (mount_point[0] != 0) {
// 读取/proc/mount文件获取当前系统上真实挂载的所有文件系统的信息;
int result = scan_mounted_volumes();
if (result < 0) {
LOGE("failed to scan mounted volumes\n");
return -1;
}
// 从已经挂载的文件系统列表中查找挂载点为path的文件系统
// 由于我们在/etc/inittab配置了设备/dev/userdata的挂载点为/userdata,因此这里可以匹配mount_point="/userdata"
const MountedVolume* mv =
find_mounted_volume_by_mount_point(mount_point);
if (mv) {
// volume is already mounted
LOGI("%s already mounted \n", path);
return 0;
}
}
LOGE("unknown volume for path [%s]\n", path);
return -1;
}
// 如果文件系统类型为ramdisk,则返回0
if (strcmp(v->fs_type, "ramdisk") == 0) {
// the ramdisk is always mounted.
return 0;
}
int result;
// 读取/proc/mount文件获取当前系统上真实挂载的所有文件系统的信息
result = scan_mounted_volumes();
if (result < 0) {
LOGE("failed to scan mounted volumes\n");
return -1;
}
// 从已经挂载的文件系统列表中查找挂载点为path的文件系统
const MountedVolume* mv =
find_mounted_volume_by_mount_point(v->mount_point);
if (mv) {
// volume is already mounted
return 0;
}
mkdir(v->mount_point, 0755); // in case it doesn't already exist
// /etc/fstab中查找的匹配项v,其描述的文件系统未挂载,则进行挂载操作
if (strcmp(v->fs_type, "yaffs2") == 0) {
......
return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0);
} else if (strcmp(v->fs_type, "ext4") == 0 ||
strcmp(v->fs_type, "vfat") == 0 ||
strcmp(v->fs_type, "ext2") == 0) {
char *blk_device;
blk_device = (char*)v->device;
if (strcmp("/mnt/sdcard", v->mount_point) == 0) {
blk_device = getenv(SD_POINT_NAME);
if (blk_device == NULL) {
setFlashPoint();
blk_device = getenv(SD_POINT_NAME);
}
result = mount(blk_device, v->mount_point, v->fs_type,
MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
if (result == 0) return 0;
}
// 进行挂载操作
result = mount(v->device, v->mount_point, v->fs_type,
MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
if (result == 0) return 0;
if (v->device2) {
LOGW("failed to mount %s (%s); trying %s\n",
v->device, strerror(errno), v->device2);
result = mount(v->device2, v->mount_point, v->fs_type,
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;
}
2.9 updateEngine
升级分区
接着使用/usr/bin/updateEngine
升级剩余分区;
const char* updateEnginebin = "/usr/bin/updateEngine";
status = do_rk_updateEngine(updateEnginebin, usbupdate_package);
函数do_rk_updateEngine
定义在rkupdate.c
文件中:
int do_rk_updateEngine(const char *binary, const char *path)
{
LOGI("[%s] start with main.\n", __func__);
char *update = "--update";
char *update_sdboot = "--update=sdboot";
int pipefd[2];
if (pipe(pipefd) == -1) {
LOGE("pipe error");
}
//updateEngine --update --image_url=path --partition=0x3a0000
char *args[6];
char args_1[20] = {0};
char args_2[URL_MAX_LENGTH] = {0};
char args_4[32] = {0};
args[0] = (char* )binary;
args[1] = args_1;
sprintf(args[1], "--pipefd=%d", pipefd[1]);
args[2] = args_2;
sprintf(args[2], "--image_url=%s", path);
if (bSDBootUpdate) {
char path_second[64] = {0};
char path_second_dir[64] = {0};
sprintf(path_second_dir, "%s", path);
dirname(path_second_dir);
sprintf(path_second, "%s/update_ab.img", path_second_dir);
if (access(path_second, F_OK) == 0) {
sprintf(args[2], "--image_url=%s", path_second);
} else {
sprintf(path_second, "%s/update_ota.img", path_second_dir);
if (access(path_second, F_OK) == 0) {
sprintf(args[2], "--image_url=%s", path_second);
}
}
}
LOGI("SDcard update data: [%s]\n", args[2]);
if (bSDBootUpdate) {
args[3] = (char *)update_sdboot;
} else {
args[3] = (char *)update;
}
args[4] = args_4;
args[5] = NULL;
if (bSDBootUpdate) {
// If SD boot, ignore --partition
sprintf(args[4], "--partition=0x%s", "FFFF00");
} else {
struct bootloader_message boot;
get_bootloader_message(&boot);
sprintf(args[4], "--partition=0x%X", *((int *)(boot.needupdate)));
}
return start_main(binary, args, pipefd);
}
2.10 finish_recovery
finish_recovery
主要进行一些收尾工作,比如:
-
保存升级日志到
/userdata/recovery/log
、/userdata/recovery/last_log
; -
清理
misc
分区偏移16k
位置数据,使得下次启动进入normal
系统;
比如我们在使用NanoPC-T4
开发板进行测试的时候,输出如下信息:
LOG_INFO: finish_recovery Enter.....
# ensure_path_mounted("/userdata/recovery/log"),检测挂载点/userdata/recovery失败
LOG_ERROR: unknown volume for path [/userdata/recovery/log]
LOG_ERROR: Can't mount /userdata/recovery/log
LOG_ERROR: Can't open /userdata/recovery/log
# ensure_path_mounted("/userdata/recovery/last_log"),检测挂载点/userdata/recovery失败
LOG_ERROR: unknown volume for path [/userdata/recovery/last_log]
LOG_ERROR: Can't mount /userdata/recovery/last_log
LOG_ERROR: Can't open /userdata/recovery/last_log
LOG_INFO: devices is not MTD.
# ensure_path_mounted("/userdata/recovery/command"),检测挂载点/userdata/recovery失败
LOG_ERROR: unknown volume for path [/userdata/recovery/command]
LOG_WARN: Can't unlink /userdata/recovery/command
LOG_INFO: recovery usage time:71632 ms
源码如下:
// clear the recovery command and prepare to boot a (hopefully working) system,
// copy our log file to cache as well (for the system to read), and
// record any intent we were asked to communicate back to the system.
// this function is idempotent: call it as many times as you like.
static void
finish_recovery(const char *send_intent) // 参数为null
{
// By this point, we're ready to return to the main system...
// 不会进入
if (send_intent != NULL) {
// 打开/userdata/recovery/intent文件
FILE *fp = fopen_path(INTENT_FILE, "w");
if (fp == NULL) {
LOGE("Can't open %s\n", INTENT_FILE);
} else {
fputs(send_intent, fp);
check_and_fclose(fp, INTENT_FILE);
}
}
LOGI("finish_recovery Enter.....\n");
// Copy logs to cache so the system can find out what happened.
// /userdata/recovery/log
// 将临时日志文件/tmp/recovery.log的内容追加到/userdata/recovery/log
copy_log_file(LOG_FILE, true);
// 将临时日志文件/tmp/recovery.log的内容写到/userdata/recovery/last_log(覆盖写入)
copy_log_file(LAST_LOG_FILE, false);
// 修改文件权限
chmod(LAST_LOG_FILE, 0640);
// Reset to mormal system boot so recovery won't cycle indefinitely.
// 如果升级成功,清空misc分区偏移16k位置数据,使得下次启动进入normal系统
struct bootloader_message boot;
memset(&boot, 0, sizeof(boot));
strlcpy(boot.systemFlag, systemFlag, sizeof(boot.systemFlag));
set_bootloader_message(&boot);
// Remove the command file, so recovery won't repeat indefinitely.
// path传入/userdata/recovery/command,则判定/userdata/recovery路径是否被挂在,很显然这里会返回-1
if (ensure_path_mounted(COMMAND_FILE) != 0 ||
(unlink(COMMAND_FILE) && errno != ENOENT)) {
LOGW("Can't unlink %s\n", COMMAND_FILE);
}
sync(); // For good measure.
}
2.10.1 copy_log_file
// 将临时日志文件/tmp/recovery.log的内容追加/覆盖写入到指定的目标文件中
static void copy_log_file(const char* destination, int append)
{
// 首先检测路径destination是否被挂载,只有被挂载了才会打开文件
FILE *log = fopen_path(destination, append ? "a" : "w");
// 在使用NanoPC-T4开发板进行测试时走到这一步就会返回
if (log == NULL) {
LOGE("Can't open %s\n", destination);
} else {
// /tmp/recovery.log
FILE *tmplog = fopen(TEMPORARY_LOG_FILE, "r");
if (tmplog == NULL) {
LOGE("Can't open %s\n", TEMPORARY_LOG_FILE);
} else {
// 如果是追加模式,则使用 fseek 将读取位置移动到tmplog_offset,确保从tmplog_offset位置开始复制
if (append) {
fseek(tmplog, tmplog_offset, SEEK_SET); // Since last write
}
char buf[4096];
while (fgets(buf, sizeof(buf), tmplog)) fputs(buf, log);
if (append) {
tmplog_offset = ftell(tmplog);
}
check_and_fclose(tmplog, TEMPORARY_LOG_FILE);
}
check_and_fclose(log, destination);
}
}
2.10.2 fopen_path
这里首先检测路径path
是否被挂载,只有被挂载了才会打开文件;
// open a given path, mounting partitions as necessary
static FILE*
fopen_path(const char *path, const char *mode)
{
// 确保路径path被挂载
// 方法第1次调用:path传入/userdata/recovery/log,则判定/userdata/recovery路径是否被挂在,很显然这里会返回-1
// 方法第2次调用:path传入/userdata/recovery/last_log,则判定/userdata/recovery路径是否被挂在,很显然这里会返回-1
if (ensure_path_mounted(path) != 0) {
LOGE("Can't mount %s\n", path);
return NULL;
}
// When writing, try to create the containing directory, if necessary.
// Use generous permissions, the system (init.rc) will reset them.
if (strchr("wa", mode[0])) dirCreateHierarchy(path, 0777, NULL, 1);
FILE *fp = fopen(path, mode);
return fp;
}
2.10.3 check_and_fclose
// close a file, log an error if the error indicator is set
static void
check_and_fclose(FILE *fp, const char *name)
{
fflush(fp);
if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno));
fclose(fp);
}
参考文章:
[1] Rockchip RK3588 - Rockchip Linux Recovery updateEngine
源码分析
[2] Rockchip RK3588 - Rockchip Linux Recovery updateEngine
测试