LV215-设备与驱动匹配
前面我们已经注册了自己的总线,并可以在总线上注册设备和驱动,那么两者怎么匹配?
一、设备和驱动匹配
1. 总线
1.1 xxx_match()
总线中,我们需要实现xxx_match()函数:
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()函数:
/**
* @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
完整源码如下所示:
#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 设备名称
主要是要定义好设备名称:
#define SDEV_NAME "sumu-dev" // 设备名称, 和驱动中的 匹配名称 相同时就可以匹配对应的驱动2.2 xxx_release()
可以在这里实现一个释放函数:
/**
* @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
完整源码如下所示:
#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()函数,实现驱动和设备的匹配:
#define SDRV_MATCH_NAME "sumu-dev" // 和 设备匹配的名字,设备名为 sumu-dev 的才能匹配成功3.2 xxx_probe()
/**
* @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函数,用于清理资源:
/**
* @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
完整源码如下:
#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)加载总线模块
insmod sbus_demo.ko
- (2)加载驱动模块
insmod sdriver_demo.ko
- (3)加载设备模块
insmod sdevice_demo.ko
可以看到,打印出了匹配成功的关键字。
- (4)查看 /sys/bus/bus-name 下的文件情况
tree /sys/bus/sbus/
- (5)卸载模块
需要注意,卸载模块的时候要先卸载设备和驱动的,总线的最后卸载,因为设备和驱动都依赖于总线:
rmmod sdevice_demo.ko
rmmod sdriver_demo.ko
rmmod sbus_demo.ko会有如下打印:

二、xxx_probe()函数执行流程
1. driver_attach()
前面分析 bus_add_driver() 函数的时候,这个函数调用了 driver_attach() 函数来探测设备,我们来看一下这个 driver_attach() 函数。该函数定义如下:
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()
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() 函数
if (!bus || !bus->p)
return -EINVAL;首先,检查传入的总线对象是否存在以及与该总线相关的私有数据是否存 在。如果总线对象或其私有数据不存在,返回 -EINVAL 表示无效的参数错误码。
klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL));接下来,初始化设备列表迭代器,以便遍历总线上的设备。 使用 klist_iter_init_node() 函数初始化一个设备列表迭代器。传递总线对象的设备列表 klist_devices、迭代器对象 i,以及可选的起始设备的节点指针。然后,在一个循环中遍历设备列表并执行指定的函数。
while (!error && (dev = next_device(&i)))
error = fn(dev, data);使用 next_device() 函数从迭代器中获取下一个设备。如果存在下一个设 备,则调用传入的函数指针 fn,并将当前设备和额外的数据参数传递给它。如果执行函数时出现错误,将错误码赋值给 error。
- 第 315 行
klist_iter_exit(&i);最后,退出设备列表迭代器,释放相关资源。使用 klist_iter_exit()函数退 出设备列表的迭代器。 总的来说, bus_for_each_dev() 函数主要是提供了一个遍历指定总线上的设备对象列表, 并对每个设备对象进行特定操作的快捷方式,可以用于驱动程序中需要管理和操作大量设备实例的场景。
1.2 __driver_attach()
前面知道fn指针指向的是__driver_attach() 函数:
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() 函数:
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()函数,它的定义如下:
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() 函数:
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 总结
经过前面代码的分析,设备和驱动匹配流程函数的调用关系如下图所示:

2. really_probe()
really_probe() 函数定义如下:
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()函数定义如下:
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() 函数,这个函数定义如下:
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() 函数,该函数定义如下:
void device_initial_probe(struct device *dev)
{
__device_attach(dev, true);
}可以看到这个函数最终又调用了 __device_attach() 函数。
2. __device_attach()
__device_attach() 函数定义如下:
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;
}- 第 791 行
device_lock(dev);通过调用 device_lock(dev) 锁定设备,确保在进行设备附加操作时不会被其他线程干扰。
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。
struct device_attach_data data = {
.dev = dev,
.check_async = allow_async,
.want_async = false,
};dev->driver 为空时,那么需要遍历总线上的驱动程序进行匹配。为此,定义了一个结构体 struct device_attach_data,其中包含了设备、是否允许异步 探测以及是否有驱动程序要求异步探测的信息。
if (dev->parent)
pm_runtime_get_sync(dev->parent);如果设备有父设备,调用 pm_runtime_get_sync(dev->parent) 增加父设备的引用计数。
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() 函数定义如下:
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() 函数完成设备和驱动程序的绑定,这个函数定义如下:
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. 总结
先加载驱动,后加载设备的时候,设备和驱动匹配的时候的函数调用关系如下:
四、总结
1. 设备和驱动数据结构关系
到为止简单地了解了总线、设备、驱动的数据结构以及注册/注销接口函数。下图是总线关联上设备与驱动之后的数据结构关系图:

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

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