Android reboot那点事儿

背景介绍

最近开发过程中遇到了一个问题,在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.总结一下

  1. 首先reboot指令set property sys.powerctl,触发init进程中property_changed,通过赋值do_shutdown之后触发HandlePowerctlMessage()来处理关机请求,通过一系列的解析调用,最终通过系统调用reboot并传入cmd进入内核;
  2. 内核收到系统调用后,调用kernel_restart函数->machine_restart->arm_pm_restart,最终调用高通原厂driver中的do_msm_restart函数,进而通过函数msm_restart_prepare,将cmd对应值写入restart_reason寄存器,之后重启交给bootloader;
  3. bootloader准备引导系统之前,通过函数check_reboot_mode读取restart_reason寄存器,通过读取寄存器内保存的值来判定启动模式,包含normal/fastboot/recovery/edl等。

原本想把BCB也介绍一下,应用程序通过调整BCB内部command,也可以起到引导系统的作用,不过这个只针对recovery,通过该command引导recovery启动,进入recovery后还会进一步解析BCB指令内容,最终可以达到自动升级的目的。

但本篇内容已经不短了,清明节后会将BCB的内容输出一片新帖。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值