Linux Power Managment 【guolele修改】

Linux Power Managment

謹以此文紀念過往的歲月

一.前言

在這個對節能要求越來越嚴格的年代,對設備的電源管理就顯的很重要的了,尤其對於可移動設備,在電源有限的情況下,續航能力就顯的很重要的。在本文中將介紹linux是如何對設備電源進行管理的。

二.睡眠

Linux的電源管理的主要幾個文件集中在/kernel/power/main.c/driver/base/power/main.c中。主要以platform設備來看linux的睡眠和喚醒。

 不過在看具體的代碼之前,需要了解suspend的幾個狀態,在linux中定義了兩種:

#define PM_SUSPEND_ON                ((__force suspend_state_t) 0)  suspend是否是打開的

#define PM_SUSPEND_STANDBY    ((__force suspend_state_t) 1)  suspend備用

#define PM_SUSPEND_MEM           ((__force suspend_state_t) 3)  mem suspend

#define PM_SUSPEND_MAX             ((__force suspend_state_t) 4)

還有幾個跟具體平臺相關的結構體:

struct platform_suspend_ops {

         int (*valid)(suspend_state_t state);  --判定平臺是否支持該種睡眠狀態

         int (*begin)(suspend_state_t state);  --初始化一個給定的系統過渡到睡眠狀態

         int (*prepare)(void);              --睡眠前的準備

         int (*enter)(suspend_state_t state);  --真正的進入睡眠

         void (*finish)(void); --當系統離開睡眠模式,在nonboot cpus正確後,恢復設備之前調用。

         void (*end)(void); --在正確恢復設備之後調用,表明系統進入工作狀態或者在過渡到睡眠狀態時出現錯誤。

         void (*recover)(void);  --從一個掛起失敗中恢復系統

};

s3c6410爲例,在s3c6410_pm_init函數中調用suspend_set_ops來設置上述函數,不過該函數本質是將一個全局變量的suspend_ops設置爲某一特定平臺的platform_suspend_ops

static struct platform_suspend_ops s3c6410_pm_ops = {

         .enter                = s3c6410_pm_enter,

         .valid                  = suspend_valid_only_mem,

};

s3c6410中僅僅實現了上面兩個函數並沒有其他函數。可以從suspend_valid_only_mem的函數名中知道,該平臺僅僅支持SUSPEND_MEM。而enter將會在下面的源碼中涉及。

我們還是從一個函數開始神祕的linux電源管理。pm_suspend爲內核提供了一個可見的睡眠函數,通過調用這個函數可以讓系統進入睡眠。我們以傳入的參數爲PM_SUSPEND_MEM爲例來開始我們的電源管理之旅,不過suspend涉及的內容太多,我們更加的關注設備的電源管理。

int pm_suspend(suspend_state_t state)

{

         if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX)

                   return enter_state(state);

         return -EINVAL;

}

enter_state進行一些刪減就會發現沒有太多東西。

static int enter_state(suspend_state_t state)

{

         if (!valid_state(state)) --檢測平臺是否支持傳入的睡眠狀態。S3c6410僅支持mem狀態。

                   return -ENODEV;

         if (!mutex_trylock(&pm_mutex)) --鎖定互斥鎖

                   return -EBUSY;

         sys_sync();    --同步系統,將緩存中的數據回寫到塊設備中

下面就將進入真正的suspend

         suspend_prepare();

         suspend_devices_and_enter(state);

         suspend_finish();

當程序執行到此就證明系統不但睡眠還被喚醒了。不要看從上面的code到這兒僅僅幾句話,但是不知道經過了多少歲月。我們將仔細的來看上面的三個函數。

         mutex_unlock(&pm_mutex);

         return 0;

}

對於下面的三個函數我更加的關注第二函數,沒有辦法啊,第一個和第三個函數我看不懂啊,其中主要是存儲和恢復用戶空間和進程。對於這個部分,確是不懂。飯還是慢慢來喫啊,一口喫不成一個胖子啊。

suspend_prepare();

suspend_devices_and_enter(state);

suspend_finish();

int suspend_devices_and_enter(suspend_state_t state)

{

         int error, ftrace_save;

         if (!suspend_ops)

                   return -ENOSYS;

         if (suspend_ops->begin) { --如果平臺支持begin的話,執行begin

                   error = suspend_ops->begin(state);

                   if (error)

                            goto Close;

         }

         suspend_console();  --console掛起,從這兒向下系統將不會打印信息,調用printk將會將信息保存在一個緩衝區中。知道console被再次resume,系統纔會恢復輸出。

         ftrace_save = __ftrace_enabled_save();  --??????

         error = device_suspend(PMSG_SUSPEND); --將設備掛起

         if (error) {

                   goto Recover_platform;

         }

         if (suspend_ops->prepare) { --睡前準備。

                   error = suspend_ops->prepare();

                   if (error)

                            goto Resume_devices;

         }

         error = disable_nonboot_cpus(); --禁止所有的非引導CPU,在多核中有用,一個CPU就蛻化爲0

         if (!error && !suspend_test(TEST_CPUS))

                   suspend_enter(state);  --進入睡眠

         enable_nonboot_cpus();

 Finish:

         if (suspend_ops->finish)

                   suspend_ops->finish();

 Resume_devices:

         device_resume(PMSG_RESUME);  --resume設備

         __ftrace_enabled_restore(ftrace_save);

         resume_console();        --resume 終端輸出

 Close:

         if (suspend_ops->end)

                   suspend_ops->end();

         return error;

Recover_platform:

         if (suspend_ops->recover)

                   suspend_ops->recover();

         goto Resume_devices;

}

那來看設備掛起

int device_suspend(pm_message_t state)

{

         int error;

         might_sleep();  --????????

         error = dpm_prepare(state); --準備掛起

         if (!error)

                   error = dpm_suspend(state); --掛起設備

         return error;

}

其實我們真正關心的設備掛起就在這裏,在這裏就會看到我們平時用的很多的platform平臺中的suspendresum

static int dpm_prepare(pm_message_t state)

{

         struct list_head list;

         int error = 0;

         INIT_LIST_HEAD(&list);

         mutex_lock(&dpm_list_mtx);

         transition_started = true;

         while (!list_empty(&dpm_list)) {

                   struct device *dev = to_device(dpm_list.next); 

                   get_device(dev);

                   dev->power.status = DPM_PREPARING;

                   mutex_unlock(&dpm_list_mtx);

                   error = prepare_device(dev, state);

                   mutex_lock(&dpm_list_mtx);

                   if (error) {

                            dev->power.status = DPM_ON;

                            if (error == -EAGAIN) {

                                     put_device(dev);

                                     continue;

                            }

                            put_device(dev);

                            break;

                   }

                   dev->power.status = DPM_SUSPENDING;

                   if (!list_empty(&dev->power.entry))

                            list_move_tail(&dev->power.entry, &list);

                   put_device(dev);

         }

         list_splice(&list, &dpm_list);

         mutex_unlock(&dpm_list_mtx);

         return error;

}

上面的函數如果對設備驅動沒有一個大概的瞭解的話,會看得一頭霧水的。那我就慢慢道來,其實也是一種知識的回顧。

list_empty(&dpm_list)學過linux的都知道這句是判定dpm_list爲空,那dpm_list是什麼東西呢,這個鏈表上有什麼東西呢?不急,咱們還是以platform爲例,來看這個鏈表上有什麼。從開闢一個platform設備開始platform_device_alloc-> device_initialize-> device_pm_init

static inline void device_pm_init(struct device *dev)

{

         dev->power.status = DPM_ON;

}

在開始的時候devicepower.status會被設置爲DPM_ON

platform_device_add中我們還要關注一個賦值pdev->dev.bus = &platform_bus_type;設備的總線類型爲platform_bus_type,那我來看看這個總線裏有什麼。

struct bus_type platform_bus_type = {

         .pm           = PLATFORM_PM_OPS_PTR,

};

static struct pm_ext_ops platform_pm_ops = {

         .base = {

                   .prepare = platform_pm_prepare,

                   .complete = platform_pm_complete,

                   .suspend = platform_pm_suspend,

                   .resume = platform_pm_resume,

                   .freeze = platform_pm_freeze,

                   .thaw = platform_pm_thaw,

                   .poweroff = platform_pm_poweroff,

                   .restore = platform_pm_restore,

         },

         .suspend_noirq = platform_pm_suspend_noirq,

         .resume_noirq = platform_pm_resume_noirq,

         .freeze_noirq = platform_pm_freeze_noirq,

         .thaw_noirq = platform_pm_thaw_noirq,

         .poweroff_noirq = platform_pm_poweroff_noirq,

         .restore_noirq = platform_pm_restore_noirq,

};

#define PLATFORM_PM_OPS_PTR  &platform_pm_ops

其實對於每一種總線都會有自己的的struct pm_ext_ops *pm;就是電源管理操作集。你看上面的操作集就定義了關於platform總線的pm管理。上面的device_pm_init僅僅將設備的pm狀態改變,並沒有具體的操作將device加到dpm_list這個鏈表上。

platform_device_add->device_add->device_pm_add這個函數挺熟悉的,不過以前看platform的時候並沒有仔細的看這個函數。今天我們來看這個函數,所謂學習就是今與昔所學相互印證。

void device_pm_add(struct device *dev)

{

         mutex_lock(&dpm_list_mtx);

         list_add_tail(&dev->power.entry, &dpm_list);

         mutex_unlock(&dpm_list_mtx);

}

你看這個函數很簡單,對就是很簡單,一個簡簡單單的鏈表添加,就完成了。在linux中,你會發現有很多東西都是採用鏈表將東西串聯起來,利用鏈表將以設備掛到一個個不同的鏈表中,實現一個設備的不同的管理。

device的結構體中有struct dev_pm_info         power;這個成員就是用於表明該設備當前的電源狀態。

enum dpm_state {

         DPM_INVALID,

         DPM_ON,  --正常工作狀態

         DPM_PREPARING, --device準備進行pm轉化

         DPM_RESUMING, --設備即將被喚醒

         DPM_SUSPENDING, --設備即將睡眠

         DPM_OFF,      --設備此時無效

         DPM_OFF_IRQ,  --設備處於深度睡眠

};

struct dev_pm_info {

         pm_message_t                 power_state;

         unsigned           can_wakeup:1;  

         unsigned           should_wakeup:1;

         enum dpm_state              status;              

         struct list_head        entry;  --這個成員名稱就暴露了其作用即是作爲一個入口。就可以通過這個鏈表的節點逆流而上,找到其所屬的device

}

到此就應該瞭解了dpm_list在哪兒被添加了成員。

我們再次回到上面dpm_prepare的函數,繼續來看,爲了方便從上面的函數中截取一段主要的來看。

while (!list_empty(&dpm_list)) {

                   struct device *dev = to_device(dpm_list.next); 

                   get_device(dev);

                   dev->power.status = DPM_PREPARING;

                   prepare_device(dev, state);

                   dev->power.status = DPM_SUSPENDING;

                   if (!list_empty(&dev->power.entry))

                            list_move_tail(&dev->power.entry, &list);

                   put_device(dev);

         }

         list_splice(&list, &dpm_list);

上面的code就是從dpm_list這個鏈表上將一個一個設備拆下來,進行prepare,添加到另外的一個list中。在所有的設備都prepare好了後,直接將listdpm_list合併就行了。還省了很多資源。

guolele PS:這裏的list與dpm_list合併不是省資源的,用的還是一樣多的內存空間,那麼這裏的作用是什麼?

先看if (!list_empty(&dev->power.entry))

static int prepare_device(struct device *dev, pm_message_t state)

{

         int error = 0;

         down(&dev->sem);

         if (dev->bus && dev->bus->pm && dev->bus->pm->base.prepare) {

                   error = dev->bus->pm->base.prepare(dev);

                   if (error)

                            goto End;

         }

         if (dev->type && dev->type->pm && dev->type->pm->prepare) {

                   error = dev->type->pm->prepare(dev);

                   if (error)

                            goto End;

         }

         if (dev->class && dev->class->pm && dev->class->pm->prepare) {

                   error = dev->class->pm->prepare(dev);

         }

 End:

         up(&dev->sem);

 

         return error;

}

上面的函數很清晰明瞭,就是調用該設備所屬的bustype以及classprepare,當然前提是這些東東都存在的情況下。以一個通用的platform設備爲例,其bus->pm=platform_pm_ops,那很明顯其prepareplatform_pm_prepare,其實現如下:

static int platform_pm_prepare(struct device *dev)

{

         struct device_driver *drv = dev->driver;

         int ret = 0;

         if (drv && drv->pm && drv->pm->prepare)

                   ret = drv->pm->prepare(dev);

 

         return ret;

}

其還是調用了該設備driverprepare,當然還是其存在的情況下。

在設備prepare完成後就應該是dpm_suspend了。其實現的原理與prepare類似,不講述了。

下面就來看suspend_enter這個函數。

static int suspend_enter(suspend_state_t state)

{

         int error = 0;

         device_pm_lock();

         arch_suspend_disable_irqs();  --禁止中斷

         if ((error = device_power_down(PMSG_SUSPEND))) { --關閉設備電源

                   goto Done;

         }

         error = suspend_ops->enter(state); --進入具體平臺的suspend,在其中會使系統進入睡眠程序停止運行,直到系統再次運行纔會執行下面的code

         device_power_up(PMSG_RESUME); --恢復設備電源

 Done:

         arch_suspend_enable_irqs(); --使能中斷

         BUG_ON(irqs_disabled());

         device_pm_unlock();

         return error;

}

這個函數比較特別一點,函數說明是關閉特殊設備,不知道做何意解。

int device_power_down(pm_message_t state)

{

         struct device *dev;

         int error = 0;

         list_for_each_entry_reverse(dev, &dpm_list, power.entry) {

                   error = suspend_device_noirq(dev, state);

                   if (error) {

                                     break;

                   }

                   dev->power.status = DPM_OFF_IRQ;

         }

         if (!error)

                   error = sysdev_suspend(state);

         if (error)

                   dpm_power_up(resume_event(state));

         return error;

}

關閉某一個設備,該函數運行於所有中斷被關閉僅有一個CPU在運行。

static int suspend_device_noirq(struct device *dev, pm_message_t state)

{

         int error = 0;

         if (!dev->bus)

                   return 0;

         if (dev->bus->pm) {

                   error = pm_noirq_op(dev, dev->bus->pm, state);

         } else if (dev->bus->suspend_late) {

                   error = dev->bus->suspend_late(dev, state);

                   suspend_report_result(dev->bus->suspend_late, error);

         }

         return error;

}

static int pm_noirq_op(struct device *dev, struct pm_ext_ops *ops,pm_message_t state)

{

         int error = 0;

         switch (state.event) {

         case PM_EVENT_SUSPEND:

                   if (ops->suspend_noirq) {

                            error = ops->suspend_noirq(dev);

                   }

                   break;

         case PM_EVENT_RESUME:

                   if (ops->resume_noirq) {

                            error = ops->resume_noirq(dev);

                   }

                   Break

}

Return err;

}

還以platform爲例:

static int platform_pm_suspend_noirq(struct device *dev)

{

         struct platform_driver *pdrv;

         int ret = 0;

         if (!dev->driver)

                   return 0;

         pdrv = to_platform_driver(dev->driver);

         if (pdrv->pm) {   --pdrv->pm存在時

                   if (pdrv->pm->suspend_noirq) 

                            ret = pdrv->pm->suspend_noirq(dev);

         } else {  --否則調用suspend_late

                   ret = platform_legacy_suspend_late(dev, PMSG_SUSPEND);

         }

         return ret;

}

static int platform_legacy_suspend_late(struct device *dev, pm_message_t mesg)

{

         struct platform_driver *drv = to_platform_driver(dev->driver);

         struct platform_device *pdev;

         int ret = 0;

         pdev = container_of(dev, struct platform_device, dev);

         if (dev->driver && drv->suspend_late)

                   ret = drv->suspend_late(pdev, mesg);

         return ret;

}

上面就是設備的suspend,而設備的喚醒則相反將前期禁止的中斷打開,使能所有的CPU,再次將所suspend的設備resume。將suspend所做的一切的一切都恢復原樣。這裏就不描述了。各位看官對linux的電源管理應該有了一個總體的理解。

三.總結

對於Android的睡眠則是在其上添加wakelock,這個可以說是對linux的一點補充吧。以後有時間再看。

                            list_move_tail(&dev->power.entry, &list);

是將suspending的設備轉到list最後再 list_splice(&list, &dpm_list);是將所有suspend的設備移到最後,這就形成前面是沒suspending後面是suspending的一個鏈表,這樣結構清晰,方便處理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章