Linux设备树开发实战:从硬件描述到驱动匹配的完整链路

一、顶级架构一句话总结

设备树(DTS) → 编译(DTB) → 内核解析 → 驱动匹配 → 硬件初始化

设备树实现了硬件描述与驱动代码分离,同一份驱动代码可适配不同硬件平台,这是嵌入式Linux开发的工业标准方案


二、为什么需要设备树?

传统方式的痛点

在传统嵌入式Linux开发中,硬件信息(如寄存器地址、中断号、外设连接关系)直接硬编码在驱动程序中,导致:

问题影响
驱动与硬件强耦合更换硬件平台需修改驱动源码
代码可移植性差同一驱动无法适配不同板卡
维护成本高硬件变更需要重新编译驱动
代码冗余大量平台相关的宏定义

设备树的解决方案

传统方式:
驱动代码 + 硬件信息(硬编码)= 不可移植

设备树方式:
驱动代码(通用)+ 设备树(硬件描述)= 高度可移植

核心思想:硬件信息从驱动代码中剥离,通过设备树文件独立描述。


三、设备树核心概念(一句话秒懂)

  • DTS(Device Tree Source):设备树源文件,人类可读的文本格式
  • DTC(Device Tree Compiler):设备树编译器,将DTS编译为DTB
  • DTB(Device Tree Blob):编译后的二进制设备树,内核可解析
  • DTSI:设备树头文件,包含公共定义,可被多个DTS包含
  • 节点(Node):设备树中的设备描述单元
  • 属性(Property):节点的具体配置信息

四、设备树文件层次结构

架构层次:

soc.dtsi                    // SoC级公共定义
    ├── CPU信息
    ├── 内存控制器
    ├── 总线架构
    └── 内置外设

board.dts                   // 板级定义
    ├── #include "soc.dtsi"
    ├── 板载设备
    ├── GPIO配置
    └── 引脚复用

五、设备树语法详解

1. 基本节点结构

/ {
    // 根节点
    
    node_name@address {
        // 节点名@设备地址
        compatible = "vendor,device";  // 兼容性字符串
        reg = <0x1000 0x100>;          // 寄存器地址和大小
        status = "okay";               // 设备状态
    };
};

2. 常用属性说明

属性说明示例
compatible兼容性字符串,用于驱动匹配“ti,omap4-i2c”
reg寄存器地址和大小<0x44e0b000 0x1000>
interrupts中断号和类型<0 24 4>
status设备状态“okay”/“disabled”
clocks时钟引用<&clk_periph>
pinctrl-*引脚复用配置pinctrl-names = “default”
gpio-*GPIO配置gpios = <&gpio0 10 0>

3. 实际案例:LED设备节点

/ {
    leds {
        compatible = "gpio-leds";
        
        led0 {
            label = "heartbeat";
            gpios = <&gpio1 21 GPIO_ACTIVE_LOW>;
            linux,default-trigger = "heartbeat";
            default-state = "off";
        };
        
        led1 {
            label = "status";
            gpios = <&gpio1 22 GPIO_ACTIVE_LOW>;
            default-state = "on";
        };
    };
};

六、设备树编译流程

1. 编译命令

# 编译单个DTS文件
dtc -I dts -O dtb -o board.dtb board.dts

# 使用内核构建系统
make dtbs ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

2. 反编译DTB

# 将DTB反编译为DTS(调试用)
dtc -I dtb -O dts -o board.dts board.dtb

3. 编译流程图

.dts源文件
    ↓ (dtc编译器)
.dtb二进制文件
    ↓ (Bootloader传递)
内核解析
    ↓
device_node结构体
    ↓
驱动匹配

七、驱动与设备树匹配机制

1. of_match_table匹配表

static const struct of_device_id my_driver_match[] = {
    { .compatible = "vendor,mydevice", },
    { .compatible = "vendor,mydevice-v2", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_match);

2. platform_driver注册

static struct platform_driver my_driver = {
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_device",
        .of_match_table = my_driver_match,
    },
};

module_platform_driver(my_driver);

3. 匹配流程

设备树节点 compatible = "vendor,mydevice"
           ↓
驱动 of_match_table 包含 "vendor,mydevice"
           ↓
匹配成功 → 调用 probe() 函数
           ↓
驱动初始化完成

八、驱动中获取设备树信息

1. 获取设备节点

// 方法1:通过compatible查找
struct device_node *np = of_find_compatible_node(NULL, NULL, "vendor,mydevice");

// 方法2:通过路径查找
struct device_node *np = of_find_node_by_path("/soc/mydevice@1000");

// 方法3:在probe中直接获取(推荐)
static int my_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    // ...
}

2. 读取属性值

// 读取reg属性
u32 reg[2];
of_property_read_u32_array(np, "reg", reg, 2);

// 读取字符串属性
const char *name;
of_property_read_string(np, "label", &name);

// 读取GPIO
int gpio = of_get_named_gpio(np, "gpios", 0);

// 读取中断号
int irq = irq_of_parse_and_map(np, 0);

// 判断属性是否存在
if (of_property_read_bool(np, "my-custom-property")) {
    // 属性存在
}

3. 完整驱动示例

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio.h>

struct my_device_data {
    int gpio;
    const char *name;
};

static int my_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    struct my_device_data *data;
    
    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;
    
    // 从设备树获取GPIO
    data->gpio = of_get_named_gpio(np, "led-gpio", 0);
    if (!gpio_is_valid(data->gpio))
        return -EINVAL;
    
    // 获取名称
    of_property_read_string(np, "label", &data->name);
    
    // 申请GPIO
    devm_gpio_request(&pdev->dev, data->gpio, data->name);
    
    // 设置为输出
    gpio_direction_output(data->gpio, 1);
    
    platform_set_drvdata(pdev, data);
    
    dev_info(&pdev->dev, "Device %s initialized\n", data->name);
    return 0;
}

static int my_remove(struct platform_device *pdev)
{
    struct my_device_data *data = platform_get_drvdata(pdev);
    gpio_set_value(data->gpio, 0);
    dev_info(&pdev->dev, "Device removed\n");
    return 0;
}

static const struct of_device_id my_match[] = {
    { .compatible = "mycompany,myled", },
    { }
};
MODULE_DEVICE_TABLE(of, my_match);

static struct platform_driver my_driver = {
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_led_driver",
        .of_match_table = my_match,
    },
};
module_platform_driver(my_driver);

MODULE_AUTHOR("Driver Developer");
MODULE_LICENSE("GPL");

九、设备树调试技巧

1. 查看内核识别的设备树

# 查看设备树节点
ls /proc/device-tree/

# 查看特定节点
cat /proc/device-tree/mydevice@1000/compatible

# 查看设备树完整信息
dtc -I fs /proc/device-tree

2. 内核日志调试

# 查看设备树解析日志
dmesg | grep -i "device tree"
dmesg | grep -i "of:"

# 查看驱动匹配日志
dmesg | grep -i "probe"

3. 常见问题排查

问题可能原因解决方案
驱动不加载compatible不匹配检查驱动和DTS的compatible字符串
获取GPIO失败GPIO未正确配置检查pinctrl配置和GPIO编号
中断不工作中断属性缺失添加interrupts和interrupt-parent
设备状态异常status属性错误设置status = “okay”

十、设备树最佳实践

1. 命名规范

// 推荐:使用厂商前缀
compatible = "ti,omap4-i2c";
compatible = "nxp,imx6-uart";

// 推荐:节点名使用通用名称
i2c@44e0b000 { ... }
serial@48020000 { ... }

// 不推荐:节点名包含厂商信息
ti-i2c@44e0b000 { ... }

2. 层次结构设计

/ {
    // SoC级定义放在dtsi
    soc {
        i2c0: i2c@44e0b000 {
            compatible = "ti,omap4-i2c";
            // 公共配置
        };
    };
    
    // 板级定义放在dts
    &i2c0 {
        status = "okay";
        clock-frequency = <400000>;
        
        // 板载设备
        eeprom@50 {
            compatible = "atmel,24c02";
            reg = <0x50>;
        };
    };
};

3. 使用引用简化代码

// 定义标签
gpio1: gpio@4804c000 { ... };

// 使用引用
gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;

十一、终极总结

设备树 = 硬件描述与驱动解耦的最优方案

  • 核心价值:驱动代码可移植、硬件配置独立维护
  • 关键机制:compatible字符串实现驱动与设备匹配
  • 开发流程:DTS编写 → DTB编译 → 内核解析 → 驱动匹配
  • 调试手段:/proc/device-tree、dmesg、dtc工具

掌握设备树,是嵌入式Linux驱动开发的必备技能!

欢迎关注微信公众号:LinuxRos

在这里插入图片描述

和大家一起聊聊Linux驱动、应用开发、嵌入式软件、AI与机器人相关干货,希望能给大家带来一点点帮助与启发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值