2019-12-27 17:55:43 +03:00
// SPDX-License-Identifier: GPL-2.0-only
# include <linux/ethtool.h>
# include <linux/phy.h>
# include "netlink.h"
# include "common.h"
struct strset_info {
bool per_dev ;
bool free_strings ;
unsigned int count ;
const char ( * strings ) [ ETH_GSTRING_LEN ] ;
} ;
static const struct strset_info info_template [ ] = {
[ ETH_SS_TEST ] = {
. per_dev = true ,
} ,
[ ETH_SS_STATS ] = {
. per_dev = true ,
} ,
[ ETH_SS_PRIV_FLAGS ] = {
. per_dev = true ,
} ,
[ ETH_SS_FEATURES ] = {
. per_dev = false ,
. count = ARRAY_SIZE ( netdev_features_strings ) ,
. strings = netdev_features_strings ,
} ,
[ ETH_SS_RSS_HASH_FUNCS ] = {
. per_dev = false ,
. count = ARRAY_SIZE ( rss_hash_func_strings ) ,
. strings = rss_hash_func_strings ,
} ,
[ ETH_SS_TUNABLES ] = {
. per_dev = false ,
. count = ARRAY_SIZE ( tunable_strings ) ,
. strings = tunable_strings ,
} ,
[ ETH_SS_PHY_STATS ] = {
. per_dev = true ,
} ,
[ ETH_SS_PHY_TUNABLES ] = {
. per_dev = false ,
. count = ARRAY_SIZE ( phy_tunable_strings ) ,
. strings = phy_tunable_strings ,
} ,
[ ETH_SS_LINK_MODES ] = {
. per_dev = false ,
. count = __ETHTOOL_LINK_MODE_MASK_NBITS ,
. strings = link_mode_names ,
} ,
2020-01-27 01:11:04 +03:00
[ ETH_SS_MSG_CLASSES ] = {
. per_dev = false ,
. count = NETIF_MSG_CLASS_COUNT ,
. strings = netif_msg_class_names ,
} ,
2020-01-27 01:11:13 +03:00
[ ETH_SS_WOL_MODES ] = {
. per_dev = false ,
. count = WOL_MODE_COUNT ,
. strings = wol_mode_names ,
} ,
2020-03-28 02:01:53 +03:00
[ ETH_SS_SOF_TIMESTAMPING ] = {
. per_dev = false ,
. count = __SOF_TIMESTAMPING_CNT ,
. strings = sof_timestamping_names ,
} ,
[ ETH_SS_TS_TX_TYPES ] = {
. per_dev = false ,
. count = __HWTSTAMP_TX_CNT ,
. strings = ts_tx_type_names ,
} ,
[ ETH_SS_TS_RX_FILTERS ] = {
. per_dev = false ,
. count = __HWTSTAMP_FILTER_CNT ,
. strings = ts_rx_filter_names ,
} ,
2020-07-10 03:42:47 +03:00
[ ETH_SS_UDP_TUNNEL_TYPES ] = {
. per_dev = false ,
. count = __ETHTOOL_UDP_TUNNEL_TYPE_CNT ,
. strings = udp_tunnel_type_names ,
} ,
2021-04-16 22:27:39 +03:00
[ ETH_SS_STATS_STD ] = {
. per_dev = false ,
. count = __ETHTOOL_STATS_CNT ,
. strings = stats_std_names ,
} ,
[ ETH_SS_STATS_ETH_PHY ] = {
. per_dev = false ,
. count = __ETHTOOL_A_STATS_ETH_PHY_CNT ,
. strings = stats_eth_phy_names ,
} ,
2021-04-16 22:27:40 +03:00
[ ETH_SS_STATS_ETH_MAC ] = {
. per_dev = false ,
. count = __ETHTOOL_A_STATS_ETH_MAC_CNT ,
. strings = stats_eth_mac_names ,
} ,
2021-04-16 22:27:41 +03:00
[ ETH_SS_STATS_ETH_CTRL ] = {
. per_dev = false ,
. count = __ETHTOOL_A_STATS_ETH_CTRL_CNT ,
. strings = stats_eth_ctrl_names ,
} ,
2021-04-16 22:27:42 +03:00
[ ETH_SS_STATS_RMON ] = {
. per_dev = false ,
. count = __ETHTOOL_A_STATS_RMON_CNT ,
. strings = stats_rmon_names ,
} ,
2019-12-27 17:55:43 +03:00
} ;
struct strset_req_info {
struct ethnl_req_info base ;
u32 req_ids ;
bool counts_only ;
} ;
# define STRSET_REQINFO(__req_base) \
container_of ( __req_base , struct strset_req_info , base )
struct strset_reply_data {
struct ethnl_reply_data base ;
struct strset_info sets [ ETH_SS_COUNT ] ;
} ;
# define STRSET_REPDATA(__reply_base) \
container_of ( __reply_base , struct strset_reply_data , base )
2020-10-06 01:07:35 +03:00
const struct nla_policy ethnl_strset_get_policy [ ] = {
2020-10-06 01:07:36 +03:00
[ ETHTOOL_A_STRSET_HEADER ] =
NLA_POLICY_NESTED ( ethnl_header_policy ) ,
2019-12-27 17:55:43 +03:00
[ ETHTOOL_A_STRSET_STRINGSETS ] = { . type = NLA_NESTED } ,
2020-10-07 13:53:50 +03:00
[ ETHTOOL_A_STRSET_COUNTS_ONLY ] = { . type = NLA_FLAG } ,
2019-12-27 17:55:43 +03:00
} ;
2020-10-06 01:07:35 +03:00
static const struct nla_policy get_stringset_policy [ ] = {
2019-12-27 17:55:43 +03:00
[ ETHTOOL_A_STRINGSET_ID ] = { . type = NLA_U32 } ,
} ;
/**
* strset_include ( ) - test if a string set should be included in reply
2020-01-27 01:11:01 +03:00
* @ info : parsed client request
2019-12-27 17:55:43 +03:00
* @ data : pointer to request data structure
* @ id : id of string set to check ( ETH_SS_ * constants )
*/
static bool strset_include ( const struct strset_req_info * info ,
const struct strset_reply_data * data , u32 id )
{
bool per_dev ;
BUILD_BUG_ON ( ETH_SS_COUNT > = BITS_PER_BYTE * sizeof ( info - > req_ids ) ) ;
if ( info - > req_ids )
return info - > req_ids & ( 1U < < id ) ;
per_dev = data - > sets [ id ] . per_dev ;
if ( ! per_dev & & ! data - > sets [ id ] . strings )
return false ;
return data - > base . dev ? per_dev : ! per_dev ;
}
static int strset_get_id ( const struct nlattr * nest , u32 * val ,
struct netlink_ext_ack * extack )
{
2020-10-06 01:07:35 +03:00
struct nlattr * tb [ ARRAY_SIZE ( get_stringset_policy ) ] ;
2019-12-27 17:55:43 +03:00
int ret ;
2020-10-06 01:07:35 +03:00
ret = nla_parse_nested ( tb , ARRAY_SIZE ( get_stringset_policy ) - 1 , nest ,
2019-12-27 17:55:43 +03:00
get_stringset_policy , extack ) ;
if ( ret < 0 )
return ret ;
if ( ! tb [ ETHTOOL_A_STRINGSET_ID ] )
return - EINVAL ;
* val = nla_get_u32 ( tb [ ETHTOOL_A_STRINGSET_ID ] ) ;
return 0 ;
}
2020-10-06 01:07:35 +03:00
static const struct nla_policy strset_stringsets_policy [ ] = {
2019-12-27 17:55:43 +03:00
[ ETHTOOL_A_STRINGSETS_STRINGSET ] = { . type = NLA_NESTED } ,
} ;
static int strset_parse_request ( struct ethnl_req_info * req_base ,
struct nlattr * * tb ,
struct netlink_ext_ack * extack )
{
struct strset_req_info * req_info = STRSET_REQINFO ( req_base ) ;
struct nlattr * nest = tb [ ETHTOOL_A_STRSET_STRINGSETS ] ;
struct nlattr * attr ;
int rem , ret ;
if ( ! nest )
return 0 ;
2020-10-06 01:07:35 +03:00
ret = nla_validate_nested ( nest ,
ARRAY_SIZE ( strset_stringsets_policy ) - 1 ,
2019-12-27 17:55:43 +03:00
strset_stringsets_policy , extack ) ;
if ( ret < 0 )
return ret ;
req_info - > counts_only = tb [ ETHTOOL_A_STRSET_COUNTS_ONLY ] ;
nla_for_each_nested ( attr , nest , rem ) {
u32 id ;
if ( WARN_ONCE ( nla_type ( attr ) ! = ETHTOOL_A_STRINGSETS_STRINGSET ,
" unexpected attrtype %u in ETHTOOL_A_STRSET_STRINGSETS \n " ,
nla_type ( attr ) ) )
return - EINVAL ;
ret = strset_get_id ( attr , & id , extack ) ;
if ( ret < 0 )
return ret ;
2020-12-14 16:25:01 +03:00
if ( id > = ETH_SS_COUNT ) {
2019-12-27 17:55:43 +03:00
NL_SET_ERR_MSG_ATTR ( extack , attr ,
" unknown string set id " ) ;
return - EOPNOTSUPP ;
}
req_info - > req_ids | = ( 1U < < id ) ;
}
return 0 ;
}
static void strset_cleanup_data ( struct ethnl_reply_data * reply_base )
{
struct strset_reply_data * data = STRSET_REPDATA ( reply_base ) ;
unsigned int i ;
for ( i = 0 ; i < ETH_SS_COUNT ; i + + )
if ( data - > sets [ i ] . free_strings ) {
kfree ( data - > sets [ i ] . strings ) ;
data - > sets [ i ] . strings = NULL ;
data - > sets [ i ] . free_strings = false ;
}
}
static int strset_prepare_set ( struct strset_info * info , struct net_device * dev ,
unsigned int id , bool counts_only )
{
2020-07-08 19:46:25 +03:00
const struct ethtool_phy_ops * phy_ops = ethtool_phy_ops ;
2019-12-27 17:55:43 +03:00
const struct ethtool_ops * ops = dev - > ethtool_ops ;
void * strings ;
int count , ret ;
if ( id = = ETH_SS_PHY_STATS & & dev - > phydev & &
2020-07-08 19:46:25 +03:00
! ops - > get_ethtool_phy_stats & & phy_ops & &
phy_ops - > get_sset_count )
ret = phy_ops - > get_sset_count ( dev - > phydev ) ;
2019-12-27 17:55:43 +03:00
else if ( ops - > get_sset_count & & ops - > get_strings )
ret = ops - > get_sset_count ( dev , id ) ;
else
ret = - EOPNOTSUPP ;
if ( ret < = 0 ) {
info - > count = 0 ;
return 0 ;
}
count = ret ;
if ( ! counts_only ) {
strings = kcalloc ( count , ETH_GSTRING_LEN , GFP_KERNEL ) ;
if ( ! strings )
return - ENOMEM ;
if ( id = = ETH_SS_PHY_STATS & & dev - > phydev & &
2020-07-08 19:46:25 +03:00
! ops - > get_ethtool_phy_stats & & phy_ops & &
phy_ops - > get_strings )
phy_ops - > get_strings ( dev - > phydev , strings ) ;
2019-12-27 17:55:43 +03:00
else
ops - > get_strings ( dev , id , strings ) ;
info - > strings = strings ;
info - > free_strings = true ;
}
info - > count = count ;
return 0 ;
}
static int strset_prepare_data ( const struct ethnl_req_info * req_base ,
struct ethnl_reply_data * reply_base ,
struct genl_info * info )
{
const struct strset_req_info * req_info = STRSET_REQINFO ( req_base ) ;
struct strset_reply_data * data = STRSET_REPDATA ( reply_base ) ;
struct net_device * dev = reply_base - > dev ;
unsigned int i ;
int ret ;
BUILD_BUG_ON ( ARRAY_SIZE ( info_template ) ! = ETH_SS_COUNT ) ;
memcpy ( & data - > sets , & info_template , sizeof ( data - > sets ) ) ;
if ( ! dev ) {
for ( i = 0 ; i < ETH_SS_COUNT ; i + + ) {
if ( ( req_info - > req_ids & ( 1U < < i ) ) & &
data - > sets [ i ] . per_dev ) {
if ( info )
GENL_SET_ERR_MSG ( info , " requested per device strings without dev " ) ;
return - EINVAL ;
}
}
2020-01-08 08:42:36 +03:00
return 0 ;
2019-12-27 17:55:43 +03:00
}
ret = ethnl_ops_begin ( dev ) ;
if ( ret < 0 )
goto err_strset ;
for ( i = 0 ; i < ETH_SS_COUNT ; i + + ) {
if ( ! strset_include ( req_info , data , i ) | |
! data - > sets [ i ] . per_dev )
continue ;
ret = strset_prepare_set ( & data - > sets [ i ] , dev , i ,
req_info - > counts_only ) ;
if ( ret < 0 )
goto err_ops ;
}
ethnl_ops_complete ( dev ) ;
return 0 ;
err_ops :
ethnl_ops_complete ( dev ) ;
err_strset :
strset_cleanup_data ( reply_base ) ;
return ret ;
}
/* calculate size of ETHTOOL_A_STRSET_STRINGSET nest for one string set */
static int strset_set_size ( const struct strset_info * info , bool counts_only )
{
unsigned int len = 0 ;
unsigned int i ;
if ( info - > count = = 0 )
return 0 ;
if ( counts_only )
return nla_total_size ( 2 * nla_total_size ( sizeof ( u32 ) ) ) ;
for ( i = 0 ; i < info - > count ; i + + ) {
const char * str = info - > strings [ i ] ;
/* ETHTOOL_A_STRING_INDEX, ETHTOOL_A_STRING_VALUE, nest */
len + = nla_total_size ( nla_total_size ( sizeof ( u32 ) ) +
ethnl_strz_size ( str ) ) ;
}
/* ETHTOOL_A_STRINGSET_ID, ETHTOOL_A_STRINGSET_COUNT */
len = 2 * nla_total_size ( sizeof ( u32 ) ) + nla_total_size ( len ) ;
return nla_total_size ( len ) ;
}
static int strset_reply_size ( const struct ethnl_req_info * req_base ,
const struct ethnl_reply_data * reply_base )
{
const struct strset_req_info * req_info = STRSET_REQINFO ( req_base ) ;
const struct strset_reply_data * data = STRSET_REPDATA ( reply_base ) ;
unsigned int i ;
int len = 0 ;
int ret ;
2021-06-12 04:49:48 +03:00
len + = nla_total_size ( 0 ) ; /* ETHTOOL_A_STRSET_STRINGSETS */
2019-12-27 17:55:43 +03:00
for ( i = 0 ; i < ETH_SS_COUNT ; i + + ) {
const struct strset_info * set_info = & data - > sets [ i ] ;
if ( ! strset_include ( req_info , data , i ) )
continue ;
ret = strset_set_size ( set_info , req_info - > counts_only ) ;
if ( ret < 0 )
return ret ;
len + = ret ;
}
return len ;
}
/* fill one string into reply */
static int strset_fill_string ( struct sk_buff * skb ,
const struct strset_info * set_info , u32 idx )
{
struct nlattr * string_attr ;
const char * value ;
value = set_info - > strings [ idx ] ;
string_attr = nla_nest_start ( skb , ETHTOOL_A_STRINGS_STRING ) ;
if ( ! string_attr )
return - EMSGSIZE ;
if ( nla_put_u32 ( skb , ETHTOOL_A_STRING_INDEX , idx ) | |
ethnl_put_strz ( skb , ETHTOOL_A_STRING_VALUE , value ) )
goto nla_put_failure ;
nla_nest_end ( skb , string_attr ) ;
return 0 ;
nla_put_failure :
nla_nest_cancel ( skb , string_attr ) ;
return - EMSGSIZE ;
}
/* fill one string set into reply */
static int strset_fill_set ( struct sk_buff * skb ,
const struct strset_info * set_info , u32 id ,
bool counts_only )
{
struct nlattr * stringset_attr ;
struct nlattr * strings_attr ;
unsigned int i ;
if ( ! set_info - > per_dev & & ! set_info - > strings )
return - EOPNOTSUPP ;
if ( set_info - > count = = 0 )
return 0 ;
stringset_attr = nla_nest_start ( skb , ETHTOOL_A_STRINGSETS_STRINGSET ) ;
if ( ! stringset_attr )
return - EMSGSIZE ;
if ( nla_put_u32 ( skb , ETHTOOL_A_STRINGSET_ID , id ) | |
nla_put_u32 ( skb , ETHTOOL_A_STRINGSET_COUNT , set_info - > count ) )
goto nla_put_failure ;
if ( ! counts_only ) {
strings_attr = nla_nest_start ( skb , ETHTOOL_A_STRINGSET_STRINGS ) ;
if ( ! strings_attr )
goto nla_put_failure ;
for ( i = 0 ; i < set_info - > count ; i + + ) {
if ( strset_fill_string ( skb , set_info , i ) < 0 )
goto nla_put_failure ;
}
nla_nest_end ( skb , strings_attr ) ;
}
nla_nest_end ( skb , stringset_attr ) ;
return 0 ;
nla_put_failure :
nla_nest_cancel ( skb , stringset_attr ) ;
return - EMSGSIZE ;
}
static int strset_fill_reply ( struct sk_buff * skb ,
const struct ethnl_req_info * req_base ,
const struct ethnl_reply_data * reply_base )
{
const struct strset_req_info * req_info = STRSET_REQINFO ( req_base ) ;
const struct strset_reply_data * data = STRSET_REPDATA ( reply_base ) ;
struct nlattr * nest ;
unsigned int i ;
int ret ;
nest = nla_nest_start ( skb , ETHTOOL_A_STRSET_STRINGSETS ) ;
if ( ! nest )
return - EMSGSIZE ;
for ( i = 0 ; i < ETH_SS_COUNT ; i + + ) {
if ( strset_include ( req_info , data , i ) ) {
ret = strset_fill_set ( skb , & data - > sets [ i ] , i ,
req_info - > counts_only ) ;
if ( ret < 0 )
goto nla_put_failure ;
}
}
nla_nest_end ( skb , nest ) ;
return 0 ;
nla_put_failure :
nla_nest_cancel ( skb , nest ) ;
return ret ;
}
const struct ethnl_request_ops ethnl_strset_request_ops = {
. request_cmd = ETHTOOL_MSG_STRSET_GET ,
. reply_cmd = ETHTOOL_MSG_STRSET_GET_REPLY ,
. hdr_attr = ETHTOOL_A_STRSET_HEADER ,
. req_info_size = sizeof ( struct strset_req_info ) ,
. reply_data_size = sizeof ( struct strset_reply_data ) ,
. allow_nodev_do = true ,
. parse_request = strset_parse_request ,
. prepare_data = strset_prepare_data ,
. reply_size = strset_reply_size ,
. fill_reply = strset_fill_reply ,
. cleanup_data = strset_cleanup_data ,
} ;