LV06-04-linux设备模型-08-注册驱动到总线

前面已经可以注册自定义总线和在总线注册设备,那么还缺少驱动了,现在来看看怎么在总线上注册一个驱动?若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
kernel/git/stable/linux.git - Linux kernel stable tree linux kernel源码(官网,tag 4.19.71)
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、驱动

1. 驱动简介

1.1 struct device_driver

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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() 定义如下:

1
2
3
4
5
6
7
8
9
/**
* 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() 定义如下:

1
2
3
4
5
6
7
/**
* 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

1
2
3
4
5
6
#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 定义如下:

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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() 函数定义如下:

1
2
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() 函数定义如下:

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

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

2.4 使用实例

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

1
2
3
4
5
6
7
8
9
10
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);

另一种是通过宏来定义:

1
2
3
4
5
6
// 下面这个宏可以简化属性变量的定义,从定义可以看出,这里定义的属性的变量前缀是 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函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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源码

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

05_device_model/15_driver_register · 苏木/imx6ull-driver-demo - 码云 - 开源中国

05_device_model/16_driver_register_with_attr · 苏木/imx6ull-driver-demo - 码云 - 开源中国

点击查看详情

这里直接已加了属性的demo为例 05_device_model/16_driver_register_with_attr · 苏木/imx6ull-driver-demo - 码云 - 开源中国

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#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() 函数来看一下注册流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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 行:检查总线是否已初始化
1
2
3
4
5
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 访问设备驱动程序结构体中的总线信息。前面初始化的时候已经填充了对应的成员:

1
2
3
4
5
6
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 行:检查驱动程序的方法是否需要更新
1
2
3
4
5
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 行:检查驱动程序是否已被注册
1
2
3
4
5
6
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 行:添加驱动程序到总线
1
2
3
ret = bus_add_driver(drv);
if (ret)
return ret;

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

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

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

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

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

2.  bus_add_driver()

bus_add_driver() 函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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 行:获取总线对象
1
2
3
bus = bus_get(drv->bus);
if (!bus)
return -EINVAL;

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

  • 651 - 659 行:分配并初始化驱动程序私有数据
1
2
3
4
5
6
7
8
9
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 行:码初始化并添加驱动程序的内核对象
1
2
3
4
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 行:将驱动程序添加到总线的驱动程序列表
1
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

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

  • 666 - 676 行:自动探测设备
1
2
3
4
5
6
7
8
9
10
11
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 行:将驱动程序添加到模块
1
module_add_driver(drv->owner, drv);

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

  • 679 - 683 行:创建驱动程序的 uevent 属性文件
1
2
3
4
5
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 属性文件
1
2
3
4
5
6
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 行:添加绑定属性文件
1
2
3
4
5
6
7
8
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() 函数添加绑定属性文件。如果添加失败,则打印错误消息。