【Linux驱动开发】第12天:Linux设备树核心:树形结构+节点+属性 完整全解

目录

  1. 设备树树形结构概述
  2. 节点(Node)全解:命名规范+标准节点+常用设备节点
  3. 属性(Property)全解:类型+核心属性+总线专用属性
  4. 标签与节点引用:设备树复用的核心
  5. 常见错误与注意事项
  6. 总结:驱动开发必背节点与属性

1. 设备树树形结构概述

设备树采用分层树形结构 描述硬件,和计算机的文件系统目录结构几乎完全一致。它以根节点为起点,向下逐层展开,真实反映了计算机系统中硬件的物理连接关系。

1.1 树形结构的三大核心元素

设备树的所有内容都由这三个基本元素组成:

复制代码
根节点(/)
├── 子节点1(代表一个硬件设备)
│   ├── 属性1 = 值1(描述设备特性)
│   ├── 属性2 = 值2
│   └── 孙子节点1(代表子设备)
│       └── 属性3 = 值3
└── 子节点2
    └── 属性4 = 值4
  • 根节点 :所有节点的父节点,用/表示,每个设备树有且只有一个
  • 节点:每个硬件设备对应一个节点,节点可以包含子节点,形成分层结构
  • 属性:每个节点包含多个键值对形式的属性,用于描述设备的具体硬件信息

1.2 最简完整设备树示例

dts 复制代码
/dts-v1/;  // 设备树版本号,必须是第一行

/ {  // 根节点
    compatible = "myvendor,myboard";
    model = "My Custom Development Board";

    // 片上系统节点,包含所有内部外设
    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        // UART串口节点
        uart@12340000 {
            compatible = "myvendor,my-uart";
            reg = <0x12340000 0x1000>;
            interrupts = <5>;
            status = "okay";
        };

        // GPIO控制器节点
        gpio@12341000 {
            compatible = "myvendor,my-gpio";
            reg = <0x12341000 0x1000>;
            gpio-controller;
            #gpio-cells = <2>;
            status = "okay";
        };
    };

    // 外接LED节点
    leds {
        compatible = "gpio-leds";

        blue-led {
            label = "blue:user";
            gpios = <&gpio 13 GPIO_ACTIVE_HIGH>;
        };
    };
};

2. 节点(Node)全解:命名规范+标准节点+常用设备节点

节点是设备树的基本单元,每个硬件设备对应一个节点。

2.1 节点命名规范(必须严格遵守)

不符合规范的节点会导致内核解析失败,驱动无法匹配。

标准命名格式
复制代码
节点名@单元地址
各部分含义
  1. 节点名 :描述设备的通用类型,如cpumemoryuarti2c

    • 只能使用小写字母、数字和连字符-
    • 不能使用下划线_、空格或其他特殊字符
    • 尽量使用内核通用的设备类型名,不要使用厂商自定义名称
  2. @单元地址:设备的唯一地址标识

    • 内存映射设备:寄存器基地址(十六进制)
    • I2C/SPI设备:从机地址/片选号
    • CPU:CPU编号
    • 作用:区分同类型的不同设备
正确与错误命名对比
正确命名 错误命名 错误原因
uart@12340000 UART@12340000 使用了大写字母
i2c@12341000 i2c_controller@12341000 节点名过长,应使用通用类型名
led@1 led_1 使用了下划线,缺少@和单元地址
cpu@0 cpu0 缺少@和单元地址

2.2 标准节点(每个设备树都必须包含)

这些节点不对应具体的外设,而是提供系统级的全局信息。

1. 根节点 /
  • 作用:整个设备树的起点,包含系统全局信息

  • 必须属性

    dts 复制代码
    / {
        compatible = "厂商,板级型号";  // 板级兼容属性,匹配内核板级支持包
        model = "开发板全称";        // 人类可读的开发板名称
        #address-cells = <1>;        // 子节点reg属性中地址部分占几个32位整数
        #size-cells = <1>;           // 子节点reg属性中长度部分占几个32位整数
    };
2. /aliases 别名节点
  • 作用:给节点起简短别名,方便内核和驱动引用

  • 示例

    dts 复制代码
    aliases {
        serial0 = &uart4;    // 给uart4节点起别名serial0
        i2c1 = &i2c1;        // 给i2c1节点起别名i2c1
        led0 = &blue_led;    // 给LED节点起别名led0
    };
  • 用途 :内核通过别名快速找到特定设备,如console=ttySTM0对应serial0别名

3. /chosen 选择节点
  • 作用:传递内核启动参数和运行时配置

  • 最重要属性bootargs(内核命令行参数)

  • 示例

    dts 复制代码
    chosen {
        bootargs = "console=ttySTM0,115200 root=/dev/mmcblk0p2 rootwait rw";
        stdout-path = &uart4;  // 指定标准输出设备
    };
4. /memory 内存节点
  • 作用:描述系统物理内存信息

  • 必须属性

    dts 复制代码
    memory@c0000000 {
        device_type = "memory";  // 固定值,标识这是内存节点
        reg = <0xc0000000 0x20000000>;  // 基地址0xc0000000,长度0x20000000(512MB)
    };
5. /cpus CPU节点
  • 作用:描述系统中的CPU核心信息

  • 结构 :包含一个或多个cpu@x子节点

  • 示例

    dts 复制代码
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
    
        cpu@0 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <0>;  // CPU编号
            clock-frequency = <800000000>;  // CPU主频800MHz
        };
    
        cpu@1 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <1>;
            clock-frequency = <800000000>;
        };
    };
6. /soc 片上系统节点
  • 作用:包含所有片上外设(UART、I2C、SPI、GPIO等)

  • 必须属性

    dts 复制代码
    soc {
        compatible = "simple-bus";  // 告诉内核这是一个简单总线
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;  // 地址映射,空表示子节点地址与父节点地址相同
    
        // 所有片上外设节点都放在这里
        uart@40010000 { ... };
        i2c@40012000 { ... };
        gpio@50002000 { ... };
    };

2.3 驱动开发最常用的通用设备节点

1. 总线类节点
(1)UART串口节点
dts 复制代码
uart4: serial@40010000 {
    compatible = "st,stm32mp157-uart";
    reg = <0x40010000 0x400>;  // 寄存器基地址和长度
    interrupts = <52>;         // 中断号
    clocks = <&rcc UART4_CLK>; // 时钟
    status = "okay";           // 启用设备
};
(2)I2C总线节点
dts 复制代码
i2c1: i2c@40012000 {
    compatible = "st,stm32mp157-i2c";
    reg = <0x40012000 0x400>;
    interrupts = <31>;
    #address-cells = <1>;  // 子节点reg占1个cell(I2C从地址)
    #size-cells = <0>;     // 子节点reg没有长度部分
    clock-frequency = <100000>;  // I2C总线频率100kHz
    status = "okay";

    // I2C从设备节点
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;  // I2C从机地址0x50
    };
};
(3)SPI总线节点
dts 复制代码
spi1: spi@40013000 {
    compatible = "st,stm32mp157-spi";
    reg = <0x40013000 0x400>;
    interrupts = <32>;
    #address-cells = <1>;  // 子节点reg占1个cell(片选号)
    #size-cells = <0>;
    status = "okay";

    // SPI从设备节点
    flash@0 {
        compatible = "winbond,w25q64";
        reg = <0>;  // 片选号0
        spi-max-frequency = <10000000>;  // 最大SPI频率10MHz
    };
};
(4)GPIO控制器节点
dts 复制代码
gpioa: gpio@50002000 {
    compatible = "st,stm32mp157-gpio";
    reg = <0x50002000 0x400>;
    gpio-controller;  // 标识这是一个GPIO控制器
    #gpio-cells = <2>; // GPIO引用需要2个参数:引脚号+极性
    status = "okay";
};
2. 外设类节点
(1)LED节点
dts 复制代码
leds {
    compatible = "gpio-leds";

    blue_led: led-blue {
        label = "blue:user";  // LED名称
        gpios = <&gpioa 13 GPIO_ACTIVE_HIGH>;  // 使用GPIOA13,高电平点亮
        linux,default-trigger = "heartbeat";  // 默认心跳模式
    };
};
(2)按键节点
dts 复制代码
keys {
    compatible = "gpio-keys";

    key-user {
        label = "user-key";
        gpios = <&gpioc 13 GPIO_ACTIVE_LOW>;  // GPIOC13,低电平有效
        linux,code = <KEY_ENTER>;  // 按键码
    };
};

3. 属性(Property)全解:类型+核心属性+总线专用属性

属性是节点的核心,用于描述设备的具体硬件特性,采用键值对形式。

3.1 属性的5种基本类型

类型 语法 示例 用途
字符串类型 双引号""包裹 compatible = "st,stm32-uart"; 描述名称、兼容属性、状态
32位整数类型 尖括号<>包裹 reg = <0x12340000 0x1000>; 描述地址、长度、中断号
字节数组类型 方括号[]包裹 mac-address = [00 11 22 33 44 55]; 描述MAC地址、二进制数据
布尔类型 只有属性名,没有值 gpio-controller; 标识设备具有某种特性
字符串数组类型 多个字符串用逗号分隔 compatible = "st,stm32-uart", "arm,pl011-uart"; 多个兼容属性

3.2 核心属性(驱动开发每天都会用到)

这些属性是所有设备节点通用的,也是最重要的属性。

1. compatible(最重要,驱动匹配唯一依据)
  • 作用:设备和驱动之间的"配对暗号"

  • 格式"厂商,设备型号"

  • 匹配规则 :内核从左到右依次匹配驱动的of_device_id表,匹配到第一个就停止

  • 示例

    dts 复制代码
    compatible = "st,stm32mp157-uart", "arm,pl011-uart";
  • 注意 :必须和驱动代码中的字符串完全一致,一个字符都不能错,否则驱动永远不会执行probe函数

2. reg(地址资源描述)
  • 作用:描述设备的寄存器地址范围或总线地址

  • 格式 :由父节点的#address-cells#size-cells决定

    • 父节点#address-cells = <1>#size-cells = <1>:格式为<基地址 长度>
    • 父节点#address-cells = <2>#size-cells = <2>:格式为<addr_high addr_low len_high len_low>
  • 示例

    dts 复制代码
    // 内存映射设备
    reg = <0x40010000 0x400>;  // 基地址0x40010000,长度0x400
    
    // I2C设备(父节点#size-cells=0)
    reg = <0x50>;  // I2C从机地址0x50
    
    // SPI设备(父节点#size-cells=0)
    reg = <0>;  // SPI片选号0
3. status(设备状态控制)
  • 作用:控制设备是否启用

  • 有效值

    含义
    okay 设备正常启用
    disabled 设备禁用,内核会忽略该节点
    fail 设备存在但工作异常
  • 注意

    • 父节点status = "disabled"时,所有子节点都会被递归忽略
    • .dtsi文件中节点默认状态通常是disabled,在.dts文件中显式改为okay启用
4. interrupts(中断资源描述)
  • 作用:描述设备使用的中断号和触发方式

  • 格式<中断号 触发方式>

  • 常用触发方式

    • IRQ_TYPE_EDGE_RISING:上升沿触发
    • IRQ_TYPE_EDGE_FALLING:下降沿触发
    • IRQ_TYPE_LEVEL_HIGH:高电平触发
    • IRQ_TYPE_LEVEL_LOW:低电平触发
  • 示例

    dts 复制代码
    interrupts = <52 IRQ_TYPE_LEVEL_HIGH>;  // 中断号52,高电平触发
5. #address-cells#size-cells
  • 作用 :定义子节点reg属性的格式

  • #address-cells:用来描述子节点reg属性的地址表中用来描述首地址的元素所用字(cell)的数量,而每个字(cell)是一个无符号32位整形。

  • #size-cells:用来描述子节点reg属性的地址表中用来描述地址范围大小的元素所用字(cell)的数量,每个字(cell)是一个无符号32位整形。

  • 示例

    dts 复制代码
    soc {
        #address-cells = <1>;  // 子节点地址占1个cell
        #size-cells = <1>;     // 子节点长度占1个cell
    
        // 子节点reg格式:<addr len>
        uart@40010000 {
            reg = <0x40010000 0x400>;
        };
    };
6. ranges(地址映射)
  • 作用:定义子总线地址到父总线地址的映射关系

  • 格式<子地址 父地址 长度>

  • 空值ranges;:表示子节点地址与父节点地址完全相同

  • 示例

    dts 复制代码
    soc {
        ranges = <0 0x40000000 0x10000000>;
        // 子地址0映射到父地址0x40000000,长度0x10000000
    };

3.3 常用总线专用属性

1. I2C总线专用
  • clock-frequency:I2C总线频率,单位Hz
  • reg:I2C从机地址
2. SPI总线专用
  • spi-max-frequency:SPI设备最大工作频率,单位Hz
  • reg:SPI片选号
  • spi-cpol:时钟极性,1表示空闲时时钟为高电平
  • spi-cpha:时钟相位,1表示在第二个时钟沿采样
3. GPIO专用
  • gpio-controller:标识这是一个GPIO控制器
  • #gpio-cells:GPIO引用需要的参数个数,通常是2(引脚号+极性)
  • gpios:引用GPIO引脚,格式为<&gpio_controller 引脚号 极性>
    • GPIO_ACTIVE_HIGH:高电平有效
    • GPIO_ACTIVE_LOW:低电平有效
4. 中断控制器专用
  • interrupt-controller:标识这是一个中断控制器
  • #interrupt-cells:中断引用需要的参数个数,通常是2(中断号+触发方式)
  • interrupt-parent:指定该设备的中断控制器

4. 标签与节点引用:设备树复用的核心

设备树通过标签引用 实现代码复用,这是.dtsi头文件机制的基础。

4.1 标签(Label)

  • 在节点名前加标签名:,给节点起一个唯一的标识

  • 作用:方便在其他地方引用该节点

  • 示例

    dts 复制代码
    uart4: serial@40010000 {
        ...
    };

4.2 节点引用

  • 使用&标签名引用已经定义的节点

  • 主要用途:在.dts文件中修改.dtsi文件中定义的节点属性

  • 示例

    dts 复制代码
    // 在stm32mp157.dtsi中定义了uart4节点,默认状态是disabled
    uart4: serial@40010000 {
        compatible = "st,stm32mp157-uart";
        reg = <0x40010000 0x400>;
        interrupts = <52>;
        status = "disabled";
    };
    
    // 在myboard.dts中引用uart4节点,修改为启用状态
    &uart4 {
        status = "okay";
    };

5. 常见错误与注意事项

  1. 节点名不规范:使用大写字母、下划线或特殊字符,导致内核解析失败
  2. 缺少@和单元地址:同类型的多个设备无法区分,驱动匹配混乱
  3. 属性值类型错误:应该用尖括号的用了双引号,应该用双引号的用了尖括号
  4. compatible属性不匹配:设备树和驱动的compatible字符串不一致,导致probe不执行
  5. 忘记以空结尾of_device_id表和数组类型的属性必须以空结尾
  6. 父节点禁用导致子节点失效 :父节点status = "disabled"时,所有子节点都会被忽略

6. 总结:驱动开发必背节点与属性

必须掌握的节点

  • 标准节点://aliases/chosen/memory/cpus/soc
  • 常用设备节点:uarti2cspigpioledskeys

必须掌握的属性

  • 核心属性:compatibleregstatusinterrupts#address-cells#size-cells
  • 总线属性:rangesclock-frequencyspi-max-frequency
  • GPIO属性:gpiosgpio-controller#gpio-cells
相关推荐
Yeats_Liao5 小时前
物联网接入层技术剖析(三):epoll在JVM中的映射
java·linux·jvm·人工智能·物联网
悠悠121385 小时前
Jenkins + Ansible 集成实战:把配置管理焊进流水线里
运维·ansible·jenkins
日取其半万世不竭5 小时前
用 n8n 搭建自己的自动化工作流平台
运维·自动化
小贾要学习5 小时前
【Linux】基于自定义TCP协议的日期计算器
linux·网络·c++·网络协议·tcp/ip
IT界的老黄牛6 小时前
从 MQ 积压追到事件总线:诊断 4K 线程吃光 7G 内存的实战
java·运维·rocketmq
世微 如初6 小时前
基于AP5160的大功率LED恒流驱动设计:原理分析与外围计算
驱动开发·单片机·芯片
2501_920047036 小时前
iptables防火墙
linux·运维·网络安全
带土16 小时前
7. 线程编程(线程概念和创建)
linux
华清远见IT开放实验室6 小时前
硬核根基,智能载体:华清远见嵌入式“硬件+仿真+课程+师资”产教融合与实践教学方案
linux·人工智能·stm32·物联网·嵌入式·虚拟仿真