嵌入式驱动学习第四周——platform总线

前言

   platform是Linux内核抽象出来的软件代码,用于设备与驱动的连接,设备与驱动通过总线进行匹配;匹配成功后会执行驱动中的probe函数,在probe函数中可以获取到设备的信息;

   嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!

1. platform总线介绍

1.1 总线式设备驱动

   外设与Soc的连接是通过接口,但是接口的不同本质上是通信协议的不同,于是内核就抽象出各种总线,将同一协议的设备连接在一起管理。

   总线分为物理总线和虚拟总线,物理总线是客观存在的,比如usb总线,有usb协议的设备连接在上面;虚拟总线式软件上模拟的,如platform总线,没有哪种设备用的platform协议,是内核为了统一用总线上方式管理设备虚拟出来的总线。

   使用总线的话,可以实现数据和方法分离,设备里包含了数据,驱动是硬件操作的方法。

   如下图所示,每个平台下的I2C控制器不同,因此主机驱动是必须的,但是对于每个SOC而言,mpu6050都是一样的,通过I2C的接口读写数据就行了,只需要一个MPU6050的驱动程序。如果再来几个 I2C 设备,比如 AT24C02、FT5206,那么只需要编写设备驱动,然后调用统一的I2C接口即可。

在这里插入图片描述
   当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。

1.2 platform总线

   并不是所有外设都有总线这个概念,比如LED,蜂鸣器,这种只是操作GPIO的,但是需要使用总线——驱动——设备模型怎么办呢,为此Linux提出platform这个虚拟总线。

   platform总线是虚拟总线,是软件层虚拟出来方便管理设备的,主要是管理类似LED灯这种比较简单,不需要专用接口的设备;

   比如LED设备,一个设备可能有好几个LED,不同的LED设备之间寄存器数目、操作方法是一样的,不一样的是寄存器的基地址不同、连接的gpio口不同,于是我们可以把不同的数据放在总线上设备信息里(描述设备的结构体里有描述资源),然后传给LED驱动,这样就可以实现一个驱动适配几个设备。

2. platform驱动框架

2.1 相关函数与结构体

   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 就需要自行实现。

   id_table 是个表( 也就是数组) ,每个元素的类型为 platform_device_id,其定义在include/linux/mod_devicetable.h

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

   driver 成员,为 device_driver 结构体变量,Linux 内核里面大量使用到了面向对象的思维,device_driver 相当于基类,提供了最基础的驱动框架。plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量。device_driver定义在 include/linux/device.h

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,用于将device和driver相匹配,定义在include/linux/device.h

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

   其中的compatible很重要,通过设备节点的compatible属性值和of_match_table中每个项目的compatible成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。

   在不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中。

/*
 * @description: 将设备信息注册到Linux内核中
 * @param-pdev : 要注册的platform设备
 * @return     : 负数,失败;0,成功
 */
int platform_device_register(struct platform_device *pdev)

   如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform设备

/*
 * @description: 注销掉对应的platform设备
 * @param-pdev : 要注销的platform设备
 * @return     : 无
 */
void platform_device_unregister(struct platform_device *pdev)

2.2 platform驱动框架

   首先要定义一个设备结构体并定义一个设备对象

struct xxx_dev {
	struct cdev cdev;
};

struct xxx_dev xxxdev;

   定义open,write等内核函数并设置file_operations结构体

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

   编写probe函数,驱动与设备匹配成功后此函数会执行,里面就是具体的驱动函数,比如字符设备驱动注册,添加cdev,创建类等。

static int xxx_probe(struct platform_device *dev)
{
	......
	cdev_init(&xxxdev.cdev, &xxx_fops);		// 注册字符设备驱动
	......		// 函数具体内容

	return 0;
}

   编写remove函数,卸载驱动的时候就会执行该函数,以前在exit函数中做的事情在此函数中,比如删除cdev,注销设备号。

static int xxx_remove(struct platform_device *dev)
{
	......
	cdev_init(&xxxdev.cdev, &xxx_fops);		// 注册字符设备驱动
	......		// 函数具体内容

	return 0;
}

   写匹配列表,即of_device_idcompatible属性,如果使用设备树的话将通过此匹配表进行驱动和设备的匹配。此匹配项的 compatible 值为“xxx”,因此当设备树中设备节点的 compatible 属性值为“xxx”的时候此设备就会与此驱动匹配。最后一行是一个标记,of_device_id 表最后一个匹配项必须是空的,因为相关的操作API会读取这个数组直到遇到一个空。

static const struct of_device_id xxx_of_match {
	{.compatible = "xxxx"},
	{ //Sentinel }
};

   platform平台驱动结构体,其中name 属性用于传统的驱动与设备匹配,也就是检查驱动和设备的 name 字段是不是相同。of_match_table 属性就是用于设备树下的驱动与设备检查。对于一个完整的驱动程序,必须提供有设备树和无设备树两种匹配方法

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("wp");

3. platform的匹配

   学会怎么用后,来看看一些底层的东西,那就是如何将设备与驱动之间进行匹配的。

   首先我们来看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函数,设备与驱动件的匹配就是match函数实现的,总线就是使用match函数根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找对应的设备,因此每条总线都必须实现该函数。

   match 函数有两个参数:dev 和 drv,分别为 device 和 device_driver 类型,即设备与驱动。

   而要介绍的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);

   其中找到match,对应的函数是platform_match,我们接下来看这个函数,其定义在drivers/base/platform.c中。

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种匹配方式。

  • 第一个if,of类型的匹配,即设备树采用的匹配方式,设备树中每个设备节点的compatible会和上面我们2中device_driver结构体中of_match_table的成员变量中存储的compatible匹配表进行比较,有的话就表示匹配成功,然后执行probe 函数;
  • 第二个if,ACPI匹配方式;
  • 第三个if,id_table匹配,每个platform_driver结构体有一个id_table成员变量,保存了很多id信息,这些 id 信息存放着这个 platformd 驱动所支持的驱动类型;
  • 第四个if,如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。

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

参考资料

[1] 【正点原子】I.MX6U嵌入式Linux驱区动开发指南 第五十四章、第五十五章
[2] platform总线
[3] 内核platform总线详解:定义、注册、匹配、使用示例

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-21 03:50:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-21 03:50:02       101 阅读
  3. 在Django里面运行非项目文件

    2024-03-21 03:50:02       82 阅读
  4. Python语言-面向对象

    2024-03-21 03:50:02       91 阅读

热门阅读

  1. 【无标题】

    2024-03-21 03:50:02       40 阅读
  2. QT 异常处理

    2024-03-21 03:50:02       39 阅读
  3. 【SpringBoot】优雅实现超大文件上传

    2024-03-21 03:50:02       35 阅读
  4. 【代码问题】mmcv+mmseg版本升级报错

    2024-03-21 03:50:02       44 阅读
  5. 【LAMMPS学习】三、构建LAMMPS(2)Make构建

    2024-03-21 03:50:02       45 阅读
  6. 2.4 ROC曲线是什么?

    2024-03-21 03:50:02       46 阅读