4. 设备树与ACPI基础
Linux 设备模型要回答两个根本问题:系统里有哪些设备,以及这些设备占用了哪些资源。在 ARM/ARM64、RISC-V 等平台上,答案通常来自设备树(Device Tree);在 x86、部分 ARM64 服务器平台上,答案通常来自 ACPI。二者目标一致,但描述方式、解析路径和内核接口完全不同。理解它们的差异,是排查“设备没被枚举”“资源冲突”“驱动 probe 失败”等问题的前提。
一、设备树:用 DTS 描述不可自动发现的硬件
设备树的核心思想是:把 SoC 内部外设、板级连线、中断、时钟、电源、GPIO 等“无法被总线自动枚举”的信息,写成一份树状数据,由 bootloader 传给内核。内核在启动早期展开设备树,生成 device_node,再转换为 platform_device 等设备对象,最后与驱动匹配。
DTS 源文件经 DTC 编译为 DTB。常见语法包括节点、属性、phandle、#address-cells、#size-cells、compatible、reg、interrupts、clocks、status 等。下面是一个简化的 I2C 温度传感器节点:
/ {
soc {
i2c1: i2c@fe5c0000 {
compatible = "vendor,i2c-v2";
reg = <0xfe5c0000 0x1000>;
clocks = <&cru_clk 42>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
temp@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupt-parent = <&gpio1>;
interrupts = <12 2>;
};
};
};
};
其中 compatible 是驱动匹配的关键,reg 描述地址与长度,interrupts 描述中断号和触发类型,status 决定节点是否启用。若 status = "disabled",内核不会为该节点创建设备,驱动自然无法 probe。
二、of_* 接口:驱动读取设备树的统一入口
驱动不直接解析 DTB,而是通过 of_* 系列接口读取节点属性。常用接口如下:
of_find_node_by_path()/of_find_compatible_node():查找节点。of_property_read_u32()/of_property_read_string():读取属性。of_get_child_by_name():遍历子节点。of_irq_get()/platform_get_irq():获取中断资源。of_clk_get()/devm_clk_get():获取时钟。of_address_to_resource():把reg转换为struct resource。
调试时,/proc/device-tree 是查看运行时设备树的最直接入口;/sys/firmware/devicetree/base 也提供同样内容。若驱动读不到属性,优先检查属性名拼写、数据类型、status 以及节点是否真的被内核展开。
三、ACPI:用表结构描述可枚举与不可枚举设备
ACPI 通过一组表向 OS 描述硬件。核心表包括 RSDP、XSDT、FADT、DSDT、SSDT、MADT、MCFG 等。其中 DSDT 与 SSDT 使用 AML 字节码描述设备层次、资源、方法和电源管理;MADT 描述 CPU 与中断控制器;MCFG 描述 PCIe 配置空间。
与设备树不同,ACPI 设备通常由总线枚举发现,ACPI 表负责补充“总线枚举不到”的信息,例如 GPIO、I2C 从设备、电源资源、中断路由。内核通过 acpi_* 接口读取这些信息,例如 acpi_dev_get_resources()、acpi_get_gpiod_by_index()、acpi_evaluate_integer()。在 Linux 中,/sys/firmware/acpi/tables 可查看原始表,acpidump 与 iasl 是常用反汇编工具。
四、设备枚举与资源分配:两条路径的对比
设备树与 ACPI 在设备枚举和资源分配上的差异,直接影响调试方向。下表给出关键对比:
| 维度 | 设备树(DT) | ACPI |
|---|---|---|
| 描述形式 | DTS/DTB,树状属性 | ACPI 表,AML 字节码 |
| 典型平台 | ARM/ARM64、RISC-V、MIPS | x86、部分 ARM64 服务器 |
| 设备来源 | 内核展开 DT 生成 platform_device | 总线枚举 + ACPI 命名空间 |
| 资源描述 | reg、interrupts、clocks | _CRS、_PRS、_DSD |
| 驱动接口 | of_*、platform_get_* | acpi_*、platform_get_* |
| 调试入口 | /proc/device-tree | /sys/firmware/acpi/tables |
在资源分配上,设备树把地址、中断、时钟、GPIO 直接写在节点里,内核按 reg 和 interrupts 建立 struct resource;ACPI 则通过 _CRS 返回当前资源设置,通过 _SRS 可设置资源。若 _CRS 与硬件实际不符,就会出现“设备存在但资源冲突”或“驱动拿到错误中断号”的问题。
五、典型场景与排障思路
场景一:I2C 传感器驱动 probe 失败。若系统使用设备树,先看 /proc/device-tree 下对应节点是否存在、status 是否为 okay、reg 地址是否与硬件一致;若系统使用 ACPI,则用 acpidump 导出 DSDT,反汇编后检查该设备 _CRS 中的 I2C 地址与中断描述。
场景二:平台设备没有生成。设备树路径下,常见原因是 compatible 与驱动 of_match_table 不匹配,或节点被 status = "disabled" 关闭;ACPI 路径下,常见原因是设备未出现在命名空间,或 _STA 返回 0 导致设备被判定为不存在。
场景三:中断号错误。设备树中 interrupts 的编码依赖 interrupt-parent 和中断控制器的 #interrupt-cells;ACPI 中中断由 _CRS 的 Interrupt() 描述。若两者与实际硬件不一致,驱动会申请到错误 IRQ,表现为中断收不到或系统报 irq: nobody cared。
status、驱动匹配、资源申请和 probe 结果。ACPI 同理,表里有设备不等于内核一定创建了对应设备对象。
六、复盘:建立统一的调试视角
无论设备树还是 ACPI,调试都可以沿同一条链路推进:描述是否存在 → 内核是否解析 → 设备是否创建 → 驱动是否匹配 → 资源是否申请成功 → probe 是否完成。设备树侧重点在 DTS 语法、of_* 接口和 /proc/device-tree;ACPI 侧重点在 ACPI 表、命名空间、_CRS/_STA 和 acpidump/iasl。把这条链路与具体平台的描述机制对应起来,就能在设备枚举和资源分配问题上快速定位到是“描述错误”“解析错误”还是“驱动错误”。