/* * Copyright (c) 2019, Mellanox Technologies. 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. */ #include #include #include #include #include #include #include #include #include "ibverbs.h" /* Determine the name of the uverbsX class for the sysfs_dev using sysfs. */ static int find_uverbs_sysfs(struct verbs_sysfs_dev *sysfs_dev) { char path[IBV_SYSFS_PATH_MAX]; struct dirent *dent; DIR *class_dir; int ret = ENOENT; if (!check_snprintf(path, sizeof(path), "%s/device/infiniband_verbs", sysfs_dev->ibdev_path)) return ENOMEM; class_dir = opendir(path); if (!class_dir) return ENOSYS; while ((dent = readdir(class_dir))) { int uv_dirfd; bool failed; if (dent->d_name[0] == '.') continue; uv_dirfd = openat(dirfd(class_dir), dent->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if (uv_dirfd == -1) break; failed = setup_sysfs_uverbs(uv_dirfd, dent->d_name, sysfs_dev); close(uv_dirfd); if (!failed) ret = 0; break; } closedir(class_dir); return ret; } static int find_uverbs_nl_cb(struct nl_msg *msg, void *data) { struct verbs_sysfs_dev *sysfs_dev = data; struct nlattr *tb[RDMA_NLDEV_ATTR_MAX]; uint64_t cdev64; int ret; ret = nlmsg_parse(nlmsg_hdr(msg), 0, tb, RDMA_NLDEV_ATTR_MAX - 1, rdmanl_policy); if (ret < 0) return ret; if (!tb[RDMA_NLDEV_ATTR_CHARDEV] || !tb[RDMA_NLDEV_ATTR_CHARDEV_ABI] || !tb[RDMA_NLDEV_ATTR_CHARDEV_NAME]) return NLE_PARSE_ERR; /* * The global uverbs abi is 6 for the request string 'uverbs'. We * don't expect to ever have to change the ABI version for uverbs * again. */ abi_ver = 6; /* * The top 32 bits of CHARDEV_ABI are reserved for a future use, * current kernels set them to 0 */ sysfs_dev->abi_ver = nla_get_u64(tb[RDMA_NLDEV_ATTR_CHARDEV_ABI]); if (tb[RDMA_NLDEV_ATTR_UVERBS_DRIVER_ID]) sysfs_dev->driver_id = nla_get_u32(tb[RDMA_NLDEV_ATTR_UVERBS_DRIVER_ID]); else sysfs_dev->driver_id = RDMA_DRIVER_UNKNOWN; /* Convert from huge_encode_dev to whatever glibc uses */ cdev64 = nla_get_u64(tb[RDMA_NLDEV_ATTR_CHARDEV]); sysfs_dev->sysfs_cdev = makedev((cdev64 & 0xfff00) >> 8, (cdev64 & 0xff) | ((cdev64 >> 12) & 0xfff00)); if (!check_snprintf(sysfs_dev->sysfs_name, sizeof(sysfs_dev->sysfs_name), "%s", nla_get_string(tb[RDMA_NLDEV_ATTR_CHARDEV_NAME]))) return NLE_PARSE_ERR; return 0; } /* Ask the kernel for the uverbs char device information */ static int find_uverbs_nl(struct nl_sock *nl, struct verbs_sysfs_dev *sysfs_dev) { if (rdmanl_get_chardev(nl, sysfs_dev->ibdev_idx, "uverbs", find_uverbs_nl_cb, sysfs_dev)) return -1; if (!sysfs_dev->sysfs_name[0]) return -1; return 0; } static int find_sysfs_devs_nl_cb(struct nl_msg *msg, void *data) { struct nlattr *tb[RDMA_NLDEV_ATTR_MAX]; struct list_head *sysfs_list = data; struct verbs_sysfs_dev *sysfs_dev; int ret; ret = nlmsg_parse(nlmsg_hdr(msg), 0, tb, RDMA_NLDEV_ATTR_MAX - 1, rdmanl_policy); if (ret < 0) return ret; if (!tb[RDMA_NLDEV_ATTR_DEV_NAME] || !tb[RDMA_NLDEV_ATTR_DEV_NODE_TYPE] || !tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_NODE_GUID] || !tb[RDMA_NLDEV_ATTR_PORT_INDEX]) return NLE_PARSE_ERR; sysfs_dev = calloc(1, sizeof(*sysfs_dev)); if (!sysfs_dev) return NLE_NOMEM; sysfs_dev->ibdev_idx = nla_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]); sysfs_dev->num_ports = nla_get_u32(tb[RDMA_NLDEV_ATTR_PORT_INDEX]); sysfs_dev->node_guid = nla_get_u64(tb[RDMA_NLDEV_ATTR_NODE_GUID]); sysfs_dev->flags |= VSYSFS_READ_NODE_GUID; if (!check_snprintf(sysfs_dev->ibdev_name, sizeof(sysfs_dev->ibdev_name), "%s", nla_get_string(tb[RDMA_NLDEV_ATTR_DEV_NAME]))) goto err; if (!check_snprintf( sysfs_dev->ibdev_path, sizeof(sysfs_dev->ibdev_path), "%s/class/infiniband/%s", ibv_get_sysfs_path(), sysfs_dev->ibdev_name)) goto err; sysfs_dev->node_type = decode_knode_type( nla_get_u8(tb[RDMA_NLDEV_ATTR_DEV_NODE_TYPE])); /* * We don't need to check the cdev as netlink only shows us devices in * this namespace */ list_add(sysfs_list, &sysfs_dev->entry); return NL_OK; err: free(sysfs_dev); return NLE_PARSE_ERR; } /* Fetch the list of IB devices and uverbs from netlink */ int find_sysfs_devs_nl(struct list_head *tmp_sysfs_dev_list) { struct verbs_sysfs_dev *dev, *dev_tmp; struct nl_sock *nl; nl = rdmanl_socket_alloc(); if (!nl) return -EOPNOTSUPP; if (rdmanl_get_devices(nl, find_sysfs_devs_nl_cb, tmp_sysfs_dev_list)) goto err; list_for_each_safe (tmp_sysfs_dev_list, dev, dev_tmp, entry) { if ((find_uverbs_nl(nl, dev) && find_uverbs_sysfs(dev)) || try_access_device(dev)) { list_del(&dev->entry); free(dev); } } nl_socket_free(nl); return 0; err: list_for_each_safe (tmp_sysfs_dev_list, dev, dev_tmp, entry) { list_del(&dev->entry); free(dev); } nl_socket_free(nl); return EINVAL; } static int get_copy_on_fork_cb(struct nl_msg *msg, void *data) { struct nlattr *tb[RDMA_NLDEV_ATTR_MAX]; int ret; ret = nlmsg_parse(nlmsg_hdr(msg), 0, tb, RDMA_NLDEV_ATTR_MAX - 1, rdmanl_policy); if (ret < 0) return ret; /* Older kernels don't support COF and don't report it through nl */ if (!tb[RDMA_NLDEV_SYS_ATTR_COPY_ON_FORK]) { *(uint8_t *)data = 0; return NL_OK; } *(uint8_t *)data = nla_get_u8(tb[RDMA_NLDEV_SYS_ATTR_COPY_ON_FORK]); return NL_OK; } bool get_copy_on_fork(void) { struct nl_sock *nl; uint8_t cof; nl = rdmanl_socket_alloc(); if (!nl) return false; if (rdmanl_get_copy_on_fork(nl, get_copy_on_fork_cb, &cof)) cof = false; nl_socket_free(nl); return cof; }