一、Linux驱动的分离
1. 为什么需要驱动分离?
在嵌入式开发中,无论处理器如何更换,外设模块的操作都是一致的,比如有三个不同的平台都要驱动MPU6050传感器,最简单的方法是针对每个平台都写一份驱动:
显然这种处理方式太low了,MPU6050都是使用I2C接口操作的,对于不同的平台,只是I2C操作方式不一样,所以这里可以将I2C接口抽象出来,给不同的平台用自己的库函数适配:
这样多种平台就可以共用同一份MPU6050驱动:
2. Linux内核中的驱动分离
在Linux内核中,一般SOC的主机控制器驱动已经由半导体厂家写好了,比如这里imx6ull的i2c控制器驱动已经由NXP写好了。
而对于具体的设备,比如MPU6050等设备,其驱动程序也由设备厂商写好了。
我们要做的是提供设备信息即可,比如:设备连接到了哪个I2C接口上?支持的速率是多少?设备在总线上的从机地址是多少?等等。
这样设计之下,SOC的外设驱动只负责外设驱动,某个设备的驱动只负责设备驱动,使用时只需要让内核将二者联系起来即可。
当我们向系统注册一个驱动的时候,内核就会在右侧的设备中查找有没有匹配的设备,同样,当我们向系统中注册一个设备的时候,内核就会在左侧的驱动中查找有没有匹配的驱动。
这个就是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结构体描述。