【微码】Mellanox中的ib_device_ops是如何“分工实现,合作初始化”的?

背景

Mellanox驱动中ib设备有各种子模块,驱动中ib驱动和ibverbs一个是对上提供访问接口。一个是对下操作驱动接口。通用Linux驱动大多采用类似file_ops的方式解耦驱动具体实现和驱动上层业务实现,但是在Mellanox中可能由于驱动模块的复杂性,涉及到多个子模块。那么多个子模块如何对上提供一个同一的file_ops,又如何做到良好的隔离各个子模块(包括文件,头文件)。本文将介绍他所使用的方法,我简单称呼为“分工实现,合作初始化”。
本文以Mellanox为例介绍了他的实现,并写了个伪代码来实现这个细节。从而为未来设计类似代码提供一个快速的范式。关于为何选择Mellanox的代码来分析,一方面他是产业上一个具体实现,并且又二十几年历史的沉淀,另一方面他经过了大量的实战演练,是属于他自己的事实标准。

通用模块

# 驱动规范header - 定义可以操作的ops文件
struct my_device_ops {
	int *modulea_1();
	int *modulea_2();
	int *moduleb_1();
	int *moduleb_2();
};

# 设备header - 定义
struct my_device {
	struct my_device_ops	     ops;
}

将不同模块的ops分工设置到dev的ops中。
void my_set_device_ops(struct my_device *dev, const struct my_device_ops *ops)
{
	struct my_device_ops *dev_ops = &dev->ops;
	//下面的宏定义实现,将入参ops中定义的不为空的操作函数,根据分工设置到同一的dev设备的ops中。也就是从驱动到设备的初始化
	#define SET_DEVICE_OP(ptr, name)                                               \
		do {                                                                   \
			if (ops->name)                                                 \
				if (!((ptr)->name))				       \
					(ptr)->name = ops->name;                       \
		} while (0)
}
	然后将所有支持的ops一一调用SET_DEVICE_OP,来选择性赋值。
	SET_DEVICE_OP(dev_ops, modulea_1);
	SET_DEVICE_OP(dev_ops, modulea_2);
	SET_DEVICE_OP(dev_ops, moduleb_1); //假设模块A注册时候因为moduleb_1为空,SET_DEVICE_OP不会操作dev_ops中的数据,从而做到分工初始化
	SET_DEVICE_OP(dev_ops, moduleb_2);
	...
}

# 模块a-header
//定义moduleA的函数
int modulea_1()
{
printk("xxx\n");
}

int modulea_2()
{
printk("xxx\n");
}

struct my_device_ops modulea_ops = { //和B使用相同的全局ops定义
	.modulea_1 = my_modulea_1;
	.modulea_2 = my_modulea_2;
};
my_set_device_ops(dev, modulea_ops)

# 模块b-header
//定义moduleA的函数
int moduleb_1()
{
printk("xxx\n");
}

int moduleb_2()
{
printk("xxx\n");
}

struct my_device_ops moduleb_ops = { //和A使用相同的全局ops定义
	.moduleb_1 = my_moduleb_1;
	.moduleb_2 = my_moduleb_2;
};
my_set_device_ops(dev, moduleb_ops)

如此一来在全局初始化中调用各个模块的device_ops后
my_set_device_ops(dev, moduleb_ops)和
my_set_device_ops(dev, modulea_ops)调用完就分工初始化一个完整的device_ops。

Mellanox实现举例

数据结构关系

设备驱动的ops

struct ib_device_ops {
	struct module *owner;
	enum rdma_driver_id driver_id;
	u32 uverbs_abi_ver;
	unsigned int uverbs_no_driver_id_binding:1;

	int (*post_send)(struct ib_qp *qp, const struct ib_send_wr *send_wr,
			 const struct ib_send_wr **bad_send_wr);
	int (*post_recv)(struct ib_qp *qp, const struct ib_recv_wr *recv_wr,
			 const struct ib_recv_wr **bad_recv_wr);
	void (*drain_rq)(struct ib_qp *qp);
	void (*drain_sq)(struct ib_qp *qp);
	int (*poll_cq)(struct ib_cq *cq, int num_entries, struct ib_wc *wc);
	...
};

设置设备驱动ops到设备dev上。

void ib_set_device_ops(struct ib_device *dev, const struct ib_device_ops *ops)
{
	struct ib_device_ops *dev_ops = &dev->ops;

	# 将ib定义的ops都一一赋值到ib设备的dev的操作函数中去。但是只设置不为空的
	if(!ops->alloc_pd) dev_ops->alloc_pd = ops->alloc_pd;
	...
	if(!ops->dev_ops) dev_ops->destroy_qp = ops->dev_ops;
...

	# 上面函数会根据ops中是否有某个函数则赋值到该函数
}

比如很多子模块定义ib_ops的地方,初始化到同一的设备dev的ops中:

## sriov定义自己的ops
static const struct ib_device_ops mlx5_ib_dev_sriov_ops = {
	.get_vf_config = mlx5_ib_get_vf_config,
	.get_vf_guid = mlx5_ib_get_vf_guid,
	.get_vf_stats = mlx5_ib_get_vf_stats,
	.set_vf_guid = mlx5_ib_set_vf_guid,
	.set_vf_link_state = mlx5_ib_set_vf_link_state,
};

ib_set_device_ops(&dev->ib_dev, &mlx5_ib_dev_sriov_ops);

## 比如counter定义自己的ops
static const struct ib_device_ops counters_ops = {
	.create_counters = mlx5_ib_create_counters,
	.destroy_counters = mlx5_ib_destroy_counters,
	.read_counters = mlx5_ib_read_counters,

	INIT_RDMA_OBJ_SIZE(ib_counters, mlx5_ib_mcounters, ibcntrs),
};

## 比如最主要的ib_dev的ops
static const struct ib_device_ops mlx5_ib_dev_ops = {
	.owner = THIS_MODULE,
	.driver_id = RDMA_DRIVER_MLX5,
	.uverbs_abi_ver	= MLX5_IB_UVERBS_ABI_VERSION,

	.add_gid = mlx5_ib_add_gid,
	.alloc_mr = mlx5_ib_alloc_mr,
	.alloc_mr_integrity = mlx5_ib_alloc_mr_integrity,
	.alloc_pd = mlx5_ib_alloc_pd,
	.alloc_ucontext = mlx5_ib_alloc_ucontext,
	.attach_mcast = mlx5_ib_mcg_attach,
	.check_mr_status = mlx5_ib_check_mr_status,
	.create_ah = mlx5_ib_create_ah,
	.create_cq = mlx5_ib_create_cq,
	.create_qp = mlx5_ib_create_qp,
...

ib_set_device_ops(&dev->ib_dev, &mlx5_ib_dev_ops);
}

从上面多个ib_set_device_ops可以看到,同一个dev的操作函数,会使用多个dev_ops进行set,并且set方式是设置方都定义自己的ib_dev_ops,并把自己支持的ib_dev_ops赋值,其他的为空不设置。然后调用ib_set_device_ops的时候,会根据如果不为空则修改dev中对应的函数。这样一来,通过ib_set_device_ops这个接口将不同的模块函数统一到一起。做到了不同模块实现函数能够独立文件开发,同时也不用提供头文件extern具体实现函数来设置全量ib_device_ops。相当于通过一种分工注册制方式来管理不同模块的接口。这种方法值得借鉴。

Mellanox中代码位置

通用模块set的函数实现位置:drivers/infiniband/core/device.c
在这里插入图片描述

在这里插入图片描述
这里也一定程度解释了ib_core的核心功能是如何体现核心的(core),比如ib_set_device_ops,就是将驱动的ops设置的device中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值