1. 平台驱动与设备树的协同设计原理
我第一次接触platform_driver_register时,完全不明白为什么要绕这么大一个圈子。直到在项目中遇到一个需要支持多种硬件变体的需求,才真正体会到这种设计的美妙之处。现代嵌入式Linux驱动开发中,平台驱动和设备树的配合就像一对默契的舞伴,共同演绎着硬件资源管理的优雅舞蹈。
传统驱动开发方式需要手动注册每个硬件设备,就像给每个客人单独安排座位。而平台驱动+设备树的组合则像是自助餐厅 - 内核根据设备树描述自动为硬件设备分配合适的驱动。这种变化带来的最大好处是:驱动代码不再需要硬编码硬件资源信息,同一份驱动可以适配不同硬件配置。
设备树就像一份硬件地图,详细记录了SoC上所有外设的位置和特性。而平台驱动则像是一位导游,拿着这份地图去寻找自己负责的景点。当两者匹配成功时,probe函数就会被调用,完成硬件的初始化工作。这种机制特别适合现代ARM架构的SoC,因为它们通常集成了大量外设,且不同厂商的配置差异很大。
2. platform_driver_register的深度解析
让我们拆解一个典型的平台驱动注册过程。以LED控制驱动为例,首先需要定义platform_driver结构体,这是整个驱动的核心。我经常把这个结构体比作驱动器的"名片",上面写着驱动名称、兼容性列表和关键操作函数。
static struct platform_driver led_driver = { .probe = led_probe, .remove = led_remove, .driver = { .name = "gpio-led", .of_match_table = of_match_ptr(led_of_match), }, };其中of_match_table是连接设备树的关键。它定义了驱动能兼容的设备树节点,就像一把钥匙对应一把锁。当设备树中出现compatible属性匹配的节点时,内核就会调用对应的probe函数。
static const struct of_device_id led_of_match[] = { { .compatible = "gpio,led" }, {}, };在实际项目中,我发现一个常见误区是开发者喜欢在驱动初始化函数中直接操作硬件。正确的做法应该是把这些操作放到probe函数中,因为只有probe被调用时才说明硬件确实存在。这种延迟初始化的设计大大提高了驱动的灵活性。
3. 设备树节点的详细设计
设备树源文件(.dts)的编写是协同设计的关键环节。以我们之前提到的LED驱动为例,对应的设备树节点应该这样定义:
leds { compatible = "gpio-leds"; led1 { label = "system-led"; gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>; default-state = "on"; }; };这里有几个要点需要注意:
- compatible属性必须与驱动中的定义完全一致
- 硬件资源(如GPIO编号)直接在节点中指定
- 可以添加自定义属性来配置设备行为
我在调试时经常使用的一个技巧是查看/sys/firmware/devicetree/base下的文件结构,这里反映了内核解析后的设备树信息。另一个有用的命令是of_node_full_name(),可以在驱动中打印完整节点路径帮助调试。
对于复杂的硬件配置,设备树还支持引用其他节点。比如一个I2C设备可以这样描述:
i2c1: i2c@40005400 { compatible = "st,stm32-i2c"; reg = <0x40005400 0x400>; interrupts = <32>; clocks = <&rcc 0 32>; eeprom: eeprom@50 { compatible = "atmel,24c256"; reg = <0x50>; pagesize = <64>; }; };4. 平台驱动与设备树的匹配机制
内核中的匹配过程其实相当精妙。当platform_driver_register被调用时,内核会遍历所有设备树节点,寻找compatible属性匹配的项。这个过程是由of_platform_populate()函数完成的。
匹配成功后,内核会创建一个platform_device结构体,并将其与platform_driver绑定。这时probe函数被调用,传入的platform_device参数包含了完整的设备树节点信息。我们可以通过这些API获取设备信息:
int value; struct device_node *np = pdev->dev.of_node; of_property_read_u32(np, "sample-rate", &value); const char *name = of_get_property(np, "label", NULL);在调试匹配问题时,我通常会检查以下几个地方:
- dmesg输出中是否有OF: [device] matches [driver]的日志
- /sys/bus/platform/devices下是否出现了对应的设备
- /sys/bus/platform/drivers下驱动是否成功绑定
一个常见的问题是设备树节点定义正确但驱动仍未加载,这通常是因为:
- compatible字符串拼写不一致
- 驱动模块未正确编译进内核
- 设备树blob未正确加载
5. 资源管理与传统方式的对比
设备树机制最显著的优势是硬件资源的集中管理。传统方式需要在驱动代码中硬编码寄存器地址、中断号等信息:
#define GPIO_BASE 0x40020000 #define IRQ_NUM 42 static struct resource res[] = { { .start = GPIO_BASE, .end = GPIO_BASE + 0x3FF, .flags = IORESOURCE_MEM, }, { .start = IRQ_NUM, .end = IRQ_NUM, .flags = IORESOURCE_IRQ, }, };而设备树方式将这些信息移到了.dts文件中:
gpio0: gpio@40020000 { compatible = "vendor,gpio"; reg = <0x40020000 0x400>; interrupts = <42>; };驱动中可以通过标准API获取这些资源:
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0);这种变化带来了几个实际好处:
- 同一驱动可以支持不同硬件配置
- 硬件变更不需要重新编译驱动
- 系统资源冲突可以在早期被发现
6. 实际项目中的经验分享
在最近的一个工业控制器项目中,我们需要支持三种不同的硬件版本。使用平台驱动+设备树的设计,我们只需要维护一个驱动代码库,配合不同的设备树文件即可。
一个实用的技巧是使用设备树覆盖(dtbo)机制。当系统启动后,可以通过加载不同的覆盖文件来动态改变硬件配置:
# 加载设备树覆盖 echo /lib/firmware/override.dtbo > /sys/kernel/config/device-tree/overlays/load对于复杂的驱动,我建议将设备树绑定文档(doc/device-tree/bindings)作为设计参考。这些文档详细说明了每个兼容字符串要求的属性和格式。
调试时,这些工具特别有用:
- dtc:编译和反编译设备树
- fdtdump:查看dtb文件内容
- /proc/device-tree:运行时查看设备树
7. 常见问题与解决方案
新手在使用平台驱动时最常遇到的几个坑:
- probe函数未被调用
- 检查compatible字符串是否完全匹配
- 确认设备树节点状态是否为"okay"
- 查看驱动是否真的注册成功
- 资源获取失败
- 检查设备树中reg/interrupts属性格式
- 确认资源索引是否正确
- 验证地址范围是否合理
- 驱动与设备绑定错误
- 检查driver_override属性
- 确认没有其他驱动匹配同一设备
- 查看/sys/bus/platform/drivers下的绑定状态
我遇到过一个棘手的问题:驱动在开发板上工作正常,但在量产版本上失败。最终发现是设备树中GPIO bank定义错误,导致引脚映射不正确。这个教训让我养成了在probe函数中添加硬件验证步骤的习惯:
static int led_probe(struct platform_device *pdev) { // 验证GPIO是否可用 if (!gpio_is_valid(led_gpio)) { dev_err(&pdev->dev, "Invalid GPIO\n"); return -EINVAL; } // 检查硬件版本 if (of_device_is_compatible(pdev->dev.of_node, "vendor,led-v2")) { // 新版硬件特殊处理 } }8. 进阶技巧与性能优化
当系统中有大量同类设备时,平台驱动的初始化顺序可能影响启动时间。这时可以使用initcall的优先级:
// 较早初始化 module_init(led_driver_init); early_initcall(led_driver_init); // 较晚初始化 late_initcall(led_driver_init);对于热插拔设备,可以实现platform_driver的notifier接口:
static int led_notifier(struct notifier_block *nb, unsigned long action, void *data) { struct platform_device *pdev = to_platform_device(data); switch (action) { case BUS_NOTIFY_BOUND_DRIVER: // 驱动绑定事件 break; } return NOTIFY_OK; }在资源访问方面,使用devm_系列API可以简化资源管理:
// 自动释放的资源申请 res = devm_ioremap_resource(&pdev->dev, mem); irq = devm_request_irq(&pdev->dev, irq_num, handler, flags, name, dev);对于高性能场景,可以考虑以下几点优化:
- 将频繁访问的寄存器地址缓存起来
- 使用中断代替轮询
- 合理设置DMA缓冲区
- 利用设备树预定义配置参数
在最近的一个项目中,通过分析设备树节点属性,我们实现了驱动参数的动态调整:
static void configure_performance(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; u32 threshold; if (!of_property_read_u32(np, "performance-threshold", &threshold)) { if (system_performance > threshold) { enable_high_speed_mode(); } } }9. 测试与验证方法
完整的驱动开发离不开充分的测试。我通常会建立以下几个测试场景:
- 设备树匹配测试
- 修改compatible字符串验证驱动是否拒绝加载
- 检查/sys/devices/platform下的设备节点
- 验证probe/remove的调用顺序
- 资源访问测试
- 故意配置错误的寄存器地址
- 测试中断处理函数的稳定性
- 验证时钟和电源管理
- 边界条件测试
- 传入无效的设备树属性值
- 模拟资源申请失败的情况
- 测试并发访问场景
一个实用的测试方法是使用内核模块参数动态改变行为:
static bool debug_mode; module_param(debug_mode, bool, 0644); static int led_probe(struct platform_device *pdev) { if (debug_mode) { enable_debug_features(); } }在自动化测试方面,可以使用kselftest框架:
#include <kselftest.h> static int test_case_1(void) { // 测试逻辑 return KSFT_PASS; } static struct kselftest_case cases[] = { KSFT_CASE(test_case_1), {} }; static struct kselftest_suite suite = { .name = "led_driver_tests", .test_cases = cases, }; kselftest_register(&suite);10. 与用户空间交互的最佳实践
平台驱动通常需要与用户空间交互,常见的几种方式:
- sysfs接口
static ssize_t show_value(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", current_value); } static DEVICE_ATTR(value, 0444, show_value, NULL); static int led_probe(struct platform_device *pdev) { device_create_file(&pdev->dev, &dev_attr_value); }- debugfs接口
static struct dentry *debug_dir; static int debug_show(struct seq_file *m, void *v) { seq_printf(m, "Driver status:\n"); seq_printf(m, " interrupts: %lu\n", stats.interrupts); return 0; } static int led_probe(struct platform_device *pdev) { debug_dir = debugfs_create_dir("led_driver", NULL); debugfs_create_file("status", 0444, debug_dir, NULL, &debug_fops); }- ioctl控制接口
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case LED_SET_BRIGHTNESS: // 设置亮度 break; } return 0; } static const struct file_operations led_fops = { .unlocked_ioctl = led_ioctl, }; static int led_probe(struct platform_device *pdev) { misc_register(&led_miscdev); }在实际项目中,我发现sysfs适合简单的状态展示和控制,而复杂的交互应该使用ioctl或netlink。对于频繁的数据传输,可以考虑实现mmap接口:
static int led_mmap(struct file *file, struct vm_area_struct *vma) { unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; if (offset >= REGISTER_SIZE) return -EINVAL; return io_remap_pfn_range(vma, vma->vm_start, (register_phys + offset) >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot); }11. 电源管理集成
现代嵌入式设备对功耗敏感,平台驱动需要正确实现电源管理接口:
static int led_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); // 保存状态并进入低功耗模式 return 0; } static int led_resume(struct device *dev) { // 恢复硬件状态 return 0; } static const struct dev_pm_ops led_pm_ops = { .suspend = led_suspend, .resume = led_resume, .poweroff = led_suspend, .restore = led_resume, }; static struct platform_driver led_driver = { .driver = { .pm = &led_pm_ops, }, };设备树中可以定义电源管理相关属性:
led-controller { compatible = "vendor,led"; power-domains = <&power_domain 0>; wakeup-source; };在驱动中可以通过这些API访问电源管理功能:
// 获取/使能电源域 struct device *dev = &pdev->dev; struct device_link *link = device_link_add(dev, pd_dev); // 处理唤醒事件 if (device_may_wakeup(dev)) { enable_irq_wake(irq); }12. 多核处理与并发控制
在SMP系统中,平台驱动需要考虑多核并发访问。常用的同步机制包括:
- 自旋锁:适用于短时间锁定
static DEFINE_SPINLOCK(led_lock); spin_lock(&led_lock); // 临界区 spin_unlock(&led_lock);- 互斥锁:适用于可能休眠的场景
static DEFINE_MUTEX(led_mutex); mutex_lock(&led_mutex); // 临界区 mutex_unlock(&led_mutex);- 原子操作:简单计数器
static atomic_t led_count = ATOMIC_INIT(0); atomic_inc(&led_count); int count = atomic_read(&led_count);设备树可以描述多核相关的硬件特性:
led-controller { compatible = "vendor,led"; reg = <0x40000000 0x1000>; interrupts = <0 42 4>; interrupt-affinity = <&cpu0>, <&cpu1>; };在中断处理中,可以使用IRQF_PERCPU标志:
ret = request_percpu_irq(irq, handler, "led", percpu_dev);对于DMA操作,需要处理缓存一致性问题:
void *buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL); // 使用DMA缓冲区 dma_free_coherent(dev, size, buf, dma_handle);13. 调试与性能分析技巧
调试平台驱动时,这些工具和技术特别有用:
- 动态调试
// 在代码中添加 pr_debug("Register value: %08x\n", readl(reg)); // 运行时启用 echo 'file led_driver.c +p' > /sys/kernel/debug/dynamic_debug/control- ftrace跟踪
echo 1 > /sys/kernel/debug/tracing/events/platform/enable cat /sys/kernel/debug/tracing/trace_pipe- 性能分析
#include <linux/sched/clock.h> u64 start = local_clock(); // 要测量的代码 u64 duration = local_clock() - start;设备树可以包含调试辅助信息:
led-controller { compatible = "vendor,led"; debug-level = <3>; test-mode; };在驱动中可以通过sysfs暴露调试接口:
static int debug_level = 1; static ssize_t debug_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", debug_level); } static ssize_t debug_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { sscanf(buf, "%d", &debug_level); return count; } static DEVICE_ATTR_RW(debug);14. 驱动模块化设计
对于复杂的平台驱动,建议采用模块化设计:
- 核心功能放在平台驱动中
- 硬件相关部分分离到单独模块
- 使用内核模块依赖机制
// 核心模块 int register_led_controller(struct device *dev, const struct led_ops *ops); // 硬件特定模块 static const struct led_ops hw_led_ops = { .set = hw_led_set, .get = hw_led_get, }; static int hw_led_probe(struct platform_device *pdev) { return register_led_controller(&pdev->dev, &hw_led_ops); }设备树可以描述模块依赖关系:
led-controller { compatible = "vendor,led"; controller-type = "pwm"; pwm-supply = <&pwm_power>; };在Makefile中管理模块依赖:
obj-$(CONFIG_LED_CORE) += led-core.o obj-$(CONFIG_LED_PWM) += led-pwm.o led-pwm-y := pwm-driver.o pwm-hal.o15. 实际案例:GPIO LED控制器
让我们看一个完整的GPIO LED控制器实现。设备树节点:
led-controller { compatible = "vendor,gpio-led"; #address-cells = <1>; #size-cells = <0>; led@0 { reg = <0>; gpios = <&gpio0 12 0>; default-state = "off"; }; led@1 { reg = <1>; gpios = <&gpio0 13 0>; default-state = "blink"; }; };驱动实现关键部分:
struct led_priv { struct gpio_desc *gpio; struct timer_list timer; }; static void led_timer_callback(struct timer_list *t) { struct led_priv *priv = from_timer(priv, t, timer); gpiod_set_value(priv->gpio, !gpiod_get_value(priv->gpio)); mod_timer(&priv->timer, jiffies + msecs_to_jiffies(500)); } static int led_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct led_priv *priv; enum of_gpio_flags flags; int ret, gpio; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); priv->gpio = devm_gpiod_get(dev, NULL, GPIOD_OUT_LOW); if (of_property_read_bool(np, "default-state")) { const char *state; of_property_read_string(np, "default-state", &state); if (!strcmp(state, "blink")) { timer_setup(&priv->timer, led_timer_callback, 0); mod_timer(&priv->timer, jiffies + msecs_to_jiffies(500)); } } platform_set_drvdata(pdev, priv); return 0; }这个实现展示了几个关键点:
- 使用gpiod接口安全访问GPIO
- 解析设备树属性配置LED行为
- 使用定时器实现闪烁功能
- 正确的资源管理(devm_系列函数)
16. 设备树覆盖与动态配置
在某些场景下,我们需要动态修改设备树配置。内核提供了设备树覆盖机制:
# 创建覆盖文件 dtc -@ -I dts -O dtb -o led-overlay.dtbo led-overlay.dts # 加载覆盖 mkdir /config/device-tree/overlays/led cat led-overlay.dtbo > /config/device-tree/overlays/led/dtbo覆盖文件示例(led-overlay.dts):
/dts-v1/; /plugin/; &led_controller { new-led { gpios = <&gpio0 14 0>; default-state = "on"; }; };在驱动中可以检测覆盖加载:
static int led_notifier_call(struct notifier_block *nb, unsigned long action, void *data) { struct of_overlay_notify_data *nd = data; if (action == OF_OVERLAY_POST_APPLY) { // 重新初始化受影响的设备 } return NOTIFY_OK; } static struct notifier_block led_overlay_nb = { .notifier_call = led_notifier_call, }; static int __init led_init(void) { of_overlay_notifier_register(&led_overlay_nb); }17. 跨平台兼容性设计
为了让驱动支持多种硬件平台,可以采用这些方法:
- 使用兼容性列表
static const struct of_device_id led_of_match[] = { { .compatible = "vendor,led-v1" }, { .compatible = "vendor,led-v2" }, {}, };- 平台数据检测
static int led_probe(struct platform_device *pdev) { const struct of_device_id *match; match = of_match_device(led_of_match, &pdev->dev); if (of_device_is_compatible(pdev->dev.of_node, "vendor,led-v2")) { // 新版硬件特殊处理 } }- 硬件抽象层
struct led_hw_ops { int (*init)(struct platform_device *pdev); void (*set)(struct platform_device *pdev, int value); }; static const struct led_hw_ops v1_ops = { .init = v1_led_init, .set = v1_led_set, }; static int led_probe(struct platform_device *pdev) { const struct led_hw_ops *ops; if (of_device_is_compatible(np, "vendor,led-v1")) ops = &v1_ops; else ops = &v2_ops; ops->init(pdev); }设备树可以描述硬件变体:
led-controller { compatible = "vendor,led", "vendor,led-v2"; vendor,version = <2>; };18. 时钟与电源管理集成
现代SoC通常有复杂的时钟和电源架构。设备树可以描述这些关系:
led-controller { compatible = "vendor,led"; clocks = <&clkctrl 5>; clock-names = "pwm"; power-domains = <&power 3>; wakeup-source; };驱动中处理时钟和电源:
static int led_probe(struct platform_device *pdev) { struct clk *clk; clk = devm_clk_get(&pdev->dev, "pwm"); clk_prepare_enable(clk); // 电源管理 pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); } static int led_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); // 保存状态并关闭时钟 return 0; }对于性能敏感的应用,可以动态调整时钟频率:
static int set_led_frequency(struct platform_device *pdev, u32 freq) { struct clk *clk = platform_get_drvdata(pdev); clk_set_rate(clk, freq); return clk_get_rate(clk); }19. 中断处理最佳实践
设备树中定义中断:
led-controller { compatible = "vendor,led"; interrupts = <0 42 IRQ_TYPE_EDGE_RISING>; interrupt-parent = <&intc>; };驱动中注册中断处理程序:
static irqreturn_t led_interrupt(int irq, void *dev_id) { struct platform_device *pdev = dev_id; // 处理中断 return IRQ_HANDLED; } static int led_probe(struct platform_device *pdev) { int irq = platform_get_irq(pdev, 0); ret = request_irq(irq, led_interrupt, IRQF_TRIGGER_RISING, "led-irq", pdev); }对于共享中断,需要特殊处理:
ret = request_irq(irq, led_interrupt, IRQF_SHARED, "led-irq", pdev);中断处理中常用的模式:
- 顶半部(快速处理)
static irqreturn_t led_interrupt(int irq, void *dev_id) { struct platform_device *pdev = dev_id; disable_irq_nosync(irq); schedule_work(&led_work); return IRQ_HANDLED; }- 底半部(延迟处理)
static void led_work_handler(struct work_struct *work) { // 长时间处理 enable_irq(irq); } static DECLARE_WORK(led_work, led_work_handler);20. DMA与缓存一致性
设备树中描述DMA资源:
led-controller { compatible = "vendor,led"; dmas = <&dma 5>; dma-names = "tx"; };驱动中配置DMA传输:
static int led_probe(struct platform_device *pdev) { struct dma_chan *chan; struct dma_slave_config config = {0}; chan = dma_request_chan(&pdev->dev, "tx"); config.direction = DMA_MEM_TO_DEV; config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; dmaengine_slave_config(chan, &config); // 准备DMA缓冲区 buf = dma_alloc_coherent(&pdev->dev, size, &dma_handle, GFP_KERNEL); }处理缓存一致性问题:
static void led_transfer(struct platform_device *pdev) { dma_sync_single_for_device(&pdev->dev, dma_handle, size, DMA_TO_DEVICE); // 启动DMA传输 dma_async_issue_pending(chan); }对于复杂的DMA场景,可以使用分散/聚集列表:
struct scatterlist sg; sg_init_one(&sg, buf, len); dma_map_sg(&pdev->dev, &sg, 1, DMA_TO_DEVICE);21. 固件加载与FPGA配置
某些硬件需要运行时加载固件。设备树中可以指定固件文件:
led-controller { compatible = "vendor,led"; firmware-name = "led/v1.bin"; };驱动中加载固件:
static int led_probe(struct platform_device *pdev) { const struct firmware *fw; ret = request_firmware(&fw, "led/v1.bin", &pdev->dev); // 编程固件到硬件 program_firmware(fw->data, fw->size); release_firmware(fw); }对于FPGA配置,可以使用特殊接口:
static int program_fpga(struct platform_device *pdev, const char *name) { struct fpga_image_info *info; struct fpga_manager *mgr; info = fpga_image_info_alloc(&pdev->dev); fpga_image_info_free(info); mgr = fpga_manager_get(&pdev->dev); ret = fpga_manager_load(mgr, info); fpga_manager_put(mgr); }设备树描述FPGA配置:
fpga { compatible = "vendor,fpga"; firmware-name = "fpga/image.bit"; config-timeout-ms = <5000>; };22. 热插拔支持
对于支持热插拔的设备,需要实现这些接口:
static int led_plug(struct platform_device *pdev) { // 初始化新插入的设备 return 0; } static void led_unplug(struct platform_device *pdev) { // 清理设备 } static struct platform_driver led_driver = { .driver = { .suppress_bind_attrs = false, }, .probe = led_plug, .remove = led_unplug, };设备树中可以标记热插拔设备:
led-controller { compatible = "vendor,led"; hotpluggable; };处理热插拔事件:
static int led_notify(struct notifier_block *nb, unsigned long action, void *data) { struct platform_device *pdev = data; switch (action) { case BUS_NOTIFY_BIND_DRIVER: // 驱动绑定 break; case BUS_NOTIFY_DRIVER_NOT_BOUND: // 绑定失败 break; } return NOTIFY_OK; }23. 安全与权限控制
设备树可以描述安全属性:
led-controller { compatible = "vendor,led"; secure-status = "okay"; protected-regions = <0x1000 0x100>; };驱动中实现安全控制:
static int led_open(struct inode *inode, struct file *file) { if (!capable(CAP_SYS_RAWIO)) return -EPERM; return 0; } static const struct file_operations led_fops = { .open = led_open, .owner = THIS_MODULE, };对于特权操作,可以使用ioctl命令:
#define LED_SECURE_ACCESS _IOW('L', 0x20, struct led_config) static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { if (!capable(CAP_SYS_ADMIN)) return -EPERM; switch (cmd) { case LED_SECURE_ACCESS: // 处理特权操作 break; } }24. 性能监控与统计
设备树可以描述性能监控点:
led-controller { compatible = "vendor,led"; performance-monitors = <&pmu 3>, <&pmu 7>; };驱动中收集性能数据:
static void init_perf_counters(struct platform_device *pdev) { struct perf_event_attr attr = { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_INSTRUCTIONS, }; event = perf_event_create_kernel_counter(&attr, -1, NULL, NULL, NULL); perf_event_enable(event); } static void read_perf_data(struct platform_device *pdev) { u64 count = perf_event_read_value(event, &enabled, &running); dev_info(&pdev->dev, "Instructions: %llu\n", count); }通过sysfs暴露性能指标:
static ssize_t stats_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct led_priv *priv = platform_get_drvdata(pdev); return sprintf(buf, "Interrupts: %lu\nThroughput: %lu B/s\n", priv->stats.interrupts, priv->stats.throughput); } static DEVICE_ATTR_RO(stats);25. 驱动测试与验证
完整的驱动测试应该包括:
- 单元测试
static int __init led_test_init(void) { struct platform_device *pdev; int ret; pdev = platform_device_alloc("led-test", -1); platform_device_add(pdev); ret = led_probe(pdev); if (ret) return ret; // 执行测试用例 test_led_operations(); platform_device_del(pdev); return 0; }- 设备树测试
/ { led-test { compatible = "vendor,led-test"; status = "okay"; }; };- 用户空间接口测试
# 测试sysfs接口 echo 1 > /sys/class/leds/test/brightness cat /sys/class/leds/test/status # 测试ioctl接口 ./led_test --set-frequency 1000- 性能测试
# 测量中断延迟 cyclictest -m -p99 -n -i1000- 压力测试
# 连续操作设备 while true; do echo 1 > /sys/class/leds/test/brightness echo 0 > /sys/class/leds/test/brightness done26. 驱动文档与维护
良好的文档对驱动维护至关重要:
- 内核文档注释
/** * struct led_platform_data - platform data for LED devices * @brightness: initial brightness value * @max_brightness: maximum brightness value */ struct led_platform_data { u8 brightness; u8 max_brightness; };- 设备树绑定文档
LED Controller ============== Required properties: - compatible: Should be "vendor,led" - reg: physical base address and length - interrupts: interrupt specifier Optional properties: - default-state: initial state ("on", "off", "blink") - blink-delay-ms: blink interval in milliseconds- 用户手册
LED Driver Usage --------------- 1. Load the driver module: # insmod led-driver