寫了九個模組的 ASL,你可能還沒有真正「摸」過一台機器裡活著的 ACPI。 Linux 是觀察 ACPI 最友善的平台:核心把 ACPI 的一舉一動攤在 與 kernel log 裡,工具鏈又與第 21 章的 iASL 系出同門。本章建立一條 你之後會反覆走的分析路線:從 sysfs 找到裝置、反組譯 DSDT、讀懂 Method、對應到硬體——第 23、24 章的 Debug 與錯誤排除都蓋在這條 路線上。

  • 說明 與 之下各目錄與檔案的意義。

  • 使用 acpidump、acpixtract 與 ,把韌體中的表 反組譯回可讀的 ASL。

  • 解讀 dmesg 中開機階段的 ACPI 表載入訊息。

  • 依「Device \(\to\) Method \(\to\) OperationRegion \(\to\) Hardware」 的方法論閱讀 DSDT,並以筆電電池完整走一遍。

  • 使用 acpiexec 在使用者空間安全地執行韌體的 AML。

Linux 上的 ACPI 觀測點

Linux 核心解譯完 AML、建好 Namespace 之後,把大量內部狀態匯出到 。你最常拜訪的是三個地方:原始表、列舉出的裝置、 以及事件計數器。先看地圖:

/sys/firmware/acpi/ ├── tables/ ← 原始 ACPI Tables(二進位) │ ├── DSDT ← 主要硬體描述(AML) │ ├── FACP ← 即 FADT(指向 DSDT) │ └── SSDT1 SSDT2 ← 每張 SSDT 一個檔案 └── interrupts/ ← SCI 與各 GPE 的觸發計數 ├── gpe17 ← 單一 GPE:計數 狀態 遮罩 ├── gpe_all ← 所有 GPE 的總計數 └── sci ← SCI 中斷總數

/sys/bus/acpi/devices/ └── PNP0C0A:00/ ← 每個 acpi_device 一個目錄 └── path hid uid status ... ← 對應 ASL 物件(見下表)

tables/:原始表的家

之下的每個檔案,就是一張完整的 ACPI Table,內容與韌體放進記憶體的二進位一模一樣:

\(ls /sys/firmware/acpi/tables/ APIC BGRT DSDT FACP FACS HPET MCFG SSDT1 SSDT2 data\) sudo xxd -l 36 /sys/firmware/acpi/tables/DSDT 00000000: 4453 4454 a5f2 0100 028d 4c45 4e4f 564f DSDT......LENOVO 00000010: 5450 2d4e 3332 2020 0010 0000 494e 544c TP-N32 ....INTL

前 36 個 byte 正是標準表頭:signature、length、OEM ID,以及編譯器 簽名 (iASL)——這些檔案就是韌體的原貨。 第 22.3 節的工具鏈,本質上就是把它們打包、還原成可讀的 ASL。

devices/:列舉出來的裝置

核心每從 Namespace 解譯出一個 ,就建立一個 ,並在 產生一個 「:序號」目錄:

\(ls /sys/bus/acpi/devices/ ACPI0003:00 LNXPWRBN:00 PNP0A08:00 PNP0C0C:00 PNP0C0F:00 LNXCPU:00 LNXSYSTM:00 PNP0C02:00 PNP0C0A:00 PNP0C0D:00 ...\) cd /sys/bus/acpi/devices/PNP0C0A:00 \(cat path hid uid status \_SB_.PCI0.LPCB.EC0_.BAT0 PNP0C0A 1 31 \end{shell} 每個目錄裡的檔案,都對應到你在前面模組寫過的 ASL 物件: \begin{center} \small \begin{tabular}{llL{7.0cm}} \toprule \rowcolor{acpiblue!10} \textbf{檔案} & \textbf{對應物件} & \textbf{內容} \\ \midrule \code{path} & 節點位置 & Namespace 完整路徑(含底線補滿的 4 字元名) \\ \code{hid} & \code{\_HID} & Hardware ID 字串,driver 比對的依據 \\ \code{uid} & \code{\_UID} & 同型多實例時的區分編號 \\ \code{status} & \code{\_STA} & 裝置狀態(十進位),開機時評估的結果 \\ \code{physical\_node} & --- & symlink,指向綁定的 platform/i2c 等實體裝置 \\ \bottomrule \end{tabular} \end{center} 上例的 \code{status} 是 31,即 0x1F:bit 0 存在、bit 1 已啟用、 bit 2 顯示於 UI、bit 3 功能正常、bit 4 電池存在——正是第 8 章 \code{\_STA} 各 bit 的意義。\code{LNX} 開頭(\code{LNXSYSTM}、 \code{LNXCPU})是核心合成的節點,不是 DSDT 寫的。 \subsection{interrupts/:抓事件的計數器} 韌體要主動通知 OS 事件時,發出 \keyterm{SCI}(System Control Interrupt);SCI 的主要來源是 \keyterm{GPE}(General Purpose Event),每個 GPE bit 觸發後核心執行 \code{\_GPE} scope 下對應的 \code{\_Lxx}/\code{\_Exx} handler。這個目錄把每一路的觸發次數 攤開給你看: \begin{shell}[title={GPE 與 SCI 計數}]\) cat /sys/firmware/acpi/interrupts/sci 8241

\(grep -H . /sys/firmware/acpi/interrupts/gpe1? | sort -t: -k2 -rn /sys/firmware/acpi/interrupts/gpe17: 8117 enabled unmasked /sys/firmware/acpi/interrupts/gpe10: 121 enabled unmasked /sys/firmware/acpi/interrupts/gpe13: 3 enabled unmasked /sys/firmware/acpi/interrupts/gpe11: 0 invalid unmasked \end{shell} 檔名與 \code{\_Lxx} 的編號都是\textbf{十六進位}:\code{gpe17} 對應 \code{\_L17} 或 \code{\_E17}。除了計數,檔案還顯示該 GPE 是否啟用 (\code{enabled}/\code{invalid},後者表示沒有對應 handler)與是否被 遮罩。這個目錄是抓 \keyterm{GPE storm}(某路 GPE 瘋狂觸發、吃光 CPU) 的神器,第 24 章的 Case Study 會實際用它辦案。 \begin{notebox}[固定事件也有計數] 同目錄 \code{ff\_} 開頭(如 \code{ff\_pwr\_btn} 電源鍵)是 Fixed Event 計數,\code{error} 記錄 GPE 處理出錯次數。\code{gpeXX} 檔案還可寫入 \code{echo disable} 暫時關閉——第 24 章的急救手段。 \end{notebox} \section{dmesg:開機時的 ACPI 足跡} 核心開機的前幾毫秒,就把它找到的每一張表逐一列在 kernel log 裡。 這是你確認「韌體到底交了什麼給 OS」最快的方法: \begin{shell}[title={開機時的表載入訊息}]\) sudo dmesg | grep "ACPI:" | head -8 ACPI: RSDP 0x00000000BFEFE014 000024 (v02 LENOVO) ACPI: XSDT 0x00000000BFEFD0E8 0000B4 (v01 LENOVO TP-N32 00001000) ACPI: FACP 0x00000000BFEF9000 0000F4 (v05 LENOVO TP-N32 00001000) ACPI: DSDT 0x00000000BFEC7000 01F2A5 (v02 LENOVO TP-N32 00001000 INTL 20230628) ACPI: SSDT 0x00000000BFEF8000 0003A2 (v02 LENOVO TP-SSDT 00001000 INTL 20230628) ACPI: Interpreter enabled ACPI: PM: (supports S0 S3 S4 S5) ACPI: EC: interrupt mode used

以 DSDT 那一行為例,欄位由左到右:signature 之後是表的實體記憶體 位址與長度(,十六進位 byte 數,約 127 KB); 是表格式 revision(DSDT v2 代表 64 位元整數語意); 是 OEM ID 與 OEM Table ID——這張表來自 哪家、哪個平台;結尾 是編譯器 ID 與版本, 就是 iASL,日期即 ACPICA 釋出版本——看得出 OEM 用了多舊的工具鏈。

接著的 表示 ACPICA 已完成 Namespace 建立; 是從 DSDT 掃出的 睡眠狀態。 開完機先跑一次 是好習慣:沒輸出最好,有輸出也別慌——第 24 章教你逐字讀懂。

acpidump 工具鏈:把韌體變回原始碼

要讀 DSDT 的內容,得先把它從韌體手上拿回來、再反組譯成 ASL。 標準流程三步,全部來自 (Debian/Ubuntu 套件名; Fedora 為 ,Arch 為 ):

\(sudo acpidump > acpi.out\) acpixtract -a acpi.out DSDT - 127653 bytes written to dsdt.dat SSDT - 930 bytes written to ssdt1.dat ...

\(iasl -d dsdt.dat Disassembly completed ASL Output: dsdt.dsl - 733725 bytes \end{shell} \code{acpidump} 把記憶體中所有表以十六進位傾印成一個文字檔; \code{acpixtract -a} 把每張表還原成二進位 \code{.dat}; \code{iasl -d} 把 AML 反組譯回 \code{.dsl}。這份兩萬多行的 \code{dsdt.dsl} 就是這台筆電的完整硬體描述。 \begin{notebox}[反組譯的兩個實用技巧] \begin{itemize} \item DSDT 與 SSDT 常互相引用,單獨反組譯會缺外部符號、 導致方法參數個數判讀錯誤:用 \code{iasl -e ssdt*.dat -d dsdt.dat} 讓 SSDT 提供符號。 \item 只要 DSDT 一張表時可直接 \code{sudo cp /sys/firmware/acpi/tables/DSDT dsdt.dat}; acpidump 的優點是打包全部、適合附在 bug report。 \end{itemize} \end{notebox} \section{閱讀 DSDT 的方法論:以筆電電池為例} 兩萬行的 dsl 檔不能從第一行讀起。韌體工程師讀 DSDT 永遠帶著問題來, 沿著固定路徑走:\textbf{Device $\to$ Method $\to$ OperationRegion $\to$ Hardware}——先用 \code{\_HID} 找到 Device,再看 OS 會呼叫的 Method,Method 讀寫的 Field 屬於哪個 OperationRegion,最後由 region 的空間型別與位址對應到真正的硬體。每一步都把答案縮小一圈。 以最生活化的「筆電電池」完整走一遍(標準 \code{\_HID} 是 \code{PNP0C0A},Control Method Battery,第 8 章)。 \textbf{第一步:從 sysfs 找到 Device。}22.1 節已查出電池的路徑是 \nspath{\_SB\_.PCI0.LPCB.EC0\_.BAT0}——光看路徑就知道它是 Embedded Controller 的下游裝置。到 dsl 裡找到它: \begin{shell}[title={Step 1: locate the device}]\) grep -n "PNP0C0A" dsdt.dsl 9482: Name (_HID, EisaId ("PNP0C0A"))

第二步:找到 Method。跳到 dsl 第 9482 行附近, 看 的內容(節錄並簡化):

// dsdt.dsl excerpt (simplified for teaching) Scope (_SB.PCI0.LPCB.EC0)

Device (BAT0)

Name (_HID, EisaId ("PNP0C0A")) // Control Method Battery Name (_UID, One)

Name (PBST, Package (0x04) Zero, // [0] state: bit0 discharging, bit1 charging Ones, // [1] present rate (mA or mW) Ones, // [2] remaining capacity Ones // [3] present voltage (mV) )

Method (_BST, 0, Serialized) PBST [Zero] = BST0 // EC field: state PBST [One] = BRTE // EC field: rate PBST [0x02] = BRCP // EC field: capacity PBST [0x03] = BVLT // EC field: voltage Return (PBST)

逐行拆解: 是規格定義的 4 元素 Package(狀態、瞬時 電流或功率、剩餘電量、電壓),OS 每次查電量都呼叫 (Battery Status),把四個 Field 名字(、 ……)的值填進 Package 後回傳—— 本身不碰 硬體位址,真正的存取藏在 Field 宣告裡。靜態資訊(設計容量、 序號等)在兄弟方法 /,讀法完全相同。

第三步:找到 OperationRegion。在 dsl 裡搜尋 的宣告處:

// dsdt.dsl excerpt (simplified for teaching) Scope (_SB.PCI0.LPCB.EC0) OperationRegion (ERAM, EmbeddedControl, Zero, 0xFF) Field (ERAM, ByteAcc, Lock, Preserve) Offset (0xA0), BST0, 8, // battery state BRTE, 16, // present rate BRCP, 16, // remaining capacity BVLT, 16 // present voltage

第四步:對應 Hardware。region 空間是 :EC 內部 256 byte 的暫存器窗口(第 9–11 章)。「讀電池電壓」的完整真相:OS 呼叫 \(\to\) interpreter 讀 Field \(\to\) EC 協定讀暫存器 \(\to\) EC 韌體透過 SMBus 問電池的 gas gauge 晶片。ASL 的角色,就是把 EC 暫存器配置翻譯成 OS 聽得懂的標準介面。

最後用 Linux 的電池驅動驗證這條鏈真的在動:

\(cat /sys/class/power_supply/BAT0/status Discharging\) cat /sys/class/power_supply/BAT0/voltage_now 11460000

的單位是 \(\mu\)V:battery driver 呼叫 拿到 mV 值 11460,乘以 1000 匯出;state、rate、 capacity 欄位則對應 、(或 )與 /。 「EC 暫存器 \(\to\) Field \(\to\) \(\to\) sysfs」這條 翻譯鏈至此完整對上。

  1. 安裝工具:。

  2. 反組譯:、 、 。

  3. 用 找出 電池的 Namespace 路徑。

  4. 在 找到 ,畫出「Method \(\to\) Field \(\to\) EC offset」對照表。

  5. 對照 驗證單位換算; 桌機改追 (Lid)或 (電源鍵)。

acpiexec:在使用者空間執行 AML

讀懂了 ASL,下一個願望自然是「跑跑看」,但改一行 DSDT 就重開機 太昂貴。 把 ACPICA interpreter 包成使用者程式: 載入你給的表、建好 Namespace,讓你像用 gdb 一樣呼叫任何 Method。

\(acpiexec dsdt.dat ssdt1.dat ssdt2.dat Table [DSDT] - 2143 Objects with 87 Devices, 612 Methods - find BAT0 \_SB_.PCI0.LPCB.EC0_.BAT0 Device - evaluate \_SB.PCI0.LPCB.EC0.BAT0._STA Evaluation returned object 0x55e3c2a4, buffer length 18 [Integer] = 000000000000000F - execute \_SB.PCI0.LPCB.EC0.BAT0._BST Execution returned object 0x55e3c4f0, buffer length 78 [Package] Contains 4 Elements: [Integer] = 0000000000000000 ...(all four elements are zero) \end{shell} 除了示範中的 \code{find}(用 4 字元名反查完整路徑)與 \code{evaluate}(執行 Method,\code{execute} 是同義字),常用的 還有 \code{namespace}(傾印整棵或某子樹的 Namespace)、 \code{methods}(列出所有 Method)與 \code{help}。帶參數執行時以 空白分隔、數字用 \code{0x} 前綴、字串加引號——第 23 章會實際用到。 注意上面的執行結果:機器明明插著電池,\code{\_STA} 卻回 0x0F (bit 4 電池存在沒亮)、\code{\_BST} 全是 0。這不是 bug: \begin{warnbox}[acpiexec 跑的是模擬硬體] acpiexec 在使用者空間執行,\textbf{不能}碰真正的 EC、MMIO 或 I/O Port:所有 OperationRegion 讀寫都由模擬 handler 接住,讀取 通常回 0。它適合驗證\textbf{控制流程、型別、Namespace 解析} (第 24 章會用它重現錯誤);要看真實硬體值,回到真機的 \code{/sys/class/power\_supply/}。 \end{warnbox} 即使讀值是假的,acpiexec 仍讓「改 ASL\)\(編譯\)\(執行」的 迴圈從一次重開機縮短到一秒鐘——第 23 章的 Debug 實驗會先在這裡 跑通,再放上真機。 \begin{keybox}[本章重點回顧] \begin{itemize} \item \code{/sys} 三大觀測點:\code{tables/}(韌體原貨)、 \code{devices/}(列舉結果:path/hid/uid/status)、 \code{interrupts/}(GPE/SCI 計數器)。 \item dmesg 開機訊息列出每張表的位址、大小、OEM 與編譯器版本, 是確認「韌體交了什麼」的第一手資料。 \item 標準工具鏈:\code{sudo acpidump > acpi.out}\)\(\code{acpixtract -a}\)\(\code{iasl -d}(記得 \code{-e} 帶上 SSDT)。 \item 讀 DSDT 的固定路徑:Device\)\(Method\)\(OperationRegion\)\(Hardware。 \item acpiexec 讓你在使用者空間執行 AML;region 讀寫是模擬的, 驗流程不驗真值。 \end{itemize} \end{keybox} \begin{exbox} \begin{enumerate} \item \code{tables/}、\code{devices/}、\code{interrupts/} 三個目錄各自回答什麼問題?除錯時分別在哪一步派上用場? \item 某裝置的 \code{status} 檔案顯示 11(十進位)。換算成 \code{\_STA} 的 bit,這個裝置處於什麼狀態?OS 會載入它的 driver 嗎? \item 為什麼反組譯 DSDT 時建議加 \code{-e ssdt*.dat}? 少了它會出什麼類型的問題? \item 動手:完成 22.4 節的 Lab,並找出上蓋開關(\code{PNP0C0D}) 的 \code{\_LID} 讀哪個 Field、屬於哪個 OperationRegion。 \item 動手:用 acpiexec 載入反組譯出的表,\code{find} 電池 (或 Lid),\code{evaluate} 其 \code{\_STA} 與 \code{\_BST},解釋你看到的回傳值。 \end{enumerate} \end{exbox} \chapter{ACPI Debug} \label{ch:acpidebug} C 程式不會動了,你會加 printf;AML 在核心深處執行,既沒有終端機 也沒有除錯器,怎麼辦?ASL 其實內建了自己的 printf——\keyterm{Debug 物件}。往它寫任何東西,interpreter 就替你印出來。問題是:印去哪裡? 要開哪些開關才看得到?這正是本章要打通的環節。 本章先講 Debug 物件的語法與原理,接著沿著「ASL\)\(AML\)\(Interpreter\)\(kernel log」把輸出的旅程走一遍,最後動手做一個 完整的 Lab:寫一張會說話的 SSDT,讓它在你的 dmesg 裡留言。 \begin{goalbox} \begin{itemize} \item 使用 \code{Store (..., Debug)} 與 \code{Debug = ...} 兩種寫法輸出除錯訊息。 \item 畫出 Debug 輸出從 ASL 原始碼到 kernel log 的完整路徑。 \item 說明 \code{CONFIG\_ACPI\_DEBUG}、\code{acpi.debug\_layer} 與 \code{acpi.debug\_level} 三個開關的分工,並寫出常用組合。 \item 於執行期透過 \code{/sys/module/acpi/parameters/} 調整輸出。 \item 用 SSDT 加 initrd(或 acpiexec)實際看到自己的 Debug 輸出。 \end{itemize} \end{goalbox} \section{Debug 物件:一個只能寫的虛擬物件} \code{Debug} 是 ASL 預先定義的特殊物件:它不存在於 Namespace、 沒有型別、\textbf{只能當 Store 的目標},不能讀。每次有值寫入, interpreter 就把該值連同型別印到除錯輸出。兩種寫法完全等價: \begin{asl}[title={片段:Debug 物件的兩種寫法}] Store ("Hello ACPI", Debug) // legacy operator style Debug = "Hello ACPI" // ASL 2.0 symbolic style Store (Local0, Debug) // print a local variable Debug = Arg0 // print a method argument Debug = CNT0 // print a named object \end{asl} 舊韌體與反組譯輸出多為 \code{Store} 寫法,新專案建議用 \code{Debug =}——這也是本書一貫「兩種語法都要認得」的原則。 各型別的輸出樣式如下(以 kernel log 為例): \begin{center} \small \begin{tabular}{lL{8.8cm}} \toprule \rowcolor{acpiblue!10} \textbf{寫入型別} & \textbf{輸出樣式(示意)} \\ \midrule String & \code{ACPI Debug: String [0x0A] "Hello ACPI"} \\ Integer & \code{ACPI Debug: Integer 0x000000000000002A} \\ Buffer & \code{ACPI Debug: Buffer [0x04] = 0xDE 0xAD 0xBE 0xEF} \\ Package & \code{ACPI Debug: Package [0x02]},並逐一遞迴列出元素 \\ \bottomrule \end{tabular} \end{center} 方括號內是長度;Integer 一律以 64 位元十六進位列印。把這些樣式記個 大概,之後在幾千行 dmesg 中一眼就能撈出自己的訊息。 下面是本章的主角:一張完整、可編譯的 SSDT,開機時會自我介紹。 (\code{\_INI} 會在核心列舉到該裝置、且 \code{\_STA} 表示存在時 自動執行一次——這讓我們不需要任何 driver 就能觸發 Debug 輸出。) \begin{asl}[title={dbgdemo.dsl:會在開機時說話的 SSDT}] DefinitionBlock ("dbgdemo.aml", "SSDT", 2, "DEMO", "DBGDEMO", 0x00000001) { Scope (\_SB) { Device (DBG0) { Name (_HID, "DEMO0002") Name (MSG0, "Hello ACPI from DBG0") Method (_STA, 0, NotSerialized) { Return (0x0F) // present, enabled, functioning } Method (_INI, 0, Serialized) { Debug = "DBG0._INI: SSDT is alive" Debug = MSG0 Local0 = 0x2A Debug = Local0 Debug = Buffer (0x04) { 0xDE, 0xAD, 0xBE, 0xEF } Store ("legacy style still works", Debug) } Method (DBLE, 1, Serialized) { Debug = Arg0 // echo the argument Return (Arg0 * 2) } } } } \end{asl} 逐段拆解: \begin{itemize} \item \code{DefinitionBlock} 的簽章是 \code{SSDT}:我們是去 \textbf{補充} DSDT,不是取代它(第 18--20 章的做法)。 \item \code{\_STA} 回 0x0F,確保核心認為裝置存在,才會呼叫 \code{\_INI}。 \item \code{\_INI} 依序印出 String、Named object、Local、Buffer, 以及一行 legacy 寫法——正好把上表的樣式各示範一次。 \item \code{DBLE} 帶一個參數並回傳兩倍,留給 acpiexec 練習 參數傳遞。 \end{itemize} \section{一行 Debug 的旅程} 「寫入 Debug 就會印出來」的背後是一條完整的管線。理解它, 之後任何一站沒動靜你都知道去哪查: \begin{vflow}[4.2mm] \fnode{ASL:Debug = 訊息或值} \fnodel{iasl 編譯}{AML:DebugOp(0x5B 0x31)為 Store 目標} \fnode{ACPI Interpreter(ACPICA)攔下對 Debug 的寫入} \fnodel{兩道閘門過濾}{acpi\_ex\_do\_debug\_object()} \fnode{printk 寫入 kernel ring buffer} \fnode[flownodealt]{dmesg / journalctl -k 顯示} \end{vflow} 編譯期,\code{Debug} 變成 AML 的 \code{DebugOp}(extended opcode \code{0x5B 0x31})。執行期,interpreter 發現 Store 的目標是 Debug, 就呼叫 ACPICA 的 \code{acpi\_ex\_do\_debug\_object()} 把值格式化後 丟給 OS 的列印服務——Linux 上就是 \code{printk}。但訊息要真的出現 在 dmesg,必須通過兩道閘門,這是下一節的主題。 \section{打開輸出:兩道閘門} \textbf{閘門一:編譯期選項 \code{CONFIG\_ACPI\_DEBUG}。} ACPICA 的所有除錯列印(包括 Debug 物件)都包在條件編譯裡; 核心沒開這個選項,程式碼根本不存在。先確認你的核心: \begin{shell}[title={檢查閘門一}]\) grep CONFIG_ACPI_DEBUG /boot/config-\((uname -r) CONFIG_ACPI_DEBUG=y \end{shell} 多數發行版的預設核心\textbf{有}開此選項(例如 Ubuntu),但若顯示 \code{is not set},就得改用發行版的 debug 核心或自行編譯。 \textbf{閘門二:執行期的 \code{debug\_layer} 與 \code{debug\_level}。} 兩個 32 位元的 bitmask,共同決定哪些訊息放行: \begin{itemize} \item \code{debug\_layer}——\textbf{誰}可以說話:ACPICA 依元件劃分 (interpreter、namespace、事件……),每個元件一個 bit。 \item \code{debug\_level}——說\textbf{哪一類}話:訊息依類型劃分 (初始化、資訊、Debug 物件輸出……),每類一個 bit。 \end{itemize} 兩者都過才會印。常用的名稱與值(節錄自 \code{include/acpi/acoutput.h}): \begin{center} \small \begin{tabular}{llL{5.4cm}} \toprule \rowcolor{acpiblue!10} \textbf{名稱} & \textbf{值} & \textbf{意義} \\ \midrule ACPI\_ALL\_COMPONENTS & 0xFFFFFFFF & layer:全部元件 \\ ACPI\_EVENTS & 0x00000004 & layer:GPE 與 Notify 路徑 \\ ACPI\_NAMESPACE & 0x00000010 & layer:Namespace 操作 \\ ACPI\_EXECUTER & 0x00000080 & layer:AML 執行核心 \\ \midrule ACPI\_LV\_INIT & 0x00000001 & level:初始化訊息 \\ ACPI\_LV\_DEBUG\_OBJECT & 0x00000002 & level:\textbf{Debug 物件輸出} \\ ACPI\_LV\_INFO & 0x00000004 & level:一般資訊 \\ \bottomrule \end{tabular} \end{center} 實務上你只需要背一個組合——「只看 Debug 物件輸出」: layer 全開、level 只開 \code{ACPI\_LV\_DEBUG\_OBJECT}: \begin{center} \small \begin{tabular}{L{6.2cm}ll} \toprule \rowcolor{acpiblue!10} \textbf{目的} & \textbf{debug\_layer} & \textbf{debug\_level} \\ \midrule 只看 Debug 物件輸出(標準組合) & 0xFFFFFFFF & 0x00000002 \\ Debug 物件+初始化訊息 & 0xFFFFFFFF & 0x00000003 \\ 一般資訊全開 & 0xFFFFFFFF & 0x00000007 \\ 全部訊息(洪水,慎用) & 0xFFFFFFFF & 0xFFFFFFFF \\ \bottomrule \end{tabular} \end{center} 設定方式有二。開機參數(對 \code{\_INI} 這類開機期執行的方法是 唯一選擇): \begin{shell}[title={開機參數(編輯 GRUB)}]\) sudo nano /etc/default/grub GRUB_CMDLINE_LINUX="acpi.debug_layer=0xFFFFFFFF acpi.debug_level=0x2" \(sudo update-grub && sudo reboot \end{shell} 或執行期動態調整(對之後才執行的方法生效,例如 EC query、 driver 呼叫的方法): \begin{shell}[title={執行期調整}] # echo 0xFFFFFFFF > /sys/module/acpi/parameters/debug_layer # echo 0x00000002 > /sys/module/acpi/parameters/debug_level \end{shell} \begin{warnbox}[別把 log 開成洪水] \code{debug\_level=0xFFFFFFFF} 會讓每個 AML opcode、每次 namespace 查找都印一行,每秒數千行,瞬間淹掉 ring buffer,機器也會明顯變慢。 需要全開時搭配 \code{log\_buf\_len=8M} 加大緩衝區,抓完立刻關掉。 日常除錯永遠從 \code{0x2}(只看 Debug 物件)開始,不夠再加。 \end{warnbox} \begin{notebox}[新版核心的預設值] 較新的 ACPICA 已把 \code{ACPI\_LV\_DEBUG\_OBJECT} 納入預設 \code{debug\_level},因此有些機器只開 \code{CONFIG\_ACPI\_DEBUG} 就能看到 Debug 輸出。但預設值隨版本變動,\textbf{顯式設定}才不吃 版本運氣——寫進 SOP 的永遠是上面那組參數。 \end{notebox} \section{Lab:讓核心印出你的 Debug 訊息} 把 23.1 的 \code{dbgdemo.dsl} 放上真機。Linux 提供 \keyterm{Table Upgrade} 機制:把 \code{.aml} 打包成 cpio 檔接在 initrd 前面,核心開機時會安裝這張 SSDT(同第 20 章載入 SSDT 的 思路,這裡走 initrd 版本)。 \begin{labbox}[SSDT + initrd + dmesg 完整流程] \begin{enumerate} \item 編譯:\code{iasl dbgdemo.dsl} 產生 \code{dbgdemo.aml}。 \item 打包並串接:aml 放進 cpio 的 \code{kernel/firmware/acpi/} 路徑,接在現有 initrd \textbf{前面}(見下方指令)。 \item 開機:GRUB 選單按 \code{e},\code{initrd} 行改指向新檔、 \code{linux} 行尾加上 \code{acpi.debug\_layer=0xFFFFFFFF acpi.debug\_level=0x2},\code{Ctrl-x} 開機(只影響本次)。 \item 驗證:\code{sudo dmesg | grep -E "Table Upgrade|ACPI Debug"}。 \end{enumerate} \end{labbox} \begin{shell}[title={步驟 1--3 的實際指令}]\) iasl dbgdemo.dsl \(mkdir -p kernel/firmware/acpi\) cp dbgdemo.aml kernel/firmware/acpi/ \(find kernel | cpio -H newc --create > ssdt.cpio\) cat ssdt.cpio /boot/initrd.img-\((uname -r) > initrd-dbg.img\) sudo cp initrd-dbg.img /boot/

\(sudo dmesg | grep -E "Table Upgrade|ACPI Debug" [ 0.008112] ACPI: Table Upgrade: install [SSDT-DEMO -DBGDEMO] [ 2.310547] ACPI Debug: String [0x18] "DBG0._INI: SSDT is alive" [ 2.310551] ACPI Debug: String [0x14] "Hello ACPI from DBG0" [ 2.310554] ACPI Debug: Integer 0x000000000000002A [ 2.310558] ACPI Debug: Buffer [0x04] = 0xDE 0xAD 0xBE 0xEF [ 2.310561] ACPI Debug: String [0x18] "legacy style still works" \end{shell} 第一行是核心宣告安裝了我們的 SSDT;接著五行正是 \code{\_INI} 的 五個 Debug 寫入,型別與長度都對得上 23.1 的樣式表。若核心開了 \code{CONFIG\_ACPI\_CONFIGFS},還可免重開機:\code{modprobe acpi\_configfs} 後在 \code{/sys/kernel/config/acpi/table/} 下 \code{mkdir} 一個目錄、把 aml \code{cat} 進其中的 \code{aml} 檔 即可(多數發行版預設沒開,故本 Lab 以 initrd 為主)。 \section{更快的迴圈:acpiexec 直接印} 上真機前,先讓 SSDT 在 acpiexec 裡跑通。acpiexec 不需要任何開關, Debug 輸出直接印在終端機: \begin{shell}[title={秒級開發迴圈}]\) iasl dbgdemo.dsl && acpiexec dbgdemo.aml

- execute _SB.DBG0._INI Executing _SB.DBG0._INI [ACPI Debug] String [0x18] "DBG0._INI: SSDT is alive" [ACPI Debug] String [0x14] "Hello ACPI from DBG0" [ACPI Debug] Integer 0x000000000000002A [ACPI Debug] Buffer [0x04] = 0xDE 0xAD 0xBE 0xEF [ACPI Debug] String [0x18] "legacy style still works"

- execute _SB.DBG0.DBLE 0x15 Executing _SB.DBG0.DBLE [ACPI Debug] Integer 0x0000000000000015 Execution returned object 0x5601a8c2, buffer length 18 [Integer] = 000000000000002A

第二段示範參數傳遞:(21)傳入 ,Debug 印出 它,方法回傳 0x2A(42)。建議的工作節奏:acpiexec 迭代到 邏輯正確,再用 initrd 上真機驗證與硬體互動的部分。

kernel 前綴為 ,acpiexec 為 , 長度欄位與縮排在不同 ACPICA 版本間略有出入。寫解析腳本時抓語意 (型別關鍵字與引號內容),不要寫死整行格式。

進階:Method Tracing 簡介

Debug 物件要你事先在原始碼裡佈點;如果拿到的是別人的韌體, 想看某個 Method 的完整執行軌跡呢?ACPICA 提供 method tracing: 指定方法名,interpreter 在進入該方法時自動打開你指定的 layer/level,離開時關閉——等於「只對這個方法開洪水」。

# cd /sys/module/acpi/parameters # echo "_SB.DBG0.DBLE" > trace_method_name # echo method > trace_state

可為 (每次都追)、 (只追第一次)、(關閉); 搭配 與 指定追蹤期間放行的訊息。同名的開機參數 ( 等)可以從第一次執行就開追。 完整說明見核心文件 。

  • Debug 是只能寫的虛擬物件:寫入即列印,型別(String/ Integer/Buffer/Package)自動標註。

  • 路徑:ASL \(\to\) AML DebugOp(0x5B 0x31)\(\to\) interpreter \(\to\) printk \(\to\) dmesg。

  • 兩道閘門:編譯期 ;執行期 (誰說話)與 (說哪類話)。

  • 標準組合:;執行期可透過 調整。

  • 開發節奏:acpiexec 秒級迭代邏輯,initrd Table Upgrade 上真機驗證。

  • method tracing 能只對單一方法開啟完整追蹤,適合分析 別人的韌體。

  1. 為什麼 Debug 物件被設計成「只能寫」?如果允許讀取, 會對 interpreter 與韌體行為造成什麼問題?

  2. 會印出幾行、 各是什麼樣式?對照 23.1 的樣式表回答。

  3. 想同時看到 Debug 物件輸出與初始化訊息, 應設多少?寫出計算過程。

  4. 動手:修改 ,在 中用 迴圈印出 1 到 5,用 acpiexec 驗證輸出。

  5. 動手(真機):完成 23.4 的 initrd Lab,把你的名字放進 ,截圖 dmesg 的輸出。

ACPI Error Debugging

打開任何一台量產筆電的 dmesg,十之八九能撈到幾行 。 有些是無害噪音,有些正是「休眠喚不醒」「風扇狂轉」「電量顯示卡死」 的元兇。分辨兩者、並把有害的那種一路追到 ASL 原始碼,是韌體工程師 最值錢的技能之一。

本章先解剖錯誤訊息的每個欄位,建立十個常見錯誤碼的直覺, 再用三個擬真 Case Study 完整演練「Kernel Log \(\to\) ACPI Error \(\to\) Method \(\to\) ASL Source \(\to\) Namespace \(\to\) Hardware」這條定位鏈。

  • 拆解一行 ACPI Error:方法路徑、錯誤碼、ACPICA 版本與 原始碼位置。

  • 說出十個常見 錯誤碼的意義、典型成因與修法。

  • 依定位鏈從 kernel log 一路追到 ASL 與硬體。

  • 診斷並修復三類實戰問題:、 與 GPE storm。

  • 使用 fwts 對韌體做自動化健檢。

解剖一行 ACPI Error

先看一組典型的錯誤(實際各為單行,此處為排版換行;下同):

\(sudo dmesg | grep -B1 "Aborting method" [ 312.480211] ACPI BIOS Error (bug): Could not resolve symbol [\_SB.PCI0.LPCB.EC0.XDAT], AE_NOT_FOUND (20230628/psargs-330) [ 312.480230] ACPI Error: Aborting method \_SB.PCI0.LPCB.EC0._Q66 due to previous error (AE_NOT_FOUND) (20230628/psparse-529) \end{shell} 逐段解讀第二行: \begin{center} \small \begin{tabular}{lL{8.4cm}} \toprule \rowcolor{acpiblue!10} \textbf{片段} & \textbf{意義} \\ \midrule \code{ACPI Error:} & 訊息來自 ACPICA(核心的 ACPI 元件) \\ \code{Aborting method ...\_Q66} & 被中止的 Method 的完整 Namespace 路徑——這台機器的 EC query 66 號 \\ \code{(AE\_NOT\_FOUND)} & ACPICA 錯誤碼:找不到引用的名稱 \\ \code{20230628} & ACPICA 版本(釋出日期),回報 bug 必附 \\ \code{psparse-529} & 拋錯位置:ACPICA 原始碼 \code{psparse.c} 第 529 行——拿去搜尋核心原始碼可直達現場 \\ \bottomrule \end{tabular} \end{center} 兩個關鍵閱讀習慣: \begin{itemize} \item \textbf{找第一行}。錯誤常成串出現:第一行(本例的 \code{Could not resolve symbol [...XDAT]})才是根因, 後面的 \code{Aborting method} 是連鎖反應。從最早的那行 開始讀。 \item \textbf{看前綴}。\code{ACPI BIOS Error (bug)} 是 ACPICA 明確判定「錯在韌體交來的表,不在核心」;純 \code{ACPI Error} 則兩種可能都有。看到 BIOS Error, 方向直接轉向 DSDT/SSDT。 \end{itemize} \begin{notebox}[錯誤碼定義在哪裡] 所有 \code{AE\_} 開頭的碼定義於 ACPICA 的 \code{acexcep.h} (核心原始碼 \code{include/acpi/acexcep.h}),\code{AE} 即 ACPI Exception。其中 \code{AE\_AML\_} 子類代表「執行 AML 期間」的錯誤 ——十之八九可以在 ASL 原始碼裡找到對應的病灶。 \end{notebox} \section{常見錯誤碼速查表} \begin{center} \footnotesize \begin{tabular}{L{4.0cm}L{2.6cm}L{4.0cm}L{3.4cm}} \toprule \rowcolor{acpiblue!10} \textbf{錯誤碼} & \textbf{意義} & \textbf{典型成因} & \textbf{修法} \\ \midrule \code{AE\_ALREADY\_EXISTS} & 要建立的名稱已存在 & \code{NotSerialized} 方法內用 \code{Name()} 且被併發重入; 同一張 SSDT 載入兩次 & 方法改 \code{Serialized};檢查重複載表 \\ \code{AE\_NOT\_FOUND} & 找不到引用的名稱 & 韌體版本差異;\code{External} 騙過編譯器但執行期不存在; 路徑拼錯 & \code{CondRefOf} 防禦;用 acpiexec 核對 Namespace \\ \code{AE\_AML\_OPERAND\_TYPE} & 運算元型別不符 & Method 某路徑沒回傳值卻被當值用;把 Package 當 Integer 運算 & 檢查所有 Return 路徑與 \code{ObjectType} \\ \code{AE\_AML\_UNINITIALIZED\_ARG} & 使用了未傳入的 ArgX & 呼叫端參數個數不足(\code{\_DSM} 轉呼叫是慣犯) & 對齊宣告與呼叫的參數個數 \\ \code{AE\_AML\_UNINITIALIZED\_\allowbreak LOCAL} & 使用未初始化的 LocalX & 某條 If 分支忘了先賦值就使用 & 進方法先初始化再分支 \\ \code{AE\_AML\_PACKAGE\_LIMIT} & Package 索引越界 & \code{Index} 超出元素個數;迴圈上限寫錯 & 以 \code{SizeOf} 界定索引範圍 \\ \code{AE\_AML\_BUFFER\_LIMIT} & Buffer 索引越界 & \code{CreateField} 系列的位移或長度算錯 & 核對位移、長度與 Buffer 大小 \\ \code{AE\_AML\_INVALID\_\allowbreak RESOURCE\_TYPE} & 資源描述子型別錯誤 & \code{\_CRS}/\code{\_SRS} 回傳的 Buffer 不是合法的 resource template & 用 ResourceTemplate 重寫並以 iasl 驗證 \\ \code{AE\_TIME} & 等待逾時 & \code{Acquire}/\code{Wait} 逾時;EC 交易沒回應 & 查 Mutex 死鎖順序與 EC 狀態 \\ \code{AE\_AML\_LOOP\_TIMEOUT} & While 迴圈超過時限 & 輪詢的硬體 bit 永遠不變(預設保險絲約 30 秒) & 迴圈加計數上限;查硬體為何不回應 \\ \bottomrule \end{tabular} \end{center} \begin{warnbox}[不是每行 ACPI Error 都要修] 量產機器的 log 常有「無害噪音」:例如 \code{\_DSM} 查詢不支援的 功能、\code{\_OSI} 分支導致某平台本來就沒有的物件被引用一次。 判斷準則是\textbf{有沒有伴隨功能異常}(睡眠、電池、熱管理、 熱鍵……)。先確認症狀,再決定投入;別看到紅字就急著改 DSDT 或刷 BIOS。 \end{warnbox} \section{Case Study 1:AE\_ALREADY\_EXISTS——重入的代價} \textbf{症狀}:使用者回報「連按亮度熱鍵,螢幕亮度偶爾卡住」。 dmesg 出現: \begin{shell}[title={Case 1 的錯誤訊息}] [ 201.118402] ACPI Error: [TBUF] Namespace lookup failure, AE_ALREADY_EXISTS (20230628/dswload2-326) [ 201.118420] ACPI Error: Aborting method \_SB.PCI0.LPCB.EC0._Q11 due to previous error (AE_ALREADY_EXISTS) (20230628/psparse-529) \end{shell} \textbf{定位}:根因行說「建立 \code{TBUF} 時發現名稱已存在」; 被中止的是 EC query \code{\_Q11}(亮度熱鍵的事件 handler, 第 12--14 章)。反組譯 DSDT,找到: \begin{asl}[title={dsdt.dsl 節錄:有 bug 的 \_Q11}] // dsdt.dsl excerpt: brightness hotkey EC query (buggy) Method (_Q11, 0, NotSerialized) // BUG: NotSerialized { Name (TBUF, Package (0x02) { Zero, Zero }) TBUF [Zero] = BRTL // EC field: current level TBUF [One] = BRTM // EC field: max level \_SB.PCI0.GFX0.UPBL (TBUF) // ask GFX to update backlight } \end{asl} \textbf{機轉}:方法內的 \code{Name()} 在\textbf{執行期}建立節點、 方法結束時拆除。使用者連按熱鍵,EC 連續送出兩個 query,第二個 \code{\_Q11} 在第一個還沒退出時進場——第二次 \code{Name (TBUF, ...)} 發現節點還在,拋出 \code{AE\_ALREADY\_EXISTS},方法中止, 亮度就「卡住」了。 \textbf{修正}:把宣告改成 \code{Method (\_Q11, 0, Serialized)} ——一字之差,interpreter 會替方法加上隱式 Mutex,重入的執行緒 排隊等前一個完成,\code{Name()} 不再撞名。這正是第 12 章的鐵律 「方法內有 \code{Name()} 就宣告 \code{Serialized}」的實戰版。 現代 iasl 對 NotSerialized 方法內的 \code{Name()} 會直接給出 remark 警告——編譯器的嘮叨要當真。 \section{Case Study 2:AE\_NOT\_FOUND——防禦性引用} \textbf{症狀}:某批機器升級 EC 韌體後,dmesg 在熱事件時報錯: \begin{shell}[title={Case 2 的錯誤訊息}] [ 88.130077] ACPI BIOS Error (bug): Could not resolve symbol [\_SB.PCI0.LPCB.EC0.SEN3], AE_NOT_FOUND (20230628/psargs-330) [ 88.130095] ACPI Error: Aborting method \_SB.PCI0.LPCB.EC0._QD5 due to previous error (AE_NOT_FOUND) (20230628/psparse-529) \end{shell} \textbf{定位}:\code{\_QD5} 引用了 EC field \code{SEN3},但這台 機器的 DSDT 沒宣告它——這張 SSDT 是兩個機種共用,另一機種的 EC 多一顆感測器。編譯時的 \code{External} 宣告(第 19 章)只是 「我保證執行期會有」的承諾,兌現不了就是 \code{AE\_NOT\_FOUND}: \begin{asl}[title={ssdt 節錄:有 bug 的 \_QD5}] // ssdt excerpt: thermal sensor query (buggy) External (\_SB.PCI0.LPCB.EC0.SEN3, FieldUnitObj) Method (_QD5, 0, Serialized) { Local0 = \_SB.PCI0.LPCB.EC0.SEN3 // may not exist! If (Local0 > 0x5F) { Notify (\_TZ.THM0, 0x80) // re-evaluate temperature } } \end{asl} \textbf{修正}:用 \code{CondRefOf} 先確認物件存在。它回傳真假而 \textbf{不會}因為找不到而拋錯,是跨韌體版本的標準防禦寫法: \begin{asl}[title={修正後的 \_QD5:CondRefOf 防禦}] Method (_QD5, 0, Serialized) { If (CondRefOf (\_SB.PCI0.LPCB.EC0.SEN3, Local0)) { If (DerefOf (Local0) > 0x5F) { Notify (\_TZ.THM0, 0x80) } } Else { Debug = "_QD5: SEN3 absent on this EC firmware" } } \end{asl} \code{CondRefOf} 成功時把\textbf{參照}放進 \code{Local0}, 用 \code{DerefOf} 取值才會真正去讀 EC。Else 分支順手放一行 Debug(第 23 章),日後遇到同型問題 dmesg 直接告訴你原因。 在 acpiexec 裡把兩個版本各跑一次,就能離線驗證修正。 \section{Case Study 3:GPE Storm——用計數器抓兇手} \textbf{症狀}:機器待機也風扇狂轉,\code{top} 顯示 kworker 執行緒 吃掉一顆核心。這是 GPE storm 的經典長相:某路 GPE 不停觸發, 核心疲於執行它的 handler。用第 22 章的計數器辦案: \begin{shell}[title={Step 1:確認 SCI 暴增,鎖定兇手 GPE}]\) cat /sys/firmware/acpi/interrupts/sci; sleep 5;  cat /sys/firmware/acpi/interrupts/sci 418772 601233

\(grep -H . /sys/firmware/acpi/interrupts/gpe?? | sort -t: -k2 -rn \ | head -3 /sys/firmware/acpi/interrupts/gpe17: 600911 enabled unmasked /sys/firmware/acpi/interrupts/gpe09: 3120 enabled unmasked /sys/firmware/acpi/interrupts/gpe02: 411 enabled unmasked \end{shell} 五秒漲了 18 萬次,幾乎全記在 \code{gpe17} 頭上。GPE 編號是十六進位, 對應 handler 就是 \code{\_GPE} scope 下的 \code{\_L17}(level)或 \code{\_E17}(edge)。到 dsl 裡看: \begin{asl}[title={dsdt.dsl 節錄:有 bug 的 \_L17}] // dsdt.dsl excerpt: thermal GPE handler (buggy) Scope (\_GPE) { Method (_L17, 0, NotSerialized) { If (\_SB.PCI0.LPCB.EC0.TSTS) // thermal status bit { Notify (\_TZ.THM0, 0x80) // re-evaluate temperature } // BUG: TSTS is never cleared. The level-triggered // GPE line stays asserted and fires again immediately. } } \end{asl} \textbf{機轉}:\code{\_L17} 是 level-triggered handler:只要事件源 沒被清掉,GPE 線持續有效,處理完馬上又觸發。這個 handler 讀了狀態 bit \code{TSTS} 卻忘了清除(該 bit 規格為 write-1-to-clear), 於是無限循環。 \textbf{修正}:在 handler 內\textbf{先清事件源再做事}——於 \code{If} 區塊開頭、\code{Notify} 之前加上一行 \code{\_SB.PCI0.LPCB.EC0.TSTS = One}(write-1-to-clear), GPE 線放掉,storm 即止。 \textbf{救急 workaround}(改不了韌體時):執行期把 \code{disable} 寫進 \code{interrupts/\allowbreak gpe17}(\code{echo disable >} 該檔案),或用開機參數 \code{acpi\_mask\_gpe=0x17} 永久遮罩—— 代價是犧牲該 GPE 承載的功能(本例為熱事件通知),只能當止血帶。 \begin{labbox}[體檢你的機器有沒有隱性 storm] \begin{enumerate} \item 記下 \code{gpe\_all} 的值,等 60 秒再記一次,相減得出 每分鐘 GPE 次數(安靜的機器通常是個位數到數十)。 \item 若異常,用本節的 \code{grep + sort} 找出最活躍的 GPE。 \item 在你反組譯的 \code{dsdt.dsl} 中找到對應的 \code{\_Lxx}/ \code{\_Exx},讀懂它服務的裝置,判斷觸發頻率是否合理。 \end{enumerate} \end{labbox} \section{通用定位流程與檢查清單} 三個 Case 走的其實是同一條鏈——課綱那條你必須內化的定位流程: \begin{vflow}[4.2mm] \fnode{Kernel Log:dmesg 擷取完整錯誤串} \fnodel{找第一行根因}{ACPI Error:錯誤碼 + Method 路徑} \fnodel{acpidump 工具鏈(第 22 章)}{Method:反組譯出 dsdt.dsl} \fnode{ASL Source:閱讀方法邏輯與引用} \fnodel{acpiexec/sysfs 驗證假設}{Namespace:物件存在?型別對?} \fnode[flownodealt]{Hardware:EC/暫存器行為對不對} \end{vflow} \begin{keybox}[ACPI 錯誤除錯 checklist] \begin{enumerate} \item 收證據:完整 dmesg(含錯誤前後各十行)、BIOS 版本、 ACPICA 版本(錯誤行裡那個日期)。 \item 看前綴:\code{ACPI BIOS Error (bug)} 直接朝韌體方向查。 \item 找第一行根因,記下 Method 路徑與 \code{AE\_} 碼, 對照 24.2 速查表。 \item 反組譯 DSDT 與所有 SSDT(記得 \code{iasl -e}), 讀該 Method 與它引用的每個名稱。 \item 用 \code{/sys/bus/acpi/devices} 與 acpiexec 驗證 Namespace 假設;GPE 問題加看 \code{interrupts/} 計數。 \item 修 ASL(或先上 workaround),用 initrd Table Upgrade 驗證,確認 log 乾淨、功能恢復。 \end{enumerate} \end{keybox} \section{fwts:自動化健檢} 手動找完個案,出貨前還需要地毯式掃描。Canonical 維護的 \keyterm{fwts}(FirmWare Test Suite)內建數百項韌體檢查, 其中 ACPI 相關測項會靜態驗證每張表的欄位、並實際執行 Method 檢查回傳型別是否符合規格: \begin{shell}[title={用 fwts 掃一遍 ACPI}]\) sudo apt install fwts \(sudo fwts acpi klog method\) grep -A3 "Test Failure Summary" results.log Test Failure Summary ==================== Critical failures: NONE High failures: 1 method: BAT0._BIF returned package of 12 elements, expected 13

測項驗表、 掃 kernel log、 逐一執行 Method 驗回傳型別;結果依嚴重度寫進 。 bring-up 階段每改一版韌體跑一輪 fwts,能在使用者看到之前攔下 大多數本章等級的問題。

  • 讀錯誤先找第一行根因; 前綴 代表 ACPICA 判定錯在韌體。錯誤行給四樣線索:Method 路徑、 碼、ACPICA 版本、原始碼位置。

  • :方法內 被併發 重入——用 修。

  • :引用不存在的名稱(韌體版本差異) ——用 防禦。

  • GPE storm 用 計數器抓兇手,根修是在 內清事件源; 只是止血帶。

  • 定位鏈:Kernel Log \(\to\) ACPI Error \(\to\) Method \(\to\) ASL Source \(\to\) Namespace \(\to\) Hardware;出貨前用 fwts 地毯式掃描。

  1. 逐段解讀: 。 你會先檢查 ASL 裡的什麼?

  2. 為什麼能修 ? 它的代價是什麼?在什麼情況下你反而要小心它造成 ?

  3. 與 都能處理「別的表定義的 物件」,兩者的職責差在哪?為什麼只用前者不安全?

  4. 動手:寫一張引用不存在物件的 SSDT,在 acpiexec 中執行, 觀察 的完整輸出;再加上 驗證錯誤消失。

  5. 動手:在你的機器上做 24.5 的 GPE 體檢 Lab,找出最活躍的 GPE,並在 dsdt.dsl 中讀懂它的 handler 在服務誰。