驱动加载模块以及与设备树匹配调用流程分析

1.module_init函数是如何在kernel中加载调用运行起来的:

首先调用module_init(x)函数进来

在kernel源码目录中找到include\linux\init.h头文件,在这里面定义了module_init()

    #define   module_init(x)    __initcall(x)

-> #define __initcall(fn)     device_initcall(fn)

->#define device_initcall(fn)       __define_initcall(fn, 6)

->#define __define_initcall(fn, id)       __define_initcall(fn, id, .initcall##id)

#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS    //使用汇编代码来定义initcall
#define ___define_initcall(fn, id, __sec)			\
	__ADDRESSABLE(fn)					\
	asm(".section	\"" #__sec ".init\", \"a\"	\n"	\
	"__initcall_" #fn #id ":			\n"	\
	    ".long	" #fn " - .			\n"	\
	    ".previous					\n");
#else
#ifdef CONFIG_LTO_CLANG				//使用Clang编译器,并启用了LTO功能,就会使用另外一种方式来定义initcall
  #define ___lto_initcall(c, l, fn, id, __sec) \
	static initcall_t __initcall_##c##_##l##_##fn##id __used \
		__attribute__((__section__( #__sec \
			__stringify(.init..##c##_##l##_##fn)))) = fn;
  #define __lto_initcall(c, l, fn, id, __sec) \
	___lto_initcall(c, l, fn, id, __sec)

  #define ___define_initcall(fn, id, __sec) \
	__lto_initcall(__COUNTER__, __LINE__, fn, id, __sec)
#else				//使用静态变量来定义initcall
  #define ___define_initcall(fn, id, __sec) \
	static initcall_t __initcall_##fn##id __used \
		__attribute__((__section__(#__sec ".init"))) = fn;	//这个宏用于定义一个在系统启动时调用的初始化函数
#endif

        __attribute__((__section__(#__sec ".init"))) = fn //__attribute__用来指定变量或结构位域的特殊属性,其后的双括弧中的内容是属性说明,这里的attribute-list为__section__(“.initcall6.init”)。通常,编译器将生成的代码存放在.text段中。但有时可能需要其他的段,或者需要将某些函数、变量存放在特殊的段中,section属性就是用来指定将一个函数、变量存放在特定的段中。即该函数添加到内核的初始化函数列表中。最后,当系统启动时,内核会按照初始化函数列表中的顺序调用这些函数,以完成设备驱动程序的初始化操作。 该宏扩展为一个静态变量声明__initcall_##fn##id,类型为initcall_t,具有__used属性和__section__属性。__section__属性指定该变量应该放置在由#__sec参数命名的部分中,并跟随函数fn。##运算符用于将fn和id参数连接成一个单一的标识符。生成的标识符用于静态变量声明和部分名称的一部分。

总体而言,这个宏用于定义一个在系统启动时调用的初始化函数。__initcall_##fn##id变量放置在可执行文件的特定部分中,并在启动时执行。__used属性用于防止编译器优化掉变量声明。它的作用是为一个静态变量定义初始化函数,并将这个初始化函数放在特定的代码段(section)中。在程序启动时,系统会按照一定的顺序调用这些初始化函数,以完成系统初始化的工作。这个段的位置在哪里,就要根据自己的处理器平台找到对应的连接脚本。例如在arm64架构中,在arch/arm64/kernel/vmlinux.lds这个链接脚本里面有如下一段代码:

 __initcall_start = .; KEEP(*(.initcallearly.init)) __initcall0_start = .; KEEP(*(.initcall0.init)) __initcall0s_start = .; KEEP(*(.initcall0s.init)) __initcall1_start = .; KEEP(*(.initcall1.init)) __initcall1s_start = .; KEEP(*(.initcall1s.init)) __initcall2_start = .; KEEP(*(.initcall2.init)) __initcall2s_start = .; KEEP(*(.initcall2s.init)) __initcall3_start = .; KEEP(*(.initcall3.init)) __initcall3s_start = .; KEEP(*(.initcall3s.init)) __initcall4_start = .; KEEP(*(.initcall4.init)) __initcall4s_start = .; KEEP(*(.initcall4s.init)) __initcall5_start = .; KEEP(*(.initcall5.init)) __initcall5s_start = .; KEEP(*(.initcall5s.init)) __initcallrootfs_start = .; KEEP(*(.initcallrootfs.init)) __initcallrootfss_start = .; KEEP(*(.initcallrootfss.init)) __initcall6_start = .; KEEP(*(.initcall6.init)) __initcall6s_start = .; KEEP(*(.initcall6s.init)) __initcall7_start = .; KEEP(*(.initcall7.init)) __initcall7s_start = .; KEEP(*(.initcall7s.init)) __initcall_end = .;
  __con_initcall_start = .; KEEP(*(.con_initcall.init)) __con_initcall_end = .;
  __security_initcall_start = .; KEEP(*(.security_initcall.init)) __security_initcall_end = .;
  . = ALIGN(4); __initramfs_start = .; KEEP(*(.init.ramfs)) . = ALIGN(8); KEEP(*(.init.ramfs.info))
  *(.init.rodata.* .init.bss)

其中例如__initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) 。其中__initcall6_start是一个符号,链接器用到的。__initcall6_start = .; ,其中的 '.'符号是对当前地址的一个引用,也就说把当前的地址给了符号__initcall6_start, *(.initcall6.init) *(.initcall6s.init) 的意思是所有的.initcall6.init段和.initcall6s.init段的内容从__initcall6_start为起始地址开始链接。

在kernel启动过程中,会调用do_initcalls函数一次调用我们通过xxx_initcall注册的各种函数,优先级高的先执行。所以我们通过module_init注册的函数在kernel启动的时候会被顺序执行。

然后分析kernel启动过程又是如何执行我们注册的init函数,当bootloader加载完kernel并解压并放置与内存中准备开始运行,首先被调用的函数是start_kernel();调用流程如下:

start_kernel -> reset_init -> kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
				                                	|
				                 	                |->static int __ref kernel_init(void *unused)
																										|
																										|-> kernel_init_freeable( )
																													|
																													|-> do_basic_setup();
																															|
																															|——> do_initcalls();

start_kenel   -> rest_init() -> kernel_thread(kernel_init, NULL, CLONE_FS) -> kernel_init_freeable() -> do_basic_setup() -> do_initcalls() -> do_initcall_level(level)

其中start_kernel函数主要完成以下工作:

  1. 初始化系统的基本功能,包括内存管理、中断处理、调度器、定时器、系统调用等。

  2. 加载并初始化内核模块,这些模块包含了许多设备驱动程序和文件系统等功能。

  3. 初始化各种子系统,例如CPU调度、进程管理、网络管理、驱动程序管理等。

  4. 启动第一个用户进程。

具体do_initcalls()函数原型如下:

static void __init do_initcalls(void)
{
	int level;

	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
		do_initcall_level(level);
}

这里通过调用一个for循环来完成对initcall_levels表的函数调用,表在main.c文件中,内容如下:

extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];

具体根据函数do_initcall_level(level)函数来执行对应的表,表中的数字也对应相对的优先级。由此,所有的**_init()函数被全部调用起来。

具体do_initcall_level()函数如下,在main.c文件中:

static void __init do_initcall_level(int level)
{
	extern const struct kernel_param __start___param[], __stop___param[];
	initcall_t *fn;
 
	strcpy(static_command_line, saved_command_line);
	parse_args(initcall_level_names[level],
		   static_command_line, __start___param,
		   __stop___param - __start___param,
		   level, level,
		   &repair_env_string);
 
	for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
		do_one_initcall(*fn);
}

函数中有一个名为level的参数,表示要执行的初始化级别。该函数通过调用parse_args函数解析内核启动参数,并将其存储在静态变量static_command_line中。

接下来,函数使用一个指针fn遍历指定级别的初始化函数列表initcall_levels[level]到initcall_levels[level+1],对于列表中的每个函数,调用do_one_initcall函数执行初始化操作。

具体do_one_initcall()函数内容如下:

int __init_or_module do_one_initcall(initcall_t fn)
{
	int count = preempt_count();
	char msgbuf[64];
	int ret;

	if (initcall_blacklisted(fn))
		return -EPERM;

	do_trace_initcall_start(fn);
	ret = fn();
	do_trace_initcall_finish(fn, ret);

	msgbuf[0] = 0;

	if (preempt_count() != count) {
		sprintf(msgbuf, "preemption imbalance ");
		preempt_count_set(count);
	}
	if (irqs_disabled()) {
		strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
		local_irq_enable();
	}
	WARN(msgbuf[0], "initcall %pF returned with %s\n", fn, msgbuf);

	add_latent_entropy();
	return ret;
}

这段代码定义了一个名为do_one_initcall的函数,用于执行一个内核初始化函数(即一个initcall_t类型的函数指针),并返回其返回值。

函数首先保存当前的抢占计数,并声明一个名为msgbuf的字符数组,用于记录一些调试信息。然后,它检查该函数是否在黑名单中,如果是,则返回错误码-EPERM,表示权限不足。

接下来,函数调用do_trace_initcall_start函数和do_trace_initcall_finish函数,这两个函数用于跟踪内核初始化函数的执行情况,包括开始和结束时间,以及返回值。

在函数执行完成后,函数会检查是否存在一些异常情况,如抢占不平衡或中断被禁止。如果有异常,它会向msgbuf中添加相关的调试信息,并打印一条警告消息,提示初始化函数返回的状态。

最后,函数调用add_latent_entropy函数,用于向内核池中添加一些随机数,增加系统的熵值。然后,函数返回该初始化函数的返回值。

2.在module_init()函数中,iic总线传入的结构体是如何把probe函数调用起来的:

首先通过dump_stack()函数打印堆栈找到对应的调用函数:

第一步,把结构体传入i2c_add_driver(driver)函数中,

#define i2c_add_driver(driver)          i2c_register_driver(THIS_MODULE, driver)

i2c_register_driver(THIS_MODULE, driver)函数原型如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;

	/* Can't register until after driver model init */
	if (WARN_ON(!is_registered))	//检查驱动模型是否已经初始化
		return -EAGAIN;

	/* add the driver to the list of i2c drivers in the driver core */
	driver->driver.owner = owner;  //标识拥有驱动程序的模块
	driver->driver.bus = &i2c_bus_type; //标识驱动程序所关联的i2c总线
	INIT_LIST_HEAD(&driver->clients); //初始化与驱动程序相关的IIc客户端列表

	/* When registration returns, the driver core
	 * will have called probe() for all matching-but-unbound devices.
	 */
	res = driver_register(&driver->driver); //将驱动程序注册到驱动程序核心
	if (res)
		return res;

	pr_debug("driver [%s] registered\n", driver->driver.name);

	/* Walk the adapters that are already present */
	i2c_for_each_dev(driver, __process_new_driver);//此函数检查是否有任何与正在注册的驱动程序匹配但未绑定的设备

	return 0;
}

这段代码定义了i2c_register_driver()函数的实现,该函数将I2C驱动程序注册到内核的I2C子系统中。函数接受两个参数:一个指向标识注册驱动程序的模块的struct module结构的指针,和一个指向包含要注册的驱动程序信息的struct i2c_driver结构的指针。

该函数首先检查驱动程序模型是否已初始化,如果没有,则返回错误代码。在注册任何驱动程序之前必须初始化驱动程序模型。

接下来,该函数初始化驱动程序的owner和bus字段,分别用于标识拥有驱动程序的模块和驱动程序所关联的I2C总线。它还初始化与驱动程序关联的I2C客户端列表。

然后,函数调用driver_register()函数将驱动程序注册到驱动程序核心。如果注册成功,则函数会打印调试消息,指示驱动程序已注册。

最后,该函数遍历所有已经存在的适配器,为每个适配器调用__process_new_driver()函数。此函数检查是否有任何与正在注册的驱动程序匹配但未绑定的设备,并为每个匹配但未绑定的设备调用probe()函数。

如果注册成功,则函数返回0。如果在注册过程中发生错误,则函数返回错误代码。

下一步,实现driver_register(struct device_driver *drv)函数,函数原型如下:

int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	if (!drv->bus->p) {
		pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n",
			   drv->name, drv->bus->name);  //检查驱动程序所属的总线是否已经初始化
		return -EINVAL;
	}

	if ((drv->bus->probe && drv->probe) ||
	    (drv->bus->remove && drv->remove) ||
	    (drv->bus->shutdown && drv->shutdown))
		printk(KERN_WARNING "Driver '%s' needs updating - please use "
			"bus_type methods\n", drv->name);  //检查驱动程序是否需要更新

	other = driver_find(drv->name, drv->bus);
	if (other) {
		printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name); //检查驱动程序是否已经注册过
		return -EBUSY;
	}

	ret = bus_add_driver(drv); //将驱动程序添加到总线上
	if (ret)
		return ret;
	ret = driver_add_groups(drv, drv->groups); //为该驱动程序添加所属组
	if (ret) {
		bus_remove_driver(drv);
		return ret;
	}
	kobject_uevent(&drv->p->kobj, KOBJ_ADD); //向内核发送时间通知,告知设备程序已经被添加到内核中

	return ret;
}

这段代码实现了driver_register()函数,用于将设备驱动程序注册到内核中。函数接受一个指向要注册的设备驱动程序的struct device_driver结构体的指针作为参数。

首先,函数检查驱动程序所属的总线是否已经初始化,如果没有,则返回错误。如果驱动程序所属的总线未初始化,则无法注册驱动程序。

接下来,函数检查驱动程序是否需要更新,如果驱动程序使用了已经弃用的方法,则会发出警告信息。

然后,函数调用driver_find()函数来检查驱动程序是否已经注册过。如果该驱动程序已经被注册,则返回错误。

接下来,函数调用bus_add_driver()函数将驱动程序添加到总线中。如果添加失败,则返回错误。如果添加成功,则调用driver_add_groups()函数为该驱动程序添加属性组。如果添加属性组失败,则移除已添加的驱动程序并返回错误。

最后,函数调用kobject_uevent()函数,向内核发送事件通知,告知设备驱动程序已经被添加到内核中。

如果函数执行成功,则返回0或正整数。如果函数执行失败,则返回错误代码。

下一步实现bus_add_driver(struct device_driver *drv)函数,函数原型如下:

int bus_add_driver(struct device_driver *drv)
{
	struct bus_type *bus;
	struct driver_private *priv;
	int error = 0;

	bus = bus_get(drv->bus);  //获取设备驱动所属的总线类型的bus_type结构体
	if (!bus)
		return -EINVAL;

	pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv) {
		error = -ENOMEM;
		goto out_put_bus;
	}
	klist_init(&priv->klist_devices, NULL, NULL);
	priv->driver = drv;
	drv->p = priv;
	priv->kobj.kset = bus->p->drivers_kset;
	error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
				     "%s", drv->name); //将priv->kobj添加到内核对象层次结构中
	if (error)
		goto out_unregister;

	klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); //将设备驱动添加到总线类型的链表的尾部
	if (drv->bus->p->drivers_autoprobe) {
		error = driver_attach(drv);  //将驱动程序连接到总线上的所有设备
		if (error)
			goto out_unregister;
	}
	module_add_driver(drv->owner, drv); 

	error = driver_create_file(drv, &driver_attr_uevent);
	if (error) {
		printk(KERN_ERR "%s: uevent attr (%s) failed\n",
			__func__, drv->name);
	}
	error = driver_add_groups(drv, bus->drv_groups);
	if (error) {
		/* How the hell do we get out of this pickle? Give up */
		printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",
			__func__, drv->name);
	}

	if (!drv->suppress_bind_attrs) {
		error = add_bind_files(drv);
		if (error) {
			/* Ditto */
			printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
				__func__, drv->name);
		}
	}

	return 0;

out_unregister:
	kobject_put(&priv->kobj);
	/* drv->p is freed in driver_release()  */
	drv->p = NULL;
out_put_bus:
	bus_put(bus);
	return error;
}

bus_add_driver() 函数的作用是将设备驱动注册到总线上,并执行与此相关的其他操作,如创建驱动文件和自动探测设备。

函数首先使用 bus_get() 函数获取设备驱动所属的总线类型的 bus_type 结构体,如果获取失败则返回 -EINVAL 错误。然后创建一个 driver_private 结构体,该结构体用于存储与设备驱动相关的数据。之后通过 kobject_init_and_add() 函数将驱动对象 priv->kobj 添加到内核对象层次结构中,并将 driver_ktype 作为对象类型。如果添加失败则返回错误并在 out_unregister 标签处释放相关资源并返回错误码。

接下来,将设备驱动添加到总线类型的 klist_drivers 链表的末尾,并使用 driver_attach() 函数将驱动程序连接到总线上的所有设备。如果在连接驱动程序时发生错误,则返回错误并在 out_unregister 标签处释放相关资源并返回错误码。

最后,通过调用 module_add_driver() 将驱动程序与其所有者模块相关联,并分别通过调用 driver_create_file() 和 driver_add_groups() 函数创建驱动程序的 uevent 文件和与总线关联的驱动程序分组。如果在创建文件或分组时发生错误,则会打印相应的错误消息,但不会导致函数返回错误码。

如果在创建所有必需的文件和分组后,还没有出现任何错误,则函数返回零。否则,在 out_unregister 标签处释放相关资源并返回错误码。

下一步实现driver_attach(struct device_driver *drv)函数,函数原型如下:

int driver_attach(struct device_driver *drv)
{
	return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); //遍历所有设备,调用回调匹配函数
}

这段代码实现了driver_attach函数,它用于将给定的驱动程序对象注册到所有匹配的设备上。它首先调用bus_for_each_dev函数遍历驱动程序所在的总线上的所有设备,然后对每个设备都调用__driver_attach函数进行处理。

bus_for_each_dev函数将遍历给定总线上的所有设备,并对每个设备调用给定的回调函数。在这个函数中,第一个参数是总线对象,第二个参数是用户定义的数据指针,第三个参数是设备驱动程序对象,第四个参数是回调函数。该函数将遍历总线上的所有设备,并对每个设备都调用回调函数进行处理。

在driver_attach函数中,传递的回调函数是__driver_attach函数,它将尝试将给定的驱动程序注册到设备上。如果成功,就会将设备添加到驱动程序的私有链表中。

下一步实现int __driver_attach(struct device *dev, void *data)函数,函数原型如下:

static int __driver_attach(struct device *dev, void *data)
{
	struct device_driver *drv = data;
	int ret;

	/*
	 * Lock device and try to bind to it. We drop the error
	 * here and always return 0, because we need to keep trying
	 * to bind to devices and some drivers will return an error
	 * simply if it didn't support the device.
	 *
	 * driver_probe_device() will spit a warning if there
	 * is an error.
	 */

	ret = driver_match_device(drv, dev); //检测设备和相应的驱动程序
	if (ret == 0) {
		/* no match */
		return 0;
	} else if (ret == -EPROBE_DEFER) {
		dev_dbg(dev, "Device match requests probe deferral\n");
		driver_deferred_probe_add(dev);
	} else if (ret < 0) {
		dev_dbg(dev, "Bus failed to match device: %d", ret);
		return ret;
	} /* ret > 0 means positive match */

	if (driver_allows_async_probing(drv)) {	//用于检查设备驱动程序是否允许异步探测的函数
		/*
		 * Instead of probing the device synchronously we will
		 * probe it asynchronously to allow for more parallelism.
		 *
		 * We only take the device lock here in order to guarantee
		 * that the dev->driver and async_driver fields are protected
		 */
		dev_dbg(dev, "probing driver %s asynchronously\n", drv->name);
		device_lock(dev);
		if (!dev->driver) {
			get_device(dev);
			dev->p->async_driver = drv;
			async_schedule(__driver_attach_async_helper, dev);
		}
		device_unlock(dev);
		return 0;
	}

	device_driver_attach(drv, dev);  //绑定设备和相应的驱动程序

	return 0;
}

这段代码的作用是在总线上枚举所有设备,并尝试将设备绑定到该设备驱动程序。每个设备驱动程序都需要实现一个driver_attach函数来处理设备的绑定。在driver_attach函数中,它调用了bus_for_each_dev函数来枚举总线上所有设备,对每个设备调用__driver_attach函数来尝试将设备绑定到该设备驱动程序。__driver_attach函数将调用driver_match_device函数来检查该设备是否与该驱动程序匹配。如果设备与驱动程序匹配,则会调用device_driver_attach函数将设备绑定到该驱动程序。如果设备与驱动程序不匹配,则将继续枚举下一个设备。如果设备匹配但是驱动程序支持异步探测,那么设备将以异步方式进行探测。如果设备匹配但是驱动程序返回-EPROBE_DEFER,则设备的探测将被推迟。

下一步实现device_driver_attach(struct device_driver *drv, struct device *dev)函数,函数原型如下:

int device_driver_attach(struct device_driver *drv, struct device *dev)
{
	int ret = 0;

	__device_driver_lock(dev, dev->parent); //锁定设备和它的父设备

	/*
	 * If device has been removed or someone has already successfully
	 * bound a driver before us just skip the driver probe call.
	 */
	if (!dev->p->dead && !dev->driver)
		ret = driver_probe_device(drv, dev);  //探测并加载相应的probe函数

	__device_driver_unlock(dev, dev->parent);

	return ret;
}

这个函数的作用是将指定的设备驱动程序和设备实例关联起来。

首先,函数会调用 __device_driver_lock() 锁定设备和它的父设备,以确保在多线程环境中对设备驱动程序的操作是安全的。然后,它会检查设备是否已经被删除(标记为 dead),或者设备已经有了一个驱动程序与之关联,如果是,就跳过驱动程序的探测过程。否则,它会调用 driver_probe_device() 函数来探测并加载适当的驱动程序,并将设备与驱动程序关联起来。

最后,函数会释放设备和它的父设备的锁,并返回探测的结果。

下一步实现driver_probe_device(struct device_driver *drv, struct device *dev)函数,函数原型如下:

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
	int ret = 0;

	if (!device_is_registered(dev)) //检测设备是否被注册过
		return -ENODEV;

	pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);

	pm_runtime_get_suppliers(dev);
	if (dev->parent)
		pm_runtime_get_sync(dev->parent);

	pm_runtime_barrier(dev);
	if (initcall_debug)
		ret = really_probe_debug(dev, drv);
	else
		ret = really_probe(dev, drv);
	pm_request_idle(dev);
 
	if (dev->parent)
		pm_runtime_put(dev->parent);

	pm_runtime_put_suppliers(dev);
	return ret;
}

这是一个C语言代码片段,它定义了一个名为driver_probe_device()的函数。该函数有两个参数,分别是指向device_driver结构的指针drv和指向device结构的指针dev。

该函数的作用是在系统中匹配设备和驱动程序,并执行探测操作。在函数中,首先使用device_is_registered()函数检查设备是否已经注册,如果没有注册,则返回错误代码-ENODEV。

接着使用pr_debug()函数打印一条调试信息,该信息描述了已匹配的设备和驱动程序的相关信息,例如驱动程序所属的总线名称,设备的名称等。

然后,该函数会调用pm_runtime_get_suppliers()函数和pm_runtime_get_sync()函数,对设备和它的父设备(如果存在)进行电源管理操作,确保它们都被激活和运行。

接下来,该函数会调用really_probe()函数或really_probe_debug()函数来探测设备,具体调用哪个函数取决于initcall_debug变量的值。如果initcall_debug为真,则调用really_probe_debug()函数;否则,调用really_probe()函数。这两个函数都会执行设备探测操作,并返回一个错误码。

最后,函数调用pm_request_idle()函数来将设备转换到空闲状态,并使用pm_runtime_put()函数和pm_runtime_put_suppliers()函数进行电源管理操作,释放设备和父设备的资源,并返回之前保存的错误码。

下一步实现really_probe(struct device *dev, struct device_driver *drv)函数,函数原型如下:

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;

	if (defer_all_probes) { //判断是否需要将该设备的探测操作延迟(defer)执行
		/*
		 * Value of defer_all_probes can be set only by
		 * device_defer_all_probes_enable() which, in turn, will call
		 * wait_for_device_probe() right after that to avoid any races.
		 */
		dev_dbg(dev, "Driver %s force probe deferral\n", drv->name);
		driver_deferred_probe_add(dev);
		return ret;
	}

	ret = device_links_check_suppliers(dev);
	if (ret == -EPROBE_DEFER)
		driver_deferred_probe_add_trigger(dev, local_trigger_count);
	if (ret)
		return ret;

	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));
	if (!list_empty(&dev->devres_head)) {
		dev_crit(dev, "Resources present before probing\n");
		ret = -EBUSY;
		goto done;
	}

re_probe:
	dev->driver = drv;

	/* If using pinctrl, bind pins now before probing */
	ret = pinctrl_bind_pins(dev);
	if (ret)
		goto pinctrl_bind_failed;

	ret = dma_configure(dev);
	if (ret)
		goto probe_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;
	}

	if (dev->bus->probe) {
		ret = dev->bus->probe(dev); //如果存在则调用i2c_device_probe函数
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {
		ret = drv->probe(dev);
		if (ret)
			goto probe_failed;
	}

	if (test_remove) {
		test_remove = false;

		if (dev->bus->remove)
			dev->bus->remove(dev);
		else if (drv->remove)
			drv->remove(dev);

		devres_release_all(dev);
		driver_sysfs_remove(dev);
		dev->driver = NULL;
		dev_set_drvdata(dev, NULL);
		if (dev->pm_domain && dev->pm_domain->dismiss)
			dev->pm_domain->dismiss(dev);
		pm_runtime_reinit(dev);

		goto re_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:
	device_links_no_driver(dev);
	devres_release_all(dev);
	dma_deconfigure(dev);
	driver_sysfs_remove(dev);
	dev->driver = NULL;
	dev_set_drvdata(dev, NULL);
	if (dev->pm_domain && dev->pm_domain->dismiss)
		dev->pm_domain->dismiss(dev);
	pm_runtime_reinit(dev);
	dev_pm_set_driver_flags(dev, 0);

	switch (ret) {
	case -EPROBE_DEFER:
		/* Driver requested deferred probing */
		dev_dbg(dev, "Driver %s requests probe deferral\n", drv->name);
		driver_deferred_probe_add_trigger(dev, local_trigger_count);
		break;
	case -ENODEV:
	case -ENXIO:
		pr_debug("%s: probe of %s rejects match %d\n",
			 drv->name, dev_name(dev), ret);
		break;
	default:
		/* driver matched but the probe failed */
		printk(KERN_WARNING
		       "%s: probe of %s failed with error %d\n",
		       drv->name, dev_name(dev), ret);
	}
	/*
	 * Ignore errors returned by ->probe so that the next driver can try
	 * its luck.
	 */
	ret = 0;
done:
	atomic_dec(&probe_count);
	wake_up_all(&probe_waitqueue);
	return ret;
}

这是一个名为really_probe的函数,它是 Linux 内核中设备驱动程序的核心函数之一。它的作用是对一个设备与它对应的设备驱动程序进行探测(probe)操作,以确定它们是否匹配,如果匹配就进行绑定(bind)操作,使得设备能够被正常使用。

函数中首先进行了一些初始化操作,比如判断是否需要将该设备的探测操作延迟(defer)执行,以及检查设备与它所依赖的供应商之间的连接关系。然后在re_probe标签处执行了一系列具体的操作,如绑定设备的 pinctrl,配置 DMA 等。在这个过程中,如果某个步骤执行失败,会进行相应的回滚操作,比如解除 pinctrl 绑定、释放 DMA 等资源。如果最终设备与设备驱动程序成功匹配,则进行绑定操作,并执行一些必要的处理,如更新设备驱动程序的 sysfs 信息,激活设备的 pm_domain 等。

最后,函数会根据探测结果返回不同的值。如果设备的探测操作被延迟执行,则返回-EPROBE_DEFER,表示需要重新进行探测操作;如果探测操作失败,则返回一个负数错误码,表示探测失败的原因;如果设备与设备驱动程序成功匹配并完成了绑定操作,则返回 1。

如果if (dev->bus->probe) {ret = dev->bus->probe(dev);中bus->probe函数存在则调用i2c_device_probe函数。

下一步实现i2c_device_probe(struct device *dev)函数,函数原型如下:

static int i2c_device_probe(struct device *dev)  //用来在i2c总线上探测并初始化新的设备
{
	struct i2c_client	*client = i2c_verify_client(dev); //
	struct i2c_driver	*driver;
	int status;

	if (!client)
		return 0;

	driver = to_i2c_driver(dev->driver); //从设备结构体中获取到对应的i2c_driver结构体指针

	if (!client->irq && !driver->disable_i2c_core_irq_mapping) { //判断该设备是否需要使用i2c核心中断映射
		int irq = -ENOENT;

		if (client->flags & I2C_CLIENT_HOST_NOTIFY) {
			dev_dbg(dev, "Using Host Notify IRQ\n");
			/* Keep adapter active when Host Notify is required */
			pm_runtime_get_sync(&client->adapter->dev);
			irq = i2c_smbus_host_notify_to_irq(client);
		} else if (dev->of_node) {
			irq = of_irq_get_byname(dev->of_node, "irq");
			if (irq == -EINVAL || irq == -ENODATA)
				irq = of_irq_get(dev->of_node, 0);
		} else if (ACPI_COMPANION(dev)) {
			irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 0);
		}
		if (irq == -EPROBE_DEFER)
			return irq;

		if (irq < 0)
			irq = 0;

		client->irq = irq;
	}

	/*
	 * An I2C ID table is not mandatory, if and only if, a suitable OF
	 * or ACPI ID table is supplied for the probing device.
	 */
	if (!driver->id_table &&
	    !i2c_acpi_match_device(dev->driver->acpi_match_table, client) &&
	    !i2c_of_match_device(dev->driver->of_match_table, client))
		return -ENODEV;       //判断驱动程序是否提供i2c id表,ACPI或OF表

	if (client->flags & I2C_CLIENT_WAKE) {
		int wakeirq = -ENOENT;

		if (dev->of_node) {
			wakeirq = of_irq_get_byname(dev->of_node, "wakeup");
			if (wakeirq == -EPROBE_DEFER)
				return wakeirq;
		}

		device_init_wakeup(&client->dev, true);

		if (wakeirq > 0 && wakeirq != client->irq)
			status = dev_pm_set_dedicated_wake_irq(dev, wakeirq);
		else if (client->irq > 0)
			status = dev_pm_set_wake_irq(dev, client->irq);
		else
			status = 0;

		if (status)
			dev_warn(&client->dev, "failed to set up wakeup irq\n");
	}

	dev_dbg(dev, "probe\n");

	status = of_clk_set_defaults(dev->of_node, false); //设置设备的时钟
	if (status < 0)
		goto err_clear_wakeup_irq;

	status = dev_pm_domain_attach(&client->dev, true);  //设置设备的电源域
	if (status)
		goto err_clear_wakeup_irq;

	/*
	 * When there are no more users of probe(),
	 * rename probe_new to probe.
	 */
	if (driver->probe_new)
		status = driver->probe_new(client);
	else if (driver->probe)
		status = driver->probe(client,
				       i2c_match_id(driver->id_table, client)); //如果为真,则调用probe函数
	else
		status = -EINVAL;

	if (status)
		goto err_detach_pm_domain;

	return 0;

err_detach_pm_domain:
	dev_pm_domain_detach(&client->dev, true);
err_clear_wakeup_irq:
	dev_pm_clear_wake_irq(&client->dev);
	device_init_wakeup(&client->dev, false);
	return status;
}

这个函数是用来在 I2C 总线上探测并初始化新的设备的。当一个新的设备被连接到 I2C 总线上时,系统会检测到这个设备并调用这个函数进行探测和初始化。该函数的参数是一个指向设备结构体的指针,其中包含了设备的描述信息和其他必要的信息。

在函数内部,首先通过 i2c_verify_client() 函数获取 i2c_client 结构体指针,如果指针为空,说明该设备不是 I2C 设备,直接返回 0。接着从设备结构体中获取到对应的 i2c_driver 结构体指针,然后判断该设备是否需要使用 I2C 核心中断映射,如果需要,则调用 i2c_smbus_host_notify_to_irq() 函数获取中断号,并设置到设备的 irq 字段中。

然后判断驱动程序是否提供了 I2C ID 表,如果没有提供,则判断设备是否匹配 ACPI 或 OF 表中的 ID 表,如果匹配,则说明该设备是支持的,可以继续进行后续操作。如果没有提供 ID 表,也没有匹配成功,则说明该设备不支持,返回 ENODEV。

如果设备的 flags 字段中设置了 I2C_CLIENT_WAKE 标志,说明该设备支持唤醒功能。此时,如果设备树中定义了唤醒中断,则获取唤醒中断号,并调用 dev_pm_set_dedicated_wake_irq() 函数设置唤醒中断。如果设备没有定义唤醒中断,则使用设备的普通中断。

接下来,调用 of_clk_set_defaults() 函数设置设备的时钟,调用 dev_pm_domain_attach() 函数附加设备的电源域,并调用驱动程序的 probe_new() 或 probe() 函数进行设备的探测和初始化。如果探测和初始化失败,则返回相应的错误码。

最后,如果探测和初始化成功,则返回 0,表示操作成功。如果操作失败,则调用 dev_pm_clear_wake_irq() 函数清除唤醒中断,并将设备的唤醒标志清除。

如果status = driver->probe(client,   i2c_match_id(driver->id_table, client))为真,则调用结构体中的probe函数。

3.在module_init()函数中,platform_driver平台总线传入的结构体是如何把probe函数调用起来的:

通过调用堆栈打印mtty_probe函数的调用过程如下:

其调用过程与iic总线调用过程相似。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

开心肖肖乐❀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值