
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中断号和相关配置gpiosGPIO管脚配置经验设备树节点命名建议采用功能描述而非芯片型号比如用i2c-touchscreen而非ft5x062.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: led0 { 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和驱动中的compatible2. 确认设备树已编译并加载资源获取失败1. 设备树reg属性错误2. 资源已被占用1. 检查设备树地址范围2. 查看/proc/iomem模块加载失败1. 依赖符号未导出2. 版本不匹配1. 检查Module.symvers2. 确认内核版本一致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/resources5. 高级技巧与优化5.1 多设备支持对于支持多个实例的驱动可以通过platform_get_id()获取设备IDstatic 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; led0 { reg 0; // ... }; led1 { 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; /* 明确指定低电平有效 */这比在驱动代码中硬编码更易于维护也符合设备树的设计哲学。