runtime PM (runtime power management) 簡介:框架
怎樣動態地打開關閉設備的電源 ? 最簡單的方法:在驅動程序中,open時打開電源,在close時關閉電源。可是有一個缺點,當多個App使用該設備時可能形成干擾。
解決方法:給驅動添加計數值,當該值大於0時打開電源,等於0時關閉電源。dom
多在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。atom
1. 在struct dev_pm_ops提供了3個回調函數:runtime_suspend,runtime_resume,runtime_idle,通常runtime_idle這個空閒函數不須要提供spa
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_synccode
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");