OSDN Git Service

RDMA: Add NLDEV_GET_CHARDEV to allow char dev discovery and autoload
authorJason Gunthorpe <jgg@mellanox.com>
Fri, 14 Jun 2019 00:38:18 +0000 (21:38 -0300)
committerDoug Ledford <dledford@redhat.com>
Wed, 19 Jun 2019 02:41:05 +0000 (22:41 -0400)
Allow userspace to issue a netlink query against the ib_device for
something like "uverbs" and get back the char dev name, inode major/minor,
and interface ABI information for "uverbs0".

Since we are now in netlink this can also trigger a module autoload to
make the uverbs device come into existence.

Largely this will let us replace searching and reading inside sysfs to
setup devices, and provides an alternative (using driver_id) to device
name based provider binding for things like rxe.

Signed-off-by: Jason Gunthorpe <jgg@mellanox.com>
Signed-off-by: Doug Ledford <dledford@redhat.com>
drivers/infiniband/core/core_priv.h
drivers/infiniband/core/device.c
drivers/infiniband/core/nldev.c
include/rdma/ib_verbs.h
include/rdma/rdma_netlink.h
include/uapi/rdma/rdma_netlink.h

index ff40a45..a953c2f 100644 (file)
@@ -88,6 +88,15 @@ typedef int (*nldev_callback)(struct ib_device *device,
 int ib_enum_all_devs(nldev_callback nldev_cb, struct sk_buff *skb,
                     struct netlink_callback *cb);
 
+struct ib_client_nl_info {
+       struct sk_buff *nl_msg;
+       struct device *cdev;
+       unsigned int port;
+       u64 abi;
+};
+int ib_get_client_nl_info(struct ib_device *ibdev, const char *client_name,
+                         struct ib_client_nl_info *res);
+
 enum ib_cache_gid_default_mode {
        IB_CACHE_GID_DEFAULT_MODE_SET,
        IB_CACHE_GID_DEFAULT_MODE_DELETE
index abb169f..7db8566 100644 (file)
@@ -1726,6 +1726,104 @@ void ib_unregister_client(struct ib_client *client)
 }
 EXPORT_SYMBOL(ib_unregister_client);
 
+static int __ib_get_global_client_nl_info(const char *client_name,
+                                         struct ib_client_nl_info *res)
+{
+       struct ib_client *client;
+       unsigned long index;
+       int ret = -ENOENT;
+
+       down_read(&clients_rwsem);
+       xa_for_each_marked (&clients, index, client, CLIENT_REGISTERED) {
+               if (strcmp(client->name, client_name) != 0)
+                       continue;
+               if (!client->get_global_nl_info) {
+                       ret = -EOPNOTSUPP;
+                       break;
+               }
+               ret = client->get_global_nl_info(res);
+               if (WARN_ON(ret == -ENOENT))
+                       ret = -EINVAL;
+               if (!ret && res->cdev)
+                       get_device(res->cdev);
+               break;
+       }
+       up_read(&clients_rwsem);
+       return ret;
+}
+
+static int __ib_get_client_nl_info(struct ib_device *ibdev,
+                                  const char *client_name,
+                                  struct ib_client_nl_info *res)
+{
+       unsigned long index;
+       void *client_data;
+       int ret = -ENOENT;
+
+       down_read(&ibdev->client_data_rwsem);
+       xan_for_each_marked (&ibdev->client_data, index, client_data,
+                            CLIENT_DATA_REGISTERED) {
+               struct ib_client *client = xa_load(&clients, index);
+
+               if (!client || strcmp(client->name, client_name) != 0)
+                       continue;
+               if (!client->get_nl_info) {
+                       ret = -EOPNOTSUPP;
+                       break;
+               }
+               ret = client->get_nl_info(ibdev, client_data, res);
+               if (WARN_ON(ret == -ENOENT))
+                       ret = -EINVAL;
+
+               /*
+                * The cdev is guaranteed valid as long as we are inside the
+                * client_data_rwsem as remove_one can't be called. Keep it
+                * valid for the caller.
+                */
+               if (!ret && res->cdev)
+                       get_device(res->cdev);
+               break;
+       }
+       up_read(&ibdev->client_data_rwsem);
+
+       return ret;
+}
+
+/**
+ * ib_get_client_nl_info - Fetch the nl_info from a client
+ * @device - IB device
+ * @client_name - Name of the client
+ * @res - Result of the query
+ */
+int ib_get_client_nl_info(struct ib_device *ibdev, const char *client_name,
+                         struct ib_client_nl_info *res)
+{
+       int ret;
+
+       if (ibdev)
+               ret = __ib_get_client_nl_info(ibdev, client_name, res);
+       else
+               ret = __ib_get_global_client_nl_info(client_name, res);
+#ifdef CONFIG_MODULES
+       if (ret == -ENOENT) {
+               request_module("rdma-client-%s", client_name);
+               if (ibdev)
+                       ret = __ib_get_client_nl_info(ibdev, client_name, res);
+               else
+                       ret = __ib_get_global_client_nl_info(client_name, res);
+       }
+#endif
+       if (ret) {
+               if (ret == -ENOENT)
+                       return -EOPNOTSUPP;
+               return ret;
+       }
+
+       if (WARN_ON(!res->cdev))
+               return -EINVAL;
+       return 0;
+}
+
 /**
  * ib_set_client_data - Set IB client context
  * @device:Device to set context for
index 69188cb..16b5d6d 100644 (file)
@@ -120,6 +120,12 @@ static const struct nla_policy nldev_policy[RDMA_NLDEV_ATTR_MAX] = {
        [RDMA_NLDEV_ATTR_DEV_PROTOCOL]          = { .type = NLA_NUL_STRING,
                                    .len = RDMA_NLDEV_ATTR_ENTRY_STRLEN },
        [RDMA_NLDEV_NET_NS_FD]                  = { .type = NLA_U32 },
+       [RDMA_NLDEV_ATTR_CHARDEV]               = { .type = NLA_U64 },
+       [RDMA_NLDEV_ATTR_CHARDEV_ABI]           = { .type = NLA_U64 },
+       [RDMA_NLDEV_ATTR_CHARDEV_TYPE]          = { .type = NLA_NUL_STRING,
+                                   .len = 128 },
+       [RDMA_NLDEV_ATTR_CHARDEV_NAME]          = { .type = NLA_NUL_STRING,
+                                   .len = RDMA_NLDEV_ATTR_ENTRY_STRLEN },
 };
 
 static int put_driver_name_print_type(struct sk_buff *msg, const char *name,
@@ -1347,6 +1353,91 @@ static int nldev_dellink(struct sk_buff *skb, struct nlmsghdr *nlh,
        return 0;
 }
 
+static int nldev_get_chardev(struct sk_buff *skb, struct nlmsghdr *nlh,
+                            struct netlink_ext_ack *extack)
+{
+       struct nlattr *tb[RDMA_NLDEV_ATTR_MAX];
+       char client_name[IB_DEVICE_NAME_MAX];
+       struct ib_client_nl_info data = {};
+       struct ib_device *ibdev = NULL;
+       struct sk_buff *msg;
+       u32 index;
+       int err;
+
+       err = nlmsg_parse(nlh, 0, tb, RDMA_NLDEV_ATTR_MAX - 1, nldev_policy,
+                         extack);
+       if (err || !tb[RDMA_NLDEV_ATTR_CHARDEV_TYPE])
+               return -EINVAL;
+
+       if (nla_strlcpy(client_name, tb[RDMA_NLDEV_ATTR_CHARDEV_TYPE],
+                       sizeof(client_name)) >= sizeof(client_name))
+               return -EINVAL;
+
+       if (tb[RDMA_NLDEV_ATTR_DEV_INDEX]) {
+               index = nla_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+               ibdev = ib_device_get_by_index(sock_net(skb->sk), index);
+               if (!ibdev)
+                       return -EINVAL;
+
+               if (tb[RDMA_NLDEV_ATTR_PORT_INDEX]) {
+                       data.port = nla_get_u32(tb[RDMA_NLDEV_ATTR_PORT_INDEX]);
+                       if (!rdma_is_port_valid(ibdev, data.port)) {
+                               err = -EINVAL;
+                               goto out_put;
+                       }
+               } else {
+                       data.port = -1;
+               }
+       } else if (tb[RDMA_NLDEV_ATTR_PORT_INDEX]) {
+               return -EINVAL;
+       }
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg) {
+               err = -ENOMEM;
+               goto out_put;
+       }
+       nlh = nlmsg_put(msg, NETLINK_CB(skb).portid, nlh->nlmsg_seq,
+                       RDMA_NL_GET_TYPE(RDMA_NL_NLDEV,
+                                        RDMA_NLDEV_CMD_GET_CHARDEV),
+                       0, 0);
+
+       data.nl_msg = msg;
+       err = ib_get_client_nl_info(ibdev, client_name, &data);
+       if (err)
+               goto out_nlmsg;
+
+       err = nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_CHARDEV,
+                               huge_encode_dev(data.cdev->devt),
+                               RDMA_NLDEV_ATTR_PAD);
+       if (err)
+               goto out_data;
+       err = nla_put_u64_64bit(msg, RDMA_NLDEV_ATTR_CHARDEV_ABI, data.abi,
+                               RDMA_NLDEV_ATTR_PAD);
+       if (err)
+               goto out_data;
+       if (nla_put_string(msg, RDMA_NLDEV_ATTR_CHARDEV_NAME,
+                          dev_name(data.cdev))) {
+               err = -EMSGSIZE;
+               goto out_data;
+       }
+
+       nlmsg_end(msg, nlh);
+       put_device(data.cdev);
+       if (ibdev)
+               ib_device_put(ibdev);
+       return rdma_nl_unicast(msg, NETLINK_CB(skb).portid);
+
+out_data:
+       put_device(data.cdev);
+out_nlmsg:
+       nlmsg_free(msg);
+out_put:
+       if (ibdev)
+               ib_device_put(ibdev);
+       return err;
+}
+
 static int nldev_sys_get_doit(struct sk_buff *skb, struct nlmsghdr *nlh,
                              struct netlink_ext_ack *extack)
 {
@@ -1404,6 +1495,9 @@ static const struct rdma_nl_cbs nldev_cb_table[RDMA_NLDEV_NUM_OPS] = {
                .doit = nldev_get_doit,
                .dump = nldev_get_dumpit,
        },
+       [RDMA_NLDEV_CMD_GET_CHARDEV] = {
+               .doit = nldev_get_chardev,
+       },
        [RDMA_NLDEV_CMD_SET] = {
                .doit = nldev_set_doit,
                .flags = RDMA_NL_ADMIN_PERM,
index 973514e..a1265e9 100644 (file)
@@ -2684,10 +2684,14 @@ struct ib_device {
        u32 iw_driver_flags;
 };
 
+struct ib_client_nl_info;
 struct ib_client {
        const char *name;
        void (*add)   (struct ib_device *);
        void (*remove)(struct ib_device *, void *client_data);
+       int (*get_nl_info)(struct ib_device *ibdev, void *client_data,
+                          struct ib_client_nl_info *res);
+       int (*get_global_nl_info)(struct ib_client_nl_info *res);
 
        /* Returns the net_dev belonging to this ib_client and matching the
         * given parameters.
index 10732ab..c7acbe0 100644 (file)
@@ -110,4 +110,6 @@ void rdma_link_register(struct rdma_link_ops *ops);
 void rdma_link_unregister(struct rdma_link_ops *ops);
 
 #define MODULE_ALIAS_RDMA_LINK(type) MODULE_ALIAS("rdma-link-" type)
+#define MODULE_ALIAS_RDMA_CLIENT(type) MODULE_ALIAS("rdma-client-" type)
+
 #endif /* _RDMA_NETLINK_H */
index f588e85..9903db2 100644 (file)
@@ -279,6 +279,8 @@ enum rdma_nldev_command {
 
        RDMA_NLDEV_CMD_RES_PD_GET, /* can dump */
 
+       RDMA_NLDEV_CMD_GET_CHARDEV,
+
        RDMA_NLDEV_NUM_OPS
 };
 
@@ -492,6 +494,18 @@ enum rdma_nldev_attr {
        RDMA_NLDEV_NET_NS_FD,                   /* u32 */
 
        /*
+        * Information about a chardev.
+        * CHARDEV_TYPE is the name of the chardev ABI (ie uverbs, umad, etc)
+        * CHARDEV_ABI signals the ABI revision (historical)
+        * CHARDEV_NAME is the kernel name for the /dev/ file (no directory)
+        * CHARDEV is the 64 bit dev_t for the inode
+        */
+       RDMA_NLDEV_ATTR_CHARDEV_TYPE,           /* string */
+       RDMA_NLDEV_ATTR_CHARDEV_NAME,           /* string */
+       RDMA_NLDEV_ATTR_CHARDEV_ABI,            /* u64 */
+       RDMA_NLDEV_ATTR_CHARDEV,                /* u64 */
+
+       /*
         * Always the end
         */
        RDMA_NLDEV_ATTR_MAX