注:以下的代码皆摘自于linux 4.9.88版本的内核源码,不同版本可能有所出入。
往期内容:
1. driver的probe触发方式
在 Linux 设备模型中,probe()
函数是驱动与设备绑定的核心机制。其触发时机与设备(device
)和设备驱动(device_driver
)的注册紧密相关。当内核发现设备与驱动匹配时,调用驱动的 probe()
函数来初始化设备,通常包括硬件初始化、资源分配、设备文件的注册等操作。以下是设备驱动 probe()
触发的几种时机,分为自动触发和手动触发:
自动触发probe()
- 设备(
device
)注册到内核时触发- 当通过
device_register()
或者其他相关接口(例如device_add()
、device_create()
)将struct device
类型的设备对象注册到内核时,内核会查找与该设备名称匹配的驱动程序。如果找到了匹配的device_driver
,并且设备未绑定其他驱动,内核会自动调用该驱动的probe()
函数来进行初始化。 - 例如:
- 当通过
struct device my_device;
device_register(&my_device);
在这个过程中,内核会尝试在总线(bus)下查找与 my_device
名称匹配的驱动程序。
- 驱动(
device_driver
)注册到内核时触发- 同样地,当通过
driver_register()
接口将一个新的device_driver
驱动对象注册到内核时,内核会遍历当前总线下已注册的所有设备,检查是否有与该驱动匹配的设备。如果存在匹配设备,并且设备未绑定其他驱动,内核将自动调用该驱动的probe()
函数。 - 例如:
- 同样地,当通过
struct device_driver my_driver;
driver_register(&my_driver);
在这个过程中,内核会为总线下所有与 my_driver
匹配的设备执行 probe()
操作。
手动触发probe()
- 手动调用
device_attach()
device_attach()
是一个手动触发设备驱动配对的函数。它会在总线下遍历所有已注册的驱动程序,寻找与指定设备匹配的驱动。如果找到匹配的驱动,且该设备尚未绑定任何驱动,则调用该驱动的probe()
。- 这种方式的使用场景通常是在设备已经存在但驱动程序尚未加载的情况下,通过手动调用
device_attach()
,使设备与驱动程序绑定并执行probe()
。
device_attach(&my_device);
- 手动调用
driver_attach()
driver_attach()
是另一个手动触发配对的接口,它的功能是遍历总线下的所有设备,查找与指定驱动匹配的设备。如果找到匹配的设备,且设备尚未绑定驱动,则执行该驱动的probe()
操作。- 使用场景通常是在驱动已经加载但设备尚未注册的情况下,通过手动调用
driver_attach()
来触发设备绑定:
driver_attach(&my_driver);
- **<font style="color:#DF2A3F;">说白了,driver_register自动触发驱动的probe函数,其实研究一下它的内部就可以发现它调用到了driver_attach</font>**
driver_register -> bus_add_driver(下面会讲) ->
int bus_add_driver(struct device_driver *drv)
{
//......
if (drv->bus->p->drivers_autoprobe) {
if (driver_allows_async_probing(drv)) {
pr_debug("bus: '%s': probing driver %s asynchronously\n",
drv->bus->name, drv->name);
async_schedule(driver_attach_async, drv);
} else {
error = driver_attach(drv); //这不就体现到了,调用了driver_attach,内部会去调用设备驱动probe
if (error)
goto out_unregister;
}
}
//......
}
- 手动调用
device_bind_driver()
- 通过
device_bind_driver()
,可以手动将驱动绑定到指定设备上,并调用该驱动的probe()
函数进行初始化。这种方式比上述手动触发的方法更为直接,因为它不进行遍历,而是显式地将驱动和设备关联。 - 在该接口中,用户可以自己指定设备和驱动的配对,而无需总线来查找匹配项:
- 通过
device_bind_driver(&my_device);
2. bus中device和device_driver的添加
在 Linux 内核中,设备和驱动程序的管理由 bus
模块处理,提供了一种通用的机制来注册和管理设备(device
)和设备驱动(device_driver
)。具体来说,bus_add_device
和 bus_add_driver
是处理设备和驱动程序注册的核心逻辑。
bus_add_device
处理逻辑
/**
* bus_add_device - add device to bus
* @dev: device being added
*
* - Add device's bus attributes.
* - Create links to device's bus.
* - Add the device to its bus's list of devices.
*/
int bus_add_device(struct device *dev)
{
struct bus_type *bus = bus_get(dev->bus);
int error = 0;
if (bus) {
pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
error = device_add_attrs(bus, dev);
if (error)
goto out_put;
error = device_add_groups(dev, bus->dev_groups);
if (error)
goto out_id;
error = sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj, dev_name(dev));
if (error)
goto out_groups;
error = sysfs_create_link(&dev->kobj,
&dev->bus->p->subsys.kobj, "subsystem");
if (error)
goto out_subsys;
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
return 0;
out_subsys:
sysfs_remove_link(&bus->p->devices_kset->kobj, dev_name(dev));
out_groups:
device_remove_groups(dev, bus->dev_groups);
out_id:
device_remove_attrs(bus, dev);
out_put:
bus_put(dev->bus);
return error;
}
bus_add_device
的主要目的是将设备添加到总线,并进行相关的初始化和管理操作。其处理逻辑如下:
- 添加默认属性到内核
- 调用
device_add_attrs
函数,该函数根据bus->dev_attrs
指针定义的默认属性,将这些属性添加到内核中。这些属性会显示在设备的 sysfs 目录下,例如/sys/devices/xxx/xxx_device/
。
- 调用
- 创建符号链接到总线的设备目录
- 使用
sysfs_create_link
创建一个符号链接,将设备的 sysfs 目录链接到该设备所在总线的devices
目录下。这一操作提供了方便的路径,以便用户空间程序能够轻松访问设备的信息。例如:
- 使用
ls -l /sys/bus/spi/devices/spi1.0
lrwxrwxrwx root root 2014-04-11 10:46 spi1.0 -> ../../../devices/platform/s3c64xx-spi.1/spi_master/spi1/spi1.0
- 创建指向总线目录的链接
- 再次调用
sysfs_create_link
,在设备的 sysfs 目录(如/sys/devices/platform/alarm/
)中,创建一个名为subsystem
的符号链接,指向该设备所在总线的目录。这种设计便于在设备目录中快速找到其所在的总线信息。
- 再次调用
ls -l /sys/devices/platform/alarm/subsystem
lrwxrwxrwx root root 2014-04-11 10:28 subsystem -> ../../../bus/platform
- 将设备指针保存到总线的设备列表
- 最后,将设备指针保存在
bus->priv->klist_devices
链表中,以便后续操作中可以方便地查找和管理该设备。
- 最后,将设备指针保存在
其实也就是调用了device_register内部会调用bus_add_device:
device_register
** 函数**:device_register
是用于注册一个新的设备的主要接口。当你调用这个函数时,它会处理设备的创建和初始化。
- **内部调用 **
bus_add_device
:- 在
device_register
中,它会执行一系列初始化操作,然后调用bus_add_device
函数。这个调用的主要目的是将设备添加到相应的总线(bus)中,并执行必要的注册操作。
- 在
bus_add_device
** 的作用**:bus_add_device
负责将设备的信息添加到总线的数据结构中,设置设备的默认属性,创建符号链接等,使得设备能够在 sysfs 中正确显示,并允许后续的设备管理和操作。
以下是一个简化的示例流程:
int device_register(struct device *dev) {
// 一些设备初始化逻辑
...
// 调用 bus_add_device 将设备添加到总线
int ret = bus_add_device(dev);
if (ret) {
// 错误处理
...
}
// 其他初始化逻辑
...
return 0;
}
device_register
的具体实现可以在drivers/base/core.c
中找到。bus_add_device
的实现如前所述,在drivers/base/bus.c
中。
bus_add_driver
处理逻辑
Linux-4.9.88\drivers\base\bus.c:
/**
* bus_add_driver - Add a driver to the bus.
* @drv: driver.
*/
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
int error = 0;
bus = bus_get(drv->bus);
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);
if (error)
goto out_unregister;
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe) {
if (driver_allows_async_probing(drv)) {
pr_debug("bus: '%s': probing driver %s asynchronously\n",
drv->bus->name, drv->name);
async_schedule(driver_attach_async, drv);
} else {
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
的目的是将设备驱动注册到相应的总线。其处理逻辑如下:
- 分配并初始化
struct driver_private
- 为该驱动的
struct driver_private
指针(priv
)分配内存,并初始化其内部字段,包括priv->klist_devices
、priv->driver
和priv->kobj.kset
等。同时,将该指针保存在device_driver
结构体的p
字段中。
- 为该驱动的
- 设置驱动的 kset
- 将驱动的 kset(
priv->kobj.kset
)设置为总线的驱动 kset(bus->p->drivers_kset
),这意味着所有驱动的 kobject 都位于该总线的驱动目录下(例如/sys/bus/xxx/drivers
)。
- 将驱动的 kset(
- 注册驱动的 kobject
- 以驱动的名字为参数,调用
kobject_init_and_add
函数,在 sysfs 中注册驱动的 kobject,并体现在/sys/bus/xxx/drivers/
目录下,例如/sys/bus/spi/drivers/spidev
。
- 以驱动的名字为参数,调用
- 将驱动保存在总线的驱动列表中
- 将该驱动保存在
bus->priv->klist_drivers
链表中,以便后续管理。
- 将该驱动保存在
- **根据
drivers_autoprobe
值决定是否调用 **driver_attach
- 根据
drivers_autoprobe
的值来选择是否调用driver_attach
函数。如果该值为1
,则会自动尝试将匹配的设备与驱动进行配对并调用probe()
函数。
- 根据
- 创建驱动的 uevent 属性
- 调用
driver_create_file
,在 sysfs 的该驱动目录下创建uevent
属性,允许用户空间程序获取和响应设备事件。
- 调用
- 添加驱动的默认属性
- 调用
driver_add_attrs
函数,在该驱动的 sysfs 目录下创建由bus->drv_attrs
指针定义的默认属性。
- 调用
- 创建
bind
和unbind
属性- 根据
suppress_bind_attrs
标志的值,决定是否在 sysfs 的该驱动目录下创建bind
和unbind
属性,这些属性允许用户空间手动绑定和解绑驱动与设备。
- 根据
通过 bus_add_device
和 bus_add_driver
接口,Linux 内核提供了一个统一的机制来注册和管理设备和驱动。前者处理设备的注册、属性的创建和符号链接的设置,后者则负责驱动的注册、kobject 的初始化和自动 probe 的控制。这些操作不仅简化了设备与驱动之间的管理流程,也为用户空间提供了便利的接口来监控和管理设备驱动。
3. Bus中设备的probe触发逻辑
这里是通过内部代码对上面第1点的进一步讲解
- 在 Linux 设备模型中,设备device和驱动device_driver的匹配和
probe()
调用实际是由总线(bus
)模块负责的。每个设备和驱动都是通过总线连接的,因此总线模块知道如何为设备和驱动进行配对。 - 当设备或驱动注册到总线时,总线会负责调用
bus_probe_device()
或driver_attach()
(内部会调用bus_probe_device),以检查当前设备和驱动的匹配情况,并决定是否的调用device_driver中probe()
。
来看一下内核代码,以设备调用了driver_register后,自动触发调用设备驱动的probe为例:
\Linux-4.9.88\Linux-4.9.88\drivers\base\driver.c:
int driver_register(struct device_driver *drv)
{
//.......
ret = bus_add_driver(drv); //继续进入看
if (ret)
return ret;
//.......
}
//---------------分割--------------------
int bus_add_driver(struct device_driver *drv)
{
//.......
if (drv->bus->p->drivers_autoprobe) { //drivers_autoprobe的影响,这个看下面小点介绍
//-----------------------------------------------------------------(1)
if (driver_allows_async_probing(drv)) {
pr_debug("bus: '%s': probing driver %s asynchronously\n",
drv->bus->name, drv->name);
async_schedule(driver_attach_async, drv);
} else {
error = driver_attach(drv); //这里,内部必定会去匹配然后调用设备驱动的probe
//继续进入内部查看
if (error)
goto out_unregister;
}
}
module_add_driver(drv->owner, drv);
//.......
}
//---------------分割--------------------
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); //继续进入
//留意一下参4:__driver_attach函数,调用设备驱动的probe函数就在里面
}
//---------------分割--------------------
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device *dev;
int error = 0;
if (!bus || !bus->p)
return -EINVAL;
klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)
error = fn(dev, data); //这里,调用了传进来的参四,也就是__driver_attach函数,来查看一下这个函数
klist_iter_exit(&i);
return error;
}
//---------------分割--------------------
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); //这里是去查看是否和device匹配,一共有四种
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 (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev);//--------------(2)篇幅有点长了,在下面(2)处在贴内核源码吧
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
return 0;
}
其中__driver_attach的ret = driver_match_device(drv, dev); 就是去查看driver和device是否匹配,一共有四种匹配方式,具体的留到platform bus的讲解时再说
(1) drivers_autoprobe
的影响
- 每个总线对象中有一个
drivers_autoprobe
变量,控制是否在设备或驱动程序注册时自动触发probe()
操作。- 当
drivers_autoprobe
设为1
(默认值),设备或驱动程序注册时会自动进行配对并调用probe()
。 - 当
drivers_autoprobe
设为0
,则需要手动调用上面所说的手动触发的接口来进行配对和probe()
。
- 当
drivers_autoprobe
是通过 sysfs 暴露给用户空间的,路径为 /sys/bus/<bus_name>/drivers_autoprobe
,因此可以通过用户空间的操作修改 drivers_autoprobe
的值,控制自动 probe
行为。
(2)总线的 probe
操作: bus_probe_device()
和 driver_attach()
bus_probe_device()
- 当设备被添加到总线时(比如调用了
device_register()
),会调用bus_probe_device()
。该函数遍历该总线下的所有驱动,检查是否有与该设备匹配的驱动程序。如果找到匹配的驱动,并且设备没有绑定驱动,则调用该驱动的probe()
函数。可以继续看内部代码:
- 当设备被添加到总线时(比如调用了
\Linux-4.9.88\drivers\base\dd.c:
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);
if (dev->parent)
pm_runtime_get_sync(dev->parent);
pm_runtime_barrier(dev);
ret = really_probe(dev, drv); //这里,内部就会去调用driver的probe函数
pm_request_idle(dev);
if (dev->parent)
pm_runtime_put(dev->parent);
return ret;
}
//---------------分割--------------------
static int really_probe(struct device *dev, struct device_driver *drv)
{
//,,,,,,,,
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev); //这,不就调用到了driver的probe函数
if (ret)
goto probe_failed;
}
//,,,,,,,,
}
driver_attach()
driver_attach()
是在驱动注册时被调用的,它会遍历总线下的所有设备,寻找与该驱动匹配的设备。如果找到匹配的设备,且设备没有绑定其他驱动,则调用该驱动的probe()
。它内部还是会调用到bus_probe_device,这个在上面代码分析中已经指出来了
设备驱动 probe()
的调用机制贯穿整个 Linux 设备模型,通过设备和驱动的注册,手动或自动进行设备和驱动的匹配。在大多数情况下,系统会自动触发 probe()
,但在某些特殊情况下(如设备和驱动的异步加载),用户也可以手动触发 probe()
来绑定设备和驱动。这一切的背后,是由总线模块负责管理设备与驱动的匹配和绑定逻辑。