2019-12-27 17:55:18 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2019-12-27 17:55:23 +03:00
# include <net/sock.h>
2019-12-27 17:55:18 +03:00
# include <linux/ethtool_netlink.h>
# include "netlink.h"
2019-12-27 17:55:23 +03:00
static struct genl_family ethtool_genl_family ;
2019-12-27 17:55:33 +03:00
static bool ethnl_ok __read_mostly ;
2019-12-27 17:55:58 +03:00
static u32 ethnl_bcast_seq ;
2019-12-27 17:55:33 +03:00
2019-12-27 17:55:23 +03:00
static const struct nla_policy ethnl_header_policy [ ETHTOOL_A_HEADER_MAX + 1 ] = {
[ ETHTOOL_A_HEADER_UNSPEC ] = { . type = NLA_REJECT } ,
[ ETHTOOL_A_HEADER_DEV_INDEX ] = { . type = NLA_U32 } ,
[ ETHTOOL_A_HEADER_DEV_NAME ] = { . type = NLA_NUL_STRING ,
. len = ALTIFNAMSIZ - 1 } ,
[ ETHTOOL_A_HEADER_FLAGS ] = { . type = NLA_U32 } ,
} ;
/**
2020-03-12 23:07:38 +03:00
* ethnl_parse_header_dev_get ( ) - parse request header
2019-12-27 17:55:23 +03:00
* @ req_info : structure to put results into
* @ header : nest attribute with request header
* @ net : request netns
* @ extack : netlink extack for error reporting
* @ require_dev : fail if no device identified in header
*
* Parse request header in nested attribute @ nest and puts results into
* the structure pointed to by @ req_info . Extack from @ info is used for error
* reporting . If req_info - > dev is not null on return , reference to it has
* been taken . If error is returned , * req_info is null initialized and no
* reference is held .
*
* Return : 0 on success or negative error code
*/
2020-03-12 23:07:38 +03:00
int ethnl_parse_header_dev_get ( struct ethnl_req_info * req_info ,
const struct nlattr * header , struct net * net ,
struct netlink_ext_ack * extack , bool require_dev )
2019-12-27 17:55:23 +03:00
{
struct nlattr * tb [ ETHTOOL_A_HEADER_MAX + 1 ] ;
const struct nlattr * devname_attr ;
struct net_device * dev = NULL ;
2020-03-15 20:17:53 +03:00
u32 flags = 0 ;
2019-12-27 17:55:23 +03:00
int ret ;
if ( ! header ) {
NL_SET_ERR_MSG ( extack , " request header missing " ) ;
return - EINVAL ;
}
ret = nla_parse_nested ( tb , ETHTOOL_A_HEADER_MAX , header ,
ethnl_header_policy , extack ) ;
if ( ret < 0 )
return ret ;
2020-03-15 20:17:53 +03:00
if ( tb [ ETHTOOL_A_HEADER_FLAGS ] ) {
flags = nla_get_u32 ( tb [ ETHTOOL_A_HEADER_FLAGS ] ) ;
if ( flags & ~ ETHTOOL_FLAG_ALL ) {
NL_SET_ERR_MSG_ATTR ( extack , tb [ ETHTOOL_A_HEADER_FLAGS ] ,
" unrecognized request flags " ) ;
nl_set_extack_cookie_u32 ( extack , ETHTOOL_FLAG_ALL ) ;
return - EOPNOTSUPP ;
}
}
2019-12-27 17:55:23 +03:00
2020-03-15 20:17:53 +03:00
devname_attr = tb [ ETHTOOL_A_HEADER_DEV_NAME ] ;
2019-12-27 17:55:23 +03:00
if ( tb [ ETHTOOL_A_HEADER_DEV_INDEX ] ) {
u32 ifindex = nla_get_u32 ( tb [ ETHTOOL_A_HEADER_DEV_INDEX ] ) ;
dev = dev_get_by_index ( net , ifindex ) ;
if ( ! dev ) {
NL_SET_ERR_MSG_ATTR ( extack ,
tb [ ETHTOOL_A_HEADER_DEV_INDEX ] ,
" no device matches ifindex " ) ;
return - ENODEV ;
}
/* if both ifindex and ifname are passed, they must match */
if ( devname_attr & &
strncmp ( dev - > name , nla_data ( devname_attr ) , IFNAMSIZ ) ) {
dev_put ( dev ) ;
NL_SET_ERR_MSG_ATTR ( extack , header ,
" ifindex and name do not match " ) ;
return - ENODEV ;
}
} else if ( devname_attr ) {
dev = dev_get_by_name ( net , nla_data ( devname_attr ) ) ;
if ( ! dev ) {
NL_SET_ERR_MSG_ATTR ( extack , devname_attr ,
" no device matches name " ) ;
return - ENODEV ;
}
} else if ( require_dev ) {
NL_SET_ERR_MSG_ATTR ( extack , header ,
" neither ifindex nor name specified " ) ;
return - EINVAL ;
}
if ( dev & & ! netif_device_present ( dev ) ) {
dev_put ( dev ) ;
NL_SET_ERR_MSG ( extack , " device not present " ) ;
return - ENODEV ;
}
req_info - > dev = dev ;
2020-03-15 20:17:53 +03:00
req_info - > flags = flags ;
2019-12-27 17:55:23 +03:00
return 0 ;
}
/**
* ethnl_fill_reply_header ( ) - Put common header into a reply message
* @ skb : skb with the message
* @ dev : network device to describe in header
* @ attrtype : attribute type to use for the nest
*
* Create a nested attribute with attributes describing given network device .
*
* Return : 0 on success , error value ( - EMSGSIZE only ) on error
*/
int ethnl_fill_reply_header ( struct sk_buff * skb , struct net_device * dev ,
u16 attrtype )
{
struct nlattr * nest ;
if ( ! dev )
return 0 ;
nest = nla_nest_start ( skb , attrtype ) ;
if ( ! nest )
return - EMSGSIZE ;
if ( nla_put_u32 ( skb , ETHTOOL_A_HEADER_DEV_INDEX , ( u32 ) dev - > ifindex ) | |
nla_put_string ( skb , ETHTOOL_A_HEADER_DEV_NAME , dev - > name ) )
goto nla_put_failure ;
/* If more attributes are put into reply header, ethnl_header_size()
* must be updated to account for them .
*/
nla_nest_end ( skb , nest ) ;
return 0 ;
nla_put_failure :
nla_nest_cancel ( skb , nest ) ;
return - EMSGSIZE ;
}
/**
* ethnl_reply_init ( ) - Create skb for a reply and fill device identification
2020-01-27 01:11:01 +03:00
* @ payload : payload length ( without netlink and genetlink header )
* @ dev : device the reply is about ( may be null )
* @ cmd : ETHTOOL_MSG_ * message type for reply
* @ hdr_attrtype : attribute type for common header
* @ info : genetlink info of the received packet we respond to
* @ ehdrp : place to store payload pointer returned by genlmsg_new ( )
2019-12-27 17:55:23 +03:00
*
* Return : pointer to allocated skb on success , NULL on error
*/
struct sk_buff * ethnl_reply_init ( size_t payload , struct net_device * dev , u8 cmd ,
u16 hdr_attrtype , struct genl_info * info ,
void * * ehdrp )
{
struct sk_buff * skb ;
skb = genlmsg_new ( payload , GFP_KERNEL ) ;
if ( ! skb )
goto err ;
* ehdrp = genlmsg_put_reply ( skb , info , & ethtool_genl_family , 0 , cmd ) ;
if ( ! * ehdrp )
goto err_free ;
if ( dev ) {
int ret ;
ret = ethnl_fill_reply_header ( skb , dev , hdr_attrtype ) ;
if ( ret < 0 )
goto err_free ;
}
return skb ;
err_free :
nlmsg_free ( skb ) ;
err :
if ( info )
GENL_SET_ERR_MSG ( info , " failed to setup reply message " ) ;
return NULL ;
}
2020-07-10 03:42:47 +03:00
void * ethnl_dump_put ( struct sk_buff * skb , struct netlink_callback * cb , u8 cmd )
{
return genlmsg_put ( skb , NETLINK_CB ( cb - > skb ) . portid , cb - > nlh - > nlmsg_seq ,
& ethtool_genl_family , 0 , cmd ) ;
}
2020-05-10 22:12:35 +03:00
void * ethnl_bcastmsg_put ( struct sk_buff * skb , u8 cmd )
2019-12-27 17:55:58 +03:00
{
return genlmsg_put ( skb , 0 , + + ethnl_bcast_seq , & ethtool_genl_family , 0 ,
cmd ) ;
}
2020-05-10 22:12:35 +03:00
int ethnl_multicast ( struct sk_buff * skb , struct net_device * dev )
2019-12-27 17:55:58 +03:00
{
return genlmsg_multicast_netns ( & ethtool_genl_family , dev_net ( dev ) , skb ,
0 , ETHNL_MCGRP_MONITOR , GFP_KERNEL ) ;
}
2019-12-27 17:55:38 +03:00
/* GET request helpers */
/**
* struct ethnl_dump_ctx - context structure for generic dumpit ( ) callback
2020-01-27 01:11:01 +03:00
* @ ops : request ops of currently processed message type
* @ req_info : parsed request header of processed request
* @ reply_data : data needed to compose the reply
* @ pos_hash : saved iteration position - hashbucket
* @ pos_idx : saved iteration position - index
2019-12-27 17:55:38 +03:00
*
* These parameters are kept in struct netlink_callback as context preserved
* between iterations . They are initialized by ethnl_default_start ( ) and used
* in ethnl_default_dumpit ( ) and ethnl_default_done ( ) .
*/
struct ethnl_dump_ctx {
const struct ethnl_request_ops * ops ;
struct ethnl_req_info * req_info ;
struct ethnl_reply_data * reply_data ;
int pos_hash ;
int pos_idx ;
} ;
static const struct ethnl_request_ops *
ethnl_default_requests [ __ETHTOOL_MSG_USER_CNT ] = {
2019-12-27 17:55:43 +03:00
[ ETHTOOL_MSG_STRSET_GET ] = & ethnl_strset_request_ops ,
2019-12-27 17:55:48 +03:00
[ ETHTOOL_MSG_LINKINFO_GET ] = & ethnl_linkinfo_request_ops ,
2019-12-27 17:56:08 +03:00
[ ETHTOOL_MSG_LINKMODES_GET ] = & ethnl_linkmodes_request_ops ,
2019-12-27 17:56:23 +03:00
[ ETHTOOL_MSG_LINKSTATE_GET ] = & ethnl_linkstate_request_ops ,
2020-01-27 01:11:04 +03:00
[ ETHTOOL_MSG_DEBUG_GET ] = & ethnl_debug_request_ops ,
2020-01-27 01:11:13 +03:00
[ ETHTOOL_MSG_WOL_GET ] = & ethnl_wol_request_ops ,
2020-03-12 23:07:48 +03:00
[ ETHTOOL_MSG_FEATURES_GET ] = & ethnl_features_request_ops ,
2020-03-12 23:08:08 +03:00
[ ETHTOOL_MSG_PRIVFLAGS_GET ] = & ethnl_privflags_request_ops ,
2020-03-12 23:08:23 +03:00
[ ETHTOOL_MSG_RINGS_GET ] = & ethnl_rings_request_ops ,
2020-03-12 23:08:38 +03:00
[ ETHTOOL_MSG_CHANNELS_GET ] = & ethnl_channels_request_ops ,
2020-03-28 02:01:08 +03:00
[ ETHTOOL_MSG_COALESCE_GET ] = & ethnl_coalesce_request_ops ,
2020-03-28 02:01:23 +03:00
[ ETHTOOL_MSG_PAUSE_GET ] = & ethnl_pause_request_ops ,
2020-03-28 02:01:38 +03:00
[ ETHTOOL_MSG_EEE_GET ] = & ethnl_eee_request_ops ,
2020-03-28 02:01:58 +03:00
[ ETHTOOL_MSG_TSINFO_GET ] = & ethnl_tsinfo_request_ops ,
2019-12-27 17:55:38 +03:00
} ;
static struct ethnl_dump_ctx * ethnl_dump_context ( struct netlink_callback * cb )
{
return ( struct ethnl_dump_ctx * ) cb - > ctx ;
}
/**
* ethnl_default_parse ( ) - Parse request message
* @ req_info : pointer to structure to put data into
* @ nlhdr : pointer to request message header
* @ net : request netns
* @ request_ops : struct request_ops for request type
* @ extack : netlink extack for error reporting
* @ require_dev : fail if no device identified in header
*
* Parse universal request header and call request specific - > parse_request ( )
* callback ( if defined ) to parse the rest of the message .
*
* Return : 0 on success or negative error code
*/
static int ethnl_default_parse ( struct ethnl_req_info * req_info ,
const struct nlmsghdr * nlhdr , struct net * net ,
const struct ethnl_request_ops * request_ops ,
struct netlink_ext_ack * extack , bool require_dev )
{
struct nlattr * * tb ;
int ret ;
tb = kmalloc_array ( request_ops - > max_attr + 1 , sizeof ( tb [ 0 ] ) ,
GFP_KERNEL ) ;
if ( ! tb )
return - ENOMEM ;
ret = nlmsg_parse ( nlhdr , GENL_HDRLEN , tb , request_ops - > max_attr ,
request_ops - > request_policy , extack ) ;
if ( ret < 0 )
goto out ;
2020-03-12 23:07:38 +03:00
ret = ethnl_parse_header_dev_get ( req_info , tb [ request_ops - > hdr_attr ] ,
net , extack , require_dev ) ;
2019-12-27 17:55:38 +03:00
if ( ret < 0 )
goto out ;
if ( request_ops - > parse_request ) {
ret = request_ops - > parse_request ( req_info , tb , extack ) ;
if ( ret < 0 )
goto out ;
}
ret = 0 ;
out :
kfree ( tb ) ;
return ret ;
}
/**
* ethnl_init_reply_data ( ) - Initialize reply data for GET request
2020-01-27 01:11:01 +03:00
* @ reply_data : pointer to embedded struct ethnl_reply_data
* @ ops : instance of struct ethnl_request_ops describing the layout
* @ dev : network device to initialize the reply for
2019-12-27 17:55:38 +03:00
*
* Fills the reply data part with zeros and sets the dev member . Must be called
* before calling the - > fill_reply ( ) callback ( for each iteration when handling
* dump requests ) .
*/
static void ethnl_init_reply_data ( struct ethnl_reply_data * reply_data ,
const struct ethnl_request_ops * ops ,
struct net_device * dev )
{
memset ( reply_data , 0 , ops - > reply_data_size ) ;
reply_data - > dev = dev ;
}
/* default ->doit() handler for GET type requests */
static int ethnl_default_doit ( struct sk_buff * skb , struct genl_info * info )
{
struct ethnl_reply_data * reply_data = NULL ;
struct ethnl_req_info * req_info = NULL ;
const u8 cmd = info - > genlhdr - > cmd ;
const struct ethnl_request_ops * ops ;
struct sk_buff * rskb ;
void * reply_payload ;
int reply_len ;
int ret ;
ops = ethnl_default_requests [ cmd ] ;
if ( WARN_ONCE ( ! ops , " cmd %u has no ethnl_request_ops \n " , cmd ) )
return - EOPNOTSUPP ;
req_info = kzalloc ( ops - > req_info_size , GFP_KERNEL ) ;
if ( ! req_info )
return - ENOMEM ;
reply_data = kmalloc ( ops - > reply_data_size , GFP_KERNEL ) ;
if ( ! reply_data ) {
kfree ( req_info ) ;
return - ENOMEM ;
}
ret = ethnl_default_parse ( req_info , info - > nlhdr , genl_info_net ( info ) , ops ,
info - > extack , ! ops - > allow_nodev_do ) ;
if ( ret < 0 )
goto err_dev ;
ethnl_init_reply_data ( reply_data , ops , req_info - > dev ) ;
rtnl_lock ( ) ;
ret = ops - > prepare_data ( req_info , reply_data , info ) ;
rtnl_unlock ( ) ;
if ( ret < 0 )
goto err_cleanup ;
2020-01-08 08:41:25 +03:00
ret = ops - > reply_size ( req_info , reply_data ) ;
2019-12-27 17:55:38 +03:00
if ( ret < 0 )
goto err_cleanup ;
2020-05-10 22:04:09 +03:00
reply_len = ret + ethnl_reply_header_size ( ) ;
2019-12-27 17:55:38 +03:00
ret = - ENOMEM ;
rskb = ethnl_reply_init ( reply_len , req_info - > dev , ops - > reply_cmd ,
ops - > hdr_attr , info , & reply_payload ) ;
if ( ! rskb )
goto err_cleanup ;
ret = ops - > fill_reply ( rskb , req_info , reply_data ) ;
if ( ret < 0 )
goto err_msg ;
if ( ops - > cleanup_data )
ops - > cleanup_data ( reply_data ) ;
genlmsg_end ( rskb , reply_payload ) ;
if ( req_info - > dev )
dev_put ( req_info - > dev ) ;
kfree ( reply_data ) ;
kfree ( req_info ) ;
return genlmsg_reply ( rskb , info ) ;
err_msg :
WARN_ONCE ( ret = = - EMSGSIZE , " calculated message payload length (%d) not sufficient \n " , reply_len ) ;
nlmsg_free ( rskb ) ;
err_cleanup :
if ( ops - > cleanup_data )
ops - > cleanup_data ( reply_data ) ;
err_dev :
if ( req_info - > dev )
dev_put ( req_info - > dev ) ;
kfree ( reply_data ) ;
kfree ( req_info ) ;
return ret ;
}
static int ethnl_default_dump_one ( struct sk_buff * skb , struct net_device * dev ,
2020-07-09 13:11:50 +03:00
const struct ethnl_dump_ctx * ctx ,
struct netlink_callback * cb )
2019-12-27 17:55:38 +03:00
{
2020-07-09 13:11:50 +03:00
void * ehdr ;
2019-12-27 17:55:38 +03:00
int ret ;
2020-07-09 13:11:50 +03:00
ehdr = genlmsg_put ( skb , NETLINK_CB ( cb - > skb ) . portid , cb - > nlh - > nlmsg_seq ,
& ethtool_genl_family , 0 , ctx - > ops - > reply_cmd ) ;
if ( ! ehdr )
return - EMSGSIZE ;
2019-12-27 17:55:38 +03:00
ethnl_init_reply_data ( ctx - > reply_data , ctx - > ops , dev ) ;
rtnl_lock ( ) ;
ret = ctx - > ops - > prepare_data ( ctx - > req_info , ctx - > reply_data , NULL ) ;
rtnl_unlock ( ) ;
if ( ret < 0 )
goto out ;
ret = ethnl_fill_reply_header ( skb , dev , ctx - > ops - > hdr_attr ) ;
if ( ret < 0 )
goto out ;
ret = ctx - > ops - > fill_reply ( skb , ctx - > req_info , ctx - > reply_data ) ;
out :
if ( ctx - > ops - > cleanup_data )
ctx - > ops - > cleanup_data ( ctx - > reply_data ) ;
ctx - > reply_data - > dev = NULL ;
2020-07-09 13:11:50 +03:00
if ( ret < 0 )
genlmsg_cancel ( skb , ehdr ) ;
else
genlmsg_end ( skb , ehdr ) ;
2019-12-27 17:55:38 +03:00
return ret ;
}
/* Default ->dumpit() handler for GET requests. Device iteration copied from
* rtnl_dump_ifinfo ( ) ; we have to be more careful about device hashtable
* persistence as we cannot guarantee to hold RTNL lock through the whole
* function as rtnetnlink does .
*/
static int ethnl_default_dumpit ( struct sk_buff * skb ,
struct netlink_callback * cb )
{
struct ethnl_dump_ctx * ctx = ethnl_dump_context ( cb ) ;
struct net * net = sock_net ( skb - > sk ) ;
int s_idx = ctx - > pos_idx ;
int h , idx = 0 ;
int ret = 0 ;
rtnl_lock ( ) ;
for ( h = ctx - > pos_hash ; h < NETDEV_HASHENTRIES ; h + + , s_idx = 0 ) {
struct hlist_head * head ;
struct net_device * dev ;
unsigned int seq ;
head = & net - > dev_index_head [ h ] ;
restart_chain :
seq = net - > dev_base_seq ;
cb - > seq = seq ;
idx = 0 ;
hlist_for_each_entry ( dev , head , index_hlist ) {
if ( idx < s_idx )
goto cont ;
dev_hold ( dev ) ;
rtnl_unlock ( ) ;
2020-07-09 13:11:50 +03:00
ret = ethnl_default_dump_one ( skb , dev , ctx , cb ) ;
2019-12-27 17:55:38 +03:00
dev_put ( dev ) ;
if ( ret < 0 ) {
if ( ret = = - EOPNOTSUPP )
goto lock_and_cont ;
if ( likely ( skb - > len ) )
ret = skb - > len ;
goto out ;
}
lock_and_cont :
rtnl_lock ( ) ;
if ( net - > dev_base_seq ! = seq ) {
s_idx = idx + 1 ;
goto restart_chain ;
}
cont :
idx + + ;
}
}
rtnl_unlock ( ) ;
out :
ctx - > pos_hash = h ;
ctx - > pos_idx = idx ;
nl_dump_check_consistent ( cb , nlmsg_hdr ( skb ) ) ;
return ret ;
}
/* generic ->start() handler for GET requests */
static int ethnl_default_start ( struct netlink_callback * cb )
{
struct ethnl_dump_ctx * ctx = ethnl_dump_context ( cb ) ;
struct ethnl_reply_data * reply_data ;
const struct ethnl_request_ops * ops ;
struct ethnl_req_info * req_info ;
struct genlmsghdr * ghdr ;
int ret ;
BUILD_BUG_ON ( sizeof ( * ctx ) > sizeof ( cb - > ctx ) ) ;
ghdr = nlmsg_data ( cb - > nlh ) ;
ops = ethnl_default_requests [ ghdr - > cmd ] ;
if ( WARN_ONCE ( ! ops , " cmd %u has no ethnl_request_ops \n " , ghdr - > cmd ) )
return - EOPNOTSUPP ;
req_info = kzalloc ( ops - > req_info_size , GFP_KERNEL ) ;
if ( ! req_info )
return - ENOMEM ;
reply_data = kmalloc ( ops - > reply_data_size , GFP_KERNEL ) ;
if ( ! reply_data ) {
2020-01-08 08:39:48 +03:00
ret = - ENOMEM ;
goto free_req_info ;
2019-12-27 17:55:38 +03:00
}
ret = ethnl_default_parse ( req_info , cb - > nlh , sock_net ( cb - > skb - > sk ) , ops ,
cb - > extack , false ) ;
if ( req_info - > dev ) {
/* We ignore device specification in dump requests but as the
* same parser as for non - dump ( doit ) requests is used , it
* would take reference to the device if it finds one
*/
dev_put ( req_info - > dev ) ;
req_info - > dev = NULL ;
}
if ( ret < 0 )
2020-01-08 08:39:48 +03:00
goto free_reply_data ;
2019-12-27 17:55:38 +03:00
ctx - > ops = ops ;
ctx - > req_info = req_info ;
ctx - > reply_data = reply_data ;
ctx - > pos_hash = 0 ;
ctx - > pos_idx = 0 ;
return 0 ;
2020-01-08 08:39:48 +03:00
free_reply_data :
kfree ( reply_data ) ;
free_req_info :
kfree ( req_info ) ;
return ret ;
2019-12-27 17:55:38 +03:00
}
/* default ->done() handler for GET requests */
static int ethnl_default_done ( struct netlink_callback * cb )
{
struct ethnl_dump_ctx * ctx = ethnl_dump_context ( cb ) ;
kfree ( ctx - > reply_data ) ;
kfree ( ctx - > req_info ) ;
return 0 ;
}
2019-12-27 17:55:58 +03:00
static const struct ethnl_request_ops *
ethnl_default_notify_ops [ ETHTOOL_MSG_KERNEL_MAX + 1 ] = {
2019-12-27 17:56:03 +03:00
[ ETHTOOL_MSG_LINKINFO_NTF ] = & ethnl_linkinfo_request_ops ,
2019-12-27 17:56:18 +03:00
[ ETHTOOL_MSG_LINKMODES_NTF ] = & ethnl_linkmodes_request_ops ,
2020-01-27 01:11:10 +03:00
[ ETHTOOL_MSG_DEBUG_NTF ] = & ethnl_debug_request_ops ,
2020-01-27 01:11:19 +03:00
[ ETHTOOL_MSG_WOL_NTF ] = & ethnl_wol_request_ops ,
2020-03-12 23:08:03 +03:00
[ ETHTOOL_MSG_FEATURES_NTF ] = & ethnl_features_request_ops ,
2020-03-12 23:08:18 +03:00
[ ETHTOOL_MSG_PRIVFLAGS_NTF ] = & ethnl_privflags_request_ops ,
2020-03-12 23:08:33 +03:00
[ ETHTOOL_MSG_RINGS_NTF ] = & ethnl_rings_request_ops ,
2020-03-12 23:08:48 +03:00
[ ETHTOOL_MSG_CHANNELS_NTF ] = & ethnl_channels_request_ops ,
2020-03-28 02:01:18 +03:00
[ ETHTOOL_MSG_COALESCE_NTF ] = & ethnl_coalesce_request_ops ,
2020-03-28 02:01:33 +03:00
[ ETHTOOL_MSG_PAUSE_NTF ] = & ethnl_pause_request_ops ,
2020-03-28 02:01:48 +03:00
[ ETHTOOL_MSG_EEE_NTF ] = & ethnl_eee_request_ops ,
2019-12-27 17:55:58 +03:00
} ;
/* default notification handler */
static void ethnl_default_notify ( struct net_device * dev , unsigned int cmd ,
const void * data )
{
struct ethnl_reply_data * reply_data ;
const struct ethnl_request_ops * ops ;
struct ethnl_req_info * req_info ;
struct sk_buff * skb ;
void * reply_payload ;
int reply_len ;
int ret ;
if ( WARN_ONCE ( cmd > ETHTOOL_MSG_KERNEL_MAX | |
! ethnl_default_notify_ops [ cmd ] ,
" unexpected notification type %u \n " , cmd ) )
return ;
ops = ethnl_default_notify_ops [ cmd ] ;
req_info = kzalloc ( ops - > req_info_size , GFP_KERNEL ) ;
if ( ! req_info )
return ;
reply_data = kmalloc ( ops - > reply_data_size , GFP_KERNEL ) ;
if ( ! reply_data ) {
kfree ( req_info ) ;
return ;
}
req_info - > dev = dev ;
req_info - > flags | = ETHTOOL_FLAG_COMPACT_BITSETS ;
ethnl_init_reply_data ( reply_data , ops , dev ) ;
ret = ops - > prepare_data ( req_info , reply_data , NULL ) ;
if ( ret < 0 )
goto err_cleanup ;
2020-01-08 08:41:25 +03:00
ret = ops - > reply_size ( req_info , reply_data ) ;
2019-12-27 17:55:58 +03:00
if ( ret < 0 )
goto err_cleanup ;
2020-05-10 22:04:09 +03:00
reply_len = ret + ethnl_reply_header_size ( ) ;
2019-12-27 17:55:58 +03:00
ret = - ENOMEM ;
skb = genlmsg_new ( reply_len , GFP_KERNEL ) ;
if ( ! skb )
goto err_cleanup ;
reply_payload = ethnl_bcastmsg_put ( skb , cmd ) ;
if ( ! reply_payload )
goto err_skb ;
ret = ethnl_fill_reply_header ( skb , dev , ops - > hdr_attr ) ;
if ( ret < 0 )
goto err_msg ;
ret = ops - > fill_reply ( skb , req_info , reply_data ) ;
if ( ret < 0 )
goto err_msg ;
if ( ops - > cleanup_data )
ops - > cleanup_data ( reply_data ) ;
genlmsg_end ( skb , reply_payload ) ;
kfree ( reply_data ) ;
kfree ( req_info ) ;
ethnl_multicast ( skb , dev ) ;
return ;
err_msg :
WARN_ONCE ( ret = = - EMSGSIZE ,
" calculated message payload length (%d) not sufficient \n " ,
reply_len ) ;
err_skb :
nlmsg_free ( skb ) ;
err_cleanup :
if ( ops - > cleanup_data )
ops - > cleanup_data ( reply_data ) ;
kfree ( reply_data ) ;
kfree ( req_info ) ;
return ;
}
2019-12-27 17:55:33 +03:00
/* notifications */
typedef void ( * ethnl_notify_handler_t ) ( struct net_device * dev , unsigned int cmd ,
const void * data ) ;
static const ethnl_notify_handler_t ethnl_notify_handlers [ ] = {
2019-12-27 17:56:03 +03:00
[ ETHTOOL_MSG_LINKINFO_NTF ] = ethnl_default_notify ,
2019-12-27 17:56:18 +03:00
[ ETHTOOL_MSG_LINKMODES_NTF ] = ethnl_default_notify ,
2020-01-27 01:11:10 +03:00
[ ETHTOOL_MSG_DEBUG_NTF ] = ethnl_default_notify ,
2020-01-27 01:11:19 +03:00
[ ETHTOOL_MSG_WOL_NTF ] = ethnl_default_notify ,
2020-03-12 23:08:03 +03:00
[ ETHTOOL_MSG_FEATURES_NTF ] = ethnl_default_notify ,
2020-03-12 23:08:18 +03:00
[ ETHTOOL_MSG_PRIVFLAGS_NTF ] = ethnl_default_notify ,
2020-03-12 23:08:33 +03:00
[ ETHTOOL_MSG_RINGS_NTF ] = ethnl_default_notify ,
2020-03-12 23:08:48 +03:00
[ ETHTOOL_MSG_CHANNELS_NTF ] = ethnl_default_notify ,
2020-03-28 02:01:18 +03:00
[ ETHTOOL_MSG_COALESCE_NTF ] = ethnl_default_notify ,
2020-03-28 02:01:33 +03:00
[ ETHTOOL_MSG_PAUSE_NTF ] = ethnl_default_notify ,
2020-03-28 02:01:48 +03:00
[ ETHTOOL_MSG_EEE_NTF ] = ethnl_default_notify ,
2019-12-27 17:55:33 +03:00
} ;
void ethtool_notify ( struct net_device * dev , unsigned int cmd , const void * data )
{
if ( unlikely ( ! ethnl_ok ) )
return ;
ASSERT_RTNL ( ) ;
if ( likely ( cmd < ARRAY_SIZE ( ethnl_notify_handlers ) & &
ethnl_notify_handlers [ cmd ] ) )
ethnl_notify_handlers [ cmd ] ( dev , cmd , data ) ;
else
WARN_ONCE ( 1 , " notification %u not implemented (dev=%s) \n " ,
cmd , netdev_name ( dev ) ) ;
}
EXPORT_SYMBOL ( ethtool_notify ) ;
2020-03-12 23:08:03 +03:00
static void ethnl_notify_features ( struct netdev_notifier_info * info )
{
struct net_device * dev = netdev_notifier_info_to_dev ( info ) ;
ethtool_notify ( dev , ETHTOOL_MSG_FEATURES_NTF , NULL ) ;
}
static int ethnl_netdev_event ( struct notifier_block * this , unsigned long event ,
void * ptr )
{
switch ( event ) {
case NETDEV_FEAT_CHANGE :
ethnl_notify_features ( ptr ) ;
break ;
}
return NOTIFY_DONE ;
}
static struct notifier_block ethnl_netdev_notifier = {
. notifier_call = ethnl_netdev_event ,
} ;
2019-12-27 17:55:18 +03:00
/* genetlink setup */
static const struct genl_ops ethtool_genl_ops [ ] = {
2019-12-27 17:55:43 +03:00
{
. cmd = ETHTOOL_MSG_STRSET_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2019-12-27 17:55:48 +03:00
{
. cmd = ETHTOOL_MSG_LINKINFO_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2019-12-27 17:55:53 +03:00
{
. cmd = ETHTOOL_MSG_LINKINFO_SET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_set_linkinfo ,
} ,
2019-12-27 17:56:08 +03:00
{
. cmd = ETHTOOL_MSG_LINKMODES_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2019-12-27 17:56:13 +03:00
{
. cmd = ETHTOOL_MSG_LINKMODES_SET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_set_linkmodes ,
} ,
2019-12-27 17:56:23 +03:00
{
. cmd = ETHTOOL_MSG_LINKSTATE_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2020-01-27 01:11:04 +03:00
{
. cmd = ETHTOOL_MSG_DEBUG_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2020-01-27 01:11:07 +03:00
{
. cmd = ETHTOOL_MSG_DEBUG_SET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_set_debug ,
} ,
2020-01-27 01:11:13 +03:00
{
. cmd = ETHTOOL_MSG_WOL_GET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2020-01-27 01:11:16 +03:00
{
. cmd = ETHTOOL_MSG_WOL_SET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_set_wol ,
} ,
2020-03-12 23:07:48 +03:00
{
. cmd = ETHTOOL_MSG_FEATURES_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2020-03-12 23:07:58 +03:00
{
. cmd = ETHTOOL_MSG_FEATURES_SET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_set_features ,
} ,
2020-03-12 23:08:08 +03:00
{
. cmd = ETHTOOL_MSG_PRIVFLAGS_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2020-03-12 23:08:13 +03:00
{
. cmd = ETHTOOL_MSG_PRIVFLAGS_SET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_set_privflags ,
} ,
2020-03-12 23:08:23 +03:00
{
. cmd = ETHTOOL_MSG_RINGS_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2020-03-12 23:08:28 +03:00
{
. cmd = ETHTOOL_MSG_RINGS_SET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_set_rings ,
} ,
2020-03-12 23:08:38 +03:00
{
. cmd = ETHTOOL_MSG_CHANNELS_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2020-03-12 23:08:43 +03:00
{
. cmd = ETHTOOL_MSG_CHANNELS_SET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_set_channels ,
} ,
2020-03-28 02:01:08 +03:00
{
. cmd = ETHTOOL_MSG_COALESCE_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2020-03-28 02:01:13 +03:00
{
. cmd = ETHTOOL_MSG_COALESCE_SET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_set_coalesce ,
} ,
2020-03-28 02:01:23 +03:00
{
. cmd = ETHTOOL_MSG_PAUSE_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2020-03-28 02:01:28 +03:00
{
. cmd = ETHTOOL_MSG_PAUSE_SET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_set_pause ,
} ,
2020-03-28 02:01:38 +03:00
{
. cmd = ETHTOOL_MSG_EEE_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2020-03-28 02:01:43 +03:00
{
. cmd = ETHTOOL_MSG_EEE_SET ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_set_eee ,
} ,
2020-03-28 02:01:58 +03:00
{
. cmd = ETHTOOL_MSG_TSINFO_GET ,
. doit = ethnl_default_doit ,
. start = ethnl_default_start ,
. dumpit = ethnl_default_dumpit ,
. done = ethnl_default_done ,
} ,
2020-05-10 22:12:33 +03:00
{
. cmd = ETHTOOL_MSG_CABLE_TEST_ACT ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_act_cable_test ,
} ,
2020-05-27 01:21:38 +03:00
{
. cmd = ETHTOOL_MSG_CABLE_TEST_TDR_ACT ,
. flags = GENL_UNS_ADMIN_PERM ,
. doit = ethnl_act_cable_test_tdr ,
} ,
2020-07-10 03:42:47 +03:00
{
. cmd = ETHTOOL_MSG_TUNNEL_INFO_GET ,
. doit = ethnl_tunnel_info_doit ,
. start = ethnl_tunnel_info_start ,
. dumpit = ethnl_tunnel_info_dumpit ,
} ,
2019-12-27 17:55:18 +03:00
} ;
2019-12-27 17:55:33 +03:00
static const struct genl_multicast_group ethtool_nl_mcgrps [ ] = {
[ ETHNL_MCGRP_MONITOR ] = { . name = ETHTOOL_MCGRP_MONITOR_NAME } ,
} ;
2020-09-29 03:58:41 +03:00
static struct genl_family ethtool_genl_family __ro_after_init = {
2019-12-27 17:55:18 +03:00
. name = ETHTOOL_GENL_NAME ,
. version = ETHTOOL_GENL_VERSION ,
. netnsok = true ,
. parallel_ops = true ,
. ops = ethtool_genl_ops ,
. n_ops = ARRAY_SIZE ( ethtool_genl_ops ) ,
2019-12-27 17:55:33 +03:00
. mcgrps = ethtool_nl_mcgrps ,
. n_mcgrps = ARRAY_SIZE ( ethtool_nl_mcgrps ) ,
2019-12-27 17:55:18 +03:00
} ;
/* module setup */
static int __init ethnl_init ( void )
{
int ret ;
ret = genl_register_family ( & ethtool_genl_family ) ;
if ( WARN ( ret < 0 , " ethtool: genetlink family registration failed " ) )
return ret ;
2019-12-27 17:55:33 +03:00
ethnl_ok = true ;
2019-12-27 17:55:18 +03:00
2020-03-12 23:08:03 +03:00
ret = register_netdevice_notifier ( & ethnl_netdev_notifier ) ;
WARN ( ret < 0 , " ethtool: net device notifier registration failed " ) ;
return ret ;
2019-12-27 17:55:18 +03:00
}
subsys_initcall ( ethnl_init ) ;