LV210-注册驱动到总线
前面已经可以注册自定义总线和在总线注册设备,那么还缺少驱动了,现在来看看怎么在总线上注册一个驱动?
一、驱动
1. 驱动简介
1.1 struct device_driver
设备能否正常工作,取决于驱动。驱动需要告诉内核, 自己可以驱动哪些设备,如何初始化设备。在内核中,使用 device_driver 结构体来描述我们的驱动,它定义在 device.h - include/linux/device.h - struct device_driver:
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_table :struct of_device_id 类型指针变量,指定该驱动支持的设备类型。当内核使能设备树时,会利用该成员与设备树中的 compatible 属性进行比较。
- remove :当设备从操作系统中拔出或者是系统重启时,会调用该回调函数;
- probe :当驱动以及设备匹配后,会执行该回调函数,对设备进行初始化。通常的代码,都是以 main 函数开始执行的,但是在内核的驱动代码,都是从 probe 函数开始的。
- groups :指向 struct attribute_group 类型的指针,指定该驱动的属性;
1.2 驱动的注册与注销
1.2.1 driver_register()
driver_register() 定义如下:
/**
* 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);【参数说明】
- drv :struct device_driver 结构体类型指针。
【返回值】
- 成功: 0
- 失败: 负数
1.2.2 driver_unregister()
driver_unregister() 定义如下:
/**
* 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);【参数说明】
- drv:struct device_driver 结构体类型指针。
【返回值】 无
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:
#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 定义如下:
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_RW、DRIVER_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:
#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() 函数定义如下:
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() 函数定义如下:
extern void driver_remove_file(struct device_driver *driver,
const struct driver_attribute *attr);该函数用于删除对应的属性文件。
2.4 使用实例
在调用 driver_create_file() 函数之前,需要先定义好属性结构体 struct driver_attribute ,并将其相关字段填充好。有两种方式,一种是直接定义:
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);另一种是通过宏来定义:
// 下面这个宏可以简化属性变量的定义,从定义可以看出,这里定义的属性的变量前缀是 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 函数实现如下:
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:
#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 目录
- (2)加载模块

加载模块后,会发现,在/sys/bus 目录生成了 sbus 目录,并且/sys/bus/sbus/drivers 目录下也生成了对应的驱动的目录,在驱动目录下,有我们创建的属性文件。
- (3)查看/修改 sdrv_attr_data 的值
总线的属性,之前已经修改和验证过了,这里只看一下设备的属性文件,其实操作都是一样的:

- (5)卸载模块

可以看到,卸载模块后,资源释放,sbus 目录被删除,相关的属性文件也都删除了。
二、驱动的注册流程
1. driver_register()
我们从 driver_register() 函数来看一下注册流程:
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;
}函数用于注册设备驱动程序并将其添加到总线中,接下来我们详细分析一下。
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 访问设备驱动程序结构体中的总线信息。前面初始化的时候已经填充了对应的成员:
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 错误码表示无效的 参数。
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 中是有这个打印的:

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

驱动和总线中都定义了 probe 方法,后面我们再详细了解。
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 错误码表示设备忙。
ret = bus_add_driver(drv);
if (ret)
return ret;调用 bus_add_driver() 函数将驱动程序添加到总线。如果添加失败,则返回相应的错误码。
ret = driver_add_groups(drv, drv->groups);
if (ret) {
bus_remove_driver(drv);
return ret;
}调用 driver_add_groups() 函数将驱动程序的组属性添加到驱动程序中。如果添加失败,则调用 bus_remove_driver() 函数移除已添加的驱动程序,并返回相应的错误码。
- 第 178 行:发送内核对象事件
kobject_uevent(&drv->p->kobj, KOBJ_ADD);调用 kobject_uevent() 函数向驱动程序的内核对象发送事件,通知驱动程序已成功添加到系统中。
2. bus_add_driver()
bus_add_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);
// 创建驱动程序的 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;
} bus = bus_get(drv->bus);
if (!bus)
return -EINVAL;通过 drv-> bus 访问设备驱动程序结构体中的总线信息。通过调用 bus_get() 函数获取总线 对象。如果总线对象不存在,则返回 -EINVAL 错误码表示无效的参数。
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。
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 行:将驱动程序添加到总线的驱动程序列表
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);使用 klist_add_tail() 函数将驱动程序的节点添加到总线的驱动程序列表中。
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 来控制:

这个文件的值默认是 1,若是我们改为 0,那么就不会自动去匹配设备了。
- 第 677 行:将驱动程序添加到模块
module_add_driver(drv->owner, drv);调用 module_add_driver() 函数将驱动程序添加到模块中。
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 属性文件。如果创建失败,则打印错 误消息。
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() 函数将驱动程序的组属性添加到驱动程序中。如果添加失败,则打印错误消息。
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() 函数添加绑定属性文件。如果添加失败,则打印错误消息。