第 1 章說過,電源管理是 ACPI 存在的理由;前幾章的 Method 與 Notify 也一直繞著電源事件打轉。本章正式把地圖攤開:G、S、D、C、P 五個字母家族各管什麼、彼此如何嵌套;一台筆電從闔上蓋子到再次喚醒, OS 與韌體之間完整的握手順序是什麼。這張地圖是 Module 7 後兩章、乃至日後所有電源除錯工作的座標系。
不用急著背數字。這一章的每個狀態都會回答同三個問題: 斷了哪些電、保住了什麼 context、醒來要多久 ——抓住這三軸,所有表格都能自己推出來。
畫出 G/S/D/C/P 五類電源狀態的階層關係。
列出 S0 到 S5 各自的硬體狀態、喚醒延遲與適用場景。
分辨 D3hot 與 D3cold,說明為何這是實務除錯的重點。
依正確順序排出進入 S3 與喚醒的完整流程 (、、 的相對位置)。
讀懂 、 這類 Package 的內容與數值來源。
電源狀態的全景地圖
ACPI 把「省電」這件事切成五個正交的家族,各自作用在不同的層級:
llL6.0cm 家族 & 範圍 & 管理對象與一句話定位
G-states & G0–G3 & 整台系統,使用者視角:開著、睡著、關機、斷電
S-states & S0–S5 & 整台系統,OSPM 視角:G-state 的技術細分
D-states & D0–D3cold & 個別裝置:每顆裝置獨立的電源深度
C-states & C0–Cn & CPU 閒置:不跑指令時睡多深
P-states & P0–Pn & CPU 執行:跑指令時用多少頻率與電壓
五個家族的嵌套關係,用一棵樹看最清楚:
G-states(全域,使用者看得到的粗分類) ├── G0 ── S0:運作中 │ ├── CPU 閒置深度:C0 Cn(C-states) │ │ └── C0 執行中的頻率電壓:P0 Pn(P-states) │ └── 各裝置電源:D0 D3cold(D-states,隨時獨立切換) ├── G1 ── S1 S4:睡眠(S3 = 睡眠,S4 = 休眠) ├── G2 ── S5:軟關機(電源鍵可喚醒) └── G3:機械斷電(拔插頭,只剩 RTC 電池)
兩個重點讀法。第一,D/C/P 都是 S0 世界裡的事: 系統醒著時,個別裝置可以睡(D3)、CPU 可以打盹(C6)、 頻率可以下探(Pn),這正是現代「待機時間」的主要來源。 第二,S 與 G 的對應是固定的:G1 涵蓋 S1 到 S4, G2 就是 S5——講 G-state 給人聽,講 S-state 給程式看。
S-states:S0 到 S5
S-states 定義整機的睡眠深度。判準就是導言說的三軸—— 斷了哪些電、保住什麼、醒來多久:
lllcL4.0cm 狀態 & CPU & RAM & 喚醒延遲 & 說明
S0 & 執行中 & 供電 & — & 正常運作,靠 C/P/D 省電
S1 & 停止時脈 & 供電 & 近乎即時 & Power on Suspend,已絕跡
S2 & 斷電 & 供電 & 極短 & 幾乎沒有平台實作
S3 & 斷電 & self-refresh & 1–3 秒 & Suspend to RAM,傳統「睡眠」
S4 & 斷電 & 斷電(內容存磁碟) & 5–30 秒 & Suspend to Disk,「休眠」
S5 & 斷電 & 斷電 & 完整開機 & Soft Off,「關機」
工程上真正活躍的只有四個:S0、S3、S4、S5。 S3 斷掉 CPU 與大部分晶片組的電,只留 RAM 自我更新與少數喚醒電路, 所以喚醒快但怕斷電;S4 把 RAM 內容寫進磁碟後全斷, 喚醒慢但拔電池也不怕;S5 連 context 都不留,下次是全新開機。 註:S4 與 S5 硬體狀態幾乎相同,差別在「磁碟裡有沒有一份待恢復的 記憶體映像」——這解釋了為什麼兩者的喚醒電路與 數值常常長得很像。
近年輕薄筆電多改走 (Low Power S0 Idle, Windows 稱 Modern Standby,Linux 稱 s2idle 路徑之一): 系統名義上留在 S0,靠平台深度閒置狀態逼近 S3 的功耗, 換來「闔蓋仍可收信、開蓋即亮」的體驗。韌體透過 FADT 的 LOW_POWER_S0_IDLE 旗標宣告支援。本書聚焦傳統 S3 流程 ——它仍是理解一切的基礎,S0ix 的除錯思路建立在同一套物件上。
D-states:D0 到 D3cold
D-states 是「單一裝置」的電源深度,與整機狀態獨立: S0 下你的相機可以在 D3,S3 下你的網卡可能還醒著等 Wake-on-LAN。
llllL3.6cm 狀態 & 電源 & Context & 可喚醒系統 & 備註
D0 & 全開 & 保留 & — & 正常工作狀態
D1 & 略降 & 多半保留 & 類別定義 & 少有裝置實作
D2 & 更低 & 多半丟失 & 類別定義 & 少有裝置實作
D3hot & 主電源仍在 & 丟失 & 視裝置而定 & 仍可被匯流排看見
D3cold & 完全移除 & 丟失 & 需輔助電源 & 得靠平台重新上電
D3 其實是兩個狀態。D3hot:裝置自己深度省電, 但主電源軌還在——以 PCIe 為例,config space 仍有回應, OS 隨時能把它叫回 D0。D3cold:電源軌整條拔掉, 裝置從匯流排上「消失」,只能靠平台(下一章的 PowerResource, 經由 )重新上電,再重新列舉。 「裝置睡了就叫不醒」「resume 後裝置不見了」這類經典災情, 八成出在 D3cold 的上電順序或時序——先記住這條分水嶺, 下一章給你修它的工具。
系統睡眠的完整流程
現在把 S3 的入睡與喚醒流程走一遍。這條流程橫跨 OS、AML 與硬體, 每一站都有固定的角色;三個根層級的 Method——、 、——是韌體參與的三個掛勾點:
三個掛勾點的時機差異是考點也是雷區:(Transition To State)在driver 還沒動作前最早被叫,適合「趁裝置都醒著」 先知會 EC;(Prepare To Sleep)在 driver 都收工、 OS 即將儲存 CPU context 前執行,做最後的喚醒源設定; 最後 OS 從 Package 取出晶片組定義的 SLP_TYP 值, 連同 SLP_EN 位元一起寫進 FADT 描述的 PM1 Control 暫存器 ——這一寫下去,硬體立刻拉閘,程式碼不會有「下一行」。
喚醒是一條對稱的回程:
值得注意:S4 與 S5 也走同一套——關機前 OS 一樣會呼叫 ,所以「關機卡住」有時也是 ASL 的鍋; 而喚醒路徑上 執行時 driver 都還沒恢復, 在裡面碰還沒上電的裝置就是自找麻煩。
_Sx 物件與 _PTS/_WAK 實作
OS 怎麼知道這台機器支援哪些 S-state、SLP_TYP 該填多少? 答案是根層級的 Package:宣告了哪個, OS 才提供哪個。DSDT 沒有 ,睡眠選單就不會有睡眠。 每個 Package 的前兩個元素是寫進 PM1a/PM1b 控制暫存器的 SLP_TYP 值,數值由晶片組 datasheet 定義,純屬平台常數:
DefinitionBlock ("sleep.aml", "SSDT", 2, "DEMO", "SLEEP", 0x01)
// Which S-states this platform supports, and the // chipset-specific SLP_TYP values for each of them Name (_S0, Package (4) 0x00, 0x00, 0x00, 0x00) Name (_S3, Package (4) 0x05, 0x05, 0x00, 0x00) Name (_S4, Package (4) 0x06, 0x06, 0x00, 0x00) Name (_S5, Package (4) 0x07, 0x07, 0x00, 0x00)
OperationRegion (DBG0, SystemIO, 0x80, 0x01) Field (DBG0, ByteAcc, NoLock, Preserve) P80H, 8 // POST code debug port
Method (_TTS, 1) // earliest / latest hook P80H = 0x54 // log: TTS ran
Method (_PTS, 1) // Prepare To Sleep P80H = Arg0 // log target S-state If (Arg0 == 3) P80H = 0x53 // arm wake sources here
Method (_WAK, 1) // Wake handler P80H = 0xA0 // log: resumed Return (Package (2) 0x00, 0x00)
逐行拆解:
四個 Package 宣告本平台支援 S0、S3、S4、S5。 以 為例,0x05 就是這顆晶片組「進 S3」的 SLP_TYP 編碼——換一顆晶片組就是另一組數字, 抄錯的下場見下面的警告框。
收到目標 S-state(), 示範用 port 0x80 記 log——寫 POST code 除錯埠是 bring-up 階段追蹤睡眠流程的傳統手藝, 邏輯分析儀或除錯卡上看得一清二楚。
規格要求回傳兩個整數的 Package: 第一個是狀態碼(0 表示正常),第二個保留。 忘記 Return 是新手最常犯的 錯誤。
收到的參數同 ;喚醒收尾時 還會以 再被叫一次, 需要區分方向時檢查 是否為零。
在 Linux 上驗證平台宣告與實際行為:
顯示 這個入口實際走哪條路: 就是傳統 S3, 則是前面補充框說的 S0ix 路線——同一台機器可能兩者都支援,以中括號標示目前選擇。
Package 的 SLP_TYP 抄錯 datasheet: 一寫 SLP_EN 就直接關機、重開,或什麼都不發生。
對 EC 下錯命令、喚醒源沒 arm 好: 睡下去就再也叫不醒,只能長按電源鍵。
忘記恢復 改過的硬體狀態: 醒來後鍵盤失靈、風扇亂轉、熱鍵失效。
沒有 Return Package:某些 OS 會回報 ACPI 錯誤,甚至影響後續睡眠週期。
喚醒後 GPE status 沒清乾淨:立刻再觸發喚醒事件, 機器「秒醒」或陷入睡了又醒的迴圈。
除錯時先用 port 0x80 或 dmesg 定位「死在哪一站」, 再對照本章兩張流程圖,十之八九能鎖定範圍。
五大家族各司其職:G(使用者視角)、S(整機睡眠深度)、 D(單一裝置)、C(CPU 閒置)、P(CPU 效能); D/C/P 都活在 S0 裡。
S-state 三軸判讀:斷了哪些電、保住什麼 context、 醒來要多久;實務上活躍的是 S0、S3、S4、S5。
D3hot 主電源仍在、匯流排看得見;D3cold 電源全斷, 要靠 PowerResource 重新上電——除錯分水嶺。
入睡順序: \(\to\) driver 收工() \(\to\) \(\to\) 讀 Package \(\to\) 寫 SLP_TYP+SLP_EN;喚醒對稱走回來, 在 driver 恢復之前執行。
Package 宣告平台支援的睡眠狀態, 內容是晶片組定義的 SLP_TYP 常數。
用「斷電範圍、context、喚醒延遲」三軸,各用一句話描述 S3、S4、S5 的差別。
把下列事件排成正確的入睡順序:寫 SLP_EN、、 、driver 儲存 context、讀 Package。
Package 裡的 0x05 是什麼?誰決定這個數字? 如果這台板子換了晶片組,這個值會怎樣?
說出 D3hot 與 D3cold 的三個差異,並解釋為什麼 「resume 後裝置消失」通常指向 D3cold。
動手:在一台 Linux 機器上執行本章的三個觀察指令, 判斷它睡眠時走 S3 還是 s2idle;再執行 觀察一次 完整 suspend/resume 留下的訊息。
Power Resources
第 15 章的 D-states 定義了裝置「可以睡多深」,但誰去真的拉 GPIO、 關穩壓器?答案是 :ACPI 把每一條電源軌 抽象成一個「可開、可關、可查詢」的物件,並讓裝置宣告 「我在 D0 需要哪幾條軌」。OS 據此用 決定何時真正開關——兩顆裝置共用一條軌時,最後一個離開的人才關燈。 這是筆電裡攝影機、感測器、SSD 等周邊省電的實作核心, 也是 D3cold 能不能正確醒來的關鍵。
本章從 PowerResource 的宣告語法開始,走過 // 三件套、reference counting 的運作、裝置端的 到 掛勾,最後以「GPIO 控制的相機電源軌」 完整範例收尾。
寫出 PowerResource 宣告並解釋 SystemLevel 與 ResourceOrder 兩個參數。
實作 、、 三件套, 遵守各自的回傳值規約。
說明 OSPM 的 reference counting 如何決定電源軌 實際開關的時機。
用 到 把裝置需求掛上資源, 說明 與 PowerResource 的分工與執行順序。
寫出 宣告喚醒能力,說明 的角色。
PowerResource 物件:宣告與參數
PowerResource 的宣告長這樣(本體裡面放的是 Method, 所以它其實是一種「帶電源語意的迷你 Device」):
PowerResource (PWRC, 0x00, 0x0000) Method (_STA, 0) Return (0x01) // is it on? Method (_ON, 0) /* switch on */ Method (_OFF, 0) /* switch off */
三個參數的意義:
llL7.2cm 參數 & 型別 & 意義
名稱 & NameSeg & 資源名稱,一般命名規則
SystemLevel & 整數 & 此資源保持 ON 時,系統最深只能睡到的 S-state(0 表示只在 S0 有效)
ResourceOrder & 16 位元整數 & 全系統唯一的順序編號, 決定多條資源的開關次序
SystemLevel 把資源和 S-states 綁在一起:一條 SystemLevel 為 0 的軌,系統要進 S1 以下就必須先關掉它;宣告成 3, 則它可以陪系統睡到 S3。ResourceOrder 解決相依問題—— OSPM 開電時從小號碼開到大號碼,關電時反向。 時脈源、上游穩壓器給小號碼,吃電的下游給大號碼, 「先有電、再有時脈、再開裝置」的順序就有了保障。
OSPM 面對「同時要開好幾條軌」的場景(例如喚醒時一口氣 恢復多顆裝置)時,唯一的排序依據就是 ResourceOrder。 兩條資源撞號,開關次序就成了未定義行為——今天能開機 不代表下一版 OS 還照同樣順序。習慣做法:以 0x10 為間隔 編號(0x00、0x10、0x20……),日後要在中間插入新資源 不必全體改號。
三件套:_ON、_OFF、_STA
每個 PowerResource 必須提供三個零參數的 Method,規約如下:
:把資源打開。返回時資源必須已可用 ——電壓要穩、時脈要鎖定,必要的 ramp-up 延遲 (/)要做在這裡面。
:把資源關閉。同樣要等關閉序列完成才返回。
:回報目前狀態,只能回 0(關)或 1(開)。 OS 開機時靠它同步初始狀態,避免多此一舉的開關。
回傳 0x0F:那是 Device 版 的 bitmask 格式。PowerResource 版只認 0 與 1,回 0x0F 在某些 OS 上會被解讀成非零即開, 另一些則行為未定義——別賭。
立刻返回:GPIO 拉高不等於電壓站穩。 少了 ramp-up 等待,下游裝置在半路電壓下初始化, 症狀是「偶爾」讀不到裝置——機率性 bug 最難查。
繞過 OSPM 私自開關:在某個 裡 「順手」把共用軌關掉,等於無視 reference counting, 另一顆還在用的裝置直接陪葬。開關永遠交給 OSPM 決定。
OSPM 的 reference counting:何時真的關電
裝置宣告需求(下一節的 ),OSPM 替每條資源記帳:
有裝置進入「需要這條資源」的 D-state:計數加一; 0 變 1 的瞬間呼叫 。
裝置離開該 D-state:計數減一;歸零的瞬間 呼叫 。
計數在中間變動(1 變 2、2 變 1):什麼都不呼叫。
PowerResource 的 / 是機制, reference counting 是政策——政策永遠在 OS 手上。 你的 AML 不需要、也不應該自己判斷「還有沒有別人在用」: 只要每顆裝置誠實申報 ,共用軌的開關時機 自然正確。這是 OSPM「描述與決策分離」哲學在電源領域的 完整重演。
裝置端的掛勾:_PR0 到 _PR3
裝置用四個 Package 物件申報「各個 D-state 需要哪些資源」:
llL6.6cm 物件 & 對應狀態 & 內容
& D0 & 進入/停留 D0 所需的資源清單
& D1 & 同上,對應 D1
& D2 & 同上,對應 D2
& D3hot & D3hot 所需;全部關掉即 D3cold
那行請畫線:D3hot 與 D3cold 的差別, 在 ASL 層面就是「 列的資源開著還是關了」。 OS 想把裝置送進 D3cold,就把 資源的計數也釋放掉; 資源歸零斷電,裝置從匯流排上消失——第 15 章那條除錯分水嶺, 機關全在這個 Package 裡。
_PSx 與 PowerResource 的分工
裝置端還有一組 到 Method(第 8 章出現過), 和 PowerResource 是分工而非競爭的關係:
PowerResource:管「可能共用」的平台資源 ——電源軌、時脈、reset 線。誰用都一樣,所以抽出來共管。
:管「這顆裝置自己」的進出手續 ——儲存暫存器、通知 EC、設定內部省電模式。
順序是規格定死的:進 D0 時先把資源開好 ( 全 ON)再叫 ; 進 D3 時先叫 再放掉資源。 口訣:有電才辦事,辦完事才斷電。
喚醒能力:_PRW、_DSW 與 _PSW
有些裝置睡了還要能叫醒系統(網卡的 Wake-on-LAN、 USB 鍵盤喚醒)。裝置用 申報喚醒能力: Package 的第一個元素是喚醒用的 GPE 編號(第 14 章的那些 GPE), 第二個是「還能喚醒系統」的最深 S-state, 之後可以再列喚醒期間必須維持的 PowerResource:
Device (XHC0) // USB host controller
Name (_HID, "DEMO0D10") Name (_PRW, Package (2) 0x0D, 3) // GPE 0x0D, up to S3
Method (_DSW, 3) // Arg0: enable (1) / disable (0) wake // Arg1: target system S-state // Arg2: target device D-state // program EC / GPIO wake latches here
搭配的動詞是 (Device Sleep Wake):OS 在裝置入睡前 呼叫它,告知「要不要武裝喚醒、系統要去哪、你會睡多深」, AML 據此設定硬體的喚醒鎖存。老式的 (一個參數, 只管開關)已被 取代,但舊表裡仍常見—— 兩者並存時 OS 用 。喚醒真的發生時, 韌體或 handler 會對裝置 Notify 0x02(第 14 章的 Device Wake), 告訴 OS 是誰按的鈴。
完整範例:GPIO 電源軌與兩顆相機
把整章組裝起來:一條由 memory-mapped GPIO 控制的 3.3V 軌 ,供兩顆相機感測器共用。這是平板、筆電上 如假包換的真實場景:
DefinitionBlock ("pwr.aml", "SSDT", 2, "DEMO", "PWRRAIL", 0x01)
Scope (_SB)
// Memory-mapped GPIO controller; bit 3 of the // register at offset 0x40 drives the camera rail OperationRegion (GPIO, SystemMemory, 0xFED80000, 0x100) Field (GPIO, DWordAcc, NoLock, Preserve) Offset (0x40), , 3, // bits 0-2: other pins GP03, 1 // bit 3: camera rail enable
PowerResource (PCAM, 0x00, 0x0010)
Method (_STA, 0) Return (GP03) // 1-bit field: 0 or 1
Method (_ON, 0) GP03 = 1 Sleep (10) // rail ramp-up time
Method (_OFF, 0) GP03 = 0
Device (CAM0) // world-facing camera
Name (_HID, "DEMO0C01") Name (_PR0, Package (1) PCAM)
Method (_PS0, 0) Sleep (5) // sensor init delay
Method (_PS3, 0) // save sensor context here if needed
Device (CAM1) // user-facing camera Name (_HID, "DEMO0C02") Name (_PR0, Package (1) PCAM)
逐行拆解:
GPIO 暫存器用 Module 5 的 OperationRegion+Field 描述, 恰好 1 位元寬,讓 直接 就滿足「只回 0 或 1」的規約 ——順手的欄位切法能省掉一堆遮罩程式碼。
拉高 GPIO 後 等電壓站穩, 對應穩壓器 datasheet 的 ramp-up 時間; 通常不必等。
兩顆相機的 都列 : 這一行就是 reference counting 的報名表。
另有 / 做感測器 自己的手續——注意它們與 / 的相對順序由 OSPM 保證,AML 不必自己協調。
兩顆相機先後使用時,OSPM 的記帳過程如下:
在 Linux 上可以直接觀察裝置目前的 D-state, 驗證這套機制真的在動:
\(ls /sys/bus/acpi/devices/ | grep DEMO DEMO0C01:00 DEMO0C02:00\) cat /sys/bus/acpi/devices/DEMO0C01:00/power_state D0
PowerResource 把電源軌抽象成物件;SystemLevel 定義 「開著時系統最深睡到哪」,ResourceOrder 決定多資源 開關次序(開小到大、關大到小,全域唯一)。
三件套規約: 返回前資源必須可用、 完整關閉、 只回 0 或 1。
OSPM reference counting:0 變 1 才 、 歸零才 ;AML 寫機制,OS 定政策。
申報各 D-state 的資源需求; 全關即 D3cold。順序口訣: 有電才辦事(先資源後 ), 辦完事才斷電(先 後資源)。
喚醒鏈: 申報 GPE 與最深 S-state, 武裝喚醒,事件發生後 Notify 0x02。
說明 PowerResource 三個參數的意義;SystemLevel 為 0 與為 3 的資源,行為差在哪?
兩顆裝置共用一條軌,依序發生:A 進 D0、B 進 D0、 A 進 D3、B 進 D3。寫出每一步的計數值與被呼叫的 /。
進入 D0 時, 資源的 和裝置的 誰先執行?為什麼這個順序不能反過來?
的兩個元素 各是什麼意思?這顆裝置在 S4 下能喚醒系統嗎?
動手:仿照 pwr.dsl 寫一條由 SystemIO GPIO 控制的軌, 讓三顆裝置共用,其中一顆還宣告 ; 用 iasl 編譯通過,並畫出三顆裝置輪流進出 D0 時的 計數時序圖。
CPU Power Management
就算整台系統穩穩待在 S0,電源管理也一刻沒停:CPU 每一毫秒 都在「跑多快」與「睡多深」之間游走——前者是 (Performance),後者是 (Idle)。 韌體用一組 ACPI 物件把「這顆 CPU 有哪些檔位、怎麼切換」 告訴 OS;OS 的 governor 再依負載即時決策。
本章依序拆解 效能表、 閒置表、 多核協調;再看現代平台(HWP/) 的衝擊;最後用 ThermalZone 接上溫度保護閉環—— 熱管理正是 P-state 限速最大的客戶。
分辨 P-states、C-states 與 T-states 各自控制什麼。
寫出 legacy Processor 與現代 Device 兩種 CPU 物件宣告。
逐欄位解讀並撰寫 、、。
逐欄位解讀並撰寫 ,說明與 FADT P_LVL 的關係。
說明 的 SW_ALL/SW_ANY/HW_ALL 協調模式。
寫出含 、、、 的 ThermalZone。
兩條軸線:跑多快與睡多深
先把三個字母釘在座標上:
llL5.4cm 家族 & 控制什麼 & 決策者與範圍
P-states & 執行時的頻率與電壓 & cpufreq governor;P0 最快, 數字越大越慢越省
C-states & 閒置時睡多深 & cpuidle governor;C0 執行中, 數字越大越省、喚醒越慢
T-states & 時脈節流(強制打拍) & thermal 子系統; 過熱急救用,現代平台罕見
P 與 C 的關係一句話講完:P-states 只在 C0 有意義 ——CPU 得先醒著才談跑多快。T-states 用「跳過時脈週期」 硬降溫,效率遠差於降 P-state,帶過即可。
現代省電策略常是「用高 P-state 火速做完,鑽進深 C-state 睡久一點」(race to idle),而不是慢吞吞跑低頻。 所以 C-state 的品質(進得深、醒得快)往往比 P-state 檔位多寡更影響續航——韌體把 寫壞時, 使用者的第一個抱怨是「電池變差了」。
Processor 物件:兩代寫法
CPU 在 Namespace 裡也是裝置,但歷史留下兩種宣告。 舊式用專屬的 關鍵字,參數依序是名稱、 ProcID(對應 MADT 的編號)、P_BLK 埠位址與長度; ACPI 6.4 起這個關鍵字正式棄用,新式寫法就是普通的 加上 為 :
// Legacy keyword, deprecated since ACPI 6.4 (iasl warns): Processor (CPU0, 0x01, 0x00000410, 0x06)
// Modern declaration, a plain Device: Device (CPU0) Name (_HID, "ACPI0007") // processor device ID Name (_UID, 0) // must match MADT entry
不論哪種殼,、 這些電源物件都放在 它的 scope 底下。(或舊式的 ProcID)是 OS 把物件 對應到實體核心的鑰匙;每顆邏輯 CPU 都要有自己的一份宣告, 所以這類表格幾乎都由 BIOS 開機時動態生成 SSDT(Module 8 實戰)。
P-states:_PSS、_PCT 與 _PPC
三件套分工: 列出檔位選單、 指出 換檔暫存器、 動態限制可用上限。 是 「Package 的 Package」,每個內層六個欄位描述一個 P-state:
llL6.6cm 欄位 & 單位 & 意義
CoreFrequency & MHz & 此檔位的核心頻率
Power & mW & 此檔位的典型功耗
Latency & \(\mu\)s & 切換到此檔位的最壞延遲
BusMasterLatency & \(\mu\)s & bus master 存取恢復的最壞延遲
Control & 整數 & 寫入控制暫存器以「要求換檔」的值
Status & 整數 & 從狀態暫存器讀回以「確認到檔」的值
完整範例——一顆有三個檔位的 CPU,換檔走原生 MSR:
DefinitionBlock ("cpupm.aml", "SSDT", 2, "DEMO", "CPUPM", 0x01)
Scope (_SB)
Name (PPCV, 0) // current _PPC limit
Device (CPU0)
Name (_HID, "ACPI0007") Name (_UID, 0)
// P-state control/status registers; FFixedHW // means: use the native MSR mechanism Name (_PCT, Package (2) ResourceTemplate () Register (FFixedHW, 0, 0, 0) , ResourceTemplate () Register (FFixedHW, 0, 0, 0) )
// MHz mW Lat BMLat Control Status Name (_PSS, Package (3) Package (6) 2400, 35000, 10, 10, 0x1800, 0x1800, Package (6) 1800, 22000, 10, 10, 0x1200, 0x1200, Package (6) 1200, 12000, 10, 10, 0x0C00, 0x0C00 )
Method (_PPC, 0) Return (PPCV) // index of highest allowed
逐行拆解:
的兩個 Register 分別是 PERF_CTL 與 PERF_STATUS。 意為「功能固定硬體」: OS 認得這家 CPU 的原生機制(x86 上是 MSR),不必給位址。
檔位由快到慢排列(規格要求 P0 在前)。 Control 值是「寫進暫存器的位元樣式」,來自 CPU datasheet;Status 用來輪詢確認換檔完成。
回傳「目前允許的最高檔位索引」: 0 表示全部開放,1 表示 P0 禁用,以此類推。
的威力在「動態」。典型劇本是拔掉變壓器改用電池: EC 的 query 方法(例如 )把 改成 1, 再對 CPU 物件發 Notify 0x80(第 14 章的 Processor 專用碼), OS 便重讀 、即刻禁用 P0——不改表、不重開機, 限速就生效了。過熱降頻(thermal throttling)走的也是同一條路。
C-states:_CST 與它的歷史
C-state 的宣告走 :外層第一個元素是狀態個數, 之後每個內層 Package 四個欄位描述一個 C-state:
llL6.2cm 欄位 & 型別 & 意義
Register & ResourceTemplate & 進入方式:FFixedHW(原生指令) 或 SystemIO(讀 I/O 埠)
Type & 1–3 & 語意等級:C1/C2/C3
Latency & \(\mu\)s & 最壞喚醒延遲
Power & mW & 停留此狀態的功耗
Name (_CST, Package (3) 2, // number of C-states Package (4) ResourceTemplate () Register (FFixedHW, 1, 2, 0) , 1, // type C1: native halt 1, // latency: 1 us 1000 // power: 1000 mW , Package (4) ResourceTemplate () Register (SystemIO, 8, 0, 0x415) , 3, // type C3 200, // latency: 200 us 250 // power: 250 mW )
讀法:C1 用 ——x86 上就是 HLT/MWAIT 原生指令;C2/C3 用 ,OS 讀一下指定的 I/O 埠, 晶片組就把 CPU 送入對應深度(範例列 C1 與 C3,C2 同理)。 Type 告訴 OS 語意差異(C3 起快取不再 snoop), Latency 讓 governor 權衡值不值得睡這麼深。
一點歷史: 之前,C-state 寫死在 FADT 的 P_BLK 裡——固定的 P_LVL2/P_LVL3 埠、最多三個狀態、 延遲是死數字。 把這一切搬進 Namespace: 數量不限、暫存器任意,還可以動態換——韌體發 Notify 0x81,OS 就重讀(接電源開放深睡、用電池收淺)。 範例的 0x415 正是傳統 P_LVL3 的位址,兩代一脈相承。
多核協調:_PSD、_CSD 與 _TSD
兩顆核心共用電壓平面時,一顆想降頻、另一顆還在全速, 誰說了算?(P-state Dependency)就是申報書: 告訴 OS 哪些 CPU 同屬一個相依域、用哪種協調方式。 Package 五欄位依序是 NumEntries(固定 5)、Revision(0)、 Domain、CoordType、NumProcessors:
llL7.0cm CoordType & 名稱 & 意義
0xFC & SW_ALL & 域內每一顆 CPU 都得執行切換動作, 缺一不可
0xFD & SW_ANY & 域內任一顆 CPU 執行切換, 即對全域生效
0xFE & HW_ALL & 硬體自行協調;各 CPU 只管申報自己的需求
Name (_PSD, Package (1) Package (5) 5, 0, 0, 0xFC, 2 // domain 0, SW_ALL, 2 CPUs )
(C-state)與 (T-state)格式如出一轍。 實務經驗: 寫錯域或協調型別,常見症狀是 「頻率卡死最低檔」或「一顆核心降頻全機陪跑」——禍首在韌體表。
現代平台的現實:HWP 與 _CPC
讀到這裡你可能會問:我的筆電真的照 換檔嗎? 誠實的答案是——大概不是。
現代 Intel 平台(Skylake 起)支援 (Hardware P-states):OS 只給效能區間提示,檔位由硬體自主微調, Linux 的 走這條路時整份 基本被繞過;AMD 與 ARM 則多用 (CPPC)連續效能控制。但別急著把本章丟掉: 與 thermal 這半邊仍然活躍、大量既有平台與虛擬機仍靠 ,而 BIOS 每天都在動態生成含這些物件的 CPU PM SSDT——讀懂寫對它們仍是韌體日常(Module 8 實戰)。
自己的平台走哪條路,在 Linux 上一查便知: 目錄下的 若顯示 就是 HWP 路線,顯示 表示 OS 正在用你的 ;而 的 狀態名稱若帶 字尾(如 ), 代表 被採用了。
Thermal:溫度閉環
CPU 電源管理的最後一塊拼圖是熱。ACPI 用 物件描述一個溫度管理區,把感測、門檻與對策接成閉環。 核心物件一覽:
llL6.4cm 物件 & 型別 & 意義
& Method & 回傳目前溫度,單位 0.1 K
& 溫度值 & passive trip:超過就對 清單裡的 CPU 降頻
// & 整數 & passive 降頻演算法的係數與取樣週期
& 溫度值 & active trip:超過就開第 0 級風扇
& Package & 第 0 級主動散熱裝置(風扇)清單
& 溫度值 & critical trip:立即關機保護
(表外還有 ——進 S4 的門檻,與 ——輪詢週期,實務較少調整。)溫度單位是本節的頭號陷阱: 0.1 K(十分之一凱氏度)。換算式: 數值 = (攝氏 + 273.15)\(\times\) 10,所以 3032 即 30.05 \(^{\circ}\)C,95 \(^{\circ}\)C 的 trip point 要寫 3682。 完整範例——含被動與主動散熱的熱區,加上配套風扇:
DefinitionBlock ("thermal.aml", "SSDT", 2, "DEMO", "THERMAL", 0x01)
External (_SB.CPU0, DeviceObj) // CPU declared in DSDT
Scope (_SB)
PowerResource (FNP0, 0x00, 0x0020) Name (FANS, 0) // demo: rail state variable Method (_STA, 0) Return (FANS) Method (_ON, 0) FANS = 1 // real: GPIO write Method (_OFF, 0) FANS = 0
Device (FAN0) Name (_HID, EisaId ("PNP0C0B")) Name (_UID, 0) Name (_PR0, Package (1) FNP0)
Scope (_TZ)
ThermalZone (TZ00)
Name (TMPV, 3032) // demo sensor: 303.2 K // real code reads EC field Method (_TMP, 0) Return (TMPV)
Name (_PSL, Package (1) _SB.CPU0) Name (_TC1, 2) // passive loop constants Name (_TC2, 3) Name (_TSP, 100) // sample every 10 s Method (_PSV, 0) Return (3682) // 95 C
Method (_AC0, 0) Return (3332) // 60 C: fan on Name (_AL0, Package (1) _SB.FAN0)
Method (_CRT, 0) Return (3782) // 105 C: off
逐行拆解:
風扇是「披著 Device 外皮的 PowerResource 客戶」: (標準 為 PNP0C0B)掛著 ;OS 要開風扇就把它拉到 D0, reference counting 順勢呼叫 ——上一章的機制無縫重用。
+:過 95 \(^{\circ}\)C 就對 清單裡的 CPU 被動降頻,// 是回授迴圈的係數與週期。
+:60 \(^{\circ}\)C 開風扇; 多級風扇再加 /,數字越小越熱。 則是 105 \(^{\circ}\)C 的保命關機線。
通知沿用第 14 章:EC 跨門檻時 對熱區 Notify 0x80,OS 重讀 ;改 trip point 則 Notify 0x81。
事件驅動的完整閉環如下:
單位寫錯:把攝氏 95 直接填進 ——那表示 9.5 K,比絕對零度暖不了多少,OS 可能 永遠 throttle 或直接忽略這個熱區。
抄錯或設太低:一到夏天就 「莫名其妙關機」,dmesg 其實寫著 thermal critical, 但沒人想到是 DSDT 的一個數字。
讀了沒 保護的 EC 欄位:開機早期讀值全錯,風扇全速或誤關機 ——Module 5 的 在這裡救命。
兩條軸線:P-states 管 C0 內跑多快,C-states 管閒置 睡多深;race to idle 是現代策略主旋律。
CPU 物件兩代寫法:legacy (已棄用)與 +; 對應 MADT。
P-state 三件套: 檔位表、 換檔暫存器、 動態上限(配 Notify 0x80)。
取代 FADT P_LVL 埠:數量任意、 可動態更新(Notify 0x81); 用 SW_ALL/SW_ANY/HW_ALL 宣告多核協調。
ThermalZone 閉環: 感測(0.1 K!)、 降頻、 風扇、 關機。
用一句話分別說明 P-state、C-state、T-state 控制什麼; 為什麼說「P-state 只在 C0 有意義」?
寫出 六個欄位的名稱與單位;並回答: 裡 C1 為什麼用 而 C2/C3 用 ?0x415 這個位址歷史上叫什麼名字?
四顆核心兩兩共用電壓平面,寫出兩個域的 宣告(SW_ALL),並說明誤寫成一個域會發生什麼事。
動手:把 thermal.dsl 的 改成 45 \(^{\circ}\)C (先換算 0.1 K),加上 / 第二級風扇(需第二個 PowerResource 與 Device), 用 iasl 編譯通過;再在 Linux 上用 找出哪個 zone 是 。