Linux 平台设备驱动框架详解

Linux 平台设备(Platform Device)驱动框架是内核中用于管理嵌入式系统或 SoC(System on Chip)中非可发现设备的机制。这些设备不像 PCI 或 USB 设备那样可以通过总线自动枚举,而是通过静态配置(如设备树 Device Tree 或平台数据)来描述,例如内置的 UART、GPIO、I2C 控制器、LED 等。平台设备框架基于设备模型(Device Model),通过"平台总线"(platform bus)模拟一个虚拟总线来统一管理这些设备。该框架简化了驱动开发,适用于 ARM、x86 等架构的嵌入式系统。本文基于 Linux 内核 4.x 及以上版本(兼容 6.x),从框架概述、关键数据结构、注册流程、probe/remove 实现、示例代码以及注意事项等方面进行详解。

1. 框架概述

在 Linux 内核中,设备分为可发现(discoverable,如 PCI)和非可发现(non-discoverable,如 SoC 内置外设)。平台设备框架针对后者,提供了一个虚拟的"平台总线"来处理这些设备:

  • 平台总线:一个伪总线(bus_type 为 platform_bus_type),用于挂载平台设备和驱动。
  • 设备描述:早期使用平台数据(platform_data)结构体静态定义资源(如 IRQ、内存地址);现代内核推荐使用设备树(Device Tree,.dts 文件)通过 compatible 属性匹配设备和驱动。
  • 匹配机制:驱动和设备通过名称(name)或 compatible 字符串匹配。
  • 生命周期:设备注册后,内核调用驱动的 probe 函数初始化硬件;卸载时调用 remove。
  • 优势:统一设备模型,支持电源管理(PM)、sysfs 接口;缺点:依赖静态配置,不如热插拔总线灵活。
  • 适用场景:嵌入式板卡如 Raspberry Pi、BeagleBone 中的 GPIO、SPI 等控制器。

平台设备与普通字符设备驱动的区别:平台设备更注重硬件资源管理(如获取 IO 地址、IRQ),而字符设备焦点在文件操作接口。平台驱动常结合字符/杂项设备提供用户空间访问。

2. 关键数据结构

平台设备框架依赖几个核心结构体,定义在 <linux/platform_device.h> 和 <linux/device.h> 中。

  • struct platform_device :描述平台设备。

    复制代码
    struct platform_device {
        const char *name;  // 设备名,用于匹配
        int id;  // 实例 ID(如 uart0 中的 0),-1 表示自动
        struct device dev;  // 嵌入的通用设备结构体
        u32 num_resources;  // 资源数量
        struct resource *resource;  // 资源数组(内存、IRQ 等)
        const struct platform_device_id *id_entry;  // ID 表匹配
        const struct of_device_id *driver_override;  // 设备树覆盖
        // 其他字段如 driver_data(私有数据)
    };

    资源通过 struct resource 定义,包括类型(RESOURCE_IO、RESOURCE_MEM、RESOURCE_IRQ)。

  • struct platform_driver :描述平台驱动。

    复制代码
    struct platform_driver {
        int (*probe)(struct platform_device *);  // 初始化回调
        int (*remove)(struct platform_device *);  // 移除回调
        void (*shutdown)(struct platform_device *);  // 关机回调(可选)
        int (*suspend)(struct platform_device *, pm_message_t);  // 电源管理(可选)
        int (*resume)(struct platform_device *);  // 恢复回调(可选)
        struct device_driver driver;  // 通用驱动结构体
        const struct platform_device_id *id_table;  // ID 匹配表
        const struct of_device_id *of_match_table;  // 设备树 compatible 匹配表
    };
  • struct resource :设备资源描述。

    复制代码
    struct resource {
        resource_size_t start;  // 起始地址
        resource_size_t end;  // 结束地址
        const char *name;  // 资源名
        unsigned long flags;  // 类型标志(如 IORESOURCE_MEM)
        struct resource *parent, *sibling, *child;  // 树状结构
    };
  • 匹配表:platform_device_id 用于名称匹配;of_device_id 用于设备树。

3. 注册流程

平台设备的注册分为设备注册和驱动注册。设备通常在板级初始化代码或设备树中定义,驱动作为模块加载。

  1. 设备注册 (platform_device_register):
    • 静态方式:在板级文件(arch/arm/mach-xxx/board.c)中定义并注册。
    • 动态方式:使用设备树,内核解析 .dtb 文件自动创建 platform_device。
    • 示例:struct platform_device my_pdev = { .name = "mydevice", .id = -1, .resource = my_resources, .num_resources = ARRAY_SIZE(my_resources) }; platform_device_register(&my_pdev);
  2. 驱动注册 (platform_driver_register):
    • 初始化 struct platform_driver。
    • 调用 platform_driver_register(&my_driver)。
    • 内核匹配后调用 probe。
  3. 卸载:platform_driver_unregister 和 platform_device_unregister。

对于设备树:

  • 在 .dts 中定义节点:mydevice@0 { compatible = "vendor,mydevice"; reg = <0x12340000 0x1000>; interrupts = <10>; };
  • 驱动的 of_match_table = { .compatible = "vendor,mydevice" };
4. probe/remove 实现

probe 是驱动的核心,负责硬件初始化;remove 负责清理。

  • probe 示例

    复制代码
    static int my_probe(struct platform_device *pdev) {
        struct resource *res;
        void __iomem *base;
        int irq;
    
        // 获取资源
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!res) return -ENODEV;
        base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(base)) return PTR_ERR(base);
    
        irq = platform_get_irq(pdev, 0);
        if (irq < 0) return irq;
    
        // 请求 IRQ
        request_irq(irq, my_handler, 0, "mydevice", pdev);
    
        // 注册字符设备或其他接口
        // ...
    
        dev_info(&pdev->dev, "Device probed\n");
        return 0;
    }
  • remove 示例

    复制代码
    static int my_remove(struct platform_device *pdev) {
        // 释放 IRQ、iounmap 等
        free_irq(platform_get_irq(pdev, 0), pdev);
        // 注销接口
        dev_info(&pdev->dev, "Device removed\n");
        return 0;
    }

    使用 devm_ 函数(device managed)简化资源管理,自动在 remove 时释放。

平台驱动常结合 file_operations 提供用户空间接口(如通过 misc_register)。

5. 示例代码:简单平台 LED 驱动

假设控制一个 LED,通过设备树描述。完整驱动代码如下(需包含头文件如 <linux/module.h>、<linux/platform_device.h>、<linux/of.h>、<linux/gpio/consumer.h> 等):

复制代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>

struct my_led_data {
    struct gpio_desc *gpio;
};

static int my_led_probe(struct platform_device *pdev) {
    struct my_led_data *data;
    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
    if (!data) return -ENOMEM;

    data->gpio = devm_gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);
    if (IS_ERR(data->gpio)) return PTR_ERR(data->gpio);

    platform_set_drvdata(pdev, data);
    dev_info(&pdev->dev, "LED probed\n");
    return 0;
}

static int my_led_remove(struct platform_device *pdev) {
    dev_info(&pdev->dev, "LED removed\n");
    return 0;
}

static const struct of_device_id my_led_of_match[] = {
    { .compatible = "example,my-led" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_led_of_match);

static struct platform_driver my_led_driver = {
    .probe = my_led_probe,
    .remove = my_led_remove,
    .driver = {
        .name = "my-led",
        .of_match_table = my_led_of_match,
    },
};

module_platform_driver(my_led_driver);
MODULE_LICENSE("GPL");

设备树节点示例(.dts):

复制代码
my-led {
    compatible = "example,my-led";
    led-gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>;
};

编译成 .ko,加载:insmod my_led.ko。测试:通过 sysfs 或自定义接口控制 LED。

6. 注意事项和高级主题
  • 资源获取:使用 platform_get_resource、platform_get_irq、devm_ioremap 等函数,避免硬编码地址。
  • 电源管理:实现 suspend/resume 支持 PM。
  • 错误处理:probe 返回负值表示失败,常用 -ENODEV、-ENOMEM。
  • 调试:查看 /sys/bus/platform/devices/ 和 dmesg。使用 of_ 函数解析设备树属性。
  • 与其它框架集成:平台驱动常作为后端,与 regmap、pinctrl 等结合。
  • 版本差异:内核 5.x+ 强调设备树,弃用平台数据;推荐使用 module_platform_driver 宏简化注册。
  • 比较:与字符驱动不同,平台聚焦硬件绑定;与杂项设备相比,平台支持总线模型和资源管理。
相关推荐
松涛和鸣1 分钟前
48、MQTT 3.1.1
linux·前端·网络·数据库·tcp/ip·html
渡我白衣2 分钟前
计算机组成原理(13):多路选择器与三态门
开发语言·javascript·ecmascript·数字电路·计算机组成原理·三态门·多路选择器
HUST4 分钟前
C语言 第十讲:操作符详解
c语言·开发语言
大小鱼鱼鱼与鱼.4 分钟前
linux磁盘扩展
linux·运维·服务器
田里的水稻8 分钟前
matlab_绘图线条颜色显示和点的形状显示
开发语言·matlab
CCPC不拿奖不改名11 分钟前
python基础:python语言的数据结构+面试习题
开发语言·数据结构·python·面试
Tan385112 分钟前
陪读蛙 Read Frog 配置 API 教程|低成本实现高质量翻译
开发语言·机器翻译·自动翻译·api key·tensdaq·陪读蛙·read frog
凑凑的小手办14 分钟前
C语言基础(一)
c语言·开发语言
lly20240616 分钟前
Lua 循环
开发语言
※※冰馨※※17 分钟前
【QT】初始化显示时正常,操作刷新后布局显示问题。
开发语言·c++·windows·qt