LV06-04-linux设备模型-09-设备与驱动的匹配

前面我们已经注册了自己的总线,并可以在总线上注册设备和驱动,那么两者怎么匹配?若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
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 xxx_match()

总线中,我们需要实现xxx_match()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static int sbus_match(struct device *dev, struct device_driver *drv)
{
const char *device_name = dev_name(dev);
int len = strlen(drv->name);

PRT("dev name is %s, drv name is %s, name len = %d\n", device_name, drv->name, len);

if (!strncmp(device_name, drv->name, len))
{
PRT("dev is %s <---> drv is %s, match success!\n", drv->name, device_name);
return 1;
}
//else的话就是没有匹配上
return 0;
}

我们在这里比较两个驱动的名称和设备的名称,若是一样,就表示匹配成功,需要返回1,若是不同,就是没有匹配上,就返回0。那能反过来吗?比如匹配成功返回0,匹配失败返回1?这样不行,应该是调用的时候判断条件进行了限制。

1.2 xxx_probe()

在总线中还需要实现一个xxx_probe()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @brief sbus_probe()
* @note 设备探测的回调函数
* @param [in]
* @param [out]
* @retval
*/
static int sbus_probe(struct device *dev)
{
struct device_driver *drv = dev->driver;
const char *device_name = dev_name(dev);

PRT("device_name=%s driver_name=%s\n", device_name, drv->name);
if (drv->probe)
{
drv->probe(dev); // 调用驱动的 sdrv_probe() 探测函数
}

return 0;
}

1.3 sbus_demo.c

完整源码如下所示:

点击查看详情
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */

#include <linux/kobject.h>
#include <linux/slab.h>

#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 SBUS_NAME "sbus" // 总线名称, /sys/bus 中将会创建对应目录,即 /sys/bus/bus-name

/**
* bus 总线的属性变量数据结构,后续实现属性回调函数之后,可以通过 echo/cat 修改这些属性的值
*/
typedef struct __SBUS_ATTR_VAR_{
char name_attr[32];
int data;
}sbus_attr_var_t;

sbus_attr_var_t sbus_attr = {0}; // sbus 的属性变量

/**
* @brief sbus_match()
* @note 负责总线下的设备以及驱动匹配,使用字符串比较的方式,通过对比驱动以及设备的名字来确定是否匹配,
* 如果相同, 则说明匹配成功。
* @param [in]
* @param [out]
* @retval 匹配成功,返回1;反之,则返回0,这里应该是驱动内部有判断的地方,若是成功不是返回1,则不会执行
* 驱动中的probe函数
*/
static int sbus_match(struct device *dev, struct device_driver *drv)
{
const char *device_name = dev_name(dev);
int len = strlen(drv->name);

PRT("dev name is %s, drv name is %s, name len = %d\n", device_name, drv->name, len);

if (!strncmp(device_name, drv->name, len))
{
PRT("dev is %s <---> drv is %s, match success!\n", drv->name, device_name);
return 1;
}
//else的话就是没有匹配上
return 0;
}

/**
* @brief sbus_probe()
* @note 设备探测的回调函数,若是驱动中也定义了xxx_probe()函数,则驱动模块加载时
* 会报 Driver 'xxx' needs updating - please use bus_type methods
* 另外,在驱动模块加载时,是优先判断总线中probe函数是否存在,若存在,则会执行总线中的这个probe函数。
* @param [in]
* @param [out]
* @retval
*/
static int sbus_probe(struct device *dev)
{
struct device_driver *drv = dev->driver;
const char *device_name = dev_name(dev);

PRT("device_name=%s driver_name=%s\n", device_name, drv->name);
if (drv->probe)
{
drv->probe(dev); // 调用驱动的 sdrv_probe() 探测函数
}

return 0;
}

// 定义一个新的总线,变量名为 g_sbus,总线结构体中最重要的一个成员,便是 match 回调函数
static struct bus_type g_sbus = {
.name = SBUS_NAME, // 总线的名称 "sbus", /sys/bus 中将会创建对应目录,即 /sys/bus/bus-name
.match = sbus_match, // 设备和驱动程序匹配的回调函数
.probe = sbus_probe, // 设备探测的回调函数
};
EXPORT_SYMBOL_GPL(g_sbus); // 导出总线符号,也就是导出 g_sbus 变量,驱动和设备中都会用到

/**
* @brief xxx_attr_show()
* @note 提供show回调函数,这样用户便可以通过cat命令,来查询相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sbus_attr_name_show(struct bus_type *bus, char *buf)
{
ssize_t count = 0;
count = sprintf(buf, "%s\n", sbus_attr.name_attr);
PRT("bus->name=%s count=%d\n", bus->name, count);

return count;
}

/**
* @brief xxx_attr_store()
* @note 提供store回调函数,这样用户便可以通过echo命令, 来修改相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sbus_attr_name_store(struct bus_type *bus, const char *buf, size_t count)
{
PRT("bus->name=%s count=%d\n", bus->name, count);
sscanf(buf, "%s\n", sbus_attr.name_attr);
return count;
}

struct bus_attribute sbus_attr_name_var = {
.attr = {
.name = "sbus_attr_name", // 属性的名称,将会创建 /sys/bus/bus-name/attr-name 文件,并可以使用 cat/echo 进行操作
.mode = 0664, // 属性的访问权限
},
.show = sbus_attr_name_show, // 属性的 show 回调函数
.store = sbus_attr_name_store, // 属性的 show 回调函数
};

/**
* @brief xxx_attr_show()
* @note 提供show回调函数,这样用户便可以通过cat命令,来查询相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sbus_attr_data_show(struct bus_type *bus, char *buf)
{
ssize_t count = 0;
count = sprintf(buf, "%d\n", sbus_attr.data);
PRT("bus->name=%s count=%d\n", bus->name, count);

return count;
}

/**
* @brief xxx_attr_store()
* @note 提供store回调函数,这样用户便可以通过echo命令, 来修改相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sbus_attr_data_store(struct bus_type *bus, const char *buf, size_t count)
{
PRT("bus->name=%s count=%d\n", bus->name, count);
sscanf(buf, "%d\n", &sbus_attr.data);
return count;
}

/**
* 下面这个宏可以简化属性变量的定义,从定义可以看出,这里定义的属性的变量前缀是 bus_attr_,
* 后面要再拼接name才行
*
* #define BUS_ATTR(_name, _mode, _show, _store)
* struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
*
* 例如下面这个变量使用时,加上前缀 bus_attr_ ,变量名为 bus_attr_sbus_attr_data
*/
BUS_ATTR(sbus_attr_data, 0664, sbus_attr_data_show, sbus_attr_data_store);

/**
* @brief sbus_demo_init()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __init int sbus_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("sbus_demo module init!\n");

ret = bus_register(&g_sbus);
if(ret < 0)
{
PRTE("bus_register fail!\n");
goto err_bus_register;
}

// 初始化属性文件控制的变量的值
snprintf(sbus_attr.name_attr, sizeof(sbus_attr.name_attr), SBUS_NAME);
sbus_attr.data = 1;

ret = bus_create_file(&g_sbus, &sbus_attr_name_var);
if(ret < 0)
{
PRTE("bus_create_file sbus_attr_name_var fail!ret=%d\n", ret);
goto err_bus_create_file1;
}

ret = bus_create_file(&g_sbus, &bus_attr_sbus_attr_data);
if(ret < 0)
{
PRTE("bus_create_file bus_attr_sbus_attr_data fail!ret=%d\n", ret);
goto err_bus_create_file2;
}

return 0;

err_bus_create_file2:
bus_remove_file(&g_sbus, &sbus_attr_name_var);
err_bus_create_file1:
bus_unregister(&g_sbus);
err_bus_register:
return ret;
}

/**
* @brief sbus_demo_exit()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __exit void sbus_demo_exit(void)
{
bus_remove_file(&g_sbus, &bus_attr_sbus_attr_data);
bus_remove_file(&g_sbus, &sbus_attr_name_var);
bus_unregister(&g_sbus); // 取消注册总线

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

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

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

2. 设备

2.1 设备名称

主要是要定义好设备名称:

1
#define SDEV_NAME  "sumu-dev"   // 设备名称, 和驱动中的 匹配名称 相同时就可以匹配对应的驱动

2.2 xxx_release()

可以在这里实现一个释放函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @brief sdev_release()
* @note
* @param [in]
* @param [out]
* @retval
*/
void sdev_release(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);

}

2.3 sdevice_demo.c

完整源码如下所示:

点击查看详情
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#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 SDEV_NAME "sumu-dev" // 设备名称, 和驱动中的 匹配名称 相同时就可以匹配对应的驱动
// /sys/bus/bus-name/devices 中将会创建对应目录,即 /sys/bus/bus-name/devices/device-name
// /sys/devices 中也会创建对应的目录,即/sys/devices/device-name

extern struct bus_type g_sbus; // g_sbus 操作函数集的那个全局变量,包含了match函数

/**
* sdev设备的属性变量数据结构,后续实现属性回调函数之后,可以通过 echo/cat 修改这些属性的值
*/
typedef struct __SDEV_ATTR_VAR_{
char name_attr[32];
int data;
}sdev_attr_var_t;

sdev_attr_var_t sdev_attr = {0}; // sdevice 的属性

/**
* @brief sdev_release()
* @note
* @param [in]
* @param [out]
* @retval
*/
void sdev_release(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);

}

static struct device sdev = {
.init_name = SDEV_NAME, // 设备的初始化名称,/sys/bus/bus-name/devices 中将会创建对应的目录,即 /sys/bus/bus-name/devices/device-name
.bus = &g_sbus, // 所属总线
.release = sdev_release, // 设备的释放回调函数
};

/**
* @brief xxx_attr_show()
* @note 提供show回调函数,这样用户便可以通过cat命令, 来查询相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sdev_attr_name_show(struct device *dev, struct device_attribute *attr, char *buf)
{
ssize_t count = 0;
count = sprintf(buf, "%s\n", sdev_attr.name_attr);
PRT("attr->name=%s count=%d\n", attr->attr.name, count);

return count;
}

/**
* @brief xxx_attr_store()
* @note 提供store回调函数,这样用户便可以通过echo命令, 来修改相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sdev_attr_name_store(struct device * dev, struct device_attribute * attr, const char *buf, size_t count)
{
PRT("attr->name=%s count=%d\n", attr->attr.name, count);
sscanf(buf, "%s\n", sdev_attr.name_attr);
return count;
}

struct device_attribute sdev_attr_name_var = {
.attr = {
.name = "sdev_attr_name", // 属性的名称,将会显示在 /sys/bus/bus-name/devices/device-name/ 中,并可以使用cat/echo 进行操作
.mode = 0664, // 属性的访问权限
},
.show = sdev_attr_name_show, // 属性的 show 回调函数
.store = sdev_attr_name_store, // 属性的 show 回调函数
};

/**
* @brief xxx_attr_show()
* @note 提供show回调函数,这样用户便可以通过cat命令, 来查询相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sdev_attr_data_show(struct device *dev, struct device_attribute *attr, char *buf)
{
ssize_t count = 0;
count = sprintf(buf, "%d\n", sdev_attr.data);
PRT("attr->name=%s count=%d\n", attr->attr.name, count);

return count;
}

/**
* @brief xxx_attr_store()
* @note 提供store回调函数,这样用户便可以通过echo命令, 来修改相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sdev_attr_data_store(struct device * dev, struct device_attribute * attr, const char *buf, size_t count)
{
PRT("attr->name=%s count=%d\n", attr->attr.name, count);
sscanf(buf, "%d\n", &sdev_attr.data);
return count;
}

/**
* 下面这个宏可以简化属性变量的定义,从定义可以看出,这里定义的属性的变量前缀是 dev_attr_,
* 后面要再拼接name才行
*
* #define DEVICE_ATTR(_name, _mode, _show, _store)
* struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
*
* 例如下面这个变量使用时,加上前缀 dev_attr_ ,变量名为 dev_attr_sdev_attr_data
*/
DEVICE_ATTR(sdev_attr_data, 0664, sdev_attr_data_show, sdev_attr_data_store);

/**
* @brief sdev_demo_init()
* @note 设备结构体以及属性文件结构体注册
* @param [in]
* @param [out]
* @retval
*/
static __init int sdev_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("sdev_demo module init!\n");

ret = device_register(&sdev);
if(ret < 0)
{
PRTE("device_register fail! ret = %d\n", ret);
goto err_device_register;
}

// 初始化属性文件控制的变量的值
snprintf(sdev_attr.name_attr, sizeof(sdev_attr.name_attr), SDEV_NAME);
sdev_attr.data = 1;

ret = device_create_file(&sdev, &sdev_attr_name_var);
if(ret < 0)
{
PRTE("device_create_file sdev_attr_name_var fail!ret=%d\n", ret);
goto err_device_create_file1;
}

ret = device_create_file(&sdev, &dev_attr_sdev_attr_data);
if(ret < 0)
{
PRTE("device_create_file dev_attr_sdev_attr_data fail!ret=%d\n", ret);
goto err_device_create_file2;
}

return 0;

err_device_create_file2:
device_remove_file(&sdev, &sdev_attr_name_var);
err_device_create_file1:
device_unregister(&sdev);
err_device_register:
return ret;
}

/**
* @brief sdev_demo_exit
* @note 设备结构体以及属性文件结构体注销。
* @param [in]
* @param [out]
* @retval
*/
static __exit void sdev_demo_exit(void)
{
device_remove_file(&sdev, &dev_attr_sdev_attr_data);
device_remove_file(&sdev, &sdev_attr_name_var);
device_unregister(&sdev); // 取消注册设备

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

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

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

3. 驱动

3.1 驱动名称

需要注意的是,驱动的名称要和设备名称一致,这样我们才能在对应的总线中通过xxx_match()函数,实现驱动和设备的匹配:

1
#define SDRV_MATCH_NAME  "sumu-dev"    // 和 设备匹配的名字,设备名为 sumu-dev 的才能匹配成功

3.2 xxx_probe()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @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;
}

其实驱动自己的探测函数实现之后,在加载的时候反而会有一个警告,后面再分析。

3.3 xxx_remove()

可以再实现一个remove函数,用于清理资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @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;
}

3.4 sdriver_demo.c

完整源码如下:

点击查看详情
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#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_MATCH_NAME "sumu-dev" // 和 设备匹配的名字,设备名为 sumu-dev 的才能匹配成功
// 这个会作为驱动的名字, /sys/bus/bus-name/drivers 中将会创建对应目录,即 /sys/bus/bus-name/drivers/driver-name
extern struct bus_type g_sbus; // g_sbus 操作函数集的那个全局变量,包含了 match 函数

/**
* sdrv驱动的属性变量数据结构,后续实现属性回调函数之后,可以通过 echo/cat 修改这些属性的值
*/
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 驱动程序的探测函数,若是总线模块中也定义了xxx_probe()函数,则驱动模块加载时
* 会报 Driver 'xxx' needs updating - please use bus_type methods
* 另外,在驱动模块加载时,是优先判断总线中probe函数是否存在,若存在,则会执行总线中的这个probe函数。
* 若总线不存在probe函数,则会执行驱动中的这个probe函数。
* @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 中将会创建对应目录,即 /sys/bus/bus-name/drivers/driver-name
.bus = &g_sbus, // 该驱动挂载在已经注册好的总线 bus-name 下。
.probe = sdrv_probe, // 驱动程序的探测函数
.remove = sdrv_remove, // 当注销驱动时,需要关闭物理设备的某些功能等
};

/**
* @brief xxx_attr_show()
* @note 提供show回调函数,这样用户便可以通过cat命令, 来查询相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sdrv_attr_name_show(struct device_driver *driver, char *buf)
{
ssize_t count = 0;
count = sprintf(buf, "%s\n", sdrv_attr.name_attr);
PRT("device_driver->name=%s count=%d\n", driver->name, count);
return count;
}

/**
* @brief xxx_attr_store()
* @note 提供store回调函数,这样用户便可以通过echo命令, 来修改相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sdrv_attr_name_store(struct device_driver *driver, const char *buf, size_t count)
{
PRT("device_driver->name=%s count=%d\n", driver->name, count);
sscanf(buf, "%s\n", sdrv_attr.name_attr);
return count;
}

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

/**
* @brief xxx_attr_show()
* @note 提供show回调函数,这样用户便可以通过cat命令, 来查询相关属性参数
* @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 xxx_attr_store()
* @note 提供store回调函数,这样用户便可以通过echo命令, 来修改相关属性参数
* @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;
}

/**
* 下面这个宏可以简化属性变量的定义,从定义可以看出,这里定义的属性的变量前缀是 driver_attr_,
* 后面要再拼接name才行
*
* #define DRIVER_ATTR_RW(_name)
* struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
* #define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)
*
* 例如下面这个变量使用时,加上前缀 driver_attr_ ,变量名为 driver_attr_sdrv_attr_name
*/
DRIVER_ATTR_RW(sdrv_attr_data);

/**
* @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;
}

// 初始化属性文件控制的变量的值
snprintf(sdrv_attr.name_attr, sizeof(sdrv_attr.name_attr), SDRV_MATCH_NAME);
sdrv_attr.data = 1;

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

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

err_driver_create_file2:
driver_remove_file(&sdrv, &sdrv_attr_name_var);
err_driver_create_file1:
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, &driver_attr_sdrv_attr_data);
driver_remove_file(&sdrv, &sdrv_attr_name_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"); /* 字符串常量内容为模块别名 */

4. 开发板测试

  • (1)加载总线模块
1
insmod sbus_demo.ko
image-20250113155339512
  • (2)加载驱动模块
1
insmod sdriver_demo.ko
image-20250113155426552
  • (3)加载设备模块
1
insmod sdevice_demo.ko
image-20250113155534905

可以看到,打印出了匹配成功的关键字。

  • (4)查看 /sys/bus/bus-name 下的文件情况
1
tree /sys/bus/sbus/
image-20250113155704752
  • (5)卸载模块

需要注意,卸载模块的时候要先卸载设备和驱动的,总线的最后卸载,因为设备和驱动都依赖于总线:

image-20250113155843741
1
2
3
rmmod sdevice_demo.ko
rmmod sdriver_demo.ko
rmmod sbus_demo.ko

会有如下打印:

image-20250113160006054

二、xxx_probe()函数执行流程

1. driver_attach()

前面分析 bus_add_driver() 函数的时候,这个函数调用了 driver_attach() 函数来探测设备,我们来看一下这个 driver_attach() 函数。该函数定义如下:

1
2
3
4
5
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
EXPORT_SYMBOL_GPL(driver_attach);

可以看到里面调用了 bus_for_each_dev() 函数。这里要注意一下,调用这个函数的时候传入了另一个函数 __driver_attach()

1.1  bus_for_each_dev()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 (!error && (dev = next_device(&i)))
error = fn(dev, data);
// 退出设备列表迭代器
klist_iter_exit(&i);
return error;
}
EXPORT_SYMBOL_GPL(bus_for_each_dev);

这个函数的作用是遍历指定总线上的所有设备,并对每个设备执行指定的函数 fn。

这个函数的参数说明如下:

bus:指定要遍历的总线对象。

start:指定开始遍历的设备对象。如果为 NULL,则从总线的第一个设备开始遍历。

data:传递给函数 fn 的额外数据。

fn:指定要执行的函数,该函数接受一个设备对象和额外数据作为参数,并返回一个整数错误码。在这里,这个指针指向 __driver_attach() 函数

1
2
if (!bus || !bus->p)
return -EINVAL;

首先,检查传入的总线对象是否存在以及与该总线相关的私有数据是否存 在。如果总线对象或其私有数据不存在,返回 -EINVAL 表示无效的参数错误码。

1
2
klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL));

接下来,初始化设备列表迭代器,以便遍历总线上的设备。 使用 klist_iter_init_node() 函数初始化一个设备列表迭代器。传递总线对象的设备列表 klist_devices、迭代器对象 i,以及可选的起始设备的节点指针。然后,在一个循环中遍历设备列表并执行指定的函数。

1
2
while (!error && (dev = next_device(&i)))
error = fn(dev, data);

使用 next_device() 函数从迭代器中获取下一个设备。如果存在下一个设 备,则调用传入的函数指针 fn,并将当前设备和额外的数据参数传递给它。如果执行函数时出现错误,将错误码赋值给 error。

1
klist_iter_exit(&i);

最后,退出设备列表迭代器,释放相关资源。使用 klist_iter_exit()函数退 出设备列表的迭代器。 总的来说, bus_for_each_dev() 函数主要是提供了一个遍历指定总线上的设备对象列表, 并对每个设备对象进行特定操作的快捷方式,可以用于驱动程序中需要管理和操作大量设备实例的场景。

1.2 __driver_attach()

前面知道fn指针指向的是__driver_attach() 函数:

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
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;// 如果没有匹配,则返回 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 && dev->bus->need_parent_lock)
device_lock(dev->parent);
device_lock(dev);// 锁定设备以保护 dev->driver 和 async_driver 字段
if (!dev->p->dead && !dev->driver)
driver_probe_device(drv, dev);//最终这里会执行到bus的xxx_probe()函数
device_unlock(dev);
if (dev->parent && dev->bus->need_parent_lock)
device_unlock(dev->parent);

return 0;
}

__driver_attach() 中使用 driver_match_device()函数尝试将驱动程序绑定到设备上。可以看到,函数返回大于0的值后表示匹配成功。继续往下会调用 driver_probe_device() 函数来尝试绑定设备和驱动程序。其他的我们暂时不关心。

 1.2.1 driver_match_device()

我们来看一下 driver_match_device() 函数:

1
2
3
4
5
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

可以看到,该函数用于检查设备是否与驱动程序匹配。其中,drv是指向设备驱动程序对象的指针,dev是指向设备对象的指针。它的执行过程如下:

(1)检查驱动程序对象的 bus 字段是否为 NULL,以及 bus 字段的 match 函数是否存在。 驱动程序对象的 bus 字段表示该驱动程序所属的总线。match 函数是总线对象中的一个函数指 针,用于检查设备与驱动程序是否匹配。

(2)如果 match函数存在,则调用总线对象的 match 函数,传入设备对象和驱动程序对象作为参数。drv->bus->match(dev, drv)表示调用总线对象的 match 函数,并将设备对象和驱动程序对象 作为参数传递给该函数。dev 是用于匹配的设备对象。drv 是用于匹配的驱动程序对象。

(3)如果总线对象的 match 函数返回 0,则表示设备与驱动程序不匹配,函数将返回 0。返回值为 0 表示不匹配。

(4)如果总线对象的 match 函数返回非零值(大于 0),则表示设备与驱动程序匹配,函数将返回 1。返回值为 1 表示匹配。

(5)如果总线对象的 match 函数不存在(为 NULL),则默认认为设备与驱动程序匹配,函数将返回 1。

总之,它其实就是调用了 drv->bus->match 函数,就是我们前面总线中实现的xxx_match()函数来完成匹配。这个逻辑就是先判断一下match函数是否为空,若为空,就返回1,上一级函数 __driver_attach() 就可以正常往下执行,若是match函数不为空,这个时候返回值就是函数执行的结果,当返回0的时候表示没有设备匹配,返回1表示有设备匹配成功。

1.2.2 driver_probe_device()

如果设备和驱动匹配上,会继续执行 driver_probe_device()函数,它的定义如下:

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
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;
// 检查设备是否已注册,如果未注册则返回错误码 -ENODEV
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;
}

其他先不管,我们直接看第 658 - 661 行,上面这个 initcall_debug 是个全局变量,应该是调试的时候用,调试的时候调用 really_probe_debug() 函数,这个函数内部也是在调用 really_probe() 函数,我们直接看这个 really_probe() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
if (ret)
goto probe_failed;
}
// ......
return ret;
}

这个函数挺长的,别的就没咋关心了,直接看第 499 - 507 行这段。从这里可以看到,这里其实就是判断了一下dev->bus->probe、drv->probe这两个函数是否存在,存在的话就执行。 dev->bus->probe这个函数其实就是总线中实现的xxx_probe()函数,而 drv->probe 则是驱动中实现的xxx_probe()函数,上面的逻辑就是优先执行总线中的probe函数,若是总线中不存在,我们还可以找驱动中的probe函数执行。

1.3 总结

经过前面代码的分析,设备和驱动匹配流程函数的调用关系如下图所示:

image-20250113193242318

2. really_probe()

really_probe() 函数定义如下:

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
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) {
/*
* 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.
*/
/* defer_all_probes 的值只能通过 device_defer_all_probes_enable() 设置,
* 而该函数会紧接着调用 wait_for_device_probe(),以避免任何竞争情况。
*/
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));
WARN_ON(!list_empty(&dev->devres_head));

re_probe:
dev->driver = drv;

/* If using pinctrl, bind pins now before probing */
/* 如果使用了 pinctrl,绑定引脚 */
ret = pinctrl_bind_pins(dev);
if (ret)
goto pinctrl_bind_failed;
// 配置 DMA
ret = dma_configure(dev);
if (ret)
goto probe_failed;
// 添加驱动的 sysfs
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);
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);// 移除驱动的 sysfs
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);// 重新初始化给定设备对象中的运行时PM字段。

goto re_probe;// 重新进行探测
}
// 完成 pinctrl 的初始化
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); // 取消 DMA 配
driver_sysfs_remove(dev); // 移除驱动的 sysfs
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);// 设置设备的驱动标志为 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.
*/
/* 忽略 ->probe 返回的错误,以便下一个驱动程序可以尝试运行。*/
ret = 0;
done:
atomic_dec(&probe_count); // 减少探测计数
wake_up(&probe_waitqueue);// 唤醒等待探测的进程
return ret;
}

三、驱动和设备加载的先后顺序

由于驱动和设备都要用到总线中的符号,所以总线一定是最先加载的,这个毫无疑问,其实在测试demo的过程中,会发现不管先加载驱动,还是先加载设备,最终都会完成匹配。

加载驱动的时候上面已经分析过了,会在 __driver_attach() 函数中调用总线中的匹配函数完成设备和驱动的匹配(若是有驱动在的话)。那么先加载驱动后加载设备,也是可以完成匹配的,设备加载的时候是怎么完成匹配的呢?

1. device_add()

device_add()函数定义如下:

1
2
3
4
5
6
7
8
9
10
int device_add(struct device *dev)
{
// ......
bus_probe_device(dev);
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
// ......
}
EXPORT_SYMBOL_GPL(device_add);

该函数内部调用了 bus_probe_device() 函数,这个函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void bus_probe_device(struct device *dev)
{
struct bus_type *bus = dev->bus;
struct subsys_interface *sif;

if (!bus)
return;

if (bus->p->drivers_autoprobe)
device_initial_probe(dev);

mutex_lock(&bus->p->mutex);
list_for_each_entry(sif, &bus->p->interfaces, node)
if (sif->add_dev)
sif->add_dev(dev, sif);
mutex_unlock(&bus->p->mutex);
}

可以看到在函数内部调用了 device_initial_probe() 函数,该函数定义如下:

1
2
3
4
void device_initial_probe(struct device *dev)
{
__device_attach(dev, true);
}

可以看到这个函数最终又调用了 __device_attach() 函数。

2. __device_attach()

__device_attach() 函数定义如下:

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
static int __device_attach(struct device *dev, bool allow_async)
{
int ret = 0;

device_lock(dev);
if (dev->driver) {
if (device_is_bound(dev)) { // 如果设备已经绑定了驱动程序,则返回 1
ret = 1;
goto out_unlock;
}
// 尝试将设备与驱动程序进行绑定
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
// 绑定失败,将设备的驱动程序指针置为 NULL
dev->driver = NULL;
ret = 0;
}
} else {
// 如果设备没有驱动程序,需要遍历总线上的驱动程序进行匹配
struct device_attach_data data = {
.dev = dev,
.check_async = allow_async,
.want_async = false,
};
// 如果设备有父设备,调用 pm_runtime_get_sync() 增加父设备的引用计数
if (dev->parent)
pm_runtime_get_sync(dev->parent);
// 遍历总线上的驱动程序,调用 __device_attach_driver() 进行匹配
ret = bus_for_each_drv(dev->bus, NULL, &data,
__device_attach_driver);
if (!ret && allow_async && data.have_async) {
/*
* If we could not find appropriate driver
* synchronously and we are allowed to do
* async probes and there are drivers that
* want to probe asynchronously, we'll
* try them.
*/
/*
* 如果无法同步找到适合的驱动程序,并且允许异步探测以及有驱动程序要求异步探测,
* 则尝试进行异步探测。
*/
dev_dbg(dev, "scheduling asynchronous probe\n");
// 增加设备的引用计数,以确保在异步探测期间设备不会被释放
get_device(dev);
// 调度异步任务 __device_attach_async_helper() 进行异步探测
async_schedule(__device_attach_async_helper, dev);
} else {
// 如果无法异步探测或者没有驱动程序要求异步探测,则调用 pm_request_idle() 进入空闲状态
pm_request_idle(dev);
}
// 如果设备有父设备,调用 pm_runtime_put() 减少父设备的引用计数
if (dev->parent)
pm_runtime_put(dev->parent);
}
out_unlock:
device_unlock(dev); // 解锁设备
return ret;
}
1
device_lock(dev);

通过调用 device_lock(dev) 锁定设备,确保在进行设备附加操作时不会被其他线程干扰。

1
2
3
4
5
6
7
8
9
10
11
12
13
if (dev->driver) {
if (device_is_bound(dev)) {
ret = 1;
goto out_unlock;
}
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
}

检查 dev->driver 成员是否为空,此处非空的情况。设备已经绑定了驱动程序,则返回 1。如果设备没有绑定驱动程序,则尝试将设备与驱动程序进行绑定。如果绑定成功,返回 1;否则,将设备的驱动程序指针置为 NULL,并返回 0。

1
2
3
4
5
struct device_attach_data data = {
.dev = dev,
.check_async = allow_async,
.want_async = false,
};

dev->driver 为空时,那么需要遍历总线上的驱动程序进行匹配。为此,定义了一个结构体 struct device_attach_data,其中包含了设备、是否允许异步 探测以及是否有驱动程序要求异步探测的信息。

1
2
if (dev->parent)
pm_runtime_get_sync(dev->parent);

如果设备有父设备,调用 pm_runtime_get_sync(dev->parent) 增加父设备的引用计数。

814 - 829

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ret = bus_for_each_drv(dev->bus, NULL, &data,
__device_attach_driver);
if (!ret && allow_async && data.have_async) {
/*
* If we could not find appropriate driver
* synchronously and we are allowed to do
* async probes and there are drivers that
* want to probe asynchronously, we'll
* try them.
*/
dev_dbg(dev, "scheduling asynchronous probe\n");
get_device(dev);
async_schedule(__device_attach_async_helper, dev);
} else {
pm_request_idle(dev);
}

调用 bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver) 遍历总线上的驱动 程序,并调用 __device_attach_driver()进行匹配。__device_attach_driver() 是一个回调函数,用于判断驱动程序是否适配当前设备。

如果无法同步找到适合的驱动程序,并且允许异步探测以及有驱动程序要求异步探测,则调度异步任务 __device_attach_async_helper() 进行异步探测。在异步探测之前,会增加设备的引用计数以确保设备在异步探测期间不会被释放。异步探测会在后台进行,不会阻塞当前线程。

如果无法异步探测或者没有驱动程序要求异步探测,则调用 pm_request_idle(dev) 进入空 闲状态,让设备进入省电模式。

3. device_bind_driver()

device_bind_driver() 函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
int device_bind_driver(struct device *dev)
{
int ret;

ret = driver_sysfs_add(dev);
if (!ret)
driver_bound(dev);
else if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_DRIVER_NOT_BOUND, dev);
return ret;
}
EXPORT_SYMBOL_GPL(device_bind_driver);

函数中调用 driver_bound() 函数完成设备和驱动程序的绑定,这个函数定义如下:

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
static void driver_bound(struct device *dev)
{
// 如果设备已经绑定了驱动程序,则输出警告信息并返回
if (device_is_bound(dev)) {
printk(KERN_WARNING "%s: device %s already bound\n",
__func__, kobject_name(&dev->kobj));
return;
}
// 输出绑定信息
pr_debug("driver: '%s': %s: bound to device '%s'\n", dev->driver->name,
__func__, dev_name(dev));
// 将设备添加到驱动程序的设备链表中
klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
// 更新设备的驱动程序链接状态
device_links_driver_bound(dev);
// 检查设备的电源管理回调函数
device_pm_check_callbacks(dev);

/*
* Make sure the device is no longer in one of the deferred lists and
* kick off retrying all pending devices
*/
/*
* 确保设备不再位于延迟探测列表中,并启动重试所有待处理设备
*/
driver_deferred_probe_del(dev);
driver_deferred_probe_trigger();
// 如果设备有总线,调用总线通知链进行通知
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_BOUND_DRIVER, dev);
// 发送内核对象事件通知
kobject_uevent(&dev->kobj, KOBJ_BIND);
}

driver_bound() 函数的作用是将驱动和设备进行绑定,首先,通过调用 device_is_bound(dev) 检查设备是否已经绑定了驱动程序。如果设备已经绑定了驱动程序,则输出警告信息并返回。如果设备未绑定驱动程序,将输出绑定信息,其中包括驱动程序的名称、函数名和设备的名称。接下 来,通过调用 klist_add_tail()将设备添加到驱动程序的设备链表中。这样,驱动程序可以通 过遍历该链表来访问所有已绑定的设备。 然后,调用 device_links_driver_bound()更新设备的驱动程序链接状态。这个函数会确保设备和驱动程序之间的链接关系是正确的。

4. 总结

先加载驱动,后加载设备的时候,设备和驱动匹配的时候的函数调用关系如下:

image-20250115142202576

四、总结

1. 设备和驱动数据结构关系

到为止简单地了解了总线、设备、驱动的数据结构以及注册/注销接口函数。下图是总线关联上设备与驱动之后的数据结构关系图:

/sys/bus目录

2. 注册流程

设备和驱动的注册流程如下:

/sys/bus目录

系统启动之后会调用buses_init函数创建/sys/bus文件目录,这部分系统在开机时已经帮我们准备好了, 接下去就是通过总线注册函数bus_register进行总线注册,注册完总线后在总线的目录下生成devices文件夹和drivers文件夹, 最后分别通过device_register以及driver_register函数注册相对应的设备和驱动。