背景介绍
最近开发过程中遇到了一个问题,在recovery里没有reboot指令,然而用busybox内置的reboot指令只能起到重启的作用,并不能带参数(reboot recovery/bootloader)进入我想要的模式,这引发了我对reboot指令的思考。
这篇讲讲Android指令reboot是重启系统,并进入不同的启动模式,常用的启动模式包括normal/fastboot/recovery/edl。
开发环境:Android O/Linux 3.18.71/SDM450
源码分析
1.reboot
reboot是Android源码编译出来的可执行文件,位置在/system/core/reboot/
#define ANDROID_RB_PROPERTY "sys.powerctl"
int main(int argc, char *argv[])
{
...
do {
...
c = getopt(argc, argv, "p");
if (c == -1) {
break;
}
switch (c) {
case 'p':
cmd = "shutdown";
break;
...
} while (1);
...
prop_len = snprintf(property_val, sizeof(property_val), "%s,%s", cmd, optarg);
ret = property_set(ANDROID_RB_PROPERTY, property_val);
...
}
源码中清晰可见reboot指令调用关系,reboot -p则cmd直接赋值shutdown,走关机流程,当然你也可以直接输入reboot shutdown,或者是我们常用的reboot bootloader/recovery;
最终都是将reboot与cmd组合起来保存到变量property_val,通过调用property_set函数保存在sys.powerctl这个property里;
2.property “sys.powerctl”
property set后触发property changed方法,/system/core/init/init.cpp
void property_changed(const std::string& name, const std::string& value) {
// If the property is sys.powerctl, we bypass the event queue and immediately handle it.
// This is to ensure that init will always and immediately shutdown/reboot, regardless of
// if there are other pending events to process or if init is waiting on an exec service or
// waiting on a property.
// In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific
// commands to be executed.
if (name == "sys.powerctl") {
// Despite the above comment, we can't call HandlePowerctlMessage() in this function,
// because it modifies the contents of the action queue, which can cause the action queue
// to get into a bad state if this function is called from a command being executed by the
// action queue. Instead we set this flag and ensure that shutdown happens before the next
// command is run in the main init loop.
// TODO: once property service is removed from init, this will never happen from a builtin,
// but rather from a callback from the property service socket, in which case this hack can
// go away.
shutdown_command = value;
do_shutdown = true;
}
...
}
这个方法是专门用来处理property变更的,可以看到方法中对sys.powerctl这个property开了小灶做了单独处理,直接将do_shutdown赋值为true,之后触发init主线程调用HandlePowerctlMessage方法处理重启请求;
while (true) {
// By default, sleep until something happens.
int epoll_timeout_ms = -1;
if (do_shutdown && !shutting_down) {
do_shutdown = false;
if (HandlePowerctlMessage(shutdown_command)) {
shutting_down = true;
}
}
...
}
/system/core/init/reboot.cpp HandlePowerctlMessage方法
bool HandlePowerctlMessage(const std::string& command) {
unsigned int cmd = 0;
std::vector<std::string> cmd_params = android::base::Split(command, ",");
std::string reboot_target = "";
bool run_fsck = false;
bool command_invalid = false;
LOG(ERROR) << "HandlePowerctlMessage() param size is "
<< cmd_params.size()
<< ", para 1 is " << cmd_params[1];
if (cmd_params.size() > 3) {
command_invalid = true;
} else if (cmd_params[0] == "shutdown") {
cmd = ANDROID_RB_POWEROFF;
if (cmd_params.size() == 2 && cmd_params[1] == "userrequested") {
// The shutdown reason is PowerManager.SHUTDOWN_USER_REQUESTED.
// Run fsck once the file system is remounted in read-only mode.
run_fsck = true;
}
} else if (cmd_params[0] == "reboot") {
cmd = ANDROID_RB_RESTART2;
if (cmd_params.size() >= 2) {
reboot_target = cmd_params[1];
// When rebooting to the bootloader notify the bootloader writing
// also the BCB.
if (reboot_target == "bootloader") {
std::string err;
LOG(ERROR) << "HandlePowerctlMessage() reboot target bootloader";
if (!write_reboot_bootloader(&err)) {
LOG(ERROR) << "reboot-bootloader: Error writing "
"bootloader_message: "
<< err;
}
}
// If there is an additional bootloader parameter, pass it along
if (cmd_params.size() == 3) {
reboot_target += "," + cmd_params[2];
}
}
}
...
}
这一步主要是解析reboot指令,通过指令得出最终传入内核的cmd是ANDROID_RB_POWEROFF还是ANDROID_RB_RESTART2;
其中bootloader的case里面有通过write_reboot_bootloader方法调整BCB(bootloader control block),方法源码在/bootable/recovery/bootloader_message/bootloader_message.cpp,这里不贴了感兴趣自己找一下;
通过该方法将command“bootonce-bootloader”更新到BCB中,但最终bootloader并没有用到这个command,所以写这个的原因我现在还不清楚,知道的评论区指点一二;
HandlePowerctlMessage方法解析完毕后进而调用DoReboot()->RebootSystem(),最终也是在RebootSystem方法中通过系统调用进入内核;
static void __attribute__((noreturn))
RebootSystem(unsigned int cmd, const std::string& rebootTarget) {
LOG(INFO) << "Reboot ending, jumping to kernel" << " rebootTarget: " << rebootTarget;
if (!IsRebootCapable()) {
// On systems where init does not have the capability of rebooting the
// device, just exit cleanly.
exit(0);
}
switch (cmd) {
case ANDROID_RB_POWEROFF:
LOG(INFO) << "Reboot ANDROID_RB_POWEROFF";
reboot(RB_POWER_OFF);
break;
case ANDROID_RB_RESTART2:
LOG(INFO) << "Reboot ANDROID_RB_RESTART2";
syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, rebootTarget.c_str());
break;
case ANDROID_RB_THERMOFF:
LOG(INFO) << "Reboot ANDROID_RB_THERMOFF";
reboot(RB_POWER_OFF);
break;
}
// In normal case, reboot should not return.
PLOG(FATAL) << "reboot call returned";
abort();
}
至此整个reboot流程中应用层的工作已经完成,后面就交给kernel跟bootloader;最后附上reboot相关宏定义,ndk里随便找了个arm架构的/sys/reboot.h;
#ifndef _SYS_REBOOT_H_
#define _SYS_REBOOT_H_
#include <sys/cdefs.h>
#include <linux/reboot.h>
__BEGIN_DECLS
/* use glibc names as well */
#define RB_AUTOBOOT LINUX_REBOOT_CMD_RESTART
#define RB_HALT_SYSTEM LINUX_REBOOT_CMD_HALT
#define RB_ENABLE_CAD LINUX_REBOOT_CMD_CAD_ON
#define RB_DISABLE_CAD LINUX_REBOOT_CMD_CAD_OFF
#define RB_POWER_OFF LINUX_REBOOT_CMD_POWER_OFF
extern int reboot(int reboot_type);
__END_DECLS
#endif /* _SYS_REBOOT_H_ */
3.Reboot system call
/kernel/msm-3.18/kernel/reboot.c
/*
* Reboot system call: for obvious reasons only root may call it,
* and even root needs to set up some magic numbers in the registers
* so that some mistake won't make this reboot the whole machine.
* You can also set the meaning of the ctrl-alt-del-key here.
*
* reboot doesn't sync: do that yourself before calling this.
*/
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
void __user *, arg)
{
...
switch (cmd) {
...
case LINUX_REBOOT_CMD_POWER_OFF:
kernel_power_off();
do_exit(0);
break;
case LINUX_REBOOT_CMD_RESTART2:
ret = strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1);
if (ret < 0) {
pr_err("%s: string copy from user space failed!\n", __func__);
ret = -EFAULT;
break;
}
buffer[sizeof(buffer) - 1] = '\0';
kernel_restart(buffer);
break;
...
}
根据应用层传入的cmd,主要用的就这俩LINUX_REBOOT_CMD_POWER_OFF对应shutdown走关机流程,LINUX_REBOOT_CMD_RESTART2走重启流程,这里我们关注一下重启流程,调用kernel_restart()函数;
/**
* kernel_restart - reboot the system
* @cmd: pointer to buffer containing command to execute for restart
* or %NULL
*
* Shutdown everything and perform a clean reboot.
* This is not safe to call in interrupt context.
*/
void kernel_restart(char *cmd)
{
kernel_restart_prepare(cmd);
migrate_to_reboot_cpu();
syscore_shutdown();
if (!cmd)
pr_emerg("Restarting system\n");
else
pr_emerg("Restarting system with command '%s'\n", cmd);
kmsg_dump(KMSG_DUMP_RESTART);
machine_restart(cmd);
}
EXPORT_SYMBOL_GPL(kernel_restart);
调用machine_restart(cmd);/kernel/msm-3.18/arch/arm/kernel/process.c
/*
* Restart requires that the secondary CPUs stop performing any activity
* while the primary CPU resets the system. Systems with a single CPU can
* use soft_restart() as their machine descriptor's .restart hook, since that
* will cause the only available CPU to reset. Systems with multiple CPUs must
* provide a HW restart implementation, to ensure that all CPUs reset at once.
* This is required so that any code running after reset on the primary CPU
* doesn't have to co-ordinate with other CPUs to ensure they aren't still
* executing pre-reset code, and using RAM that the primary CPU's code wishes
* to use. Implementing such co-ordination would be essentially impossible.
*/
void machine_restart(char *cmd)
{
local_irq_disable();
smp_send_stop();
/* Flush the console to make sure all the relevant messages make it
* out to the console drivers */
arm_machine_flush_console();
if (arm_pm_restart)
arm_pm_restart(reboot_mode, cmd);
else
do_kernel_restart(cmd);
/* Give a grace period for failure to restart of 1s */
mdelay(1000);
/* Whoops - the platform was unable to reboot. Tell the user! */
printk("Reboot failed -- System halted\n");
local_irq_disable();
while (1);
}
调用arm_pm_restart(),这里通过调查发现函数指针非零,赋值的地方在高通的一个原厂driver内部;
/kernel/msm-3.18/drivers/power/reset/msm-poweroff.c;
static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd)
{
pr_notice("Going down for restart now\n");
msm_restart_prepare(cmd);
#ifdef CONFIG_MSM_DLOAD_MODE
/*
* Trigger a watchdog bite here and if this fails,
* device will take the usual restart path.
*/
if (WDOG_BITE_ON_PANIC && in_panic)
msm_trigger_wdog_bite();
#endif
scm_disable_sdi();
halt_spmi_pmic_arbiter();
deassert_ps_hold();
mdelay(10000);
}
static int msm_restart_probe(struct platform_device *pdev)
{
...
pm_power_off = do_msm_poweroff;
arm_pm_restart = do_msm_restart;
...
}
所以最终reboot调用进入do_msm_restart()函数,在进入msm_restart_prepare()来到了问题的核心代码:
static void msm_restart_prepare(const char *cmd)
{
...
if (cmd != NULL) {
if (!strncmp(cmd, "bootloader", 10)) {
qpnp_pon_set_restart_reason(
PON_RESTART_REASON_BOOTLOADER);
__raw_writel(0x77665500, restart_reason);
} else if (!strncmp(cmd, "recovery", 8)) {
qpnp_pon_set_restart_reason(
PON_RESTART_REASON_RECOVERY);
__raw_writel(0x77665502, restart_reason);
} else if (!strcmp(cmd, "rtc")) {
qpnp_pon_set_restart_reason(
PON_RESTART_REASON_RTC);
__raw_writel(0x77665503, restart_reason);
} else if (!strcmp(cmd, "dm-verity device corrupted")) {
qpnp_pon_set_restart_reason(
PON_RESTART_REASON_DMVERITY_CORRUPTED);
__raw_writel(0x77665508, restart_reason);
} else if (!strcmp(cmd, "dm-verity enforcing")) {
qpnp_pon_set_restart_reason(
PON_RESTART_REASON_DMVERITY_ENFORCE);
__raw_writel(0x77665509, restart_reason);
} else if (!strcmp(cmd, "keys clear")) {
qpnp_pon_set_restart_reason(
PON_RESTART_REASON_KEYS_CLEAR);
__raw_writel(0x7766550a, restart_reason);
} else if (!strncmp(cmd, "oem-", 4)) {
unsigned long code;
int ret;
ret = kstrtoul(cmd + 4, 16, &code);
if (!ret)
__raw_writel(0x6f656d00 | (code & 0xff),
restart_reason);
} else if (!strncmp(cmd, "edl", 3)) {
enable_emergency_dload_mode();
} else {
__raw_writel(0x77665501, restart_reason);
}
}
...
}
通过传入的cmd不同来区分不同的case,在这里reboot指令能够支持的cmd也一览无余,我还意外发现了edl指令,进入emergency download模式刷机;代码逻辑也一目了然,cmd不同对应值也不同,最终都会写入restart_reason寄存器;
由于我的调查过程没有打印太多日志确认调用关系,这里我们可以通过调查restart reason寄存器的地址,稍后在bootloader里核对一下;
/* dtsi config */
qcom,msm-imem@8600000 {
compatible = "qcom,msm-imem";
reg = <0x08600000 0x1000>;
ranges = <0x0 0x08600000 0x1000>;
#address-cells = <1>;
#size-cells = <1>;
...
restart_reason@65c {
compatible = "qcom,msm-imem-restart_reason";
reg = <0x65c 4>;
};
...
};
/* linux driver parse dts */
np = of_find_compatible_node(NULL, NULL,
"qcom,msm-imem-restart_reason");
if (!np) {
pr_err("unable to find DT imem restart reason node\n");
} else {
restart_reason = of_iomap(np, 0);
if (!restart_reason) {
pr_err("unable to map imem restart reason offset\n");
ret = -ENOMEM;
goto err_restart_reason;
}
}
通过字符串“qcom,msm-imem-restart_reason”我们找到对应dts节点,它的父节点基地址为0x08600000,他自己的地址是0x65c,所以内核中restart_reason的地址就是0x08600000 + 0x65c;
到此内核已将restart reason写入了约定好的地址,内核也已完成了他的工作,后面就是重启系统再次进入bootloader。
4.bootloader check restart reason
我们直接来到LK中引导系统的核心函数aboot_init(),/bootable/bootloader/lk/app/aboot/aboot.c
bool boot_into_fastboot = false;
extern unsigned boot_into_recovery;
void aboot_init(const struct app_descriptor *app)
{
#if USE_PON_REBOOT_REG
reboot_mode = check_hard_reboot_mode();
#else
reboot_mode = check_reboot_mode();
#endif
}
boot_into_fastboot跟boot_into_recovery均为全局变量,用来记录是否进入fastboot跟recovery;
我们在LK的config.h中并未看到USE_PON_REBOOT_REG的定义,所以调用下面的函数check_reboot_mode;
/bootable/bootloader/lk/platform/msm_shared/reboot.c;
#define FASTBOOT_MODE 0x77665500
#define RECOVERY_MODE 0x77665502
unsigned check_reboot_mode(void)
{
uint32_t restart_reason = 0;
/* Read reboot reason and scrub it */
restart_reason = readl(RESTART_REASON_ADDR);
writel(0x00, RESTART_REASON_ADDR);
return restart_reason;
}
代码简短清晰,可以看到LK中也是读取了一个寄存器的值并保存到restart_reason,两个启动模式对应的值也与内核中写入的值相吻合,最后我们看一下RESTART_REASON_ADDR寄存器是否也能与上述吻合,做最终的check;
/bootable/bootloader/lk/platform/msm8953/include/platform/iomap.h
#define MSM_SHARED_IMEM_BASE 0x08600000
#define RESTART_REASON_ADDR (MSM_SHARED_IMEM_BASE + 0x65C)
通过对应平台的寄存器定义可以看到,该地址同样也为0x08600000 + 0x65c,与内核相吻合,可以证实内核与bootloader就是通过该寄存器来体现启动模式的。
之后的事,就是bootloader对相应变量赋值,判断进入哪种启动模式。
5.总结一下
- 首先reboot指令set property sys.powerctl,触发init进程中property_changed,通过赋值do_shutdown之后触发HandlePowerctlMessage()来处理关机请求,通过一系列的解析调用,最终通过系统调用reboot并传入cmd进入内核;
- 内核收到系统调用后,调用kernel_restart函数->machine_restart->arm_pm_restart,最终调用高通原厂driver中的do_msm_restart函数,进而通过函数msm_restart_prepare,将cmd对应值写入restart_reason寄存器,之后重启交给bootloader;
- bootloader准备引导系统之前,通过函数check_reboot_mode读取restart_reason寄存器,通过读取寄存器内保存的值来判定启动模式,包含normal/fastboot/recovery/edl等。
原本想把BCB也介绍一下,应用程序通过调整BCB内部command,也可以起到引导系统的作用,不过这个只针对recovery,通过该command引导recovery启动,进入recovery后还会进一步解析BCB指令内容,最终可以达到自动升级的目的。
但本篇内容已经不短了,清明节后会将BCB的内容输出一片新帖。