RDMA: Add NLDEV_GET_CHARDEV to allow char dev discovery and autoload

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>
This commit is contained in:
Jason Gunthorpe 2019-06-13 21:38:18 -03:00 committed by Doug Ledford
parent 5d60c11154
commit 0e2d00eb6f
6 changed files with 221 additions and 0 deletions

View 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

View 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

View 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,

View 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.

View 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 */

View 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
};
@ -491,6 +493,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
*/