linux platform框架

  1. linux platform框架
  2. 驱动的分离
  3. platform平台驱动模型简介
    1. platform总线
    2. platform驱动
      1. platform驱动框架如下所示:
    3. platform设备
  4. 实验程序编写

linux platform框架

驱动的分离

主机驱动和设备驱动分隔开来,比如I2C、SPI等等都会采用驱动分隔的方式来简化驱动的开发。在实际的驱动开发中,一般I2C主机控制器驱动已经由半导体厂家编写好了,而设备驱动一般也由设备器件的厂家编写好了,只需要提供设备信息即可,比如I2C设备的话提供设备连接到了哪个I2C接口上,I2C的速度是多少等等

​ 这样就相当于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可。这个就是Linux中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离

​ 前面引出了总线(bus)、驱动(driver)和设备(device)模型,比如I2C、SPI、USB等总线。在SOC中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?为了解决此问题,Linux提出了platform这个虚拟总线,相应的就有platfor m_driver和platform_device

platform平台驱动模型简介

platform只是为了驱动的分离与分层而提出来的一种框架,其驱动的具体实现还是需要字符设备驱动、块设备驱动或网络设备驱动

platform总线

在SOC中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?为了解决此问题,Linux提出了platform这个虚拟总线

linux6.12源码\linux-6.12.1\linux-6.12.1\include\linux\device\bus.h下

struct bus_type {
    const char        *name;
    const char        *dev_name;
    const struct attribute_group **bus_groups;
    const struct attribute_group **dev_groups;
    const struct attribute_group **drv_groups;

    int (*match)(struct device *dev, const struct device_driver *drv); //匹配函数
    int (*uevent)(const struct device *dev, struct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    void (*sync_state)(struct device *dev);
    void (*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);

    int (*num_vf)(struct device *dev);

    int (*dma_configure)(struct device *dev);
    void (*dma_cleanup)(struct device *dev);

    const struct dev_pm_ops *pm;

    bool need_parent_lock;
};
    

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

platform_bus_type就是platform平台总线的一个实例, 里面的成员platform_match函数在F:\linux6.12源码\linux-6.12.1\linux-6.12.1\drivers\base\platform.c下:

static int platform_match(struct device *dev, const 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 */
    /*of_driver_match_device函数定义在文件include/linux/of_device.h中。device_driver结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的compatible属性会和of_match_table表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后probe函数就会执行。*/
    if (of_driver_match_device(dev, drv))    //1.第一种设备树(DTB)采用的匹配方式
        return 1;

    /* Then try ACPI style match */
    if (acpi_driver_match_device(dev, drv))     //2.第二种ACPI匹配方式。
        return 1;

    /* Then try to match against the id table */    
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;    //3.第三种,id_table匹配,每个platform_driver结构体有一个id_table成员变量,这些id信息存放着这个platformd驱动所支持的驱动类型

    /* fall-back to driver name match */
    return (strcmp(pdev->name, drv->name) == 0); //第四种直接比较驱动和设备的name字段匹配方式
}

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

platform驱动

platform_driver结构体表示platform驱动

![platform drvier](/iamges/platform drvier.png)

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

  2. device_driver相当于基类,提供了最基础的驱动框架plaform_driver继承了这个基类,然后在此基础上又添加了一些特有的成员变量

  3. 基类里的of_match_table就是采用设备树的时候驱动使用的匹配表,同样是数组,里面成员变量compatible非常重要,因为对于设备树而言,就是通过设备节点的compatible属性值和of_match_table中每个项目的compatible成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。

此结构体定义在文件linux-6.12.1\include\linux\device\driver.h中,内容如下:

struct device_driver {
    const char        *name;
    const 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 */
    enum probe_type probe_type;

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

    int (*probe) (struct device *dev);    
    void (*sync_state)(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 attribute_group **dev_groups;

    const struct dev_pm_ops *pm;
    void (*coredump) (struct device *dev);

    struct driver_private *p;
};

platform驱动框架如下所示:

示例代码34.2.2.5 platform驱动框架 
/* 设备结构体 */ 
1  struct xxx_dev{ 
2      struct cdev cdev; 
3      /* 设备结构体其他具体内容 */ 
4  }; 
5 
6  struct xxx_dev xxxdev; /* 定义个设备结构体变量 */ 
7
8  static int xxx_open(struct inode *inode, struct file *filp) 
9  { 
10     /* 函数具体内容 */ 
11     return 0; 
12 } 
13 
14 static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) 
15 { 
16     /* 函数具体内容 */ 
17     return 0; 
18 } 
19 
20 /* 
21 * 字符设备驱动操作集 
22 */ 
23 static struct file_operations xxx_fops = { 
24     .owner = THIS_MODULE, 
25     .open = xxx_open, 
26     .write = xxx_write, 
27 }; 
28 
29 /* 
30 * platform驱动的probe函数 
31 * 驱动与设备匹配成功以后此函数就会执行 
32 */ 
33 static int xxx_probe(struct platform_device *dev) 
34 { 
35     ...... 
36     cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */ 
37     /* 函数具体内容 */ 
38     return 0; 
39 } 
40 
41 static int xxx_remove(struct platform_device *dev) 
42 { 
43     ...... 
44     cdev_del(&xxxdev.cdev);/* 删除cdev */ 
45     /* 函数具体内容 */ 
46     return 0; 
47 } 
48 
49 /* 匹配列表 */
50 static const struct of_device_id xxx_of_match[] = { 
51     { .compatible = "xxx-gpio" }, 
52     { /* Sentinel */ } 
53 }; 
54 
55 /* 
56 * platform平台驱动结构体 
57 */ 
58 static struct platform_driver xxx_driver = { 
59     .driver = { 
60     .name = "xxx", 
61     .of_match_table = xxx_of_match, 
62 }, 
63     .probe = xxx_probe, 
64     .remove = xxx_remove, 
65 }; 
66 
67 /* 驱动模块加载 */ 
68 static int __init xxxdriver_init(void) 
69 { 
70     return platform_driver_register(&xxx_driver); 
71 } 
72 
73 /* 驱动模块卸载 */ 
74 static void __exit xxxdriver_exit(void) 
75 { 
76     platform_driver_unregister(&xxx_driver); 
77 } 
78 
79 module_init(xxxdriver_init); 
80 module_exit(xxxdriver_exit); 
81 MODULE_LICENSE("GPL"); 
82 MODULE_AUTHOR("zuozhongkai");

platform设备

platform驱动已经准备好了,还需要platform设备,否则的话单单一个驱动也做不了什么。platform_device这个结构体表示platform设备,这里要注意,如果内核支持设备树的话就不要再使用platform_device来描述设备了,因为改用设备树去描述了

platform_device在 \linux-6.12.1\linux-6.12.1\include\linux\platform_device.h下定义:

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

    const struct platform_device_id    *id_entry;
    /*
     * Driver name to force a match.  Do not set directly, because core
     * frees it.  Use driver_set_override() to set or clear it.
     */
    const char *driver_override;

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

    /* arch specific additions */
    struct pdev_archdata    archdata;
};
  1. name表示设备名字,要和所使用的platform驱动的name字段相同,否则的话设备就无法匹配到对应的驱动。比如对应的platform驱动的name字段为“xxx-gpio”,那么此name字段也要设置为“xxx-gpio”。

  2. num_resources表示资源数量,一般为第8行resource资源的大小。

  3. resource表示资源,也就是设备信息,比如外设寄存器等。Linux内核使用resource结构体表示资源,resource结构体定义在include/linux/ioport.h文件里面,内容为:

ioport

start和end分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址,name表示资源名字,flags表示资源类型,可选的资源类型都定义在了文件include/linux/ioport.h里面,如下所示:

iodefine

​ 在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用platform_device_register函数将设备信息注册到Linux内核中,此函数原型如下所示:

int platform_device_register(struct platform_device *pdev)

对应着卸载函数

int platform_device_unregister(struct platform_device *pdev)

platform设备信息框架如下所示:

示例代码54.2.3.4 platform设备框架 
1  /* 寄存器地址定义*/ 
2  #define PERIPH1_REGISTER_BASE (0X20000000) /* 外设1寄存器首地址 */ 
3  #define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设2寄存器首地址 */ 
4  #define REGISTER_LENGTH 4 
5 
6  /* 资源 */ 
7  static struct resource xxx_resources[] = { 
8      [0] = { 
9          .start = PERIPH1_REGISTER_BASE, 
10         .end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1), 
11         .flags = IORESOURCE_MEM, 
12     }, 
13     [1] = { 
14         .start = PERIPH2_REGISTER_BASE, 
15         .end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1), 
16         .flags = IORESOURCE_MEM, 
17     }, 
18 }; 
19 
20 /* platform设备结构体 */ 
21 static struct platform_device xxxdevice = { 
22     .name = "xxx-gpio", 
23     .id = -1, 
24     .num_resources = ARRAY_SIZE(xxx_resources), 
25     .resource = xxx_resources, 
26 }; 
27 
28 /* 设备模块加载 */ 
29 static int __init xxxdevice_init(void) 
30 { 
31     return platform_device_register(&xxxdevice); 
32 } 
33 
34 /* 设备模块注销 */ 
35 static void __exit xxx_resourcesdevice_exit(void) 
36 { 
37     platform_device_unregister(&xxxdevice); 
38 } 
39 
40 module_init(xxxdevice_init); 
41 module_exit(xxxdevice_exit); 
42 MODULE_LICENSE("GPL");
43 MODULE_AUTHOR("zuozhongkai");

实验程序编写

在编写platform驱动的时候,首先定义一个platform_driver结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及probe函数。当驱动和设备匹配成功以后probe函数就会执行,具体的驱动程序在probe函数里面编写,比如字符设备驱动等等。当定义并初始化好platform_driver结构体变量以后,需要在驱动入口函数里面调用platform_driver_register函数向Linux内核注册一个platform驱动。对应驱动卸载函数中platform_driver_unregister函数卸载platform驱动


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com

×

喜欢就点赞,疼爱就打赏