---
tags: I/O, System Software
---
# USB(2): 電源管理
## USB 電源管理術語
在 USB 電源管理相關討論或規格書中,常使用到一些專有名詞。以下先針對幾個常見名詞做介紹,以便後續章節的說明。
### Remote Wakeup
當裝置被 suspend 時,它通常不會主動 resume,而是一直等到 host 指示它。舉例來說,對於處於低功耗狀態的儲存裝置,當使用者需要向其傳輸檔案檔案時,可能對相應的應用程式做出 I/O 請求,則 host 可以主動喚醒儲存裝置以達成目的。
不過,有些情境下,可能需要反過來仰賴裝置具有自行 resume 的能力,才能順利完成工作。比如當使用者按下處於低功耗的鍵盤,則裝置必須能夠通知 host。或者對 suspend 的 USB hub 插入 USB 裝置時,同理。此功能在 USB 上即稱為 "Remote Wakeup",有時也被稱為 "Wake On LAN"。
顯而易見的,因為裝置需要產生這樣的 resume 訊號,所以它不能完全關閉自己,僅是處於耗電較低的狀態。而通常 USB 裝置產生 resume 訊號的能力是軟體可以控制的。因此,在讓裝置進入睡眠之前,驅動程式必須引導裝置在 suspend 以前開啟 remote wakeup 功能。
### USB device idle
當 kernel 認為裝置不忙於做任何工作時,該裝置就處於 idle 狀態,也因此是被 suspend 的候選對象。確切的條件取決於裝置的驅動程式之實作。因此即使裝置沒有實際工作,驅動程式也可以聲明該裝置並不 idle。
舉例來說,在 Linux 中,只要 [usbfs](https://docs.huihoo.com/linux/kernel/2.6.26/usb/ch07.html) 檔案保持打開狀態,無論是否有任何 I/O 操作,裝置就不會被視為 idle。
### Global Suspend & Selective Suspend
USB 的 suspend 又可以分成 global suspend 和 selective suspend 兩種。
Global Suspend 是指把 USB host controller 後方的整個 bus 暫停。因此 bus 上的所有裝置在觀察到暫停後,都將進入 suspend 狀態。這做法的限制是顯而易見的。只要 topology 上的有任一裝置非 idle 狀態,所有裝置都無法真正進入低功耗模式。
與之相對的,Selective Suspend 透過將命令 SetPortFeature(`PORT_SUSPEND`) 傳送到 hub 上的特定 port,可讓該 port 進入 suspend,則 port 上的裝置將因為傳輸停止都進入 suspend。同時,hub 上其他的 port 則不會受影響。
得虧於 Selective Suspend 的特性,USB 裝置電源管理的粒度得以細分,在優化整體平台的省電上帶來重大收益。
### Function Suspend & Device Suspend
USB 3.0 規格定義了一個稱為 function suspend 的新功能。此功能使複合設備(composite devices)的單一 function 能夠獨立於其他 functions 進入低功耗狀態。例如一個定義了鍵盤的 function 和滑鼠 function 的複合設備, 使用者想保持鍵盤 function 處於工作狀態,但在一段時間內沒有移動滑鼠。則可以偵測滑鼠的空閒狀態並將其 function 進行 suspend,同時鍵盤仍維持在工作狀態。
無論裝置 function 的電源狀態為何,整個裝置可以直接轉換為 suspend 狀態,這稱為 device suspend。如果某個特定 function 和整個裝置進入 suspend 後,該 function 的 suspend 狀態將可以在裝置 suspend/resume 過程中保留。
與 USB 2.0 裝置的 remote wakeup 類似,USB 3.0 複合裝置中的單一 function 也可以獨立從低功耗狀態喚醒,而不會影響其他 function 的電源狀態。
## Link Power Management (LPM) on USB 2.0
### Overview
早期 USB 2.0 定義了 suspend/resume 作為 USB 的電源管理機制,以簡單的協議設計偵測 suspend 與發出信號進行 resume 的方式。但由於機制上的缺陷,其狀態轉換需 >20ms 的延遲,很難作為優化功耗的手段。
舉實際案例來說,為了 suspend 裝置,host 將暫停與裝置之間的 port 上的流量,則裝置如果在閒置 3 ms 後發現流量停止則得知需進入 suspend 狀態。此外,裝置還需要遵守只有在偵測到 suspend 之後的再 2 毫秒後才能啟動 resume。resume 的延遲在此機制下也更加繁重。
為此,USB2.0 標準隨後引入 LPM 機制作為替代方案,後者讓狀態之間的轉換可以降低到 10 us。留意到 LPM 的擴充需要同時更改 host 和 device 才能達到效果。但是這個特性是向後相容的,host 可以確定裝置是否支援 LPM,而支援 LPM 的裝置也可以於舊版本的 host 上正常運作。
新的 LPM 將連接狀態區分為 L0, L1, L2 和 L3,狀態的轉換如圖。留意到舊的 suspend/resume 機制其實已經隱含 L0, L2, L3 狀態的概念,只是沒有特定命名而已。
![image](https://hackmd.io/_uploads/HJ_a_kao6.png)
**L0(On)** 狀態下,port 可以被啟用以傳播 transaction signal。L0 中 port 可能正在主動發送或接收資料(L0-active),或是能夠執行此操作但尚未進行(L0-idle)。進入 L0 可能是透過 reset 或 resume signal(來自 L1 或 L2)。
**L1(Sleep)** 與 L2 類似,但差別在支援更細的粒度。進入 L1 的方法是透過向 hub 或 host port 做出轉換到 L1 的請求,LPM transaction 被傳送到下游裝置之後,只有後者回應 ACK,轉換才會成立。從 L1 離開則可能透過 remote wake, resume 訊號, reset 訊號或是斷開連接時。
L1 不像 L2 那樣對連接的裝置施加任何特定的功耗要求(如來自 VBUS - 在 USB 介面中由 host 提供電源電壓以供電的電路線之功耗)。處於 L1 時,host 或 device 都可以發起 resume 訊號。與 L2 相比,儘管 resume 的訊號等級相同,但 L1 到 L0 轉換相關的訊號持續時間和轉換延遲要短得多。
L1 下,host 會根據其目前 workload 策略,向裝置提供未來發出 resume 來離開 L1 的最短時間的提示,範圍是 50 至 1200 µs。則裝置可以根據這個時間提示來布置自己的電源效率最佳化策略。
**L2(Suspend)** 對應舊版本的 suspend 一詞。進入 L2 是由對 hub 或 host port 的命令觸發的,此時 hub 或 host port 停止發送訊號(並且可能會使 port 退出 high speed mode)。就像舊有的 suspend/resume 機制一樣,若裝置觀察到 3 ms 的不活動狀態,則轉換成立。
L2 對所連接的設備施加了功耗要求。而脫離可以透過 remote wake, resume 訊號, reset 訊號或是斷開連接時。
**L3(Off)** 下,hub 無法交換任何資料訊號。對應於斷電、斷開連線和停用狀態。
### 補充
一般來說,L1 的進入必須由 host 端的軟體來驅動,但由軟體發起轉換一定程度上限制了 L1 的優勢。相較於硬體直接控制,由軟體行為干預硬體必然會有額外的延遲。有些 xHCI controller 可以選擇支援稱為 hardware LPM 的功能,可讓 host 軟體只需對 controller 參數進行設定之下,然後硬體即可自動進行 L1 轉換。不過這僅限於非 hub 的裝置。
:::danger
USB 3.0 下似乎 xHCI 可以讓 hub 進 U1/U2? 但沒找到詳細的敘述說明。
:::
## LPM on USB 3.0
### Overview
![image](https://hackmd.io/_uploads/HkxyLOBn6.png)
如上圖,在 USB 3.0 中對 Link 狀態機做了更詳細的定義。對於 LPM 而言,U-state 是我們要重點關注的部分:
* U0: active 狀態
* U1: link idle-fast exit,暫停 Rx/Tx。由硬體觸發轉換
* U2: link idle-slow exit,暫停 clock gen(PLL)。由硬體觸發轉換
* U3: link suspend,裝置部分電源被關閉,僅保留 reset 和 wakeup 相關訊號判斷的邏輯。由軟體觸發轉換(SetPortFeature(PORT_LINK_STATE))
![image](https://hackmd.io/_uploads/BJxiuVlna.png)
## Latency Tolerance Message (LTM)
USB 3.0 裝置可以選擇性支援稱為 Latency Tolerance Message 的新功能。 如果 xHCI host controller 和裝置都支援 LTM,則將其打開可使系統硬體更了解其 PCI 裝置的延遲容忍值,以幫助在 power 和 performance 之間取捨。
LTM 協定使 USB 裝置能夠通知 host 在遇到意外副作用之前它們可以容忍服務停止多長時間。在 LTM 下,每個裝置提供稱為 Best Effort Latency Tolerance (BELT) 的數值。裝置將動態變更其 BELT 值,以更準確地反映例如較長的預期空閒時間。則平台可以利用 BELT 並搭配其他系統相關資訊,在系統層級節省更多能源,且避免產生意外副作用的風險。
## Autosuspend
在 Linux 系統上存在 [Runtime power management(Runtime PM, RPM)](https://docs.kernel.org/power/runtime_pm.html) 的機制,這允許系統上各元件在 idle 狀態時進入低功耗狀態,但不影響其他元件的運作。要達此目標,就需要 driver 和 kernel 兩方的配合。在 Runtime PM 之中另外存在 [autosuspend](https://docs.kernel.org/power/runtime_pm.html#autosuspend-or-automatically-delayed-suspends) 這個子集合,可在 RPM 的基礎上附加上 timer 以延遲 suspend 的時間(考慮來回 suspend/resume 可能帶來的附加成本)。
在 USB 子系統中,其在 RPM 的基礎上提供了 [USB autosuspend](https://lwn.net/Articles/373550/) 的 APIs。因此 USB 裝置可以藉此更為簡單的完成 RPM 的實作,達到更佳的省電效果。
### APIs
驅動程式負責實作 autosuspend 相關的 callback。具體是在 `struct usb_driver` 下的這三個指標。
```
.suspend
.resume
.reset_resume
```
* `suspend`: USB 會呼叫此函式來告知驅動程式裝置將被 suspend。若回傳負錯誤代碼,則 suspend 將中止。若正常則回傳 0,通常此情況還必須取消所有未完成的 [URB](https://docs.kernel.org/driver-api/usb/URB.html) (`usb_kill_urb()`) 且不再提交新的 URB。
* `resume`: 通知驅動程式裝置已經 resume,可以返回到正常工作狀態。URB 可以再次提交。
* `reset_resume`: 此 callback 的實作是可選的。這告知驅動程式裝置已 resume 並且也 reset。因為裝置可能已經遺失了大部分或全部狀態,為此驅動程式應該重做任何必要的裝置初始化。
此外,驅動程式必須告訴 USB 子系統允許 autosuspend 功能,具體是透過設置 `usb_driver` 結構中的 `supports_autosuspend` 為 1 以啟動。
在 USB 子系統會維護一個 counter,每當出現不應該 suspend 的事件時,USB core 就會觸發驅動程式的對應 callback,後者需由特定 API 去增加 counter;反過來說,如果該事件不復存在,則減少 counter。當 counter 為 0 時,核心 USB 子系統將可以判斷目標裝置能夠被 suspend。
在 callback 中可能會使用到以下幾個 APIs:
* `usb_autopm_get_interface()`: 增加 counter,若裝置原本狀態為 suspend,resume 之
* `usb_autopm_put_interface()`: 減少 counter,若歸零,則可能 suspend 裝置
* `usb_autopm_get_interface_async()`: 類似於 `usb_autopm_get_interface()`,但 resume 的進行可能非立即
* `usb_autopm_put_interface_async()`: 類似於 `usb_autopm_put_interface()`
:::info
async 方法與其對應函式的差別是可以用於 atomic context。
:::
* `usb_autopm_get_interface_no_resume()`: 增加 counter,但 resume 不會因此發生
* `usb_autopm_put_interface_no_suspend()`: 減少 counter,但 suspsend 不會因此發生
* `usb_enable_autosuspend()`: 此 API 用來啟用 autosuspend,通常是在 `probe()` 下,驅動程式確認裝置合適於 autosuspend 時使用
* `usb_disable_autosuspend()`: 禁用 autosuspend
有時驅動程式需要確保在 autosuspend 的前提是 remote wakeup 的啟用。舉例來說,如果使用者無法透過在鍵盤上打字來使鍵盤 remote wakeup,那麼就不該 autosuspend 之。此時驅動程式可以在 `probe()` 時將 `intf->needs_remote_wakeup` 設為 1,則 USB core 會根據 remote wakeup 可不可用決定是否應該 suspend 之。
* `usb_mark_last_busy`: 這告訴 PM core 裝置正結束工作,因此 autosuspend 的 idle-delay 應當將重新計算
留意到 async 操作會有 race 問題。例如,當 USB core 才剛確定裝置已 idle 夠久準備要 suspend 時,驅動程式可能才呼叫 `usb_autopm_get_interface_async()` 要求裝置繼續工作。驅動程式在 `suspend` 時應考慮此情形,將其與 I/O request handler 或 URB completion handler 正確同步。如果判斷驅動程式需要繼續使用該裝置,應該使 suspend 失敗並回傳 -EBUSY 。
不過上述這種 fail 處理方式不可以在 external suspend (指不是因 USB core 原因發生的 suspend,例如系統級的 suspend/resume(由 userspace 觸發)、remote wakeup(由裝置觸發)) 下被接受,只有 internal suspend(autosuspend) 時可以。實作上可通過 `PMSG_IS_AUTO()` 去判斷 suspend 發生時是屬於 external 還是 internal。
### Sysfs 介面
sysfs 提供了可用於控制 RPM 的使用者介面,可以透過 `/sys/bus/usb/devices/.../power/` (`...` 替代為裝置的 ID) 設定。 相關的屬性檔有:`wakeup`、`control`、`autosuspend_delay_ms`。
* wakeup: 如果裝置不支援 remote wakeup 則不會看該檔案。若存在,可對該檔案寫入 `enabled` 或 `disabled` 決定裝置下次 suspend 時是否啟用 remote wakeup
* control: 這檔案包含 `on` 或 `auto` 兩種內容。`on` 表示裝置將直接被 resume 並禁用 autosuspend (system suspend 不受影響)。`auto` 則 autosuspend 啟用
* `autosuspend_delay_ms`: 檔案包含一個整數值,是 kernel 啟動 autosuspend 之前應保持 idle 的 ms 時間。預設值為 2000。設為 0 表示裝置一進入 idle 就立即 suspend,設為負值則裝置將不會 autospend。這值稱為 idle-delay 時間
:::info
所以 `autosuspend_delay_ms` 設為 -1 和 `control` 設為 `on` 是同等效果!
:::
### WARNING!
雖然 USB 規範規定所有 USB 裝置必須支援電源管理。然而,事實是許多裝置對此的支持存在缺陷。suspend 大部分沒什麼問題,但是嘗試 resume 時,可能發生會自行斷開與 USB bus 的連接的狀況,甚至完全停止工作。這似乎在印表機和掃描器中尤其普遍。
為此理由,kernel 通常會對 hub 以外的所有裝置停用 autosuspend。因為除 Hub 以外的裝置經常有相關問題被回報。使用者可以根據實際狀況手動將它們新增至 udev 腳本以在開機使啟用。也可以更改預設的 idle-delay 時間,因 2 秒並不是每個裝置的最佳選擇。
如果驅動程式知道對應裝置可以正常支援 suspend/resume,則它可以自行啟用 autosuspend。例如,筆記型電腦的攝影機的視訊驅動程式可能會這樣做,因為這些裝置通常很少使用,適合 autosuspend。
### Dynamic PM and System PM
留意到 autosuspend 和 system suspend 共同運作的情況。
情況一: 當 system suspend 發生時,裝置已經 autosuspend。理論上 system suspend 後再 system resume 時,裝置應盡可能直接維持 suspend 狀態。但在實務上可能無法實現此理想。目前 kernel 實際採用的策略是在 system resume 後先 resume 所有裝置,再讓需要 suspend 的裝置在之後處理之。
情況二: 當 system suspend 正在進行時,發生 runtime PM 事件。例如,裝置可能會在 system suspend 時送出 remote wakeup 訊號。此時實際發生的結果可能是未知的。remote wakeup 可能成功,導致 system suspend 中止(abort)。或者 remote wakeup 失敗,但系統仍嘗試保持 active 狀態,從而導致系統在 system suspend 完成後立即 resume。又或者 remote wakeup 直接失敗並進入 suspend。
## Hardware link PM
前面我們提到 USB link 的電源狀態不僅可由軟體操控,也可由硬體自動引導 LPM。在使用 [xHCI](https://en.wikipedia.org/wiki/Extensible_Host_Controller_Interface) 介面的 USB host controller 下,這可以支援 USB 在閒置下由硬體切換至更省電的 link 狀態,在 USB2.0 會是 L1 而 USB3.0 則是 U1/U2,這些模式可提供一定省電之餘在 resume 時也不會太耗時。
硬體 LPM 的使用者介面位於每個 USB 裝置的 sysfs 的 `/sys/bus/usb/devices/.../power/` 路徑中,"..." 表示裝置 ID。相關屬性檔則有以下兩種:
* `usb2_hardware_lpm`: 當支援 LPM 的 USB2 裝置插入支援軟體 LPM 的 xHCI host root xhub 時,host 會先以軟體 LPM 測試是否裝置支援進出 L1 狀態。如果成功,host controller 也支援 USB2.0 硬體 LPM,此檔案將顯示
* 驅動程式將可以寫入 y/Y/1 啟用硬體 LPM,寫入 n/N/0 停用,這主要用於測試目的。
* `usb3_hardware_lpm_u1`、`power/usb3_hardware_lpm_u2`: 當支援 LPM 的 USB 3.0 裝置插入支援 link 電源管理的 的 xHCI host 時,它將檢查 BOS descriptor 中是否設定了 U1 和 U2 退出延遲。如果檢查通過且表示可以為裝置啟用 USB3 硬體 LPM,此檔案將顯示。
## Port Power Control
在某些情況下,USB 子系統還可以提供關掉 hub 上特定 port 電源的功能。具體是向 hub 發出 `Set/ClearPortFeature(PORT_POWER)` request 來控制電源。對於 root 或平台內部的 hub,則 host controller 驅動程式將 `PORT_POWER` request 轉換為 platform firmware (ACPI) control method 以調整至對應電源狀態。
收到 `ClearPortFeature(PORT_POWER)` 請求後,對應的 USB port 在邏輯上關閉。這表示 VBUS 實際上是否關閉取決於 hub 的行為,但無論如何,port 將失去與其裝置的連接、不會回應熱插拔事件、也不回應 remote wakeup 事件。
:::warning
如果裝置被設定為為需傳送 remote wakeup 事件,則欲藉由 port power control 進行特定 port 的斷電將被阻止。
:::
> 延伸閱讀:
> * [AC Power Control by USB Hub](https://www.gniibe.org/development/ac-power-control-by-USB-hub/)
> * [mvp/uhubctl](https://github.com/mvp/uhubctl)
## Reference
* [USB 2.0介面史上省電大戰略](https://www.2cm.com.tw/2cm/zh-tw/tech/C5796C88243946BBAF2A5B2DB9972997)
* [快速傳送多媒體內容 - USB 3.0牛刀小試](https://www.2cm.com.tw/2cm/zh-tw/archives/82589D1354D34F32B22E9C65A631038F)
* [Power Management for USB](https://www.kernel.org/doc/html/next/driver-api/usb/power-management.html)
* [USB selective suspend](https://learn.microsoft.com/en-us/windows-hardware/drivers/usbcon/usb-selective-suspend)
* [Link Power Management (LPM) in USB 2.0](https://techcommunity.microsoft.com/t5/microsoft-usb-blog/link-power-management-lpm-in-usb-2-0/ba-p/270812)
* [Demystifying USB Selective Suspend](https://techcommunity.microsoft.com/t5/microsoft-usb-blog/demystifying-usb-selective-suspend/ba-p/270736)
* [How to implement function suspend in a composite driver](https://learn.microsoft.com/en-us/windows-hardware/drivers/usbcon/how-to--implement-remote-and-function-wake-support)
* [USB Runtime suspend and Resume implementation in DWC3 USB controller](https://stackoverflow.com/questions/44842711/usb-runtime-suspend-and-resume-implementation-in-dwc3-usb-controller)
* [Linux: Testing USB 3.0 low-power modes (U1, U2, U3)](https://xillybus.com/tutorials/usb-superspeed-lpm-control-testing-with-linux)
* [USB autosuspend](https://lwn.net/Articles/373550/)