2018-03-05 03:39:05 +03:00
/*
* Copyright Samuel Mendoza - Jonas , IBM Corporation 2018.
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/if_arp.h>
# include <linux/rtnetlink.h>
# include <linux/etherdevice.h>
# include <linux/module.h>
# include <net/genetlink.h>
# include <net/ncsi.h>
# include <linux/skbuff.h>
# include <net/sock.h>
# include <uapi/linux/ncsi.h>
# include "internal.h"
# include "ncsi-netlink.h"
static struct genl_family ncsi_genl_family ;
static const struct nla_policy ncsi_genl_policy [ NCSI_ATTR_MAX + 1 ] = {
[ NCSI_ATTR_IFINDEX ] = { . type = NLA_U32 } ,
[ NCSI_ATTR_PACKAGE_LIST ] = { . type = NLA_NESTED } ,
[ NCSI_ATTR_PACKAGE_ID ] = { . type = NLA_U32 } ,
[ NCSI_ATTR_CHANNEL_ID ] = { . type = NLA_U32 } ,
} ;
static struct ncsi_dev_priv * ndp_from_ifindex ( struct net * net , u32 ifindex )
{
struct ncsi_dev_priv * ndp ;
struct net_device * dev ;
struct ncsi_dev * nd ;
struct ncsi_dev ;
if ( ! net )
return NULL ;
dev = dev_get_by_index ( net , ifindex ) ;
if ( ! dev ) {
pr_err ( " NCSI netlink: No device for ifindex %u \n " , ifindex ) ;
return NULL ;
}
nd = ncsi_find_dev ( dev ) ;
ndp = nd ? TO_NCSI_DEV_PRIV ( nd ) : NULL ;
dev_put ( dev ) ;
return ndp ;
}
static int ncsi_write_channel_info ( struct sk_buff * skb ,
struct ncsi_dev_priv * ndp ,
struct ncsi_channel * nc )
{
struct nlattr * vid_nest ;
struct ncsi_channel_filter * ncf ;
struct ncsi_channel_mode * m ;
u32 * data ;
int i ;
nla_put_u32 ( skb , NCSI_CHANNEL_ATTR_ID , nc - > id ) ;
m = & nc - > modes [ NCSI_MODE_LINK ] ;
nla_put_u32 ( skb , NCSI_CHANNEL_ATTR_LINK_STATE , m - > data [ 2 ] ) ;
if ( nc - > state = = NCSI_CHANNEL_ACTIVE )
nla_put_flag ( skb , NCSI_CHANNEL_ATTR_ACTIVE ) ;
if ( ndp - > force_channel = = nc )
nla_put_flag ( skb , NCSI_CHANNEL_ATTR_FORCED ) ;
nla_put_u32 ( skb , NCSI_CHANNEL_ATTR_VERSION_MAJOR , nc - > version . version ) ;
nla_put_u32 ( skb , NCSI_CHANNEL_ATTR_VERSION_MINOR , nc - > version . alpha2 ) ;
nla_put_string ( skb , NCSI_CHANNEL_ATTR_VERSION_STR , nc - > version . fw_name ) ;
vid_nest = nla_nest_start ( skb , NCSI_CHANNEL_ATTR_VLAN_LIST ) ;
if ( ! vid_nest )
return - ENOMEM ;
ncf = nc - > filters [ NCSI_FILTER_VLAN ] ;
i = - 1 ;
if ( ncf ) {
while ( ( i = find_next_bit ( ( void * ) & ncf - > bitmap , ncf - > total ,
i + 1 ) ) < ncf - > total ) {
data = ncsi_get_filter ( nc , NCSI_FILTER_VLAN , i ) ;
/* Uninitialised channels will have 'zero' vlan ids */
if ( ! data | | ! * data )
continue ;
nla_put_u16 ( skb , NCSI_CHANNEL_ATTR_VLAN_ID ,
* ( u16 * ) data ) ;
}
}
nla_nest_end ( skb , vid_nest ) ;
return 0 ;
}
static int ncsi_write_package_info ( struct sk_buff * skb ,
struct ncsi_dev_priv * ndp , unsigned int id )
{
struct nlattr * pnest , * cnest , * nest ;
struct ncsi_package * np ;
struct ncsi_channel * nc ;
bool found ;
int rc ;
if ( id > ndp - > package_num ) {
netdev_info ( ndp - > ndev . dev , " NCSI: No package with id %u \n " , id ) ;
return - ENODEV ;
}
found = false ;
NCSI_FOR_EACH_PACKAGE ( ndp , np ) {
if ( np - > id ! = id )
continue ;
pnest = nla_nest_start ( skb , NCSI_PKG_ATTR ) ;
if ( ! pnest )
return - ENOMEM ;
nla_put_u32 ( skb , NCSI_PKG_ATTR_ID , np - > id ) ;
if ( ndp - > force_package = = np )
nla_put_flag ( skb , NCSI_PKG_ATTR_FORCED ) ;
cnest = nla_nest_start ( skb , NCSI_PKG_ATTR_CHANNEL_LIST ) ;
if ( ! cnest ) {
nla_nest_cancel ( skb , pnest ) ;
return - ENOMEM ;
}
NCSI_FOR_EACH_CHANNEL ( np , nc ) {
nest = nla_nest_start ( skb , NCSI_CHANNEL_ATTR ) ;
if ( ! nest ) {
nla_nest_cancel ( skb , cnest ) ;
nla_nest_cancel ( skb , pnest ) ;
return - ENOMEM ;
}
rc = ncsi_write_channel_info ( skb , ndp , nc ) ;
if ( rc ) {
nla_nest_cancel ( skb , nest ) ;
nla_nest_cancel ( skb , cnest ) ;
nla_nest_cancel ( skb , pnest ) ;
return rc ;
}
nla_nest_end ( skb , nest ) ;
}
nla_nest_end ( skb , cnest ) ;
nla_nest_end ( skb , pnest ) ;
found = true ;
}
if ( ! found )
return - ENODEV ;
return 0 ;
}
static int ncsi_pkg_info_nl ( struct sk_buff * msg , struct genl_info * info )
{
struct ncsi_dev_priv * ndp ;
unsigned int package_id ;
struct sk_buff * skb ;
struct nlattr * attr ;
void * hdr ;
int rc ;
if ( ! info | | ! info - > attrs )
return - EINVAL ;
if ( ! info - > attrs [ NCSI_ATTR_IFINDEX ] )
return - EINVAL ;
if ( ! info - > attrs [ NCSI_ATTR_PACKAGE_ID ] )
return - EINVAL ;
ndp = ndp_from_ifindex ( genl_info_net ( info ) ,
nla_get_u32 ( info - > attrs [ NCSI_ATTR_IFINDEX ] ) ) ;
if ( ! ndp )
return - ENODEV ;
skb = genlmsg_new ( NLMSG_DEFAULT_SIZE , GFP_KERNEL ) ;
if ( ! skb )
return - ENOMEM ;
hdr = genlmsg_put ( skb , info - > snd_portid , info - > snd_seq ,
& ncsi_genl_family , 0 , NCSI_CMD_PKG_INFO ) ;
if ( ! hdr ) {
2018-03-08 12:36:04 +03:00
kfree_skb ( skb ) ;
2018-03-05 03:39:05 +03:00
return - EMSGSIZE ;
}
package_id = nla_get_u32 ( info - > attrs [ NCSI_ATTR_PACKAGE_ID ] ) ;
attr = nla_nest_start ( skb , NCSI_ATTR_PACKAGE_LIST ) ;
2018-03-26 14:27:12 +03:00
if ( ! attr ) {
kfree_skb ( skb ) ;
return - EMSGSIZE ;
}
2018-03-05 03:39:05 +03:00
rc = ncsi_write_package_info ( skb , ndp , package_id ) ;
if ( rc ) {
nla_nest_cancel ( skb , attr ) ;
goto err ;
}
nla_nest_end ( skb , attr ) ;
genlmsg_end ( skb , hdr ) ;
return genlmsg_reply ( skb , info ) ;
err :
genlmsg_cancel ( skb , hdr ) ;
2018-03-08 12:36:04 +03:00
kfree_skb ( skb ) ;
2018-03-05 03:39:05 +03:00
return rc ;
}
static int ncsi_pkg_info_all_nl ( struct sk_buff * skb ,
struct netlink_callback * cb )
{
struct nlattr * attrs [ NCSI_ATTR_MAX ] ;
struct ncsi_package * np , * package ;
struct ncsi_dev_priv * ndp ;
unsigned int package_id ;
struct nlattr * attr ;
void * hdr ;
int rc ;
rc = genlmsg_parse ( cb - > nlh , & ncsi_genl_family , attrs , NCSI_ATTR_MAX ,
ncsi_genl_policy , NULL ) ;
if ( rc )
return rc ;
if ( ! attrs [ NCSI_ATTR_IFINDEX ] )
return - EINVAL ;
ndp = ndp_from_ifindex ( get_net ( sock_net ( skb - > sk ) ) ,
nla_get_u32 ( attrs [ NCSI_ATTR_IFINDEX ] ) ) ;
if ( ! ndp )
return - ENODEV ;
package_id = cb - > args [ 0 ] ;
package = NULL ;
NCSI_FOR_EACH_PACKAGE ( ndp , np )
if ( np - > id = = package_id )
package = np ;
if ( ! package )
return 0 ; /* done */
hdr = genlmsg_put ( skb , NETLINK_CB ( cb - > skb ) . portid , cb - > nlh - > nlmsg_seq ,
& ncsi_genl_family , 0 , NCSI_CMD_PKG_INFO ) ;
if ( ! hdr ) {
rc = - EMSGSIZE ;
goto err ;
}
attr = nla_nest_start ( skb , NCSI_ATTR_PACKAGE_LIST ) ;
rc = ncsi_write_package_info ( skb , ndp , package - > id ) ;
if ( rc ) {
nla_nest_cancel ( skb , attr ) ;
goto err ;
}
nla_nest_end ( skb , attr ) ;
genlmsg_end ( skb , hdr ) ;
cb - > args [ 0 ] = package_id + 1 ;
return skb - > len ;
err :
genlmsg_cancel ( skb , hdr ) ;
return rc ;
}
static int ncsi_set_interface_nl ( struct sk_buff * msg , struct genl_info * info )
{
struct ncsi_package * np , * package ;
struct ncsi_channel * nc , * channel ;
u32 package_id , channel_id ;
struct ncsi_dev_priv * ndp ;
unsigned long flags ;
if ( ! info | | ! info - > attrs )
return - EINVAL ;
if ( ! info - > attrs [ NCSI_ATTR_IFINDEX ] )
return - EINVAL ;
if ( ! info - > attrs [ NCSI_ATTR_PACKAGE_ID ] )
return - EINVAL ;
ndp = ndp_from_ifindex ( get_net ( sock_net ( msg - > sk ) ) ,
nla_get_u32 ( info - > attrs [ NCSI_ATTR_IFINDEX ] ) ) ;
if ( ! ndp )
return - ENODEV ;
package_id = nla_get_u32 ( info - > attrs [ NCSI_ATTR_PACKAGE_ID ] ) ;
package = NULL ;
spin_lock_irqsave ( & ndp - > lock , flags ) ;
NCSI_FOR_EACH_PACKAGE ( ndp , np )
if ( np - > id = = package_id )
package = np ;
if ( ! package ) {
/* The user has set a package that does not exist */
2018-03-08 12:36:28 +03:00
spin_unlock_irqrestore ( & ndp - > lock , flags ) ;
2018-03-05 03:39:05 +03:00
return - ERANGE ;
}
channel = NULL ;
if ( ! info - > attrs [ NCSI_ATTR_CHANNEL_ID ] ) {
/* Allow any channel */
channel_id = NCSI_RESERVED_CHANNEL ;
} else {
channel_id = nla_get_u32 ( info - > attrs [ NCSI_ATTR_CHANNEL_ID ] ) ;
NCSI_FOR_EACH_CHANNEL ( package , nc )
if ( nc - > id = = channel_id )
channel = nc ;
}
if ( channel_id ! = NCSI_RESERVED_CHANNEL & & ! channel ) {
/* The user has set a channel that does not exist on this
* package
*/
2018-03-08 12:36:28 +03:00
spin_unlock_irqrestore ( & ndp - > lock , flags ) ;
2018-03-05 03:39:05 +03:00
netdev_info ( ndp - > ndev . dev , " NCSI: Channel %u does not exist! \n " ,
channel_id ) ;
return - ERANGE ;
}
ndp - > force_package = package ;
ndp - > force_channel = channel ;
spin_unlock_irqrestore ( & ndp - > lock , flags ) ;
netdev_info ( ndp - > ndev . dev , " Set package 0x%x, channel 0x%x%s as preferred \n " ,
package_id , channel_id ,
channel_id = = NCSI_RESERVED_CHANNEL ? " (any) " : " " ) ;
/* Bounce the NCSI channel to set changes */
ncsi_stop_dev ( & ndp - > ndev ) ;
ncsi_start_dev ( & ndp - > ndev ) ;
return 0 ;
}
static int ncsi_clear_interface_nl ( struct sk_buff * msg , struct genl_info * info )
{
struct ncsi_dev_priv * ndp ;
unsigned long flags ;
if ( ! info | | ! info - > attrs )
return - EINVAL ;
if ( ! info - > attrs [ NCSI_ATTR_IFINDEX ] )
return - EINVAL ;
ndp = ndp_from_ifindex ( get_net ( sock_net ( msg - > sk ) ) ,
nla_get_u32 ( info - > attrs [ NCSI_ATTR_IFINDEX ] ) ) ;
if ( ! ndp )
return - ENODEV ;
/* Clear any override */
spin_lock_irqsave ( & ndp - > lock , flags ) ;
ndp - > force_package = NULL ;
ndp - > force_channel = NULL ;
spin_unlock_irqrestore ( & ndp - > lock , flags ) ;
netdev_info ( ndp - > ndev . dev , " NCSI: Cleared preferred package/channel \n " ) ;
/* Bounce the NCSI channel to set changes */
ncsi_stop_dev ( & ndp - > ndev ) ;
ncsi_start_dev ( & ndp - > ndev ) ;
return 0 ;
}
static const struct genl_ops ncsi_ops [ ] = {
{
. cmd = NCSI_CMD_PKG_INFO ,
. policy = ncsi_genl_policy ,
. doit = ncsi_pkg_info_nl ,
. dumpit = ncsi_pkg_info_all_nl ,
. flags = 0 ,
} ,
{
. cmd = NCSI_CMD_SET_INTERFACE ,
. policy = ncsi_genl_policy ,
. doit = ncsi_set_interface_nl ,
. flags = GENL_ADMIN_PERM ,
} ,
{
. cmd = NCSI_CMD_CLEAR_INTERFACE ,
. policy = ncsi_genl_policy ,
. doit = ncsi_clear_interface_nl ,
. flags = GENL_ADMIN_PERM ,
} ,
} ;
static struct genl_family ncsi_genl_family __ro_after_init = {
. name = " NCSI " ,
. version = 0 ,
. maxattr = NCSI_ATTR_MAX ,
. module = THIS_MODULE ,
. ops = ncsi_ops ,
. n_ops = ARRAY_SIZE ( ncsi_ops ) ,
} ;
int ncsi_init_netlink ( struct net_device * dev )
{
int rc ;
rc = genl_register_family ( & ncsi_genl_family ) ;
if ( rc )
netdev_err ( dev , " ncsi: failed to register netlink family \n " ) ;
return rc ;
}
int ncsi_unregister_netlink ( struct net_device * dev )
{
int rc ;
rc = genl_unregister_family ( & ncsi_genl_family ) ;
if ( rc )
netdev_err ( dev , " ncsi: failed to unregister netlink family \n " ) ;
return rc ;
}