• 驱动开发之platform总线


    1.前言

    在前面的实验以及提出的各种问题中,为了提高移植性,降低模块耦合度,提让模块高内聚,分离device与driver是一个必然的趋势了。为了解决这个问题,心心念念的platform总线出来。

    linux从2.6起就加入了一套新的驱动管理和注册的机制platform平台总线,是一条虚拟的总线,并不是一个物理的总线。

    platform平台总线连接了平台设备(Platform Devices)和平台驱动(Platform Drivers)。平台设备是通过结构体platform_device来表示的,包含了设备的名称、资源(如内存地址、中断号等)和设备特定的属性。而平台驱动则是通过结构体platform_driver来实现,定义了与设备交互的函数指针,如探测、移除、打开、关闭、读写等操作。

    2.platform总线工作原理

    device与driver的注册无先后顺序,不管是device注册还是driver注册,都会触发platrom总线的检测函数,检测另一端是否已经注册,完成校验,匹配。其他匹配过程,可以看一下下面这个图,一个大佬画得,很清晰明了(链接:参考第一个)

     2.1.platform 总线介绍

    2.1.1.总线结构体

    此结构体定义在文件 include/linux/device.h

    1. struct bus_type {
    2. const char *name; /* 总线名字 */
    3. const char *dev_name;
    4. struct device *dev_root;
    5. struct device_attribute *dev_attrs;
    6. const struct attribute_group **bus_groups; /* 总线属性 */
    7. const struct attribute_group **dev_groups; /* 设备属性 */
    8. const struct attribute_group **drv_groups; /* 驱动属性 */
    9. int (*match)(struct device *dev, struct device_driver *drv);
    10. int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    11. int (*probe)(struct device *dev);
    12. int (*remove)(struct device *dev);
    13. void (*shutdown)(struct device *dev);
    14. int (*online)(struct device *dev);
    15. int (*offline)(struct device *dev);
    16. int (*suspend)(struct device *dev, pm_message_t state);
    17. int (*resume)(struct device *dev);
    18. const struct dev_pm_ops *pm;
    19. const struct iommu_ops *iommu_ops;
    20. struct subsys_private *p;
    21. struct lock_class_key lock_key;
    22. };

     match 函数,就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱 动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数match 函数有 两个参数:dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。

    2.1.2.bus实例platform 总线

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

    1. struct bus_type platform_bus_type = {
    2. .name = "platform",
    3. .dev_groups = platform_dev_groups,
    4. .match = platform_match,
    5. .uevent = platform_uevent,
    6. .pm = &platform_dev_pm_ops,
    7. };

     这些函数实现都在drivers/base/platform.c内部,不需要我们去实现,但是作为学习可以看看。

    2.1.3.platform 总线的match函数

    目前支持的匹配规则有五种,如代码注释所示:

    1. static int platform_match(struct device *dev,struct device_driver *drv)
    2. {
    3. struct platform_device *pdev = to_platform_device(dev);
    4. struct platform_driver *pdrv = to_platform_driver(drv);
    5. /*When driver_override is set,only bind to the matching driver. 当device设置driver_override时,只绑定到匹配的驱动程序 */
    6. if (pdev->driver_override)
    7. return !strcmp(pdev->driver_override, drv->name);
    8. /* Attempt an OF style match first 第一种匹配方式, OF 类型的匹配,也就是设备树采用的匹配方式 */
    9. if (of_driver_match_device(dev, drv))
    10. return 1;
    11. /* Then try ACPI style match 第二种匹配方式,ACPI 匹配方式 */
    12. if (acpi_driver_match_device(dev, drv))
    13. return 1;
    14. /* Then try to match against the id table . 第三种匹配方式id_table 匹配,每个 platform_driver 结构体有一个 id_table成员变量,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型*/
    15. if (pdrv->id_table)
    16. return platform_match_id(pdrv->id_table, pdev) != NULL;
    17. /* fall-back to driver name match 第四种匹配方式,如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功*/
    18. return (strcmp(pdev->name, drv->name) == 0);
    19. }

     3.platform 总线的使用

    使用platform总线是很简单的,只需要实现的结构体的函数指针定义的函数,然后通过相应的接口注册进去即可。(使用很简单,如果使用很难,这个东西就不会那么出名了,懂的都懂

     3.1.platform 驱动

    1. struct platform_driver {
    2. int (*probe)(struct platform_device *);
    3. int (*remove)(struct platform_device *);
    4. void (*shutdown)(struct platform_device *);
    5. int (*suspend)(struct platform_device *, pm_message_t state);
    6. int (*resume)(struct platform_device *);
    7. struct device_driver driver;
    8. const struct platform_device_id *id_table;
    9. bool prevent_deferred_probe;
    10. };

     probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行。

    remove函数,当platform_driver_unregister(&led_driver);函数调用时,会调用bus_remove_driver函数,该函数里面会再调用driver_detach函数,最后一层一层下去调用remove函数,完成资源释放, 或者在dev释放的时候,也会触发remove函数进行资源释放。测试实验见第4节。

    probe ,remove函数一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么 probe 就需要自行实现。

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

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

    1. struct platform_device_id {
    2. char name[PLATFORM_NAME_SIZE];
    3. kernel_ulong_t driver_data;
    4. };

    3.1.1.struct device_driver结构体

    1. struct device_driver {
    2. const char *name;
    3. struct bus_type *bus;
    4. struct module *owner;
    5. const char *mod_name; /* used for built-in modules */
    6. bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
    7. const struct of_device_id *of_match_table;
    8. const struct acpi_device_id *acpi_match_table;
    9. int (*probe) (struct device *dev);
    10. int (*remove) (struct device *dev);
    11. void (*shutdown) (struct device *dev);
    12. int (*suspend) (struct device *dev, pm_message_t state);
    13. int (*resume) (struct device *dev);
    14. const struct attribute_group **groups;
    15. const struct dev_pm_ops *pm;
    16. struct driver_private *p;
    17. };

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

    1. struct of_device_id {
    2. char name[32];
    3. char type[32];
    4. char compatible[128];
    5. const void *data;
    6. };

    compatible 非常重要,因为对于设备树而言,就是通过设备节点的 compatible 属 性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备 和此驱动匹配成功。这是设备树匹配方法

    struct device_driver结构体里面的name就是最后一种匹配方法了。

    虽然有四种匹配方法,但是只要有一种方法存在,而且匹配失败了,剩下的匹配方法就不能进行,就认为无法匹配了,请注意!!!!

    3.2.platform 设备

    1. struct platform_device {
    2. const char *name;
    3. int id;
    4. bool id_auto;
    5. struct device dev;
    6. u32 num_resources;
    7. struct resource *resource;
    8. const struct platform_device_id *id_entry;
    9. char *driver_override; /* Driver name to force a match 强制匹配的驱动程序名 */
    10. /* MFD cell pointer */
    11. struct mfd_cell *mfd_cell;
    12. /* arch specific additions */
    13. struct pdev_archdata archdata;
    14. };
    name 表示设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设
    备就无法匹配到对应的驱动
    driver_override成员: 就是match里面的第一种强制匹配了
    num_resources 表示资源数量
    resource 表示资源,也就是设备信息,resource 结构体内容如下:
    1. struct resource {
    2. resource_size_t start;
    3. resource_size_t end;
    4. const char *name;
    5. unsigned long flags;
    6. struct resource *parent, *sibling, *child;
    7. };
    start end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止
    地址, name 表示资源名字, flags 表示资源类型,可选的资源类型都定义在了文件
    include/linux/ioport.h 里面,如:
    1. #define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
    2. #define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
    3. #define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
    4. #define IORESOURCE_MEM 0x00000200
    5. #define IORESOURCE_REG 0x00000300 /* Register offsets */
    6. #define IORESOURCE_IRQ 0x00000400
    7. #define IORESOURCE_DMA 0x00000800
    8. #define IORESOURCE_BUS 0x00001000

    一般是IORESOURCE_MEM与IORESOURCE_IRQ使用的比较多。

    3.3.驱动注册接口

    int platform_driver_register (struct platform_driver *driver)
    driver :要注册的 platform 驱动。
    返回值: 负数,失败; 0 ,成功。
    void platform_driver_unregister(struct platform_driver *drv)
    drv :要卸载的 platform 驱动。
    返回值: 无。

     3.4.设备注册和注销接口

    int platform_device_register(struct platform_device *pdev)
    pdev :要注册的 platform 设备。
    返回值: 负数,失败; 0 ,成功。
    void platform_device_unregister(struct platform_device *pdev)
    pdev :要注销的 platform 设备。
    返回值: 无。

    3.5.小结

    从接口看,就两组接口(4个接口),两个结构体,就可以完成一个platform总线设备开发,是不是感觉很简单。下面进行一些实验验证。

    4.测试实验

    例子来源:韦东山老师的例子:

    4.1.driver部分

    1. #include <linux/module.h>
    2. #include <linux/fs.h>
    3. #include <linux/errno.h>
    4. #include <linux/miscdevice.h>
    5. #include <linux/kernel.h>
    6. #include <linux/major.h>
    7. #include <linux/mutex.h>
    8. #include <linux/proc_fs.h>
    9. #include <linux/seq_file.h>
    10. #include <linux/stat.h>
    11. #include <linux/init.h>
    12. #include <linux/device.h>
    13. #include <linux/tty.h>
    14. #include <linux/kmod.h>
    15. #include <linux/gfp.h>
    16. #include <linux/platform_device.h>
    17. static int hello_probe(struct platform_device *pdev)
    18. {
    19. int i = 0;
    20. printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    21. struct resource *source[2];
    22. for (i = 0; i < 2; i++)
    23. {
    24. source[i] = platform_get_resource(pdev, IORESOURCE_MEM, i);
    25. if (!source[i])
    26. {
    27. dev_err(&pdev->dev, "No MEM resource for always on\n");
    28. continue;
    29. }
    30. printk(" MEM resource i:%d, start:%x, end:%x \r\n", i, source[i]->start, source[i]->end);
    31. }
    32. for (i = 0; i < 2; i++)
    33. {
    34. source[i] = platform_get_resource(pdev, IORESOURCE_IRQ, i);
    35. if (!source[i])
    36. {
    37. dev_err(&pdev->dev, "No IRQ resource for always on\n");
    38. continue;
    39. }
    40. printk(" IRQ resource i:%d, start:%x, end:%x \r\n", i, source[i]->start, source[i]->end);
    41. }
    42. return 0;
    43. }
    44. static int hello_remove(struct platform_device *pdev)
    45. {
    46. printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    47. return 0;
    48. }
    49. static struct platform_driver hello_driver =
    50. {
    51. .probe = hello_probe,
    52. .remove = hello_remove,
    53. .driver =
    54. {
    55. .name = "100ask_led",
    56. },
    57. };
    58. static int __init hello_drv_init(void)
    59. {
    60. int err;
    61. printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    62. err = platform_driver_register(&hello_driver);
    63. return err;
    64. }
    65. static void __exit hello_drv_exit(void)
    66. {
    67. printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    68. platform_driver_unregister(&hello_driver);
    69. }
    70. module_init(hello_drv_init);
    71. module_exit(hello_drv_exit);
    72. MODULE_LICENSE("GPL");

    4.2.deivce部分

    1. #include <linux/module.h>
    2. #include <linux/fs.h>
    3. #include <linux/errno.h>
    4. #include <linux/miscdevice.h>
    5. #include <linux/kernel.h>
    6. #include <linux/major.h>
    7. #include <linux/mutex.h>
    8. #include <linux/proc_fs.h>
    9. #include <linux/seq_file.h>
    10. #include <linux/stat.h>
    11. #include <linux/init.h>
    12. #include <linux/device.h>
    13. #include <linux/tty.h>
    14. #include <linux/kmod.h>
    15. #include <linux/gfp.h>
    16. #include <linux/platform_device.h>
    17. static struct resource resources[] = {
    18. [0] =
    19. {
    20. .start = (3<<8)|(1),
    21. .flags = IORESOURCE_IRQ,
    22. },
    23. [1] = {
    24. .start = 0x1000,
    25. .end = 0x1010,
    26. .flags = IORESOURCE_MEM,
    27. },
    28. };
    29. static void hello_dev_release(struct device *dev)
    30. {
    31. printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    32. }
    33. static struct platform_device hello_dev =
    34. {
    35. .name = "100ask_led",
    36. .dev =
    37. {
    38. .release = hello_dev_release,
    39. },
    40. .num_resources = ARRAY_SIZE(resources),
    41. .resource = resources,
    42. };
    43. static int __init hello_dev_init(void)
    44. {
    45. int err;
    46. printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    47. err = platform_device_register(&hello_dev);
    48. return err;
    49. }
    50. static void __exit hello_dev_exit(void)
    51. {
    52. printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    53. platform_device_unregister(&hello_dev);
    54. }
    55. module_init(hello_dev_init);
    56. module_exit(hello_dev_exit);
    57. MODULE_LICENSE("GPL");

     4.3.测试结果

    (1)先加载drv,再加载dev.然后卸载dev,再卸载drv.结果如下:

    可以发现,dev与drv匹配上之后,会触发drv的probe函数。当dev卸载的时候,也会触发drv的remove函数(前提drv没有被释放)。dev里面的release函数是在dev的platform_device_unregister函数调用之后触发的,与drv没关系

    数据获取也正常,当然数据是随便造的,这是普通设备的数据获取,设备树的数据是不一样的,等设备树章节,再介绍,获取数据,以及设备树下的匹配规则。

    (2)先加载dev,再加载drv,然后卸载drv,再卸载dev,结果如下:

     可以发现,dev与drv匹配上之后,会触发drv的probe函数。当drv卸载的时候,platform_driver_unregister函数也会触发drv的remove函数,原理可见3.1小节。

    4.4.通过id_table进行匹配

    1. static const struct platform_device_id led_id_table[] = {
    2. {"hellodevice1", 1},
    3. {"hellodevice2", 2},
    4. {"100ask_led", 3},
    5. { },
    6. };
    7. static struct platform_driver hello_driver =
    8. {
    9. .probe = hello_probe,
    10. .remove = hello_remove,
    11. .driver =
    12. {
    13. .name = "100ask_led1",
    14. },
    15. .id_table = led_id_table,
    16. };
    id_table匹配只需要改一下drv的参数即可,dev不需要改动。测试结果和上面测试一样。

    4.5.driver_override强制匹配

    1. static struct platform_device hello_dev =
    2. {
    3. .name = "100ask_led1",
    4. .dev =
    5. {
    6. .release = hello_dev_release,
    7. },
    8. .num_resources = ARRAY_SIZE(resources),
    9. .resource = resources,
    10. .driver_override = "100ask_led1",
    11. };

    强制匹配,只需要修改dev里面的参数,这个名字必须得与platform_driver结构体下面的.driver成员里面的name匹配上,才可以触发probe函数。

    ok,目前platform总线就这样了,与设备树的使用,将在下一章进行介绍。

    参考:

    一张图掌握 Linux platform 平台设备驱动框架!【建议收藏】-CSDN博客

    手把手教Linux驱动10-platform总线详解_linux驱动中platform总线驱动的ppt-CSDN博客

    Linux驱动开发(二)---驱动与设备的分离设计_linux driver device 分离-CSDN博客

  • 相关阅读:
    JVM命令行监控工具
    用 strace 跟踪系统命令的调用
    Seata 1.5.2 源码学习
    以一次 Data Catalog 架构升级为例聊业务系统的性能优化
    C语言,编写程序输出半径为1到15的圆的面积,若面积在30到100之间则予以输出,否则,不予输出
    【自然语言处理(NLP)】基于ERNIE-GEN的中文自动文摘
    GitLab故障排查
    java.sql.SQLException: Cannot set billDate: incompatible types.
    树状数组笔记
    【程序员表白大师】html七夕脱单必看源码制作
  • 原文地址:https://blog.csdn.net/m0_46392035/article/details/139275208