当前位置: 首页 > news >正文

Linux设备树(dts/dtsi/dtb、设备树概念,设备树解析,驱动匹配)

设备树

文章目录

    • 设备树
      • I 设备树基本概念
        • 1.设备树的作用
        • 2.为什么需要设备树
        • 3.设备树基本结构
        • 4.常用节点类型
        • 5.常用属性
        • 6.设备树的编译
      • II 内核对设备树文件的解析
        • 1.内核启动时,从 uboot 获取.dtb的内存地址
        • 2.解析.dtb为内部数据结构(device_node)
        • 3.根据compatible属性匹配驱动,创建platform_device等设备对象
        • 4.驱动通过platform_device获取设备属性,完成硬件初始化
      • III 总结

I 设备树基本概念

1.设备树的作用
  • 设备树 (Device Tree) 是描述硬件资源的数据结构,实现硬件配置与内核分离
  • 核心作用:向内核驱动程序提供硬件信息
  • 系统启动时,会先运行 uboot,uboot 加载内核和设备树到内存,启动内核并传递设备树内存地址给内核,内核解析设备树获取硬件信息
2.为什么需要设备树
  • 在传统嵌入式开发中,硬件信息(如外设地址、中断号等)通常硬编码到内核源码中。当硬件发生微小变化(如同一型号外设的地址偏移),就需要重新修改并编译内核,极大增加了内核适配不同硬件的复杂度
  • 设备树将所有硬件信息从内核中剥离,以独立的设备树文件(.dts)描述,内核通过解析设备树动态获取硬件信息,实现了 “一份内核适配多份硬件”,大幅简化了嵌入式系统的移植与维护。
3.设备树基本结构

一个简单的设备树文件包含版本声明、根节点及若干子节点,每个节点都包含各种属性信息。

    /dts-v1/;  // 版本声明,固定格式#include "xxx.dtsi"  // 可引用其他设备树片段(.dtsi为通用片段文件)/ {       // 根节点,唯一的顶级节点#address-cells = <1>;  // 地址字段长度(单位:32位),用于子节点reg属性#size-cells = <1>;     // 大小字段长度(单位:32位),用于子节点reg属性node1: device@12340000 {  // 子节点,命名格式:"设备类型@地址"(地址用于区分同类型设备)compatible = "vendor,node1-v1";  // 兼容属性,用于驱动匹配reg = <0x12340000 0x1000>;  // 地址范围:起始地址 长度status = "okay";  // 设备状态:启用};node2 {compatible = "vendor,node2-v2";pinctrl-names = "default";  // 引脚配置名称pinctrl-0 = <&pinctrl_node2>;  // 引用引脚配置节点interrupt-parent = <&intc>;  // 中断父节点(通常为中断控制器)interrupts = <5 0x04>;  // 中断号 触发方式(0x04为高电平触发)};};

节点命名规则

节点名通常采用device-type@address格式。其中:

  • device-type:描述设备类型(如 uart、led、i2c 等);

  • address:设备在父总线中的地址(如寄存器起始地址),用于区分同一总线上的同类型设备(如uart@12340000uart@12350000)。

4.常用节点类型

(1)根节点: /

  所有设备树的顶级节点,必须包含#address-cells#size-cells属性,用于定义子节点reg属性的地址和大小字段长度

    / {#address-cells = <2>;  // 地址由2个32位字段组成(如高32位+低32位)#size-cells = <1>;     // 大小由1个32位字段组成};

(2)CPU 节点: cpus

  描述系统中的 CPU 核心信息,包含cpu子节点(每个核心一个)。

    cpus {#address-cells = <1>;#size-cells = <0>;  // CPU地址无大小,设为0cpu@0 {  // 第一个CPU核心compatible = "arm,cortex-a7";  // CPU型号reg = <0>;  // CPU编号clocks = <&cpu_clk>;  // CPU时钟源operating-points-v2 = <&cpu_opp_table>;  // 工作点(频率/电压)表};};

(3)内存节点: memory

  描述系统物理内存,必须包含reg属性定义内存地址范围。

    memory@80000000 {device_type = "memory";  // 固定属性,标识为内存节点reg = <0x80000000 0x20000000>;  // 内存范围:起始地址0x80000000,大小512MB(0x20000000)};

(4)总线节点: amba, soc, i2c, spi

  描述系统总线(如 I2C、SPI、AMBA 总线),通常包含总线时钟、速率等属性。

    i2c@12360000 {compatible = "vendor,i2c-v3";reg = <0x12360000 0x1000>;clock-frequency = <100000>;  // I2C总线频率100kHz#address-cells = <1>;  // I2C设备地址长度(7位或10位)#size-cells = <0>;sensor@48 {  // I2C总线上的传感器设备(地址0x48)compatible = "vendor,temp-sensor";reg = <0x48>;  // I2C设备地址};};

(5)外设节点

  描述具体硬件设备(如 UART、LED、LCD 等)

    led@0 {compatible = "gpio-leds";label = "user-led";  // LED标签(用于用户空间识别)gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>;  // 引脚:GPIO控制器 引脚号 有效电平default-state = "off";  // 默认状态};
5.常用属性

(1)compatible

  • 作用:用于驱动与设备的匹配(“设备找驱动”)。

  • 格式:compatible = "厂商,设备型号", "通用型号";(从具体到通用,驱动匹配时优先匹配前面的)。

  • 示例:

        compatible = "nvidia,tegra20-uart", "ns16550"; // 英伟达Tegra20的UART,兼容通用ns16550串口
    

(2)reg

  • 作用:描述设备在父总线中的地址范围(如寄存器地址、内存地址)。

  • 格式:reg = <address1 length1 [address2 length2 ...]>;(地址和长度的数量由父节点#address-cells#size-cells定义)。

  • 示例:

        reg = <0x12340000 0x1000>;  // 起始地址0x12340000,长度0x1000(4KB)
    

(3)status

  • 作用:描述设备状态。

  • 常用值:

    • "okay":设备启用;

    • "disabled":设备禁用(内核不解析);

    • "fail":设备存在但不可用。

(4)interrupts

  • 作用:描述设备中断信息(依赖interrupt-parent指定中断控制器)。

  • 格式:interrupts = <中断号 触发方式 [优先级]>;

  • 触发方式常用值:

    • 0x01:上升沿触发;
    • 0x02:下降沿触发;
    • 0x04:高电平触发;
    • 0x08:低电平触发。
  • 示例:

        interrupt-parent = <\&intc>;  // 中断控制器为intc节点interrupts = <5 0x04>;  // 中断号5,高电平触发
    

(5)clocks / clock-names

  • 作用:描述设备使用的时钟源。

  • 格式:clocks = <&clock_node1>, <&clock_node2>;clock-names = "clk1", "clk2";(用于区分多个时钟)。

  • 示例:

        clocks = <\&clk\_pll 0>, <\&clk\_ahb 2>;clock-names = "core-clk", "bus-clk";  // 核心时钟、总线时钟
    

(6)gpios

  • 作用:描述设备使用的 GPIO 引脚。

  • 格式:gpios = <&gpio_controller pin_number [flags]>;

  • flags 常用值:

    • GPIO_ACTIVE_HIGH:高电平有效;
    • GPIO_ACTIVE_LOW:低电平有效。

(7)model

  • 作用:描述设备 / 系统的型号(比 compatible 更简洁)。

  • 示例:model = "raspberrypi,3-model-b";(树莓派 3B 型号)。

(8)phandle / 引用

  • 作用:实现节点间的引用(如中断父节点、引脚配置节点等)。

  • 用法:通过&节点标签引用(节点需先定义标签,如node1: uart@12340000,引用时用&node1)。

6.设备树的编译

(1)编译工具dtc(Device Tree Compiler)

  • 功能:将.dts(设备树源文件)编译为.dtb(设备树二进制文件,内核可解析)。

  • 常用命令:

        dtc -I dts -O dtb -o output.dtb input.dts  # 编译:输入.dts,输出.dtbdtc -I dtb -O dts -o input.dts output.dtb  # 反编译:从.dtb还原为.dts
    

(2)设备树文件类型

  • .dts:针对具体板卡的设备树文件(包含板级特有信息);*
  • .dtsi:通用设备树片段(如 SOC 级通用硬件描述,可被多个.dts引用)。

II 内核对设备树文件的解析

1.内核启动时,从 uboot 获取.dtb的内存地址
  • uboot 在引导内核启动时,通过特定寄存器(如 ARM 架构的 r2 寄存器)将.dtb 的物理内存地址传递给内核。例如在 ARM 平台中,内核启动入口函数会从 r2 寄存器读取该地址。
2.解析.dtb为内部数据结构(device_node)
  • 内核会遍历.dtb 中的节点和属性,将其转换为device_node结构体链表

        struct device_node {const char *name;        // 节点名称(如"uart@12340000")const char *type;        // 节点类型(由device_type属性指定)phandle phandle;         // 节点句柄(用于节点引用)struct device_node *parent;  // 父节点指针struct device_node *child;   // 子节点指针struct device_node *sibling; // 兄弟节点指针struct property *properties; // 属性链表};
    
  • 每个节点的属性会被解析为property结构体,包含属性名、值长度和值数据

        struct property {const char *name;        // 属性名(如"compatible")int length;              // 属性值长度void *value;             // 属性值数据(如"vendor,uart"的二进制表示)struct property *next;   // 下一个属性};
    
  • 解析完成后,内核会构建以根节点(/)为起点的device_node树状结构,所有硬件信息都以节点和属性的形式保存在该结构中

3.根据compatible属性匹配驱动,创建platform_device等设备对象
  • 设备对象创建:

    • 内核遍历device_node树,对每个具备硬件驱动需求的节点创建platform_device对象。
    • platform_device与device_node存在关联,前者的dev.of_node成员指向对应的device_node,便于驱动通过设备对象反向查询设备树信息。
  • compatible属性驱动匹配:​

    • 驱动程序通过of_device_id结构体声明支持的设备类型,其中包含与设备树compatible属性对应的字符串
        static const struct of_device_id uart_of_match[] = {{ .compatible = "nvidia,tegra20-uart" },{ .compatible = "ns16550" },{ /* 终止符 */ }};MODULE_DEVICE_TABLE(of, uart_of_match);
    
    • 内核通过of_match_device函数比对设备节点的compatible属性与驱动的of_device_id列表,找到匹配的驱动
4.驱动通过platform_device获取设备属性,完成硬件初始化
  • 内核提供了一系列of_xxx接口函数,用于从device_node中读取属性值,​
    • 获取 compatible 属性:of_get_property(node, “compatible”, &len)​
    • 获取 reg 属性(地址信息):of_address_to_resource(node, index, &res),将 - reg 属性解析为resource结构体(包含起始地址和长度)。​
    • 获取中断属性:irq_of_parse_and_map(node, index),返回中断号。​
    • 获取 GPIO 属性:of_get_named_gpio(node, “gpios”, index),返回 GPIO 编号。
        static int uart_probe(struct platform_device *pdev) {struct device_node *node = pdev->dev.of_node;struct resource res;int irq;// 获取寄存器地址of_address_to_resource(node, 0, &res);void __iomem *base = ioremap(res.start, resource_size(&res));// 获取中断号irq = irq_of_parse_and_map(node, 0);// 初始化硬件(如设置波特率、中断使能等)uart_hw_init(base, irq);return 0;}
    
  • 驱动获取寄存器地址、中断号、时钟频率等硬件参数后,调用驱动的probe函数完成初始化,例如内存映射(ioremap)、中断注册(request_irq)、时钟使能等操作,最终实现硬件设备的正常工作。

III 总结

  • 设备树是嵌入式系统中连接硬件与内核的关键桥梁,通过结构化的节点和属性描述硬件信息,实现了 “硬件描述与内核解耦”。
  • 设备树的结构、节点类型、核心属性及编译方法,是嵌入式开发中适配硬件、调试驱动的基础。
  • 实际开发中,需结合具体 SOC 和板卡的 datasheet 编写设备树,并通过dtc工具验证语法正确性,最终实现内核对硬件的自动识别与驱动。
http://www.dtcms.com/a/274158.html

相关文章:

  • P1204 [USACO1.2] 挤牛奶Milking Cows
  • 如何设置直播间的观看门槛,让直播间安全有效地运行?
  • 云原生周刊:镜像兼容性
  • 假日流量红利:如何用ASO策略抢占季节性下载高峰?
  • 不同质押周期对代币价格稳定性的具体影响及数据支撑
  • MinIO文件存储服务工具详细使用指南
  • 和服腰封改造:3种解构主义造型的东方美学新解
  • 2025年亚太中文赛赛题浅析-助攻快速选题
  • 【氮化镓】100 V GaN晶体管在关态应力下的双退化
  • Spring Boot中请求参数读取方式
  • HTTP 请求方法详解:GET、POST、PUT、DELETE 等
  • Python中类静态方法:@classmethod/@staticmethod详解和实战示例
  • LeetCode 278. 第一个错误的版本
  • 基于生产者消费者模型的线程池【Linux操作系统】
  • mysql中的自增ID
  • 物联网-ESP8266
  • API、MCP Client、MCP Server、LLM之间的业务逻辑关系
  • 医疗预约系统中的录音与图片上传功能实现:Vue3+Uniapp 实战
  • 在线重装 Proxmox VE
  • Swift中SwiftyJSON使用详情
  • 墙裂推荐!McpStore库三行代码为Agent添加MCP能力
  • 业务建模如何让金融数字化转型 “轻” 装上
  • CentOS7环境安装包部署并配置MySQL5.7
  • 什么是proxy
  • 使用浏览器inspect调试wx小程序
  • 构建基于表单配置的 Jenkins 测试项目(接口、UI、APP、Jmeter)
  • 加速市场反馈,助力产品迭代升级​
  • 如何使用 Python 删除 Excel 中的行、列和单元格 – 详解
  • IAR携手矽力杰与普华基础软件,共推RISC-V车规芯片高安全应用落地
  • docker 启动中间件