| /* | |
| * 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 <util/rdma_nl.h> | |
| #include <dirent.h> | |
| #include <fcntl.h> | |
| #include <unistd.h> | |
| #include <sys/sysmacros.h> | |
| #include <ccan/list.h> | |
| #include <util/util.h> | |
| #include <infiniband/driver.h> | |
| #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; | |
| } |