Linux平台总线驱动开发详解与实战

Linux平台总线驱动开发详解与实战

1. Linux平台总线驱动概述

在Linux设备驱动开发中,平台总线(platform bus)是一种虚拟总线机制,用于管理那些没有物理总线连接的设备。这种机制将硬件资源信息与驱动代码分离,使得驱动开发更加模块化和可维护。

1.1 为什么需要平台总线

传统的字符设备驱动开发方式存在一个明显问题:设备信息和驱动代码高度耦合。在这种模式下,硬件寄存器地址、中断号等资源信息直接硬编码在驱动代码中。当硬件发生变化时,必须修改驱动源码并重新编译。

平台总线机制通过以下方式解决了这个问题:

  • 将硬件资源信息抽象为platform_device结构
  • 将驱动逻辑封装在platform_driver结构
  • 通过总线机制自动匹配设备和驱动

1.2 平台总线的核心组件

平台总线架构包含三个核心组件:

  1. 平台设备(platform_device):描述硬件资源信息
  2. 平台驱动(platform_driver):实现设备操作逻辑
  3. 平台总线(platform_bus_type):负责设备和驱动的匹配

这种分离的设计使得:

  • 同一驱动可以支持不同硬件配置
  • 硬件变更无需修改驱动代码
  • 驱动开发更加标准化

2. 平台设备(platform_device)详解

2.1 platform_device结构体

平台设备使用platform_device结构体表示,其定义如下:

struct platform_device { const char *name; // 设备名称 int id; // 设备ID struct device dev; // 基础设备结构 u32 num_resources; // 资源数量 struct resource *resource; // 资源数组 const struct platform_device_id *id_entry; /* 其他成员省略 */ };

关键成员说明:

  • name:设备名称,用于总线匹配
  • id:设备编号,区分同名设备
  • dev:继承自基础设备模型
  • resource:描述硬件资源(I/O内存、中断等)

2.2 硬件资源描述

硬件资源通过struct resource描述:

struct resource { resource_size_t start; // 起始地址 resource_size_t end; // 结束地址 const char *name; unsigned long flags; // 资源类型标志 };

常用资源类型标志:

  • IORESOURCE_MEM:内存映射资源
  • IORESOURCE_IO:I/O端口资源
  • IORESOURCE_IRQ:中断资源

2.3 设备私有数据

对于无法用标准资源描述的硬件信息,可以通过platform_data传递:

struct platform_device pdev = { .dev = { .platform_data = &custom_data, } };

3. 平台驱动(platform_driver)实现

3.1 platform_driver结构体

平台驱动使用platform_driver结构体表示:

struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); struct device_driver driver; const struct platform_device_id *id_table; };

关键成员说明:

  • probe:设备匹配成功后调用的初始化函数
  • remove:设备移除时调用的清理函数
  • driver:基础驱动结构
  • id_table:支持的设备ID表

3.2 驱动匹配机制

平台总线提供四种匹配方式,按优先级排序:

  1. 设备树匹配:通过compatible属性匹配
  2. ACPI匹配:用于电源管理相关设备
  3. ID表匹配:比较id_table中的name字段
  4. 名称匹配:直接比较设备和驱动的name字段

3.3 资源获取接口

驱动中获取设备资源的常用接口:

// 获取内存/I/O资源 struct resource *platform_get_resource( struct platform_device *dev, unsigned int type, unsigned int num); // 获取中断号 int platform_get_irq( struct platform_device *pdev, unsigned int num); // 获取私有数据 void *dev_get_platdata(const struct device *dev);

4. 平台总线工作原理

4.1 总线注册流程

Linux内核启动时会自动注册平台总线:

struct bus_type platform_bus_type = { .name = "platform", .match = platform_match, /* 其他成员 */ }; int __init platform_bus_init(void) { return bus_register(&platform_bus_type); }

4.2 设备驱动匹配过程

匹配函数platform_match的实现逻辑:

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); // 1. 检查driver_override if (pdev->driver_override) return !strcmp(pdev->driver_override, drv->name); // 2. 尝试设备树匹配 if (of_driver_match_device(dev, drv)) return 1; // 3. 尝试ACPI匹配 if (acpi_driver_match_device(dev, drv)) return 1; // 4. 尝试ID表匹配 if (pdrv->id_table) return platform_match_id(pdrv->id_table, pdev) != NULL; // 5. 回退到名称匹配 return (strcmp(pdev->name, drv->name) == 0); }

5. 实战:LED平台驱动开发

5.1 平台设备定义

定义LED设备的硬件资源:

// GPIO寄存器定义 #define GPIO_BASE 0xfdd60000 #define GPIO_DR_H (GPIO_BASE + 0x0004) #define GPIO_DDR_H (GPIO_BASE + 0x000C) // 资源定义 static struct resource pdev_led_resource[] = { [0] = DEFINE_RES_MEM(GPIO_DR_H, 4), [1] = DEFINE_RES_MEM(GPIO_DDR_H, 4), }; // 私有数据(引脚偏移) unsigned int pdev_led_hwinfo[1] = { 7 }; // 平台设备定义 static struct platform_device pdev_led = { .name = "pdev_led", .id = 0, .num_resources = ARRAY_SIZE(pdev_led_resource), .resource = pdev_led_resource, .dev = { .platform_data = pdev_led_hwinfo, }, };

5.2 平台驱动实现

实现LED平台驱动:

// 设备ID表 static struct platform_device_id pdev_led_ids[] = { {.name = "pdev_led"}, {} }; // probe函数 static int pdrv_led_probe(struct platform_device *pdev) { // 1. 获取硬件资源 struct resource *mem_dr = platform_get_resource(pdev, IORESOURCE_MEM, 0); struct resource *mem_ddr = platform_get_resource(pdev, IORESOURCE_MEM, 1); // 2. 映射寄存器 void __iomem *va_dr = devm_ioremap(&pdev->dev, mem_dr->start, resource_size(mem_dr)); void __iomem *va_ddr = devm_ioremap(&pdev->dev, mem_ddr->start, resource_size(mem_ddr)); // 3. 初始化GPIO unsigned int val = ioread32(va_ddr); val |= (1 << (pin + 16)) | (1 << pin); iowrite32(val, va_ddr); // 4. 注册字符设备 // ...字符设备注册代码... return 0; } // 平台驱动定义 static struct platform_driver pdrv_led = { .probe = pdrv_led_probe, .remove = pdrv_led_remove, .driver = { .name = "pdev_led", }, .id_table = pdev_led_ids, };

5.3 模块加载与测试

加载驱动模块并测试:

# 加载平台设备模块 insmod pdev_led.ko # 加载平台驱动模块 insmod pdrv_led.ko # 测试LED控制 echo 1 > /dev/pdrv_led # 点亮LED echo 0 > /dev/pdrv_led # 熄灭LED

6. 常见问题与调试技巧

6.1 匹配失败排查

当设备和驱动没有成功匹配时,可以检查:

  1. 确认设备和驱动的name字段完全一致
  2. 检查/sys/bus/platform/devices和/sys/bus/platform/drivers目录
  3. 使用dmesg查看内核日志中的匹配错误信息

6.2 资源获取失败处理

资源获取失败的可能原因:

  1. 资源索引号不正确
  2. 资源类型不匹配
  3. 设备未正确注册

建议添加错误检查代码:

struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Failed to get MEM resource\n"); return -ENODEV; }

6.3 设备树兼容性处理

对于支持设备树的平台,建议实现of_match_table:

static const struct of_device_id led_of_match[] = { { .compatible = "mycompany,led" }, {}, }; static struct platform_driver pdrv_led = { .driver = { .of_match_table = led_of_match, }, };

7. 性能优化建议

7.1 资源管理优化

使用devm_系列函数自动管理资源:

// 自动释放的ioremap void __iomem *va = devm_ioremap(&pdev->dev, res->start, resource_size(res)); // 自动释放的内存分配 struct my_data *data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);

7.2 延迟初始化

对于耗时的初始化操作,可以使用延迟工作队列:

static int pdrv_led_probe(struct platform_device *pdev) { INIT_DELAYED_WORK(&data->work, led_init_work); schedule_delayed_work(&data->work, msecs_to_jiffies(100)); return 0; }

7.3 电源管理

实现电源管理回调以节省功耗:

static int __maybe_unused pdrv_led_suspend(struct device *dev) { // 保存状态并进入低功耗模式 return 0; } static int __maybe_unused pdrv_led_resume(struct device *dev) { // 恢复状态 return 0; } static const struct dev_pm_ops pdrv_led_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(pdrv_led_suspend, pdrv_led_resume) };

8. 进阶话题

8.1 平台总线与设备树

现代Linux驱动开发中,平台设备信息通常通过设备树描述:

led-controller@fdd60000 { compatible = "mycompany,led"; reg = <0xfdd60000 0x1000>; led-pin = <7>; };

驱动中通过of_函数读取设备树信息:

int pin; struct device_node *np = pdev->dev.of_node; of_property_read_u32(np, "led-pin", &pin);

8.2 多设备支持

通过id_table支持多个设备变体:

static struct platform_device_id pdev_led_ids[] = { {.name = "led-v1", .driver_data = (kernel_ulong_t)&v1_data}, {.name = "led-v2", .driver_data = (kernel_ulong_t)&v2_data}, {} };

8.3 平台总线与字符设备

平台总线常与字符设备结合使用:

  1. 平台总线处理硬件资源管理
  2. 字符设备提供用户空间接口
  3. probe函数中完成字符设备注册

这种组合既保持了硬件抽象,又提供了标准的用户接口。