| 
									
										
										
										
											2011-05-20 11:46:11 -07:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2010 Voltaire Inc.  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. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-05-27 15:29:33 -04:00
										 |  |  | #include <linux/export.h>
 | 
					
						
							| 
									
										
										
										
											2011-05-20 11:46:11 -07:00
										 |  |  | #include <net/netlink.h>
 | 
					
						
							|  |  |  | #include <net/net_namespace.h>
 | 
					
						
							|  |  |  | #include <net/sock.h>
 | 
					
						
							|  |  |  | #include <rdma/rdma_netlink.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct ibnl_client { | 
					
						
							|  |  |  | 	struct list_head		list; | 
					
						
							|  |  |  | 	int				index; | 
					
						
							|  |  |  | 	int				nops; | 
					
						
							|  |  |  | 	const struct ibnl_client_cbs   *cb_table; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static DEFINE_MUTEX(ibnl_mutex); | 
					
						
							|  |  |  | static struct sock *nls; | 
					
						
							|  |  |  | static LIST_HEAD(client_list); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int ibnl_add_client(int index, int nops, | 
					
						
							|  |  |  | 		    const struct ibnl_client_cbs cb_table[]) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ibnl_client *cur; | 
					
						
							|  |  |  | 	struct ibnl_client *nl_client; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	nl_client = kmalloc(sizeof *nl_client, GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!nl_client) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	nl_client->index	= index; | 
					
						
							|  |  |  | 	nl_client->nops		= nops; | 
					
						
							|  |  |  | 	nl_client->cb_table	= cb_table; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&ibnl_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	list_add_tail(&nl_client->list, &client_list); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_unlock(&ibnl_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(ibnl_add_client); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int ibnl_remove_client(int index) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	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; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	pr_warn("Can't remove callback for client idx %d. Not found\n", index); | 
					
						
							|  |  |  | 	mutex_unlock(&ibnl_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return -EINVAL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(ibnl_remove_client); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void *ibnl_put_msg(struct sk_buff *skb, struct nlmsghdr **nlh, int seq, | 
					
						
							| 
									
										
										
										
											2014-03-26 17:07:35 -05:00
										 |  |  | 		   int len, int client, int op, int flags) | 
					
						
							| 
									
										
										
										
											2011-05-20 11:46:11 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	unsigned char *prev_tail; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	prev_tail = skb_tail_pointer(skb); | 
					
						
							| 
									
										
										
										
											2012-06-26 21:43:19 -07:00
										 |  |  | 	*nlh = nlmsg_put(skb, 0, seq, RDMA_NL_GET_TYPE(client, op), | 
					
						
							| 
									
										
										
										
											2014-03-26 17:07:35 -05:00
										 |  |  | 			 len, flags); | 
					
						
							| 
									
										
										
										
											2012-06-26 21:43:19 -07:00
										 |  |  | 	if (!*nlh) | 
					
						
							|  |  |  | 		goto out_nlmsg_trim; | 
					
						
							| 
									
										
										
										
											2011-05-20 11:46:11 -07:00
										 |  |  | 	(*nlh)->nlmsg_len = skb_tail_pointer(skb) - prev_tail; | 
					
						
							| 
									
										
										
										
											2012-06-26 21:43:19 -07:00
										 |  |  | 	return nlmsg_data(*nlh); | 
					
						
							| 
									
										
										
										
											2011-05-20 11:46:11 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-06-26 21:43:19 -07:00
										 |  |  | out_nlmsg_trim: | 
					
						
							| 
									
										
										
										
											2011-05-20 11:46:11 -07:00
										 |  |  | 	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); | 
					
						
							| 
									
										
										
										
											2012-04-01 20:19:38 -04:00
										 |  |  | 	if (nla_put(skb, type, len, data)) | 
					
						
							|  |  |  | 		goto nla_put_failure; | 
					
						
							| 
									
										
										
										
											2011-05-20 11:46:11 -07:00
										 |  |  | 	nlh->nlmsg_len += skb_tail_pointer(skb) - prev_tail; | 
					
						
							|  |  |  | 	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 ibnl_client *client; | 
					
						
							|  |  |  | 	int type = nlh->nlmsg_type; | 
					
						
							|  |  |  | 	int index = RDMA_NL_GET_CLIENT(type); | 
					
						
							|  |  |  | 	int op = RDMA_NL_GET_OP(type); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	list_for_each_entry(client, &client_list, list) { | 
					
						
							|  |  |  | 		if (client->index == index) { | 
					
						
							|  |  |  | 			if (op < 0 || op >= client->nops || | 
					
						
							| 
									
										
										
										
											2013-09-30 22:03:54 +02:00
										 |  |  | 			    !client->cb_table[op].dump) | 
					
						
							| 
									
										
										
										
											2011-05-20 11:46:11 -07:00
										 |  |  | 				return -EINVAL; | 
					
						
							| 
									
										
										
										
											2012-02-24 14:30:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				struct netlink_dump_control c = { | 
					
						
							|  |  |  | 					.dump = client->cb_table[op].dump, | 
					
						
							| 
									
										
										
										
											2012-10-04 20:15:49 +00:00
										 |  |  | 					.module = client->cb_table[op].module, | 
					
						
							| 
									
										
										
										
											2012-02-24 14:30:15 +00:00
										 |  |  | 				}; | 
					
						
							|  |  |  | 				return netlink_dump_start(nls, skb, nlh, &c); | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2011-05-20 11:46:11 -07:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_info("Index %d wasn't found in client list\n", index); | 
					
						
							|  |  |  | 	return -EINVAL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void ibnl_rcv(struct sk_buff *skb) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	mutex_lock(&ibnl_mutex); | 
					
						
							|  |  |  | 	netlink_rcv_skb(skb, &ibnl_rcv_msg); | 
					
						
							|  |  |  | 	mutex_unlock(&ibnl_mutex); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-26 17:07:35 -05:00
										 |  |  | int ibnl_unicast(struct sk_buff *skb, struct nlmsghdr *nlh, | 
					
						
							|  |  |  | 			__u32 pid) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return nlmsg_unicast(nls, skb, pid); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(ibnl_unicast); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int ibnl_multicast(struct sk_buff *skb, struct nlmsghdr *nlh, | 
					
						
							|  |  |  | 			unsigned int group, gfp_t flags) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return nlmsg_multicast(nls, skb, 0, group, flags); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(ibnl_multicast); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-05-20 11:46:11 -07:00
										 |  |  | int __init ibnl_init(void) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-06-29 06:15:21 +00:00
										 |  |  | 	struct netlink_kernel_cfg cfg = { | 
					
						
							|  |  |  | 		.input	= ibnl_rcv, | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-08 02:53:54 +00:00
										 |  |  | 	nls = netlink_kernel_create(&init_net, NETLINK_RDMA, &cfg); | 
					
						
							| 
									
										
										
										
											2011-05-20 11:46:11 -07:00
										 |  |  | 	if (!nls) { | 
					
						
							|  |  |  | 		pr_warn("Failed to create netlink socket\n"); | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void ibnl_cleanup(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ibnl_client *cur, *next; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&ibnl_mutex); | 
					
						
							|  |  |  | 	list_for_each_entry_safe(cur, next, &client_list, list) { | 
					
						
							|  |  |  | 		list_del(&(cur->list)); | 
					
						
							|  |  |  | 		kfree(cur); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	mutex_unlock(&ibnl_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	netlink_kernel_release(nls); | 
					
						
							|  |  |  | } |