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函数主要完成以下工作:
-
初始化系统的基本功能,包括内存管理、中断处理、调度器、定时器、系统调用等。
-
加载并初始化内核模块,这些模块包含了许多设备驱动程序和文件系统等功能。
-
初始化各种子系统,例如CPU调度、进程管理、网络管理、驱动程序管理等。
-
启动第一个用户进程。
具体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总线调用过程相似。