目录
[1、device PM callbacks](#1、device PM callbacks)
[2、dev_pm_ops 结构体](#2、dev_pm_ops 结构体)
[3、设备模型中的 dev_pm_ops](#3、设备模型中的 dev_pm_ops)
[5、platform bus suspend](#5、platform bus suspend)
[6、suspend virtio_mmio driver](#6、suspend virtio_mmio driver)
Linux Version:linux-5.4.239
前言
在一个操作系统中,外部设备应该是数量最多,且耗电最严重的了,所以对外设的电源管理就尤为重要了,因为这会直接影响一个系统 suspend 时的功耗。而对外设的电源管理其核心就是当系统 suspend 时,外设能够正常 suspend 或关闭,而当系统 resume 时,外设能够正常返回到工作状态。
1、device PM callbacks
一个系统那这么多的外设是它怎样管理的呢?以 Linux 系统为例,在旧版本内核中是通过一系列的 device PM callbacks 实现的,但是随着系统设计越来越复杂,简单的 callbacks 已经不能满足要求,所以在新版本的 Linux kernel 中则是通过 struct dev_pm_ops 结构体对这些 device PM callbacks 实现统一的管理。
这里以 struct device_driver 为例,如下:
objectivec
// include/linux/device.h
struct device_driver {
const char *name;
struct bus_type *bus;
......
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
......
const struct dev_pm_ops *pm;
void (*coredump) (struct device *dev);
struct driver_private *p;
};
shutdown、suspend、resume 这些函数指针就是旧版本 Linux kernel 的 device PM callbacks,而在新版本中则是通过 dev_pm_ops 实现对外设的电源管理。
2、dev_pm_ops 结构体
struct dev_pm_ops 结构体如下:
objectivec
// include/linux/pm.h
struct dev_pm_ops {
int (*prepare)(struct device *dev);
void (*complete)(struct device *dev);
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
int (*freeze)(struct device *dev);
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
int (*suspend_late)(struct device *dev);
int (*resume_early)(struct device *dev);
int (*freeze_late)(struct device *dev);
int (*thaw_early)(struct device *dev);
int (*poweroff_late)(struct device *dev);
int (*restore_early)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
int (*freeze_noirq)(struct device *dev);
int (*thaw_noirq)(struct device *dev);
int (*poweroff_noirq)(struct device *dev);
int (*restore_noirq)(struct device *dev);
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};
dev_pm_ops 结构体在对老版本 device PM callbacks 进行封装的前提下又定义了非常多的 callbacks,在系统 suspend 时会依次调用 prepare--->suspend--->suspend_late--->suspend_noirq 这些回调,而在 resume 时则会调用 resume_noirq---> resume_ early--->resume 等相关回调,在系统 suspend/resume 的不同阶段调用不同的回调函数,实现对设备电源的统一管理。
3、设备模型中的 dev_pm_ops
通过前面 struct device_driver 结构体也可以看出 dev_pm_ops 结构体一般是和设备模型相关的结构体一起使用的,在 bus_type、device_driver、class、device_type 等设备模型的结构体中包含 dev_pm_ops 即可,如下:
objectivec
// include/linux/device.h
struct bus_type {
......
// 老版本 PM callbacks
int (*suspend)(struct device *dev, pm_message_t state);
const struct dev_pm_ops *pm;
......
};
struct device_driver {
......
// 老版本 PM callbacks
int (*suspend) (struct device *dev, pm_message_t state);
const struct dev_pm_ops *pm;
......
};
struct class {
......
const struct dev_pm_ops *pm;
......
};
struct device_type {
......
const struct dev_pm_ops *pm;
};
struct device {
......
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
......
};
其中dev_pm_info 结构体主要保存和电源管理相关的状态,如当前的power_state、prepare 是否完成、suspend 是否完成等,如下:
objectivec
// include/linux/pm.h
struct dev_pm_info {
pm_message_t power_state;
unsigned int can_wakeup:1;
unsigned int async_suspend:1;
bool in_dpm_list:1; /* Owned by the PM core */
bool is_prepared:1; /* Owned by the PM core */
bool is_suspended:1; /* Ditto */
......
#ifdef CONFIG_PM_SLEEP
......
bool no_pm_callbacks:1; /* Owned by the PM core */
unsigned int must_resume:1; /* Owned by the PM core */
unsigned int may_skip_resume:1; /* Set by subsystems */
#else
unsigned int should_wakeup:1;
#endif
#ifdef CONFIG_PM
struct hrtimer suspend_timer;
u64 timer_expires;
struct work_struct work;
wait_queue_head_t wait_queue;
struct wake_irq *wakeirq;
......
#endif
......
};
dev_pm_domain 结构体则是考虑到power共用的情况,当一个设备属于一个电源域(power domain)时,设备发起suspend/resume必须要让power domain framework 知道当前设备的状态,这样才能在适当的时间去关闭或者打开设备,但实现具体suspend和resume的代码肯定还是需要驱动自己编写,因为只有驱动对自己的设备最熟悉了。
dev_pm_domain 如下:
objectivec
// include/linux/pm.h
struct dev_pm_domain {
struct dev_pm_ops ops;
void (*detach)(struct device *dev, bool power_off);
int (*activate)(struct device *dev);
void (*sync)(struct device *dev);
void (*dismiss)(struct device *dev);
};
4、调用流程
这里以suspend为例进行分析,操作系统在 suspend 的过程中,会根据设备模型中的dev_pm_ops结构体按照如下的顺序调用相应的回调
dev->pm_domain->ops
dev->type->pm
dev->class->pm
dev->bus->pm
dev->driver->pm
具体代码如下:
objectivec
// drivers/base/power/main.c
static pm_callback_t pm_op(const struct dev_pm_ops *ops, pm_message_t state)
{
switch (state.event) {
#ifdef CONFIG_SUSPEND
case PM_EVENT_SUSPEND:
return ops->suspend;
case PM_EVENT_RESUME:
return ops->resume;
#endif /* CONFIG_SUSPEND */
......
}
return NULL;
}
static int __device_suspend(struct device *dev, pm_message_t state, bool async)
{
......
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 && dev->class->pm) {
info = "class ";
callback = pm_op(dev->class->pm, state);
goto Run;
}
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:
// 如果这里还没有获取到 callback 并且 dev->driver 为真,那就设置 callback 为 driver 中的 suspend 函数
if (!callback && dev->driver && dev->driver->pm) {
info = "driver ";
callback = pm_op(dev->driver->pm, state);
}
error = dpm_run_callback(callback, dev, state, info);
......
}
5、platform bus suspend
现在如果想 suspend 一个设备,那到底是用 type、class 、bus、driver 中的那一个 suspend 回调呢?这里将会以platform bus 的 suspend 进行分析,看看 bus 中的 suspend 做了什么操作。
通过前面 __device_suspend 函数可以得出,如果 bus 中有提供了 dev_pm_ops 结构体,即dev->bus->pm 为真,则会调用bus->pm 下的 suspend 函数,platform bus 初始化如下:
objectivec
// drivers/base/platform.c
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
static const struct dev_pm_ops platform_dev_pm_ops = {
......
USE_PLATFORM_PM_SLEEP_OPS
};
// include/linux/platform_device.h
#define USE_PLATFORM_PM_SLEEP_OPS \
.suspend = platform_pm_suspend, \
.resume = platform_pm_resume, \
......
可以上面的代码段看到 platform_bus 中的 pm = platform_dev_pm_ops,而在 platform_dev_pm_ops 函数中将 suspend 函数初始化为 platform_pm_suspend 函数了,所以在 __device_suspend 函数中调用的 dev->bus->pm->suspend 函数就是调用的这个函数了,如下:
objectivec
// drivers/base/platform.c
int platform_pm_suspend(struct device *dev)
{
struct device_driver *drv = dev->driver;
int ret = 0;
if (!drv)
return 0;
if (drv->pm) {
if (drv->pm->suspend)
ret = drv->pm->suspend(dev);
} else {
ret = platform_legacy_suspend(dev, PMSG_SUSPEND);
}
return ret;
}
这个函数一开始就会通过 struct device 结构体获取到 struct device_driver,然后会判断 device_driver 中的 pm (dev_pm_ops)是否有提供,如果有提供那就调用驱动中的 suspend 函数,否则,调用legacy的接口,即 struct platform_driver *pdrv -> suspend,可以看到,当调用 bus 中的 pm->suspend 函数时,其实最终是调用的 dev->bus->pm,即驱动中的 suspend 函数,因为如果想 suspend 一个设备时只有设备自己的驱动程序最清楚要做什么,所以最后还是调用的驱动中的 suspend 函数。
但是,因为platform bus是一个虚拟的bus,所以不需要做一些和硬件相关的操作,而对于一些物理bus,就需要在 bus 的 suspend 函数中实现 bus suspend 的操作逻辑。
6、suspend virtio_mmio driver
下面将以一张图片来展示实际的 platform driver 的 suspend 流程,如下:
virtio-mmio 是通过 platform bus 进行初始化的,在 virtio_mmio_probe 函数中调用 register_virtio_device 函数注册一个 virtio device ,并挂到了 virtio bus 上面,然后初始化 dev_pm_ops 结构体,即 pm->suspend = virtio_mmio_freeze,通过前面的分析可以得出当调用 platform bus中的 pm->suspend 函数时,会调用 platform driver 中的 suspend 函数,即 virtio_mmio_freeze,如下:
objectivec
// drivers/virtio/virtio_mmio.c
static int virtio_mmio_freeze(struct device *dev)
{
struct virtio_mmio_device *vm_dev = dev_get_drvdata(dev);
return virtio_device_freeze(&vm_dev->vdev);
}
int virtio_device_freeze(struct virtio_device *dev)
{
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
virtio_config_disable(dev);
dev->failed = dev->config->get_status(dev) & VIRTIO_CONFIG_S_FAILED;
if (drv && drv->freeze)
return drv->freeze(dev);
return 0;
}
在 platform driver 的 pm->suspend 函数中就会处理和具体 device 有关的 suspend 操作,因为这里是 virtio 设备,所以在平台 bus 下面可能注册了很多的 virtio device,并在 kernel 中注册 virtio driver,这些 virtio device/driver 都被挂在了 virtio bus 下面,所以在 virtio_device_freeze 函数中还会继续调用 virtio_driver 中执行 suspend 的函数,即 drv->freeze(dev)。
这里以 virtio_driver 为例,如下:
objectivec
// drivers/block/virtio_blk.c
static struct virtio_driver virtio_blk = {
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.feature_table_legacy = features_legacy,
.feature_table_size_legacy = ARRAY_SIZE(features_legacy),
.driver.name = KBUILD_MODNAME,
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtblk_probe,
.remove = virtblk_remove,
.config_changed = virtblk_config_changed,
#ifdef CONFIG_PM_SLEEP
.freeze = virtblk_freeze,
.restore = virtblk_restore,
#endif
};
virtblk_freeze 函数就是 virtio blk driver 在系统 suspend 时需要做的事情,如下:
objectivec
// drivers/block/virtio_blk.c
static int virtblk_freeze(struct virtio_device *vdev)
{
struct virtio_blk *vblk = vdev->priv;
/* Ensure we don't receive any more interrupts */
vdev->config->reset(vdev);
/* Make sure no work handler is accessing the device. */
flush_work(&vblk->config_work);
blk_mq_quiesce_queue(vblk->disk->queue);
vdev->config->del_vqs(vdev);
kfree(vblk->vqs);
return 0;
}
7、总结
最后用一张图片总结从运行 suspend 命令到 suspend 一个具体的设备的大概流程,如下:
References
[1] https://www.kernel.org/doc/html/latest/driver-api/pm/cpuidle.html
[2] https://www.eefocus.com/article/527386.html
[3] https://blog.csdn.net/qq_48361010/article/details/140874424