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) clean4. 调试与问题排查
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 调试技巧
设备树检查:
# 查看解析后的设备树 ls /proc/device-tree/ # 检查特定节点属性 hexdump -C /proc/device-tree/leds/reg驱动调试:
// 在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>; /* 明确指定低电平有效 */这比在驱动代码中硬编码更易于维护,也符合设备树的设计哲学。