在编写设备驱动的时候,我们会填写device_driver的pm方法, 这些方法是必须要有的,系统无法正常休眠,设置导致死机。驱动实现例如:
static const struct dev_pm_ops raydium_ts_pm_ops = {
.suspend = raydium_ts_suspend,
.resume = raydium_ts_resume,
};
static struct i2c_driver raydium_ts_driver = {
.probe = raydium_ts_probe,
.remove = raydium_ts_remove,
.id_table = raydium_ts_id,
.driver = {
.name = RAYDIUM_NAME,
.owner = THIS_MODULE,
.of_match_table = raydium_match_table,
.pm = &raydium_ts_pm_ops,
},
};
我们在device_driver->dev_pm_ops里填写了设备suspend和resume方法,这两个是设备被挂起到RAM是的PM.
应用层操作操作系统进入休眠时,应用会写入操作/sys/power/state节点,往节点写入字符串"mem"可是系统挂起到RAM.
设备挂起到RAM流程如下:
state_store -> /kernel/kernel/power/main.c
pm_suspend(state); -> /kernel/kernel/power/suspend.c
enter_state(state);
suspend_devices_and_enter(state); -> /kernel/kernel/power/suspend.c
详细看下suspend_devices_and_enter方法的实现:
int suspend_devices_and_enter(suspend_state_t state)
{
error = platform_suspend_begin(state);
error = dpm_suspend_start(PMSG_SUSPEND);
if (error) {
pr_err("PM: Some devices failed to suspend, or early wake event detected\n");
log_suspend_abort_reason("Some devices failed to suspend, or early wake event detected");
goto Recover_platform;
}
}
我们关注dpm_suspend_start方法。如果改方法执行失败,此处将打印:
<3>[ 350.999928] PM: Some devices failed to suspend, or early wake event detected
<6>[ 351.081086] PM: resume of devices complete after 81.136 msecs
提示suspend失败。我们进入dpm_suspend_start方法,就可以看到suspend失败的原因了。
/kernel/drivers/base/power/main.c
int dpm_suspend_start(pm_message_t state)
{
int error;
// 执行设备 ->prepare,然后将设备键入到dpm_prepared_list链表中
error = dpm_prepare(state);
error = dpm_suspend(state);
return error;
}
dpm_prepare函数将从dpm_list链表中取出各个设备,这些设备是在设备注册是键入的。取出后调用设备的parpare回调方法并回调执行。最后将设备加入到dpm_prepared_list链表中。
int dpm_suspend(pm_message_t state)
{
cpufreq_suspend();
while (!list_empty(&dpm_prepared_list)) {
struct device *dev = to_device(dpm_prepared_list.prev);
error = device_suspend(dev); // 执行休眠
if (!list_empty(&dev->power.entry))
list_move(&dev->power.entry, &dpm_suspended_list);
put_device(dev); // 设备使用引用计数
}
}
dpm_suspend调用device_suspend继续休眠操作。休眠后,将对设备引用计数做自减。
static int __device_suspend(struct device *dev, pm_message_t state, bool async)
{
pm_callback_t callback = NULL;
struct timer_list timer;
init_timer_on_stack(&timer);
timer.expires = jiffies + HZ * 12;
timer.function = dpm_drv_timeout;
timer.data = (unsigned long)&data;
add_timer(&timer);
if (dev->pm_domain) {
info = "power domain ";
callback = pm_op(&dev->pm_domain->ops, state);
goto Run;
}
if (dev->type && dev->type->pm) {
info = "type ";
callback = pm_op(dev->type->pm, state);
goto Run;
}
if (dev->class) {
if (dev->class->pm) {
info = "class ";
callback = pm_op(dev->class->pm, state);
goto Run;
} else if (dev->class->suspend) {
pm_dev_dbg(dev, state, "legacy class ");
error = legacy_suspend(dev, state, dev->class->suspend,
"legacy class ");
goto End;
}
}
if (dev->bus) {
if (dev->bus->pm) {
info = "bus ";
callback = pm_op(dev->bus->pm, state);
} else if (dev->bus->suspend) {
pm_dev_dbg(dev, state, "legacy bus ");
error = legacy_suspend(dev, state, dev->bus->suspend,
"legacy bus ");
goto End;
}
}
Run:
if (!callback && dev->driver && dev->driver->pm) {
info = "driver ";
callback = pm_op(dev->driver->pm, state);
}
error = dpm_run_callback(callback, dev, state, info);
...
}
此处按照pm_domain->class->bus->driver的优先级,取得pm.suspend操作。对于SPI,I2C设备,pm操作在bus, class都是没有实现的,最终调用到device_driver中的suspend中。也就是我们最开始填写的dev_pm_ops中的resume和suspend函数。设备挂起到RAM操作就结束了。
除了挂起到RAM,dev_pm_ops中还有runtime resume和runtime suspend,也就是运行时PM。运行PM和挂起到RAM时的PM不太一样,运行时PM是针对单个设备,系统在非睡眠状态的情况下,某个设备在空闲时进入运行时挂起状态,而在不空闲时执行运行时回复,使得设备进入正常工作状态。如此,这个设备在运行时会更省电。