天天看点

LINUX设备驱动模型之platform(平台)总线简介1、什么是platform(平台)总线?2、platform总线在内核的表现形式3、platform总线管理下的platform_device和platform_driver4、驱动和设备的匹配5、platform驱动6、platform总线下的驱动编写流程

@TOC

1、什么是platform(平台)总线?

内核版本:4.1.15(含设备树)

运行平台:IMX6ULL

相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。

那为什么需要platform总线呢?其实是Linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。因为对于usb设备、i2c设备、pci设备、spi设备等等,他们与cpu的通信都是直接挂在相应的总线下面与我们的cpu进行数据交互的,但是在我们的嵌入式系统当中,并不是所有的设备都能够归属于这些常见的总线,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。所以Linux驱动模型为了保持完整性,将这些设备挂在一条虚拟的总线上(platform总线),而不至于使得有些设备挂在总线上,另一些设备没有挂在总线上。Linux 提出了 platform 这个虚拟总线。

2、platform总线在内核的表现形式

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

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

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

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

3、platform总线管理下的platform_device和platform_driver

两个结构体platform_device和platform_driver对于任何一种Linux设备驱动模型下的总线都由两个部分组成:描述设备相关的结构体和描述驱动相关的结构体.

设备结构体源码在include\linux\platform_device.h中定义:

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};
           

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

4、驱动和设备的匹配

platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数。我们来看一下驱动和设备是如何匹配的, 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);
}

           

从上面的代码可以看出驱动和设备的匹配有四种方法,我们依次来看一下:

4.1、第一种匹配方式

OF 类型的匹配,也就是设备树采用的匹配方式,of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。 device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行。

of_driver_match_device 函数原型如下:

/**
 * of_driver_match_device - Tell if a driver's of_match_table matches a device.
 * @drv: the device_driver structure to test
 * @dev: the device structure to match against
 */
static inline int of_driver_match_device(struct device *dev,
					 const struct device_driver *drv)
{
	return of_match_device(drv->of_match_table, dev) != NULL;
}
           

device_driver 结构体表示如下:

struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;
	const char		*mod_name;	/* used for built-in modules */

	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */

	const struct of_device_id	*of_match_table;
	const struct acpi_device_id	*acpi_match_table;

	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;

	const struct dev_pm_ops *pm;

	struct driver_private *p;
};
           

of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为 of_device_id 结构体类型,此结构体定义在文件 include/linux/mod_devicetable.h 中,内容如下

struct of_device_id {
	 char name[32];
	 char type[32];
	 char compatible[128];
	 const void *data;
};
           

其中的of_match_table成员变量保存着驱动的compatible匹配表。

形如:

LINUX设备驱动模型之platform(平台)总线简介1、什么是platform(平台)总线?2、platform总线在内核的表现形式3、platform总线管理下的platform_device和platform_driver4、驱动和设备的匹配5、platform驱动6、platform总线下的驱动编写流程

4.2、第二种匹配方式

如果of_driver_match_device没有匹配到则使用acpi进行匹配。

首先从device中找到对应的acpi_device,找到acpi_device设备后就可以进行匹配了,acpi设备使用两种方式匹配

1、 acpi伙伴匹配

源码路径:drivers\acpi\scan.c

static struct acpi_device *acpi_companion_match(const struct device *dev)
{
	struct acpi_device *adev;
	struct mutex *physical_node_lock;

	adev = ACPI_COMPANION(dev);
	if (!adev)
		return NULL;

	if (list_empty(&adev->pnp.ids))
		return NULL;

	physical_node_lock = &adev->physical_node_lock;
	mutex_lock(physical_node_lock);
	if (list_empty(&adev->physical_node_list)) {
		adev = NULL;
	} else {
		const struct acpi_device_physical_node *node;

		node = list_first_entry(&adev->physical_node_list,
					struct acpi_device_physical_node, node);
		if (node->dev != dev)
			adev = NULL;
	}
	mutex_unlock(physical_node_lock);

	return adev;
}
           

该函数从acpi_device的pnp节点找到伙伴节点,并判断设备是否为该pnp节点上的第一个物理节点,果是物理节点则匹配成功。 为什么只使用第一个节点,注释上有说名

2、 id方式匹配

如果pnp节点没有匹配则使用__acpi_match_device函数匹配acpi_device和驱动支持的id

static const struct acpi_device_id *__acpi_match_device(
	struct acpi_device *device,
	const struct acpi_device_id *ids,
	const struct of_device_id *of_ids)
{
	const struct acpi_device_id *id;
	struct acpi_hardware_id *hwid;

	/*
	 * If the device is not present, it is unnecessary to load device
	 * driver for it.
	 */
	if (!device || !device->status.present)
		return NULL;

	list_for_each_entry(hwid, &device->pnp.ids, list) {
		/* First, check the ACPI/PNP IDs provided by the caller. */
		for (id = ids; id->id[0]; id++)
			if (!strcmp((char *) id->id, hwid->id))
				return id;

		/*
		 * Next, check the special "PRP0001" ID and try to match the
		 * "compatible" property if found.
		 *
		 * The id returned by the below is not valid, but the only
		 * caller passing non-NULL of_ids here is only interested in
		 * whether or not the return value is NULL.
		 */
		if (!strcmp("PRP0001", hwid->id)
		    && acpi_of_match_device(device, of_ids))
			return id;
	}
	return NULL;
}
           

4.3、第三种匹配方式

id_table 匹配,每个 platform_driver 结构体有一个 id_table成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型

形如:

LINUX设备驱动模型之platform(平台)总线简介1、什么是platform(平台)总线?2、platform总线在内核的表现形式3、platform总线管理下的platform_device和platform_driver4、驱动和设备的匹配5、platform驱动6、platform总线下的驱动编写流程

4.2、第四种匹配方式

如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。

对于支持设备树的 Linux 版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,也就是直接比较驱动和设备的 name 字段,毕竟这种方式最简单了。

形如:

LINUX设备驱动模型之platform(平台)总线简介1、什么是platform(平台)总线?2、platform总线在内核的表现形式3、platform总线管理下的platform_device和platform_driver4、驱动和设备的匹配5、platform驱动6、platform总线下的驱动编写流程

5、platform驱动

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

这个结构体当中有个probe函数, 当驱动与设备匹配成功以后 probe 函数就会执行。

driver 成员,为 device_driver 结构体变量, Linux 内核里面大量使用到了面向对象的思维, device_driver 相当于基类,提供了最基础的驱动框架。 plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量。我们在编写platfoem总线下的驱动的时候都是田中的这个结构体。

形如:

LINUX设备驱动模型之platform(平台)总线简介1、什么是platform(平台)总线?2、platform总线在内核的表现形式3、platform总线管理下的platform_device和platform_driver4、驱动和设备的匹配5、platform驱动6、platform总线下的驱动编写流程

id_table 表,也就是我们上一小节讲解 platform 总线匹配驱动和设备的时候采用的第三种方法, id_table 是个表(也就是数组),每个元素的类型为 platform_device_id,platform_device_id 结构体内容如下:

struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data;
};
           

6、platform总线下的驱动编写流程

①:首先定义一个 platform_driver 结构体变量

②:然后实现结构体中的各个成员变量,重点是实现匹配方法以及 probe 函数。

③:当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用platform_driver_register 函数向 Linux 内核注册一个 platform 驱动

④:驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动

其中platform_driver_register 与platform_driver_unregister 原型如下:

函数参数和返回值含义如下:

driver:要注册的 platform 驱动。

返回值: 负数,失败; 0,成功。

函数参数和返回值含义如下:

drv:要卸载的 platform 驱动。

返回值: 无。

其中框架流程如下:

struct xxx_dev{
	struct cdev cdev;
	/* 设备结构体其他具体内容 */
};

struct xxx_dev xxxdev; /* 定义个设备结构体变量 */

static int xxx_open(struct inode *inode, struct file *filp)
{
	/* 函数具体内容 */
	return 0;
}

static ssize_t xxx_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
	/* 函数具体内容 */
	return 0;
}

/*
* 字符设备驱动操作集
*/
static struct file_operations xxx_fops = {
	.owner = THIS_MODULE,
	 .open = xxx_open,
	.write = xxx_write,
};

/*
* platform 驱动的 probe 函数
* 驱动与设备匹配成功以后此函数就会执行
*/
static int xxx_probe(struct platform_device *dev)
{
	......
	cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
	/* 函数具体内容 */
	return 0;
}

static int xxx_remove(struct platform_device *dev)
{
	......
	cdev_del(&xxxdev.cdev);/* 删除 cdev */
	/* 函数具体内容 */
	return 0;
}
/* 匹配列表 */
static const struct of_device_id xxx_of_match[] = {
	{ .compatible = "xxx-gpio" },
	{ /* Sentinel */ }
};

/*
* platform 平台驱动结构体
*/
static struct platform_driver xxx_driver = {
	.driver = {
	.name = "xxx",
	.of_match_table = xxx_of_match,
	},
	.probe = xxx_probe,
	.remove = xxx_remove,
};

 /* 驱动模块加载 */
static int __init xxxdriver_init(void)
{
	return platform_driver_register(&xxx_driver);
}

/* 驱动模块卸载 */
static void __exit xxxdriver_exit(void)
{
	 platform_driver_unregister(&xxx_driver);
}

 module_init(xxxdriver_init);
 module_exit(xxxdriver_exit);
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("qingmu");
           

总体来说, platform 驱动还是传统的字符设备驱动、块设备驱动或网络设备驱动,只是套上了一张“platform” 的皮,目的是为了使用总线、驱动和设备这个驱动模型来实现驱动的分离与分层。

继续阅读