1、什么是platform总线

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

所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段。Linux总线设备驱动模型的框架如下图所示:

从图中我们可以很清楚的看出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系统提供的一种附加手段。

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