Linux內核 runtime_PM 框架

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");
相關文章
相關標籤/搜索