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

Posted Mculover666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了i.MX6ULL驱动开发 | 23 - Linux下的驱动分离与分层——platform平台驱动模型相关的知识,希望对你有一定的参考价值。

一、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结构体描述。

以上是关于i.MX6ULL驱动开发 | 23 - Linux下的驱动分离与分层——platform平台驱动模型的主要内容,如果未能解决你的问题,请参考以下文章

i.MX6ULL驱动开发 | 15 - Linux UART 驱动框架

i.MX6ULL驱动开发 | 09 -基于Linux自带的LED驱动点亮LED

i.MX6ULL驱动开发 | 08 -基于pinctrl子系统和gpio子系统点亮LED

i.MX6ULL驱动开发 | 08 -基于pinctrl子系统和gpio子系统点亮LED

i.MX6ULL驱动开发 | 13 - Linux SPI 驱动框架

i.MX6ULL驱动开发 | 13 - Linux SPI 驱动框架