2020-05-10 22:12:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
# include <linux/phy.h>
2020-05-10 22:12:36 +03:00
# include <linux/ethtool_netlink.h>
2020-05-10 22:12:33 +03:00
# include "netlink.h"
# include "common.h"
2020-05-27 01:21:41 +03:00
/* 802.3 standard allows 100 meters for BaseT cables. However longer
* cables might work , depending on the quality of the cables and the
* PHY . So allow testing for up to 150 meters .
*/
# define MAX_CABLE_LENGTH_CM (150 * 100)
2020-05-10 22:12:33 +03:00
2020-10-06 01:07:35 +03:00
const struct nla_policy ethnl_cable_test_act_policy [ ] = {
2020-10-06 01:07:36 +03:00
[ ETHTOOL_A_CABLE_TEST_HEADER ] =
NLA_POLICY_NESTED ( ethnl_header_policy ) ,
2020-05-10 22:12:33 +03:00
} ;
2020-05-27 01:21:38 +03:00
static int ethnl_cable_test_started ( struct phy_device * phydev , u8 cmd )
2020-05-10 22:12:40 +03:00
{
struct sk_buff * skb ;
int err = - ENOMEM ;
void * ehdr ;
skb = genlmsg_new ( NLMSG_GOODSIZE , GFP_KERNEL ) ;
if ( ! skb )
goto out ;
2020-05-27 01:21:38 +03:00
ehdr = ethnl_bcastmsg_put ( skb , cmd ) ;
2020-05-10 22:12:40 +03:00
if ( ! ehdr ) {
err = - EMSGSIZE ;
goto out ;
}
err = ethnl_fill_reply_header ( skb , phydev - > attached_dev ,
ETHTOOL_A_CABLE_TEST_NTF_HEADER ) ;
if ( err )
goto out ;
err = nla_put_u8 ( skb , ETHTOOL_A_CABLE_TEST_NTF_STATUS ,
ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED ) ;
if ( err )
goto out ;
genlmsg_end ( skb , ehdr ) ;
return ethnl_multicast ( skb , phydev - > attached_dev ) ;
out :
nlmsg_free ( skb ) ;
phydev_err ( phydev , " %s: Error %pe \n " , __func__ , ERR_PTR ( err ) ) ;
return err ;
}
2020-05-10 22:12:33 +03:00
int ethnl_act_cable_test ( struct sk_buff * skb , struct genl_info * info )
{
struct ethnl_req_info req_info = { } ;
2020-07-06 07:27:58 +03:00
const struct ethtool_phy_ops * ops ;
2020-10-06 01:07:34 +03:00
struct nlattr * * tb = info - > attrs ;
2020-05-10 22:12:33 +03:00
struct net_device * dev ;
int ret ;
ret = ethnl_parse_header_dev_get ( & req_info ,
tb [ ETHTOOL_A_CABLE_TEST_HEADER ] ,
genl_info_net ( info ) , info - > extack ,
true ) ;
if ( ret < 0 )
return ret ;
dev = req_info . dev ;
if ( ! dev - > phydev ) {
ret = - EOPNOTSUPP ;
goto out_dev_put ;
}
rtnl_lock ( ) ;
2020-07-06 07:27:58 +03:00
ops = ethtool_phy_ops ;
if ( ! ops | | ! ops - > start_cable_test ) {
ret = - EOPNOTSUPP ;
goto out_rtnl ;
}
2020-05-10 22:12:33 +03:00
ret = ethnl_ops_begin ( dev ) ;
if ( ret < 0 )
goto out_rtnl ;
2020-07-06 07:27:58 +03:00
ret = ops - > start_cable_test ( dev - > phydev , info - > extack ) ;
2020-05-10 22:12:33 +03:00
ethnl_ops_complete ( dev ) ;
2020-05-10 22:12:40 +03:00
if ( ! ret )
2020-05-27 01:21:38 +03:00
ethnl_cable_test_started ( dev - > phydev ,
ETHTOOL_MSG_CABLE_TEST_NTF ) ;
2020-05-10 22:12:40 +03:00
2020-05-10 22:12:33 +03:00
out_rtnl :
rtnl_unlock ( ) ;
out_dev_put :
2021-12-14 11:42:30 +03:00
ethnl_parse_header_dev_put ( & req_info ) ;
2020-05-10 22:12:33 +03:00
return ret ;
}
2020-05-10 22:12:36 +03:00
2020-05-27 01:21:38 +03:00
int ethnl_cable_test_alloc ( struct phy_device * phydev , u8 cmd )
2020-05-10 22:12:36 +03:00
{
int err = - ENOMEM ;
2020-05-27 01:21:39 +03:00
/* One TDR sample occupies 20 bytes. For a 150 meter cable,
* with four pairs , around 12 K is needed .
*/
phydev - > skb = genlmsg_new ( SZ_16K , GFP_KERNEL ) ;
2020-05-10 22:12:36 +03:00
if ( ! phydev - > skb )
goto out ;
2020-05-27 01:21:38 +03:00
phydev - > ehdr = ethnl_bcastmsg_put ( phydev - > skb , cmd ) ;
2020-05-10 22:12:36 +03:00
if ( ! phydev - > ehdr ) {
err = - EMSGSIZE ;
goto out ;
}
err = ethnl_fill_reply_header ( phydev - > skb , phydev - > attached_dev ,
ETHTOOL_A_CABLE_TEST_NTF_HEADER ) ;
if ( err )
goto out ;
err = nla_put_u8 ( phydev - > skb , ETHTOOL_A_CABLE_TEST_NTF_STATUS ,
ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED ) ;
if ( err )
goto out ;
phydev - > nest = nla_nest_start ( phydev - > skb ,
ETHTOOL_A_CABLE_TEST_NTF_NEST ) ;
2020-05-10 22:12:37 +03:00
if ( ! phydev - > nest ) {
err = - EMSGSIZE ;
2020-05-10 22:12:36 +03:00
goto out ;
2020-05-10 22:12:37 +03:00
}
2020-05-10 22:12:36 +03:00
return 0 ;
out :
nlmsg_free ( phydev - > skb ) ;
2020-05-10 22:12:37 +03:00
phydev - > skb = NULL ;
2020-05-10 22:12:36 +03:00
return err ;
}
EXPORT_SYMBOL_GPL ( ethnl_cable_test_alloc ) ;
void ethnl_cable_test_free ( struct phy_device * phydev )
{
nlmsg_free ( phydev - > skb ) ;
2020-05-10 22:12:37 +03:00
phydev - > skb = NULL ;
2020-05-10 22:12:36 +03:00
}
EXPORT_SYMBOL_GPL ( ethnl_cable_test_free ) ;
void ethnl_cable_test_finished ( struct phy_device * phydev )
{
nla_nest_end ( phydev - > skb , phydev - > nest ) ;
genlmsg_end ( phydev - > skb , phydev - > ehdr ) ;
ethnl_multicast ( phydev - > skb , phydev - > attached_dev ) ;
}
EXPORT_SYMBOL_GPL ( ethnl_cable_test_finished ) ;
2020-05-10 22:12:37 +03:00
int ethnl_cable_test_result ( struct phy_device * phydev , u8 pair , u8 result )
{
struct nlattr * nest ;
int ret = - EMSGSIZE ;
nest = nla_nest_start ( phydev - > skb , ETHTOOL_A_CABLE_NEST_RESULT ) ;
if ( ! nest )
return - EMSGSIZE ;
if ( nla_put_u8 ( phydev - > skb , ETHTOOL_A_CABLE_RESULT_PAIR , pair ) )
goto err ;
if ( nla_put_u8 ( phydev - > skb , ETHTOOL_A_CABLE_RESULT_CODE , result ) )
goto err ;
nla_nest_end ( phydev - > skb , nest ) ;
return 0 ;
err :
nla_nest_cancel ( phydev - > skb , nest ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( ethnl_cable_test_result ) ;
int ethnl_cable_test_fault_length ( struct phy_device * phydev , u8 pair , u32 cm )
{
struct nlattr * nest ;
int ret = - EMSGSIZE ;
nest = nla_nest_start ( phydev - > skb ,
ETHTOOL_A_CABLE_NEST_FAULT_LENGTH ) ;
if ( ! nest )
return - EMSGSIZE ;
if ( nla_put_u8 ( phydev - > skb , ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR , pair ) )
goto err ;
if ( nla_put_u32 ( phydev - > skb , ETHTOOL_A_CABLE_FAULT_LENGTH_CM , cm ) )
goto err ;
nla_nest_end ( phydev - > skb , nest ) ;
return 0 ;
err :
nla_nest_cancel ( phydev - > skb , nest ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( ethnl_cable_test_fault_length ) ;
2020-05-27 01:21:38 +03:00
2020-05-27 01:21:41 +03:00
struct cable_test_tdr_req_info {
struct ethnl_req_info base ;
} ;
2020-10-06 01:07:35 +03:00
static const struct nla_policy cable_test_tdr_act_cfg_policy [ ] = {
2020-05-27 01:21:41 +03:00
[ ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST ] = { . type = NLA_U32 } ,
[ ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST ] = { . type = NLA_U32 } ,
[ ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP ] = { . type = NLA_U32 } ,
[ ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR ] = { . type = NLA_U8 } ,
} ;
2020-10-06 01:07:35 +03:00
const struct nla_policy ethnl_cable_test_tdr_act_policy [ ] = {
2020-10-06 01:07:36 +03:00
[ ETHTOOL_A_CABLE_TEST_TDR_HEADER ] =
NLA_POLICY_NESTED ( ethnl_header_policy ) ,
2020-05-27 01:21:41 +03:00
[ ETHTOOL_A_CABLE_TEST_TDR_CFG ] = { . type = NLA_NESTED } ,
2020-05-27 01:21:38 +03:00
} ;
2020-05-27 01:21:41 +03:00
/* CABLE_TEST_TDR_ACT */
2020-05-29 00:43:24 +03:00
static int ethnl_act_cable_test_tdr_cfg ( const struct nlattr * nest ,
struct genl_info * info ,
struct phy_tdr_config * cfg )
2020-05-27 01:21:41 +03:00
{
2020-10-06 01:07:35 +03:00
struct nlattr * tb [ ARRAY_SIZE ( cable_test_tdr_act_cfg_policy ) ] ;
2020-05-27 01:21:41 +03:00
int ret ;
2020-06-24 04:25:45 +03:00
cfg - > first = 100 ;
cfg - > step = 100 ;
cfg - > last = MAX_CABLE_LENGTH_CM ;
cfg - > pair = PHY_PAIR_ALL ;
if ( ! nest )
return 0 ;
2020-10-06 01:07:35 +03:00
ret = nla_parse_nested ( tb ,
ARRAY_SIZE ( cable_test_tdr_act_cfg_policy ) - 1 ,
nest , cable_test_tdr_act_cfg_policy ,
info - > extack ) ;
2020-05-27 01:21:41 +03:00
if ( ret < 0 )
return ret ;
if ( tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST ] )
cfg - > first = nla_get_u32 (
tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST ] ) ;
2020-06-24 04:25:45 +03:00
2020-05-27 01:21:41 +03:00
if ( tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST ] )
cfg - > last = nla_get_u32 ( tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST ] ) ;
if ( tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP ] )
cfg - > step = nla_get_u32 ( tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP ] ) ;
if ( tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR ] ) {
cfg - > pair = nla_get_u8 ( tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR ] ) ;
if ( cfg - > pair > ETHTOOL_A_CABLE_PAIR_D ) {
NL_SET_ERR_MSG_ATTR (
info - > extack ,
tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR ] ,
" invalid pair parameter " ) ;
return - EINVAL ;
}
}
if ( cfg - > first > MAX_CABLE_LENGTH_CM ) {
NL_SET_ERR_MSG_ATTR ( info - > extack ,
tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST ] ,
" invalid first parameter " ) ;
return - EINVAL ;
}
if ( cfg - > last > MAX_CABLE_LENGTH_CM ) {
NL_SET_ERR_MSG_ATTR ( info - > extack ,
tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST ] ,
" invalid last parameter " ) ;
return - EINVAL ;
}
if ( cfg - > first > cfg - > last ) {
NL_SET_ERR_MSG ( info - > extack , " invalid first/last parameter " ) ;
return - EINVAL ;
}
if ( ! cfg - > step ) {
NL_SET_ERR_MSG_ATTR ( info - > extack ,
tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP ] ,
" invalid step parameter " ) ;
return - EINVAL ;
}
if ( cfg - > step > ( cfg - > last - cfg - > first ) ) {
NL_SET_ERR_MSG_ATTR ( info - > extack ,
tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP ] ,
" step parameter too big " ) ;
return - EINVAL ;
}
return 0 ;
}
2020-05-27 01:21:38 +03:00
int ethnl_act_cable_test_tdr ( struct sk_buff * skb , struct genl_info * info )
{
struct ethnl_req_info req_info = { } ;
2020-07-06 07:27:58 +03:00
const struct ethtool_phy_ops * ops ;
2020-10-06 01:07:34 +03:00
struct nlattr * * tb = info - > attrs ;
2020-05-27 01:21:41 +03:00
struct phy_tdr_config cfg ;
2020-05-27 01:21:38 +03:00
struct net_device * dev ;
int ret ;
ret = ethnl_parse_header_dev_get ( & req_info ,
tb [ ETHTOOL_A_CABLE_TEST_TDR_HEADER ] ,
genl_info_net ( info ) , info - > extack ,
true ) ;
if ( ret < 0 )
return ret ;
dev = req_info . dev ;
if ( ! dev - > phydev ) {
ret = - EOPNOTSUPP ;
goto out_dev_put ;
}
2020-05-27 01:21:41 +03:00
ret = ethnl_act_cable_test_tdr_cfg ( tb [ ETHTOOL_A_CABLE_TEST_TDR_CFG ] ,
info , & cfg ) ;
if ( ret )
goto out_dev_put ;
2020-05-27 01:21:38 +03:00
rtnl_lock ( ) ;
2020-07-06 07:27:58 +03:00
ops = ethtool_phy_ops ;
if ( ! ops | | ! ops - > start_cable_test_tdr ) {
ret = - EOPNOTSUPP ;
goto out_rtnl ;
}
2020-05-27 01:21:38 +03:00
ret = ethnl_ops_begin ( dev ) ;
if ( ret < 0 )
goto out_rtnl ;
2020-07-06 07:27:58 +03:00
ret = ops - > start_cable_test_tdr ( dev - > phydev , info - > extack , & cfg ) ;
2020-05-27 01:21:38 +03:00
ethnl_ops_complete ( dev ) ;
if ( ! ret )
ethnl_cable_test_started ( dev - > phydev ,
ETHTOOL_MSG_CABLE_TEST_TDR_NTF ) ;
out_rtnl :
rtnl_unlock ( ) ;
out_dev_put :
2021-12-14 11:42:30 +03:00
ethnl_parse_header_dev_put ( & req_info ) ;
2020-05-27 01:21:38 +03:00
return ret ;
}
2022-07-23 10:32:22 +03:00
2020-05-27 01:21:39 +03:00
int ethnl_cable_test_amplitude ( struct phy_device * phydev ,
u8 pair , s16 mV )
{
struct nlattr * nest ;
int ret = - EMSGSIZE ;
nest = nla_nest_start ( phydev - > skb ,
ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE ) ;
if ( ! nest )
return - EMSGSIZE ;
if ( nla_put_u8 ( phydev - > skb , ETHTOOL_A_CABLE_AMPLITUDE_PAIR , pair ) )
goto err ;
if ( nla_put_u16 ( phydev - > skb , ETHTOOL_A_CABLE_AMPLITUDE_mV , mV ) )
goto err ;
nla_nest_end ( phydev - > skb , nest ) ;
return 0 ;
err :
nla_nest_cancel ( phydev - > skb , nest ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( ethnl_cable_test_amplitude ) ;
int ethnl_cable_test_pulse ( struct phy_device * phydev , u16 mV )
{
struct nlattr * nest ;
int ret = - EMSGSIZE ;
nest = nla_nest_start ( phydev - > skb , ETHTOOL_A_CABLE_TDR_NEST_PULSE ) ;
if ( ! nest )
return - EMSGSIZE ;
if ( nla_put_u16 ( phydev - > skb , ETHTOOL_A_CABLE_PULSE_mV , mV ) )
goto err ;
nla_nest_end ( phydev - > skb , nest ) ;
return 0 ;
err :
nla_nest_cancel ( phydev - > skb , nest ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( ethnl_cable_test_pulse ) ;
int ethnl_cable_test_step ( struct phy_device * phydev , u32 first , u32 last ,
u32 step )
{
struct nlattr * nest ;
int ret = - EMSGSIZE ;
nest = nla_nest_start ( phydev - > skb , ETHTOOL_A_CABLE_TDR_NEST_STEP ) ;
if ( ! nest )
return - EMSGSIZE ;
if ( nla_put_u32 ( phydev - > skb , ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE ,
first ) )
goto err ;
if ( nla_put_u32 ( phydev - > skb , ETHTOOL_A_CABLE_STEP_LAST_DISTANCE , last ) )
goto err ;
if ( nla_put_u32 ( phydev - > skb , ETHTOOL_A_CABLE_STEP_STEP_DISTANCE , step ) )
goto err ;
nla_nest_end ( phydev - > skb , nest ) ;
return 0 ;
err :
nla_nest_cancel ( phydev - > skb , nest ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( ethnl_cable_test_step ) ;