当前位置:首页 > 技术 > 正文内容

Linux 设备树详解:从基础到实践

访客 技术 2026年5月25日 3

一、设备树概述

设备树(Device Tree)是一种描述硬件配置的树形数据结构,其源文件称为 DTS(DeviceTree Source)。DTS 文件采用层级结构来描述嵌入式系统的硬件组件,特别是板级外设。这种结构清晰地展示了系统总线与连接在其上的各种外设控制器之间的关系,例如 IIC、GPIO 和 SPI 控制器。

例如,IIC 控制器可以细分为 IIC1 和 IIC2,每个 IIC 总线上又可以挂载不同的设备,如 FT5206、AT24C02 或 MPU6050。

通常,.dts 文件用于描述特定开发板的硬件信息,而 .dtsi 文件则用于描述片上系统(SOC)的通用外设信息,如 CPU 数量、主频和外设控制器等。

引入设备树的主要动机之一是简化 Linux 内核中针对不同硬件平台的配置,避免为每个板子维护大量冗余的板级信息文件。

二、DTS、DTB 与 DTC

DTS 是设备树的源码文件,DTB(DeviceTree Blob)则是 DTS 编译后的二进制文件。将 DTS 编译为 DTB 需要使用 DTC(DeviceTree Compiler)工具。

在 Linux 源码根目录下,可以通过以下命令编译设备树:

cd /path/to/linux-source
make dtbs

上述命令会编译所有选中的设备树文件。若仅需编译特定的设备树文件,例如 stm32mp157d-ed1.dts,可执行:

make stm32mp157d-ed1.dtb

Linux 内核如何确定编译哪个 DTS 文件呢?这通常通过 arch/arm/boot/dts/Makefile 文件中的配置来指定。例如,对于 STM32MP1 SOC,该文件中会列出所有基于此 SOC 的板子对应的 DTB 文件:

dtb-$(CONFIG_ARCH_STM32) += \
    stm32mp157d-ed1.dtb \
    stm32mp157f-ev1.dtb \
    # ... 其他板子

当内核配置中启用 CONFIG_ARCH_STM32 时,所有列出的 DTB 文件都会被编译。若要为新的 STM32MP1 开发板添加支持,只需创建对应的 .dts 文件,并将其名称添加到上述列表中。

编译生成的 DTB 文件在系统启动时由 U-Boot 传递给 Linux 内核,内核通过解析 DTB 来了解硬件配置。

三、DTS 语法

学习 DTS 语法有助于理解和修改设备树文件。通常,我们会在 SOC 厂商提供的 DTS 文件基础上进行修改。

  1. dtsi 头文件

设备树支持包含 .h.dtsi.dts 文件。推荐使用 .dtsi 作为头文件后缀。

例如,STM32MP1 系列包含 stm32mp151、stm32mp153 和 stm32mp157 等型号。其中,stm32mp151 是基础型号,stm32mp153 和 stm32mp157 在其基础上增加了更多外设。因此,stm32mp151.dtsi 文件描述了这三个 SOC 共有的外设资源。

/ {
    #address-cells = <1>;
    #size-cells = <1>;

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            ...
        };
    };

    spi2: spi@4000b000 {
        compatible = "st,stm32h7-spi";
        reg = <0x4000b000 0x400>;
        ...
    };
}

根节点 / 包含 cpusspi2 等子节点。每个节点用花括号括起,并包含一系列属性。

  1. 设备节点

设备树中的每个硬件组件都表示为一个节点,称为设备节点。节点通过属性(键值对)来描述其特性。

以下是简化后的设备树模板:

/ {
    #address-cells = <1>;
    #size-cells = <1>;

    aliases {
        serial0 = &uart4;
    };

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <0>;
            ...
        };
    };

    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        interrupt-parent = <&intc>;
        ranges;

        sram: sram@10000000 {
            compatible = "mmio-sram";
            reg = <0x10000000 0x60000>;
            ...
        };
    };
}

节点格式为 node-name@unit-address,其中 node-name 是节点名称,unit-address 是设备地址或寄存器基地址。

  1. 标准属性

① compatible 属性

compatible 属性是设备与驱动匹配的关键。其值是一个字符串列表,格式通常为 "manufacturer,model"

例如:

compatible = "cirrus,cs42l51";

如果设备节点的 compatible 属性值与驱动中的 OF 匹配表(of_device_id)中的任何值匹配,则该设备将使用该驱动。

② model 属性

model 属性描述开发板或设备模块的名称:

model = "STMicroelectronics STM32MP157C-DK2 Discovery Board";

③ status 属性

status 属性表示设备的状态:

  • "okay": 设备可操作。
  • "disabled": 设备当前不可操作,但未来可能变为可操作。
  • "fail": 设备不可操作,且不大可能恢复。

④ #address-cells 和 #size-cells 属性

这两个属性定义子节点的地址信息格式。#address-cells 指定地址占用的字长,#size-cells 指定地址长度占用的字长。

例如:

cpus {
    #address-cells = <1>;
    #size-cells = <0>;

    cpu0: cpu@0 {
        reg = <0>;  // 地址为 0,无长度
    };
};

sram: sram@10000000 {
    reg = <0x10000000 0x60000>;  // 起始地址和长度
};

⑤ reg 属性

reg 属性描述设备的地址空间资源,通常为 (address, length) 对。

⑥ ranges 属性

ranges 属性定义地址映射,格式为 (child-bus-address, parent-bus-address, length)。若为空,表示子地址空间与父地址空间相同。

四、创建小型设备树模板

以下是一个基于 STM32MP157 SOC 的简化设备树示例,仅用于演示语法。

  1. 添加 cpus 节点

/ {
    compatible = "st,stm32mp157d-atk", "st,stm32mp157";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <0>;
        };

        cpu1: cpu@1 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <1>;
        };
    }
}
  1. 添加 soc 节点

/ {
    ...
    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        sram: sram@10000000 {
            compatible = "mmio-sram";
            reg = <0x10000000 0x60000>;
        };

        timers6: timer@40004000 {
            compatible = "st,stm32-timers";
            reg = <0x40004000 0x400>;
        };

        spi2: spi@4000b000 {
            compatible = "st,stm32h7-spi";
            reg = <0x4000b000 0x400>;
        };

        usart2: serial@4000e000 {
            compatible = "st,stm32h7-uart";
            reg = <0x4000e000 0x400>;
        };

        i2c1: i2c@40012000 {
            compatible = "st,stm32mp15-i2c";
            reg = <0x40012000 0x400>;
        };
    };
}

五、设备树在系统中的体现

Linux 内核启动时会解析设备树,并在 /proc/device-tree 目录下创建与节点对应的目录结构。

例如,根节点的属性会以文件形式存在:

/proc/device-tree/
├── #address-cells
├── #size-cells
├── compatible
├── model
└── name

子节点则作为子目录存在,例如 /proc/device-tree/soc/

六、特殊节点

  1. aliases 子节点

aliases 节点用于定义节点别名,方便通过别名访问节点:

aliases {
    serial0 = &uart4;
};
  1. chosen 子节点

chosen 节点用于 U-Boot 向 Linux 内核传递数据,特别是 bootargs 参数:

chosen {
    bootargs = "console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait";
};

七、设备树常用 OF 操作函数

Linux 内核提供了一系列以 of_ 为前缀的函数,用于从设备树中提取节点和属性信息。

  1. 查找节点的 OF 函数

  • of_find_node_by_name: 通过节点名称查找节点。
  • of_find_node_by_type: 通过 device_type 属性查找节点。
  • of_find_compatible_node: 通过 compatible 属性查找节点。
  • of_find_node_by_path: 通过路径查找节点。
  1. 查找父/子节点的 OF 函数

  • of_get_parent: 获取父节点。
  • of_get_next_child: 迭代查找子节点。
  1. 提取属性值的 OF 函数

  • of_find_property: 查找指定属性。
  • of_property_read_u32_index: 读取指定索引的 u32 值。
  • of_property_read_u8_array: 读取 u8 数组。
  • of_property_read_string: 读取字符串值。
  1. 其他常用 OF 函数

  • of_device_is_compatible: 检查设备兼容性。
  • of_get_address: 获取地址属性。
  • of_translate_address: 将设备树地址转换为物理地址。
  • of_address_to_resource: 将 reg 属性转换为 resource 结构。
  • of_iomap: 将 reg 属性映射为虚拟地址。
标签: 设备树

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。