2019-12-27 17:56:23 +03:00
// SPDX-License-Identifier: GPL-2.0-only
# include "netlink.h"
# include "common.h"
2020-05-20 09:29:14 +03:00
# include <linux/phy.h>
2019-12-27 17:56:23 +03:00
struct linkstate_req_info {
struct ethnl_req_info base ;
} ;
struct linkstate_reply_data {
2020-06-29 23:46:16 +03:00
struct ethnl_reply_data base ;
int link ;
int sqi ;
int sqi_max ;
bool link_ext_state_provided ;
struct ethtool_link_ext_state_info ethtool_link_ext_state_info ;
2019-12-27 17:56:23 +03:00
} ;
# define LINKSTATE_REPDATA(__reply_base) \
container_of ( __reply_base , struct linkstate_reply_data , base )
2020-10-06 01:07:35 +03:00
const struct nla_policy ethnl_linkstate_get_policy [ ] = {
2020-10-06 01:07:36 +03:00
[ ETHTOOL_A_LINKSTATE_HEADER ] =
NLA_POLICY_NESTED ( ethnl_header_policy ) ,
2019-12-27 17:56:23 +03:00
} ;
2020-05-20 09:29:14 +03:00
static int linkstate_get_sqi ( struct net_device * dev )
{
struct phy_device * phydev = dev - > phydev ;
int ret ;
if ( ! phydev )
return - EOPNOTSUPP ;
mutex_lock ( & phydev - > lock ) ;
if ( ! phydev - > drv | | ! phydev - > drv - > get_sqi )
ret = - EOPNOTSUPP ;
else
ret = phydev - > drv - > get_sqi ( phydev ) ;
mutex_unlock ( & phydev - > lock ) ;
return ret ;
}
static int linkstate_get_sqi_max ( struct net_device * dev )
{
struct phy_device * phydev = dev - > phydev ;
int ret ;
if ( ! phydev )
return - EOPNOTSUPP ;
mutex_lock ( & phydev - > lock ) ;
if ( ! phydev - > drv | | ! phydev - > drv - > get_sqi_max )
ret = - EOPNOTSUPP ;
else
ret = phydev - > drv - > get_sqi_max ( phydev ) ;
mutex_unlock ( & phydev - > lock ) ;
return ret ;
2020-06-29 23:46:16 +03:00
} ;
static int linkstate_get_link_ext_state ( struct net_device * dev ,
struct linkstate_reply_data * data )
{
int err ;
if ( ! dev - > ethtool_ops - > get_link_ext_state )
return - EOPNOTSUPP ;
err = dev - > ethtool_ops - > get_link_ext_state ( dev , & data - > ethtool_link_ext_state_info ) ;
if ( err )
return err ;
data - > link_ext_state_provided = true ;
return 0 ;
2020-05-20 09:29:14 +03:00
}
2019-12-27 17:56:23 +03:00
static int linkstate_prepare_data ( const struct ethnl_req_info * req_base ,
struct ethnl_reply_data * reply_base ,
struct genl_info * info )
{
struct linkstate_reply_data * data = LINKSTATE_REPDATA ( reply_base ) ;
struct net_device * dev = reply_base - > dev ;
int ret ;
ret = ethnl_ops_begin ( dev ) ;
if ( ret < 0 )
return ret ;
data - > link = __ethtool_get_link ( dev ) ;
2020-05-20 09:29:14 +03:00
ret = linkstate_get_sqi ( dev ) ;
if ( ret < 0 & & ret ! = - EOPNOTSUPP )
2020-06-25 01:09:08 +03:00
goto out ;
2020-05-20 09:29:14 +03:00
data - > sqi = ret ;
ret = linkstate_get_sqi_max ( dev ) ;
if ( ret < 0 & & ret ! = - EOPNOTSUPP )
2020-06-25 01:09:08 +03:00
goto out ;
2020-05-20 09:29:14 +03:00
data - > sqi_max = ret ;
2020-06-29 23:46:16 +03:00
if ( dev - > flags & IFF_UP ) {
ret = linkstate_get_link_ext_state ( dev , data ) ;
if ( ret < 0 & & ret ! = - EOPNOTSUPP & & ret ! = - ENODATA )
goto out ;
}
2020-06-25 01:09:08 +03:00
ret = 0 ;
out :
2019-12-27 17:56:23 +03:00
ethnl_ops_complete ( dev ) ;
2020-06-25 01:09:08 +03:00
return ret ;
2019-12-27 17:56:23 +03:00
}
static int linkstate_reply_size ( const struct ethnl_req_info * req_base ,
const struct ethnl_reply_data * reply_base )
{
2020-05-20 09:29:14 +03:00
struct linkstate_reply_data * data = LINKSTATE_REPDATA ( reply_base ) ;
int len ;
len = nla_total_size ( sizeof ( u8 ) ) /* LINKSTATE_LINK */
2019-12-27 17:56:23 +03:00
+ 0 ;
2020-05-20 09:29:14 +03:00
if ( data - > sqi ! = - EOPNOTSUPP )
len + = nla_total_size ( sizeof ( u32 ) ) ;
if ( data - > sqi_max ! = - EOPNOTSUPP )
len + = nla_total_size ( sizeof ( u32 ) ) ;
2020-06-29 23:46:16 +03:00
if ( data - > link_ext_state_provided )
len + = nla_total_size ( sizeof ( u8 ) ) ; /* LINKSTATE_EXT_STATE */
if ( data - > ethtool_link_ext_state_info . __link_ext_substate )
len + = nla_total_size ( sizeof ( u8 ) ) ; /* LINKSTATE_EXT_SUBSTATE */
2020-05-20 09:29:14 +03:00
return len ;
2019-12-27 17:56:23 +03:00
}
static int linkstate_fill_reply ( struct sk_buff * skb ,
const struct ethnl_req_info * req_base ,
const struct ethnl_reply_data * reply_base )
{
struct linkstate_reply_data * data = LINKSTATE_REPDATA ( reply_base ) ;
if ( data - > link > = 0 & &
nla_put_u8 ( skb , ETHTOOL_A_LINKSTATE_LINK , ! ! data - > link ) )
return - EMSGSIZE ;
2020-05-20 09:29:14 +03:00
if ( data - > sqi ! = - EOPNOTSUPP & &
nla_put_u32 ( skb , ETHTOOL_A_LINKSTATE_SQI , data - > sqi ) )
return - EMSGSIZE ;
if ( data - > sqi_max ! = - EOPNOTSUPP & &
nla_put_u32 ( skb , ETHTOOL_A_LINKSTATE_SQI_MAX , data - > sqi_max ) )
return - EMSGSIZE ;
2020-06-29 23:46:16 +03:00
if ( data - > link_ext_state_provided ) {
if ( nla_put_u8 ( skb , ETHTOOL_A_LINKSTATE_EXT_STATE ,
data - > ethtool_link_ext_state_info . link_ext_state ) )
return - EMSGSIZE ;
if ( data - > ethtool_link_ext_state_info . __link_ext_substate & &
nla_put_u8 ( skb , ETHTOOL_A_LINKSTATE_EXT_SUBSTATE ,
data - > ethtool_link_ext_state_info . __link_ext_substate ) )
return - EMSGSIZE ;
}
2019-12-27 17:56:23 +03:00
return 0 ;
}
const struct ethnl_request_ops ethnl_linkstate_request_ops = {
. request_cmd = ETHTOOL_MSG_LINKSTATE_GET ,
. reply_cmd = ETHTOOL_MSG_LINKSTATE_GET_REPLY ,
. hdr_attr = ETHTOOL_A_LINKSTATE_HEADER ,
. req_info_size = sizeof ( struct linkstate_req_info ) ,
. reply_data_size = sizeof ( struct linkstate_reply_data ) ,
. prepare_data = linkstate_prepare_data ,
. reply_size = linkstate_reply_size ,
. fill_reply = linkstate_fill_reply ,
} ;