问题
发现在执行poweroff
之后通过按下 power 按键无法开机。
怀疑是在关机之后没有释放电源管理模块。
PMU数据手册
As with the enable sequence, a typical disable sequence is initiated when the user presses the push-button, which interrupts the processor via the nPBSTAT output. The actual disable sequence is completely software-controlled, but typically involved initiating various “clean-up” processes before the processor finally de-asserts PWRHLD.
也就是说,在执行完毕软件上的关机之后,需要拉低PWRHLD的电平
原理图
在原理图上,PMU上的PWRHLD与CPU的PSHOLD管脚是相连。
CPU数据手册
在CPU的数据手册中的PS_HOLD_CONTROL说明,定义了对PSHOLD的控制方法。

代码
在执行poweroff之后内核输出的最后一行是System halted.
。通过在内核中搜索,我发现:
kernel/sys.c
void kernel_halt(void)
{
kernel_shutdown_prepare(SYSTEM_HALT);
syscore_shutdown();
printk(KERN_EMERG "System halted.\n");
kmsg_dump(KMSG_DUMP_HALT);
machine_halt();
}
这里有一条语句输出了该信息,紧接着执行了machine_halt()
。
这个是机器相关的代码,应该在arch/
目录下。
紧接着,在arch/arm/kernel/process.c
中找到了machine_halt()
的实现:
void machine_halt(void)
{
machine_shutdown();
while (1);
}
实际上在这里,执行完machine_shutdown()
之后,CPU会进入死循环。而CPU的电源并没有被断开。
疑惑
不过我在观察代码上下文的时候发现也有一个kernel_power_off()
,这和kernel_halt()
有何不同呢?
我查阅了一下在内核中这两者的引用,发现这两者其实都是在reboot
的系统调用例程中:
switch (cmd) {
...
case LINUX_REBOOT_CMD_HALT:
kernel_halt();
do_exit(0);
panic("cannot halt");
case LINUX_REBOOT_CMD_POWER_OFF:
kernel_power_off();
do_exit(0);
break;
...
}
也就是说,如果cmd
为LINUX_REBOOT_CMD_HALT
的话,将执行kernel_halt()
;如果cmd
为LINUX_REBOOT_CMD_POWER_OFF
的话,将执行kernel_power_off()
。
这两者的区别是什么呢?
在reboot
系统调用的man手册中,对这两者分别作了说明:
LINUX_REBOOT_CMD_HALT
(RB_HALT_SYSTEM, 0xcdef0123; since Linux 1.1.76). The message "System halted." is printed, and the system is halted. Control is given to the ROM monitor, if there is one. If not preceded by a sync(2), data will be lost.
...
LINUX_REBOOT_CMD_POWER_OFF
(RB_POWER_OFF, 0x4321fedc; since Linux 2.1.30). The message "Power down." is printed, the system is stopped, and all power is removed from the system, if possible. If not preceded by a sync(2), data will be lost.
但是为什么我这里执行的是kernel_halt()
而不是kernel_power_off()
呢?
在switch (cmd)
之前,有:
if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
cmd = LINUX_REBOOT_CMD_HALT;
即:若没有实现pm_power_off()
,则使用kernel_halt()
而不是kernel_power_off()
。
arch/arm/kernel/process.c
中machine_power_off()
代码如下:
void machine_power_off(void)
{
machine_shutdown();
if (pm_power_off)
pm_power_off();
}
与machine_halt()
的区别在于:
{
machine_shutdown();
- while (1);
+ if (pm_power_off)
+ pm_power_off();
}
即machine_halt()
是让CPU陷入死循环,machine_power_off()
则是执行pm_power_off()
。而这里pm_power_off()
需要实现的功能是让PMU断开CPU的供电。
代码
从上面可以看到,现在的问题所在是针对这块板子的pm_power_off()
没有实现。
而pm_power_off()
所需要执行的工作就是拉低PMU的PWRHLD,即CPU上的PSHOLD。
{
u32 tmp;
tmp = __raw_readl(S5P_PMU_PS_HOLD_CONTROL);
tmp |= BIT(9);
tmp &= ~BIT(8);
__raw_writel(tmp, S5P_PMU_PS_HOLD_CONTROL);
}