【Bus】编写一个Demo虚拟的总线-设备-驱动模型

1. 前言

Linux平台为了驱动的可重用性,虚拟了很多的虚拟总线。很经典的就是platform总线,只要platform device和platform driver的名字匹配就调用driver的probe函数。

在分析内核源码时,经常会遇到各种总线。为了方便和加深理解,本篇文章写了一个虚拟Demo总线来加深对总线模型的理解。下面是总线-设备-驱动模型大致架构如下:
在这里插入图片描述

2. 总线驱动模型三要素

三要素主要是包括:总线设备驱动

2.1 总线

  • struct bus_type 结构体:
    struct bus_type {
    	const char		*name; // 总线名称,如:/sys/bus/name
    	const struct attribute_group **bus_groups; // 总线属性
    	const struct attribute_group **dev_groups; // 设备属性,指向为每个加入总线的设备建立的默认属性链表
    	const struct attribute_group **drv_groups; // 驱动程序属性
    
    	/* match:当任何属于该Bus的device或者device_driver添加到内核时,内核都会调用该接口,如果新加的device或device_driver匹配上了自己的另一半的话,该接口要返回非零值,此时Bus模块的核心逻辑就会执行后续的处理。 */
    	int (*match)(struct device *dev, struct device_driver *drv);
    	/* uevent:一个由具体的bus core层实现的回调函数。当任何属于该Bus的device,发生添加、移除或者其它动作时,Bus模块的核心逻辑就会调用该接口,以便bus core层能够修改环境变量。 */
    	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    	int (*probe)(struct device *dev); // device和driver匹配后调用,并且最终会调用到driver的probe。
    	int (*remove)(struct device *dev); // 设备移除调用
    	void (*shutdown)(struct device *dev); // 关机调用
    	......
    };
    
    这里定义了一个name为virt_bus的虚拟总线,只实现了部分功能。非常重要的是virt_device_match函数,它决定了device和driver是否匹配成功,定义如下:在这里插入图片描述
  • 总线注册关键函数:
    int bus_register(struct bus_type *bus) // 注册一条总线
    void bus_unregister(struct bus_type *bus) // 注销一条总线
    
    Demo的平台总线的注册、注销如下:
    在这里插入图片描述
    注册成功后,会在/sys/bus/目录下生成一个新的bus name:virt_bus,如下:
    在这里插入图片描述

2.2 设备

  • struct device 结构体:
     struct device // linux/device.h
     {
        struct bus_type    *bus; //代表该设备挂在哪条总线上
        ...
     }
    
  • 设备注册关键函数:
    int device_register(struct device *dev)  // 注册一个设备
    void device_unregister(struct device *dev) // 注销一个设备
    
    Demo的设备注册、注销如下:
    (备注:dev_set_name这个函数很关键,一定要设置device的name,否则会导致device注册失败。)
    在这里插入图片描述
    设备注册成功后,会在/sys/bus/virt_bus/devices总线目录下生成该设备的name:tst3125-002d,它就是通过dev_set_name函数来设置的。效果如下:
    在这里插入图片描述

2.3 驱动

当新的driver注册到该总线时,会遍历该总线上所有设备是否有匹配。同理,当新的device注册到该总线时,也会遍历总线上所有的驱动是否有匹配。

  • struct device 结构体:
     struct device_driver //device.h
     {
     	const char  *name; // driver的name
        
        struct bus_type        *bus; //代表该驱动挂在哪条总线上
        int (*probe) (struct device *dev);  //探测函数
        int (*remove) (struct device *dev);  //移除驱动
        ...
     }
    
  • 驱动注册关键函数:
    int driver_register(struct device_driver *drv)
    void driver_unregister(struct device_driver *drv)
    
    Demo的设备注册、注销如下:
    在这里插入图片描述
    驱动注册成功后,会在/sys/bus/virt_bus/drivers总线目录下生成该驱动的name:tst3125。如果匹配成功,就会调用driver里面的probe函数。具体效果如下:
    在这里插入图片描述

3. Demo Code

3.1 virt_bus_core.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>

#include "virt_bus_core.h"

extern struct device_type virt_client_type;

const struct virt_device_id *virt_match_id(const struct virt_device_id *id,
						const struct virt_client *client)
{
	if (!(id && client))
		return NULL;

	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0)
			return id;
		id++;
	}
	return NULL;
}

struct virt_client *virt_verify_client(struct device *dev)
{
    return (dev->type == &virt_client_type)
            ? to_virt_client(dev)
            : NULL;
}

static int virt_device_match(struct device *dev, struct device_driver *drv)
{
	struct virt_client	*client = virt_verify_client(dev);
	struct virt_driver	*driver;

	driver = to_virt_driver(drv);

	/* Finally an virt match */
	if (virt_match_id(driver->id_table, client))
		return 1;

	return 0;
}

static int virt_device_probe(struct device *dev)
{
	struct virt_client	*client = virt_verify_client(dev);
	struct virt_driver	*driver;
	int status;

	if (!client)
		return 0;

	driver = to_virt_driver(dev->driver);

    if (driver->probe)
    	status = driver->probe(client,
    	            virt_match_id(driver->id_table, client));
    else
    	status = -EINVAL;

    return status;
}

static int virt_device_remove(struct device *dev)
{
	struct virt_client	*client = virt_verify_client(dev);
	struct virt_driver	*driver;
	int status = 0;

	if (!client || !dev->driver)
		return 0;

	driver = to_virt_driver(dev->driver);
	if (driver->remove) {
		dev_dbg(dev, "remove\n");
		status = driver->remove(client);
	}

	return status;
}

static void virt_client_dev_release(struct device *dev)
{
	kfree(to_virt_client(dev));
}

static ssize_t
show_name(struct device *dev, struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "%s\n", dev->type == &virt_client_type ?
		       to_virt_client(dev)->name : "unknown");
}
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);

static struct attribute *virt_dev_attrs[] = {
	&dev_attr_name.attr,
	NULL
};
ATTRIBUTE_GROUPS(virt_dev);

struct bus_type virt_bus_type = {
	.name		= "virt_bus",
	.match		= virt_device_match,
	.probe		= virt_device_probe,
	.remove		= virt_device_remove,
};
EXPORT_SYMBOL_GPL(virt_bus_type);

struct device_type virt_client_type = {
    .name       = "virt_client_device",
    .groups		= virt_dev_groups,
    .release    = virt_client_dev_release,
};
EXPORT_SYMBOL_GPL(virt_client_type);

struct virt_client *
virt_register_device(struct virt_board_info const *info)
{
    struct virt_client	*client;
    int         status;

	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client)
		return ERR_PTR(-ENOMEM);

	client->addr = info->addr;
	strlcpy(client->name, info->type, sizeof(client->name));

	client->dev.bus = &virt_bus_type;
	client->dev.type = &virt_client_type;

    dev_set_name(&client->dev, "%s-%04x", info->type, info->addr);

	status = device_register(&client->dev);
	if (status)
		goto out_err_silent;

    printk("device [%s] registered\n", client->name);

    return client;

out_err_silent:
	kfree(client);
	return ERR_PTR(status);
}
EXPORT_SYMBOL_GPL(virt_register_device);

void virt_unregister_device(struct virt_client *client)
{
	if (IS_ERR_OR_NULL(client))
		return;
    device_unregister(&client->dev);
}
EXPORT_SYMBOL_GPL(virt_unregister_device);

int virt_register_driver(struct module *owner, struct virt_driver *driver)
{
	int res;

	/* add the driver to the list of virt drivers in the driver core */
	driver->driver.owner = owner;
	driver->driver.bus = &virt_bus_type;
	
	/* When registration returns, the driver core
	 * will have called probe() for all matching-but-unbound devices.
	 */
	res = driver_register(&driver->driver);
	if (res)
		return res;

	printk("driver [%s] registered\n", driver->driver.name);

	return 0;
}
EXPORT_SYMBOL(virt_register_driver);

void virt_unregister_driver(struct virt_driver *driver)
{
	driver_unregister(&driver->driver);
	printk("driver [%s] unregistered\n", driver->driver.name);
}
EXPORT_SYMBOL(virt_unregister_driver);

static int __init virt_bus_core_init(void)
{
    int retval;

	retval = bus_register(&virt_bus_type);
	if (retval)
		return retval;
		
    return 0;
}

static void __exit virt_bus_core_exit(void)
{
    bus_unregister(&virt_bus_type);
}

module_init(virt_bus_core_init);
module_exit(virt_bus_core_exit);
MODULE_LICENSE("GPL");

3.2 virt_device.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/of_device.h>
#include <linux/string.h>

#include "virt_bus_core.h"

static struct virt_client *tst3125_client = NULL;

static int __init virt_device_tst3125_init(void)
{
	static struct virt_board_info board_info = {
	    .type = "tst3125",
	    .addr = 0x2d,
	};

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	tst3125_client = virt_register_device(&board_info);

	return 0;
}

static void __exit virt_device_tst3125_exit(void)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    
    virt_unregister_device(tst3125_client);
}

module_init(virt_device_tst3125_init);
module_exit(virt_device_tst3125_exit);
MODULE_LICENSE("GPL");

3.3 virt_driver.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/of_device.h>
#include <linux/string.h>

#include "virt_bus_core.h"

static int tst3125_probe(struct virt_client *client, const struct virt_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

    printk("[%s] client->name = %s id->name = %s\n", __FUNCTION__, client->name, id->name);

	return 0;
}

static int tst3125_remove(struct virt_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

    printk("[%s] client->name = %s\n", __FUNCTION__, client->name);

	return 0;
}


static const struct of_device_id of_match_ids_tst3125[] = {
	{ .compatible = "virtual,tst3125",		.data = NULL },
	{ /* END OF LIST */ },
};

static const struct virt_device_id tst3125_ids[] = {
	{ "tst3125",	(kernel_ulong_t)NULL },
	{ /* END OF LIST */ }
};

static struct virt_driver virt_tst3125_driver = {
	.driver = {
		.name = "tst3125",
		.of_match_table = of_match_ids_tst3125,
	},
	.probe = tst3125_probe,
	.remove = tst3125_remove,
	.id_table = tst3125_ids,
};

static int __init virt_driver_tst3125_init(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return virt_register_driver(THIS_MODULE, &virt_tst3125_driver);
}

static void __exit virt_driver_tst3125_exit(void)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	virt_unregister_driver(&virt_tst3125_driver);
}

module_init(virt_driver_tst3125_init);
module_exit(virt_driver_tst3125_exit);
MODULE_LICENSE("GPL");

问题一:virt_device_match匹配函数是如何被调用的?

为了方便分析问题,在match函数中添加错误代码,通过内核报错的打印信息来分析它的调用关系。
在这里插入图片描述
在这里插入图片描述
通过错误LOG分析调用关系如下:
在这里插入图片描述
在这里插入图片描述通过错误LOG分析调用关系如下:
在这里插入图片描述

4. 工程代码下载地址

完整的实验工程Demo代码下载地址如下:
https://download.csdn.net/download/ZHONGCAI0901/87741928

5. 参考资料

https://blog.csdn.net/qq_16504163/article/details/118562670

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值