最全Linux驱动开发八股文(十五)

你好,我是拉依达。

这是我的Linux驱动开发八股文详细解析系列

本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。

全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料

现重新对内容进行整理,希望可以帮助到更多学习嵌入式的同学。

【下面是拉依达推荐学习相关专栏:】
一、Linux驱动学习专栏:拉依达的Linux驱动八股文 - 牛客网
二、Linux应用学习专栏:拉依达的Linux应用八股文 - 牛客网
【我的嵌入式学习和校招经验】 拉依达的嵌入式学习和秋招经验-CSDN博客
嵌入式学习规划/就业经验指导,可私信咨询

———————————————————————————————————————————————————

十五、Platform设备驱动框架

15.1 设备驱动的分层思想

Linux 内核完全由 C 语言和汇编语言写成, 但是却频繁用到了面向对象的设计思想

设备驱动方面,为同类的设备设计了一个框架框架中的核心层则实现了该设备通用的一些功能。同样的, 如果具体的设备不想使用核心层的函数, 它可以重载之。

return_type core_funca(xxx_device * bottom_dev, param1_type param1, param1_type param2)
{
    if(bottom_dev->funca)
        return bottom_dev->funca(param1, param2);
    
    /* 核心层通用的 funca 代码 */
  
    bottom_dev->funca_ops1(); 	/*通用的步骤代码 A */
    ...
    bottom_dev->funca_ops2();	/*通用的步骤代码 B */   
    ...
    bottom_dev->funca_ops3();	/*通用的步骤代码 C */
}

上述 core_funca 的实现中, 会检查底层设备是否重载了 funca(), 如果重载了, 就调用底层的代码, 否则直接使用通用层的。 这样做的好处是, 核心层的代码可以处理绝大多数该类设备的funca()对应的功能,只有少数特殊设备需要重新实现 funca()。

15.2 驱动的分离与分层

设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息 (比如从设备树中获取到设备信息),根据获取到的设备信息来初始化设备。

驱动只负责驱动,设备只负责设备,总线法将两者进行匹配

这就是 Linux 中的总线(bus)、驱动(driver)和设备(device)模型,即驱动分离

alt

当向系统注册一个驱动的时,总线会在设备中查找与之匹配的设备,如果有,就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在驱动中查找与之匹配的驱动,如果有,也联系起来。 Linux 内核中大量的驱动程序都采用总线、驱动和设备模式。

15.3 Platform 平台总线驱动模型

在 Linux 2.6 以后的设备驱动模型中, 需关心总线、 设备和驱动这 3 个实体, 总线将设备和驱动绑定

Linux 设备和驱动通常都需要挂接在一种总线上, 对于本身依附于 PCI、 USB、 I2C、 SPI 等的设备而言, 这自然不是问题。但是在嵌入式系统里面, 在 SoC 系统中集成的独立外设控制器、 挂接在 SoC内存空间的外设等却不依附于此类总线。

基于这一背景, Linux 发明了一种虚拟的总线称为 platform 总线, 相应的设备称为 platform_device, 驱动称为 platform_driver。 平台总线模型就是把原来的驱动C文件给分成了俩个 C 文件,一个是 device.c(描述硬件信息,设备树可替代), 一个是 driver.c(驱动信息)

alt

一、platform 驱动

在 Linux 内核中, 用platform_driver结构体表示platform驱动,platform_driver 结构体定义指定名称平台设备驱动注册函数平台设备驱动注销函数

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 函数就会执行, 一般驱动的提供者会编写
  • remove函数:当 driver 和 device 任意一个 remove 的时, 就会执行该函数
  • driver: device_driver 结构体变量, Linux 内核里面大量使用到了面向对象的思维, device_driver相当于基类,提供了最基础的驱动框架。 plaform_driver 继承了这个基类,在此基础上又添加了一些特有的成员变量
  • id_table 表保存 id 信息。 这些 id 信息存放着platformd 驱动所支持的驱动类型。 id_table是个表(数组), 每个元素的类型为 platform_device_id,

platform_device_id 结构体内容如下:

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

device_driver 结构体定义内容如下:

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 结构体类型

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

compatible: 在支持设备树的内核中, 就是通过设备节点的compatible属性值of_match_table中每个项目的 compatible 成员变量进行比较, 如果有相等的就表示设备和此驱动匹配成功

驱动和设备匹配成功后驱动会从设备里面获得硬件资源, 匹配成功了后, driver.c 要从 device.c(或者是设备树) 中获得硬件资源, 那么 driver.c 就是在 probe 函数中获得的。

二、platform 设备(可以被设备树替代)

在 platform 平台下用platform_device结构体表示platform设备, 如果内核支持设备树的话就不用使用 platform_device 来描述设备, 使用设备树去描述platform_device即可。

struct platform_device {
    const char *name;
    int id;
    bool id_auto;
    struct device dev;
    u32 num_resources;
    struct resource *resource;
 
    const struct platform_device_id *id_entry;
    char *driver_override; /* Driver name to force a match */
 
    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;
 
    /* arch specific additions */
    struct pdev_archdata archdata;
};
  • name:设备名字,要和所使用的 platform 驱动的 name 字段相同,否则设备就无法匹配到对应的驱动。比如对应的 platform 驱动的name字段为xxx-gpio,则此name字段也要设置为xxx-gpio。
  • id :用来区分如果设备名字相同的时,通过在后面添加一个数字来代表不同的设备
  • dev:内置的device结构体
  • num_resources :资源数量,一般为resource 资源的大小(个数),ARRAY_SIZE 来测量一个数组的元素个数。
  • resource:指向一个资源结构体数组,即设备信息,比如外设寄存器等。

Linux 内核使用 resource结构体表示资源

struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    struct resource *parent, *sibling, *child;
};

start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,表示内存起始和终止地址, name 表示资源名字, flags 表示资源类型

使用 platform_device_register 函数设备信息注册到 Linux 内核中

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

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

void platform_device_unregister(struct platform_device *pdev)
  • pdev:要注销的 platform 设备
  • 返回值: 无

三、platform 总线

platform设备和platform驱动,相当于把设备和驱动分离了, 需要 platform 总线进行配, platform 设备和 platform 驱动进行内核注册时, 都是注册到总线上。

在 Linux 内核中使用 bus_type 结构体表示总线

struct bus_type {
    const char *name; /* 总线名字 */
    const char *dev_name;
    struct device *dev_root;
    struct device_attribute *dev_attrs;
    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 函数有两个参数dev 和 drv,这两个参数分别为 device 和 device_driver 类型,即设备和驱动

platform 总线是 bus_type 的一个具体实例

struct bus_type platform_bus_type = {
    .name = "platform",
    .dev_groups = platform_dev_groups,
    .match = platform_match,
    .uevent = platform_uevent,
    .pm = &platform_dev_pm_ops,
};

platform_match 匹配函数, 用来匹配注册到 platform 总线的设备和驱动。

四、platform 总线具体匹配方法

查看platform_match函数,如何匹配驱动和设备的

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

驱动和设备的匹配有四种方法

  1. OF 类型的匹配 设备树采用的匹配方式,of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。

    device_driver 结构体(设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表

    设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行

  2. ACPI 匹配方式

  3. id_table 匹配 每个 platform_driver 结构体(设备驱动)有一个 id_table成员变量,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型

  4. 名字匹配 如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,如果相等的话就匹配成功

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

#嵌入式##校招##八股文##Linux##linux驱动#
拉依达的Linux驱动八股文 文章被收录于专栏

你好,我是拉依达。 这是我的Linux驱动开发八股文详细解析系列。 本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。 全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料。 现在我重新对内容进行整理,已专栏的形式发布在牛客上,希望可以帮助到更多学习嵌入式的同学。

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务