在嵌入式 Linux 驱动开发中,随着系统复杂度的增加,驱动的可重用性和可维护性变得尤为重要。为了应对这一挑战,Linux 内核提出了 驱动分离与分层 的设计思想,并在此基础上引入了 Platform 设备驱动模型。本文将深入探讨 Linux 驱动的分离与分层思想,以及 Platform 设备驱动模型的实现原理和开发方法。


1. 驱动分离与分层的思想

1.1 为什么需要驱动分离与分层?

在 Linux 内核中,驱动程序占据了大量的代码量。如果不对驱动程序进行有效的管理,代码的重复性和复杂性将迅速增加,导致内核变得臃肿且难以维护。为了解决这一问题,Linux 提出了 驱动分离与分层 的设计思想。

1.1.1 驱动分离

驱动分离的核心思想是将 设备驱动主机驱动 分开。例如,对于 I2C 设备,I2C 控制器的主机驱动由芯片厂商提供,而设备驱动则由设备厂商提供。通过这种方式,设备驱动可以独立于具体的硬件平台,从而实现代码的重用。

1.1.2 驱动分层

驱动分层的核心思想是将驱动程序分为多个层次,每一层负责不同的功能。例如,输入子系统(Input Subsystem)将驱动程序分为 设备原始驱动层核心层。设备原始驱动层负责与硬件交互,而核心层则负责处理输入事件的上报和处理。


1.2 驱动分离与分层的优势

  1. 代码重用性:通过分离设备驱动和主机驱动,可以减少代码的重复性。
  2. 可维护性:分层设计使得驱动程序的逻辑更加清晰,便于维护和扩展。
  3. 兼容性:设备驱动可以独立于具体的硬件平台,提高了驱动的兼容性。

2. Platform 设备驱动模型

在 Linux 内核中,有些外设并没有总线概念(如 GPIO、LED 等),但为了使用总线、驱动和设备模型,Linux 引入了 Platform 设备驱动模型。Platform 模型是一种虚拟总线模型,它包括以下三个核心组件:

  1. Platform 总线:负责管理 Platform 设备和 Platform 驱动的匹配。
  2. Platform 设备:描述设备的信息(如设备名称、资源等)。
  3. Platform 驱动:实现设备的驱动逻辑。

2.1 Platform 总线

Platform 总线是 Linux 内核中的一种虚拟总线,由 bus_type 结构体表示。其定义如下:

struct bus_type platform_bus_type = {
    .name = "platform",
    .dev_groups = platform_dev_groups,
    .match = platform_match,
    .uevent = platform_uevent,
    .pm = &platform_dev_pm_ops,
};

2.1.1 Platform 总线的匹配机制

Platform 总线的核心是 match 函数,它负责匹配 Platform 设备和 Platform 驱动。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);

    /* 设备树匹配 */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* ID 表匹配 */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* 名称匹配 */
    return (strcmp(pdev->name, drv->name) == 0);
}

匹配机制分为以下几种:

  1. 设备树匹配:通过设备树的 compatible 属性进行匹配。
  2. ID 表匹配:通过 id_table 进行匹配。
  3. 名称匹配:通过设备名称和驱动名称进行匹配。

2.2 Platform 驱动

Platform 驱动由 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 state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
};

2.2.1 Platform 驱动的核心函数

  1. probe 函数:当驱动与设备匹配成功时,probe 函数会被调用。通常在此函数中完成设备的初始化工作。
  2. remove 函数:当设备被移除时,remove 函数会被调用。通常在此函数中完成资源的释放工作。

2.2.2 Platform 驱动的注册与注销

Platform 驱动的注册和注销通过以下函数完成:

int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv);

2.3 Platform 设备

Platform 设备由 platform_device 结构体表示,其定义如下:

struct platform_device {
    const char *name;
    int id;
    struct device dev;
    u32 num_resources;
    struct resource *resource;
    const struct platform_device_id *id_entry;
};

2.3.1 Platform 设备的核心成员

  1. name:设备名称,用于与驱动进行匹配。
  2. resource:设备资源(如寄存器地址、中断号等)。
  3. num_resources:资源数量。

2.3.2 Platform 设备的注册与注销

Platform 设备的注册和注销通过以下函数完成:

int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);

3. Platform 设备驱动的开发

3.1 设备树下的 Platform 驱动

在支持设备树的 Linux 内核中,设备信息通过设备树描述。Platform 驱动通过设备树的 compatible 属性与设备进行匹配。

3.1.1 设备树示例

&i2c1 {
    mpu6050: mpu6050@68 {
        compatible = "invensense,mpu6050";
        reg = <0x68>;
    };
};

3.1.2 Platform 驱动示例

static const struct of_device_id mpu6050_of_match[] = {
    { .compatible = "invensense,mpu6050" },
    { }
};

static struct platform_driver mpu6050_driver = {
    .driver = {
        .name = "mpu6050",
        .of_match_table = mpu6050_of_match,
    },
    .probe = mpu6050_probe,
    .remove = mpu6050_remove,
};

module_platform_driver(mpu6050_driver);

3.2 传统方式下的 Platform 驱动

在不支持设备树的 Linux 内核中,设备信息通过 platform_device 结构体描述。

3.2.1 Platform 设备示例

static struct resource mpu6050_resources[] = {
    {
        .start = 0x68,
        .end = 0x68,
        .flags = IORESOURCE_MEM,
    },
};

static struct platform_device mpu6050_device = {
    .name = "mpu6050",
    .id = -1,
    .num_resources = ARRAY_SIZE(mpu6050_resources),
    .resource = mpu6050_resources,
};

3.2.2 Platform 驱动示例

static struct platform_driver mpu6050_driver = {
    .driver = {
        .name = "mpu6050",
    },
    .probe = mpu6050_probe,
    .remove = mpu6050_remove,
};

module_platform_driver(mpu6050_driver);

4. 总结

Linux 驱动的分离与分层思想是 Linux 内核设计中的重要理念,它通过将驱动分为多个层次和模块,提高了代码的重用性和可维护性。Platform 设备驱动模型是这一思想的典型应用,它通过虚拟总线的方式,实现了设备与驱动的分离。

在实际开发中,Platform 设备驱动模型广泛应用于各种外设驱动开发中。无论是设备树方式还是传统方式,Platform 模型都为驱动开发提供了统一的框架,极大地简化了驱动开发的复杂度。

通过本文的讲解,相信读者已经对 Linux 驱动的分离与分层思想以及 Platform 设备驱动模型有了深入的理解。在实际开发中,合理运用这些知识,可以帮助开发者编写出更加高效、稳定的驱动程序。