diff options
Diffstat (limited to 'drivers/infiniband/core/netlink.c')
| -rw-r--r-- | drivers/infiniband/core/netlink.c | 321 | 
1 files changed, 175 insertions, 146 deletions
| diff --git a/drivers/infiniband/core/netlink.c b/drivers/infiniband/core/netlink.c index 94931c474d41..b12e58787c3d 100644 --- a/drivers/infiniband/core/netlink.c +++ b/drivers/infiniband/core/netlink.c @@ -1,4 +1,5 @@  /* + * Copyright (c) 2017 Mellanox Technologies Inc.  All rights reserved.   * Copyright (c) 2010 Voltaire Inc.  All rights reserved.   *   * This software is available to you under a choice of one of two @@ -37,239 +38,267 @@  #include <net/net_namespace.h>  #include <net/sock.h>  #include <rdma/rdma_netlink.h> +#include <linux/module.h>  #include "core_priv.h" -struct ibnl_client { -	struct list_head		list; -	int				index; -	int				nops; -	const struct ibnl_client_cbs   *cb_table; -}; +#include "core_priv.h" -static DEFINE_MUTEX(ibnl_mutex); +static DEFINE_MUTEX(rdma_nl_mutex);  static struct sock *nls; -static LIST_HEAD(client_list); +static struct { +	const struct rdma_nl_cbs   *cb_table; +} rdma_nl_types[RDMA_NL_NUM_CLIENTS]; -int ibnl_chk_listeners(unsigned int group) +int rdma_nl_chk_listeners(unsigned int group)  { -	if (netlink_has_listeners(nls, group) == 0) -		return -1; -	return 0; +	return (netlink_has_listeners(nls, group)) ? 0 : -1;  } +EXPORT_SYMBOL(rdma_nl_chk_listeners); -int ibnl_add_client(int index, int nops, -		    const struct ibnl_client_cbs cb_table[]) +static bool is_nl_msg_valid(unsigned int type, unsigned int op)  { -	struct ibnl_client *cur; -	struct ibnl_client *nl_client; +	static const unsigned int max_num_ops[RDMA_NL_NUM_CLIENTS] = { +		[RDMA_NL_RDMA_CM] = RDMA_NL_RDMA_CM_NUM_OPS, +		[RDMA_NL_IWCM] = RDMA_NL_IWPM_NUM_OPS, +		[RDMA_NL_LS] = RDMA_NL_LS_NUM_OPS, +		[RDMA_NL_NLDEV] = RDMA_NLDEV_NUM_OPS, +	}; -	nl_client = kmalloc(sizeof *nl_client, GFP_KERNEL); -	if (!nl_client) -		return -ENOMEM; +	/* +	 * This BUILD_BUG_ON is intended to catch addition of new +	 * RDMA netlink protocol without updating the array above. +	 */ +	BUILD_BUG_ON(RDMA_NL_NUM_CLIENTS != 6); -	nl_client->index	= index; -	nl_client->nops		= nops; -	nl_client->cb_table	= cb_table; +	if (type >= RDMA_NL_NUM_CLIENTS) +		return false; -	mutex_lock(&ibnl_mutex); +	return (op < max_num_ops[type]) ? true : false; +} -	list_for_each_entry(cur, &client_list, list) { -		if (cur->index == index) { -			pr_warn("Client for %d already exists\n", index); -			mutex_unlock(&ibnl_mutex); -			kfree(nl_client); -			return -EINVAL; -		} +static bool is_nl_valid(unsigned int type, unsigned int op) +{ +	const struct rdma_nl_cbs *cb_table; + +	if (!is_nl_msg_valid(type, op)) +		return false; + +	cb_table = rdma_nl_types[type].cb_table; +#ifdef CONFIG_MODULES +	if (!cb_table) { +		mutex_unlock(&rdma_nl_mutex); +		request_module("rdma-netlink-subsys-%d", type); +		mutex_lock(&rdma_nl_mutex); +		cb_table = rdma_nl_types[type].cb_table;  	} +#endif -	list_add_tail(&nl_client->list, &client_list); - -	mutex_unlock(&ibnl_mutex); - -	return 0; +	if (!cb_table || (!cb_table[op].dump && !cb_table[op].doit)) +		return false; +	return true;  } -EXPORT_SYMBOL(ibnl_add_client); -int ibnl_remove_client(int index) +void rdma_nl_register(unsigned int index, +		      const struct rdma_nl_cbs cb_table[])  { -	struct ibnl_client *cur, *next; - -	mutex_lock(&ibnl_mutex); -	list_for_each_entry_safe(cur, next, &client_list, list) { -		if (cur->index == index) { -			list_del(&(cur->list)); -			mutex_unlock(&ibnl_mutex); -			kfree(cur); -			return 0; -		} +	mutex_lock(&rdma_nl_mutex); +	if (!is_nl_msg_valid(index, 0)) { +		/* +		 * All clients are not interesting in success/failure of +		 * this call. They want to see the print to error log and +		 * continue their initialization. Print warning for them, +		 * because it is programmer's error to be here. +		 */ +		mutex_unlock(&rdma_nl_mutex); +		WARN(true, +		     "The not-valid %u index was supplied to RDMA netlink\n", +		     index); +		return;  	} -	pr_warn("Can't remove callback for client idx %d. Not found\n", index); -	mutex_unlock(&ibnl_mutex); -	return -EINVAL; +	if (rdma_nl_types[index].cb_table) { +		mutex_unlock(&rdma_nl_mutex); +		WARN(true, +		     "The %u index is already registered in RDMA netlink\n", +		     index); +		return; +	} + +	rdma_nl_types[index].cb_table = cb_table; +	mutex_unlock(&rdma_nl_mutex); +} +EXPORT_SYMBOL(rdma_nl_register); + +void rdma_nl_unregister(unsigned int index) +{ +	mutex_lock(&rdma_nl_mutex); +	rdma_nl_types[index].cb_table = NULL; +	mutex_unlock(&rdma_nl_mutex);  } -EXPORT_SYMBOL(ibnl_remove_client); +EXPORT_SYMBOL(rdma_nl_unregister);  void *ibnl_put_msg(struct sk_buff *skb, struct nlmsghdr **nlh, int seq,  		   int len, int client, int op, int flags)  { -	unsigned char *prev_tail; - -	prev_tail = skb_tail_pointer(skb); -	*nlh = nlmsg_put(skb, 0, seq, RDMA_NL_GET_TYPE(client, op), -			 len, flags); +	*nlh = nlmsg_put(skb, 0, seq, RDMA_NL_GET_TYPE(client, op), len, flags);  	if (!*nlh) -		goto out_nlmsg_trim; -	(*nlh)->nlmsg_len = skb_tail_pointer(skb) - prev_tail; +		return NULL;  	return nlmsg_data(*nlh); - -out_nlmsg_trim: -	nlmsg_trim(skb, prev_tail); -	return NULL;  }  EXPORT_SYMBOL(ibnl_put_msg);  int ibnl_put_attr(struct sk_buff *skb, struct nlmsghdr *nlh,  		  int len, void *data, int type)  { -	unsigned char *prev_tail; - -	prev_tail = skb_tail_pointer(skb); -	if (nla_put(skb, type, len, data)) -		goto nla_put_failure; -	nlh->nlmsg_len += skb_tail_pointer(skb) - prev_tail; +	if (nla_put(skb, type, len, data)) { +		nlmsg_cancel(skb, nlh); +		return -EMSGSIZE; +	}  	return 0; - -nla_put_failure: -	nlmsg_trim(skb, prev_tail - nlh->nlmsg_len); -	return -EMSGSIZE;  }  EXPORT_SYMBOL(ibnl_put_attr); -static int ibnl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, -			struct netlink_ext_ack *extack) +static int rdma_nl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, +			   struct netlink_ext_ack *extack)  { -	struct ibnl_client *client;  	int type = nlh->nlmsg_type; -	int index = RDMA_NL_GET_CLIENT(type); +	unsigned int index = RDMA_NL_GET_CLIENT(type);  	unsigned int op = RDMA_NL_GET_OP(type); +	const struct rdma_nl_cbs *cb_table; + +	if (!is_nl_valid(index, op)) +		return -EINVAL; + +	cb_table = rdma_nl_types[index].cb_table; -	list_for_each_entry(client, &client_list, list) { -		if (client->index == index) { -			if (op >= client->nops || !client->cb_table[op].dump) -				return -EINVAL; - -			/* -			 * For response or local service set_timeout request, -			 * there is no need to use netlink_dump_start. -			 */ -			if (!(nlh->nlmsg_flags & NLM_F_REQUEST) || -			    (index == RDMA_NL_LS && -			     op == RDMA_NL_LS_OP_SET_TIMEOUT)) { -				struct netlink_callback cb = { -					.skb = skb, -					.nlh = nlh, -					.dump = client->cb_table[op].dump, -					.module = client->cb_table[op].module, -				}; - -				return cb.dump(skb, &cb); -			} - -			{ -				struct netlink_dump_control c = { -					.dump = client->cb_table[op].dump, -					.module = client->cb_table[op].module, -				}; -				return netlink_dump_start(nls, skb, nlh, &c); -			} -		} +	if ((cb_table[op].flags & RDMA_NL_ADMIN_PERM) && +	    !netlink_capable(skb, CAP_NET_ADMIN)) +		return -EPERM; + +	/* FIXME: Convert IWCM to properly handle doit callbacks */ +	if ((nlh->nlmsg_flags & NLM_F_DUMP) || index == RDMA_NL_RDMA_CM || +	    index == RDMA_NL_IWCM) { +		struct netlink_dump_control c = { +			.dump = cb_table[op].dump, +		}; +		return netlink_dump_start(nls, skb, nlh, &c);  	} -	pr_info("Index %d wasn't found in client list\n", index); -	return -EINVAL; +	if (cb_table[op].doit) +		return cb_table[op].doit(skb, nlh, extack); + +	return 0;  } -static void ibnl_rcv_reply_skb(struct sk_buff *skb) +/* + * This function is similar to netlink_rcv_skb with one exception: + * It calls to the callback for the netlink messages without NLM_F_REQUEST + * flag. These messages are intended for RDMA_NL_LS consumer, so it is allowed + * for that consumer only. + */ +static int rdma_nl_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, +						   struct nlmsghdr *, +						   struct netlink_ext_ack *))  { +	struct netlink_ext_ack extack = {};  	struct nlmsghdr *nlh; -	int msglen; +	int err; -	/* -	 * Process responses until there is no more message or the first -	 * request. Generally speaking, it is not recommended to mix responses -	 * with requests. -	 */  	while (skb->len >= nlmsg_total_size(0)) { +		int msglen; +  		nlh = nlmsg_hdr(skb); +		err = 0;  		if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len) -			return; - -		/* Handle response only */ -		if (nlh->nlmsg_flags & NLM_F_REQUEST) -			return; - -		ibnl_rcv_msg(skb, nlh, NULL); +			return 0; +		/* +		 * Generally speaking, the only requests are handled +		 * by the kernel, but RDMA_NL_LS is different, because it +		 * runs backward netlink scheme. Kernel initiates messages +		 * and waits for reply with data to keep pathrecord cache +		 * in sync. +		 */ +		if (!(nlh->nlmsg_flags & NLM_F_REQUEST) && +		    (RDMA_NL_GET_CLIENT(nlh->nlmsg_type) != RDMA_NL_LS)) +			goto ack; + +		/* Skip control messages */ +		if (nlh->nlmsg_type < NLMSG_MIN_TYPE) +			goto ack; + +		err = cb(skb, nlh, &extack); +		if (err == -EINTR) +			goto skip; + +ack: +		if (nlh->nlmsg_flags & NLM_F_ACK || err) +			netlink_ack(skb, nlh, err, &extack); + +skip:  		msglen = NLMSG_ALIGN(nlh->nlmsg_len);  		if (msglen > skb->len)  			msglen = skb->len;  		skb_pull(skb, msglen);  	} + +	return 0;  } -static void ibnl_rcv(struct sk_buff *skb) +static void rdma_nl_rcv(struct sk_buff *skb)  { -	mutex_lock(&ibnl_mutex); -	ibnl_rcv_reply_skb(skb); -	netlink_rcv_skb(skb, &ibnl_rcv_msg); -	mutex_unlock(&ibnl_mutex); +	mutex_lock(&rdma_nl_mutex); +	rdma_nl_rcv_skb(skb, &rdma_nl_rcv_msg); +	mutex_unlock(&rdma_nl_mutex);  } -int ibnl_unicast(struct sk_buff *skb, struct nlmsghdr *nlh, -			__u32 pid) +int rdma_nl_unicast(struct sk_buff *skb, u32 pid) +{ +	int err; + +	err = netlink_unicast(nls, skb, pid, MSG_DONTWAIT); +	return (err < 0) ? err : 0; +} +EXPORT_SYMBOL(rdma_nl_unicast); + +int rdma_nl_unicast_wait(struct sk_buff *skb, __u32 pid)  {  	int err;  	err = netlink_unicast(nls, skb, pid, 0);  	return (err < 0) ? err : 0;  } -EXPORT_SYMBOL(ibnl_unicast); +EXPORT_SYMBOL(rdma_nl_unicast_wait); -int ibnl_multicast(struct sk_buff *skb, struct nlmsghdr *nlh, -			unsigned int group, gfp_t flags) +int rdma_nl_multicast(struct sk_buff *skb, unsigned int group, gfp_t flags)  {  	return nlmsg_multicast(nls, skb, 0, group, flags);  } -EXPORT_SYMBOL(ibnl_multicast); +EXPORT_SYMBOL(rdma_nl_multicast); -int __init ibnl_init(void) +int __init rdma_nl_init(void)  {  	struct netlink_kernel_cfg cfg = { -		.input	= ibnl_rcv, +		.input	= rdma_nl_rcv,  	};  	nls = netlink_kernel_create(&init_net, NETLINK_RDMA, &cfg); -	if (!nls) { -		pr_warn("Failed to create netlink socket\n"); +	if (!nls)  		return -ENOMEM; -	}  	nls->sk_sndtimeo = 10 * HZ;  	return 0;  } -void ibnl_cleanup(void) +void rdma_nl_exit(void)  { -	struct ibnl_client *cur, *next; +	int idx; -	mutex_lock(&ibnl_mutex); -	list_for_each_entry_safe(cur, next, &client_list, list) { -		list_del(&(cur->list)); -		kfree(cur); -	} -	mutex_unlock(&ibnl_mutex); +	for (idx = 0; idx < RDMA_NL_NUM_CLIENTS; idx++) +		rdma_nl_unregister(idx);  	netlink_kernel_release(nls);  } + +MODULE_ALIAS_NET_PF_PROTO(PF_NETLINK, NETLINK_RDMA); |