文章目录

  • 1. 前言
  • 2. 概念
    • 2.1. 数据结构
    • 2.2. probe函数
  • 3. bus、device、driver加载顺序
    • 3.1. 加载方式
    • 3.2. 加载顺序
  • 4. device、driver匹配流程
    • 4.1. 加载driver
    • 4.2. 加载device
  • 5. Reference

1. 前言

最近回看了下Linux设备驱动相关知识,做了个总结。有些话需要说在前面:

  • 文中有些内容为个人理解(上标H所标识内容),未必准确,有误请评论指正。
  • 4.2节的内容主要目的是为了搞清楚driver和device在加载的过程中是如何通过bus相互匹配。

本文源码源自4.10.17版本linux内核。

2. 概念

Linux设备驱动有三个基本概念:总线驱动以及设备。三者之间关系H三者之间关系^H三者之间关系H简单描述如下:

  1. 总线为外设或片内模组与核心处理器之间的通信总线,例如SPI、I2C、SDIO、USB等。
  2. 每个驱动、设备都需要挂载到一种总线上;
  3. 挂载到相同总线的驱动和设备可以通过该总线进行匹配,实现设备与对应的驱动之间的绑定。

2.1. 数据结构

以下为总线、驱动及设备在linux内核中对应的核心数据结构

// 总线数据结构 -- include/linux/device.h
struct bus_type {const char     *name;const char        *dev_name;struct device     *dev_root;struct device_attribute   *dev_attrs; /* use dev_groups instead */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;
};
// drivers/base/base.h
struct subsys_private {struct kset subsys;          // the struct kset that defines this subsystemstruct kset *devices_kset;    // the subsystem's 'devices' directorystruct list_head interfaces;struct mutex mutex;struct kset *drivers_kset;  // the list of drivers associatedstruct klist klist_devices;struct klist klist_drivers;struct blocking_notifier_head bus_notifier;unsigned int drivers_autoprobe:1;struct bus_type *bus;struct kset glue_dirs;struct class *class;
};
// 驱动数据结构 -- include/linux/device.h
struct device_driver {const char        *name;struct bus_type       *bus;   // 指向本驱动挂载的busstruct 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);    // 探测驱动与设备是否匹配,匹配则完成绑定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;    // 如下
};
// drivers/base/base.h
struct driver_private {struct kobject kobj;struct klist klist_devices;struct klist_node knode_bus;struct module_kobject *mkobj;struct device_driver *driver;
};
// 设备数据结构 -- include/linux/device.h
struct device {struct device        *parent;struct device_private   *p;struct kobject kobj;const char       *init_name; /* initial name of the device */const struct device_type *type;struct mutex     mutex;  /* mutex to synchronize calls to* its driver.*/struct bus_type  *bus;       /* type of bus device is on */struct device_driver *driver; /* which driver has allocated thisdevice */void     *platform_data; /* Platform specific data, devicecore doesn't touch it */void      *driver_data;   /* Driver data, set and get withdev_set/get_drvdata */struct dev_links_info links;struct dev_pm_info    power;struct dev_pm_domain  *pm_domain;#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAINstruct irq_domain    *msi_domain;
#endif
#ifdef CONFIG_PINCTRLstruct dev_pin_info    *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQstruct list_head   msi_list;
#endif#ifdef CONFIG_NUMAint     numa_node;  /* NUMA node this device is close to */
#endifu64       *dma_mask;  /* dma mask (if dma'able device) */u64     coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */unsigned long  dma_pfn_offset;struct device_dma_parameters *dma_parms;struct list_head dma_pools;  /* dma pools (if dma'ble) */struct dma_coherent_mem    *dma_mem; /* internal for coherent memoverride */
#ifdef CONFIG_DMA_CMAstruct cma *cma_area;      /* contiguous memory area for dmaallocations */
#endif/* arch specific additions */struct dev_archdata  archdata;struct device_node *of_node; /* associated device tree node */struct fwnode_handle *fwnode; /* firmware device node */dev_t            devt;   /* dev_t, creates the sysfs "dev" */u32           id; /* device instance */spinlock_t     devres_lock;struct list_head    devres_head;struct klist_node   knode_class;struct class        *class;const struct attribute_group **groups;   /* optional groups */void   (*release)(struct device *dev);struct iommu_group   *iommu_group;struct iommu_fwspec    *iommu_fwspec;bool          offline_disabled:1;bool         offline:1;
};

2.2. probe函数

bus_type和device_driver中都包含probe()函数,两者关系可追踪到函数really_probe()中(该函数在后续的调用加载流程中会描述具体执行的位置,可先跳过,后续查看加载流程时返回此处查看),如下:

static int really_probe(struct device *dev, struct device_driver *drv)
{int ret = -EPROBE_DEFER;int local_trigger_count = atomic_read(&deferred_trigger_count);bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&!drv->suppress_bind_attrs;...atomic_inc(&probe_count);pr_debug("bus: '%s': %s: probing driver %s with device %s\n",drv->bus->name, __func__, drv->name, dev_name(dev));WARN_ON(!list_empty(&dev->devres_head));re_probe:dev->driver = drv;/* If using pinctrl, bind pins now before probing */ret = pinctrl_bind_pins(dev);if (ret)goto pinctrl_bind_failed;if (driver_sysfs_add(dev)) {printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",__func__, dev_name(dev));goto probe_failed;}if (dev->pm_domain && dev->pm_domain->activate) {ret = dev->pm_domain->activate(dev);if (ret)goto probe_failed;}/** Ensure devices are listed in devices_kset in correct order* It's important to move Dev to the end of devices_kset before* calling .probe, because it could be recursive and parent Dev* should always go first*/devices_kset_move_last(dev);/*****  以下为重点  ****************/if (dev->bus->probe) {     // 若bus_type中定义了probe,则调用bus_type的proberet = dev->bus->probe(dev);if (ret)goto probe_failed;} else if (drv->probe) {   // 若bus_type中未定义了probe,则调用bus_type的proberet = drv->probe(dev);if (ret)goto probe_failed;}// 总结:在看过的有限几个总线源码里,bus_type.probe()最终依然会调用device_driver.probe()//      个人理解 —— device_driver.probe()是必然会调用的,//                 添加bus_type.probe()是因为有的总线需要在device_driver.probe()之前做些额外的处理/*****  重点已结束  ****************/...pinctrl_init_done(dev);if (dev->pm_domain && dev->pm_domain->sync)dev->pm_domain->sync(dev);driver_bound(dev);ret = 1;pr_debug("bus: '%s': %s: bound device %s to driver %s\n",drv->bus->name, __func__, dev_name(dev), drv->name);goto done;probe_failed:if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_DRIVER_NOT_BOUND, dev);
pinctrl_bind_failed:...ret = 0;
done:atomic_dec(&probe_count);wake_up(&probe_waitqueue);return ret;
}

3. bus、device、driver加载顺序

3.1. 加载方式


bus加载方式

以SPI总线为例(drivers/spi/spi.c),初始化函数加载调用统一使用postcore_initcall()将总线的初始化接口spi_init()添加到内核的启动序列中,跟踪一下该函数:

postcore_initcall(spi_init)  // drivers/spi/spi.c
-> #define postcore_initcall(fn) __define_initcall(fn, 2)    // include/linux/init.h

driver加载方式——动态加载、静态加载

驱动加载有两种方式:

  • 静态加载:驱动随着linux内核的启动自动加载
  • 动态加载:linux系统启动后,使用insmod命令加载

本节讨论的加载顺序为linux系统启动过程中的加载顺序,因此只考虑静态加载的情况。

驱动静态加载方式与总线类似,也是调用了统一的接口将驱动的初始化函数添加到内核的启动序列中。以基于SPI总线的驱动rtc-pcf2123(drivers/rtc/rtc-pcf2123.c)为例,驱动加载使用的统一接口为module_init()。下面代码可以示意驱动如何调用该函数,以及该函数如何将驱动初始化接口添加到内核启动序列中。

module_spi_driver(pcf2123_driver)    // drivers/rtc/rtc-pcf2123.c-> #define module_spi_driver(__spi_driver) \ // include/linux/spi/spi.hmodule_driver(__spi_driver, spi_register_driver, \spi_unregister_driver)-> #define module_driver(__driver, __register, __unregister, ...) \    //include/linux/device.hstatic int __init __driver##_init(void) \   // 对rtc-pcf2123驱动,这里定义了初始化函数pcf2123_driver_init(){ \return __register(&(__driver) , ##__VA_ARGS__); \    // 对rtc-pcf2123驱动,这里调用了接口spi_register_driver(&(pcf2123_driver))} \module_init(__driver##_init); \    static void __exit __driver##_exit(void) \{ \__unregister(&(__driver) , ##__VA_ARGS__); \} \module_exit(__driver##_exit);-> #define module_init(x)   __initcall(x);  // include/linux/module.h-> #define __initcall(fn) device_initcall(fn)   // include/linux/init.h-> #define device_initcall(fn) __define_initcall(fn, 6)   // include/linux/init.h

device加载方式——platform

linux内核使用devicetree描述具体硬件平台的设备参数,platform平台通过调用接口of_platform_default_populate_init(),将devicetree中的设备节点转换为内核可识别的platform设备数据结构platform_device,该结构中包含device结构。
linux内核启动后,调用arch_initcall_sync()接口将of_platform_default_populate_init()添加到启动序列中。跟踪该函数:

arch_initcall_sync(of_platform_default_populate_init)    // drivers/of/platform.c
-> #define arch_initcall_sync(fn) __define_initcall(fn, 3s)  // include/linux/init.h

3.2. 加载顺序

从bus、device、driver的加载方式可以看到,三者的初始化都调用了相同的接口__define_initcall(fn, id),函数定义及功能说明如下:

//   include/linux/init.h
#define __define_initcall(fn, id) \ static initcall_t __initcall_##fn##id __used \  __attribute__((__section__(".initcall" #id ".init"))) = fn;
/*************************/
// 功能说明
// 1. 定义名为__initcall_##fn##id的函数指针,指向fn函数
// 2. 将该函数指针存放在.initcall#id.init段中
// 3. .initcall#id.init段在内核初始化的过程中会被调用。
//    其中,id越小的.initcall#id.init段内的接口越早被调用

对该接口有兴趣,可以参照Reference2的详细说明。

由于bus、device、driver的id分别为2、3s、6,因此,初始化的顺序依次为:bus、device、driver

4. device、driver匹配流程

此节主要想讨论device和driver是如何找到可以匹配的彼此。因此,分别从driver和device两个方向,简述从初始化到调用probe()完成driver与device的匹配的过程。
首先做个说明,从后续描述将会看到,driver和device都挂载在总线上,因此二者向找到相互匹配的彼此,都需要通过bus这一媒介

4.1. 加载driver

driver还是以rtc-pcf2123驱动为例,从驱动的初始化函数pcf2123_driver_init()开始,调用流程如下:

pcf2123_driver_init()
-> spi_register_driver(&(pcf2123_driver))    // 前两步参照3.2节的代码说明吧-> __spi_register_driver(THIS_MODULE, &(pcf2123_driver))   // include/linux/spi/spi.h-> driver_register(&(&pcf2123_driver)->driver)  // include/linux/spi/spi.c-> bus_add_driver(&(&pcf2123_driver)->driver)   // drivers/base/driver.c// 重要地bus媒介在这里-> driver_attach(&(&pcf2123_driver)->driver)    // drivers/base/bus.c->  __driver_attach(dev, &(&pcf2123_driver)->driver) // drivers/base/dd.c// 该函数被循环调用,将&(&pcf2123_driver)->driver驱动// 依次与spi总线上已挂载的每个设备dev进行匹配-> driver_probe_device(&(&pcf2123_driver)->driver, dev) // drivers/base/dd.c-> really_probe(dev, &(&pcf2123_driver)->driver)  // drivers/base/dd.c// 可以参照2.2节中的接口说明
/***************************/
// 到此,该驱动完成初始化;且若总线上已经挂在了可匹配的设备,则完成匹配

4.2. 加载device

由于积累有限,还无法完全理解platform平台。因此,草率地将linux设备分为两种H草率地将linux设备分为两种^H草率地将linux设备分为两种H(命名只是为了区分我的这两种分类,非官方命名):

  • 总线设备:挂载与SPI、I2C等总线的设备
  • platform设备:挂载与platform总线的设备

两种设备初始化使用不同的处理流程,但是最终都会调用同一个接口device_add(),下面先介绍两种设备从初始化到device_add()接口的调用流程。


总线设备device_add()前流程

以I2C设备为例,设备初始化从接口i2c_new_device()开始,接口调用流程如下:

i2c_new_device(i2c_adapter *adap)    // drivers/i2c/i2c-core.c
-> device_register(struct device *dev)   // drivers/base/core.c-> device_add(dev) // drivers/base/core.c

platform设备device_add()前流程

platform设备就从初始化设备树节点的of_platform_default_populate_init()开始,接口调用流程如下:

of_platform_default_populate_init()  // drivers/of/platform.c
-> of_platform_default_populate()    // drivers/of/platform.c-> of_platform_populate()    // drivers/of/platform.c-> of_platform_bus_create()  // drivers/of/platform.c// 遍历devicetree的设备节点,每个节点调用一次该函数// 函数为自己及其children创建platform设备-> of_platform_device_create_pdata()    // drivers/of/platform.c-> of_device_add()   // drivers/of/device.c-> device_add()    // drivers/base/core.c-> bus_add_device() // drivers/base/bus.c// 完成设备向bus的添加

device与driver的匹配

上节device_add()调用bus_add_device()将设备添加到了bus的设备列表中。继续调用bus_probe_device(),已完成deice与driver的绑定。具体流程如下,设备从调用device_add()probe()接口完成device与driver的匹配。

device_add() // drivers/base/core.c
-> bus_probe_device(dev) // drivers/base/bus.c// 重要的bus媒介出现了-> device_initial_probe(dev)  // drivers/base/bus.c// 说句实话,这里没有经过运行验证,感觉应该走这个函数对应的分支-> __device_attach(dev, true)    // drivers/base/dd.c-> driver_probe_device(drv, dev) // drivers/base/dd.c-> really_probe(dev, drv)    // drivers/base/dd.c// 可以参照2.2节中的接口说明
/***************************/
// 到此,该设备完成初始化;且若总线上已经挂载了可匹配的驱动,则完成匹配

5. Reference

  1. 《学习笔记——《LINUX设备驱动程序(第三版)》Linux设备模型:内核添加、删除设备、驱动程序》
  2. module_init机制的理解
  3. 内核对设备树的处理(四)__device_node转换为platform_device

linux设备驱动——bus、device、driver加载顺序与匹配流程相关推荐

  1. linux设备模型bus,device,driver,(kobject、ktype、kset,bus_type、device、device_driver)

    1.1Linux设备驱动模型简介 1.什么是设备驱动模型 (1)类class.总线bus(负责将设备和驱动挂接起来).设备devices.驱动driver(可以看到在驱动源码中,不管是什么样的驱动,都 ...

  2. LINUX设备模型BUS,DEVICE,DRIVER

    虽然看了上面一篇转载的<使用/sys/访问系统>对总线,驱动,设备都讲得比较细但还是没有太多的感觉.在此就先把自己今天所学回忆一下. 为了满足新的要求,linux2.6提供了新的设备模型: ...

  3. linux设备驱动模型 - device/bus/driver

    在linux驱动模型中,为了便于管理各种设备,我们把不同设备分别挂在他们对应的总线上,设备对应的驱动程序也在总线上找,这样就提出了deivce-bus-driver的模型,硬件上有许多设备总线,那么我 ...

  4. linux设备驱动目录,Linux设备模型(5)_device和device driver

    Linux设备模型(5)_device和device driver 作者:wowo 发布于:2014-4-2 19:28 分类:统一设备模型 1. 前言 device和device driver是Li ...

  5. linux设备驱动子系统,Linux设备驱动子系统终极弹 - USB

    0. 预备理论 1. USB Core 2. USB Hub 3. USB OTG 4. USB Host 5. USB Gadget 6. USB Mass Storage USB博大精深,不是一两 ...

  6. Linux设备驱动开发(5.4.58)-3-NEWCHR

    本文目标 本文在上一节的基础上,采用可以指定主设备号,次设备号,手动申请dev_t和自动申请dev_t的API,封装一个全局的对象结构体并用private_data在方法间传递,减少全局变量使用,寄存 ...

  7. 深入浅出:Linux设备驱动之字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...

  8. 蜕变成蝶~Linux设备驱动之字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...

  9. linux设备驱动--字符设备模型

    linux设备驱动--字符设备模型 最近正在学习设备驱动开发,因此打算写一个系列博客,即是对自己学习的一个总结,也是对自己的一个督促,有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋 ...

最新文章

  1. java 组合框_Java知多少(88)列表和组合框
  2. Linux下通过设置PS1变量改变bash提示符颜色
  3. 汇总jQuery的61种选择器及示例
  4. 数据结构基础:栈(Stack)
  5. 浅谈Vue.js的优势
  6. Cannot obtain primary key information from the database, generated objects may be incomplete
  7. 使用 jQuery Mobile 与 HTML5 开发 Web App (十六) —— HTML5 Web Storage
  8. 删除eclipse或者MyEclipse的workspace记录
  9. 05-03 docker 常用命令
  10. arcgis 批量计算几何_计算几何_ArcGIS基础视频教程第一季_其他视频-51CTO学院
  11. 移动通信技术的未来发展趋势分析
  12. 各地级市系列环境指标数据(2003-2017年)
  13. 跨时区时间运算以及时间实时更新方法
  14. sql盲注特点_sql盲注
  15. c 语言除法运算,C 语言简单加减乘除运算
  16. docker的使用方法
  17. rockchip中的vop
  18. githubDesktop的使用说明(附下载链接)
  19. python最好用的助手_python 好用
  20. linux文件权限的例子,Linux基础教程之linux文件权限深度解读

热门文章

  1. 【CRISPR-Cas9神助攻】 Cas9稳定表达细胞系、CRISPR/Cas9敲除细胞系
  2. PCAN-UDS API实现uds协议的BootLoader刷写流程
  3. 小甲鱼python课件免费_小甲鱼零基础入门学习Python+全套源码课件-资源共享吧收集整理...
  4. c语言do while什么意思,C语言中do while语句的用法是什么?
  5. 智能手环PCB绘制之引脚
  6. Hashtable 的实现原理
  7. 相对定位、绝对定位与固定定位
  8. 访问我的路径C:\Users\14575\Documents\Tencent Files\1457518657\FileRecv\face\face
  9. 实体店店员收银不得不掌握的技巧在这里
  10. 【Android App】人脸识别中使用Opencv比较两张人脸相似程度实战(附源码和演示 超详细)