Linux电源管理——Device Power Management Interface

devtools/2025/1/20 5:41:11/

目录

前言

1、device PM callbacks

2、dev_pm_ops 结构体

3、设备模型中的 dev_pm_ops

4、调用流程

suspend-toc" style="margin-left:0px;">5、platform bus suspend

suspend%20virtio_mmio%20driver-toc" style="margin-left:0px;">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 为例,如下:

//  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 结构体如下:

//  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 即可,如下:

//  include/linux/device.h
struct bus_type {......
//  老版本 PM callbacksint (*suspend)(struct device *dev, pm_message_t state);const struct dev_pm_ops *pm;......
};struct device_driver {......
//  老版本 PM callbacksint (*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 是否完成等,如下:

//  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 */
#elseunsigned int		should_wakeup:1;
#endif
#ifdef CONFIG_PMstruct 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 如下:

//  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

具体代码如下:

//  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_SUSPENDcase 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);......
}

suspend">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 初始化如下:

//  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 函数就是调用的这个函数了,如下:

//  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 的操作逻辑。

suspend%20virtio_mmio%20driver">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,如下:

//  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 为例,如下:

//  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 时需要做的事情,如下:

//  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


http://www.ppmy.cn/devtools/152011.html

相关文章

Java 基础线程篇

一、线程声明及线程间通信 package org.example;import java.util.Random;public class ThreadTest {public void test1() {Thread t1 new Thread(()->{System.out.println("t1...");});Thread t2 new Thread(()->{System.out.println("t2...");}…

SpringBoot+Vue小区智享物业管理系统(高质量源码,可定制,提供文档,免费部署到本地)

作者简介:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容:🌟Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

三台 Centos7.9 中 Docker 部署 Redis 哨兵模式

三台 Centos7.9 中 Docker 部署 Redis 哨兵模式 1. 环境规划2. 配置 Docker Compose3. 配置 Redis 密码和持久化4. 配置哨兵5. 启动服务6. 验证 Redis 哨兵模式7. 注意事项 1. 环境规划 三台服务器的角色分配如下: IP Address容器端口角色192.168.15.128redis-mas…

Leetcode - 周赛431

目录 一,3411. 最长乘积等价子数组 二,3412. 计算字符串的镜像分数 三,3413. 收集连续 K 个袋子可以获得的最多硬币数量 四,3414. 不重叠区间的最大得分 一,3411. 最长乘积等价子数组 本题数据范围小,直…

51单片机——DS18B20温度传感器

由于DS18B20数字温度传感器是单总线接口,所以需要使用51单片机的一个IO口模拟单总线时序与DS18B20通信,将检测的环境温度读取出来 1、DS18B20模块电路 传感器接口的单总线管脚接至单片机P3.7IO口上 2、DS18B20介绍 2.1 DS18B20外观实物图 管脚1为GN…

【linux命令】ip命令使用

1、设置网口IP 方法1:通过IP设置网口ip 添加静态IP: ip addr add 1.1.1.1/24 dev eth0 删除ip: ip addr del 1.1.1.1/24 dev eth0 方法2:nmtui 配置IP另外方法: nmtui 2、添加路由 添加路由: ip route add 目标网…

RV1126+FFMPEG推流项目(9)AI和AENC模块绑定,并且开启线程采集

前面两篇已经交代AI和AENC模块的配置,这篇就让这两个模块绑定起来,绑定的原因是,Aenc从Ai模块拿到采集的原始数据进行编码。 使用 RK_MPI_SYS_Bind 把 AI 节点和 AENC 进行绑定,其中 enModId 是模块 ID 号选择的是 RK_ID_AI、s32C…

智能网联汽车的数据脱敏

2021 年施行的数据安全法旨在规范数据处理活动,保障数据安全,促进包括自动驾驶数据在内的开发利用,保护个人、组织的合法权益,维护国家主权、安全和发展利益。同年施行的个人信息保护法则进一步细化了个人信息权益的保护&#xff…