Skip to content

LV210-注册驱动到总线

前面已经可以注册自定义总线和在总线注册设备,那么还缺少驱动了,现在来看看怎么在总线上注册一个驱动?

一、驱动

1. 驱动简介

1.1 struct device_driver

设备能否正常工作,取决于驱动。驱动需要告诉内核, 自己可以驱动哪些设备,如何初始化设备。在内核中,使用 device_driver 结构体来描述我们的驱动,它定义在 device.h - include/linux/device.h - struct device_driver

c
struct device_driver {
	const char		*name;
	struct bus_type		*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;
	void (*coredump) (struct device *dev);

	struct driver_private *p;
};
  • name :指定驱动名称,总线进行匹配时,利用该成员与设备名进行比较;
  • bus :表示该驱动依赖于哪个总线,内核需要保证在驱动执行之前,对应的总线能够正常工作;
  • suppress_bind_attrs :布尔量,用于指定是否通过 sysfs 导出 bind 与 unbind 文件,bind 与 unbind 文件是驱动用于绑定/解绑关联的设备。
  • owner :表示该驱动的拥有者,一般设置为 THIS_MODULE;
  • of_match_tablestruct of_device_id 类型指针变量,指定该驱动支持的设备类型。当内核使能设备树时,会利用该成员与设备树中的 compatible 属性进行比较。
  • remove :当设备从操作系统中拔出或者是系统重启时,会调用该回调函数;
  • probe :当驱动以及设备匹配后,会执行该回调函数,对设备进行初始化。通常的代码,都是以 main 函数开始执行的,但是在内核的驱动代码,都是从 probe 函数开始的。
  • groups :指向 struct attribute_group 类型的指针,指定该驱动的属性;

1.2 驱动的注册与注销

1.2.1 driver_register()

driver_register() 定义如下:

c
/**
 * driver_register - register driver with bus
 * @drv: driver to register
 *
 * We pass off most of the work to the bus_add_driver() call,
 * since most of the things we have to do deal with the bus
 * structures.
 */
int driver_register(struct device_driver *drv);

参数说明

返回值

  • 成功: 0
  • 失败: 负数
1.2.2 driver_unregister()

driver_unregister() 定义如下:

c
/**
 * driver_unregister - remove driver from system.
 * @drv: driver.
 *
 * Again, we pass off most of the work to the bus-level call.
 */
void driver_unregister(struct device_driver *drv);

参数说明

返回值

1.3 总结

跟设备一样,当成功注册总线时,会在/sys/bus 目录下创建对应总线的目录,该目录下有两个子目录,分别是 drivers 和 devices, 我们使用 driver_register() 注册的驱动从属于某个总线时,该总线的 drivers 目录下便会存在该驱动文件。

2. 驱动属性文件

驱动属性文件,和设备属性文件的作用是一样,唯一的区别在于函数参数的不同。

2.1 DRIVER_ATTR_RW/RO/WO()

驱动这里其实有三个宏,它定义在 device.h - include/linux/device.h - DRIVER_ATTR_RW/RO/WO

c
#define DRIVER_ATTR_RW(_name) \
	struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
#define DRIVER_ATTR_RO(_name) \
	struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR_WO(_name) \
	struct driver_attribute driver_attr_##_name = __ATTR_WO(_name)

struct driver_attribute 定义如下:

c
struct driver_attribute {
	struct attribute attr;
	ssize_t (*show)(struct device_driver *driver, char *buf);
	ssize_t (*store)(struct device_driver *driver, const char *buf,
			 size_t count);
};

DRIVER_ATTR_RWDRIVER_ATTR_RO 以及 DRIVER_ATTR_WO 宏定义用于定义一个 struct driver_attribute 类型的变量,带有 driver_attr_的前缀,区别在于文件权限不同, RW 后缀表示文件可读写,RO 后缀表示文件仅可读,WO 后缀表示文件仅可写。而且我们会发现,DRIVER_ATTR 类型的宏定义没有参数来设置 show 和 store 回调函数, 那如何设置这两个参数呢?在写驱动代码时,只需要我们提供 xxx_store 以及 xxx_show 这两个函数, 并确保两个函数的 xxx 和 DRIVER_ATTR 类型的宏定义中名字是一致的即可。为什么要保证名字是一样的?我们可以看到,这里的三个宏内部是用了另一个宏__ATTR_RW__ATTR_RO__ATTR_RO

c
#define __ATTR_RO(_name) {						\
	.attr	= { .name = __stringify(_name), .mode = 0444 },		\
	.show	= _name##_show,						\
}

#define __ATTR_RO_MODE(_name, _mode) {					\
	.attr	= { .name = __stringify(_name),				\
		    .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },		\
	.show	= _name##_show,						\
}

#define __ATTR_WO(_name) {						\
	.attr	= { .name = __stringify(_name), .mode = 0200 },		\
	.store	= _name##_store,					\
}

#define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)

可以看到这里面的函数名后缀都是固定的,前缀是由我们定义的属性名决定,所以这里要注意一下。

2.2 driver_create_file()

driver_create_file() 函数定义如下:

c
extern int __must_check driver_create_file(struct device_driver *driver,
					const struct driver_attribute *attr);

该函数用于创建属性文件,使用 driver_create_file() 函数, 会在 /sys/bus/<bus-name>/drivers/<driver-name>/ 目录下创建文件。

2.3 driver_remove_file()

driver_remove_file() 函数定义如下:

c
extern void driver_remove_file(struct device_driver *driver,
			       const struct driver_attribute *attr);

该函数用于删除对应的属性文件。

2.4 使用实例

在调用 driver_create_file() 函数之前,需要先定义好属性结构体 struct driver_attribute ,并将其相关字段填充好。有两种方式,一种是直接定义:

c
struct driver_attribute sdrv_attr_data_var = {
    .attr = {
        .name = "sdrv_attr_data",    // 属性的名称, 将会显示在 /sys/bus/bus-name/drivers/driver-name 下,并可以使用 cat/echo 进行操作
        .mode = 0664,                // 属性的访问权限
    },
    .show = sdrv_attr_data_show,     // 属性的 show 回调函数
    .store = sdrv_attr_data_store,   // 属性的 show 回调函数
};

ret = driver_create_file(&sdrv, &sdrv_attr_data_var);

另一种是通过宏来定义:

c
// 下面这个宏可以简化属性变量的定义,从定义可以看出,这里定义的属性的变量前缀是 driver_attr_,后面要再拼接 name 才行
// #define DRIVER_ATTR_RW(_name) 
//      struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
// #define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)
DRIVER_ATTR_RW(sdrv_attr_data);
ret = driver_create_file(&sdrv, &driver_attr_sdrv_attr_data);

show 和 store 函数实现如下:

c
typedef struct __SDRV_ATTR_VAR_{
    char name_attr[32];
    int data;
}sdrv_attr_var_t;

sdrv_attr_var_t sdrv_attr = {0};     // sdriver 的属性

static ssize_t sdrv_attr_data_show(struct device_driver *driver, char *buf)
{
    ssize_t count = 0;
    count = sprintf(buf, "%d\n", sdrv_attr.data);
    PRT("device_driver->name=%s count=%d\n", driver->name, count);
    return count;
}

static ssize_t sdrv_attr_data_store(struct device_driver *driver, const char *buf, size_t count)
{
    PRT("device_driver->name=%s count=%d\n", driver->name, count);
    sscanf(buf, "%d\n", &sdrv_attr.data);
    return count;
}

3. 设备注册到总线 demo

3.1 demo 源码

源码可以看这里,这里有两个,一个是只驱动,另一个还为驱动添加了属性文件操作。

这里直接已加了属性的 demo 为例 05_device_model/16_driver_register_with_attr

c
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */

#include <linux/device.h>

#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"

// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif

#define SDRV_NAME        "sdrv"
#define SDRV_MATCH_NAME  "sdrv-sdev"   // 和设备匹配的名字,设备名为 sdrv-sdev 的才能匹配成功
                                       // 这个会作为驱动的名字,出现在 /sys/bus/bus-name/drivers/ 中
extern struct bus_type sbus;

typedef struct __SDRV_ATTR_VAR_{
    char name_attr[32];
    int data;
}sdrv_attr_var_t;

sdrv_attr_var_t sdrv_attr = {0};     // sdriver 的属性

/**
 * @brief  sdrv_probe()
 * @note   驱动程序的探测函数, 当驱动和设备匹配上的时候,就会执行这个函数
 * @param [in]
 * @param [out]
 * @retval 
 */
static int sdrv_probe(struct device *dev)
{
    int len = 0;

    const char *device_name = dev_name(dev);
    len = strlen(device_name);

	PRT("dev name is %s, len = %d\n", device_name, len);
	return 0;
}

/**
 * @brief  sdrv_remove()
 * @note   驱动程序的移除函数
 * @param [in]
 * @param [out]
 * @retval 
 */
static int sdrv_remove(struct device *dev)
{
    int len = 0;

    const char *device_name = dev_name(dev);
    len = strlen(device_name);

	PRT("dev name is %s, len = %d\n", device_name, len);
	return 0;
}

//定义一个驱动结构体 sdrv,名字需要和设备的名字相同,否则就不能成功匹配
static struct device_driver sdrv = {
	.name = SDRV_MATCH_NAME,      // 驱动程序的名称, 将会显示在 /sys/bus/bus-name/drivers/ 
	.bus = &sbus,                 // 该驱动挂载在已经注册好的总线 sbus 下。
	.probe = sdrv_probe,          // 当驱动和设备匹配成功之后,便会执行驱动的 probe 函数
	.remove = sdrv_remove,        // 当注销驱动时,需要关闭物理设备的某些功能等
};

/**
 * @brief  sdev_attr_data_show()
 * @note   保证 show 函数的前缀与驱动属性文件一致,sdev_attr_data()的前缀 sdev_attr_data 和 DRIVER_ATTR_RW 的参数 sdev_attr_data
 * @param [in]
 * @param [out]
 * @retval 
 */
static ssize_t sdrv_attr_data_show(struct device_driver *driver, char *buf)
{
    ssize_t count = 0;
    count = sprintf(buf, "%d\n", sdrv_attr.data);
    PRT("device_driver->name=%s count=%d\n", driver->name, count);
    return count;
}

/**
 * @brief  sdev_attr_data_show()
 * @note   保证 store 函数的前缀与驱动属性文件一致,sdev_attr_data()的前缀 sdev_attr_data 和 DRIVER_ATTR_RW 的参数 sdev_attr_data
 * @param [in]
 * @param [out]
 * @retval 
 */
static ssize_t sdrv_attr_data_store(struct device_driver *driver, const char *buf, size_t count)
{
    PRT("device_driver->name=%s count=%d\n", driver->name, count);
    sscanf(buf, "%d\n", &sdrv_attr.data);
    return count;
}

// 下面这个宏可以简化属性变量的定义,从定义可以看出,这里定义的属性的变量前缀是 dev_attr_,后面要再拼接 name 才行
// #define DRIVER_ATTR_RW(_name) 
//      struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
// #define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)

struct driver_attribute sdrv_attr_data_var = {
    .attr = {
        .name = "sdrv_attr_data",    // 属性的名称, 将会显示在 /sys/bus/bus-name/drivers/driver-name 下,并可以使用 cat/echo 进行操作
        .mode = 0664,                // 属性的访问权限
    },
    .show = sdrv_attr_data_show,     // 属性的 show 回调函数
    .store = sdrv_attr_data_store,   // 属性的 show 回调函数
};

/**
 * @brief  sdrv_demo_init
 * @note   调用 driver_register 函数注册我们的驱动
 * @param [in]
 * @param [out]
 * @retval 
 */
static __init int sdrv_demo_init(void)
{
    int ret = 0;
	printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n", __FUNCTION__,
           __LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION);
    PRT("sdrv_demo module init!\n");

	ret = driver_register(&sdrv);
    if(ret < 0)
    {
        PRTE("driver_register fail!\n");
        goto err_driver_register;
    }

	ret = driver_create_file(&sdrv, &sdrv_attr_data_var);
    if(ret < 0)
    {
        PRTE("driver_create_file fail!ret=%d\n", ret);
        goto err_driver_create_file;
    }
	return 0;

err_driver_create_file:
    driver_unregister(&sdrv);

err_driver_register:
    return ret;
}

/**
 * @brief  sdrv_demo_exit
 * @note   注销驱动以及驱动属性文件
 * @param [in]
 * @param [out]
 * @retval 
 */
static __exit void sdrv_demo_exit(void)
{
    driver_remove_file(&sdrv, &sdrv_attr_data_var);
	driver_unregister(&sdrv);

    PRT("sdrv_demo module exit!\n");
}

module_init(sdrv_demo_init); // 将__init 定义的函数指定为驱动的入口函数
module_exit(sdrv_demo_exit); // 将__exit 定义的函数指定为驱动的出口函数

/* 模块信息(通过 modinfo chrdev_led_demo 查看) */
MODULE_LICENSE("GPL v2");            /* 源码的许可证协议 */
MODULE_AUTHOR("sumu");               /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description");   /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */

3.2 开发板测试

我们编译完,把对对应的驱动拷贝到开发板中,按以下步骤测试效果。

  • (1)一开始的 /sys/bus 目录
image-20250113112434476
  • (2)加载模块
image-20250113112647995

加载模块后,会发现,在/sys/bus 目录生成了 sbus 目录,并且/sys/bus/sbus/drivers 目录下也生成了对应的驱动的目录,在驱动目录下,有我们创建的属性文件。

  • (3)查看/修改 sdrv_attr_data 的值

总线的属性,之前已经修改和验证过了,这里只看一下设备的属性文件,其实操作都是一样的:

image-20250113112841287
  • (5)卸载模块
image-20250113113136103

可以看到,卸载模块后,资源释放,sbus 目录被删除,相关的属性文件也都删除了。

二、驱动的注册流程

1. driver_register()

我们从 driver_register() 函数来看一下注册流程:

c
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;
}

函数用于注册设备驱动程序并将其添加到总线中,接下来我们详细分析一下。

  • 151 - 155 行:检查总线是否已初始化
c
	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;
	}

通过 drv-> bus 访问设备驱动程序结构体中的总线信息。前面初始化的时候已经填充了对应的成员:

c
static struct device_driver sdrv = {
	.name = SDRV_MATCH_NAME,      // 驱动程序的名称, 将会显示在 /sys/bus/bus-name/drivers/ 
	.bus = &sbus,                 // 该驱动挂载在已经注册好的总线 sbus 下。
	.probe = sdrv_probe,          // 当驱动和设备匹配成功之后,便会执行驱动的 probe 函数
	.remove = sdrv_remove,        // 当注销驱动时,需要关闭物理设备的某些功能等
};

如果总线的 p 成员为 NULL, 表示总线未初始化。如果总线未初始化,则打印错误消息,并返回 -EINVAL 错误码表示无效的 参数。

  • 157 - 161 行:检查驱动程序的方法是否需要更新
c
	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);

通过检查驱动程序结构体中的 bus-> probe 和 drv-> probe、bus-> remove 和 drv-> remove、 bus-> shutdown 和 drv-> shutdown 成员是否同时存在来判断。如果存在需要更新的方法组合, 说明驱动程序需要更新。在这种情况下,打印警告消息,建议使用 bus_type 方法进行更新。其实前面的 demo 中是有这个打印的:

image-20250113125707505

原因是什么呢?就是因为这个:

image-20250113130212907

驱动和总线中都定义了 probe 方法,后面我们再详细了解。

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

调用 driver_find() 函数来查找是否已经注册了同名的驱动程序。如果找到同名驱动程序, 表示驱动程序已经注册过。在这种情况下,打印错误消息,并返回 -EBUSY 错误码表示设备忙。

  • 170 - 172 行:添加驱动程序到总线
c
	ret = bus_add_driver(drv);
	if (ret)
		return ret;

调用 bus_add_driver() 函数将驱动程序添加到总线。如果添加失败,则返回相应的错误码。

  • 173 - 177 行:添加驱动程序的组属性
c
	ret = driver_add_groups(drv, drv->groups);
	if (ret) {
		bus_remove_driver(drv);
		return ret;
	}

调用 driver_add_groups() 函数将驱动程序的组属性添加到驱动程序中。如果添加失败,则调用 bus_remove_driver() 函数移除已添加的驱动程序,并返回相应的错误码。

  • 178 行:发送内核对象事件
c
	kobject_uevent(&drv->p->kobj, KOBJ_ADD);

调用 kobject_uevent() 函数向驱动程序的内核对象发送事件,通知驱动程序已成功添加到系统中。

2.  bus_add_driver()

bus_add_driver() 函数定义如下:

c
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);
	// 创建驱动程序的 uevent 属性文件
	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;
}
  • 645 - 647 行:获取总线对象
c
	bus = bus_get(drv->bus);
	if (!bus)
		return -EINVAL;

通过 drv-> bus 访问设备驱动程序结构体中的总线信息。通过调用 bus_get() 函数获取总线 对象。如果总线对象不存在,则返回 -EINVAL 错误码表示无效的参数。

  • 651 - 659 行:分配并初始化驱动程序私有数据
c
	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;

调用 kzalloc() 函数为驱动程序的私有数据结构体 priv 分配内存,并使用 GFP_KERNEL 标志进行内存分配。如果内存分配失败,则返回 -ENOMEM 错误码表示内存不足。使用 klist_init() 函数初始化 priv 结构体中的设备列表。设置 priv 结构体中的驱动程序指针,并将其赋值为当 前的驱动程序。

将 drv-> p 指向 priv 结构体,以便后续的释放操作。然后设置 priv-> kobj.kset 成员为总线对象的 drivers_kset。

  • 660 - 664 行:码初始化并添加驱动程序的内核对象
c
	error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
				     "%s", drv->name);
	if (error)
		goto out_unregister;

调用 kobject_init_and_add() 函数始化并添加驱动程序的内核对象。如果初始化或添加失败,则跳转到 out_unregister 进行错误处理。

  • 665 行:将驱动程序添加到总线的驱动程序列表
c
	klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

使用 klist_add_tail() 函数将驱动程序的节点添加到总线的驱动程序列表中。

  • 666 - 676 行:自动探测设备
c
	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;
		}
	}

如果总线启用了自动探测(drivers_autoprobe 标志),则调用 driver_attach() 函数尝试自动探测设备。如果自动探测失败,则跳转到 out_unregister 进行错误处理。其中变量 drivers_autoprobe 也可以在用户空间通过属性文件 drivers_autoprobe 来控制:

image-20250113162603098

这个文件的值默认是 1,若是我们改为 0,那么就不会自动去匹配设备了。

  • 677 行:将驱动程序添加到模块
c
	module_add_driver(drv->owner, drv);

调用 module_add_driver() 函数将驱动程序添加到模块中。

  • 679 - 683 行:创建驱动程序的 uevent 属性文件
c
	error = driver_create_file(drv, &driver_attr_uevent);
	if (error) {
		printk(KERN_ERR "%s: uevent attr (%s) failed\n",
			__func__, drv->name);
	}

调用 driver_create_file() 函数为驱动程序创建 uevent 属性文件。如果创建失败,则打印错 误消息。

  • 684 - 689 行:创建驱动程序的 uevent 属性文件
c
	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);
	}

调用 driver_add_groups() 函数将驱动程序的组属性添加到驱动程序中。如果添加失败,则打印错误消息。

  • 691 - 698 行:添加绑定属性文件
c
	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);
		}
	}

如果驱动程序没有禁止绑定属性文件(suppress_bind_attrs 标志),则调用 add_bind_files() 函数添加绑定属性文件。如果添加失败,则打印错误消息。