背景
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中。