IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> linux设备驱动——bus、device、driver加载顺序与匹配流程 -> 正文阅读

[嵌入式]linux设备驱动——bus、device、driver加载顺序与匹配流程

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 subsystem
	struct kset *devices_kset;	// the subsystem's 'devices' directory
	struct list_head interfaces;
	struct mutex mutex;

	struct kset *drivers_kset;	// the list of drivers associated
	struct 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;	// 指向本驱动挂载的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);	// 探测驱动与设备是否匹配,匹配则完成绑定
	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 this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	void		*driver_data;	/* Driver data, set and get with
					   dev_set/get_drvdata */
	struct dev_links_info	links;
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
	struct irq_domain	*msi_domain;
#endif
#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
	struct list_head	msi_list;
#endif

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations 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 mem
					     override */
#ifdef CONFIG_DMA_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#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的probe
		ret = dev->bus->probe(dev);
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {	// 若bus_type中未定义了probe,则调用bus_type的probe
		ret = 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.h
	       module_driver(__spi_driver, spi_register_driver, \
			             spi_unregister_driver)

   -> #define module_driver(__driver, __register, __unregister, ...) \	//include/linux/device.h
	  static 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平台。因此, 草率地将 l i n u x 设备分为两 种 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

device与driver的匹配

以下流程为,设备从调用bus_add_device()probe()接口完成device与driver的匹配

device_add(struct device *dev)	// 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
  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-09-30 01:07:48  更:2022-09-30 01:08:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 16:34:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码