Linux内核 runtime_PM 框架

Linux内核 runtime_PM 框架

runtime PM (runtime power management) 简介:

怎样动态地打开关闭设备的电源 ? 最简单的方法:在驱动程序中,open时打开电源,在close时关闭电源。但是有一个缺点,当多个App使用该设备时可能造成干扰。
解决方法:给驱动添加计数值,当该值大于0时打开电源,等于0时关闭电源。

多在ioctl中进行控制,例如alsa的驱动代码

runtime PM只是提供辅助函数,比如:
(1).增加计数/减少计数
(2).使能runtime pm

最好的资料是runtime_pm.txt  TODO:翻译它

例子:\drivers\input\misc\bma150.c
pm_runtime_enable //bma150_probe
pm_runtime_disable //bma150_remove
pm_runtime_get_sync //bma150_open
pm_runtime_put_sync //bma150_close

pm_runtime_enable/pm_runtime_disable 使能/禁止runtime PM,分别对dev->power.disable_depth执行++和--操作,这个变量的初始化值是1,默认是disable的状态。
pm_runtime_get_sync/pm_runtime_put_sync 增加/减少计数值,并判断是否进入suspend/resume。

1. 在struct dev_pm_ops提供了3个回调函数:runtime_suspend,runtime_resume,runtime_idle,一般runtime_idle这个空闲函数不需要提供

2. 上面4个函数不会直接导致runtime_suspend,runtime_resume,runtime_idle被调用,只是使能和修改计数值,当引用计数减为0,调用suspend,
从0变为大于0调用resume。

3. 对于runtime PM,默认状态下设备的状态是suspend,如果硬件上它是运行状态,需要调用pm_runtime_set_active()来修改它的状态,然后调用
pm_runtime_enable()来使能runtime PM。一般是在probe()的结尾处使用,以为它可能导致runtime的suspend/resume函数立即调用。一般在驱动remove中
调用pm_runtime_disable()

4. 在open()/release()接口中可以调用pm_runtime_get_sync/pm_runtime_put_sync

5. autosuspend:
为了不想让设备频繁地开、关,可以使用autosuspend功能,驱动中执行update_autosuspend()来启用autosuspend功能。[TODO]
put设备时换做执行:
pm_runtime_mark_last_busy()
pm_runtime_put_sync_autosuspend()
用户空间可以通过设置下面sysfs文件来设置autosuspend延迟时间:
ehco 2000 > /sys/devices/.../power/autosuspend_delay_ms

6. struct dev_pm_ops 注解翻译:
用于定义要在所有情况下使用的一组PM操作(如系统挂起,休眠或运行时PM)。 注意:通常,系统挂起回调.suspend 和.resume 应该与
对应的运行时PM回调.runtime_suspend 和.runtime_resume 不同,因为.runtime_suspend 始终适用于已经暂停的设备,而.suspend 应
该假设在调用它时设备可能正在做某事(它应该确保设备在它返回后可靠地处于暂停状态)。 因此,最好将“late” suspend 和“early”resume
回调指针.suspend_late和.resume_early分别指向与.runtime_suspend和.runtime_resume相同的例程(类似于休眠)。

7.流程分析:

复制代码

pm_runtime_get_sync
    __pm_runtime_resume(dev, RPM_GET_PUT) 
        atomic_inc(&dev->power.usage_count); // 若上级arg2&RPM_GET_PUT为真,才调用
        rpm_resume(dev, rpmflags) //关闭本地CPU中断后调用它
            if (dev->power.disable_depth > 0) retval = -EACCES; //若要使用runtime PM的函数,需要首先pm_runtime_enable。
            if (!dev->power.timer_autosuspends) /*为了防止设备频繁的开关,可以设置timer_autosuspends的值*/
                pm_runtime_deactivate_timer(dev);
            if (dev->power.runtime_status == RPM_ACTIVE) {  /*如果已经是ACTIVE,就没有必要再次resume*/
            if (dev->power.runtime_status == RPM_RESUMING || dev->power.runtime_status == RPM_SUSPENDING) 如果设备正处于RPM_RESUMING和RPM_SUSPENDING状态,等待其完成
            if (!parent && dev->parent) //增加父级的使用计数器并在必要时恢复它,在resume设备本身之前先resume父设备。
            开始resume设备自己:
                dev->pm_domain->ops->runtime_resume    //或
                dev->type->pm->runtime_resume          //或
                dev->class->pm->runtime_resume         //或
                dev->bus->pm->runtime_resume           //或 前4个被称为subsystem level的callback,优先调用,第5个是驱动级别的。
                dev->driver->pm->runtime_resume        //或
            __update_runtime_status(dev, RPM_SUSPENDED); //如果resume失败,重新设置回SUSPENDED状态
            if (parent) atomic_inc(&parent->power.child_count); //如果resume成功时给父亲的child_count加1
            
            wake_up_all(&dev->power.wait_queue); //唤醒其它进程
            

pm_runtime_put_sync
    __pm_runtime_idle(dev, RPM_GET_PUT)
        if (!atomic_dec_and_test(&dev->power.usage_count)) //减少usage_count引用计数
        rpm_idle(dev, rpmflags); //让设备进入idle状态
            rpm_check_suspend_allowed //检查是否允许设备进入suspend状态,看来内核把idle和suspend一样看待了。
                if (dev->power.disable_depth > 0) retval = -EACCES; //调用时还没有pm_runtime_enable,就失败。
                if (atomic_read(&dev->power.usage_count) > 0) retval = -EAGAIN;
                if (!dev->power.ignore_children && atomic_read(&dev->power.child_count)) retval = -EBUSY; //它的孩子不全睡眠它是不能睡眠的
            if (dev->power.runtime_status != RPM_ACTIVE) retval = -EAGAIN; //如果不是出于ACTIVE状态直接返回。
            开始suspend设备自己:
            dev->pm_domain->ops->runtime_suspend    //或
            dev->type->pm->runtime_suspend          //或
            dev->class->pm->runtime_suspend         //或
            dev->bus->pm->runtime_suspend           //或 前4个被称为subsystem level的callback,优先调用,第5个是驱动级别的。
            dev->driver->pm->runtime_suspend        //或
        wake_up_all(&dev->power.wait_queue); //唤醒其它进程

复制代码

8. 将当前进程放入等待队列中睡眠:

复制代码

rpm_resume():

DEFINE_WAIT(wait);

for (;;) {
  prepare_to_wait(&dev->power.wait_queue, &wait, TASK_UNINTERRUPTIBLE);

  if (dev->power.runtime_status != RPM_RESUMING && dev->power.runtime_status != RPM_SUSPENDING)
    break;

  schedule();
}
finish_wait(&dev->power.wait_queue, &wait);

复制代码

9. 另外,额外说一下异步实现原理

request_firmware_nowait()
    INIT_WORK(&fw_work->work, request_firmware_work_func);
    schedule_work(&fw_work->work);

10. 如何使用runtime PM
(1). 驱动封装接口,App去调用,如把pm_runtime_get_sync放在open()中。
(2). 通过sysfs接口使用:
操作 /sys/devices/.../power/control 导致 drivers/base/power/sysfs.c/control_store() 被调用。对自己的驱动设置了runtime PM 操作后也可以使用
这种操作来测试自己的runtime PM 中的suspend/resume。

复制代码

//App 禁止驱动程序对设备进行runtime PM
echo auto > /sys/devices/.../power/control:control_store --> pm_runtime_forbid --> 
                                            atomic_inc(&dev->power.usage_count);
                                            rpm_resume(dev, 0);
//App 允许驱动程序对设备进行runtime PM
echo on > /sys/devices/.../power/control:control_store --> pm_runtime_allow -->
                                            if (atomic_dec_and_test(&dev->power.usage_count))
                                                rpm_idle(dev, RPM_AUTO | RPM_ASYNC);

可以echo on > /sys/devices/.../power/control  //来启用一个休眠的设备

复制代码

unsigned int runtime_auto;
- 如果被设置了,则表示用户空间允许设备驱动程序通过/sys/devices/.../power/control接口在运行时为设备供电; 它只能在pm_runtime_allow()
和pm_runtime_forbid()辅助函数的帮助下修改.

用户空间可以通过将其/sys/devices/.../power/control属性的值更改为“on”来有效地禁止设备的驱动程序在运行时对设备进行电源管理,
这会导致调用pm_runtime_forbid()。
原则上,驱动程序还可以使用该机制来有效地关闭设备的运行时电源管理,直到用户空间将其打开为止。 即,在初始化期间,驱动程序可
以确保设备的运行时PM状态为“活动”并调用pm_runtime_forbid()。
但是,应该注意的是,如果用户空间已经故意将/sys/devices/.../power/control的值更改为“auto”以允许驱动程序在运行时对设备进行电
源管理,则驱动程序这样使用pm_runtime_forbid()可能会导致混淆。

11. 修改驱动程序使用runtime PM, 可以参考:drivers\input\misc\bma150.c

复制代码

/*不使用autosuspend的电源管理框架:*/

static struct platform_device lcd_dev;


/* 提供给用户的电源管理框架 */
static int mylcd_open(struct fb_info *info, int user)
{
    pm_runtime_get_sync(&lcd_dev.dev);
    return 0;
}
static int mylcd_release(struct fb_info *info, int user)
{
    pm_runtime_put_sync(&lcd_dev.dev);
    return 0;
}

/* suspend和resume的通知,见第一篇博客 */
static int lcd_suspend_notifier(struct notifier_block *nb,
                unsigned long event,
                void *dummy)
{

    switch (event) {
    case PM_SUSPEND_PREPARE:
        printk("lcd suspend notifiler test: PM_SUSPEND_PREPARE\n");
        return NOTIFY_OK;
    case PM_POST_SUSPEND:
        printk("lcd suspend notifiler test: PM_POST_SUSPEND\n");
        return NOTIFY_OK;

    default:
        return NOTIFY_DONE;
    }
}

static struct notifier_block lcd_pm_notif_block = {
    .notifier_call = lcd_suspend_notifier,
};

static void lcd_release(struct device * dev)
{
}

static struct platform_device lcd_dev = {
    .name         = "mylcd",
    .id       = -1,
    .dev = {
        .release = lcd_release,
    },
};
static int lcd_probe(struct platform_device *pdev)
{
    pm_runtime_set_active(&pdev->dev);
    pm_runtime_enable(&pdev->dev);
    return 0;
}
static int lcd_remove(struct platform_device *pdev)
{
    pm_runtime_disable(&pdev->dev);
    return 0;
}
static int lcd_suspend(struct device *dev)
{
    int i;
    unsigned long *dest = &lcd_regs_backup;
    unsigned long *src  = lcd_regs;

    /* 1.保存寄存器状态 */
    for (i = 0; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
    {
        dest[i] = src[i];
    }

    /* 2.断设备的电 */
    lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
    *gpbdat &= ~1;     /* 关闭背光 */
    return 0;
}

static int lcd_resume(struct device *dev)
{
    int i;
    unsigned long *dest = lcd_regs;
    unsigned long *src  = &lcd_regs_backup;

    /* 1.还原到掉电之前的状态 */
    struct clk *clk = clk_get(NULL, "lcd");
    clk_enable(clk);
    clk_put(clk);
    for (i = 0; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
    {
        dest[i] = src[i];
    }

    /* 2.重新上电*/
    lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
    lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
    *gpbdat |= 1;     /* 输出高电平, 使能背光 */
    return 0;
}

static struct dev_pm_ops lcd_pm = {
    .suspend = lcd_suspend,
    .resume  = lcd_resume,
    .runtime_suspend = lcd_suspend,
    .runtime_resume  = lcd_resume,
};

struct platform_driver lcd_drv = {
    .probe        = lcd_probe,
    .remove        = lcd_remove,
    .driver        = {
        .name    = "mylcd",
        .pm     = &lcd_pm,
    }
};

static int lcd_init(void)
{
    /* 电源管理 */
    register_pm_notifier(&lcd_pm_notif_block); /*一注册就可能导致电源runtime函数立即被调用*/

    platform_device_register(&lcd_dev);
    platform_driver_register(&lcd_drv);

    return 0;
}

static void lcd_exit(void)
{
    unregister_pm_notifier(&lcd_pm_notif_block);
    platform_device_unregister(&lcd_dev);
    platform_driver_unregister(&lcd_drv);
}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");

复制代码

复制代码

/*不使用autosuspend的电源管理框架:*/

static struct platform_device lcd_dev;

static int mylcd_open(struct fb_info *info, int user)
{
    pm_runtime_get_sync(&lcd_dev.dev);
    return 0;
}
static int mylcd_release(struct fb_info *info, int user)
{
    pm_runtime_mark_last_busy(&lcd_dev.dev);
    pm_runtime_put_sync_autosuspend(&lcd_dev.dev);
    return 0;
}

static int lcd_suspend_notifier(struct notifier_block *nb,
                unsigned long event,
                void *dummy)
{

    switch (event) {
    case PM_SUSPEND_PREPARE:
        printk("lcd suspend notifiler test: PM_SUSPEND_PREPARE\n");
        return NOTIFY_OK;
    case PM_POST_SUSPEND:
        printk("lcd suspend notifiler test: PM_POST_SUSPEND\n");
        return NOTIFY_OK;

    default:
        return NOTIFY_DONE;
    }
}

static struct notifier_block lcd_pm_notif_block = {
    .notifier_call = lcd_suspend_notifier,
};

static void lcd_release(struct device * dev)
{
}

static struct platform_device lcd_dev = {
    .name         = "mylcd",
    .id       = -1,
    .dev = {
        .release = lcd_release,
    },
};
static int lcd_probe(struct platform_device *pdev)
{
    /* 因为runtime PM 默认上电是关闭的,而这个设备默认上电就是使用的 */
    pm_runtime_set_active(&pdev->dev);
    pm_runtime_use_autosuspend(&pdev->dev);
    pm_runtime_enable(&pdev->dev);
    return 0;
}
static int lcd_remove(struct platform_device *pdev)
{
    pm_runtime_disable(&pdev->dev);
    return 0;
}
static int lcd_suspend(struct device *dev)
{
    int i;
    unsigned long *dest = &lcd_regs_backup;
    unsigned long *src  = lcd_regs;

    for (i = 0; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
    {
        dest[i] = src[i];
    }

    lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
    *gpbdat &= ~1;     /* 关闭背光 */
    return 0;
}

static int lcd_resume(struct device *dev)
{
    int i;
    unsigned long *dest = lcd_regs;
    unsigned long *src  = &lcd_regs_backup;

    struct clk *clk = clk_get(NULL, "lcd");
    clk_enable(clk);
    clk_put(clk);

    for (i = 0; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
    {
        dest[i] = src[i];
    }

    lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
    lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
    *gpbdat |= 1;     /* 输出高电平, 使能背光 */
    return 0;
}

static struct dev_pm_ops lcd_pm = {
    .suspend = lcd_suspend,
    .resume  = lcd_resume,
    .runtime_suspend = lcd_suspend,
    .runtime_resume  = lcd_resume,
};

struct platform_driver lcd_drv = {
    .probe        = lcd_probe,
    .remove        = lcd_remove,
    .driver        = {
        .name    = "mylcd",
        .pm     = &lcd_pm,
    }
};


static int lcd_init(void)
{
    /* 电源管理 */
    register_pm_notifier(&lcd_pm_notif_block);

    platform_device_register(&lcd_dev);
    platform_driver_register(&lcd_drv);

    return 0;
}

static void lcd_exit(void)
{
    unregister_pm_notifier(&lcd_pm_notif_block);
    platform_device_unregister(&lcd_dev);
    platform_driver_unregister(&lcd_drv);
}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");

你可能感兴趣的:(linux)