Skip to content

LV215-设备与驱动匹配

前面我们已经注册了自己的总线,并可以在总线上注册设备和驱动,那么两者怎么匹配?

一、设备和驱动匹配

1. 总线

1.1 xxx_match()

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

c
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()函数:

c
/**
 * @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

完整源码如下所示:

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 设备名称

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

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

2.2 xxx_release()

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

c
/**
 * @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

完整源码如下所示:

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()函数,实现驱动和设备的匹配:

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

3.2 xxx_probe()

c
/**
 * @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函数,用于清理资源:

c
/**
 * @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

完整源码如下:

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)加载总线模块
shell
insmod sbus_demo.ko
image-20250113155339512
  • (2)加载驱动模块
shell
insmod sdriver_demo.ko
image-20250113155426552
  • (3)加载设备模块
shell
insmod sdevice_demo.ko
image-20250113155534905

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

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

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

image-20250113155843741
shell
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() 函数。该函数定义如下:

c
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()

c
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() 函数

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

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

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

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

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

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

c
	klist_iter_exit(&i);

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

1.2 __driver_attach()

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

c
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() 函数:

c
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()函数,它的定义如下:

c
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() 函数:

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

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

c
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() 函数,这个函数定义如下:

c
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() 函数,该函数定义如下:

c
void device_initial_probe(struct device *dev)
{
	__device_attach(dev, true);
}

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

2. __device_attach()

__device_attach() 函数定义如下:

c
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;
}
c
	device_lock(dev);

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

c
	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。

c
		struct device_attach_data data = {
			.dev = dev,
			.check_async = allow_async,
			.want_async = false,
		};

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

c
		if (dev->parent)
			pm_runtime_get_sync(dev->parent);

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

814 - 829

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

c
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() 函数完成设备和驱动程序的绑定,这个函数定义如下:

c
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函数注册相对应的设备和驱动。