裝置連結¶
預設情況下,驅動核心只強制執行裝置層級結構中由父/子關係產生的裝置之間的依賴關係:在掛起、恢復或關閉系統時,裝置會根據這種關係進行排序,即子裝置總是在其父裝置之前掛起,而父裝置總是在其子裝置之前恢復。
有時需要表示超越單純父/子關係的裝置依賴,例如兄弟裝置之間的依賴,並讓驅動核心自動處理它們。
其次,驅動核心預設不強制執行任何驅動存在依賴,即一個裝置必須繫結到驅動程式後,另一個裝置才能正確探測或執行。
通常這兩種依賴型別同時出現,因此一個裝置對另一個裝置的依賴既涉及到驅動存在,也涉及到掛起/恢復和關機順序。
裝置連結允許在驅動核心中表示此類依賴關係。
在其標準或受管理的形式中,裝置連結結合了兩種依賴型別:它保證了“供應商”裝置與其“消費者”裝置之間正確的掛起/恢復和關機順序,並保證了供應商上驅動的存在。消費者裝置不會在供應商繫結到驅動程式之前被探測,並且會在供應商解綁之前解綁。
當供應商上的驅動存在無關緊要,並且只需要正確的掛起/恢復和關機順序時,裝置連結可以簡單地透過設定 DL_FLAG_STATELESS 標誌來建立。換句話說,強制供應商上驅動的存在是可選的。
另一個可選功能是執行時電源管理(PM)整合:透過在新增裝置連結時設定 DL_FLAG_PM_RUNTIME 標誌,PM 核心被指示在消費者執行時恢復時,以及只要消費者處於執行時恢復狀態,就執行時恢復供應商並使其保持活躍。
用法¶
新增裝置連結的最早時間點是已經為供應商呼叫了 device_add(),併為消費者呼叫了 device_initialize() 之後。
稍後新增它們是合法的,但必須注意系統保持一致狀態:例如,不能在掛起/恢復轉換過程中新增裝置連結,因此要麼需要使用 lock_system_sleep() 來阻止此類轉換的開始,要麼需要從保證不會與掛起/恢復轉換並行執行的函式中新增裝置連結,例如來自裝置 ->probe 回撥或啟動時 PCI quirk。
另一個不一致狀態的例子是,一個裝置連結代表了驅動存在依賴,但卻是在供應商尚未開始探測時從消費者的 ->probe 回撥中新增的:如果驅動核心早些知道這個裝置連結,它就不會一開始就探測消費者。因此,消費者有責任在新增連結後檢查供應商的存在,並在供應商不存在時延遲探測。[請注意,在供應商仍在探測時,從消費者的 ->probe 回撥中建立連結是有效的,但消費者必須知道供應商在連結建立時已經功能正常(例如,如果消費者剛剛獲取了一些資源,而如果供應商當時不正常,這些資源就不會可用)。]
如果一個設定了 DL_FLAG_STATELESS 的裝置連結(即一個無狀態裝置連結)是在供應商或消費者驅動的 ->probe 回撥中新增的,為了對稱性,它通常會在其 ->remove 回撥中被刪除。這樣,如果驅動被編譯為模組,裝置連結會在模組載入時新增,並在解除安裝時有序刪除。適用於裝置連結新增的相同限制(例如,排除並行掛起/恢復轉換)同樣適用於刪除。由驅動核心管理的裝置連結由其自動刪除。
在新增裝置連結時可以指定多個標誌,其中兩個已在上面提及:DL_FLAG_STATELESS 表示不需要驅動存在依賴(但只需要正確的掛起/恢復和關機順序),以及 DL_FLAG_PM_RUNTIME 表示需要執行時 PM 整合。
另外兩個標誌專門針對從消費者 ->probe 回撥新增裝置連結的用例:可以指定 DL_FLAG_RPM_ACTIVE 以執行時恢復供應商,並防止其在消費者執行時掛起之前掛起。DL_FLAG_AUTOREMOVE_CONSUMER 會導致裝置連結在消費者探測失敗或稍後解綁時自動清除。
同樣,當裝置連結從供應商的 ->probe 回撥中新增時,DL_FLAG_AUTOREMOVE_SUPPLIER 會導致裝置連結在供應商探測失敗或稍後解綁時自動清除。
如果既沒有設定 DL_FLAG_AUTOREMOVE_CONSUMER 也沒有設定 DL_FLAG_AUTOREMOVE_SUPPLIER,則可以使用 DL_FLAG_AUTOPROBE_CONSUMER 來請求驅動核心在驅動繫結到供應商裝置後,自動探測連結上的消費者驅動。
然而請注意,DL_FLAG_AUTOREMOVE_CONSUMER、DL_FLAG_AUTOREMOVE_SUPPLIER 或 DL_FLAG_AUTOPROBE_CONSUMER 與 DL_FLAG_STATELESS 的任何組合都是無效的,不能使用。
限制¶
驅動作者應該注意,受管理裝置連結的驅動存在依賴(即在新增連結時未指定 DL_FLAG_STATELESS)可能會導致消費者裝置的探測無限期延遲。如果消費者需要在達到某個 initcall 級別之前進行探測,這就會成為一個問題。更糟糕的是,如果供應商驅動被列入黑名單或缺失,消費者將永遠不會被探測。
此外,受管理的裝置連結不能直接刪除。它們會在根據 DL_FLAG_AUTOREMOVE_CONSUMER 和 DL_FLAG_AUTOREMOVE_SUPPLIER 標誌不再需要時,由驅動核心刪除。然而,無狀態裝置連結(即設定了 DL_FLAG_STATELESS 的裝置連結)預計將由呼叫 device_link_add() 新增它們的程式碼,透過 device_link_del() 或 device_link_remove() 來移除。
將 DL_FLAG_RPM_ACTIVE 和 DL_FLAG_STATELESS 一起傳遞給 device_link_add() 可能會導致在隨後呼叫 device_link_del() 或 device_link_remove() 以移除其返回的裝置連結後,供應商裝置的 PM-執行時使用計數器保持非零。這種情況發生的原因是,如果 device_link_add() 對相同的消費者-供應商對連續呼叫兩次,而在這兩次呼叫之間沒有移除連結,那麼在嘗試移除連結時允許供應商的 PM-執行時使用計數器下降可能會導致它在消費者仍處於 PM-執行時活躍狀態時被掛起,這是必須避免的。[要解決此限制,只需讓消費者至少執行時掛起一次,或者在 device_link_add() 和 device_link_del() 或 device_link_remove() 呼叫之間,為消費者呼叫 pm_runtime_set_suspended() 並停用 PM-執行時即可。]
有時驅動程式依賴於可選資源。當這些資源不存在時,它們能夠以降級模式(功能集或效能降低)執行。一個例子是 SPI 控制器,它可以使用 DMA 引擎或在 PIO 模式下工作。控制器可以在探測時確定可選資源的存在,但在資源不存在時無法知道它們是會在不久的將來可用(由於供應商驅動探測)還是永遠不可用。因此,無法確定是否延遲探測。在探測後當可選資源可用時通知驅動程式是可能的,但這會給驅動程式帶來高昂的成本,因為根據此類資源的可用性在執行時在操作模式之間切換比基於探測延遲的機制要複雜得多。無論如何,可選資源超出了裝置連結的範圍。
示例¶
MMU 裝置與匯流排主控裝置並存,兩者位於相同的電源域。MMU 為匯流排主控裝置實現 DMA 地址轉換,並且只要匯流排主控裝置處於活躍狀態,MMU 就應執行時恢復並保持活躍。匯流排主控裝置的驅動程式不應在 MMU 繫結之前繫結。為了實現這一點,一個具有執行時 PM 整合的裝置連結從匯流排主控裝置(消費者)新增到 MMU 裝置(供應商)。關於執行時 PM 的效果與 MMU 是主控裝置的父裝置時相同。
兩個裝置共享同一電源域通常會建議使用
struct dev_pm_domain或 struct generic_pm_domain,然而這些並非碰巧共享電源開關的獨立裝置,而是 MMU 裝置為匯流排主控裝置提供服務,沒有它就毫無用處。裝置連結在裝置之間建立了一種合成的層級關係,因此更適合。一個 Thunderbolt 主機控制器包含多個 PCIe 熱插拔埠和一個 NHI 裝置來管理 PCIe 交換機。從系統睡眠恢復時,NHI 裝置需要在熱插拔埠恢復之前重新建立到連線裝置的 PCI 隧道。如果熱插拔埠是 NHI 的子裝置,這種恢復順序將由 PM 核心自動強制執行,但不幸的是它們是旁系裝置。解決方案是新增從熱插拔埠(消費者)到 NHI 裝置(供應商)的裝置連結。此用例不需要驅動存在依賴。
混合顯示卡筆記本中的獨立 GPU 通常包含一個用於 HDMI/DP 音訊的 HDA 控制器。在裝置層級結構中,HDA 控制器是 VGA 裝置的兄弟裝置,但兩者共享相同的電源域,並且 HDA 控制器僅在 HDMI/DP 顯示器連線到 VGA 裝置時才需要。從 HDA 控制器(消費者)到 VGA 裝置(供應商)的裝置連結恰當地表示了這種關係。
ACPI 允許透過 _DEP 物件定義裝置啟動順序。一個典型的例子是,當某個裝置上的 ACPI 電源管理方法透過 I2C 訪問實現,並且需要特定的 I2C 控制器存在且功能正常,才能使該裝置的電源管理工作。
在某些 SoC 中,顯示、影片編解碼和影片處理 IP 核對處理突發訪問和壓縮/解壓縮的透明記憶體訪問 IP 核存在功能依賴。
替代方案¶
一個
struct dev_pm_domain可用於覆蓋匯流排、類或裝置型別回撥。它旨在用於共享單個開關的裝置,但它不保證特定的掛起/恢復順序,這需要單獨實現。它本身也不會跟蹤相關裝置的執行時 PM 狀態,並且只有當所有裝置都執行時掛起時才關閉電源開關。此外,它不能用於強制執行特定的關機順序或驅動存在依賴。struct generic_pm_domain 比裝置連結重量級得多,並且不允許關機順序或驅動存在依賴。它也不能在 ACPI 系統上使用。
實現¶
裝置層級結構,顧名思義是一個樹形結構,一旦添加了裝置連結,它就變成了一個有向無環圖。
這些裝置在掛起/恢復期間的排序由 dpm_list 決定。在關機期間,它由 devices_kset 決定。在沒有裝置連結的情況下,這兩個列表是裝置樹的扁平化一維表示,使得一個裝置被放置在其所有祖先之後。這是透過自上而下遍歷 ACPI 名稱空間或 OpenFirmware 裝置樹,並在發現裝置時將其附加到列表來實現的。
一旦添加了裝置連結,列表就需要滿足一個附加約束:一個裝置遞迴地放置在其所有供應商之後。為確保這一點,在新增裝置連結後,消費者及其下面的整個子圖(消費者的所有子裝置和消費者)都將被移動到列表的末尾。(從 device_link_add() 呼叫 device_reorder_to_tail()。)
為了防止圖中引入依賴迴圈,在新增裝置連結時會驗證供應商不依賴於消費者或消費者的任何子裝置或消費者。(從 device_link_add() 呼叫 device_is_dependent()。)如果違反了該約束,device_link_add() 將返回 NULL 並記錄 WARNING 警告。
值得注意的是,這也會阻止從父裝置到子裝置的裝置連結的新增。然而,反過來則是允許的,即從子裝置到父裝置的裝置連結。由於驅動核心已經保證了父子裝置之間正確的掛起/恢復和關機順序,因此這種裝置連結只有在還需要驅動存在依賴時才有意義。在這種情況下,驅動作者應仔細權衡裝置連結是否是實現此目的的正確工具。更合適的做法可能是簡單地使用延遲探測或新增一個裝置標誌,使父驅動在子驅動之前被探測。
狀態機¶
-
enum device_link_state¶
裝置連結狀態。
常量
DL_STATE_NONE未跟蹤驅動程式的存在。
DL_STATE_DORMANT供應商/消費者驅動均不存在。
DL_STATE_AVAILABLE供應商驅動存在,但消費者驅動不存在。
DL_STATE_CONSUMER_PROBE消費者正在探測(供應商驅動存在)。
DL_STATE_ACTIVE供應商和消費者驅動均存在。
DL_STATE_SUPPLIER_UNBIND供應商驅動正在解綁。
.=============================.
| |
v |
DORMANT <=> AVAILABLE <=> CONSUMER_PROBE => ACTIVE
^ |
| |
'============ SUPPLIER_UNBIND <============'
裝置連結的初始狀態由
device_link_add()根據供應商和消費者上驅動的存在情況自動確定。如果在任何裝置被探測之前建立連結,則其狀態被設定為DL_STATE_DORMANT。當供應商裝置繫結到驅動程式時,與其消費者的連結狀態會變為
DL_STATE_AVAILABLE。(從driver_bound()呼叫device_links_driver_bound()。)在消費者裝置被探測之前,透過檢查消費者裝置是否不在 wait_for_suppliers 列表中以及檢查到供應商的連結是否處於
DL_STATE_AVAILABLE狀態來驗證供應商驅動的存在。連結的狀態被更新為DL_STATE_CONSUMER_PROBE。(從really_probe()呼叫device_links_check_suppliers()。)這會阻止供應商解綁。(從device_links_unbind_consumers()呼叫wait_for_device_probe()。)如果探測失敗,到供應商的連結將恢復到
DL_STATE_AVAILABLE。(從really_probe()呼叫device_links_no_driver()。)如果探測成功,到供應商的連結將進展到
DL_STATE_ACTIVE。(從driver_bound()呼叫device_links_driver_bound()。)當消費者驅動稍後被移除時,到供應商的連結將恢復到
DL_STATE_AVAILABLE。(從device_links_driver_cleanup()呼叫__device_links_no_driver(),而device_links_driver_cleanup()又從__device_release_driver()呼叫。)在供應商驅動被移除之前,未繫結到驅動程式的消費者連結的狀態被更新為
DL_STATE_SUPPLIER_UNBIND。(從__device_release_driver()呼叫device_links_busy()。)這會阻止消費者繫結。(從really_probe()呼叫device_links_check_suppliers()。)已繫結的消費者會從其驅動程式中釋放;正在探測的消費者會等待直到完成。(從__device_release_driver()呼叫device_links_unbind_consumers()。)一旦所有到消費者的連結都處於DL_STATE_SUPPLIER_UNBIND狀態,供應商驅動程式就被釋放,連結恢復到DL_STATE_DORMANT。(從__device_release_driver()呼叫device_links_driver_cleanup()。)
API¶
參見 device_link_add()、device_link_del() 和 device_link_remove()。