一、Linux驱动的分离

1. 为什么需要驱动分离?

在嵌入式开发中,无论处理器如何更换,外设模块的操作都是一致的,比如有三个不同的平台都要驱动MPU6050传感器,最简单的方法是针对每个平台都写一份驱动:

i.MX6ULL驱动开发 | 23 - Linux下的驱动分离与分层——platform平台驱动模型_linux内核

显然这种处理方式太low了,MPU6050都是使用I2C接口操作的,对于不同的平台,只是I2C操作方式不一样,所以这里可以将I2C接口抽象出来,给不同的平台用自己的库函数适配:

i.MX6ULL驱动开发 | 23 - Linux下的驱动分离与分层——platform平台驱动模型_linux内核_02


这样多种平台就可以共用同一份MPU6050驱动:

i.MX6ULL驱动开发 | 23 - Linux下的驱动分离与分层——platform平台驱动模型_驱动开发_03

2. Linux内核中的驱动分离

在Linux内核中,一般SOC的主机控制器驱动已经由半导体厂家写好了,比如这里imx6ull的i2c控制器驱动已经由NXP写好了。

而对于具体的设备,比如MPU6050等设备,其驱动程序也由设备厂商写好了。

我们要做的是提供设备信息即可,比如:设备连接到了哪个I2C接口上?支持的速率是多少?设备在总线上的从机地址是多少?等等。

这样设计之下,SOC的外设驱动只负责外设驱动,某个设备的驱动只负责设备驱动,使用时只需要让内核将二者联系起来即可。

i.MX6ULL驱动开发 | 23 - Linux下的驱动分离与分层——platform平台驱动模型_驱动开发_04


当我们向系统注册一个驱动的时候,内核就会在右侧的设备中查找有没有匹配的设备,同样,当我们向系统中注册一个设备的时候,内核就会在左侧的驱动中查找有没有匹配的驱动。

这个就是Linux内核中的总线(bus)、驱动(driver)、设备(device)模型,也称之为Linux内核中的驱动分离。

3. Linux内核中的驱动分层

分层的目的是为了在不同的层处理不同的内容

以input子系统为例,input子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸板等。

  • 设备原始驱动层:负责获取输入设备的原始值。
  • input核心层:处理各种IO模型,并且提供file_operations操作集合。

这样分层设计之后,在编写输入设别的驱动时,只需要考虑到如何上报输入事件即可,至于如何处理这些上报的输入事件,是上层需要做的事情,无需关注。

二、platform平台驱动模型

1. 为什么需要platform?

Linux内核中的驱动程序分离为:总线(bus)、驱动(driver)、设备(device)模型,但是有些SOC中有些外设是没有总线这个概念的。

为了解决此问题,Linux内核中提出了platform平台模型作为虚拟总线,相应的有platform_driver和platform_device。

2. platform总线

2.1. platform总线的类型——bus_type结构体

Linux内核中使用bus_type结构体表示总线,定义在文件​​include/linux/device.h​​,如下:

struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;

int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);

int (*online)(struct device *dev);
int (*offline)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);

const struct dev_pm_ops *pm;

const struct iommu_ops *iommu_ops;

struct subsys_private *p;
struct lock_class_key lock_key;
};

总线最重要的工作就是根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,该任务主要依赖match函数指针,所以每一条总线都必须实现此函数。

int (*match)(struct device *dev, struct device_driver *drv);

可以看到,match函数有两个参数:dev和drv。

  • dev:device类型,表示设备
  • drv:device_driver类型,表示驱动

2.2. platform总线

platform总线是bus_type的一个具体实例,定义在文件​​drivers/base/platform.c​​中,如下:

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

platform_bus_type就是platform平台总线,其中 platform_match 就是匹配函数。

2.3. 驱动和设备如何匹配

platform_match 函数定义在​​drivers/base/platform.c​​中,

/**
* platform_match - bind platform device to platform driver.
* @dev: device.
* @drv: driver.
*
* Platform device IDs are assumed to be encoded like this:
* "<name><instance>", where <name> is a short description of the type of
* device, like "pci" or "floppy", and <instance> is the enumerated
* instance of the device, like '0' or '42'. Driver IDs are simply
* "<name>". So, extract the <name> from the platform_device structure,
* and compare it against the name of the driver. Return whether they match
* or not.
*/
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);

/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);

/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;

/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;

/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}

从代码中可以看到,设备和驱动的匹配有四种方法。

(1)OF类型的匹配(设备树采用的匹配方式):根据设备节点的 compatible 属性和驱动的of_match_table表进行匹配;
(2)ACPI匹配方式
(3)id_table匹配
(4)直接比较驱动和设备的name字段是否相等

3. platform驱动(重点)

3.1. platform_driver结构体

platform_driver结构体表示platform驱动,定义在文件​​include/linux/platform_device.h​​中,如下:

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;
bool prevent_deferred_probe;
};

(1)probe函数

当驱动与设备匹配成功以后probe函数就会执行,由驱动的提供者编写。

(2)remove函数

驱动卸载的时候会执行

(3)driver成员

device_driver相当于基类,提供了最基础的驱动框架,platform_driver继承了这个基类。

(4)id_table表

(5)of_match_table表:设备树匹配表

3.2. platform驱动可用API

(1)向内核注册一个platform驱动

/**
* __platform_driver_register - register a driver for platform-level devices
* @drv: platform driver structure
* @owner: owning module/driver
*/
int __platform_driver_register(struct platform_driver *drv,
struct module *owner);

/*
* use a macro to avoid include chaining to get THIS_MODULE
*/
#define platform_driver_register(drv)\
__platform_driver_register(drv, THIS_MODULE)

(2)从内核卸载一个platform驱动

extern void platform_driver_unregister(struct platform_driver *);

4. platform设备

如果内核支持设备树,platform设备用设备树描述。Linux内核启动的时候会从设备树中读取信息,然后将其组织成platform_device结构体形式。

如果内核不支持设备树,可以直接用platform_device结构体描述。