Skip to main content
 首页 » 操作系统

Linux内核 runtime_PM 框架

2022年07月19日22sxdcgaq8080

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");

12. runtime PM sysfs文件接口介绍

autosuspend_delay_ms :rw, 当设备没有使用的时候delay多长时间(单位ms)后设置设备为deactive状态 
control :rw是否使能runtime PM, 写“on”使能,写“auto”禁止。cat它显示当前设备的runtime PM是否使能。 
runtime_active_time : 设备active状态的时间。 
runtime_status : r,表示是否支持runtime PM。 
runtime_suspended_time:设备inactive状态的时间。

本文参考链接:https://www.cnblogs.com/hellokitty2/p/9974273.html