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