驱动开发中platform设备驱动架构详解

1、什么是platform总线

Linux2.6开始Linux加入了一套驱动管理和注册机制—platform总线驱动模型。platform总线是一条虚拟总线(只有一条),这类总线没有对应的硬件结构platform_device为相应的设备,platform_driver为相应的驱动。与传统的bus/device/driver机制相比,platform由内核统一进行管理,提高了代码的可移植性和安全性。

所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段。Linux总线设备驱动模型的框架如下图所示:
驱动开发中platform设备驱动架构详解_第1张图片
从图中我们可以很清楚的看出Linux platform总线设备驱动模型的整体架构。在总线设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。

当向内核注册驱动程序时,要调用platform_driver_register函数将驱动程序注册到总线,并将其放入所属总线的drv链表中,注册驱动的时候还会调用所属总线的match函数寻找该总线上与之匹配的设备,如果找到与之匹配的设备则会调用相应的probe函数将相应的设备和驱动进行绑定,这一匹配过程是由总线自动完成的。
参考:Linux总线、设备、驱动模型_babyzhaoshu521的博客-CSDN博客_linux总线驱动模型

2、Platform介绍

Platform 平台设备驱动模型的作用是将驱动的实现和资源分离,是一个虚拟的总线平台。这其中存在三个成员platform_busplatform_deviceplatform_driverplatform_deviceplatform_driver注册不分先后顺序。

platform_bus:由链表实现,不对应实际的物理总线。

platform_device:驱动的资源比如一些 I/O端口,中断号之类的。

platform_driver:驱动的功能实现比如 注册驱动,实现file_operations 等

3、platform_driver结构体

用于描述驱动的实现。通过platform_drivername成员匹配上deviceprobe函数会被调用,在设备拔出时系统会调用remove成员做清理工作。

struct platform_driver {
	int (*probe)(struct platform_device *);    // device和driver的name匹配成功后调用probe函数
	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;                // id_table->name没有设置时,使用driver->name 进行匹配
	const struct platform_device_id *id_table;
};
 
struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];    // 用来和platform_device->name进行匹配
	kernel_ulong_t driver_data;
};

4、platform主要函数

/* platform_device注册和卸载函数。 */
int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);
/* platform_driver注册和卸载函数。 */
int platform_driver_register(struct platform_driver *drv)void platform_driver_unregister(struct platform_driver *drv)
/* 获取platform_device中保存的资源 */
struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int num);
/* 获取platform_device的中断资源 */
int platform_get_irq(struct platform_device *dev, unsigned int num)
/* 用于批量注册平台设备 */
int platform_add_devices(struct platform_device **devs, int num) 

5、举个栗子

这是一个LED灯的驱动,我们就以LED中的platform_driver 为例进行分析:

struct platform_driver led_drv = {
	.probe		= led_probe,
	.remove		= led_remove,
	.driver		= {
		.name	= "myled",
		.of_match_table = of_match_leds, /* 能支持哪些来自于dts的platform_device */
	}
};

probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行,非常重要的函数。一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么 probe 就需要自行实现。

remove 函数,platform_driver 结构体中的 remove 成员变量,当关闭 platform设备驱动的时候此函数就会执行,以前在驱动卸载 exit 函数里面要做的事情就放到此函数中来。比如,使用 iounmap 释放内存、删除 cdev,注销设备号等等。

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

6、platform 驱动框架

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

第 1~27 行,传统的字符设备驱动,所谓的 platform 驱动并不是独立于字符设备驱动、块设备驱动和网络设备驱动之外的其他种类的驱动。platform 只是为了驱动的分离与分层而提出来的一种框架,其驱动的具体实现还是需要字符设备驱动、块设备驱动或网络设备驱动。

第 33~39 行,xxx_probe 函数,当驱动和设备匹配成功以后此函数就会执行,以前在驱动入口 init 函数里面编写的字符设备驱动程序就全部放到此 probe 函数里面。比如注册字符设备驱动、添加 cdev、创建类等等。

第 41~47 行,xxx_remove 函数,platform_driver 结构体中的 remove 成员变量,当关闭 platform设备驱动的时候此函数就会执行,以前在驱动卸载 exit 函数里面要做的事情就放到此函数中来。比如,使用 iounmap 释放内存、删除 cdev,注销设备号等等。

第 50~53 行,xxx_of_match 匹配表,如果使用设备树的话将通过此匹配表进行驱动和设备的匹配。 第 51 行设置了一个匹配项,此匹配项的 compatible 值为“xxx-gpio”,因此当设备树中设备节点的 compatible 属性值为“xxx-gpio”的时候此设备就会与此驱动匹配。

第 52 行是一个标记,of_device_id 表最后一个匹配项必须是空的

第 58 ~ 65 行,定义一个 platform_driver 结构体变量 xxx_driver,表示 platform 驱动,第 59~62行设置 paltform_driver 中的 device_driver 成员变量的 name 和 of_match_table 这两个属性。其中name 属性用于传统的驱动与设备匹配,也就是检查驱动和设备的 name 字段是不是相同。of_match_table 属性就是用于设备树下的驱动与设备检查。对于一个完整的驱动程序,必须提供有设备树和无设备树两种匹配方法。

第63 和 64 这两行设置 probe 和 remove 这两成员变量。

第68~71行,驱动入口函数,调用platform_driver_register函数向Linux内核注册一个platform驱动,也就是上面定义的 xxx_driver 结构体变量。

第 74~77 行,驱动出口函数,调用 platform_driver_unregister 函数卸载前面注册的 platform驱动。

7、platform总结

1)定义一个 platform_driver 结构体变量。

2)实现probe函数。

3)实现remove函数。

4)实现of_match_table。

5)调用platform_driver_register 函数向 Linux 内核注册一个 platform 驱动。

6)调用platform_driver_unregister 函数卸载 platform 驱动。

8、platform_device注册过程

platform_device的注册过程可以简化为以下过程:

struct platform_device *pdev;		// 定义一个平台设备并初始化
platform_device_register(pdev)		// 注册
	->>platform_device_add(pdev)
		->>device_add(&pdev->dev)
			->>bus_probe_device(&pdev->dev)
				->>device_attach(&pdev->dev)
					->>bus_for_each_drv(&(pdev->dev)->bus, NULL, &pdev->dev, __device_attach)
						->>__device_attach(drv, &pdev->dev)
							->>driver_probe_device(drv, &pdev->dev)
								->>really_probe(&pdev->dev, drv)
									->>&pdev->dev->bus->probe(dev) 或 drv->probe(dev)

到此Linux内核的总线设备驱动模型分析完毕。从上面的分析过程可以看出,所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段。

点击下方公众号卡片获取资料

你可能感兴趣的:(驱动开发学习笔记,Linux学习笔记,驱动开发,架构,linux)