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、MIPSx86、部分 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 与硬件实际不符,就会出现“设备存在但资源冲突”或“驱动拿到错误中断号”的问题。

核心判断:设备树是“静态描述 + 内核展开”,ACPI 是“表描述 + 命名空间求值”。调试时先确认系统走哪条路径,再选择对应的查看工具和接口,能避免大量无效排查。

五、典型场景与排障思路

场景一: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。把这条链路与具体平台的描述机制对应起来,就能在设备枚举和资源分配问题上快速定位到是“描述错误”“解析错误”还是“驱动错误”。

设备树与 ACPI:设备枚举与资源分配路径 设备树(DT)路径 DTS / DTB 节点、属性、phandle 内核展开 device_node → platform_device of_* 接口 读 reg / irq / clk 资源分配 struct resource ACPI 路径 ACPI 表 DSDT / SSDT / MADT 命名空间 设备层次与 _STA acpi_* 接口 _CRS / _DSD / _PRS 资源分配 IRQ / GPIO / MEM 统一调试链路 描述存在 → 内核解析 → 设备创建 → 驱动匹配 → 资源申请 → probe DT 看 /proc/device-tree;ACPI 看 acpidump / iasl 关键差异: DT 是静态树状描述,内核展开为 platform_device;ACPI 是表 + 命名空间求值,总线枚举与 _CRS 共同决定资源。 排障时先确认平台走哪条路径,再检查对应描述、解析接口与资源申请结果,避免把“描述错误”误判为“驱动错误”。