2011-05-20 22:46:11 +04: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 23:29:33 +04:00
# include <linux/export.h>
2011-05-20 22:46:11 +04:00
# include <net/netlink.h>
# include <net/net_namespace.h>
# include <net/sock.h>
# include <rdma/rdma_netlink.h>
2017-05-14 15:49:57 +03:00
# include "core_priv.h"
2011-05-20 22:46:11 +04:00
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 ) ;
2015-08-14 15:52:07 +03:00
int ibnl_chk_listeners ( unsigned int group )
{
if ( netlink_has_listeners ( nls , group ) = = 0 )
return - 1 ;
return 0 ;
}
2011-05-20 22:46:11 +04:00
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-27 02:07:35 +04:00
int len , int client , int op , int flags )
2011-05-20 22:46:11 +04:00
{
unsigned char * prev_tail ;
prev_tail = skb_tail_pointer ( skb ) ;
2012-06-27 08:43:19 +04:00
* nlh = nlmsg_put ( skb , 0 , seq , RDMA_NL_GET_TYPE ( client , op ) ,
2014-03-27 02:07:35 +04:00
len , flags ) ;
2012-06-27 08:43:19 +04:00
if ( ! * nlh )
goto out_nlmsg_trim ;
2011-05-20 22:46:11 +04:00
( * nlh ) - > nlmsg_len = skb_tail_pointer ( skb ) - prev_tail ;
2012-06-27 08:43:19 +04:00
return nlmsg_data ( * nlh ) ;
2011-05-20 22:46:11 +04:00
2012-06-27 08:43:19 +04:00
out_nlmsg_trim :
2011-05-20 22:46:11 +04: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-02 04:19:38 +04:00
if ( nla_put ( skb , type , len , data ) )
goto nla_put_failure ;
2011-05-20 22:46:11 +04: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 ) ;
2017-04-12 15:34:04 +03:00
static int ibnl_rcv_msg ( struct sk_buff * skb , struct nlmsghdr * nlh ,
struct netlink_ext_ack * extack )
2011-05-20 22:46:11 +04:00
{
struct ibnl_client * client ;
int type = nlh - > nlmsg_type ;
int index = RDMA_NL_GET_CLIENT ( type ) ;
2016-05-06 22:45:25 +03:00
unsigned int op = RDMA_NL_GET_OP ( type ) ;
2011-05-20 22:46:11 +04:00
list_for_each_entry ( client , & client_list , list ) {
if ( client - > index = = index ) {
2016-05-06 22:45:25 +03:00
if ( op > = client - > nops | | ! client - > cb_table [ op ] . dump )
2011-05-20 22:46:11 +04:00
return - EINVAL ;
2012-02-24 18:30:15 +04:00
2015-08-14 15:52:07 +03:00
/*
* 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 ) ;
}
2012-02-24 18:30:15 +04:00
{
struct netlink_dump_control c = {
. dump = client - > cb_table [ op ] . dump ,
2012-10-05 00:15:49 +04:00
. module = client - > cb_table [ op ] . module ,
2012-02-24 18:30:15 +04:00
} ;
return netlink_dump_start ( nls , skb , nlh , & c ) ;
}
2011-05-20 22:46:11 +04:00
}
}
pr_info ( " Index %d wasn't found in client list \n " , index ) ;
return - EINVAL ;
}
2015-08-14 15:52:07 +03:00
static void ibnl_rcv_reply_skb ( struct sk_buff * skb )
{
struct nlmsghdr * nlh ;
int msglen ;
/*
* 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 ) ) {
nlh = nlmsg_hdr ( skb ) ;
if ( nlh - > nlmsg_len < NLMSG_HDRLEN | | skb - > len < nlh - > nlmsg_len )
return ;
/* Handle response only */
if ( nlh - > nlmsg_flags & NLM_F_REQUEST )
return ;
2017-04-12 15:34:04 +03:00
ibnl_rcv_msg ( skb , nlh , NULL ) ;
2015-08-14 15:52:07 +03:00
msglen = NLMSG_ALIGN ( nlh - > nlmsg_len ) ;
if ( msglen > skb - > len )
msglen = skb - > len ;
skb_pull ( skb , msglen ) ;
}
}
2011-05-20 22:46:11 +04:00
static void ibnl_rcv ( struct sk_buff * skb )
{
mutex_lock ( & ibnl_mutex ) ;
2015-08-14 15:52:07 +03:00
ibnl_rcv_reply_skb ( skb ) ;
2011-05-20 22:46:11 +04:00
netlink_rcv_skb ( skb , & ibnl_rcv_msg ) ;
mutex_unlock ( & ibnl_mutex ) ;
}
2014-03-27 02:07:35 +04:00
int ibnl_unicast ( struct sk_buff * skb , struct nlmsghdr * nlh ,
__u32 pid )
{
2016-07-28 23:02:26 +03:00
int err ;
err = netlink_unicast ( nls , skb , pid , 0 ) ;
return ( err < 0 ) ? err : 0 ;
2014-03-27 02:07:35 +04:00
}
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 22:46:11 +04:00
int __init ibnl_init ( void )
{
2012-06-29 10:15:21 +04:00
struct netlink_kernel_cfg cfg = {
. input = ibnl_rcv ,
} ;
2012-09-08 06:53:54 +04:00
nls = netlink_kernel_create ( & init_net , NETLINK_RDMA , & cfg ) ;
2011-05-20 22:46:11 +04:00
if ( ! nls ) {
pr_warn ( " Failed to create netlink socket \n " ) ;
return - ENOMEM ;
}
2016-07-28 23:02:26 +03:00
nls - > sk_sndtimeo = 10 * HZ ;
2011-05-20 22:46:11 +04:00
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 ) ;
}