Linux驱动之平台总线

一、平台总线的核心定位与架构

1.1 为什么需要平台总线?

Linux 内核中的总线(如 PCIe、USB)主要用于 “可热插拔的外部设备”,而片上外设(如 SOC 内部的 GPIO、定时器)通常与硬件紧密绑定,无法热插拔。平台总线正是为解决这类 “非热插拔片上设备” 的驱动适配问题而生,其核心价值:

  • 解耦设备与驱动:设备信息(如寄存器地址、中断号)单独描述,驱动逻辑通用化,同一驱动可适配不同硬件(只需修改设备信息);
  • 统一管理资源:内核通过平台总线统一分配 / 释放设备资源(如 IO 地址、中断),避免资源冲突;
  • 简化驱动开发:开发者无需关注总线底层细节,只需按规范实现设备和驱动,内核自动完成匹配。

1.2 平台总线的三层架构

平台总线采用 “总线(Bus)→ 设备(Device)→ 驱动(Driver) ” 三层结构,三者关系如下:

  • 总线(Platform Bus):内核预定义的虚拟总线(无物理硬件),负责管理所有平台设备和驱动,提供 “设备 - 驱动匹配” 的核心逻辑;
  • 设备(Platform Device):描述硬件设备的信息(如寄存器基地址、中断号、GPIO 引脚),通常由设备树(Device Tree)或板级文件(Board File)定义;
  • 驱动(Platform Driver):实现设备的控制逻辑(如初始化硬件、数据读写),驱动代码与硬件信息分离,可复用。

核心流程:平台总线启动后,会遍历所有已注册的 platform_device 和 platform_driver,当二者匹配成功时,自动调用驱动的初始化函数(probe),完成硬件与驱动的绑定。

二、核心组件与数据结构

平台总线的实现依赖两个核心结构体:platform_device(设备)和 platform_driver(驱动),以及配套的注册 / 注销函数。

2.1 平台设备(Platform Device)

用于描述硬件设备的 “资源信息”,核心是 “告诉内核设备在哪里、用哪些资源”。

2.1.1 核心结构体:struct platform_device
#include <linux/platform_device.h>

struct platform_device {
    const char  *name;          // 设备名(用于与驱动匹配,需与驱动的name一致)
    int         id;             // 设备编号(多个同类型设备时区分,通常设为 -1 表示单设备)
    struct device dev;          // 内嵌的通用设备结构体(内核设备模型的基础)
    u32         num_resources;  // 设备资源数量(如寄存器、中断、GPIO 等)
    struct resource *resource;  // 资源数组(存储设备的具体资源信息)
    const struct platform_device_id *id_entry; // 设备 ID 表(用于多设备匹配)
};
2.1.2 设备资源:struct resource

用于定义设备的硬件资源(如 IO 地址、中断、内存),常见类型如下:

struct resource {
    resource_size_t start;  // 资源起始地址(如寄存器基地址、中断号)
    resource_size_t end;    // 资源结束地址(如寄存器末地址,中断号与start相同)
    const char *name;       // 资源名称(可选,用于调试)
    unsigned long flags;    // 资源类型(核心字段,如 IORESOURCE_MEM 表示内存资源)
    struct resource *parent, *sibling, *child; // 资源链表(内核内部管理)
};

常见资源类型(flags 取值)

  • IORESOURCE_MEM:内存映射的寄存器资源(如 SOC 内部的 GPIO 控制器寄存器);
  • IORESOURCE_IRQ:中断资源(如设备的中断号);
  • IORESOURCE_IO:IO 端口资源(x86 架构常用,ARM 架构较少用);
  • IORESOURCE_GPIO:GPIO 资源(部分内核版本支持,或通过 devm_gpio_request 单独申请)。
2.1.3 设备注册与注销函数
  • 注册设备int platform_device_register(struct platform_device *pdev)功能:将平台设备注册到平台总线上,内核会触发总线的匹配逻辑。返回值:0 表示成功,非 0 表示失败(如设备名重复、资源冲突)。

  • 注销设备void platform_device_unregister(struct platform_device *pdev)功能:从平台总线上注销设备,释放资源,并解绑对应的驱动。

2.2 平台驱动(Platform Driver)

用于实现设备的控制逻辑(如初始化硬件、处理中断、数据收发),核心是 “如何操作设备”,与具体硬件资源解耦。

2.2.1 核心结构体:struct platform_driver
struct platform_driver {
    int (*probe)(struct platform_device *pdev);  // 设备-驱动匹配成功后调用(初始化硬件)
    int (*remove)(struct platform_device *pdev); // 设备注销或驱动卸载时调用(释放资源)
    void (*shutdown)(struct platform_device *pdev); // 设备关机时调用(可选)
    int (*suspend)(struct platform_device *pdev, pm_message_t state); // 设备休眠时调用(可选)
    int (*resume)(struct platform_device *pdev); // 设备唤醒时调用(可选)
    struct device_driver driver;  // 内嵌的通用驱动结构体(核心字段:.name 用于匹配设备)
    const struct platform_device_id *id_table; // 驱动支持的设备 ID 表(多设备匹配)
    bool prevent_deferred_probe;  // 禁止延迟探测(可选,默认 false)
};

关键函数说明

  • probe:核心初始化函数,匹配成功后自动执行,通常完成:资源申请(如 IO 地址映射、中断注册)、硬件初始化(如配置寄存器)、创建设备节点等;
  • remove:资源释放函数,通常完成:中断注销、IO 地址解映射、GPIO 释放等,避免资源泄漏。
2.2.2 驱动注册与注销函数
  • 注册驱动int platform_driver_register(struct platform_driver *pdrv)功能:将平台驱动注册到平台总线上,内核会遍历已注册的平台设备,触发匹配逻辑。返回值:0 表示成功,非 0 表示失败(如驱动名无效)。

  • 注销驱动void platform_driver_unregister(struct platform_driver *pdrv)功能:从平台总线上注销驱动,自动调用已匹配设备的 remove 函数,释放资源。

三、设备与驱动的匹配机制

平台总线的核心能力是 “自动匹配设备与驱动”,匹配逻辑由总线实现,开发者只需保证 “设备与驱动的标识一致”。常见匹配方式有两种:

3.1 名称匹配(最常用)

  • 原理:通过 platform_device.name 与 platform_driver.driver.name 进行字符串比较,若完全一致,则匹配成功;
  • 适用场景:单类型设备(如一个驱动仅适配一种设备);
  • 示例:设备侧:pdev->name = "my_gpio_led";驱动侧:pdrv->driver.name = "my_gpio_led";此时二者名称一致,总线会触发驱动的 probe 函数。

3.2 ID 表匹配(多设备适配)

  • 原理:当一个驱动需要适配多种设备时,通过 platform_device_id 表定义支持的设备列表,总线遍历 ID 表,若设备名在表中存在,则匹配成功;
  • 适用场景:多类型设备(如一个驱动适配不同型号的同功能芯片);
  • 实现步骤
  • 定义设备 ID 表:
    // 驱动侧:定义支持的设备 ID 表
    static const struct platform_device_id led_driver_id_table[] = {
        {"my_gpio_led_v1", 0},  // 设备1:名称+私有数据(私有数据可选)
        {"my_gpio_led_v2", 1},  // 设备2:名称+私有数据
        { },  // 表的结束标志(必须添加)
    };
    MODULE_DEVICE_TABLE(platform, led_driver_id_table); // 告知内核该 ID 表
    
  • 驱动绑定 ID 表:
    pdrv->id_table = led_driver_id_table; // 驱动关联 ID 表
    
  • 设备侧只需设置 pdev->name 为 ID 表中的任一名称(如 "my_gpio_led_v2"),即可匹配成功。

四、实战代码示例(GPIO 控制 LED)

以 “平台总线驱动控制 GPIO LED” 为例,完整展示设备定义、驱动实现、资源操作的流程(基于设备树传递资源,现代 Linux 开发常用)。

4.1 设备侧:设备树描述平台设备(替代传统板级文件)

现代 Linux 中,平台设备的资源通常通过设备树(.dts)定义,无需手动编写 platform_device 代码,内核会自动解析设备树生成平台设备。

设备树节点定义(添加到 SOC 对应的设备树文件中)
// 平台设备节点:描述 GPIO LED 的资源(GPIO 引脚、设备名)
my_gpio_led {
    compatible = "my,gpio-led";  // 兼容属性(用于驱动匹配,替代 name 匹配)
    led-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>; // GPIO 资源:gpio1 组的第 2 引脚,高电平点亮
    status = "okay";  // 设备使能("disabled" 表示禁用)
};
  • 说明compatible 属性用于驱动匹配(替代传统的名称匹配),驱动侧需通过 of_match_table 声明支持的 compatible 值。

4.2 驱动侧:平台驱动实现(控制 LED)

驱动通过平台总线匹配设备树节点,申请 GPIO 资源,实现 LED 的点亮 / 熄灭控制。

完整驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>

// 驱动私有数据:存储设备相关资源(如 GPIO 编号)
struct led_driver_data {
    int led_gpio;  // LED 对应的 GPIO 编号
};

// 1. probe 函数:匹配成功后初始化硬件
static int led_platform_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct led_driver_data *drv_data;
    int ret;

    // (1)分配私有数据内存(devm_ 前缀:内核自动管理内存,设备注销时自动释放)
    drv_data = devm_kzalloc(dev, sizeof(struct led_driver_data), GFP_KERNEL);
    if (!drv_data) {
        return -ENOMEM; // 内存分配失败
    }

    // (2)从设备树获取 GPIO 资源(解析 led-gpios 属性)
    drv_data->led_gpio = of_get_named_gpio_flags(dev->of_node, "led-gpios", 0, NULL);
    if (drv_data->led_gpio < 0) {
        dev_err(dev, "failed to get led gpio\n");
        return drv_data->led_gpio; // 获取 GPIO 失败
    }

    // (3)申请 GPIO 资源(避免资源冲突)
    ret = devm_gpio_request(dev, drv_data->led_gpio, "led_gpio");
    if (ret < 0) {
        dev_err(dev, "failed to request led gpio\n");
        return ret;
    }

    // (4)配置 GPIO 为输出模式,并初始化熄灭 LED
    gpio_direction_output(drv_data->led_gpio, 0);
    // 点亮 LED(测试)
    gpio_set_value(drv_data->led_gpio, 1);
    dev_info(dev, "led probe success: gpio %d is on\n", drv_data->led_gpio);

    // (5)将私有数据绑定到设备结构体,供后续函数(如 remove)使用
    platform_set_drvdata(pdev, drv_data);
    return 0;
}

// 2. remove 函数:设备注销时释放资源
static int led_platform_remove(struct platform_device *pdev)
{
    struct led_driver_data *drv_data = platform_get_drvdata(pdev);

    // 熄灭 LED
    gpio_set_value(drv_data->led_gpio, 0);
    dev_info(&pdev->dev, "led remove success: gpio %d is off\n", drv_data->led_gpio);

    // 无需手动释放 devm_ 申请的资源(GPIO、内存),内核自动处理
    return 0;
}

// 3. 设备树匹配表:驱动支持的 compatible 属性列表
static const struct of_device_id led_of_match_table[] = {
    { .compatible = "my,gpio-led" }, // 与设备树节点的 compatible 一致
    { /* Sentinel */ } // 表的结束标志
};
MODULE_DEVICE_TABLE(of, led_of_match_table); // 告知内核该匹配表

// 4. 定义平台驱动结构体
static struct platform_driver led_platform_driver = {
    .probe = led_platform_probe,    // 匹配成功后执行
    .remove = led_platform_remove,  // 设备注销时执行
    .driver = {
        .name = "my_gpio_led_driver", // 驱动名(可选,用于名称匹配)
        .of_match_table = led_of_match_table, // 设备树匹配表(优先于名称匹配)
        .owner = THIS_MODULE,
    },
};

// 5. 驱动初始化与注销(模块入口/出口)
static int __init led_driver_init(void)
{
    // 注册平台驱动
    return platform_driver_register(&led_platform_driver);
}

static void __exit led_driver_exit(void)
{
    // 注销平台驱动
    platform_driver_unregister(&led_platform_driver);
}

module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Platform Bus Driver for GPIO LED");
MODULE_AUTHOR("Your Name");

4.3 代码说明

  1. 私有数据(led_driver_data:存储设备的 GPIO 编号,避免全局变量,支持多设备适配;
  2. 设备树解析:通过 of_get_named_gpio_flags 从设备树获取 GPIO 资源,实现 “硬件信息与驱动解耦”;
  3. 资源管理:使用 devm_ 前缀的函数(devm_kzallocdevm_gpio_request)申请资源,内核自动释放,避免资源泄漏;
  4. 匹配优先级:设备树匹配(of_match_table)优先级高于名称匹配,是现代 Linux 驱动的标准方式。

0voice · GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值