| /* | |
| * Copyright (c) 2004, 2005 Topspin Communications. All rights reserved. | |
| * Copyright (c) 2006, 2007 Cisco Systems, Inc. All rights reserved. | |
| * | |
| * This software is available to you under a choice of one of two | |
| * licenses. You may choose to be licensed under the terms of the GNU | |
| * General Public License (GPL) Version 2, available from the file | |
| * COPYING in the main directory of this source tree, or the | |
| * OpenIB.org BSD license below: | |
| * | |
| * Redistribution and use in source and binary forms, with or | |
| * without modification, are permitted provided that the following | |
| * conditions are met: | |
| * | |
| * - Redistributions of source code must retain the above | |
| * copyright notice, this list of conditions and the following | |
| * disclaimer. | |
| * | |
| * - Redistributions in binary form must reproduce the above | |
| * copyright notice, this list of conditions and the following | |
| * disclaimer in the documentation and/or other materials | |
| * provided with the distribution. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| * SOFTWARE. | |
| */ | |
| #define _GNU_SOURCE | |
| #include <config.h> | |
| #include <endian.h> | |
| #include <stdio.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <fcntl.h> | |
| #include <unistd.h> | |
| #include <stdlib.h> | |
| #include <alloca.h> | |
| #include <errno.h> | |
| #include <rdma/ib_user_ioctl_cmds.h> | |
| #include <util/symver.h> | |
| #include <util/util.h> | |
| #include "ibverbs.h" | |
| static pthread_mutex_t dev_list_lock = PTHREAD_MUTEX_INITIALIZER; | |
| static struct list_head device_list = LIST_HEAD_INIT(device_list); | |
| LATEST_SYMVER_FUNC(ibv_get_device_list, 1_1, "IBVERBS_1.1", | |
| struct ibv_device **, | |
| int *num) | |
| { | |
| struct ibv_device **l = NULL; | |
| struct verbs_device *device; | |
| static bool initialized; | |
| int num_devices; | |
| int i = 0; | |
| if (num) | |
| *num = 0; | |
| pthread_mutex_lock(&dev_list_lock); | |
| if (!initialized) { | |
| if (ibverbs_init()) | |
| goto out; | |
| initialized = true; | |
| } | |
| num_devices = ibverbs_get_device_list(&device_list); | |
| if (num_devices < 0) { | |
| errno = -num_devices; | |
| goto out; | |
| } | |
| l = calloc(num_devices + 1, sizeof (struct ibv_device *)); | |
| if (!l) { | |
| errno = ENOMEM; | |
| goto out; | |
| } | |
| list_for_each(&device_list, device, entry) { | |
| l[i] = &device->device; | |
| ibverbs_device_hold(l[i]); | |
| i++; | |
| } | |
| if (num) | |
| *num = num_devices; | |
| out: | |
| pthread_mutex_unlock(&dev_list_lock); | |
| return l; | |
| } | |
| LATEST_SYMVER_FUNC(ibv_free_device_list, 1_1, "IBVERBS_1.1", | |
| void, | |
| struct ibv_device **list) | |
| { | |
| int i; | |
| for (i = 0; list[i]; i++) | |
| ibverbs_device_put(list[i]); | |
| free(list); | |
| } | |
| LATEST_SYMVER_FUNC(ibv_get_device_name, 1_1, "IBVERBS_1.1", | |
| const char *, | |
| struct ibv_device *device) | |
| { | |
| return device->name; | |
| } | |
| LATEST_SYMVER_FUNC(ibv_get_device_guid, 1_1, "IBVERBS_1.1", | |
| __be64, | |
| struct ibv_device *device) | |
| { | |
| struct verbs_sysfs_dev *sysfs_dev = verbs_get_device(device)->sysfs; | |
| char attr[24]; | |
| uint64_t guid = 0; | |
| uint16_t parts[4]; | |
| int i; | |
| pthread_mutex_lock(&dev_list_lock); | |
| if (sysfs_dev && sysfs_dev->flags & VSYSFS_READ_NODE_GUID) { | |
| guid = sysfs_dev->node_guid; | |
| pthread_mutex_unlock(&dev_list_lock); | |
| return htobe64(guid); | |
| } | |
| pthread_mutex_unlock(&dev_list_lock); | |
| if (ibv_read_ibdev_sysfs_file(attr, sizeof(attr), sysfs_dev, | |
| "node_guid") < 0) | |
| return 0; | |
| if (sscanf(attr, "%hx:%hx:%hx:%hx", | |
| parts, parts + 1, parts + 2, parts + 3) != 4) | |
| return 0; | |
| for (i = 0; i < 4; ++i) | |
| guid = (guid << 16) | parts[i]; | |
| pthread_mutex_lock(&dev_list_lock); | |
| sysfs_dev->node_guid = guid; | |
| sysfs_dev->flags |= VSYSFS_READ_NODE_GUID; | |
| pthread_mutex_unlock(&dev_list_lock); | |
| return htobe64(guid); | |
| } | |
| int ibv_get_device_index(struct ibv_device *device) | |
| { | |
| struct verbs_sysfs_dev *sysfs_dev = verbs_get_device(device)->sysfs; | |
| return sysfs_dev ? sysfs_dev->ibdev_idx : -1; | |
| } | |
| void verbs_init_cq(struct ibv_cq *cq, struct ibv_context *context, | |
| struct ibv_comp_channel *channel, | |
| void *cq_context) | |
| { | |
| cq->context = context; | |
| cq->channel = channel; | |
| if (cq->channel) { | |
| pthread_mutex_lock(&context->mutex); | |
| ++cq->channel->refcnt; | |
| pthread_mutex_unlock(&context->mutex); | |
| } | |
| cq->cq_context = cq_context; | |
| cq->comp_events_completed = 0; | |
| cq->async_events_completed = 0; | |
| pthread_mutex_init(&cq->mutex, NULL); | |
| pthread_cond_init(&cq->cond, NULL); | |
| } | |
| static struct ibv_cq_ex * | |
| __lib_ibv_create_cq_ex(struct ibv_context *context, | |
| struct ibv_cq_init_attr_ex *cq_attr) | |
| { | |
| struct ibv_cq_ex *cq; | |
| if (cq_attr->wc_flags & ~IBV_CREATE_CQ_SUP_WC_FLAGS) { | |
| errno = EOPNOTSUPP; | |
| return NULL; | |
| } | |
| cq = get_ops(context)->create_cq_ex(context, cq_attr); | |
| if (cq) | |
| verbs_init_cq(ibv_cq_ex_to_cq(cq), context, | |
| cq_attr->channel, cq_attr->cq_context); | |
| return cq; | |
| } | |
| static bool has_ioctl_write(struct ibv_context *ctx) | |
| { | |
| int rc; | |
| DECLARE_COMMAND_BUFFER(cmdb, UVERBS_OBJECT_DEVICE, | |
| UVERBS_METHOD_INVOKE_WRITE, 1); | |
| if (VERBS_IOCTL_ONLY) | |
| return true; | |
| if (VERBS_WRITE_ONLY) | |
| return false; | |
| /* | |
| * This command should return ENOSPC since the request length is too | |
| * small. | |
| */ | |
| fill_attr_const_in(cmdb, UVERBS_ATTR_WRITE_CMD, | |
| IB_USER_VERBS_CMD_QUERY_DEVICE); | |
| rc = execute_ioctl(ctx, cmdb); | |
| if (rc == EPROTONOSUPPORT) | |
| return false; | |
| if (rc == ENOTTY) | |
| return false; | |
| return true; | |
| } | |
| /* | |
| * Ownership of cmd_fd is transferred into this function, and it will either | |
| * be released during the matching call to verbs_uninit_contxt or during the | |
| * failure path of this function. | |
| */ | |
| int verbs_init_context(struct verbs_context *context_ex, | |
| struct ibv_device *device, int cmd_fd, | |
| uint32_t driver_id) | |
| { | |
| struct ibv_context *context = &context_ex->context; | |
| ibverbs_device_hold(device); | |
| context->device = device; | |
| context->cmd_fd = cmd_fd; | |
| context->async_fd = -1; | |
| pthread_mutex_init(&context->mutex, NULL); | |
| context_ex->context.abi_compat = __VERBS_ABI_IS_EXTENDED; | |
| context_ex->sz = sizeof(*context_ex); | |
| context_ex->priv = calloc(1, sizeof(*context_ex->priv)); | |
| if (!context_ex->priv) { | |
| errno = ENOMEM; | |
| close(cmd_fd); | |
| return -1; | |
| } | |
| context_ex->priv->driver_id = driver_id; | |
| verbs_set_ops(context_ex, &verbs_dummy_ops); | |
| context_ex->priv->use_ioctl_write = has_ioctl_write(context); | |
| return 0; | |
| } | |
| /* | |
| * Allocate and initialize a context structure. This is called to create the | |
| * driver wrapper, and context_offset is the number of bytes into the wrapper | |
| * structure where the verbs_context starts. | |
| */ | |
| void *_verbs_init_and_alloc_context(struct ibv_device *device, int cmd_fd, | |
| size_t alloc_size, | |
| struct verbs_context *context_offset, | |
| uint32_t driver_id) | |
| { | |
| void *drv_context; | |
| struct verbs_context *context; | |
| drv_context = calloc(1, alloc_size); | |
| if (!drv_context) { | |
| errno = ENOMEM; | |
| close(cmd_fd); | |
| return NULL; | |
| } | |
| context = drv_context + (uintptr_t)context_offset; | |
| if (verbs_init_context(context, device, cmd_fd, driver_id)) | |
| goto err_free; | |
| return drv_context; | |
| err_free: | |
| free(drv_context); | |
| return NULL; | |
| } | |
| static void set_lib_ops(struct verbs_context *vctx) | |
| { | |
| vctx->create_cq_ex = __lib_ibv_create_cq_ex; | |
| /* | |
| * The compat symver entry point behaves identically to what used to | |
| * be pointed to by _compat_query_port. | |
| */ | |
| #undef ibv_query_port | |
| vctx->context.ops._compat_query_port = ibv_query_port; | |
| vctx->query_port = __lib_query_port; | |
| vctx->context.ops._compat_query_device = ibv_query_device; | |
| /* | |
| * In order to maintain backward/forward binary compatibility | |
| * with apps compiled against libibverbs-1.1.8 that use the | |
| * flow steering addition, we need to set the two | |
| * ABI_placeholder entries to match the driver set flow | |
| * entries. This is because apps compiled against | |
| * libibverbs-1.1.8 use an inline ibv_create_flow and | |
| * ibv_destroy_flow function that looks in the placeholder | |
| * spots for the proper entry points. For apps compiled | |
| * against libibverbs-1.1.9 and later, the inline functions | |
| * will be looking in the right place. | |
| */ | |
| vctx->ABI_placeholder1 = | |
| (void (*)(void))vctx->ibv_create_flow; | |
| vctx->ABI_placeholder2 = | |
| (void (*)(void))vctx->ibv_destroy_flow; | |
| } | |
| struct ibv_context *verbs_open_device(struct ibv_device *device, void *private_data) | |
| { | |
| struct verbs_device *verbs_device = verbs_get_device(device); | |
| int cmd_fd = -1; | |
| struct verbs_context *context_ex; | |
| int ret; | |
| if (verbs_device->sysfs) { | |
| /* | |
| * We'll only be doing writes, but we need O_RDWR in case the | |
| * provider needs to mmap() the file. | |
| */ | |
| cmd_fd = open_cdev(verbs_device->sysfs->sysfs_name, | |
| verbs_device->sysfs->sysfs_cdev); | |
| if (cmd_fd < 0) | |
| return NULL; | |
| } | |
| /* | |
| * cmd_fd ownership is transferred into alloc_context, if it fails | |
| * then it closes cmd_fd and returns NULL | |
| */ | |
| context_ex = verbs_device->ops->alloc_context(device, cmd_fd, private_data); | |
| if (!context_ex) | |
| return NULL; | |
| set_lib_ops(context_ex); | |
| if (verbs_device->sysfs) { | |
| if (context_ex->context.async_fd == -1) { | |
| ret = ibv_cmd_alloc_async_fd(&context_ex->context); | |
| if (ret) { | |
| ibv_close_device(&context_ex->context); | |
| return NULL; | |
| } | |
| } | |
| } | |
| return &context_ex->context; | |
| } | |
| LATEST_SYMVER_FUNC(ibv_open_device, 1_1, "IBVERBS_1.1", | |
| struct ibv_context *, | |
| struct ibv_device *device) | |
| { | |
| return verbs_open_device(device, NULL); | |
| } | |
| struct ibv_context *ibv_import_device(int cmd_fd) | |
| { | |
| struct verbs_device *verbs_device = NULL; | |
| struct verbs_context *context_ex; | |
| struct ibv_device **dev_list; | |
| struct ibv_context *ctx = NULL; | |
| struct stat st; | |
| int ret; | |
| int i; | |
| if (fstat(cmd_fd, &st) || !S_ISCHR(st.st_mode)) { | |
| errno = EINVAL; | |
| return NULL; | |
| } | |
| dev_list = ibv_get_device_list(NULL); | |
| if (!dev_list) { | |
| errno = ENODEV; | |
| return NULL; | |
| } | |
| for (i = 0; dev_list[i]; ++i) { | |
| if (verbs_get_device(dev_list[i])->sysfs->sysfs_cdev == | |
| st.st_rdev) { | |
| verbs_device = verbs_get_device(dev_list[i]); | |
| break; | |
| } | |
| } | |
| if (!verbs_device) { | |
| errno = ENODEV; | |
| goto out; | |
| } | |
| if (!verbs_device->ops->import_context) { | |
| errno = EOPNOTSUPP; | |
| goto out; | |
| } | |
| /* In case the underlay cdev number was assigned in the meantime to | |
| * other device as of some disassociate flow, the next call on the | |
| * FD will end up with EIO (i.e. query_context command) and we should | |
| * be safe from using the wrong device. | |
| */ | |
| context_ex = verbs_device->ops->import_context(&verbs_device->device, cmd_fd); | |
| if (!context_ex) | |
| goto out; | |
| set_lib_ops(context_ex); | |
| context_ex->priv->imported = true; | |
| ctx = &context_ex->context; | |
| ret = ibv_cmd_alloc_async_fd(ctx); | |
| if (ret) { | |
| ibv_close_device(ctx); | |
| ctx = NULL; | |
| } | |
| out: | |
| ibv_free_device_list(dev_list); | |
| return ctx; | |
| } | |
| void verbs_uninit_context(struct verbs_context *context_ex) | |
| { | |
| free(context_ex->priv); | |
| if (context_ex->context.cmd_fd != -1) | |
| close(context_ex->context.cmd_fd); | |
| if (context_ex->context.async_fd != -1) | |
| close(context_ex->context.async_fd); | |
| ibverbs_device_put(context_ex->context.device); | |
| } | |
| LATEST_SYMVER_FUNC(ibv_close_device, 1_1, "IBVERBS_1.1", | |
| int, | |
| struct ibv_context *context) | |
| { | |
| const struct verbs_context_ops *ops = get_ops(context); | |
| ops->free_context(context); | |
| return 0; | |
| } | |
| LATEST_SYMVER_FUNC(ibv_get_async_event, 1_1, "IBVERBS_1.1", | |
| int, | |
| struct ibv_context *context, | |
| struct ibv_async_event *event) | |
| { | |
| struct ib_uverbs_async_event_desc ev; | |
| if (read(context->async_fd, &ev, sizeof ev) != sizeof ev) | |
| return -1; | |
| event->event_type = ev.event_type; | |
| switch (event->event_type) { | |
| case IBV_EVENT_CQ_ERR: | |
| event->element.cq = (void *) (uintptr_t) ev.element; | |
| break; | |
| case IBV_EVENT_QP_FATAL: | |
| case IBV_EVENT_QP_REQ_ERR: | |
| case IBV_EVENT_QP_ACCESS_ERR: | |
| case IBV_EVENT_COMM_EST: | |
| case IBV_EVENT_SQ_DRAINED: | |
| case IBV_EVENT_PATH_MIG: | |
| case IBV_EVENT_PATH_MIG_ERR: | |
| case IBV_EVENT_QP_LAST_WQE_REACHED: | |
| event->element.qp = (void *) (uintptr_t) ev.element; | |
| break; | |
| case IBV_EVENT_SRQ_ERR: | |
| case IBV_EVENT_SRQ_LIMIT_REACHED: | |
| event->element.srq = (void *) (uintptr_t) ev.element; | |
| break; | |
| case IBV_EVENT_WQ_FATAL: | |
| event->element.wq = (void *) (uintptr_t) ev.element; | |
| break; | |
| default: | |
| event->element.port_num = ev.element; | |
| break; | |
| } | |
| get_ops(context)->async_event(context, event); | |
| return 0; | |
| } | |
| LATEST_SYMVER_FUNC(ibv_ack_async_event, 1_1, "IBVERBS_1.1", | |
| void, | |
| struct ibv_async_event *event) | |
| { | |
| switch (event->event_type) { | |
| case IBV_EVENT_CQ_ERR: | |
| { | |
| struct ibv_cq *cq = event->element.cq; | |
| pthread_mutex_lock(&cq->mutex); | |
| ++cq->async_events_completed; | |
| pthread_cond_signal(&cq->cond); | |
| pthread_mutex_unlock(&cq->mutex); | |
| return; | |
| } | |
| case IBV_EVENT_QP_FATAL: | |
| case IBV_EVENT_QP_REQ_ERR: | |
| case IBV_EVENT_QP_ACCESS_ERR: | |
| case IBV_EVENT_COMM_EST: | |
| case IBV_EVENT_SQ_DRAINED: | |
| case IBV_EVENT_PATH_MIG: | |
| case IBV_EVENT_PATH_MIG_ERR: | |
| case IBV_EVENT_QP_LAST_WQE_REACHED: | |
| { | |
| struct ibv_qp *qp = event->element.qp; | |
| pthread_mutex_lock(&qp->mutex); | |
| ++qp->events_completed; | |
| pthread_cond_signal(&qp->cond); | |
| pthread_mutex_unlock(&qp->mutex); | |
| return; | |
| } | |
| case IBV_EVENT_SRQ_ERR: | |
| case IBV_EVENT_SRQ_LIMIT_REACHED: | |
| { | |
| struct ibv_srq *srq = event->element.srq; | |
| pthread_mutex_lock(&srq->mutex); | |
| ++srq->events_completed; | |
| pthread_cond_signal(&srq->cond); | |
| pthread_mutex_unlock(&srq->mutex); | |
| return; | |
| } | |
| case IBV_EVENT_WQ_FATAL: | |
| { | |
| struct ibv_wq *wq = event->element.wq; | |
| pthread_mutex_lock(&wq->mutex); | |
| ++wq->events_completed; | |
| pthread_cond_signal(&wq->cond); | |
| pthread_mutex_unlock(&wq->mutex); | |
| return; | |
| } | |
| default: | |
| return; | |
| } | |
| } |