* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
*
* File Name : ibv_extend.c
* Description : The implementation of RDMA NDA Function extension interface
*/
#define _GNU_SOURCE
#include <infiniband/verbs.h>
#include <infiniband/driver.h>
#include <ccan/list.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <dlfcn.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <securec.h>
#include "config.h"
#include "ibv_extend.h"
#define OUTPFX "ibv_extend: "
const size_t driver_name_max_len = 128;
const size_t trunc_first = 1;
const size_t trunc_second = 2;
const size_t trunc_last = 3;
const size_t trunc_reserve_len = 4;
const size_t warning_msg_buf_len = 512;
#define API_EXPORT __attribute__((visibility("default")))
struct ibv_extend_driver {
struct list_node entry;
const struct verbs_device_extend_ops *ops;
};
struct ibv_extend_driver_name {
struct list_node entry;
char *ext_name;
};
static LIST_HEAD(extend_driver_list);
static LIST_HEAD(extend_driver_name_list);
* @brief Warning日志封装函数
* @param format 格式化字符串
* @param ... 可变参数
*
* 通过检查环境变量IBV_EXTEND_SHOW_WARNINGS来决定是否输出Warning日志
*/
static void ibv_extend_warning(const char *format, ...)
{
static volatile int show_warning = -1;
char msg_buf[warning_msg_buf_len];
va_list args;
int len;
if (format == NULL) {
(void)fprintf(stderr, OUTPFX "Warning: (null format)\n");
return;
}
if (show_warning == -1) {
int env_result = (getenv("IBV_EXTEND_SHOW_WARNINGS") != NULL) ? 1 : 0;
show_warning = env_result;
}
if (show_warning) {
va_start(args, format);
len = vsnprintf_s(msg_buf, sizeof(msg_buf), sizeof(msg_buf) - 1, format, args);
va_end(args);
if (len < 0) {
(void)fprintf(stderr, OUTPFX "Warning: (format error)\n");
return;
}
if ((size_t)len >= sizeof(msg_buf) - 1) {
size_t truncate_pos = sizeof(msg_buf) - trunc_reserve_len;
msg_buf[truncate_pos] = '.';
msg_buf[truncate_pos + trunc_first] = '.';
msg_buf[truncate_pos + trunc_second] = '.';
msg_buf[truncate_pos + trunc_last] = '\0';
}
(void)fprintf(stderr, OUTPFX "Warning: %s\n", msg_buf);
}
}
* @brief 注册扩展驱动
* @param ops 驱动操作结构体指针
*/
API_EXPORT void verbs_register_driver_extend(const struct verbs_device_extend_ops *ops)
{
struct ibv_extend_driver *driver;
if (!ops) {
(void)fprintf(stderr,
OUTPFX "Error: couldn't register driver for NULL ops\n");
return;
}
if (!ops->name) {
(void)fprintf(stderr,
OUTPFX "Error: couldn't register driver for NULL name\n");
return;
}
driver = malloc(sizeof(struct ibv_extend_driver));
if (!driver) {
(void)fprintf(stderr,
OUTPFX "Error: couldn't allocate extend driver for %s\n",
ops->name);
return;
}
driver->ops = ops;
list_add_tail(&extend_driver_list, &driver->entry);
}
* 尝试查找与指定设备匹配的扩展驱动程序。
* @param device 指定的IB设备。
* @return 如果找到匹配的驱动程序,返回其扩展操作结构体指针,否则返回NULL。
*/
static const struct verbs_device_extend_ops *try_extend_driver(struct ibv_device *device)
{
const struct verbs_device_extend_ops *ext_ops = NULL;
struct verbs_device *vdev;
struct ibv_extend_driver *driver;
if (!device) {
return NULL;
}
vdev = verbs_get_device(device);
list_for_each(&extend_driver_list, driver, entry) {
if (strcmp(vdev->ops->name, driver->ops->name) == 0) {
ext_ops = driver->ops;
break;
}
}
return ext_ops;
}
* 处理driver配置项
* @param driver_name 驱动名称
* @return 0-成功,-1-失败
*
* 该函数负责处理配置文件中的driver配置项
*/
static int handle_driver_config(const char *driver_name)
{
struct ibv_extend_driver_name *ext_driver_name;
ext_driver_name = malloc(sizeof(struct ibv_extend_driver_name));
if (!ext_driver_name) {
(void)fprintf(stderr, OUTPFX "Error: couldn't allocate extend driver name '%s'.\n",
driver_name);
return -1;
}
ext_driver_name->ext_name = strdup(driver_name);
if (!ext_driver_name->ext_name) {
(void)fprintf(stderr, OUTPFX "Error: couldn't allocate extend driver name '%s'.\n",
driver_name);
free(ext_driver_name);
return -1;
}
list_add(&extend_driver_name_list, &ext_driver_name->entry);
return 0;
}
* 检查是否为空行或注释行
* @param line 配置行内容
* @return 1-是空行或注释行,0-不是
*/
static bool is_empty_or_comment_line(const char *line)
{
if (line == NULL) {
return 1;
}
return line[0] == '\n' || line[0] == '#' || line[0] == '\0';
}
* 校验配置驱动名称的合法性
* @param driver_name 驱动名称
* @param path 配置文件路径
* @return 1-合法,0-不合法
*/
static bool validate_config_driver_name(const char *driver_name, const char *path)
{
if (driver_name == NULL || driver_name[0] == '\0') {
ibv_extend_warning("missing driver name in file '%s'", path);
return 0;
}
if (strlen(driver_name) > driver_name_max_len) {
ibv_extend_warning("driver name too long in file '%s'", path);
return 0;
}
for (const char *p = driver_name; *p != '\0'; p++) {
if (!isalnum((unsigned char)*p) && *p != '_' && *p != '-') {
ibv_extend_warning("invalid character in driver name '%s' in file '%s'",
driver_name, path);
return 0;
}
}
return 1;
}
* 处理driver配置项
* @param ext_config 配置内容指针
* @param path 配置文件路径
*/
static void process_driver_config(char *ext_config, const char *path)
{
char *field;
ext_config += strspn(ext_config, "\t ");
field = strsep(&ext_config, "\n\t ");
if (!validate_config_driver_name(field, path)) {
return;
}
(void)handle_driver_config(field);
}
* 解析单行配置
* @param line 配置行内容
* @param path 配置文件路径(用于错误提示)
*
* 该函数负责解析配置文件的单行内容
*/
static void parse_config_line(char *line, const char *path)
{
char *ext_config;
char *field;
if (line == NULL || path == NULL) {
return;
}
ext_config = line + strspn(line, "\t ");
if (is_empty_or_comment_line(ext_config)) {
return;
}
field = strsep(&ext_config, "\n\t ");
if (field == NULL || field[0] == '\0') {
ibv_extend_warning("invalid config line in file '%s'", path);
return;
}
if (strcmp(field, "driver") == 0 && ext_config != NULL) {
process_driver_config(ext_config, path);
} else {
ibv_extend_warning("ignoring bad config directive '%s' in file '%s'",
field, path);
}
}
* 读取扩展配置文件
* @param path 配置文件路径
*
* 此函数打开并读取指定路径的扩展配置文件
*/
static void read_extend_config_file(const char *path)
{
FILE *ext_conf;
char *line = NULL;
size_t buflen = 0;
ssize_t len;
ext_conf = fopen(path, "r" STREAM_CLOEXEC);
if (!ext_conf) {
ibv_extend_warning("couldn't read extend config file %s", path);
return;
}
while ((len = getline(&line, &buflen, ext_conf)) != -1) {
parse_config_line(line, path);
}
if (line) {
free(line);
}
(void)fclose(ext_conf);
}
* 读取扩展配置文件
* 该函数用于读取指定目录下的扩展配置文件。
*/
static void read_extend_config()
{
DIR *ext_conf_dir;
struct dirent *ext_dent;
char *tmp_path;
ext_conf_dir = opendir(EXTEND_IBV_CONFIG_DIR);
if (!ext_conf_dir) {
return;
}
while ((ext_dent = readdir(ext_conf_dir))) {
struct stat buf;
if (strcmp(ext_dent->d_name, ".") == 0 || strcmp(ext_dent->d_name, "..") == 0) {
continue;
}
if (strstr(ext_dent->d_name, "..") != NULL || strchr(ext_dent->d_name, '/') != NULL) {
continue;
}
if (asprintf(&tmp_path, "%s/%s", EXTEND_IBV_CONFIG_DIR, ext_dent->d_name) < 0) {
ibv_extend_warning("couldn't read extend config file %s/%s",
EXTEND_IBV_CONFIG_DIR, ext_dent->d_name);
goto out;
}
if (lstat(tmp_path, &buf)) {
goto next;
}
if (!S_ISREG(buf.st_mode)) {
goto next;
}
if (buf.st_uid != 0 && buf.st_uid != getuid()) {
goto next;
}
read_extend_config_file(tmp_path);
next:
free(tmp_path);
}
out:
closedir(ext_conf_dir);
}
* 校验驱动名称的基本有效性
* @param name 驱动程序名称
* @return 0-有效,-1-无效
*/
static int validate_driver_name(const char *name)
{
if (!name) {
ibv_extend_warning("couldn't load NULL extend file");
return -1;
}
if (name[0] == '\0') {
ibv_extend_warning("couldn't load empty extend file");
return -1;
}
if (strlen(name) > driver_name_max_len) {
ibv_extend_warning("driver name too long '%s'", name);
return -1;
}
return 0;
}
* 校验驱动名称字符的合法性(非绝对路径时)
* @param name 驱动程序名称
* @return 0-有效,-1-无效
*/
static int validate_driver_name_chars(const char *name)
{
for (const char *p = name; *p != '\0'; p++) {
if (!isalnum((unsigned char)*p) && *p != '_' && *p != '-') {
ibv_extend_warning("invalid character in driver name '%s'", name);
return -1;
}
}
return 0;
}
* 尝试加载指定路径的驱动库
* @param so_name 驱动库完整路径
* @return 0-加载成功,-1-加载失败
*/
static int try_load_driver(const char *so_name)
{
void *dlhandle = dlopen(so_name, RTLD_NOW);
char *error;
if (dlhandle) {
return 0;
}
error = dlerror();
if (error) {
ibv_extend_warning("dlopen '%s' failed: %s", so_name, error);
} else {
ibv_extend_warning("dlopen '%s' failed: unknown error", so_name);
}
return -1;
}
* 加载绝对路径的驱动程序
* @param name 驱动程序绝对路径
* @return 0-成功,-1-失败
*/
static int load_absolute_path_driver(const char *name)
{
char *so_name = NULL;
int ret = -1;
if (strstr(name, "..") != NULL) {
ibv_extend_warning("invalid path '%s' (path traversal detected)", name);
return -1;
}
if (asprintf(&so_name, "%s", name) < 0) {
ibv_extend_warning("couldn't load extend driver '%s'", name);
return -1;
}
if (try_load_driver(so_name) == 0) {
ret = 0;
}
free(so_name);
return ret;
}
* 加载相对路径的驱动程序
* @param name 驱动程序名称
* @return 0-成功,-1-失败
*/
static int load_relative_path_driver(const char *name)
{
char *so_name = NULL;
if (sizeof(EXTEND_VERBS_PROVIDER_DIR) > 1) {
if (asprintf(&so_name, EXTEND_VERBS_PROVIDER_DIR "/lib%s.so", name) < 0) {
ibv_extend_warning("couldn't load extend driver '%s'", name);
return -1;
}
if (try_load_driver(so_name) == 0) {
free(so_name);
return 0;
}
free(so_name);
}
if (asprintf(&so_name, "lib%s.so", name) < 0) {
ibv_extend_warning("couldn't load extend driver '%s'", name);
return -1;
}
if (try_load_driver(so_name) == 0) {
free(so_name);
return 0;
}
free(so_name);
return -1;
}
* 加载扩展驱动程序。
* @param name 驱动程序名称,不能为空。
*
* 该函数尝试加载指定的扩展驱动程序
* 1. 如果name是绝对路径,则直接加载
* 2. 如果定义EXTEND_VERBS_PROVIDER_DIR,则尝试在此目录下查找并加载驱动程序
* 3. 最后尝试在当前目录下加载驱动程序
*/
static void load_extend_driver(const char *name)
{
if (validate_driver_name(name) != 0) {
return;
}
if (name[0] == '/') {
(void)load_absolute_path_driver(name);
return;
}
if (validate_driver_name_chars(name) != 0) {
return;
}
(void)load_relative_path_driver(name);
}
* 从环境变量加载驱动程序
* @param env 环境变量字符串
*/
static void load_drivers_from_env(const char *env)
{
char *list, *split, *env_name;
list = strdup(env);
if (!list) {
(void)fprintf(stderr,
OUTPFX "Error: failed to allocate memory for IBV_EXTEND_DRIVERS.\n");
return;
}
split = list;
while ((env_name = strsep(&split, ":;"))) {
load_extend_driver(env_name);
}
free(list);
}
* 加载配置文件中的驱动程序
*/
static void load_drivers_from_config(void)
{
struct ibv_extend_driver_name *name, *next_name;
list_for_each_safe (&extend_driver_name_list, name, next_name, entry) {
load_extend_driver(name->ext_name);
list_del(&name->entry);
free(name->ext_name);
free(name);
}
}
* @brief 加载扩展驱动程序
*
* 此函数用于加载扩展驱动程序。
*/
static void load_extend_drivers()
{
const char *env;
read_extend_config();
if (getuid() == geteuid() && (env = getenv("IBV_EXTEND_DRIVERS"))) {
load_drivers_from_env(env);
}
load_drivers_from_config();
}
* @brief 初始化verbs_extend上下文
*
* @param context ib上下文
* @return 返回扩展的上下文,如果失败则返回NULL
*/
API_EXPORT struct ibv_context_extend *ibv_open_extend(struct ibv_context *context)
{
struct ibv_device *device;
const struct verbs_device_extend_ops *ext_ops = NULL;
if (context == NULL) {
ibv_extend_warning("couldn't open extend context for NULL ibv_context");
return NULL;
}
device = context->device;
ext_ops = try_extend_driver(device);
if (ext_ops) {
if (!ext_ops->alloc_context) {
ibv_extend_warning("alloc_context is NULL for ops %s", ext_ops->name);
return NULL;
}
return ext_ops->alloc_context(context);
}
load_extend_drivers();
ext_ops = try_extend_driver(device);
if (ext_ops) {
if (!ext_ops->alloc_context) {
ibv_extend_warning("alloc_context is NULL for ops %s", ext_ops->name);
return NULL;
}
return ext_ops->alloc_context(context);
}
ibv_extend_warning("no available ops for open extend context");
return NULL;
}
* @brief 关闭扩展上下文
* @param context 扩展上下文,由ibv_open_extend分配
* @return 0-成功,其他-失败
*/
API_EXPORT int ibv_close_extend(struct ibv_context_extend *context)
{
struct ibv_context *ctx;
struct ibv_device *device;
const struct verbs_device_extend_ops *ext_ops;
if (context == NULL) {
return -EINVAL;
}
ctx = context->context;
if (!ctx) {
return -EINVAL;
}
device = ctx->device;
ext_ops = try_extend_driver(device);
if (!ext_ops) {
return -ENOENT;
}
if (ext_ops->free_context) {
ext_ops->free_context(context);
}
return 0;
}
* @brief 查询设备支持的扩展能力
* @param context 扩展上下文
* @param ext_dev_attr 扩展设备属性输出参数
* @return 0-成功,其他值-失败
*/
API_EXPORT int ibv_query_device_extend(struct ibv_context_extend *context,
struct ibv_device_attr_extend *ext_dev_attr)
{
if (context == NULL || ext_dev_attr == NULL) {
return -EINVAL;
}
if (context->ops == NULL || context->ops->query_device == NULL) {
return -EPERM;
}
ext_dev_attr->ext_cap = 0;
return context->ops->query_device(context->context, ext_dev_attr);
}
* @brief 创建扩展QP
* @param context 扩展上下文
* @param qp_init_attr QP初始化属性
* @return QP扩展结构体指针,失败返回NULL
*/
API_EXPORT struct ibv_qp_extend *ibv_create_qp_extend(struct ibv_context_extend *context,
struct ibv_qp_init_attr_extend *qp_init_attr)
{
if (context == NULL || qp_init_attr == NULL) {
return NULL;
}
if (context->ops == NULL || context->ops->create_qp == NULL) {
return NULL;
}
return context->ops->create_qp(context->context, qp_init_attr);
}
* @brief 创建扩展CQ
* @param context 扩展上下文
* @param cq_init_attr CQ初始化属性
* @return CQ扩展结构体指针,失败返回NULL
*/
API_EXPORT struct ibv_cq_extend *ibv_create_cq_extend(struct ibv_context_extend *context,
struct ibv_cq_init_attr_extend *cq_init_attr)
{
if (context == NULL || cq_init_attr == NULL) {
return NULL;
}
if (context->ops == NULL || context->ops->create_cq == NULL) {
return NULL;
}
return context->ops->create_cq(context->context, cq_init_attr);
}
* @brief 创建扩展SRQ
* @param context 扩展上下文
* @param srq_init_attr SRQ初始化属性
* @return SRQ扩展结构体指针,失败返回NULL
*/
API_EXPORT struct ibv_srq_extend *ibv_create_srq_extend(struct ibv_context_extend *context,
struct ibv_srq_init_attr_extend *srq_init_attr)
{
if (context == NULL || srq_init_attr == NULL) {
return NULL;
}
if (context->ops == NULL || context->ops->create_srq == NULL) {
return NULL;
}
return context->ops->create_srq(context->context, srq_init_attr);
}
* @brief 销毁扩展QP
* @param context 扩展上下文
* @param qp_extend QP扩展结构体
* @return 0-成功,其他-失败
*/
API_EXPORT int ibv_destroy_qp_extend(struct ibv_context_extend *context, struct ibv_qp_extend *qp_extend)
{
if (context == NULL || qp_extend == NULL) {
return -EINVAL;
}
if (context->ops == NULL || context->ops->destroy_qp == NULL) {
return -EPERM;
}
return context->ops->destroy_qp(qp_extend);
}
* @brief 销毁扩展CQ
* @param context 扩展上下文
* @param cq_extend CQ扩展结构体
* @return 0-成功,其他-失败
*/
API_EXPORT int ibv_destroy_cq_extend(struct ibv_context_extend *context, struct ibv_cq_extend *cq_extend)
{
if (context == NULL || cq_extend == NULL) {
return -EINVAL;
}
if (context->ops == NULL || context->ops->destroy_cq == NULL) {
return -EPERM;
}
return context->ops->destroy_cq(cq_extend);
}
* @brief 销毁扩展SRQ
* @param context 扩展上下文
* @param srq_extend SRQ扩展结构体
* @return 0-成功,其他-失败
*/
API_EXPORT int ibv_destroy_srq_extend(struct ibv_context_extend *context, struct ibv_srq_extend *srq_extend)
{
if (context == NULL || srq_extend == NULL) {
return -EINVAL;
}
if (context->ops == NULL || context->ops->destroy_srq == NULL) {
return -EPERM;
}
return context->ops->destroy_srq(srq_extend);
}
* @brief 获取库版本号
* @param major 主版本号输出参数(可为NULL)
* @param minor 次版本号输出参数(可为NULL)
* @param patch 修订号输出参数(可为NULL)
* @return 版本字符串指针(静态字符串,无需释放)
*/
API_EXPORT const char *ibv_extend_get_version(uint32_t *major, uint32_t *minor, uint32_t *patch)
{
if (major) {
*major = IBV_EXTEND_VERSION_MAJOR;
}
if (minor) {
*minor = IBV_EXTEND_VERSION_MINOR;
}
if (patch) {
*patch = IBV_EXTEND_VERSION_PATCH;
}
return IBV_EXTEND_VERSION_STRING;
}
* @brief 检查版本兼容性
* @param driver_major 驱动编译时的主版本号
* @param driver_minor 驱动编译时的次版本号
* @param driver_patch 驱动编译时的修订号
* @return 0-兼容,-1-不兼容
*
* 此函数在运行时检查驱动编译时使用的头文件版本与当前库版本是否兼容。
* 建议在驱动初始化时调用此函数进行版本检查。
*/
API_EXPORT int ibv_extend_check_version(uint32_t driver_major, uint32_t driver_minor, uint32_t driver_patch)
{
if (driver_major != IBV_EXTEND_VERSION_MAJOR) {
(void)fprintf(stderr,
OUTPFX "Error: Version check failed: major version mismatch "
"(driver: %u, library: %u)\n",
driver_major, IBV_EXTEND_VERSION_MAJOR);
return -1;
}
if (driver_minor > IBV_EXTEND_VERSION_MINOR) {
(void)fprintf(stderr,
OUTPFX "Error: Version check failed: driver minor version newer than library "
"(driver: %u.%u.%u, library: %u.%u.%u)\n",
driver_major, driver_minor, driver_patch,
IBV_EXTEND_VERSION_MAJOR, IBV_EXTEND_VERSION_MINOR, IBV_EXTEND_VERSION_PATCH);
return -1;
}
if (driver_minor == IBV_EXTEND_VERSION_MINOR &&
driver_patch > IBV_EXTEND_VERSION_PATCH) {
(void)fprintf(stderr,
OUTPFX "Error: Version check failed: driver patch version newer than library "
"(driver: %u.%u.%u, library: %u.%u.%u)\n",
driver_major, driver_minor, driver_patch,
IBV_EXTEND_VERSION_MAJOR, IBV_EXTEND_VERSION_MINOR, IBV_EXTEND_VERSION_PATCH);
return -1;
}
return 0;
}
* @brief 清理扩展驱动资源
*
* 此函数释放在运行过程中未释放的资源
* 通过 __attribute__((destructor)) 实现自动清理,应用无需手动调用。
*/
static void ibv_extend_cleanup(void)
{
struct ibv_extend_driver *driver, *next_driver;
struct ibv_extend_driver_name *name, *next_name;
list_for_each_safe(&extend_driver_list, driver, next_driver, entry) {
list_del(&driver->entry);
free(driver);
}
list_for_each_safe(&extend_driver_name_list, name, next_name, entry) {
list_del(&name->entry);
free(name->ext_name);
free(name);
}
}
__attribute__((destructor)) static void ibv_extend_destructor(void)
{
ibv_extend_cleanup();
}