如何修改windows系驱动加载顺序?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何修改windows系驱动加载顺序?相关的知识,希望对你有一定的参考价值。

参考技术A http://www.52pojie.cn/thread-89229-1-1.html

linux设备驱动——busdevicedriver加载顺序与匹配流程

文章目录

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

以上是关于如何修改windows系驱动加载顺序?的主要内容,如果未能解决你的问题,请参考以下文章

linux设备驱动——busdevicedriver加载顺序与匹配流程

Spark依赖包加载顺序

如何断开网络驱动器,不让其自动加载

java 类属性的加载顺序(带有继承关系的)

在 window.onload = function() 之后协调多个 Javascript 文件按顺序加载

linux启动顺序