第 1 章說過,ACPI 存在的一大理由是替「不可列舉」的硬體向 OS 自我介紹,而自我介紹的載體,就是本章的主角—— 物件:一個 Device 節點搭配一組名稱固定的標準子物件,就構成 OS 眼中「一個完整的裝置」。本章依 OS 列舉的順序講解它們:身分 (、、、)、 狀態()、資源(、、 )、屬性與初始化(、)。

  • 寫出含 、、 的最小 Device,並畫出 OS 列舉、比對 ID、綁 Driver 的流程圖。

  • 說明 的 EisaId 與字串兩種格式及命名規則。

  • 區分 與 的分工,寫出 PCI 裝置 的 編碼。

  • 逐位元解讀 ,診斷 0x0F、0x0B、0x00 的差異。

  • 用 ResourceTemplate 寫出 ,並說明 / 動態資源三件套。

  • 說明 裝置屬性與 的執行時機。

Device 物件:向 OS 註冊一個裝置節點

從 OS 的角度看, 關鍵字做的事只有一件:在 ACPI Namespace 掛出一個「裝置節點」,等著被列舉。節點本身沒有任何魔法, 真正決定這個裝置「是誰、能不能用、用什麼資源」的,是掛在它底下、 名稱以底線開頭的標準子物件。先看課綱指定的最小範例:

Device (DEV0) Name (_HID, "ACME0001") // who am I: vendor + product Name (_UID, 0) // instance number 0 Method (_STA, 0) // am I present and working? Return (0x0F) // present + enabled + OK

逐行拆解:

  • :在目前 Scope 底下建立裝置節點, 命名規則同一般物件(至多 4 碼、字母或底線開頭)。

  • :硬體識別碼——裝置的 身分證字號,OS 拿它比對 driver 資料庫。

  • :實例編號,用來區分同 的多顆裝置。

  • :回答「我在嗎」; 表示存在、已啟用、顯示於 UI、運作正常。

OS 開機列舉裝置的整條流水線如下——這張圖是本章的骨架:

寫 ASL 描述裝置,本質上是回答 OS 的三個問題:你是誰 ()、你好嗎()、 你用什麼()——接下來各節依序拆解每一環。

_HID:裝置的主要身分證

(Hardware ID)是 OSPM 列舉裝置時的首要依據: OS 拿它去比對 driver 資料庫(Windows 的 INF、Linux 的 表),決定這個節點交給誰驅動。 它有兩種合法格式:

Name (_HID, EisaId ("PNP0C0D")) // EISA form: 3 letters + 4 hex Name (_HID, "ACME0001") // ACPI form: 4 chars + 4 hex

兩種格式的規則:PNP ID 是「3 個大寫字母的 vendor 代碼 +4 個十六進位數字」,可包在 裡,也可直接寫成 7 碼字串(如 );ACPI ID 是「4 碼 vendor(大寫字母或數字)+4 碼十六進位 product」,一律寫成 8 碼字串。 是 PnP 時代的遺產:把 7 碼 ID 壓成 32-bit 整數放進 AML( 即整數 )。課綱範例 就是 「向 UEFI Forum 註冊的 vendor ACME、其 0001 號產品」。

什麼時候必須有 ?一句話:凡是要 OSPM 自己 列舉的裝置(platform device),一定要有 ;掛在可 自我列舉匯流排上的裝置改用 (見稍後一節)。沒有身分 的 Device 也合法,但 OS 只把它當容器,不會綁 driver。

PNP 與 ACPI 的 vendor 代碼由 UEFI Forum 統一發放(uefi.org 可查 清單): 開頭保留給歷史裝置類別、 開頭保留 給規格定義的裝置。出貨韌體請用公司註冊的代碼;本書範例一律用 虛構的 。

實務上有一批「熟面孔」ID 值得先混個臉熟: (上蓋開關)、(電源鍵)、 (Embedded Controller)、(PS/2 鍵盤)、 (PCIe root bridge)、 (AC adapter)——本書後續章節會反覆遇到它們。

_CID 與 _UID:相容身分與實例編號

(Compatible ID)回答的問題是:「如果找不到我的專屬 driver,我還相容於誰?」OS 的比對順序是先拿 找專屬 driver,找不到才依序嘗試 清單。最經典的例子是鍵盤:

Device (KBD0)

Name (_HID, "ACME0010") // vendor-specific ID Name (_CID, EisaId ("PNP0303")) // generic AT keyboard

// _CID may also be a priority-ordered list: // Name (_CID, Package (2) "PNP0303", "PNP030B" )

廠商若有客製 driver(例如支援特殊快捷鍵),OS 用 比中它;沒有就退而求其次,用 綁內建的標準鍵盤 driver,鍵盤至少能打字。如註解所示, 可以是單一 ID, 也可以是候選清單(Package),順序由「最精確」排到「最通用」。 現代版的同一招是 I2C 觸控板:vendor 專屬 加 (HID over I2C),通用 HID driver 永遠 接得住。(Unique ID)則解決「同型號、多實例」: 多個 Device 共用同一個 時,OS 靠它分辨誰是誰。

Device (UAR1) Name (_HID, EisaId ("PNP0501")) Name (_UID, 1) Device (UAR2) Name (_HID, EisaId ("PNP0501")) Name (_UID, 2)

可以是整數或字串,規則有二:同 之間 必須唯一、跨開機必須穩定——OS 拿它記住每個實例的設定, 編號亂跳,設定就漂移到別顆裝置上。

_ADR:可列舉匯流排上的位址

PCI、USB 這類匯流排有自我列舉能力:裝置插上去,匯流排協定就能 發現它、讀出它的 ID。這種裝置不需要 —— OS 已經知道硬體存在,ACPI 節點的任務只剩「對號入座」:用 宣告「我描述的是匯流排上的哪個位址」,好讓 OS 把這個節點的 ACPI method(電源控制、喚醒能力等)附加到正確的 硬體裝置上。

Scope (_SB.PCI0) Device (GFX0) Name (_ADR, 0x00020000) // device 0x02, function 0

PCI 的 編碼是固定公式:——device 0x1F 的 function 3 就編成 ; 是萬用字元(該 device 的所有 function)。其他匯流排各有規則:USB 用 root hub port 編號、 SATA 用 port 加 port multiplier 的組合、SD 用 slot 編號。

分工原則一句話:OS 自己找得到的裝置用 對號,找不到的用 自我介紹。規格要求每個 Device 至少具備其中之一;兩者都放是舊時代遺風,新設計應避免。

_STA:裝置的健康申報

(Status)是 OS 列舉時的第一道關卡:先問「你在嗎」, 再決定要不要繼續讀 、載 driver。之後裝置狀態改變 (例如韌體發出 )時,OS 也會重新評估它。 回傳值是一個位元遮罩:

clL7.0cm 位元 & 名稱 & 意義
bit 0 & Present & 裝置實體存在
bit 1 & Enabled & 已啟用且正在 decode 資源
bit 2 & Show in UI & 應顯示在使用者介面中
bit 3 & Functioning & 運作正常(清 0 表示故障)
bit 4 & Battery Present & 電池槽裝置專用:電池在位

位元不能亂組:bit 0 清零(不存在)時 bit 1 必須跟著清零—— 不存在的裝置不可能在 decode 資源。三個必背的常見值: 0x0F(bit 0–3 全設)是正常工作的裝置,OS 列舉、載 driver、顯示於 UI;0x0B(缺 bit 2)是「正常運作但別 出現在 UI」,給使用者不該看到的內部裝置;0x00 是 「不存在」,OS 直接跳過、不載 driver。電池槽裝置再加 bit 4, 正常值成為 0x1F。

沒有 的裝置,OS 一律視同回傳 。所以 「永遠存在」的裝置可以不寫;要寫就寫對(殺傷力見警告框)。 它真正的價值在動態場景——同一份 DSDT 服務多個硬體 版本時拿它當開關: 裡讀一個對應硬體跳線 的 Field(第 10 章的主角,例如 ),高階 SKU 回 、其餘回 ——同一顆 AML 燒進三個機種, 高階款看得到感測器,低階款彷彿它不存在。

  • 回傳 0x00:OS 當裝置不存在——Windows 裝置管理員 看不到、Linux 連 platform device 都不建。常見事故: 讀 EC 欄位,但 EC region 尚未就緒(第 9 章 ),讀到 0 就回 Zero,觸控板從此消失。

  • 漏掉 bit 3(Functioning):裝置看得到卻標成 故障,Windows 顯示黃色驚嘆號、driver 不啟動;Linux 卻 可能照常 probe——兩邊行為不一致,更難追。

  • 非法組合:bit 0 清零卻設 bit 1,行為未定義, 別賭任何 OS 的容錯。

排錯守則:懷疑裝置消失,第一步就是確認 的回傳值。

最後一個規格認可的特例:回傳 0x08(只設 bit 3)表示「本體 不存在但功能正常」——OS 不執行 、不載 driver, 但仍會繼續列舉子節點;想讓橋接器隱形、卻保留底下 子樹時用得到。

_CRS、_PRS 與 _SRS:資源申報三件套

(Current Resource Settings)回傳一個 Buffer,內容是 一串二進位的 resource descriptor,描述裝置目前使用的 MMIO、 IO port、中斷、DMA 等資源。手刻二進位太苦,ASL 提供 巨集,在編譯期把可讀的巨集展開成 descriptor bytes。先看常用巨集的總覽:

Name (_CRS, ResourceTemplate () Memory32Fixed (ReadWrite, 0xFED90000, 0x1000) // 4 KB MMIO IO (Decode16, 0x0300, 0x0300, 0x01, 0x10) // 16 ports Interrupt (ResourceConsumer, Level, ActiveLow, Exclusive) 24 // GSI 24 IRQNoFlags () 5 // ISA IRQ 5 DMA (Compatibility, NotBusMaster, Transfer8) 1 )

逐一拆解:

  • :位址固定的 32-bit MMIO;可重定位或 64-bit 改用 /。

  • :legacy IO port;固定位址寫 min = max。

  • :編號是 (Global System Interrupt),可超過 15; 喚醒源用 (Module 7)。

  • /: legacy PIC 中斷(0–15),只剩 SuperIO、PS/2 在用。

  • :ISA DMA 通道 (0–7);現代裝置自帶 bus-master DMA,用不到它。

資源固定的裝置給 就夠了。資源可調的裝置 (歷史上是 ISA PnP 卡,今日剩 SuperIO 一族)要湊齊動態三件套:

  • (Possible):列出可接受的資源組合——在 ResourceTemplate 裡用 把 「IO 0x3F8 + IRQ 4」「IO 0x2F8 + IRQ 3」一組組包起來。

  • (Set):OS 挑一組包成 buffer 當 Arg0 呼叫它;method 用 挖出選中的 位址與中斷,寫進 SuperIO 組態暫存器。

  • (Current):設定生效後回報新值——三件套 形成「查詢、改定、確認」的循環。

配套的 讓 OS 重新配置前先叫裝置停止 decode。現代 平台多數裝置資源固定,你會寫到的幾乎只有 ;但讀懂 三件套,看到老平台的 DSDT 才不會迷路。

_DSD 與 _INI:裝置屬性與初始化

能描述的東西有限:它說得出「哪段 MMIO、哪個 IRQ」, 說不出「這顆感測器的取樣間隔是 250 ms」。(Device Specific Data)補上這塊:以 UUID 標記格式、用 key-value 形式 傳遞任意裝置屬性:

Name (_DSD, Package () ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () Package (2) "compatible", "acme,wdt-100" , Package (2) "timeout-sec", 30 )

開頭的 UUID 是「Device Properties」格式的固定識別碼,後面每項 是一組 。屬性命名慣例沿用 Linux Device Tree 的 binding;driver 端用 這類 API 直接讀取——第 25 章會寫出完整的 driver 對接。注意 ACPI 不驗證 key 的拼字與型別,韌體與 driver 得自己講好。

則是裝置的「開機初始化鉤子」,執行時機明確: OSPM 建立 Namespace、列舉裝置時執行,早於 driver 載入; 執行條件是 回報 present(或裝置沒有 ; 回傳 0x08 的特例不執行,但子樹照走);順序由上而下,父節點先於 子節點。典型用途是一次性的硬體預設值,例如 在 driver 接手前先 停掉 watchdog( 是對應控制暫存器的 Field)。切記 只跑一次,反覆執行的邏輯屬於 或 driver。

綜合範例:一個完整的平台裝置

把本章所有元件組裝起來:一顆虛構的 ACME watchdog,MMIO 在 0xFED90000、中斷走 GSI 24。這是一份完整可編譯的 SSDT:

DefinitionBlock ("wdt0.aml", "SSDT", 2, "ACME", "WDTTABLE", 1) Scope (_SB) Device (WDT0) Name (_HID, "ACME0001") // ACME watchdog Name (_UID, 0) // instance 0 Method (_STA, 0, NotSerialized) Return (0x0F) // present and working Name (_CRS, ResourceTemplate () Memory32Fixed (ReadWrite, 0xFED90000, 0x1000) // 4 KB registers Interrupt (ResourceConsumer, Level, ActiveLow, Exclusive) 24 // GSI 24 )

存成 、以 編譯即得 (0 Errors、0 Warnings)。載入後,它在 Namespace 中的位置如下:

 └── _SB_ ├── PCI0 ... ← 平台原有的其他裝置 └── WDT0 ← 本例宣告的裝置 ├── _HID = "ACME0001" ← 身分:driver 比對用 ├── _UID = 0 ← 第 0 號實例 ├── _STA → 0x0F ← 健康申報:一切正常 └── _CRS → Buffer ← MMIO 4 KB + GSI 24

逐行拆解:

  • :平台裝置一律掛在 (System Bus)底下,這是 OS 列舉的起點; SSDT 形式(Module 2)方便獨立編譯與載入。

  • :Linux driver 會在 表裡放同一字串認領裝置(第 25 章實作); 讓日後加第二顆時不動既有描述。

  • 固定回 :裝置焊死在板上、永遠 存在;省略 效果相同。

  • :4 KB MMIO 加 level-triggered、active-low 的 GSI 24;driver probe 時據此映射位址、註冊中斷。

到這裡,OS 看得到裝置、找得到 driver、拿得到資源;唯一還做不到 的是讓 AML 自己讀寫 0xFED90000 的暫存器——那正是下一個 模組 OperationRegion 與 Field 的主場。

  • Device 節點加標準子物件,回答 OS 三問:你是誰 ()、你好嗎()、你用什麼 ()。

  • 兩種格式:EisaId 的 3+4 碼、字串的 4+4 碼; 是相容身分備援;可列舉匯流排改用 (PCI:)。

  • 位元申報狀態:0x0F 正常、0x0B 隱藏、 0x00 不存在;沒寫視同 0x0F。

  • 申報現用資源,ResourceTemplate 巨集生成 descriptor;可調資源加 /。

  • 傳遞 key-value 屬性給 driver; 在列舉期做一次性初始化。

  1. 、、 分別回答 OS 的 什麼問題?缺了其中一個各會發生什麼事?

  2. 寫出一個鍵盤裝置:vendor 專屬 ID 、 相容於 ,並說明 OS 的比對順序。

  3. 回傳 0x0B,裝置管理員會看到什麼?0x08 呢? 0x07 是合法值嗎?為什麼?

  4. 感測器暫存器在 0xFED92000(4 KB)、中斷走 GSI 25 (level、active-low):寫出它的 。

  5. 動手做:把綜合範例改成雙實例(WDT0/WDT1, 0/1,MMIO 錯開 4 KB),以 編譯後用 反組譯,觀察 的 bytes。