Linux平台驱动注册:platform_driver_register与probe对比
1. Linux平台设备驱动注册机制概述在Linux设备驱动开发中platform机制是一种重要的虚拟总线架构主要用于管理片上系统(SoC)中的各种外设控制器和集成设备。这套机制由platform_device和platform_driver两个核心结构体组成分别代表平台设备和平台驱动。作为开发者我们经常需要在驱动模块初始化时注册驱动到系统。Linux提供了两种主要方式platform_driver_register()和platform_driver_probe()。这两种方法看似相似但在实际使用中存在关键差异理解这些差异对于编写正确、高效的驱动代码至关重要。2. platform_driver_register的工作原理2.1 基本注册流程platform_driver_register()是驱动注册的标准方法其函数原型如下int platform_driver_register(struct platform_driver *drv);当调用这个函数时内核会执行以下操作序列将platform_driver结构中的driver成员注册到platform_bus_type总线总线核心开始遍历已注册的所有platform_device对每个设备调用总线的match函数比较设备和驱动的name字段如果匹配成功则调用驱动的probe函数进行设备初始化这个过程的调用链可以简化为platform_driver_register() → driver_register() → bus_add_driver() → driver_attach() → __driver_attach() → driver_probe_device() → really_probe() → drv-probe()2.2 典型使用场景platform_driver_register()适用于以下情况驱动需要支持热插拔设备同一驱动可能对应多个设备实例设备可能在驱动加载后才被注册例如一个支持多个串口设备的驱动可能会这样注册static struct platform_driver serial_driver { .driver { .name ttyS, }, .probe serial_probe, .remove serial_remove, }; module_init(serial_init); static int __init serial_init(void) { return platform_driver_register(serial_driver); }2.3 内存占用特点使用platform_driver_register()注册的驱动其probe函数不会被放入.init.text段这意味着驱动会一直占用内存即使probe已经完成适合需要动态加载/卸载的驱动模块支持后续可能新增的设备匹配3. platform_driver_probe的独特机制3.1 函数原型与核心差异platform_driver_probe()的函数原型如下int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *))与platform_driver_register()相比关键区别在于probe函数指针作为参数显式传递注册后驱动无法再匹配新的设备probe函数会被标记为__init段3.2 内部实现解析platform_driver_probe()的内部实现机制值得关注它设置了一个特殊的标志位(probe_type PROBE_FORCE_SYNCHRONOUS)将probe函数放入.init.text段在注册完成后立即尝试匹配现有设备匹配成功后清除驱动的匹配能力这种设计带来了两个重要特性驱动只能匹配注册时已存在的设备初始化完成后probe函数占用的内存可以被释放3.3 适用场景分析platform_driver_probe()特别适合以下情况驱动只对应固定的、已知的设备设备在驱动加载前已经注册需要节省内存的嵌入式系统例如SoC内置的硬件控制器驱动可以这样实现static int soc_controller_probe(struct platform_device *pdev) { // 初始化硬件控制器 } static struct platform_driver soc_driver { .driver { .name soc-controller, }, .remove soc_controller_remove, }; module_init(soc_init); static int __init soc_init(void) { return platform_driver_probe(soc_driver, soc_controller_probe); }4. 两种方法的对比与选型建议4.1 功能特性对比特性platform_driver_registerplatform_driver_probe热插拔支持是否多设备支持是否probe内存释放否是后期设备匹配是否使用复杂度简单需要更多考虑4.2 性能影响分析在资源受限的嵌入式系统中platform_driver_probe()的优势明显内存节省probe函数在初始化后被释放启动速度同步探测模式减少延迟确定性明确的设备绑定关系实测数据显示在含有数十个平台设备的系统中使用_probe()版本可节省数十KB的内核内存。4.3 实际项目选型指南根据项目需求选择合适的方法选择platform_driver_register当驱动需要支持动态设备添加同一驱动服务多个设备实例设备可能热插拔驱动作为可加载模块使用选择platform_driver_probe当驱动只对应固定的内置设备内存优化是首要考虑设备在驱动加载前已确认存在驱动静态编译进内核5. 深入原理设备与驱动的绑定过程5.1 总线匹配机制platform总线使用简单的名称匹配策略。当注册驱动或设备时总线核心会比较static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev to_platform_device(dev); struct platform_driver *pdrv to_platform_driver(drv); return (strcmp(pdev-name, drv-name) 0); }这意味着设备和驱动的name字段必须完全一致才能匹配成功。5.2 probe函数的执行上下文probe函数在匹配成功后立即执行需要注意执行在原子上下文不能睡眠需要完成所有硬件初始化和资源申请应正确处理失败情况释放已申请资源典型的probe函数结构static int sample_probe(struct platform_device *pdev) { struct resource *res; void __iomem *regs; // 获取内存资源 res platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -ENODEV; // 申请IO内存 regs devm_ioremap_resource(pdev-dev, res); if (IS_ERR(regs)) return PTR_ERR(regs); // 初始化硬件 if (hardware_init(regs) 0) return -ENODEV; // 注册设备操作接口 return register_device_interface(pdev-dev); }5.3 资源管理要点平台设备通常通过resource结构提供硬件资源struct resource { resource_size_t start; // 起始地址 resource_size_t end; // 结束地址 const char *name; unsigned long flags; // 资源类型(IORESOURCE_MEM/IRQ等) // ... };驱动中应使用标准接口获取资源// 获取内存资源 struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num); // 获取中断号 int platform_get_irq(struct platform_device *dev, unsigned int num);6. 实际案例分析6.1 串口驱动实现对比使用platform_driver_register:static struct platform_driver serial_driver { .driver { .name ttyS, }, .probe serial_probe, .remove serial_remove, }; module_init(serial_init); static int __init serial_init(void) { return platform_driver_register(serial_driver); }使用platform_driver_probe:static int serial_probe(struct platform_device *pdev) { // 探测实现 } static struct platform_driver serial_driver { .driver { .name ttyS, }, .remove serial_remove, }; module_init(serial_init); static int __init serial_init(void) { return platform_driver_probe(serial_driver, serial_probe); }6.2 常见问题排查问题1probe函数未被调用可能原因设备名称与驱动不匹配设备注册在驱动之后(对_probe版本)资源申请失败问题2内存泄漏解决方案对于_register版本确保remove函数正确实现使用devm_系列资源管理函数对于_probe版本确保无后续资源引用问题3竞态条件预防措施对共享资源使用适当的锁避免在probe中执行耗时操作考虑使用异步探测模式7. 高级应用与最佳实践7.1 平台数据传递除了标准资源平台设备还可以通过platform_data传递自定义数据// 定义平台数据 struct my_device_data { int mode; u32 clock_rate; }; // 设备注册时设置数据 struct my_device_data data { .mode 1, .clock_rate 50000000, }; struct platform_device my_device { .name my-device, .dev { .platform_data data, }, }; // 驱动中获取数据 static int my_probe(struct platform_device *pdev) { struct my_device_data *data dev_get_platdata(pdev-dev); // 使用数据... }7.2 设备树集成在现代Linux内核中平台设备通常通过设备树描述my_device: my-device1000000 { compatible vendor,my-device; reg 0x1000000 0x1000; interrupts 0 45 4; mode 1; clock-frequency 50000000; };驱动需要通过of_match_table进行匹配static const struct of_device_id my_of_match[] { { .compatible vendor,my-device }, {}, }; MODULE_DEVICE_TABLE(of, my_of_match); static struct platform_driver my_driver { .driver { .name my-device, .of_match_table my_of_match, }, .probe my_probe, .remove my_remove, };7.3 电源管理集成平台驱动可以集成电源管理功能static int my_suspend(struct device *dev) { // 保存状态降低功耗 return 0; } static int my_resume(struct device *dev) { // 恢复状态 return 0; } static const struct dev_pm_ops my_pm_ops { SET_SYSTEM_SLEEP_PM_OPS(my_suspend, my_resume) }; static struct platform_driver my_driver { .driver { .name my-device, .pm my_pm_ops, }, // ... };8. 性能优化技巧8.1 延迟初始化策略对于复杂驱动可以考虑延迟部分初始化static int my_probe(struct platform_device *pdev) { // 立即必要的初始化 init_hardware(); // 延迟非关键初始化 schedule_delayed_work(priv-init_work, HZ); return 0; } static void delayed_init(struct work_struct *work) { // 执行延迟初始化 }8.2 资源池管理对于频繁申请释放的资源建立资源池struct resource_pool { struct list_head free_list; spinlock_t lock; }; static int pool_init(struct resource_pool *pool, int count) { INIT_LIST_HEAD(pool-free_list); spin_lock_init(pool-lock); for (int i 0; i count; i) { struct resource_item *item kmalloc(sizeof(*item), GFP_KERNEL); list_add(item-list, pool-free_list); } return 0; }8.3 异步探测模式对于耗时较长的探测可以使用异步模式static int my_probe(struct platform_device *pdev) { async_schedule(my_async_probe, pdev); return 0; } static void my_async_probe(void *data, async_cookie_t cookie) { struct platform_device *pdev data; // 执行实际探测 }9. 调试与问题诊断9.1 常用调试技巧检查匹配状态ls /sys/bus/platform/devices/ ls /sys/bus/platform/drivers/查看设备资源cat /proc/iomem cat /proc/interrupts动态调试#include linux/dynamic_debug.h dynamic_dev_dbg(pdev-dev, Probe started\n);9.2 常见错误处理资源冲突处理res platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(pdev-dev, Memory resource not found\n); return -EINVAL; }中断处理irq platform_get_irq(pdev, 0); if (irq 0) { dev_err(pdev-dev, Failed to get IRQ: %d\n, irq); return irq; }DMA配置if (!pdev-dev.dma_mask) pdev-dev.dma_mask pdev-dev.coherent_dma_mask; if (dma_set_coherent_mask(pdev-dev, DMA_BIT_MASK(32))) { dev_err(pdev-dev, DMA mask setting failed\n); return -EIO; }10. 现代内核中的演进10.1 设备树的普及随着设备树的广泛应用平台设备的注册方式发生了变化传统方式在板级文件中硬编码platform_device设备树方式通过DTS描述硬件内核自动创建platform_device驱动需要同时支持两种方式static const struct of_device_id my_of_match[] { { .compatible vendor,my-device }, {}, }; static struct platform_driver my_driver { .driver { .name my-device, .of_match_table of_match_ptr(my_of_match), }, .probe my_probe, };10.2 资源管理API演进现代内核推荐使用devm_系列资源管理函数static int my_probe(struct platform_device *pdev) { struct resource *res; void __iomem *regs; res platform_get_resource(pdev, IORESOURCE_MEM, 0); regs devm_ioremap_resource(pdev-dev, res); if (IS_ERR(regs)) return PTR_ERR(regs); irq platform_get_irq(pdev, 0); ret devm_request_irq(pdev-dev, irq, irq_handler, 0, dev_name(pdev-dev), priv); // ... }这些函数会自动在驱动卸载时释放资源简化了错误处理流程。10.3 异步驱动初始化为提高启动速度内核支持异步驱动初始化static int __init my_init(void) { async_schedule(my_async_init, NULL); return 0; } static void my_async_init(void *data, async_cookie_t cookie) { platform_driver_register(my_driver); }这种模式特别适合不相互依赖的驱动初始化。