不到断气不罢休 发表于 2025-1-19 13:38:47

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 为例,如下:
//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 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 是否完成等,如下:
//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 如下:
//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_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 初始化如下:
//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 的操纵逻辑。
6、suspend virtio_mmio driver

         下面将以一张图片来展示实际的 platform driver 的 suspend 流程,如下:
https://i-blog.csdnimg.cn/direct/a3a19c06a8c7453181e76a975cad5ac0.jpeg
        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 一个具体的装备的大概流程,如下:
https://i-blog.csdnimg.cn/direct/7815da16c51f46d5aafc52f44ae746ec.jpeg
References 

https://www.kernel.org/doc/html/latest/driver-api/pm/cpuidle.html
https://www.eefocus.com/article/527386.html
https://blog.csdn.net/qq_48361010/article/details/140874424
http://www.wowotech.net/?post=149
http://www.wowotech.net/pm_subsystem/pm_interface.html

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Linux电源管理——Device Power Management Interface