Linux设备树与platform驱动开发实战指南
2026/7/24 11:50:17 网站建设 项目流程

1. 设备树与platform驱动概述

在Linux内核开发中,设备树(Device Tree)和platform驱动是嵌入式系统开发的核心技术组合。这套机制彻底改变了传统嵌入式Linux的设备管理方式,让驱动开发从硬编码走向了声明式配置。

我最早接触这套技术是在2015年开发基于i.MX6的工控主板时。当时内核刚从board file过渡到设备树,最大的感受就是:再也不用为了改个GPIO引脚而重新编译内核了。设备树将硬件描述从内核代码中抽离出来,而platform驱动则提供了标准的对接方式。

2. 设备树驱动开发基础

2.1 设备树语法精要

设备树源文件(.dts)采用类似JSON的结构化语法,核心是节点(node)和属性(property)。一个典型的设备节点如下:

/ { compatible = "acme,myboard"; gpio_keys { compatible = "gpio-keys"; #address-cells = <1>; #size-cells = <0>; power { label = "Power Button"; gpios = <&gpio1 18 GPIO_ACTIVE_LOW>; linux,code = <116>; /* KEY_POWER */ }; }; };

关键属性说明:

  • compatible:驱动匹配的"身份证",格式为"厂商,设备型号"
  • reg:寄存器地址和长度
  • interrupts:中断号和相关配置
  • gpios:GPIO管脚配置

经验:设备树节点命名建议采用功能描述而非芯片型号,比如用"i2c-touchscreen"而非"ft5x06"

2.2 platform驱动框架解析

platform驱动的核心结构体是platform_driver,典型定义如下:

static struct platform_driver my_driver = { .probe = my_probe, .remove = my_remove, .driver = { .name = "my-device", .of_match_table = my_of_match, .owner = THIS_MODULE, }, };

其中关键元素:

  • probe:设备匹配成功后执行的初始化函数
  • remove:设备移除或驱动卸载时的清理函数
  • of_match_table:设备树匹配表,用于关联设备树节点

匹配表通常这样定义:

static const struct of_device_id my_of_match[] = { { .compatible = "acme,mydevice" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, my_of_match);

3. 完整驱动开发实战

3.1 设备树节点编写

以开发一个LED控制器为例,首先在设备树中添加节点:

/ { leds { compatible = "my,led-controller"; reg = <0x12345000 0x1000>; #address-cells = <1>; #size-cells = <0>; led0: led@0 { reg = <0>; label = "system-status"; default-state = "on"; }; }; };

3.2 驱动代码实现

完整的platform驱动示例:

#include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #define DRV_NAME "my_led" struct my_led_data { void __iomem *regs; struct gpio_desc *gpio; }; static int my_led_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct my_led_data *data; struct resource *res; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; /* 获取寄存器资源 */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); >obj-m := my_led.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean

4. 调试与问题排查

4.1 常见问题速查表

问题现象可能原因解决方案
probe函数未执行1. compatible不匹配
2. 设备树未加载
1. 检查dts和驱动中的compatible
2. 确认设备树已编译并加载
资源获取失败1. 设备树reg属性错误
2. 资源已被占用
1. 检查设备树地址范围
2. 查看/proc/iomem
模块加载失败1. 依赖符号未导出
2. 版本不匹配
1. 检查Module.symvers
2. 确认内核版本一致

4.2 调试技巧

  1. 设备树检查

    # 查看解析后的设备树 ls /proc/device-tree/ # 检查特定节点属性 hexdump -C /proc/device-tree/leds/reg
  2. 驱动调试

    // 在probe函数中添加调试打印 dev_dbg(&pdev->dev, "Register map: %p\n",># 查看已注册的平台设备 ls /sys/bus/platform/devices/ # 查看特定设备的资源 cat /sys/bus/platform/devices/leds/resources

5. 高级技巧与优化

5.1 多设备支持

对于支持多个实例的驱动,可以通过platform_get_id()获取设备ID:

static int my_led_probe(struct platform_device *pdev) { int id = platform_get_id(pdev); // 使用id区分不同实例 }

设备树中需要添加reg属性:

leds { compatible = "my,led-controller"; #address-cells = <1>; #size-cells = <0>; led@0 { reg = <0>; // ... }; led@1 { reg = <1>; // ... }; };

5.2 电源管理

实现基本的电源管理回调:

static int my_led_suspend(struct device *dev) { struct my_led_data *data = dev_get_drvdata(dev); gpiod_set_value(data->gpio, 0); return 0; } static int my_led_resume(struct device *dev) { struct my_led_data *data = dev_get_drvdata(dev); gpiod_set_value(data->gpio, 1); return 0; } static const struct dev_pm_ops my_led_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(my_led_suspend, my_led_resume) }; static struct platform_driver my_led_driver = { .driver = { .pm = &my_led_pm_ops, // ... }, // ... };

5.3 用户空间接口

通过sysfs提供用户空间控制:

static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct my_led_data *data = dev_get_drvdata(dev); return sprintf(buf, "%d\n", gpiod_get_value(data->gpio)); } static ssize_t brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct my_led_data *data = dev_get_drvdata(dev); int value; if (kstrtoint(buf, 10, &value)) return -EINVAL; gpiod_set_value(data->gpio, value); return count; } static DEVICE_ATTR_RW(brightness); static int my_led_probe(struct platform_device *pdev) { // ... device_create_file(&pdev->dev, &dev_attr_brightness); // ... }

6. 实际开发经验分享

在RK3562平台上移植驱动时,发现设备树节点未被识别。经过排查发现,新内核要求compatible字符串必须严格匹配,旧驱动中的"my-led-controller"与新设备树中的"my,led-controller"的逗号差异导致匹配失败。

另一个常见问题是资源冲突。曾遇到I2C设备无法正常工作的情况,最终发现是设备树中定义的I2C地址与另一个驱动冲突。通过i2cdetect工具可以快速排查这类问题:

i2cdetect -y 0 # 检查I2C0总线上的设备

对于GPIO使用,建议在设备树中明确指定激活电平:

gpios = <&gpio1 12 GPIO_ACTIVE_LOW>; /* 明确指定低电平有效 */

这比在驱动代码中硬编码更易于维护,也符合设备树的设计哲学。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询