ToB企服应用市场:ToB评测及商务社交产业平台

标题: Linux电源管理——Device Power Management Interface [打印本页]

作者: 不到断气不罢休    时间: 2025-1-19 13:38
标题: Linux电源管理——Device Power Management Interface
目录
媒介
1、device PM callbacks
2、dev_pm_ops 布局体
3、装备模型中的 dev_pm_ops
4、调用流程
5、platform bus suspend
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 为例,如下:
  1. //  include/linux/device.h
  2. struct device_driver {
  3.         const char                *name;
  4.         struct bus_type                *bus;
  5.     ......
  6.         int (*probe) (struct device *dev);
  7.         int (*remove) (struct device *dev);
  8.         void (*shutdown) (struct device *dev);
  9.         int (*suspend) (struct device *dev, pm_message_t state);
  10.         int (*resume) (struct device *dev);
  11.         ......
  12.         const struct dev_pm_ops *pm;
  13.         void (*coredump) (struct device *dev);
  14.         struct driver_private *p;
  15. };
复制代码
        shutdown、suspend、resume 这些函数指针就是旧版本 Linux kernel 的 device PM callbacks,而在新版本中则是通过 dev_pm_ops 实现对外设的电源管理。
2、dev_pm_ops 布局体

struct dev_pm_ops 布局体如下:
  1. //  include/linux/pm.h
  2. struct dev_pm_ops {
  3.         int (*prepare)(struct device *dev);
  4.         void (*complete)(struct device *dev);
  5.         int (*suspend)(struct device *dev);
  6.         int (*resume)(struct device *dev);
  7.         int (*freeze)(struct device *dev);
  8.         int (*thaw)(struct device *dev);
  9.         int (*poweroff)(struct device *dev);
  10.         int (*restore)(struct device *dev);
  11.         int (*suspend_late)(struct device *dev);
  12.         int (*resume_early)(struct device *dev);
  13.         int (*freeze_late)(struct device *dev);
  14.         int (*thaw_early)(struct device *dev);
  15.         int (*poweroff_late)(struct device *dev);
  16.         int (*restore_early)(struct device *dev);
  17.         int (*suspend_noirq)(struct device *dev);
  18.         int (*resume_noirq)(struct device *dev);
  19.         int (*freeze_noirq)(struct device *dev);
  20.         int (*thaw_noirq)(struct device *dev);
  21.         int (*poweroff_noirq)(struct device *dev);
  22.         int (*restore_noirq)(struct device *dev);
  23.         int (*runtime_suspend)(struct device *dev);
  24.         int (*runtime_resume)(struct device *dev);
  25.         int (*runtime_idle)(struct device *dev);
  26. };
复制代码
        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 即可,如下:
  1. //  include/linux/device.h
  2. struct bus_type {
  3.         ......
  4. //  老版本 PM callbacks
  5.         int (*suspend)(struct device *dev, pm_message_t state);
  6.         const struct dev_pm_ops *pm;
  7.         ......
  8. };
  9. struct device_driver {
  10.         ......
  11. //  老版本 PM callbacks
  12.         int (*suspend) (struct device *dev, pm_message_t state);
  13.         const struct dev_pm_ops *pm;
  14.         ......
  15. };
  16. struct class {
  17.         ......
  18.         const struct dev_pm_ops *pm;
  19.         ......
  20. };
  21. struct device_type {
  22.         ......
  23.         const struct dev_pm_ops *pm;
  24. };
  25. struct device {
  26.         ......
  27.         struct dev_pm_info        power;
  28.         struct dev_pm_domain        *pm_domain;
  29.         ......
  30. };
复制代码
        其中dev_pm_info 布局体主要保存和电源管理干系的状态,如当前的power_state、prepare 是否完成、suspend 是否完成等,如下:
  1. //  include/linux/pm.h
  2. struct dev_pm_info {
  3.         pm_message_t                power_state;
  4.         unsigned int                can_wakeup:1;
  5.         unsigned int                async_suspend:1;
  6.         bool                        in_dpm_list:1;        /* Owned by the PM core */
  7.         bool                        is_prepared:1;        /* Owned by the PM core */
  8.         bool                        is_suspended:1;        /* Ditto */
  9.         ......
  10. #ifdef CONFIG_PM_SLEEP
  11.         ......
  12.         bool                        no_pm_callbacks:1;        /* Owned by the PM core */
  13.         unsigned int                must_resume:1;        /* Owned by the PM core */
  14.         unsigned int                may_skip_resume:1;        /* Set by subsystems */
  15. #else
  16.         unsigned int                should_wakeup:1;
  17. #endif
  18. #ifdef CONFIG_PM
  19.         struct hrtimer                suspend_timer;
  20.         u64                        timer_expires;
  21.         struct work_struct        work;
  22.         wait_queue_head_t        wait_queue;
  23.         struct wake_irq                *wakeirq;
  24.         ......
  25. #endif
  26.         ......
  27. };
复制代码
        dev_pm_domain 布局体则是思量到power共用的情况,当一个装备属于一个电源域(power domain)时,装备发起suspend/resume必须要让power domain framework 知道当前装备的状态,如许才能在适当的时间去关闭或者打开装备,但实现具体suspend和resume的代码肯定还是必要驱动自己编写,由于只有驱动对自己的装备最熟悉了。
dev_pm_domain 如下:
  1. //  include/linux/pm.h
  2. struct dev_pm_domain {
  3.         struct dev_pm_ops        ops;
  4.         void (*detach)(struct device *dev, bool power_off);
  5.         int (*activate)(struct device *dev);
  6.         void (*sync)(struct device *dev);
  7.         void (*dismiss)(struct device *dev);
  8. };
复制代码
4、调用流程

        这里以suspend为例进行分析,操纵系统在 suspend 的过程中,会根据装备模型中的dev_pm_ops布局体按照如下的次序调用相应的回调
   dev->pm_domain->ops
        dev->type->pm
            dev->class->pm
                dev->bus->pm
                    dev->driver->pm
  具体代码如下:
  1. //  drivers/base/power/main.c
  2. static pm_callback_t pm_op(const struct dev_pm_ops *ops, pm_message_t state)
  3. {
  4.         switch (state.event) {
  5. #ifdef CONFIG_SUSPEND
  6.         case PM_EVENT_SUSPEND:
  7.                 return ops->suspend;
  8.         case PM_EVENT_RESUME:
  9.                 return ops->resume;
  10. #endif /* CONFIG_SUSPEND */
  11.         ......
  12.         }
  13.         return NULL;
  14. }
  15. static int __device_suspend(struct device *dev, pm_message_t state, bool async)
  16. {
  17.         ......
  18.         if (dev->pm_domain) {
  19.                 info = "power domain ";
  20.                 callback = pm_op(&dev->pm_domain->ops, state);
  21.                 goto Run;
  22.         }
  23.         if (dev->type && dev->type->pm) {
  24.                 info = "type ";
  25.                 callback = pm_op(dev->type->pm, state);
  26.                 goto Run;
  27.         }
  28.         if (dev->class && dev->class->pm) {
  29.                 info = "class ";
  30.                 callback = pm_op(dev->class->pm, state);
  31.                 goto Run;
  32.         }
  33.         if (dev->bus) {
  34.                 if (dev->bus->pm) {
  35.                         info = "bus ";
  36.                         callback = pm_op(dev->bus->pm, state);
  37.                 } else if (dev->bus->suspend) {
  38.                         pm_dev_dbg(dev, state, "legacy bus ");
  39.                         error = legacy_suspend(dev, state, dev->bus->suspend,
  40.                                                 "legacy bus ");
  41.                         goto End;
  42.                 }
  43.         }
  44. Run:
  45. //  如果这里还没有获取到 callback 并且 dev->driver 为真,那就设置 callback 为 driver 中的 suspend 函数
  46.         if (!callback && dev->driver && dev->driver->pm) {
  47.                 info = "driver ";
  48.                 callback = pm_op(dev->driver->pm, state);
  49.         }
  50.         error = dpm_run_callback(callback, dev, state, info);
  51.         ......
  52. }
复制代码
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 初始化如下:
  1. //  drivers/base/platform.c
  2. struct bus_type platform_bus_type = {
  3.         .name                = "platform",
  4.         .dev_groups        = platform_dev_groups,
  5.         .match                = platform_match,
  6.         .uevent                = platform_uevent,
  7.         .dma_configure        = platform_dma_configure,
  8.         .pm                = &platform_dev_pm_ops,
  9. };
  10. static const struct dev_pm_ops platform_dev_pm_ops = {
  11.         ......
  12.         USE_PLATFORM_PM_SLEEP_OPS
  13. };
  14. //  include/linux/platform_device.h
  15. #define USE_PLATFORM_PM_SLEEP_OPS \
  16.         .suspend = platform_pm_suspend, \
  17.         .resume = platform_pm_resume, \
  18.         ......
复制代码
         可以上面的代码段看到 platform_bus 中的 pm = platform_dev_pm_ops,而在 platform_dev_pm_ops 函数中将 suspend 函数初始化为 platform_pm_suspend 函数了,以是在 __device_suspend 函数中调用的 dev->bus->pm->suspend 函数就是调用的这个函数了,如下:
  1. //  drivers/base/platform.c
  2. int platform_pm_suspend(struct device *dev)
  3. {
  4.         struct device_driver *drv = dev->driver;
  5.         int ret = 0;
  6.         if (!drv)
  7.                 return 0;
  8.         if (drv->pm) {
  9.                 if (drv->pm->suspend)
  10.                         ret = drv->pm->suspend(dev);
  11.         } else {
  12.                 ret = platform_legacy_suspend(dev, PMSG_SUSPEND);
  13.         }
  14.         return ret;
  15. }
复制代码
        这个函数一开始就会通过 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,如下:
  1. //  drivers/virtio/virtio_mmio.c
  2. static int virtio_mmio_freeze(struct device *dev)
  3. {
  4.         struct virtio_mmio_device *vm_dev = dev_get_drvdata(dev);
  5.         return virtio_device_freeze(&vm_dev->vdev);
  6. }
  7. int virtio_device_freeze(struct virtio_device *dev)
  8. {
  9.         struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
  10.         virtio_config_disable(dev);
  11.         dev->failed = dev->config->get_status(dev) & VIRTIO_CONFIG_S_FAILED;
  12.         if (drv && drv->freeze)
  13.                 return drv->freeze(dev);
  14.         return 0;
  15. }
复制代码
        在 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 为例,如下:
  1. //  drivers/block/virtio_blk.c
  2. static struct virtio_driver virtio_blk = {
  3.         .feature_table                        = features,
  4.         .feature_table_size                = ARRAY_SIZE(features),
  5.         .feature_table_legacy                = features_legacy,
  6.         .feature_table_size_legacy        = ARRAY_SIZE(features_legacy),
  7.         .driver.name                        = KBUILD_MODNAME,
  8.         .driver.owner                        = THIS_MODULE,
  9.         .id_table                        = id_table,
  10.         .probe                                = virtblk_probe,
  11.         .remove                                = virtblk_remove,
  12.         .config_changed                        = virtblk_config_changed,
  13. #ifdef CONFIG_PM_SLEEP
  14.         .freeze                                = virtblk_freeze,
  15.         .restore                        = virtblk_restore,
  16. #endif
  17. };
复制代码
virtblk_freeze 函数就是 virtio blk driver 在系统 suspend 时必要做的事情,如下:
  1. //  drivers/block/virtio_blk.c
  2. static int virtblk_freeze(struct virtio_device *vdev)
  3. {
  4.         struct virtio_blk *vblk = vdev->priv;
  5.         /* Ensure we don't receive any more interrupts */
  6.         vdev->config->reset(vdev);
  7.         /* Make sure no work handler is accessing the device. */
  8.         flush_work(&vblk->config_work);
  9.         blk_mq_quiesce_queue(vblk->disk->queue);
  10.         vdev->config->del_vqs(vdev);
  11.         kfree(vblk->vqs);
  12.         return 0;
  13. }
复制代码
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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4