Linux电源管理——Device Power Management Interface

目录

前言

[1、device PM callbacks](#1、device PM callbacks)

[2、dev_pm_ops 结构体](#2、dev_pm_ops 结构体)

[3、设备模型中的 dev_pm_ops](#3、设备模型中的 dev_pm_ops)

4、调用流程

[5、platform bus suspend](#5、platform bus suspend)

[6、suspend virtio_mmio driver](#6、suspend virtio_mmio driver)

7、总结

References


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

[4] http://www.wowotech.net/?post=149

[5] http://www.wowotech.net/pm_subsystem/pm_interface.html

相关推荐
流星白龙1 小时前
【Linux】13.Linux进程概念(2)
linux·运维·服务器
菜要多训练1 小时前
Ubuntu22.04系统切换内核版本
linux
余额不足121382 小时前
Linux 操作二:文件映射与文件状态
android·linux·服务器
一个小坑货2 小时前
CentOS 9 Stream 上安装飞书客户端
linux·centos·飞书
缘友一世3 小时前
epoll 的边缘触发(Edge Triggered)与水平触发(Level Triggered)
linux·网络
一往.无前~3 小时前
【无标题】
linux·运维·服务器
舰长1153 小时前
麒麟服务器安装最新 neo4j/5.9.0 图数据库
linux·运维·服务器
fulufulucode4 小时前
【Linux】线程与同步互斥相关知识详细梳理
linux·服务器·开发语言
躺不平的理查德4 小时前
shell-特殊位置变量
linux·运维·服务器·bash