2020-03-12 23:07:48 +03:00
// SPDX-License-Identifier: GPL-2.0-only
# include "netlink.h"
# include "common.h"
# include "bitset.h"
struct features_req_info {
struct ethnl_req_info base ;
} ;
struct features_reply_data {
struct ethnl_reply_data base ;
u32 hw [ ETHTOOL_DEV_FEATURE_WORDS ] ;
u32 wanted [ ETHTOOL_DEV_FEATURE_WORDS ] ;
u32 active [ ETHTOOL_DEV_FEATURE_WORDS ] ;
u32 nochange [ ETHTOOL_DEV_FEATURE_WORDS ] ;
u32 all [ ETHTOOL_DEV_FEATURE_WORDS ] ;
} ;
# define FEATURES_REPDATA(__reply_base) \
container_of ( __reply_base , struct features_reply_data , base )
static const struct nla_policy
features_get_policy [ ETHTOOL_A_FEATURES_MAX + 1 ] = {
[ ETHTOOL_A_FEATURES_UNSPEC ] = { . type = NLA_REJECT } ,
[ ETHTOOL_A_FEATURES_HEADER ] = { . type = NLA_NESTED } ,
[ ETHTOOL_A_FEATURES_HW ] = { . type = NLA_REJECT } ,
[ ETHTOOL_A_FEATURES_WANTED ] = { . type = NLA_REJECT } ,
[ ETHTOOL_A_FEATURES_ACTIVE ] = { . type = NLA_REJECT } ,
[ ETHTOOL_A_FEATURES_NOCHANGE ] = { . type = NLA_REJECT } ,
} ;
static void ethnl_features_to_bitmap32 ( u32 * dest , netdev_features_t src )
{
unsigned int i ;
for ( i = 0 ; i < ETHTOOL_DEV_FEATURE_WORDS ; i + + )
dest [ i ] = src > > ( 32 * i ) ;
}
static int features_prepare_data ( const struct ethnl_req_info * req_base ,
struct ethnl_reply_data * reply_base ,
struct genl_info * info )
{
struct features_reply_data * data = FEATURES_REPDATA ( reply_base ) ;
struct net_device * dev = reply_base - > dev ;
netdev_features_t all_features ;
ethnl_features_to_bitmap32 ( data - > hw , dev - > hw_features ) ;
ethnl_features_to_bitmap32 ( data - > wanted , dev - > wanted_features ) ;
ethnl_features_to_bitmap32 ( data - > active , dev - > features ) ;
ethnl_features_to_bitmap32 ( data - > nochange , NETIF_F_NEVER_CHANGE ) ;
all_features = GENMASK_ULL ( NETDEV_FEATURE_COUNT - 1 , 0 ) ;
ethnl_features_to_bitmap32 ( data - > all , all_features ) ;
return 0 ;
}
static int features_reply_size ( const struct ethnl_req_info * req_base ,
const struct ethnl_reply_data * reply_base )
{
const struct features_reply_data * data = FEATURES_REPDATA ( reply_base ) ;
bool compact = req_base - > flags & ETHTOOL_FLAG_COMPACT_BITSETS ;
unsigned int len = 0 ;
int ret ;
ret = ethnl_bitset32_size ( data - > hw , data - > all , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
if ( ret < 0 )
return ret ;
len + = ret ;
ret = ethnl_bitset32_size ( data - > wanted , NULL , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
if ( ret < 0 )
return ret ;
len + = ret ;
ret = ethnl_bitset32_size ( data - > active , NULL , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
if ( ret < 0 )
return ret ;
len + = ret ;
ret = ethnl_bitset32_size ( data - > nochange , NULL , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
if ( ret < 0 )
return ret ;
len + = ret ;
return len ;
}
static int features_fill_reply ( struct sk_buff * skb ,
const struct ethnl_req_info * req_base ,
const struct ethnl_reply_data * reply_base )
{
const struct features_reply_data * data = FEATURES_REPDATA ( reply_base ) ;
bool compact = req_base - > flags & ETHTOOL_FLAG_COMPACT_BITSETS ;
int ret ;
ret = ethnl_put_bitset32 ( skb , ETHTOOL_A_FEATURES_HW , data - > hw ,
data - > all , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
if ( ret < 0 )
return ret ;
ret = ethnl_put_bitset32 ( skb , ETHTOOL_A_FEATURES_WANTED , data - > wanted ,
NULL , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
if ( ret < 0 )
return ret ;
ret = ethnl_put_bitset32 ( skb , ETHTOOL_A_FEATURES_ACTIVE , data - > active ,
NULL , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
if ( ret < 0 )
return ret ;
return ethnl_put_bitset32 ( skb , ETHTOOL_A_FEATURES_NOCHANGE ,
data - > nochange , NULL , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
}
const struct ethnl_request_ops ethnl_features_request_ops = {
. request_cmd = ETHTOOL_MSG_FEATURES_GET ,
. reply_cmd = ETHTOOL_MSG_FEATURES_GET_REPLY ,
. hdr_attr = ETHTOOL_A_FEATURES_HEADER ,
. max_attr = ETHTOOL_A_FEATURES_MAX ,
. req_info_size = sizeof ( struct features_req_info ) ,
. reply_data_size = sizeof ( struct features_reply_data ) ,
. request_policy = features_get_policy ,
. prepare_data = features_prepare_data ,
. reply_size = features_reply_size ,
. fill_reply = features_fill_reply ,
} ;
2020-03-12 23:07:58 +03:00
/* FEATURES_SET */
static const struct nla_policy
features_set_policy [ ETHTOOL_A_FEATURES_MAX + 1 ] = {
[ ETHTOOL_A_FEATURES_UNSPEC ] = { . type = NLA_REJECT } ,
[ ETHTOOL_A_FEATURES_HEADER ] = { . type = NLA_NESTED } ,
[ ETHTOOL_A_FEATURES_HW ] = { . type = NLA_REJECT } ,
[ ETHTOOL_A_FEATURES_WANTED ] = { . type = NLA_NESTED } ,
[ ETHTOOL_A_FEATURES_ACTIVE ] = { . type = NLA_REJECT } ,
[ ETHTOOL_A_FEATURES_NOCHANGE ] = { . type = NLA_REJECT } ,
} ;
static void ethnl_features_to_bitmap ( unsigned long * dest , netdev_features_t val )
{
const unsigned int words = BITS_TO_LONGS ( NETDEV_FEATURE_COUNT ) ;
unsigned int i ;
bitmap_zero ( dest , NETDEV_FEATURE_COUNT ) ;
for ( i = 0 ; i < words ; i + + )
dest [ i ] = ( unsigned long ) ( val > > ( i * BITS_PER_LONG ) ) ;
}
static netdev_features_t ethnl_bitmap_to_features ( unsigned long * src )
{
const unsigned int nft_bits = sizeof ( netdev_features_t ) * BITS_PER_BYTE ;
const unsigned int words = BITS_TO_LONGS ( NETDEV_FEATURE_COUNT ) ;
netdev_features_t ret = 0 ;
unsigned int i ;
for ( i = 0 ; i < words ; i + + )
ret | = ( netdev_features_t ) ( src [ i ] ) < < ( i * BITS_PER_LONG ) ;
ret & = ~ ( netdev_features_t ) 0 > > ( nft_bits - NETDEV_FEATURE_COUNT ) ;
return ret ;
}
static int features_send_reply ( struct net_device * dev , struct genl_info * info ,
const unsigned long * wanted ,
const unsigned long * wanted_mask ,
const unsigned long * active ,
const unsigned long * active_mask , bool compact )
{
struct sk_buff * rskb ;
void * reply_payload ;
int reply_len = 0 ;
int ret ;
reply_len = ethnl_reply_header_size ( ) ;
ret = ethnl_bitset_size ( wanted , wanted_mask , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
if ( ret < 0 )
goto err ;
reply_len + = ret ;
ret = ethnl_bitset_size ( active , active_mask , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
if ( ret < 0 )
goto err ;
reply_len + = ret ;
ret = - ENOMEM ;
rskb = ethnl_reply_init ( reply_len , dev , ETHTOOL_MSG_FEATURES_SET_REPLY ,
ETHTOOL_A_FEATURES_HEADER , info ,
& reply_payload ) ;
if ( ! rskb )
goto err ;
ret = ethnl_put_bitset ( rskb , ETHTOOL_A_FEATURES_WANTED , wanted ,
wanted_mask , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
if ( ret < 0 )
goto nla_put_failure ;
ret = ethnl_put_bitset ( rskb , ETHTOOL_A_FEATURES_ACTIVE , active ,
active_mask , NETDEV_FEATURE_COUNT ,
netdev_features_strings , compact ) ;
if ( ret < 0 )
goto nla_put_failure ;
genlmsg_end ( rskb , reply_payload ) ;
ret = genlmsg_reply ( rskb , info ) ;
return ret ;
nla_put_failure :
nlmsg_free ( rskb ) ;
WARN_ONCE ( 1 , " calculated message payload length (%d) not sufficient \n " ,
reply_len ) ;
err :
GENL_SET_ERR_MSG ( info , " failed to send reply message " ) ;
return ret ;
}
int ethnl_set_features ( struct sk_buff * skb , struct genl_info * info )
{
DECLARE_BITMAP ( wanted_diff_mask , NETDEV_FEATURE_COUNT ) ;
DECLARE_BITMAP ( active_diff_mask , NETDEV_FEATURE_COUNT ) ;
DECLARE_BITMAP ( old_active , NETDEV_FEATURE_COUNT ) ;
2020-08-17 16:34:05 +03:00
DECLARE_BITMAP ( old_wanted , NETDEV_FEATURE_COUNT ) ;
2020-03-12 23:07:58 +03:00
DECLARE_BITMAP ( new_active , NETDEV_FEATURE_COUNT ) ;
2020-08-17 16:34:05 +03:00
DECLARE_BITMAP ( new_wanted , NETDEV_FEATURE_COUNT ) ;
2020-03-12 23:07:58 +03:00
DECLARE_BITMAP ( req_wanted , NETDEV_FEATURE_COUNT ) ;
DECLARE_BITMAP ( req_mask , NETDEV_FEATURE_COUNT ) ;
struct nlattr * tb [ ETHTOOL_A_FEATURES_MAX + 1 ] ;
struct ethnl_req_info req_info = { } ;
struct net_device * dev ;
2020-03-12 23:08:03 +03:00
bool mod ;
2020-03-12 23:07:58 +03:00
int ret ;
ret = nlmsg_parse ( info - > nlhdr , GENL_HDRLEN , tb ,
ETHTOOL_A_FEATURES_MAX , features_set_policy ,
info - > extack ) ;
if ( ret < 0 )
return ret ;
if ( ! tb [ ETHTOOL_A_FEATURES_WANTED ] )
return - EINVAL ;
ret = ethnl_parse_header_dev_get ( & req_info ,
tb [ ETHTOOL_A_FEATURES_HEADER ] ,
genl_info_net ( info ) , info - > extack ,
true ) ;
if ( ret < 0 )
return ret ;
dev = req_info . dev ;
rtnl_lock ( ) ;
ethnl_features_to_bitmap ( old_active , dev - > features ) ;
2020-08-17 16:34:05 +03:00
ethnl_features_to_bitmap ( old_wanted , dev - > wanted_features ) ;
2020-03-12 23:07:58 +03:00
ret = ethnl_parse_bitset ( req_wanted , req_mask , NETDEV_FEATURE_COUNT ,
tb [ ETHTOOL_A_FEATURES_WANTED ] ,
netdev_features_strings , info - > extack ) ;
if ( ret < 0 )
goto out_rtnl ;
if ( ethnl_bitmap_to_features ( req_mask ) & ~ NETIF_F_ETHTOOL_BITS ) {
GENL_SET_ERR_MSG ( info , " attempt to change non-ethtool features " ) ;
ret = - EINVAL ;
goto out_rtnl ;
}
2020-08-17 16:34:05 +03:00
/* set req_wanted bits not in req_mask from old_wanted */
2020-03-12 23:07:58 +03:00
bitmap_and ( req_wanted , req_wanted , req_mask , NETDEV_FEATURE_COUNT ) ;
2020-08-17 16:34:05 +03:00
bitmap_andnot ( new_wanted , old_wanted , req_mask , NETDEV_FEATURE_COUNT ) ;
bitmap_or ( req_wanted , new_wanted , req_wanted , NETDEV_FEATURE_COUNT ) ;
2020-08-17 16:34:07 +03:00
if ( ! bitmap_equal ( req_wanted , old_wanted , NETDEV_FEATURE_COUNT ) ) {
dev - > wanted_features & = ~ dev - > hw_features ;
dev - > wanted_features | = ethnl_bitmap_to_features ( req_wanted ) & dev - > hw_features ;
__netdev_update_features ( dev ) ;
2020-03-12 23:07:58 +03:00
}
ethnl_features_to_bitmap ( new_active , dev - > features ) ;
2020-03-12 23:08:03 +03:00
mod = ! bitmap_equal ( old_active , new_active , NETDEV_FEATURE_COUNT ) ;
2020-03-12 23:07:58 +03:00
ret = 0 ;
if ( ! ( req_info . flags & ETHTOOL_FLAG_OMIT_REPLY ) ) {
bool compact = req_info . flags & ETHTOOL_FLAG_COMPACT_BITSETS ;
bitmap_xor ( wanted_diff_mask , req_wanted , new_active ,
NETDEV_FEATURE_COUNT ) ;
bitmap_xor ( active_diff_mask , old_active , new_active ,
NETDEV_FEATURE_COUNT ) ;
bitmap_and ( wanted_diff_mask , wanted_diff_mask , req_mask ,
NETDEV_FEATURE_COUNT ) ;
bitmap_and ( req_wanted , req_wanted , wanted_diff_mask ,
NETDEV_FEATURE_COUNT ) ;
bitmap_and ( new_active , new_active , active_diff_mask ,
NETDEV_FEATURE_COUNT ) ;
ret = features_send_reply ( dev , info , req_wanted ,
wanted_diff_mask , new_active ,
active_diff_mask , compact ) ;
}
2020-03-12 23:08:03 +03:00
if ( mod )
ethtool_notify ( dev , ETHTOOL_MSG_FEATURES_NTF , NULL ) ;
2020-03-12 23:07:58 +03:00
out_rtnl :
rtnl_unlock ( ) ;
dev_put ( dev ) ;
return ret ;
}