前面 24 章,你一直站在「韌體端」寫 ASL、除錯 ASL。本章換到桌子的 另一側:OS 工程師拿到你的 DSDT,是怎麼把 變成 、把 變成 的參數、 把 Method 變成一個 C 函式呼叫的?這是全書最長的一章,也是把你從 「會寫 ASL 的人」升級成「能做系統整合的韌體工程師」的一章—— 第 26 章 Project 1 的 裝置正是本章的主角。

  • 用 C 正確操作硬體:、、 位元遮罩、MMIO 與 port I/O,並對照同一件事的 ASL 寫法。

  • 說明 ACPI 列舉如何把 變成 ,並寫出綁定 的 driver:取 資源、呼叫 Method、匯出 sysfs。

  • 寫出 GPIO/I2C/SPI/PCI 的最小 ASL 描述與 C 端取用方式, 並用 在韌體與驅動之間傳遞 key-value 參數。

  • 描述 EDK II 中 ACPI 表從 到開機安裝的完整流程, 以及 FADT/MADT/MCFG 在 bring-up 時要填什麼。

全景圖:兩條路在核心會合

先把課綱的中心圖攤開。ACPI 這門技術有兩條產線:開發端把 人寫的 ASL 編譯成 AML、包進 DSDT/SSDT 燒入韌體;執行端 由 OS 內建的 interpreter 把 AML 解譯成 Namespace。兩條路在 Linux kernel 會合,往下才是 driver 與真正的硬體:

ACPI 規格(韌體與 OS 之間的合約) ├── 開發端:ASL 原始碼(人寫的,第 3 章起) │ ↓ iASL Compiler(第 21 章) │ AML 位元組碼,打包成 DSDT / SSDT │ ↓ 整合進韌體(BIOS / UEFI / EDK II,25.7 節) └── 執行端:ACPI Interpreter(ACPICA,OS 內建) ↓ 開機解譯 AML、建立 Namespace Linux Kernel:ACPI subsystem 列舉裝置(25.3 節) ↓ _HID 比對、_CRS 轉資源 ACPI Driver(platform / i2c / spi / pci,25.4-25.6 節) ↓ readl / writel、inb / outb、evaluate Method Hardware(MMIO 暫存器、I/O Port、EC、GPIO)

本章路線:25.2 先練 C 的暫存器基本功(圖的最底層);25.3 看核心 怎麼列舉;25.4 寫出完整的 platform driver;25.5 與 25.6 補齊各 子系統與參數傳遞的膠水;25.7 回到韌體端看 EDK II 生產線;25.8 用一顆 LED 把全鏈路串一遍。

韌體工程師的 C 語言功力

為什麼講 ASL 的書要教 C?因為 ACPI 的兩端都是 C:韌體端的 EDK II 與 OS 端的 kernel/ACPICA 都是 C 專案,ASL 只是中間那層 描述語言。同一件事——讀一個暫存器的某幾個 bit——你必須會用兩種 語言寫,並且知道它們最後落到同一次匯流排存取。

pointer 與 volatile:跟編譯器搶控制權

C 的指標讓你把「位址」當一般變數操作,這是直接戳硬體的基礎。 但編譯器不知道某個位址背後是硬體:它看到迴圈裡重複讀同一個位址, 會「好心」只讀一次、把值快取在 CPU 暫存器——對一般記憶體是最佳化, 對硬體暫存器是災難。 就是告訴編譯器: 每次讀寫都必須真的執行,不准合併、不准刪除。

/* Poll a ready bit at a fixed MMIO address (bare metal). */ #define STAT_REG ((volatile unsigned int *)0xFEDF0004UL) #define STAT_RDY (1u << 0)

static int wait_ready(void)

int timeout = 1000;

while ((*STAT_REG & STAT_RDY) == 0) if (–timeout == 0) return -1; /* never spin forever */ return 0;

拿掉 ,開最佳化()後這個迴圈會變成 死迴圈: 只被讀一次。另外注意 ——這正是第 24 章 的教訓在 C 端的 版本:輪詢硬體永遠要有停損。

同一件事在 ASL 怎麼寫?宣告 OperationRegion 與 Field,在 迴圈裡讀那個 Field(見 25.2.3 與第 9–11 章)。 關鍵差異是 ASL 不需要 volatile:Field 存取每次都由 interpreter 真的執行一次匯流排存取,天生 volatile。還要記得 只約束編譯器,不提供跨 CPU 的同步保證; kernel 裡也幾乎不直接解參考指標讀硬體,而是用 25.2.4 的 /——它們把 volatile 語意、必要的 記憶體排序與可攜性一次包好。

struct 對齊與 __packed:描述線上格式

ACPI Table 是「線上格式」(wire format):每個欄位的 byte 位置由 規格釘死。用 C struct 描述它時要小心對齊:編譯器為了讓 落在 4 的倍數位址,會在成員之間塞看不見的 padding, 一塞整個表就對不上了。 屬性禁止 padding:

#include <linux/types.h>

struct acpi_table_header char signature[4]; /* e.g. "DSDT" */ u32 length; /* whole table, in bytes */ u8 revision; u8 checksum; /* all bytes must sum to 0 */ char oem_id[6]; char oem_table_id[8]; u32 oem_revision; char asl_compiler_id[4]; /* "INTL" for iASL */ u32 asl_compiler_revision; __packed; /* exactly 36 bytes, no padding */

這個 36 byte 的表頭你已經在第 22 章用 親眼看過。 實務備註:這個 layout 恰好天然對齊(欄位排列本來就沒有洞), kernel 內建定義因此沒加 ,但描述韌體資料結構時 防禦性地加上是業界慣例;checksum 規則是「全表 byte 總和 mod 256 為 0」——第 21 章 iasl 幫你算,自己拼表(25.7 節)就得自己算。 對照 ASL: 的參數填的正是這些欄位, length 與 checksum 由 iasl 代勞。

位元操作:mask 與 shift 的肌肉記憶

硬體暫存器慣用「一個 32 bit 暫存器塞好幾個欄位」。C 端的標準動作 是 read-modify-write(RMW):讀出、清掉目標欄位、填新值、寫回。 kernel 提供 、、 讓意圖一目瞭然:

/* from <linux/bits.h>, <linux/bitfield.h>, <linux/io.h> */ #define LED_CTRL 0x00 /* register offset */ #define LED_CTRL_EN BIT(0) /* bit 0: enable */ #define LED_CTRL_BRT GENMASK(7, 4) /* bits 7..4: level */

static void led_set(void __iomem *base, unsigned int level)

u32 v;

v = readl(base + LED_CTRL); /* read */ v &=  LED_CTRL_BRT; /* clear field */ v |= FIELD_PREP(LED_CTRL_BRT, level); /* set field */ v |= LED_CTRL_EN; writel(v, base + LED_CTRL); /* write back */

同一顆暫存器在 ASL 裡怎麼描述?Field 直接把 bit 欄位「切」出名字, RMW 由 interpreter 代勞( 正是「寫欄位時保留其他 bit」的意思,第 10 章):

OperationRegion (LEDR, SystemMemory, 0xFEDF0000, 0x10) Field (LEDR, DWordAcc, NoLock, Preserve) LEDE, 1, // bit 0: enable , 3, // bits 1..3: reserved (skipped) LEDB, 4 // bits 7..4: brightness

Method (LSET, 1, Serialized) LEDB = Arg0 // interpreter does the RMW for you LEDE = One

兩相對照就看出分工:ASL 把「欄位在哪」宣告成資料,C 把「欄位在哪」 寫成程式。韌體工程師的日常,就是拿著同一份暫存器規格表, 在兩邊寫出彼此一致的描述——欄位位置抄錯一位,兩邊就開始互相說謊。

MMIO 與 Port I/O:兩種戳硬體的匯流排

x86 平台有兩套定址空間。MMIO(memory-mapped I/O)把裝置 暫存器映進實體位址空間,kernel 中先 取得虛擬位址, 再用 / 存取;port I/O 是獨立的 64 KB I/O 空間,用 / 指令存取:

void __iomem *base;

/* map the MMIO range that came from _CRS (see 25.4) */ base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base);

writel(LED_CTRL_EN, base + LED_CTRL); /* enable the LED */

回傳的是帶 註記的 cookie, 不可直接解參考(靜態檢查工具 sparse 會抓),一律透過 / 系列;port I/O 則是 這樣的一行(0x80 是經典的 POST code 埠)。 ASL 端用 OperationRegion 的空間型別區分這兩種匯流排: 對應 MMIO(上一小節的 LEDR), 對應 port I/O——宣告 加一個 8 bit Field 後,對該 Field 賦值就等於 。

四塊拼圖到此湊齊:指標與 volatile、struct 與對齊、bit 操作、 MMIO/port I/O。接下來看核心怎麼把 ASL 描述的裝置送到你的 C 程式手上。

Linux ACPI Subsystem:從 Namespace 到 device model

Linux 的 ACPI 程式碼分兩層,位置要背起來:

  • ——: interpreter、Namespace、事件處理。這份程式碼與 iasl、 acpiexec 同一個上游專案,OS 無關。

  • ——Linux 膠水層: (列舉)、、……把 ACPI 物件接上 Linux 自己的裝置模型。

  • ——驅動工程師的 API 入口。

開機時,膠水層走訪 Namespace,替每個 建立一個 ——第 22 章在 看到的每個目錄就是它。這個結構 打包了韌體節點的一切:成員 存 / 等身分、 記錄 D-state、 是 25.6 節屬性 API 的掛載點,最後內嵌一個 讓它加入統一裝置模型。但 只是「韌體節點」 的化身,多數 driver 並不直接綁它。對於有 資源、 又不屬於特定匯流排的裝置,膠水層會再生一個 ——Linux 用來承載「不可列舉裝置」的 通用容器:

兩個世界在 sysfs 互相指認——ACPI 裝置目錄下的 symlink 指向 platform 裝置目錄,後者的 又指回來,用 即可驗證。 兩個例外先記著,25.5 節會展開: 裡若是 I2C/SPI 的 serial bus 描述子,裝置改由對應的 controller driver 建立 /;PCI 裝置本來就自我列舉, ACPI 節點只是「伴侶」(companion),driver 可用 取回 呼叫 Method。

實作:綁定 DEMO0001 的 platform driver

理論夠了,動手寫。目標:一個完整可編譯的 kernel module,綁定 為 的裝置:probe 時讀出 的 MMIO 資源、呼叫 ASL 的 、把結果匯出成 sysfs attribute。它正是第 26 章 Project 1 的主角——先看 ASL 端:

DefinitionBlock ("demo.aml", "SSDT", 2, "DEMO", "DEMODEV", 0x00000001)

Scope (_SB)

Device (DEM0)

Name (_HID, "DEMO0001") // matched by the driver Name (_CRS, ResourceTemplate () Memory32Fixed (ReadWrite, 0xFEDF0000, 0x00000100) )

Method (TEST, 1, Serialized) Debug = "DEM0.TEST called from the driver" Return (Arg0 * 2)

是教學用的假位址:本章 driver 只讀出 資源不映射它,在真機上載入無害。若要進一步 實際 讀寫,務必先查 確認該區段沒有別人——對著 別家裝置的暫存器亂寫,輕則當機重則損壞硬體。安全的完整實驗環境 (QEMU 加自訂硬體模型)是第 26 章 Project 1 的內容。

沒寫 時核心視同「存在且啟用」(第 8 章)。driver 全文約 75 行,分兩段列出,第一段是私有資料與 sysfs attribute:

// demo_acpi.c - ACPI platform driver for the DEMO0001 device #include <linux/module.h> #include <linux/platform_device.h> #include <linux/acpi.h>

struct demo_priv u64 test_result; ;

/* cat /sys/devices/platform/DEMO0001:00/test_result */ static ssize_t test_result_show(struct device *dev, struct device_attribute *attr, char *buf)

struct demo_priv *priv = dev_get_drvdata(dev);

return sysfs_emit(buf, "

static DEVICE_ATTR_RO(test_result);

第二段是主體:probe 取資源、呼叫 ASL 的 、掛上 attribute,加上比對表與模組樣板:

static int demo_probe(struct platform_device *pdev)

struct device *dev = &pdev->dev; struct acpi_object_list args; union acpi_object arg; unsigned long long value; struct demo_priv *priv; struct resource *res; acpi_status status;

priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; dev_set_drvdata(dev, priv);

/* the resource was parsed from _CRS by the ACPI core */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return dev_err_probe(dev, -ENODEV, "no MMIO in _CRS"); dev_info(dev, "MMIO from _CRS:

/* evaluate the ASL method TEST(21) under this device */ arg.type = ACPI_TYPE_INTEGER; arg.integer.value = 21; args.count = 1; args.pointer = &arg; status = acpi_evaluate_integer(ACPI_HANDLE(dev), "TEST", &args, &value); if (ACPI_FAILURE(status)) return dev_err_probe(dev, -EIO, "TEST failed"); priv->test_result = value; dev_info(dev, "TEST(21) =

return device_create_file(dev, &dev_attr_test_result);

static void demo_remove(struct platform_device *pdev) device_remove_file(&pdev->dev, &dev_attr_test_result);

static const struct acpi_device_id demo_acpi_ids[] = "DEMO0001", 0 , ; MODULE_DEVICE_TABLE(acpi, demo_acpi_ids);

static struct platform_driver demo_driver = .probe = demo_probe, .remove = demo_remove, .driver = .name = "demo_acpi", .acpi_match_table = demo_acpi_ids, , ; module_platform_driver(demo_driver);

MODULE_DESCRIPTION("Demo ACPI platform driver (DEMO0001)"); MODULE_LICENSE("GPL");

逐段拆解: 拿到的 是 ACPI core 解析 的成果; 取回裝置的 Namespace 節點, 以它為起點用相對路徑 執行方法,參數包成 送進 ——kernel 電池驅動呼叫 (第 22 章)用的 是同一套機制。綁定則靠三件套: 表、 ,以及把表匯出成 modalias 別名 ()讓 udev 自動 的 。Makefile 與操作步驟:

obj-m += demo_acpi.o KDIR ?= /lib/modules/\((shell uname -r)/build all:\)(MAKE) -C \((KDIR) M=\)(PWD) modules clean: \((MAKE) -C\)(KDIR) M=\((PWD) clean \end{shell} \begin{labbox}[編譯、載入、驗證] \begin{enumerate} \item \code{iasl demo.dsl} 產生 \code{demo.aml},依第 23 章的 initrd Table Upgrade 載入,重開機後確認 \code{/sys/bus/acpi/devices/DEMO0001:00} 存在。 \item 安裝 kernel headers 後執行 \code{make},再 \code{sudo insmod demo\_acpi.ko}——driver 立即與裝置 配對並執行 probe。 \item \code{sudo dmesg -t | tail} 檢查 \code{\_CRS} 資源與 \code{TEST(21) = 42};\code{cat} 讀 sysfs 的 \code{test\_result} 應輸出 42。 \item 收尾:\code{sudo rmmod demo\_acpi}。 \end{enumerate} \end{labbox} \begin{shell}[title={驗證輸出}]\) sudo insmod demo_acpi.ko \(sudo dmesg -t | tail -3 demo_acpi DEMO0001:00: MMIO from _CRS: [mem 0xfedf0000-0xfedf00ff] ACPI Debug: String [0x20] "DEM0.TEST called from the driver" demo_acpi DEMO0001:00: TEST(21) = 42\) cat /sys/devices/platform/DEMO0001:00/test_result 42

整條鏈在這一刻閉合: 變成 dmesg 那行 MMIO 資源範圍;C 端傳入的 21 走進 乘以 2 回來;夾在中間的 是 ASL 方法執行的現場證明(需要第 23 章的兩道 閘門打開才看得到)。

本章以 kernel 6.x 為準:platform driver 的 自 6.11 起回傳 ;I2C 的 自 6.3 起只帶一個 參數。移植程式碼時,以你手上核心樹的標頭為最終依據。

各子系統的 ACPI 膠水

DEMO0001 走的是 platform bus;真實平台上更多裝置掛在 GPIO、I2C、 SPI、PCI 上。ACPI 對每種匯流排都定義了資源描述子,kernel 的對應 子系統負責翻譯: 配上 命名,C 端就能用 要腳位;/ 會自動生出 / ;PCI 裝置則以 與 配對。以下逐一給最小對照。

GPIO:GpioIo 加 _DSD 命名

ASL 端在 放 (或 )描述 腳位,再用 給每組 GPIO 一個名字——C 端就是 用這個名字來要腳位的:

Device (SNS0) Name (_HID, "DEMO0003") Name (_CRS, ResourceTemplate () GpioIo (Exclusive, PullNone, 0, 0, IoRestrictionOutputOnly, "
_SB.GPI0", 0, ResourceConsumer) 42
) Name (_DSD, Package () ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () Package () "reset-gpios", Package () ^SNS0, Zero, Zero, One )

條目的值是四元素 Package:指回持有 的 裝置、 中第幾個 GPIO 資源、資源內第幾根腳、以及 active-low 旗標(此處 One 表示低電位有效)。C 端把 去掉字尾變成名字 :

#include <linux/gpio/consumer.h>

static int sns_reset(struct device *dev)

struct gpio_desc *reset;

/* "reset" matches the _DSD entry "reset-gpios" */ reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(reset)) return PTR_ERR(reset);

gpiod_set_value_cansleep(reset, 0); /* deassert reset */ return 0;

注意 API 談的是邏輯值: 已宣告 active-low,邏輯 1(assert reset)在硬體上輸出低電位。極性由韌體 描述、C 程式碼保持中立——這正是把硬體細節放進 ACPI 的價值。

I2C 與 SPI:描述即列舉

I2C 裝置的位址、速度、掛在哪個 controller,全部寫在 裡。當 I2C controller driver 註冊 adapter 時,核心掃描 Namespace,替每個引用它的裝置自動建立 ——driver 完全不用自己 instantiate:

Device (TMP1) Name (_HID, "DEMO0004") Name (_CRS, ResourceTemplate () I2cSerialBusV2 (0x0048, ControllerInitiated, 400000, AddressingMode7Bit, "
_SB.PCI0.I2C1", 0x00, ResourceConsumer, , Exclusive)
)

static int tmp_probe(struct i2c_client *client)

s32 raw;

/* client->addr == 0x48, from I2cSerialBusV2 */ raw = i2c_smbus_read_word_swapped(client, 0x00); if (raw < 0) return raw;

dev_info(&client->dev, "temp raw = 0x return 0;

註冊樣板與 25.4 同構,不再重列: 表填 ,掛進 的比對表欄位,最後用 收尾。SPI 的邏輯也完全同構: 描述 chip select、線模式、速度與時脈 極性,controller 註冊時自動生出 ,由 以同樣的比對表認領:

SpiSerialBusV2 (0x0000, PolarityLow, FourWireMode, 0x08, ControllerInitiated, 1000000, ClockPolarityLow, ClockPhaseFirst, "
_SB.PCI0.SPI1", 0x00, ResourceConsumer, , Exclusive)

PCI:_ADR 配對與 _PRT 一句話

PCI 裝置能自我列舉(第 1 章),所以 ACPI 節點不負責「發現」, 只負責「補充」:以 (高 16 bit 是 device、低 16 bit 是 function)宣告自己對應哪個 PCI 位址,核心據此把 ACPI 節點與 配成 companion,電源方法(/ )與 才有處落地:

Device (SAT0) Name (_ADR, 0x00170000) // device 0x17, function 0

至於中斷:PCI root bridge 下的 (PCI Routing Table) 負責把每個裝置的 INTA–INTD 腳對應到中斷控制器輸入——一句話記住 它:PCI 傳統中斷的路由表由 ACPI 提供,MSI 則不經過它。

_DSD 與 device properties:韌體傳參數給驅動

GPIO 小節已經偷用過 (Device Specific Data);它的 一般形式是「UUID 加 key-value Package」,等於韌體塞給 driver 的 組態檔。搭配的 C 端 API 是 家族:

Name (_DSD, Package () ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () Package () "sample-rate-hz", 48000 , Package () "channel-map", Package () 0, 1, 2, 3 )

static int demo_read_props(struct device *dev)

u32 map[4]; u32 rate; int ret;

ret = device_property_read_u32(dev, "sample-rate-hz", &rate); if (ret) return ret; /* mandatory property */

ret = device_property_read_u32_array(dev, "channel-map", map, ARRAY_SIZE(map)); if (ret) return ret;

dev_info(dev, "rate= return 0;

字串與布林屬性各有對應 API(;布林以 查「存在即為真」)。命名慣例: 屬性名用小寫加連字號(沿襲 Device Tree 傳統),單位直接寫進名字 (、)讓讀的人不用猜。

是 抽象層的門面: 同一份 driver 程式碼,在 x86 上從 讀值、在 ARM Device Tree 平台上從 DT 節點讀值,一行都不用改。寫跨平台 driver 時,優先用它而不是 ACPI 專屬 API——這也是 kernel 社群 review 時的既定要求。

韌體端:EDK II 的 ACPI 表生產線

視角換回韌體端。你寫好的 在 UEFI 韌體專案 ()裡是這樣被生產、運送、交貨的。先看它住哪裡:

PlatformPkg/AcpiTables/ ├── AcpiTables.inf ← 打包元件:收集以下所有表 ├── Dsdt/Dsdt.asl ← 本書主角:手寫的 ASL ├── Facp/Facp.aslc ← FADT:C 結構逐欄位填 ├── Madt/Madt.aslc ← MADT:CPU 與中斷拓撲 └── Mcfg/Mcfg.aslc ← MCFG:PCIe ECAM 基底

由 iasl 編譯; 是 EDK II 慣例的 「用 C 寫的表」——像 FADT 這種純欄位的表,直接定義一個 25.2.2 風格的 struct 常數即可。建置時 iasl 以 選項執行,除了 還會多吐一個 ——AML 映像的 C 陣列版,讓韌體能以原始碼形式攜帶這張表:

/* dsdt.hex (excerpt), generated by "iasl -tc dsdt.asl". * The array symbol name varies with the iasl version. */ unsigned char AmlCode[] = 0x44, 0x53, 0x44, 0x54, 0xC0, 0x0F, 0x00, 0x00, /* DSDT,len */ 0x02, 0x8D, 0x44, 0x45, 0x4D, 0x4F, 0x20, 0x20, /* rev,chks */ /* ... 4032 bytes in total ... */ ;

開頭八個 byte 正是 25.2.2 表頭 struct 的前兩個欄位: 與 little-endian 的長度 0xFC0=4032。整條生產線:

開機時,平台的 driver 在 FV 裡按 GUID 找到 裝表的 FFS 檔,逐張呼叫 :把表複製到 ACPI 保留記憶體、填進 XSDT、重算 checksum——第 1 章那條 「RSDP \(\to\) XSDT \(\to\) 各表」的鏈就是在這一刻搭好的。

最後,bring-up 時三張「非 AML」的表各自要填什麼 (DSDT 描述裝置,這三張描述平台骨架):

lL10.4cm 表 & bring-up 時的必填重點
FADT & SCI 中斷號、PM1a Event/Control block 位址、PM Timer、 與 enable/disable 命令值、DSDT/FACS 位址、HW-reduced 旗標;填錯常見電源鍵沒反應、睡眠失敗
MADT & Local APIC 位址、每顆 CPU 一筆 LAPIC/x2APIC entry、 IOAPIC entry、Interrupt Source Override(IRQ0 對 GSI2 是經典);填錯常見只認到一顆 CPU、中斷錯亂
MCFG & PCIe ECAM 基底位址、segment 編號、起訖 bus 號;填錯常見 讀不到延伸組態空間

端到端案例:一顆 LED 的旅程

收尾前,把全書串成一個故事。硬體工程師遞來一頁規格:「LED 控制器,MMIO 基底 ,offset 0 是控制暫存器: bit 0 enable、bits 7..4 亮度,開機預設關。讓使用者能從 Linux 控制它。」你的整合路線:

每一站都是你已經修過的學分:讀懂暫存器規格、規劃 bit 欄位靠 25.2 的 C 功力與第 9–11 章的 Field 語意;寫 Device、 、 是第 8 章與第 18–20 章的 SSDT 實戰; 編譯與靜態驗證交給第 21 章的 iasl 與 fwts;整合進韌體走 25.7 的 EDK II 生產線(或第 23 章的 initrd 捷徑);列舉與 driver 綁定是 25.3 與 25.4;驗證與除錯的整套工具則在 Module 10(第 22–24 章)。

ASL 端你已經會了:25.2.3 的 region 加 / 兩個欄位,包成一個 為 的 Device,配上對應的 。driver 端沿用 25.4 的骨架, 兩處小改:probe 用 把資源映成 指標;sysfs attribute 換成可寫版本,先以 把字串轉成 0–15 的亮度值, 再照搬 25.2.3 的 那組 RMW 序列寫進暫存器, 回傳 收工。

於是「」 這個再平凡不過的指令,背後是:韌體描述(ASL)\(\to\) 編譯(iasl) \(\to\) 韌體安裝(EDK II)\(\to\) 核心列舉(ACPI subsystem)\(\to\) driver 綁定()\(\to\) 位址翻譯( 加 )\(\to\) 位元操作(RMW)\(\to\) 光子離開 LED。 若哪一站斷了,Module 10 的整套工具(dmesg、sysfs、Debug 物件、 ACPI Error 解剖)都在等你。第 26 章起的實戰專案,會讓你把這條鏈 親手走上五遍。

  • ASL 與 C 是同一份暫存器規格的兩種寫法: OperationRegion/Field 對應 pointer、mask、 /;Field 存取天生 volatile。

  • 列舉鏈:DSDT 的 Device \(\to\) \(\to\) \(\to\) driver probe;I2C/SPI 裝置 由 controller 列舉,PCI 節點只當 companion。

  • platform driver 三件套: 比對表、 取 、 呼叫 Method。

  • 傳 key-value 參數,C 端用 讀取,同一套 API 通吃 ACPI 與 Device Tree。

  • EDK II 生產線: \(\to\) iasl()\(\to\) FFS/FV \(\to\) AcpiPlatformDxe 安裝 \(\to\) XSDT; FADT/MADT/MCFG 是 bring-up 的平台骨架。

  1. 解決什麼問題、不解決什麼問題?為什麼 kernel 要求用 / 而不是直接 解參考 指標?

  2. 手算 的值,並寫出把亮度欄位設成 5 而不動其他 bit 的完整 RMW 序列(C 與 ASL 各一版)。

  3. 在 的 加一個 屬性,修改 driver 讀出並印到 dmesg。

  4. 動手:完整跑一遍 25.4 的 Lab;然後把 ASL 的 改成回傳 ,重新載入 SSDT 與模組, 用 sysfs 驗證結果變成 63。