pm runtime

本文介紹Linux運行時I/O設備的電源管理框架。屬於Linux內核文檔的翻譯。

原文:http://www.kernel.org/doc/Documentation/power/runtime_pm.txt

翻譯:CoryXie <[email protected]>

1. 介紹

對I/O設備的運行時電源管理(運行時PM)的支持,是在電源管理的核心(PM core)下藉助於以下方式實現的:

  • 電源管理工作隊列pm_wq,總線類型(bus types)和設備驅動(device drivers)可以把自己的PM相關的工作項(work items)置於其上。我們強烈建議,pm_wq用於對所有運行時PM相關的工作項進行排隊,因爲這使得他們能夠與全系統的電源轉換(power transitions)進行同步【掛起到RAM(suspend to RAM),休眠(hibernation),以及從系統睡眠狀態恢復(resume)】。pm_wq是在include/linux/pm_runtime.h中聲明的,定義在kernel /power/ main.c中。
  • 在 “struct device”的“power” 成員中的一些運行時PM字段(這是struct dev_pm_info類型,在include/linux/pm.h中定義),可用於同步設備彼此之間的運行時PM操作。
  • struct dev_pm_ops”中的三個設備運行時PM回調函數(在include/linux/pm.h中定義)。
  •  一組定義在drivers/base/power/runtime.c中的輔助函數,他們可以用於執行運行時PM操作,而在這樣種方式下,他們之間的同步由PM核心負責照顧。鼓勵在總線類型和設備驅動程序中使用這些函數。

下面描述在“struct dev_pm_ops” 中存在的運行時PM回調函數,設備運行時PM字段“struct dev_pm_info”,以及運行時PM核心輔助函數。

2. 設備運行時PM回調函數

在“struct dev_pm_ops”中有三個設備運行時PM回調函數:

struct dev_pm_ops {
        ...
        int (*runtime_suspend)(struct device *dev);
        int (*runtime_resume)(struct device *dev);
        int (*runtime_idle)(struct device *dev);
        ...
};

->runtime_suspend(), -> runtime_resume()和 ->runtime_idle()回調函數會被PM核心針對下列類型執行:

  • 設備類型(device type),
  • 或設備類(device class)(如果該設備類型的struct dev_pm_ops對象不存在),
  • 或給定設備的總線類型(bus type)(如果設備類型的struct dev_pm_ops,以及設備類的struct dev_pm_ops對象都不存在)

這就允許設備類型覆蓋總線類型或類所提供的回調函數,如果有必要的話。

下面的文檔中,總線類型,設備類型和類的回調函數都被稱爲子系統級的回調函數(subsystem-level callbacks)。
默認情況下,回調函數是在進程上下文中,允許中斷的情況下被調用的。然而,子系統可以使用pm_runtime_irq_safe()輔助函數告訴PM核心,設備的 -> runtime_suspend()和 -> runtime_resume()回調函數應該在禁止中斷的原子上下文中被調用(-> runtime_idle()仍然使用默認的方式調用)。這意味着,這些回調例程不得block 或者sleep;但同時也意味着在第4節末尾列出的同步輔助函數(synchronous helper functions),可以在中斷處理程序或原子上下文中被使用。
子系統的掛起回調函數(suspend callback)_完全_負責_恰當地處理設備的掛起。它可以(但不是必須)包括執行自己的設備驅動程序的->runtime_suspend()回調(從PM核心的角度看,並不是必須要設備驅動實現 ->runtime_suspend()回調函數,只要子系統級的掛起回調函數知道怎麼去處理設備就行)。
  • 一旦子系統級的掛起回調函數(suspend callback)對給定設備成功地完成,PM核心就認爲設備的確已經被掛起,但這並不意味着該設備真的已進入低功耗狀態。這裏的本意是,該設備將無法處理數據,且將無法與CPU和RAM通信,直到它的子系統級的恢復回調函數(resume callback)被執行。子系統級的掛起回調成功執行後,設備的運行時PM狀態是“掛起的(suspended)”。
  • 如果子系統級的掛起回調函數(suspend callback)返回-EBUSY或-EAGAIN,設備的運行時PM狀態是“活躍的(active)”,這意味着該設備在此之後必須完全處於可運作狀態。
  • 如果子系統級的掛起回調函數返回一個不同於-EBUSY和-EAGAIN的錯誤代碼,PM核心認爲這是一個致命的錯誤,會拒絕針對該設備運行第4節所述的輔助函數,直到它的狀態被直接設置爲“活躍的(active)”,或“掛起的(suspended)”(PM核心提供了特殊的輔助函數用於此目的)。
特別的,如果爲了能夠適當地工作,驅動程序需要遠程喚醒功能(即,設備請求使其電源狀態變化的硬件機制,如PCI PME), 而device_run_wake()返回“false”的設備,則->runtime_suspend()應該返回-EBUSY。另一方面,對於device_run_wake()返回“true”的設備,且在子系統級掛起回調的執行過程中該設備進入了低功耗狀態,我們期望設備的遠程喚醒就已經被啓動。一般情況下,所有在運行時被設置進入低功耗狀態的輸入設備應該啓用遠程喚醒。
 
子系統級的恢復回調函數(resume callback)要_完全_負責_處理設備的恢復,這可能(但不一定)包括執行自己的設備驅動程序的 ->runtime_resume()回調(從PM核心的角度看,並不是必須要在設備驅動程序中實現->runtime_resume()回調函數,只要子系統級的恢復回調知道怎樣能處理設備就行)。 
  • 一旦子系統級的恢復回調(resume callback)已順利完成,PM核心認爲設備已處於完全可運作狀態,這意味着該設備必須能夠完成I/O操作。 然後,設備的運行時PM狀態是“活躍的(active)”。
  • 如果子系統級的恢復回調(resume callback)返回一個錯誤代碼,PM核心認爲這是一個致命的錯誤,會拒絕針對該設備運行第4節所述的輔助函數,直到其狀態被直接設置爲“活躍的(active)”或“掛起的(suspended)”(PM核心提供了特殊的輔助函數用於此目的)。
每當設備看起來空閒的時候【這是通過兩個計數器來向PM核心指示的,設備使用計數(usage counter),以及設備的“活躍子設備”(active children)計數】,子系統級的空閒回調函數(idle callback)就會被PM核心執行。
  • 如果任何一個計數器被減少到零(使用PM核心所提供的輔助函數),就檢查另一個計數器。如果該計數器也等於零,PM核心就執行子系統級的空閒回調,使用設備作參數。
子系統級的空閒回調(idle callback)執行的操作是完全依賴於子系統本身的,但期望和建議的操作是,檢查設備是否可以掛起(即掛起該設備的所有必要條件是否滿足),且在這種情況下,爲該設備排隊一個掛起請求(queue up a suspend request)。這個回調函數返回的值將被PM核心忽略。
在第4節所述的PM核心所提供的輔助函數,保證對總線類型的運行時PM回調滿足以下約束:
 
  1. 回調是互斥的(例如,對於同一個設備,禁止並行執行->runtime_suspend()和->runtime_resume(),或另一個->runtime_suspend()的實例);例外的情形是,-> runtime_suspend()或 -> runtime_resume()可以和-> runtime_idle()並行執行(雖然對同一設備,-> runtime_idle()將不會在任何其他回調正在執行時啓動)。
  2. -> runtime_idle() 和-> runtime_suspend()只能對 “活躍的(active)”設備執行(即PM核心只會對運行時PM狀態是“活躍的(active)” 的設備執行 ->runtime_idle() 和-> runtime_suspend())。
  3. ->runtime_idle()和->runtime_suspend()只能對其使用計數(usage counter)是零,且其“活躍子設備”(‘active' children)個數是零或“power.ignore_children”標誌被設置的設備執行。
  4. ->runtime_resume()只能對“掛起(suspended)”狀態的設備執行(即PM核心只會對運行時PM狀態是“掛起(suspended)”的設備執行->runtime_resume())。
此外,由PM核心提供的輔助函數遵循以下規則:
  • 如果 ->runtime_suspend()正將要執行,或有一個等待中的請求來執行它,對同一設備->runtime_idle()就不會被執行。
  • 請求執行或安排執行 ->runtime_suspend(),將取消任何等待對相同設備的->runtime_idle()執行請求。
  • 如果 ->runtime_resume()正將要執行,或有一個掛起的請求來執行它,相同設備的其他的回調將不會被執行。
執行->runtime_resume()請求,將取消任何對同一設備的等待中的(pending)或已被調度的(scheduled)回調執行請求,除了已被調度的自動掛起(autosuspend)。

3. 設備的運行時PM字段

以下是在'struct dev_pm_info'中的設備的運行時PM字段,定義在include / linux/ pm.h:

  • struct timer_list suspend_timer;

用於調度(延遲的)掛起和自動休眠(suspend and autosuspend)請求的定時器。

  • unsigned long timer_expires;

定時器到期時間,單位是jiffies(如果這異於零,則定時器正在運行,並將於該時間到期;否則定時器未運行)。

  • struct work_struct work;

用於請求排隊的工作結構(即pm_wq中工作項)。

  • wait_queue_head_t wait_queue;

等待隊列,當任何輔助函數需要等待另一個完成的時候使用。

  • spinlock_t lock;

用於同步。

  • atomic_t usage_count;

設備的使用計數。

  • atomic_t child_count;

“活躍的(active)”的子設備的個數。

  • unsigned int ignore_children;

如果置位,child_count的值將被忽略(但仍然要被更新)

  • unsigned int disable_depth;

用於禁用輔助函數(如果該值等於零,它們正常工作),它的初始值是1(即運行時PM最初對所有設備都是禁用的)

  • unsigned int runtime_error;

如果該值被設置,就表明有致命錯誤(在第2節中所述的回調函數返回的錯誤代碼之一),因此輔助函數直到這個標誌被清除之前將無法正常工作,這是失敗的回調函數返回的錯誤代碼。

  • unsigned int idle_notification;
      如果該值被設置,則->runtime_idle()正在被執行。
  • unsigned int request_pending;
如果該值被設置,則有掛起的請求(即有工作項被排隊在pm_wq中)
  • enum rpm_request request;
掛起的請求類型(如果request_pending被設置時有效)。
  • unsigned int deferred_resume;
當設備正在執行-> runtime_suspend()的時候,如果->runtime_resume()將要運行,而等待掛起操作完成並不實際,就會設置該值;這裏的意思是“一旦你掛起完成,我就開始恢復”。
  • unsigned int run_wake;
如果設備能夠生成運行時喚醒事件,該值就被設置。
  • enum rpm_status runtime_status;

設備的運行時PM狀態; 此字段的初始值是RPM_SUSPENDED,這意味着PM核心認爲每個設備最初都處於'掛起',不論其實際的硬件狀態如何。

  • unsigned int no_callbacks;

表示該設備不使用運行時PM回調(參見第8節),它只可能會被輔助函數pm_runtime_no_callbacks()修改。

  • unsigned int irq_safe;

表示->runtime_suspend()和->runtime_resume()回調函數將在持有自旋鎖並禁止中斷的情況下被調用。

  • unsigned int use_autosuspend;

表明該設備的驅動程序支持延遲的自動休眠功能(見第9節),它只可能被輔助函數pm_runtime{_dont}_use_autosuspend()修改。

  • unsigned int timer_autosuspends;

表明PM核心應該在定時器到期時嘗試進行自動休眠(autosuspend),而不是一個常規的掛起(normal suspend)。

  • int autosuspend_delay;

延遲時間(以毫秒爲單位),可用於自動休眠功能。

  • unsigned long last_busy;

所有上述字段都是“structdevice”的成員“power”中的成員。

4. 運行時PM設備輔助函數

以下的運行時PM輔助函數被定義在drivers/base/power/runtime.c以及 include/linux/pm_runtime.h中:

  • void pm_runtime_init(struct device * dev);

初始化dev_pm_info結構中的設備運行時PM字段。

  • void pm_runtime_remove(struct device *dev);

確保設備的運行時PM在該設備從設備層次刪除後將被禁用。

  •  int pm_runtime_idle(struct device *dev);

執行子系統級的設備空閒回調,返回0成功,或失敗的錯誤代碼,其中的-EINPROGRESS 表示->runtime_idle()已經在執行。

  • int pm_runtime_suspend(struct device *dev);

對設備執行子系統級的掛起回調;返回0表示成功;如果設備的運行時PM狀態已經是“掛起”則返回1;或失敗時返回錯誤代碼,其中,-EAGAIN或-EBUSY意味着企圖在未來再次掛起設備是安全的。

  • int pm_runtime_autosuspend(struct device*dev);

與pm_runtime_suspend()相同,除了考慮了自動休眠延遲時間;如果pm_runtime_autosuspend_expiration()說該延遲尚未到期,那麼就會調度適當時間的自動休眠功能,並返回0。

  • int pm_runtime_resume(struct device *dev);

對設備執行子系統級的恢復回調;返回0表示成功;如果設備的運行時PM狀態已經是“活躍的(active)”就返回1;或失敗時錯誤代碼,其中-EAGAIN意味着在未來試圖恢復設備可能是安全的;但應附加對‘power.runtime_error’進行檢查。

  • int pm_request_idle(struct device *dev);

對設備提交一個執行子系統級的空閒回調的請求(請求由一個pm_wq的工作項代表);返回0表示成功,或如果請求沒有排隊成功就返回錯誤代碼。

  • int pm_request_autosuspend(struct device*dev);

調度子系統級的掛起回調函數,使其在設備的自動休眠延遲(autosuspend delay)過期時執行;如果延遲已過期,則工作項立即被排隊。

  • int pm_schedule_suspend(struct device *dev,unsigned int delay);

調度在未來執行設備的子系統級的掛起回調,其中“delay”是在pm_wq上排隊掛起回調工作項之前等待的時間,以毫秒爲單位(如果“delay”是零,工作項馬上進行排隊);返回0表示成功;如果該設備的運行時PM狀態已經是“掛起”時返回1;或在當請求沒有被調度(或 “delay”爲0時被排隊)時返回錯誤代碼;如果->runtime_suspend()的執行已經被調度但尚未到期,則“delay”的新值將被用來作爲等待的時間。

  • int pm_request_resume(struct device *dev);

對設備提交一個執行子系統級恢復回調的請求(該請求由一個pm_wq中的工作項代表);成功返回0;如果設備的運行時PM狀態已經是”活躍的(active)“則返回1;或當請求沒有被排上隊時返回錯誤代碼。

  • void pm_runtime_get_noresume(struct device*dev);

遞增設備的使用計數。

  • int pm_runtime_get(struct device *dev);

遞增設備的使用計數,運行pm_request_resume(dev),並返回其結果。

  • int pm_runtime_get_sync(struct device *dev);

遞增設備的使用計數,運行pm_runtime_resume(dev),並返回其結果。

  • void pm_runtime_put_noidle(struct device*dev);

遞減設備的使用計數。

  • int pm_runtime_put(struct device *dev);

設備的使用計數減1,如果結果是0,則運行pm_request_idle(dev)並返回其結果。

  • int pm_runtime_put_autosuspend(struct device*dev);

設備的使用計數減1,如果結果是0,則運行pm_request_autosuspend(dev)並返回其結果。

  • int pm_runtime_put_sync(struct device *dev);

設備的使用計數減1,如果結果是0,則運行pm_runtime_idle(dev)並返回其結果。

  • int pm_runtime_put_sync_suspend(struct device*dev);

設備的使用計數減1,如果結果是0,則運行pm_runtime_suspend(dev)並返回其結果。

  • int pm_runtime_put_sync_autosuspend(structdevice *dev);

設備的使用計數減1,如果結果是0,則運行pm_runtime_autosuspend(dev)並返回其結果。

  • void pm_runtime_enable(struct device *dev);

使能運行時PM的輔助函數,使其能運行第2節中所描述的設備的總線類型的運行時PM回調。

  • int pm_runtime_disable(struct device *dev);

防止運行時PM輔助函數運行設備的子系統級的運行時PM回調,確保設備的所有等待中的運行時PM操作已完成或取消;如果有一個恢復請求正在等待,且爲了滿足該請求而執行設備的子系統級的恢復回調是必要的,則返回1;否則返回0。

  • void pm_suspend_ignore_children(struct device*dev, bool enable);

設置/取消設備的power.ignore_children標誌。

  • int pm_runtime_set_active(struct device*dev);

清除設備的“power.runtime_error”標誌,設置設備的運行時PM狀態爲”活躍的(active)“,並更新其父設備的”活躍子設備“計數(唯一有效的使用此函數的條件是,如果“power.runtime_error”被設置,或者“power.disable_depth”大於零);如果設備的父設備是不活躍的,且其“power.ignore_children”標誌沒有設置,該函數就會失敗並返回錯誤代碼。

  • void pm_runtime_set_suspended(struct device *dev);

清除設備的“power.runtime_error”標誌,設置設備的運行時PM狀態爲“掛起”,並恰當更新其父設備的“活躍的子設備”計數(此函數唯一有效的使用條件是,如果“power.runtime_error”被設置,或“power.disable_depth”大於零)。

  • bool pm_runtime_suspended(struct device*dev);

如果該設備的運行時PM狀態爲“掛起”且其“power.disable_depth”字段等於0,返回true;否則返回false。

  • void pm_runtime_allow(struct device *dev);

設置設備的power.runtime_auto標誌,並遞減其使用計數(用於/sys/devices/.../power/control接口,實際上允許使設備在運行時被電源管理)。

  • void pm_runtime_forbid(struct device *dev);

取消設置設備的power.runtime_auto標誌,並遞增其使用計數(用於/sys/devices/.../power/control接口,實際上禁止設備在運行時被電源管理)。

  • void pm_runtime_no_callbacks(struct device*dev);

設置設備的power.no_callbacks標誌,並從/sys/devices/.../power中刪除運行時PM屬性(或防止設備在註冊時添加他們)。

  • void pm_runtime_irq_safe(struct device *dev);

設置設備的power.irq_safe標誌,造成運行時PM掛起和恢復回調在禁止中斷的情況下被調用(但不包括空閒回調)。

  • void pm_runtime_mark_last_busy(struct device*dev);

設置power.last_busy字段爲當前時間。

  • void pm_runtime_use_autosuspend(struct device*dev);

設置power.use_autosuspend標誌,使能自動休眠延遲。

  • void pm_runtime_dont_use_autosuspend(structdevice *dev);

清除power.use_autosuspend標誌,禁用自動休眠延遲。

  • void pm_runtime_set_autosuspend_delay(structdevice *dev, int delay);

設置power.autosuspend_delay的值爲“delay”(以毫秒爲單位),如果“delay”是負的,則防止運行時掛起。

  •  unsigned longpm_runtime_autosuspend_expiration(struct device *dev);

基於power.last_busy和power.autosuspend_delay計算當前自動休眠延遲的到期時間;如果延遲時間是1000毫秒或更大,則到期時間四捨五入精確到秒(rounded up);如果延遲時間已經過期或power.use_autosuspend沒有設置,則返回0;否則返回以jiffies計的過期時間。

從中斷上下文中執行以下輔助函數是安全的:

  • pm_request_idle()
  • pm_request_autosuspend()
  • pm_schedule_suspend()
  • pm_request_resume()
  • pm_runtime_get_noresume()
  • pm_runtime_get()
  • pm_runtime_put_noidle()
  • pm_runtime_put()
  • pm_runtime_put_autosuspend()
  • pm_runtime_enable()
  • pm_suspend_ignore_children()
  • pm_runtime_set_active()
  • pm_runtime_set_suspended()
  • pm_runtime_suspended()
  • pm_runtime_mark_last_busy()
  • pm_runtime_autosuspend_expiration()

如果pm_runtime_irq_safe()爲設備調用,則以下輔助函數也可以在中斷上下文中使用:

  • pm_runtime_suspend()
  • pm_runtime_autosuspend()
  • pm_runtime_resume()
  • pm_runtime_get_sync()
  • pm_runtime_put_sync_suspend()

5. 運行時PM初始化,設備檢測和刪除

最初,所有設備的運行時PM被禁用,這意味着第4節中描述的大部分的運行時PM輔助函數將返回-EAGAIN,直到爲設備調用pm_runtime_enable()之後。

此外,所有設備的運行時PM的初始狀態都是'掛起(suspended)',但它不一定反映實際的物理設備狀態。因此,如果設備最初是活躍的(即,它能夠處理I/O),其運行時PM狀態必須在pm_runtime_set_active()的幫助之下,在爲設備調用pm_runtime_enable()之前,被改變爲“活躍”。

然而,如果該設備有父設備且其父設備的運行時PM是啓用的,爲設備調用pm_runtime_set_active()會影響其父設備,除非其父設備的“power.ignore_children”標誌位被設置。也就是說,在這種情況下,使用PM核心的輔助函數,父設備不能在運行時被掛起,只要子設備的狀態是“活躍的”,即使子設備的運行時PM還是禁用的(即pm_runtime_enable ()尚未對該子設備調用,或對該子設備已調用pm_runtime_disable())。出於這個原因,一旦pm_runtime_set_active()被爲設備調用,pm_runtime_enable()也應該被儘早調用;否則其運行時PM狀態應該在pm_runtime_set_suspended()的幫助下改回爲“掛起”。

如果設備的默認初始運行時PM狀態(即“掛起”)反映了實際設備狀態,它的總線類型或它的驅動程序的->probe()回調函數將可能需要使用在第4節描述的PM核心的輔助函數喚醒它。在這種情況下,應使用pm_runtime_resume()。當然,爲達此目的,在此之前,設備的運行時PM應通過調用pm_runtime_enable()被啓動。

如果設備的總線類型或驅動程序的->probe()回調運行pm_runtime_suspend()或pm_runtime_idle()或與之對應的異步函數(asynchronous counterparts),他們將失敗返回-EAGAIN,因爲該設備的使用計數已經被驅動程序核心遞增,然後再執行->probe()。儘管如此,可能仍然比較想要設備在->probe()完成後儘快被掛起,所以驅動那時候會核心採用pm_runtime_put_sync()來調用子系統級的設備空閒回調。

此外,在__device_release_driver()中,驅動核心可以防止運行時PM回調與總線通知(notifier)回調競爭,這是必要的,因爲一些子系統使用通知(notifier)來進行影響運行時PM的操作。這是通過在driver_sysfs_remove()和BUS_NOTIFY_UNBIND_DRIVER通知之前調用pm_runtime_get_sync()來實現該目的的。如果設備已經處於掛起狀態,這將恢復該設備,並會防止在這些例程正在執行時再次被掛起。

爲了讓總線類型和驅動程序在其->remove()例程中調用pm_runtime_suspend()將設備放到掛起狀態,在__ ​​device_release_driver()中驅動程序核心在運行BUS_NOTIFY_UNBIND_DRIVER通知後執行pm_runtime_put_sync()。這就需要總線類型和驅動程序避免其->remove()回調函數與運行時PM直接競爭,但也讓驅動程序在處理設備的移除過程中有更多的靈活性。

通過將/sys/devices/.../power/control屬性值改變爲“on”,用戶空間可以有效地禁止設備驅動程序進行運行時電源管理,這會導致pm_runtime_forbid()被調用。原則上,也可以使用這個機制有效地關閉運行時設備電源管理,直到用戶空間打開它。也就是說,在初始化時,驅動程序可以確保設備的運行時PM狀態是“活躍的(active)”,並調用pm_runtime_forbid()。應該指出的是,如果用戶空間已經有意改變/sys/devices/.../power/control 的值爲“自動”,讓驅動在運行時進行設備的電源管理,驅動程序這樣用pm_runtime_forbid()可能會讓用戶空間產生混淆。

6. 運行時PM和系統休眠

運行時PM和系統休眠(即,系統掛起和休眠,也被稱爲掛起到RAM和掛起到磁盤)以多種方式互相交互。如果系統休眠開始時設備處於活躍狀態,那麼一切都簡單。但如果設備已掛起,會發生什麼呢?

對於運行時PM和系統休眠,設備可能有不同的喚醒設置。例如,遠程喚醒可能會在運行時PM中啓用,但不允許系統休眠時啓用(device_may_wakeup(dev)返回“false”)。當發生這種情況時,子系統級系統掛起回調(system suspend callback)負責改變設備的喚醒設定(它可能將這個責任交給設備驅動器的系統掛起例程)。爲了做到這一點,可能需要先恢復設備,再掛起它。如果驅動程序對運行時掛起和系統休眠使用不同的電源級別或其他設置,也是如此。

在系統恢復時,設備一般應恢復到全功率狀態,即使他們在系統休眠開始前已經被掛起。這有幾個原因,包括:

  • 該設備可能需要切換功率等級(power levels),喚醒設置等。
  • 遠程喚醒事件可能已被固件丟失。
  • 該設備的子設備可能需要該設備以全功率運行,以恢復他們自己。
  •  驅動程序對設備狀態的想法可能與設備的物理狀態不同。這可能在從休眠(hibernation)狀態恢復時發生。
  • 該設備可能需要進行復位。
  •  即使設備被掛起,如果其使用計數>0,那麼它仍然很可能會在不久的將來需要運行時恢復。
  • 總是回到全功率是最簡單的。

如果系統睡眠開始前設備已經被掛起,那麼它的運行時PM狀態將必須被更新,以反映實際的系統睡眠後的狀態。做到這一點的方法是:

  • pm_runtime_disable(dev);
  • pm_runtime_set_active(dev);
  • pm_runtime_enable(dev);

7. 通用的子系統回調函數

子系統可能希望通過使用PM核心提供的一套通用的,定義在driver/base/power/generic_ops.c中的電源管理回調函數,以節省代碼空間:

  •  int pm_generic_runtime_idle(struct device*dev);

調用此設備的驅動程序提供的->runtime_idle()回調函數(如果有定義的話),並在該回調返回值是0或者回調沒有定義的情況下,調用pm_runtime_suspend()。

  • int pm_generic_runtime_suspend(struct device*dev);

調用此設備的驅動程序提供的 ->runtime_suspend()回調函數,並返回其結果,或如果該回調函數沒有定義時返回-EINVAL。

  • int pm_generic_runtime_resume(struct device*dev);

調用此設備的驅動程序提供的->runtime_resume()回調函數,並返回其結果,或如果該回調函數沒有定義時返回-EINVAL。

  • int pm_generic_suspend(struct device *dev);

如果該設備未在運行時被掛起,調用此設備的驅動程序提供的->suspend()回調函數,並    返回其結果,或如果該回調函數沒有定義時返回-EINVAL。

  • int pm_generic_resume(struct device *dev);

調用此設備的驅動程序提供的->resume()回調函數,且如果成功的話,改變設備的運行時PM狀態爲“活躍的”。

  • int pm_generic_freeze(struct device *dev);

如果該設備未在運行時被掛起,調用此設備的驅動程序提供的-> freeze ()回調函數,並    返回其結果,或如果該回調函數沒有定義時返回-EINVAL。

  •  int pm_generic_thaw(struct device *dev);

如果該設備未在運行時被掛起,調用此設備的驅動程序提供的-> thaw ()回調函數,並    返回其結果,或如果該回調函數沒有定義時返回-EINVAL。

  •  int pm_generic_poweroff(struct device *dev);

如果該設備未在運行時被掛起,調用此設備的驅動程序提供的-> poweroff ()回調函數,並返回其結果,或如果該回調函數沒有定義時返回-EINVAL。

  •  int pm_generic_restore(struct device *dev);

調用此設備的驅動程序提供的-> restore()回調函數,且如果成功的話,改變設備的運行時PM狀態爲“活躍的”。


這些函數可以被賦值給系統級dev_pm_ops結構體的下列回調函數指針:
  • ->runtime_idle(),
  • ->runtime_suspend(),
  • ->runtime_resume(),
  • ->suspend(),
  • ->resume(),
  • ->freeze(),
  • ->thaw(),
  • ->poweroff()
  • ->restore()

如果子系統希望同時使用所有的這些函數,可以簡單地將GENERIC_SUBSYS_PM_OPS宏(定義在include/linux/pm.h)賦值給其dev_pm_ops結構的指針。

希望使用相同的函數作爲系統掛起(system suspend), 凍結(freeze),斷電(poweroff)以及運行時掛起(run-time suspend),以及類似的,系統恢復(system resume),解凍(thaw),恢復(restore)和運行時恢復(run-timeresume)等回調函數的設備驅動程序,可以在定義在include/linux/pm.h中的UNIVERSAL_DEV_PM_OPS宏的幫助下做到這一點(可能是其最後一個參數設置爲NULL)。

9. 自動休眠功能或自動延時掛起

改變設備的電源狀態並不是免費的,它也需要時間和能耗。只有當有理由認爲設備將保持在這種狀態下大量的時間時,才應將設備置入低功耗狀態。一個通常的啓發式說法,一直沒有怎麼用的設備很可能繼續保持在未使用狀態,按照這個建議,驅動程序不應該允許設備在運行時掛起,直到他們處於非活躍狀態已經有一段最低限度的時間。即使該啓發式說法最終並非最佳,它仍然會阻止設備在低功耗和全功率狀態之間迅速“反彈”。

術語“自動休眠(autosuspend)”是一個歷史遺留下來的名字。這並不意味着該設備就會自動掛起(子系統或驅動程序仍然需要調用適當的PM例程),然而這意味着運行時掛起(run-time suspends)將自動被延遲,直到所需的一段時間空閒後。

不活躍(Inactivity)是根據power.last_busy字段來確定的。驅動程序應該在進行I/O後調用pm_runtime_mark_last_busy()來更新這個字段,通常是在剛要調用pm_runtime_put_autosuspend()之前。所需的空閒時間長度是一個策略問題。子系統可以在最初調用pm_runtime_set_autosuspend_delay()設置該長度,但設備註冊後該長度應由用戶空間控制,使用/sys/devices/.../power/autosuspend_delay_ms屬性。

爲了使用自動休眠(autosuspend),子系統或驅動程序必須調用pm_runtime_use_autosuspend()(最好是在註冊設備之前),此後他們應該使用各種*_autosuspend()輔助函數,來代替非自動休眠的對應函數(non-autosuspend counterparts):

  • 不用:pm_runtime_suspend    而用:pm_runtime_autosuspend;
  • 不用:pm_schedule_suspend   而用:pm_request_autosuspend;
  • 不用:pm_runtime_put        而用:pm_runtime_put_autosuspend;
  • 不用:pm_runtime_put_sync   而用:pm_runtime_put_sync_autosuspend.

驅動程序可以繼續使用非自動休眠功能輔助函數,他們會表現正常,而不把自動休眠延遲考慮進來。同樣,如果power.use_autosuspend字段沒有被設置,則自動休眠的輔助函數,使用起來就像是非自動休眠的對應函數(non-autosuspend counterparts)。

該實現非常適合用於異步中斷上下文中。然而,這樣的使用不可避免地涉及到競爭,這是由於PM核心不能同步 ->runtime_suspend()回調與I/O請求的到來。該同步必須由驅動程序使用其私有鎖來完成。這裏是一個原理性的僞代碼示例:

        foo_read_or_write(structfoo_priv *foo, void *data)
        {
               lock(&foo->private_lock);
               add_request_to_io_queue(foo,data);
               if(foo->num_pending_requests++ == 0)
                       pm_runtime_get(&foo->dev);
               if(!foo->is_suspended)
                       foo_process_next_request(foo);
               unlock(&foo->private_lock);
        }
 
        foo_io_completion(structfoo_priv *foo, void *req)
        {
               lock(&foo->private_lock);
               if(--foo->num_pending_requests == 0) {
                       pm_runtime_mark_last_busy(&foo->dev);
                       pm_runtime_put_autosuspend(&foo->dev);
               }else {
                       foo_process_next_request(foo);
               }
               unlock(&foo->private_lock);
               /*Send req result back to the user ... */
        }
 
        intfoo_runtime_suspend(struct device *dev)
        {
               structfoo_priv foo = container_of(dev, ...);
               intret = 0;
 
               lock(&foo->private_lock);
               if(foo->num_pending_requests > 0) {
                       ret= -EBUSY;
               }else {
                       /*... suspend the device ... */
                       foo->is_suspended= 1;
               }
               unlock(&foo->private_lock);
               returnret;
        }
 
        intfoo_runtime_resume(struct device *dev)
        {
               structfoo_priv foo = container_of(dev, ...);
 
               lock(&foo->private_lock);
               /*... resume the device ... */
               foo->is_suspended= 0;
               pm_runtime_mark_last_busy(&foo->dev);
               if(foo->num_pending_requests > 0)
                       foo_process_requests(foo);
               unlock(&foo->private_lock);
               return0;
        }

最重要的一點是,在foo_io_completion()要求自動休眠之後,foo_runtime_suspend()回調可能與foo_read_or_write()競爭。因此foo_runtime_suspend()必須檢查是否有任何掛起的I/O請求(在持有私有鎖的情況下),然後才允許掛起進行。

此外,power.autosuspend_delay字段可以由用戶空間在任何時間改變。如果驅動程序關心這個,它可以在持有其私有鎖的情況下在->runtime_suspend()回調內調用pm_runtime_autosuspend_expiration()。如果該函數返回非零值,那麼該延誤尚未過期,則該回調應該返回-EAGAIN。

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