写在前面
进行Linux驱动开发的同学们,应该会经历过调试休眠唤醒的方案。休眠唤醒机制的目的是为了让设备在不需要执行动作时,进入一种低功耗状态,可以在设备进入suspend(休眠)的时候,对其进行下电,在进入resume(唤醒)的时候,再对其进行上电操作。本文重点对于休眠唤醒过程中的device的执行顺序进行分析。
场景假设
假设有如下一场景: 自己添加的设备my_dev在唤醒的时候需要通过other_dev中的获取的数据,那么,系统必须确保唤醒时other_dev早于自己的设备my_dev。
设备的休眠唤醒顺序
其实决定device休眠唤醒顺序的数据结构为dpm_list。向该链表中添加数据通过device_pm_add函数。
suspend的顺序为dpm_list链表的从尾到头的设备。
resume的顺序为dpm_list链表的从头到尾的设备。
类型1:通过device-tree添加设备
普遍来说,如果一个设备树节点包含compatible属性,那么其在开机过程中会自动转化为一个device。
假设我们在device-tree的"/"节点下,添加一个自己的结点xp_test_node1:
rk_wlan: wireless-wlan {
compatible = "wlan-platdata";
rockchip,grf = <&grf>;
wifi_chip_type = "ap6354";
sdio_vref = <1800>;
WIFI,host_wake_irq = <&gpio0 3 GPIO_ACTIVE_HIGH>; /* GPIO0_a3 */
status = "okay";
};
rk_bluetooth: wireless-bluetooth {
compatible = "bluetooth-platdata";
clocks = <&rk808 1>;
clock-names = "ext_clock";
//wifi-bt-power-toggle;
uart_rts_gpios = <&gpio2 19 GPIO_ACTIVE_LOW>; /* GPIO2_C3 */
pinctrl-names = "default", "rts_gpio";
pinctrl-0 = <&uart0_rts>;
pinctrl-1 = <&uart0_gpios>;
//BT,power_gpio = <&gpio3 19 GPIO_ACTIVE_HIGH>; /* GPIOx_xx */
BT,reset_gpio = <&gpio0 9 GPIO_ACTIVE_HIGH>; /* GPIO0_B1 */
BT,wake_gpio = <&gpio2 26 GPIO_ACTIVE_HIGH>; /* GPIO2_D2 */
BT,wake_host_irq = <&gpio0 4 GPIO_ACTIVE_HIGH>; /* GPIO0_A4 */
status = "okay";
};
test-power {
status = "okay";
};
/* my_dts_node */
xp_test_node1: xp_test_node1 {
compatible = "timelessxp,test_dts_node";
status = "ok";
};
我们在kernel/drivers/base/power/main.c中的device_pm_add函数中通过打调用栈的方式来定位这个过程。
void device_pm_add(struct device *dev)
{
/* Skip PM setup/initialization. */
if (device_pm_not_required(dev))
return;
pr_debug("PM: Adding info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
device_pm_check_callbacks(dev);
mutex_lock(&dpm_list_mtx);
pr_err("%s: %s->", __func__, dev_name(dev));
if (strcmp(dev_name(dev), "xp_test_node1") == 0) {
pr_err("%s: %s->", __func__, dev_name(dev));
dump_stack();
}
if (dev->parent && dev->parent->power.is_prepared)
dev_warn(dev, "parent %s should not be sleeping\n",
dev_name(dev->parent));
list_add_tail(&dev->power.entry, &dpm_list);
dev->power.in_dpm_list = true;
mutex_unlock(&dpm_list_mtx);
}
打印出的调用栈如下:
...
[ 0.160441] device_pm_add: edp-panel->
[ 0.160868] device_pm_add: adc-keys->
[ 0.161293] device_pm_add: gpio-leds->
[ 0.161713] device_pm_add: sdio-pwrseq->
[ 0.162142] device_pm_add: wireless-wlan->
[ 0.162600] device_pm_add: wireless-bluetooth->
[ 0.163059] device_pm_add: xp_test_node1->
[ 0.163569] device_pm_add: xp_test_node1->
[ 0.163967] CPU: 5 PID: 1 Comm: swapper/0 Not tainted 4.19.111 #9
[ 0.164939] Hardware name: FriendlyElec NanoPC-T4 (DT)
[ 0.165431] Call trace:
[ 0.165679] dump_backtrace+0x0/0x178
[ 0.166032] show_stack+0x14/0x20
[ 0.166360] dump_stack+0x94/0xb4
[ 0.166683] device_pm_add+0x11c/0x128
[ 0.167046] device_add+0x338/0x6d8
[ 0.167387] of_device_add+0x5c/0x70
[ 0.167731] of_platform_device_create_pdata+0xac/0x108
[ 0.168232] of_platform_bus_create+0x13c/0x358
[ 0.168667] of_platform_populate+0x74/0xd0
[ 0.169073] of_platform_default_populate_init+0xac/0xc8
[ 0.169584] do_one_initcall+0x48/0x240
[ 0.169956] kernel_init_freeable+0x210/0x37c
[ 0.170376] kernel_init+0x10/0x108
[ 0.170713] ret_from_fork+0x10/0x18
...
由此可见,通过device-tree中添加的device,添加到dpm_list的顺序为:
of_platform_default_populate_init
-> of_platform_populate
-> of_platform_bus_create
-> of_platform_device_create_pdata
-> of_device_add
-> device_add
-> device_pm_add
另外从日志中我们可以看到
[ 0.162142] device_pm_add: wireless-wlan->
[ 0.162600] device_pm_add: wireless-bluetooth->
[ 0.163059] device_pm_add: xp_test_node1->
添加的顺序为我们在设备树中添加节点的顺序。