Linux平台总线驱动开发详解与实战
1. Linux平台总线驱动概述
在Linux设备驱动开发中,平台总线(platform bus)是一种虚拟总线机制,用于管理那些没有物理总线连接的设备。这种机制将硬件资源信息与驱动代码分离,使得驱动开发更加模块化和可维护。
1.1 为什么需要平台总线
传统的字符设备驱动开发方式存在一个明显问题:设备信息和驱动代码高度耦合。在这种模式下,硬件寄存器地址、中断号等资源信息直接硬编码在驱动代码中。当硬件发生变化时,必须修改驱动源码并重新编译。
平台总线机制通过以下方式解决了这个问题:
- 将硬件资源信息抽象为platform_device结构
- 将驱动逻辑封装在platform_driver结构
- 通过总线机制自动匹配设备和驱动
1.2 平台总线的核心组件
平台总线架构包含三个核心组件:
- 平台设备(platform_device):描述硬件资源信息
- 平台驱动(platform_driver):实现设备操作逻辑
- 平台总线(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 驱动匹配机制
平台总线提供四种匹配方式,按优先级排序:
- 设备树匹配:通过compatible属性匹配
- ACPI匹配:用于电源管理相关设备
- ID表匹配:比较id_table中的name字段
- 名称匹配:直接比较设备和驱动的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 # 熄灭LED6. 常见问题与调试技巧
6.1 匹配失败排查
当设备和驱动没有成功匹配时,可以检查:
- 确认设备和驱动的name字段完全一致
- 检查/sys/bus/platform/devices和/sys/bus/platform/drivers目录
- 使用dmesg查看内核日志中的匹配错误信息
6.2 资源获取失败处理
资源获取失败的可能原因:
- 资源索引号不正确
- 资源类型不匹配
- 设备未正确注册
建议添加错误检查代码:
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 平台总线与字符设备
平台总线常与字符设备结合使用:
- 平台总线处理硬件资源管理
- 字符设备提供用户空间接口
- probe函数中完成字符设备注册
这种组合既保持了硬件抽象,又提供了标准的用户接口。