2020-01-09 15:43:44 +00:00
// SPDX-License-Identifier: GPL-2.0-only
/****************************************************************************
* Driver for Solarflare network controllers and boards
* Copyright 2018 Solarflare Communications Inc .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation , incorporated herein by reference .
*/
# include "mcdi_port_common.h"
2020-01-10 13:27:50 +00:00
# include "efx_common.h"
2020-06-30 13:02:56 +01:00
# include "nic.h"
2020-01-09 15:43:44 +00:00
int efx_mcdi_get_phy_cfg ( struct efx_nic * efx , struct efx_mcdi_phy_data * cfg )
{
MCDI_DECLARE_BUF ( outbuf , MC_CMD_GET_PHY_CFG_OUT_LEN ) ;
size_t outlen ;
int rc ;
BUILD_BUG_ON ( MC_CMD_GET_PHY_CFG_IN_LEN ! = 0 ) ;
BUILD_BUG_ON ( MC_CMD_GET_PHY_CFG_OUT_NAME_LEN ! = sizeof ( cfg - > name ) ) ;
rc = efx_mcdi_rpc ( efx , MC_CMD_GET_PHY_CFG , NULL , 0 ,
outbuf , sizeof ( outbuf ) , & outlen ) ;
if ( rc )
goto fail ;
if ( outlen < MC_CMD_GET_PHY_CFG_OUT_LEN ) {
rc = - EIO ;
goto fail ;
}
cfg - > flags = MCDI_DWORD ( outbuf , GET_PHY_CFG_OUT_FLAGS ) ;
cfg - > type = MCDI_DWORD ( outbuf , GET_PHY_CFG_OUT_TYPE ) ;
cfg - > supported_cap =
MCDI_DWORD ( outbuf , GET_PHY_CFG_OUT_SUPPORTED_CAP ) ;
cfg - > channel = MCDI_DWORD ( outbuf , GET_PHY_CFG_OUT_CHANNEL ) ;
cfg - > port = MCDI_DWORD ( outbuf , GET_PHY_CFG_OUT_PRT ) ;
cfg - > stats_mask = MCDI_DWORD ( outbuf , GET_PHY_CFG_OUT_STATS_MASK ) ;
memcpy ( cfg - > name , MCDI_PTR ( outbuf , GET_PHY_CFG_OUT_NAME ) ,
sizeof ( cfg - > name ) ) ;
cfg - > media = MCDI_DWORD ( outbuf , GET_PHY_CFG_OUT_MEDIA_TYPE ) ;
cfg - > mmd_mask = MCDI_DWORD ( outbuf , GET_PHY_CFG_OUT_MMD_MASK ) ;
memcpy ( cfg - > revision , MCDI_PTR ( outbuf , GET_PHY_CFG_OUT_REVISION ) ,
sizeof ( cfg - > revision ) ) ;
return 0 ;
fail :
netif_err ( efx , hw , efx - > net_dev , " %s: failed rc=%d \n " , __func__ , rc ) ;
return rc ;
}
void efx_link_set_advertising ( struct efx_nic * efx ,
const unsigned long * advertising )
{
memcpy ( efx - > link_advertising , advertising ,
sizeof ( __ETHTOOL_DECLARE_LINK_MODE_MASK ( ) ) ) ;
efx - > link_advertising [ 0 ] | = ADVERTISED_Autoneg ;
if ( advertising [ 0 ] & ADVERTISED_Pause )
efx - > wanted_fc | = ( EFX_FC_TX | EFX_FC_RX ) ;
else
efx - > wanted_fc & = ~ ( EFX_FC_TX | EFX_FC_RX ) ;
if ( advertising [ 0 ] & ADVERTISED_Asym_Pause )
efx - > wanted_fc ^ = EFX_FC_TX ;
}
int efx_mcdi_set_link ( struct efx_nic * efx , u32 capabilities ,
u32 flags , u32 loopback_mode , u32 loopback_speed )
{
MCDI_DECLARE_BUF ( inbuf , MC_CMD_SET_LINK_IN_LEN ) ;
BUILD_BUG_ON ( MC_CMD_SET_LINK_OUT_LEN ! = 0 ) ;
MCDI_SET_DWORD ( inbuf , SET_LINK_IN_CAP , capabilities ) ;
MCDI_SET_DWORD ( inbuf , SET_LINK_IN_FLAGS , flags ) ;
MCDI_SET_DWORD ( inbuf , SET_LINK_IN_LOOPBACK_MODE , loopback_mode ) ;
MCDI_SET_DWORD ( inbuf , SET_LINK_IN_LOOPBACK_SPEED , loopback_speed ) ;
2022-01-04 11:35:43 +00:00
return efx_mcdi_rpc ( efx , MC_CMD_SET_LINK , inbuf , sizeof ( inbuf ) ,
2020-01-09 15:43:44 +00:00
NULL , 0 , NULL ) ;
}
int efx_mcdi_loopback_modes ( struct efx_nic * efx , u64 * loopback_modes )
{
MCDI_DECLARE_BUF ( outbuf , MC_CMD_GET_LOOPBACK_MODES_OUT_LEN ) ;
size_t outlen ;
int rc ;
rc = efx_mcdi_rpc ( efx , MC_CMD_GET_LOOPBACK_MODES , NULL , 0 ,
outbuf , sizeof ( outbuf ) , & outlen ) ;
if ( rc )
goto fail ;
if ( outlen < ( MC_CMD_GET_LOOPBACK_MODES_OUT_SUGGESTED_OFST +
MC_CMD_GET_LOOPBACK_MODES_OUT_SUGGESTED_LEN ) ) {
rc = - EIO ;
goto fail ;
}
* loopback_modes = MCDI_QWORD ( outbuf , GET_LOOPBACK_MODES_OUT_SUGGESTED ) ;
return 0 ;
fail :
netif_err ( efx , hw , efx - > net_dev , " %s: failed rc=%d \n " , __func__ , rc ) ;
return rc ;
}
void mcdi_to_ethtool_linkset ( u32 media , u32 cap , unsigned long * linkset )
{
# define SET_BIT(name) __set_bit(ETHTOOL_LINK_MODE_ ## name ## _BIT, \
linkset )
bitmap_zero ( linkset , __ETHTOOL_LINK_MODE_MASK_NBITS ) ;
switch ( media ) {
case MC_CMD_MEDIA_KX4 :
SET_BIT ( Backplane ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_1000FDX_LBN ) )
SET_BIT ( 1000 baseKX_Full ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_10000FDX_LBN ) )
SET_BIT ( 10000 baseKX4_Full ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_40000FDX_LBN ) )
SET_BIT ( 40000 baseKR4_Full ) ;
break ;
case MC_CMD_MEDIA_XFP :
case MC_CMD_MEDIA_SFP_PLUS :
case MC_CMD_MEDIA_QSFP_PLUS :
SET_BIT ( FIBRE ) ;
2021-10-19 23:13:32 +02:00
if ( cap & ( 1 < < MC_CMD_PHY_CAP_1000FDX_LBN ) ) {
2020-01-09 15:43:44 +00:00
SET_BIT ( 1000 baseT_Full ) ;
2021-10-19 23:13:32 +02:00
SET_BIT ( 1000 baseX_Full ) ;
}
if ( cap & ( 1 < < MC_CMD_PHY_CAP_10000FDX_LBN ) ) {
SET_BIT ( 10000 baseCR_Full ) ;
SET_BIT ( 10000 baseLR_Full ) ;
SET_BIT ( 10000 baseSR_Full ) ;
}
if ( cap & ( 1 < < MC_CMD_PHY_CAP_40000FDX_LBN ) ) {
2020-01-09 15:43:44 +00:00
SET_BIT ( 40000 baseCR4_Full ) ;
2021-10-19 23:13:32 +02:00
SET_BIT ( 40000 baseSR4_Full ) ;
}
if ( cap & ( 1 < < MC_CMD_PHY_CAP_100000FDX_LBN ) ) {
2020-01-09 15:43:44 +00:00
SET_BIT ( 100000 baseCR4_Full ) ;
2021-10-19 23:13:32 +02:00
SET_BIT ( 100000 baseSR4_Full ) ;
}
if ( cap & ( 1 < < MC_CMD_PHY_CAP_25000FDX_LBN ) ) {
2020-01-09 15:43:44 +00:00
SET_BIT ( 25000 baseCR_Full ) ;
2021-10-19 23:13:32 +02:00
SET_BIT ( 25000 baseSR_Full ) ;
}
2020-01-09 15:43:44 +00:00
if ( cap & ( 1 < < MC_CMD_PHY_CAP_50000FDX_LBN ) )
SET_BIT ( 50000 baseCR2_Full ) ;
break ;
case MC_CMD_MEDIA_BASE_T :
SET_BIT ( TP ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_10HDX_LBN ) )
SET_BIT ( 10 baseT_Half ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_10FDX_LBN ) )
SET_BIT ( 10 baseT_Full ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_100HDX_LBN ) )
SET_BIT ( 100 baseT_Half ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_100FDX_LBN ) )
SET_BIT ( 100 baseT_Full ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_1000HDX_LBN ) )
SET_BIT ( 1000 baseT_Half ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_1000FDX_LBN ) )
SET_BIT ( 1000 baseT_Full ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_10000FDX_LBN ) )
SET_BIT ( 10000 baseT_Full ) ;
break ;
}
if ( cap & ( 1 < < MC_CMD_PHY_CAP_PAUSE_LBN ) )
SET_BIT ( Pause ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_ASYM_LBN ) )
SET_BIT ( Asym_Pause ) ;
if ( cap & ( 1 < < MC_CMD_PHY_CAP_AN_LBN ) )
SET_BIT ( Autoneg ) ;
# undef SET_BIT
}
2020-01-09 15:43:59 +00:00
u32 ethtool_linkset_to_mcdi_cap ( const unsigned long * linkset )
{
u32 result = 0 ;
# define TEST_BIT(name) test_bit(ETHTOOL_LINK_MODE_ ## name ## _BIT, \
linkset )
if ( TEST_BIT ( 10 baseT_Half ) )
result | = ( 1 < < MC_CMD_PHY_CAP_10HDX_LBN ) ;
if ( TEST_BIT ( 10 baseT_Full ) )
result | = ( 1 < < MC_CMD_PHY_CAP_10FDX_LBN ) ;
if ( TEST_BIT ( 100 baseT_Half ) )
result | = ( 1 < < MC_CMD_PHY_CAP_100HDX_LBN ) ;
if ( TEST_BIT ( 100 baseT_Full ) )
result | = ( 1 < < MC_CMD_PHY_CAP_100FDX_LBN ) ;
if ( TEST_BIT ( 1000 baseT_Half ) )
result | = ( 1 < < MC_CMD_PHY_CAP_1000HDX_LBN ) ;
2021-10-19 23:13:32 +02:00
if ( TEST_BIT ( 1000 baseT_Full ) | | TEST_BIT ( 1000 baseKX_Full ) | |
TEST_BIT ( 1000 baseX_Full ) )
2020-01-09 15:43:59 +00:00
result | = ( 1 < < MC_CMD_PHY_CAP_1000FDX_LBN ) ;
2021-10-19 23:13:32 +02:00
if ( TEST_BIT ( 10000 baseT_Full ) | | TEST_BIT ( 10000 baseKX4_Full ) | |
TEST_BIT ( 10000 baseCR_Full ) | | TEST_BIT ( 10000 baseLR_Full ) | |
TEST_BIT ( 10000 baseSR_Full ) )
2020-01-09 15:43:59 +00:00
result | = ( 1 < < MC_CMD_PHY_CAP_10000FDX_LBN ) ;
2021-10-19 23:13:32 +02:00
if ( TEST_BIT ( 40000 baseCR4_Full ) | | TEST_BIT ( 40000 baseKR4_Full ) | |
TEST_BIT ( 40000 baseSR4_Full ) )
2020-01-09 15:43:59 +00:00
result | = ( 1 < < MC_CMD_PHY_CAP_40000FDX_LBN ) ;
2021-10-19 23:13:32 +02:00
if ( TEST_BIT ( 100000 baseCR4_Full ) | | TEST_BIT ( 100000 baseSR4_Full ) )
2020-01-09 15:43:59 +00:00
result | = ( 1 < < MC_CMD_PHY_CAP_100000FDX_LBN ) ;
2021-10-19 23:13:32 +02:00
if ( TEST_BIT ( 25000 baseCR_Full ) | | TEST_BIT ( 25000 baseSR_Full ) )
2020-01-09 15:43:59 +00:00
result | = ( 1 < < MC_CMD_PHY_CAP_25000FDX_LBN ) ;
if ( TEST_BIT ( 50000 baseCR2_Full ) )
result | = ( 1 < < MC_CMD_PHY_CAP_50000FDX_LBN ) ;
if ( TEST_BIT ( Pause ) )
result | = ( 1 < < MC_CMD_PHY_CAP_PAUSE_LBN ) ;
if ( TEST_BIT ( Asym_Pause ) )
result | = ( 1 < < MC_CMD_PHY_CAP_ASYM_LBN ) ;
if ( TEST_BIT ( Autoneg ) )
result | = ( 1 < < MC_CMD_PHY_CAP_AN_LBN ) ;
# undef TEST_BIT
return result ;
}
u32 efx_get_mcdi_phy_flags ( struct efx_nic * efx )
{
struct efx_mcdi_phy_data * phy_cfg = efx - > phy_data ;
enum efx_phy_mode mode , supported ;
u32 flags ;
/* TODO: Advertise the capabilities supported by this PHY */
supported = 0 ;
if ( phy_cfg - > flags & ( 1 < < MC_CMD_GET_PHY_CFG_OUT_TXDIS_LBN ) )
supported | = PHY_MODE_TX_DISABLED ;
if ( phy_cfg - > flags & ( 1 < < MC_CMD_GET_PHY_CFG_OUT_LOWPOWER_LBN ) )
supported | = PHY_MODE_LOW_POWER ;
if ( phy_cfg - > flags & ( 1 < < MC_CMD_GET_PHY_CFG_OUT_POWEROFF_LBN ) )
supported | = PHY_MODE_OFF ;
mode = efx - > phy_mode & supported ;
flags = 0 ;
if ( mode & PHY_MODE_TX_DISABLED )
flags | = ( 1 < < MC_CMD_SET_LINK_IN_TXDIS_LBN ) ;
if ( mode & PHY_MODE_LOW_POWER )
flags | = ( 1 < < MC_CMD_SET_LINK_IN_LOWPOWER_LBN ) ;
if ( mode & PHY_MODE_OFF )
flags | = ( 1 < < MC_CMD_SET_LINK_IN_POWEROFF_LBN ) ;
return flags ;
}
u8 mcdi_to_ethtool_media ( u32 media )
{
switch ( media ) {
case MC_CMD_MEDIA_XAUI :
case MC_CMD_MEDIA_CX4 :
case MC_CMD_MEDIA_KX4 :
return PORT_OTHER ;
case MC_CMD_MEDIA_XFP :
case MC_CMD_MEDIA_SFP_PLUS :
case MC_CMD_MEDIA_QSFP_PLUS :
return PORT_FIBRE ;
case MC_CMD_MEDIA_BASE_T :
return PORT_TP ;
default :
return PORT_OTHER ;
}
}
2020-01-09 15:43:44 +00:00
void efx_mcdi_phy_decode_link ( struct efx_nic * efx ,
struct efx_link_state * link_state ,
u32 speed , u32 flags , u32 fcntl )
{
switch ( fcntl ) {
case MC_CMD_FCNTL_AUTO :
WARN_ON ( 1 ) ; /* This is not a link mode */
link_state - > fc = EFX_FC_AUTO | EFX_FC_TX | EFX_FC_RX ;
break ;
case MC_CMD_FCNTL_BIDIR :
link_state - > fc = EFX_FC_TX | EFX_FC_RX ;
break ;
case MC_CMD_FCNTL_RESPOND :
link_state - > fc = EFX_FC_RX ;
break ;
default :
WARN_ON ( 1 ) ;
2020-08-23 17:36:59 -05:00
fallthrough ;
2020-01-09 15:43:44 +00:00
case MC_CMD_FCNTL_OFF :
link_state - > fc = 0 ;
break ;
}
link_state - > up = ! ! ( flags & ( 1 < < MC_CMD_GET_LINK_OUT_LINK_UP_LBN ) ) ;
link_state - > fd = ! ! ( flags & ( 1 < < MC_CMD_GET_LINK_OUT_FULL_DUPLEX_LBN ) ) ;
link_state - > speed = speed ;
}
2020-01-09 15:43:59 +00:00
/* The semantics of the ethtool FEC mode bitmask are not well defined,
* particularly the meaning of combinations of bits . Which means we get to
* define our own semantics , as follows :
* OFF overrides any other bits , and means " disable all FEC " ( with the
* exception of 25 G KR4 / CR4 , where it is not possible to reject it if AN
* partner requests it ) .
* AUTO on its own means use cable requirements and link partner autoneg with
* fw - default preferences for the cable type .
* AUTO and either RS or BASER means use the specified FEC type if cable and
* link partner support it , otherwise autoneg / fw - default .
* RS or BASER alone means use the specified FEC type if cable and link partner
* support it and either requests it , otherwise no FEC .
* Both RS and BASER ( whether AUTO or not ) means use FEC if cable and link
* partner support it , preferring RS to BASER .
*/
2020-09-07 17:15:08 +01:00
u32 ethtool_fec_caps_to_mcdi ( u32 supported_cap , u32 ethtool_cap )
2020-01-09 15:43:59 +00:00
{
u32 ret = 0 ;
if ( ethtool_cap & ETHTOOL_FEC_OFF )
return 0 ;
if ( ethtool_cap & ETHTOOL_FEC_AUTO )
2020-09-07 17:15:08 +01:00
ret | = ( ( 1 < < MC_CMD_PHY_CAP_BASER_FEC_LBN ) |
( 1 < < MC_CMD_PHY_CAP_25G_BASER_FEC_LBN ) |
( 1 < < MC_CMD_PHY_CAP_RS_FEC_LBN ) ) & supported_cap ;
if ( ethtool_cap & ETHTOOL_FEC_RS & &
supported_cap & ( 1 < < MC_CMD_PHY_CAP_RS_FEC_LBN ) )
2020-01-09 15:43:59 +00:00
ret | = ( 1 < < MC_CMD_PHY_CAP_RS_FEC_LBN ) |
( 1 < < MC_CMD_PHY_CAP_RS_FEC_REQUESTED_LBN ) ;
2020-09-07 17:15:08 +01:00
if ( ethtool_cap & ETHTOOL_FEC_BASER ) {
if ( supported_cap & ( 1 < < MC_CMD_PHY_CAP_BASER_FEC_LBN ) )
ret | = ( 1 < < MC_CMD_PHY_CAP_BASER_FEC_LBN ) |
( 1 < < MC_CMD_PHY_CAP_BASER_FEC_REQUESTED_LBN ) ;
if ( supported_cap & ( 1 < < MC_CMD_PHY_CAP_25G_BASER_FEC_LBN ) )
ret | = ( 1 < < MC_CMD_PHY_CAP_25G_BASER_FEC_LBN ) |
( 1 < < MC_CMD_PHY_CAP_25G_BASER_FEC_REQUESTED_LBN ) ;
}
2020-01-09 15:43:59 +00:00
return ret ;
}
/* Invert ethtool_fec_caps_to_mcdi. There are two combinations that function
* can never produce , ( baser xor rs ) and neither req ; the implementation below
* maps both of those to AUTO . This should never matter , and it ' s not clear
* what a better mapping would be anyway .
*/
u32 mcdi_fec_caps_to_ethtool ( u32 caps , bool is_25g )
{
bool rs = caps & ( 1 < < MC_CMD_PHY_CAP_RS_FEC_LBN ) ,
rs_req = caps & ( 1 < < MC_CMD_PHY_CAP_RS_FEC_REQUESTED_LBN ) ,
baser = is_25g ? caps & ( 1 < < MC_CMD_PHY_CAP_25G_BASER_FEC_LBN )
: caps & ( 1 < < MC_CMD_PHY_CAP_BASER_FEC_LBN ) ,
baser_req = is_25g ? caps & ( 1 < < MC_CMD_PHY_CAP_25G_BASER_FEC_REQUESTED_LBN )
: caps & ( 1 < < MC_CMD_PHY_CAP_BASER_FEC_REQUESTED_LBN ) ;
if ( ! baser & & ! rs )
return ETHTOOL_FEC_OFF ;
return ( rs_req ? ETHTOOL_FEC_RS : 0 ) |
( baser_req ? ETHTOOL_FEC_BASER : 0 ) |
( baser = = baser_req & & rs = = rs_req ? 0 : ETHTOOL_FEC_AUTO ) ;
}
2020-01-09 15:44:12 +00:00
/* Verify that the forced flow control settings (!EFX_FC_AUTO) are
* supported by the link partner . Warn the user if this isn ' t the case
*/
void efx_mcdi_phy_check_fcntl ( struct efx_nic * efx , u32 lpa )
{
struct efx_mcdi_phy_data * phy_cfg = efx - > phy_data ;
u32 rmtadv ;
/* The link partner capabilities are only relevant if the
* link supports flow control autonegotiation
*/
if ( ~ phy_cfg - > supported_cap & ( 1 < < MC_CMD_PHY_CAP_AN_LBN ) )
return ;
/* If flow control autoneg is supported and enabled, then fine */
if ( efx - > wanted_fc & EFX_FC_AUTO )
return ;
rmtadv = 0 ;
if ( lpa & ( 1 < < MC_CMD_PHY_CAP_PAUSE_LBN ) )
rmtadv | = ADVERTISED_Pause ;
if ( lpa & ( 1 < < MC_CMD_PHY_CAP_ASYM_LBN ) )
rmtadv | = ADVERTISED_Asym_Pause ;
if ( ( efx - > wanted_fc & EFX_FC_TX ) & & rmtadv = = ADVERTISED_Asym_Pause )
netif_err ( efx , link , efx - > net_dev ,
" warning: link partner doesn't support pause frames " ) ;
}
bool efx_mcdi_phy_poll ( struct efx_nic * efx )
{
struct efx_link_state old_state = efx - > link_state ;
MCDI_DECLARE_BUF ( outbuf , MC_CMD_GET_LINK_OUT_LEN ) ;
int rc ;
WARN_ON ( ! mutex_is_locked ( & efx - > mac_lock ) ) ;
BUILD_BUG_ON ( MC_CMD_GET_LINK_IN_LEN ! = 0 ) ;
rc = efx_mcdi_rpc ( efx , MC_CMD_GET_LINK , NULL , 0 ,
outbuf , sizeof ( outbuf ) , NULL ) ;
if ( rc )
efx - > link_state . up = false ;
else
efx_mcdi_phy_decode_link (
efx , & efx - > link_state ,
MCDI_DWORD ( outbuf , GET_LINK_OUT_LINK_SPEED ) ,
MCDI_DWORD ( outbuf , GET_LINK_OUT_FLAGS ) ,
MCDI_DWORD ( outbuf , GET_LINK_OUT_FCNTL ) ) ;
return ! efx_link_state_equal ( & efx - > link_state , & old_state ) ;
}
2020-09-07 17:14:34 +01:00
int efx_mcdi_phy_probe ( struct efx_nic * efx )
{
struct efx_mcdi_phy_data * phy_data ;
MCDI_DECLARE_BUF ( outbuf , MC_CMD_GET_LINK_OUT_LEN ) ;
u32 caps ;
int rc ;
/* Initialise and populate phy_data */
phy_data = kzalloc ( sizeof ( * phy_data ) , GFP_KERNEL ) ;
if ( phy_data = = NULL )
return - ENOMEM ;
rc = efx_mcdi_get_phy_cfg ( efx , phy_data ) ;
if ( rc ! = 0 )
goto fail ;
/* Read initial link advertisement */
BUILD_BUG_ON ( MC_CMD_GET_LINK_IN_LEN ! = 0 ) ;
rc = efx_mcdi_rpc ( efx , MC_CMD_GET_LINK , NULL , 0 ,
outbuf , sizeof ( outbuf ) , NULL ) ;
if ( rc )
goto fail ;
/* Fill out nic state */
efx - > phy_data = phy_data ;
efx - > phy_type = phy_data - > type ;
efx - > mdio_bus = phy_data - > channel ;
efx - > mdio . prtad = phy_data - > port ;
efx - > mdio . mmds = phy_data - > mmd_mask & ~ ( 1 < < MC_CMD_MMD_CLAUSE22 ) ;
efx - > mdio . mode_support = 0 ;
if ( phy_data - > mmd_mask & ( 1 < < MC_CMD_MMD_CLAUSE22 ) )
efx - > mdio . mode_support | = MDIO_SUPPORTS_C22 ;
if ( phy_data - > mmd_mask & ~ ( 1 < < MC_CMD_MMD_CLAUSE22 ) )
efx - > mdio . mode_support | = MDIO_SUPPORTS_C45 | MDIO_EMULATE_C22 ;
caps = MCDI_DWORD ( outbuf , GET_LINK_OUT_CAP ) ;
if ( caps & ( 1 < < MC_CMD_PHY_CAP_AN_LBN ) )
mcdi_to_ethtool_linkset ( phy_data - > media , caps ,
efx - > link_advertising ) ;
else
phy_data - > forced_cap = caps ;
/* Assert that we can map efx -> mcdi loopback modes */
BUILD_BUG_ON ( LOOPBACK_NONE ! = MC_CMD_LOOPBACK_NONE ) ;
BUILD_BUG_ON ( LOOPBACK_DATA ! = MC_CMD_LOOPBACK_DATA ) ;
BUILD_BUG_ON ( LOOPBACK_GMAC ! = MC_CMD_LOOPBACK_GMAC ) ;
BUILD_BUG_ON ( LOOPBACK_XGMII ! = MC_CMD_LOOPBACK_XGMII ) ;
BUILD_BUG_ON ( LOOPBACK_XGXS ! = MC_CMD_LOOPBACK_XGXS ) ;
BUILD_BUG_ON ( LOOPBACK_XAUI ! = MC_CMD_LOOPBACK_XAUI ) ;
BUILD_BUG_ON ( LOOPBACK_GMII ! = MC_CMD_LOOPBACK_GMII ) ;
BUILD_BUG_ON ( LOOPBACK_SGMII ! = MC_CMD_LOOPBACK_SGMII ) ;
BUILD_BUG_ON ( LOOPBACK_XGBR ! = MC_CMD_LOOPBACK_XGBR ) ;
BUILD_BUG_ON ( LOOPBACK_XFI ! = MC_CMD_LOOPBACK_XFI ) ;
BUILD_BUG_ON ( LOOPBACK_XAUI_FAR ! = MC_CMD_LOOPBACK_XAUI_FAR ) ;
BUILD_BUG_ON ( LOOPBACK_GMII_FAR ! = MC_CMD_LOOPBACK_GMII_FAR ) ;
BUILD_BUG_ON ( LOOPBACK_SGMII_FAR ! = MC_CMD_LOOPBACK_SGMII_FAR ) ;
BUILD_BUG_ON ( LOOPBACK_XFI_FAR ! = MC_CMD_LOOPBACK_XFI_FAR ) ;
BUILD_BUG_ON ( LOOPBACK_GPHY ! = MC_CMD_LOOPBACK_GPHY ) ;
BUILD_BUG_ON ( LOOPBACK_PHYXS ! = MC_CMD_LOOPBACK_PHYXS ) ;
BUILD_BUG_ON ( LOOPBACK_PCS ! = MC_CMD_LOOPBACK_PCS ) ;
BUILD_BUG_ON ( LOOPBACK_PMAPMD ! = MC_CMD_LOOPBACK_PMAPMD ) ;
BUILD_BUG_ON ( LOOPBACK_XPORT ! = MC_CMD_LOOPBACK_XPORT ) ;
BUILD_BUG_ON ( LOOPBACK_XGMII_WS ! = MC_CMD_LOOPBACK_XGMII_WS ) ;
BUILD_BUG_ON ( LOOPBACK_XAUI_WS ! = MC_CMD_LOOPBACK_XAUI_WS ) ;
BUILD_BUG_ON ( LOOPBACK_XAUI_WS_FAR ! = MC_CMD_LOOPBACK_XAUI_WS_FAR ) ;
BUILD_BUG_ON ( LOOPBACK_XAUI_WS_NEAR ! = MC_CMD_LOOPBACK_XAUI_WS_NEAR ) ;
BUILD_BUG_ON ( LOOPBACK_GMII_WS ! = MC_CMD_LOOPBACK_GMII_WS ) ;
BUILD_BUG_ON ( LOOPBACK_XFI_WS ! = MC_CMD_LOOPBACK_XFI_WS ) ;
BUILD_BUG_ON ( LOOPBACK_XFI_WS_FAR ! = MC_CMD_LOOPBACK_XFI_WS_FAR ) ;
BUILD_BUG_ON ( LOOPBACK_PHYXS_WS ! = MC_CMD_LOOPBACK_PHYXS_WS ) ;
rc = efx_mcdi_loopback_modes ( efx , & efx - > loopback_modes ) ;
if ( rc ! = 0 )
goto fail ;
/* The MC indicates that LOOPBACK_NONE is a valid loopback mode,
2020-09-08 20:22:19 +01:00
* but by convention we don ' t
*/
2020-09-07 17:14:34 +01:00
efx - > loopback_modes & = ~ ( 1 < < LOOPBACK_NONE ) ;
/* Set the initial link mode */
2020-09-08 20:22:19 +01:00
efx_mcdi_phy_decode_link ( efx , & efx - > link_state ,
MCDI_DWORD ( outbuf , GET_LINK_OUT_LINK_SPEED ) ,
MCDI_DWORD ( outbuf , GET_LINK_OUT_FLAGS ) ,
MCDI_DWORD ( outbuf , GET_LINK_OUT_FCNTL ) ) ;
2020-09-07 17:14:34 +01:00
/* Record the initial FEC configuration (or nearest approximation
* representable in the ethtool configuration space )
*/
efx - > fec_config = mcdi_fec_caps_to_ethtool ( caps ,
efx - > link_state . speed = = 25000 | |
efx - > link_state . speed = = 50000 ) ;
/* Default to Autonegotiated flow control if the PHY supports it */
efx - > wanted_fc = EFX_FC_RX | EFX_FC_TX ;
if ( phy_data - > supported_cap & ( 1 < < MC_CMD_PHY_CAP_AN_LBN ) )
efx - > wanted_fc | = EFX_FC_AUTO ;
efx_link_set_wanted_fc ( efx , efx - > wanted_fc ) ;
return 0 ;
fail :
kfree ( phy_data ) ;
return rc ;
}
void efx_mcdi_phy_remove ( struct efx_nic * efx )
{
struct efx_mcdi_phy_data * phy_data = efx - > phy_data ;
efx - > phy_data = NULL ;
kfree ( phy_data ) ;
}
void efx_mcdi_phy_get_link_ksettings ( struct efx_nic * efx , struct ethtool_link_ksettings * cmd )
{
struct efx_mcdi_phy_data * phy_cfg = efx - > phy_data ;
MCDI_DECLARE_BUF ( outbuf , MC_CMD_GET_LINK_OUT_LEN ) ;
int rc ;
cmd - > base . speed = efx - > link_state . speed ;
cmd - > base . duplex = efx - > link_state . fd ;
cmd - > base . port = mcdi_to_ethtool_media ( phy_cfg - > media ) ;
cmd - > base . phy_address = phy_cfg - > port ;
cmd - > base . autoneg = ! ! ( efx - > link_advertising [ 0 ] & ADVERTISED_Autoneg ) ;
cmd - > base . mdio_support = ( efx - > mdio . mode_support &
( MDIO_SUPPORTS_C45 | MDIO_SUPPORTS_C22 ) ) ;
mcdi_to_ethtool_linkset ( phy_cfg - > media , phy_cfg - > supported_cap ,
cmd - > link_modes . supported ) ;
memcpy ( cmd - > link_modes . advertising , efx - > link_advertising ,
sizeof ( __ETHTOOL_DECLARE_LINK_MODE_MASK ( ) ) ) ;
BUILD_BUG_ON ( MC_CMD_GET_LINK_IN_LEN ! = 0 ) ;
rc = efx_mcdi_rpc ( efx , MC_CMD_GET_LINK , NULL , 0 ,
outbuf , sizeof ( outbuf ) , NULL ) ;
if ( rc )
return ;
mcdi_to_ethtool_linkset ( phy_cfg - > media ,
MCDI_DWORD ( outbuf , GET_LINK_OUT_LP_CAP ) ,
cmd - > link_modes . lp_advertising ) ;
}
int efx_mcdi_phy_set_link_ksettings ( struct efx_nic * efx , const struct ethtool_link_ksettings * cmd )
{
struct efx_mcdi_phy_data * phy_cfg = efx - > phy_data ;
u32 caps ;
int rc ;
if ( cmd - > base . autoneg ) {
caps = ( ethtool_linkset_to_mcdi_cap ( cmd - > link_modes . advertising ) |
1 < < MC_CMD_PHY_CAP_AN_LBN ) ;
} else if ( cmd - > base . duplex ) {
switch ( cmd - > base . speed ) {
case 10 : caps = 1 < < MC_CMD_PHY_CAP_10FDX_LBN ; break ;
case 100 : caps = 1 < < MC_CMD_PHY_CAP_100FDX_LBN ; break ;
case 1000 : caps = 1 < < MC_CMD_PHY_CAP_1000FDX_LBN ; break ;
case 10000 : caps = 1 < < MC_CMD_PHY_CAP_10000FDX_LBN ; break ;
case 40000 : caps = 1 < < MC_CMD_PHY_CAP_40000FDX_LBN ; break ;
case 100000 : caps = 1 < < MC_CMD_PHY_CAP_100000FDX_LBN ; break ;
case 25000 : caps = 1 < < MC_CMD_PHY_CAP_25000FDX_LBN ; break ;
case 50000 : caps = 1 < < MC_CMD_PHY_CAP_50000FDX_LBN ; break ;
default : return - EINVAL ;
}
} else {
switch ( cmd - > base . speed ) {
case 10 : caps = 1 < < MC_CMD_PHY_CAP_10HDX_LBN ; break ;
case 100 : caps = 1 < < MC_CMD_PHY_CAP_100HDX_LBN ; break ;
case 1000 : caps = 1 < < MC_CMD_PHY_CAP_1000HDX_LBN ; break ;
default : return - EINVAL ;
}
}
2020-09-07 17:15:08 +01:00
caps | = ethtool_fec_caps_to_mcdi ( phy_cfg - > supported_cap , efx - > fec_config ) ;
2020-09-07 17:14:34 +01:00
rc = efx_mcdi_set_link ( efx , caps , efx_get_mcdi_phy_flags ( efx ) ,
efx - > loopback_mode , 0 ) ;
if ( rc )
return rc ;
if ( cmd - > base . autoneg ) {
efx_link_set_advertising ( efx , cmd - > link_modes . advertising ) ;
phy_cfg - > forced_cap = 0 ;
} else {
efx_link_clear_advertising ( efx ) ;
phy_cfg - > forced_cap = caps ;
}
return 0 ;
}
2020-01-09 15:44:12 +00:00
int efx_mcdi_phy_get_fecparam ( struct efx_nic * efx , struct ethtool_fecparam * fec )
{
MCDI_DECLARE_BUF ( outbuf , MC_CMD_GET_LINK_OUT_V2_LEN ) ;
u32 caps , active , speed ; /* MCDI format */
bool is_25g = false ;
size_t outlen ;
int rc ;
BUILD_BUG_ON ( MC_CMD_GET_LINK_IN_LEN ! = 0 ) ;
rc = efx_mcdi_rpc ( efx , MC_CMD_GET_LINK , NULL , 0 ,
outbuf , sizeof ( outbuf ) , & outlen ) ;
if ( rc )
return rc ;
if ( outlen < MC_CMD_GET_LINK_OUT_V2_LEN )
return - EOPNOTSUPP ;
/* behaviour for 25G/50G links depends on 25G BASER bit */
speed = MCDI_DWORD ( outbuf , GET_LINK_OUT_V2_LINK_SPEED ) ;
is_25g = speed = = 25000 | | speed = = 50000 ;
caps = MCDI_DWORD ( outbuf , GET_LINK_OUT_V2_CAP ) ;
fec - > fec = mcdi_fec_caps_to_ethtool ( caps , is_25g ) ;
/* BASER is never supported on 100G */
if ( speed = = 100000 )
fec - > fec & = ~ ETHTOOL_FEC_BASER ;
active = MCDI_DWORD ( outbuf , GET_LINK_OUT_V2_FEC_TYPE ) ;
switch ( active ) {
case MC_CMD_FEC_NONE :
fec - > active_fec = ETHTOOL_FEC_OFF ;
break ;
case MC_CMD_FEC_BASER :
fec - > active_fec = ETHTOOL_FEC_BASER ;
break ;
case MC_CMD_FEC_RS :
fec - > active_fec = ETHTOOL_FEC_RS ;
break ;
default :
netif_warn ( efx , hw , efx - > net_dev ,
" Firmware reports unrecognised FEC_TYPE %u \n " ,
active ) ;
/* We don't know what firmware has picked. AUTO is as good a
* " can't happen " value as any other .
*/
fec - > active_fec = ETHTOOL_FEC_AUTO ;
break ;
}
return 0 ;
}
2020-09-07 17:15:08 +01:00
/* Basic validation to ensure that the caps we are going to attempt to set are
* in fact supported by the adapter . Note that ' no FEC ' is always supported .
*/
static int ethtool_fec_supported ( u32 supported_cap , u32 ethtool_cap )
{
if ( ethtool_cap & ETHTOOL_FEC_OFF )
return 0 ;
if ( ethtool_cap & &
! ethtool_fec_caps_to_mcdi ( supported_cap , ethtool_cap ) )
return - EINVAL ;
return 0 ;
}
2020-09-07 17:14:34 +01:00
int efx_mcdi_phy_set_fecparam ( struct efx_nic * efx , const struct ethtool_fecparam * fec )
{
struct efx_mcdi_phy_data * phy_cfg = efx - > phy_data ;
u32 caps ;
int rc ;
2020-09-07 17:15:08 +01:00
rc = ethtool_fec_supported ( phy_cfg - > supported_cap , fec - > fec ) ;
if ( rc )
return rc ;
2020-09-07 17:14:34 +01:00
/* Work out what efx_mcdi_phy_set_link_ksettings() would produce from
* saved advertising bits
*/
if ( test_bit ( ETHTOOL_LINK_MODE_Autoneg_BIT , efx - > link_advertising ) )
caps = ( ethtool_linkset_to_mcdi_cap ( efx - > link_advertising ) |
1 < < MC_CMD_PHY_CAP_AN_LBN ) ;
else
caps = phy_cfg - > forced_cap ;
2020-09-07 17:15:08 +01:00
caps | = ethtool_fec_caps_to_mcdi ( phy_cfg - > supported_cap , fec - > fec ) ;
2020-09-07 17:14:34 +01:00
rc = efx_mcdi_set_link ( efx , caps , efx_get_mcdi_phy_flags ( efx ) ,
efx - > loopback_mode , 0 ) ;
if ( rc )
return rc ;
/* Record the new FEC setting for subsequent set_link calls */
efx - > fec_config = fec - > fec ;
return 0 ;
}
2020-01-09 15:44:12 +00:00
int efx_mcdi_phy_test_alive ( struct efx_nic * efx )
{
MCDI_DECLARE_BUF ( outbuf , MC_CMD_GET_PHY_STATE_OUT_LEN ) ;
size_t outlen ;
int rc ;
BUILD_BUG_ON ( MC_CMD_GET_PHY_STATE_IN_LEN ! = 0 ) ;
rc = efx_mcdi_rpc ( efx , MC_CMD_GET_PHY_STATE , NULL , 0 ,
outbuf , sizeof ( outbuf ) , & outlen ) ;
if ( rc )
return rc ;
if ( outlen < MC_CMD_GET_PHY_STATE_OUT_LEN )
return - EIO ;
if ( MCDI_DWORD ( outbuf , GET_PHY_STATE_OUT_STATE ) ! = MC_CMD_PHY_STATE_OK )
return - EINVAL ;
return 0 ;
}
2020-06-30 13:11:35 +01:00
int efx_mcdi_port_reconfigure ( struct efx_nic * efx )
{
struct efx_mcdi_phy_data * phy_cfg = efx - > phy_data ;
u32 caps = ( efx - > link_advertising [ 0 ] ?
ethtool_linkset_to_mcdi_cap ( efx - > link_advertising ) :
phy_cfg - > forced_cap ) ;
2020-09-07 17:15:08 +01:00
caps | = ethtool_fec_caps_to_mcdi ( phy_cfg - > supported_cap , efx - > fec_config ) ;
2020-06-30 13:11:35 +01:00
return efx_mcdi_set_link ( efx , caps , efx_get_mcdi_phy_flags ( efx ) ,
efx - > loopback_mode , 0 ) ;
}
2020-09-07 17:14:34 +01:00
static const char * const mcdi_sft9001_cable_diag_names [ ] = {
" cable.pairA.length " ,
" cable.pairB.length " ,
" cable.pairC.length " ,
" cable.pairD.length " ,
" cable.pairA.status " ,
" cable.pairB.status " ,
" cable.pairC.status " ,
" cable.pairD.status " ,
} ;
static int efx_mcdi_bist ( struct efx_nic * efx , unsigned int bist_mode ,
int * results )
{
unsigned int retry , i , count = 0 ;
size_t outlen ;
u32 status ;
MCDI_DECLARE_BUF ( inbuf , MC_CMD_START_BIST_IN_LEN ) ;
MCDI_DECLARE_BUF ( outbuf , MC_CMD_POLL_BIST_OUT_SFT9001_LEN ) ;
u8 * ptr ;
int rc ;
BUILD_BUG_ON ( MC_CMD_START_BIST_OUT_LEN ! = 0 ) ;
MCDI_SET_DWORD ( inbuf , START_BIST_IN_TYPE , bist_mode ) ;
rc = efx_mcdi_rpc ( efx , MC_CMD_START_BIST ,
inbuf , MC_CMD_START_BIST_IN_LEN , NULL , 0 , NULL ) ;
if ( rc )
goto out ;
/* Wait up to 10s for BIST to finish */
for ( retry = 0 ; retry < 100 ; + + retry ) {
BUILD_BUG_ON ( MC_CMD_POLL_BIST_IN_LEN ! = 0 ) ;
rc = efx_mcdi_rpc ( efx , MC_CMD_POLL_BIST , NULL , 0 ,
outbuf , sizeof ( outbuf ) , & outlen ) ;
if ( rc )
goto out ;
status = MCDI_DWORD ( outbuf , POLL_BIST_OUT_RESULT ) ;
if ( status ! = MC_CMD_POLL_BIST_RUNNING )
goto finished ;
msleep ( 100 ) ;
}
rc = - ETIMEDOUT ;
goto out ;
finished :
results [ count + + ] = ( status = = MC_CMD_POLL_BIST_PASSED ) ? 1 : - 1 ;
/* SFT9001 specific cable diagnostics output */
if ( efx - > phy_type = = PHY_TYPE_SFT9001B & &
( bist_mode = = MC_CMD_PHY_BIST_CABLE_SHORT | |
bist_mode = = MC_CMD_PHY_BIST_CABLE_LONG ) ) {
ptr = MCDI_PTR ( outbuf , POLL_BIST_OUT_SFT9001_CABLE_LENGTH_A ) ;
if ( status = = MC_CMD_POLL_BIST_PASSED & &
outlen > = MC_CMD_POLL_BIST_OUT_SFT9001_LEN ) {
for ( i = 0 ; i < 8 ; i + + ) {
results [ count + i ] =
EFX_DWORD_FIELD ( ( ( efx_dword_t * ) ptr ) [ i ] ,
EFX_DWORD_0 ) ;
}
}
count + = 8 ;
}
rc = count ;
out :
return rc ;
}
2020-09-08 20:22:19 +01:00
int efx_mcdi_phy_run_tests ( struct efx_nic * efx , int * results , unsigned int flags )
2020-09-07 17:14:34 +01:00
{
struct efx_mcdi_phy_data * phy_cfg = efx - > phy_data ;
u32 mode ;
int rc ;
if ( phy_cfg - > flags & ( 1 < < MC_CMD_GET_PHY_CFG_OUT_BIST_LBN ) ) {
rc = efx_mcdi_bist ( efx , MC_CMD_PHY_BIST , results ) ;
if ( rc < 0 )
return rc ;
results + = rc ;
}
/* If we support both LONG and SHORT, then run each in response to
2020-09-08 20:22:19 +01:00
* break or not . Otherwise , run the one we support
*/
2020-09-07 17:14:34 +01:00
mode = 0 ;
if ( phy_cfg - > flags & ( 1 < < MC_CMD_GET_PHY_CFG_OUT_BIST_CABLE_SHORT_LBN ) ) {
if ( ( flags & ETH_TEST_FL_OFFLINE ) & &
( phy_cfg - > flags &
( 1 < < MC_CMD_GET_PHY_CFG_OUT_BIST_CABLE_LONG_LBN ) ) )
mode = MC_CMD_PHY_BIST_CABLE_LONG ;
else
mode = MC_CMD_PHY_BIST_CABLE_SHORT ;
} else if ( phy_cfg - > flags &
( 1 < < MC_CMD_GET_PHY_CFG_OUT_BIST_CABLE_LONG_LBN ) )
mode = MC_CMD_PHY_BIST_CABLE_LONG ;
if ( mode ! = 0 ) {
rc = efx_mcdi_bist ( efx , mode , results ) ;
if ( rc < 0 )
return rc ;
results + = rc ;
}
return 0 ;
}
const char * efx_mcdi_phy_test_name ( struct efx_nic * efx , unsigned int index )
{
struct efx_mcdi_phy_data * phy_cfg = efx - > phy_data ;
if ( phy_cfg - > flags & ( 1 < < MC_CMD_GET_PHY_CFG_OUT_BIST_LBN ) ) {
if ( index = = 0 )
return " bist " ;
- - index ;
}
if ( phy_cfg - > flags & ( ( 1 < < MC_CMD_GET_PHY_CFG_OUT_BIST_CABLE_SHORT_LBN ) |
( 1 < < MC_CMD_GET_PHY_CFG_OUT_BIST_CABLE_LONG_LBN ) ) ) {
if ( index = = 0 )
return " cable " ;
- - index ;
if ( efx - > phy_type = = PHY_TYPE_SFT9001B ) {
if ( index < ARRAY_SIZE ( mcdi_sft9001_cable_diag_names ) )
return mcdi_sft9001_cable_diag_names [ index ] ;
index - = ARRAY_SIZE ( mcdi_sft9001_cable_diag_names ) ;
}
}
return NULL ;
}
# define SFP_PAGE_SIZE 128
# define SFF_DIAG_TYPE_OFFSET 92
# define SFF_DIAG_ADDR_CHANGE BIT(2)
# define SFF_8079_NUM_PAGES 2
# define SFF_8472_NUM_PAGES 4
# define SFF_8436_NUM_PAGES 5
# define SFF_DMT_LEVEL_OFFSET 94
/** efx_mcdi_phy_get_module_eeprom_page() - Get a single page of module eeprom
* @ efx : NIC context
* @ page : EEPROM page number
* @ data : Destination data pointer
* @ offset : Offset in page to copy from in to data
* @ space : Space available in data
*
* Return :
* > = 0 - amount of data copied
* < 0 - error
*/
static int efx_mcdi_phy_get_module_eeprom_page ( struct efx_nic * efx ,
unsigned int page ,
u8 * data , ssize_t offset ,
ssize_t space )
{
MCDI_DECLARE_BUF ( outbuf , MC_CMD_GET_PHY_MEDIA_INFO_OUT_LENMAX ) ;
MCDI_DECLARE_BUF ( inbuf , MC_CMD_GET_PHY_MEDIA_INFO_IN_LEN ) ;
unsigned int payload_len ;
unsigned int to_copy ;
2020-09-08 20:22:19 +01:00
size_t outlen ;
2020-09-07 17:14:34 +01:00
int rc ;
if ( offset > SFP_PAGE_SIZE )
return - EINVAL ;
to_copy = min ( space , SFP_PAGE_SIZE - offset ) ;
MCDI_SET_DWORD ( inbuf , GET_PHY_MEDIA_INFO_IN_PAGE , page ) ;
rc = efx_mcdi_rpc_quiet ( efx , MC_CMD_GET_PHY_MEDIA_INFO ,
inbuf , sizeof ( inbuf ) ,
outbuf , sizeof ( outbuf ) ,
& outlen ) ;
if ( rc )
return rc ;
if ( outlen < ( MC_CMD_GET_PHY_MEDIA_INFO_OUT_DATA_OFST +
SFP_PAGE_SIZE ) )
return - EIO ;
payload_len = MCDI_DWORD ( outbuf , GET_PHY_MEDIA_INFO_OUT_DATALEN ) ;
if ( payload_len ! = SFP_PAGE_SIZE )
return - EIO ;
memcpy ( data , MCDI_PTR ( outbuf , GET_PHY_MEDIA_INFO_OUT_DATA ) + offset ,
to_copy ) ;
return to_copy ;
}
static int efx_mcdi_phy_get_module_eeprom_byte ( struct efx_nic * efx ,
unsigned int page ,
u8 byte )
{
u8 data ;
2020-09-08 20:22:19 +01:00
int rc ;
2020-09-07 17:14:34 +01:00
rc = efx_mcdi_phy_get_module_eeprom_page ( efx , page , & data , byte , 1 ) ;
if ( rc = = 1 )
return data ;
return rc ;
}
static int efx_mcdi_phy_diag_type ( struct efx_nic * efx )
{
/* Page zero of the EEPROM includes the diagnostic type at byte 92. */
return efx_mcdi_phy_get_module_eeprom_byte ( efx , 0 ,
SFF_DIAG_TYPE_OFFSET ) ;
}
static int efx_mcdi_phy_sff_8472_level ( struct efx_nic * efx )
{
/* Page zero of the EEPROM includes the DMT level at byte 94. */
return efx_mcdi_phy_get_module_eeprom_byte ( efx , 0 ,
SFF_DMT_LEVEL_OFFSET ) ;
}
static u32 efx_mcdi_phy_module_type ( struct efx_nic * efx )
{
struct efx_mcdi_phy_data * phy_data = efx - > phy_data ;
if ( phy_data - > media ! = MC_CMD_MEDIA_QSFP_PLUS )
return phy_data - > media ;
/* A QSFP+ NIC may actually have an SFP+ module attached.
* The ID is page 0 , byte 0.
*/
switch ( efx_mcdi_phy_get_module_eeprom_byte ( efx , 0 , 0 ) ) {
case 0x3 :
return MC_CMD_MEDIA_SFP_PLUS ;
case 0xc :
case 0xd :
return MC_CMD_MEDIA_QSFP_PLUS ;
default :
return 0 ;
}
}
int efx_mcdi_phy_get_module_eeprom ( struct efx_nic * efx , struct ethtool_eeprom * ee , u8 * data )
{
int rc ;
ssize_t space_remaining = ee - > len ;
unsigned int page_off ;
bool ignore_missing ;
int num_pages ;
int page ;
switch ( efx_mcdi_phy_module_type ( efx ) ) {
case MC_CMD_MEDIA_SFP_PLUS :
num_pages = efx_mcdi_phy_sff_8472_level ( efx ) > 0 ?
SFF_8472_NUM_PAGES : SFF_8079_NUM_PAGES ;
page = 0 ;
ignore_missing = false ;
break ;
case MC_CMD_MEDIA_QSFP_PLUS :
num_pages = SFF_8436_NUM_PAGES ;
page = - 1 ; /* We obtain the lower page by asking for -1. */
ignore_missing = true ; /* Ignore missing pages after page 0. */
break ;
default :
return - EOPNOTSUPP ;
}
page_off = ee - > offset % SFP_PAGE_SIZE ;
page + = ee - > offset / SFP_PAGE_SIZE ;
while ( space_remaining & & ( page < num_pages ) ) {
rc = efx_mcdi_phy_get_module_eeprom_page ( efx , page ,
data , page_off ,
space_remaining ) ;
if ( rc > 0 ) {
space_remaining - = rc ;
data + = rc ;
page_off = 0 ;
page + + ;
} else if ( rc = = 0 ) {
space_remaining = 0 ;
} else if ( ignore_missing & & ( page > 0 ) ) {
int intended_size = SFP_PAGE_SIZE - page_off ;
space_remaining - = intended_size ;
if ( space_remaining < 0 ) {
space_remaining = 0 ;
} else {
memset ( data , 0 , intended_size ) ;
data + = intended_size ;
page_off = 0 ;
page + + ;
rc = 0 ;
}
} else {
return rc ;
}
}
return 0 ;
}
int efx_mcdi_phy_get_module_info ( struct efx_nic * efx , struct ethtool_modinfo * modinfo )
{
int sff_8472_level ;
int diag_type ;
switch ( efx_mcdi_phy_module_type ( efx ) ) {
case MC_CMD_MEDIA_SFP_PLUS :
sff_8472_level = efx_mcdi_phy_sff_8472_level ( efx ) ;
/* If we can't read the diagnostics level we have none. */
if ( sff_8472_level < 0 )
return - EOPNOTSUPP ;
/* Check if this module requires the (unsupported) address
* change operation .
*/
diag_type = efx_mcdi_phy_diag_type ( efx ) ;
2020-09-08 20:22:19 +01:00
if ( sff_8472_level = = 0 | |
2020-09-07 17:14:34 +01:00
( diag_type & SFF_DIAG_ADDR_CHANGE ) ) {
modinfo - > type = ETH_MODULE_SFF_8079 ;
modinfo - > eeprom_len = ETH_MODULE_SFF_8079_LEN ;
} else {
modinfo - > type = ETH_MODULE_SFF_8472 ;
modinfo - > eeprom_len = ETH_MODULE_SFF_8472_LEN ;
}
break ;
case MC_CMD_MEDIA_QSFP_PLUS :
modinfo - > type = ETH_MODULE_SFF_8436 ;
modinfo - > eeprom_len = ETH_MODULE_SFF_8436_LEN ;
break ;
default :
return - EOPNOTSUPP ;
}
return 0 ;
}
2020-06-30 13:11:35 +01:00
static unsigned int efx_calc_mac_mtu ( struct efx_nic * efx )
{
return EFX_MAX_FRAME_LEN ( efx - > net_dev - > mtu ) ;
}
2020-01-10 13:27:17 +00:00
int efx_mcdi_set_mac ( struct efx_nic * efx )
{
u32 fcntl ;
MCDI_DECLARE_BUF ( cmdbytes , MC_CMD_SET_MAC_IN_LEN ) ;
BUILD_BUG_ON ( MC_CMD_SET_MAC_OUT_LEN ! = 0 ) ;
/* This has no effect on EF10 */
ether_addr_copy ( MCDI_PTR ( cmdbytes , SET_MAC_IN_ADDR ) ,
efx - > net_dev - > dev_addr ) ;
2020-06-30 13:11:35 +01:00
MCDI_SET_DWORD ( cmdbytes , SET_MAC_IN_MTU , efx_calc_mac_mtu ( efx ) ) ;
2020-01-10 13:27:17 +00:00
MCDI_SET_DWORD ( cmdbytes , SET_MAC_IN_DRAIN , 0 ) ;
/* Set simple MAC filter for Siena */
MCDI_POPULATE_DWORD_1 ( cmdbytes , SET_MAC_IN_REJECT ,
SET_MAC_IN_REJECT_UNCST , efx - > unicast_filter ) ;
MCDI_POPULATE_DWORD_1 ( cmdbytes , SET_MAC_IN_FLAGS ,
SET_MAC_IN_FLAG_INCLUDE_FCS ,
! ! ( efx - > net_dev - > features & NETIF_F_RXFCS ) ) ;
switch ( efx - > wanted_fc ) {
case EFX_FC_RX | EFX_FC_TX :
fcntl = MC_CMD_FCNTL_BIDIR ;
break ;
case EFX_FC_RX :
fcntl = MC_CMD_FCNTL_RESPOND ;
break ;
default :
fcntl = MC_CMD_FCNTL_OFF ;
break ;
}
if ( efx - > wanted_fc & EFX_FC_AUTO )
fcntl = MC_CMD_FCNTL_AUTO ;
if ( efx - > fc_disable )
fcntl = MC_CMD_FCNTL_OFF ;
MCDI_SET_DWORD ( cmdbytes , SET_MAC_IN_FCNTL , fcntl ) ;
return efx_mcdi_rpc ( efx , MC_CMD_SET_MAC , cmdbytes , sizeof ( cmdbytes ) ,
NULL , 0 , NULL ) ;
}
2020-06-30 13:11:35 +01:00
int efx_mcdi_set_mtu ( struct efx_nic * efx )
{
MCDI_DECLARE_BUF ( inbuf , MC_CMD_SET_MAC_EXT_IN_LEN ) ;
BUILD_BUG_ON ( MC_CMD_SET_MAC_OUT_LEN ! = 0 ) ;
MCDI_SET_DWORD ( inbuf , SET_MAC_EXT_IN_MTU , efx_calc_mac_mtu ( efx ) ) ;
MCDI_POPULATE_DWORD_1 ( inbuf , SET_MAC_EXT_IN_CONTROL ,
SET_MAC_EXT_IN_CFG_MTU , 1 ) ;
return efx_mcdi_rpc ( efx , MC_CMD_SET_MAC , inbuf , sizeof ( inbuf ) ,
NULL , 0 , NULL ) ;
}
2020-06-30 13:02:56 +01:00
enum efx_stats_action {
EFX_STATS_ENABLE ,
EFX_STATS_DISABLE ,
EFX_STATS_PULL ,
} ;
static int efx_mcdi_mac_stats ( struct efx_nic * efx ,
enum efx_stats_action action , int clear )
{
MCDI_DECLARE_BUF ( inbuf , MC_CMD_MAC_STATS_IN_LEN ) ;
int rc ;
int change = action = = EFX_STATS_PULL ? 0 : 1 ;
int enable = action = = EFX_STATS_ENABLE ? 1 : 0 ;
int period = action = = EFX_STATS_ENABLE ? 1000 : 0 ;
dma_addr_t dma_addr = efx - > stats_buffer . dma_addr ;
u32 dma_len = action ! = EFX_STATS_DISABLE ?
efx - > num_mac_stats * sizeof ( u64 ) : 0 ;
BUILD_BUG_ON ( MC_CMD_MAC_STATS_OUT_DMA_LEN ! = 0 ) ;
MCDI_SET_QWORD ( inbuf , MAC_STATS_IN_DMA_ADDR , dma_addr ) ;
MCDI_POPULATE_DWORD_7 ( inbuf , MAC_STATS_IN_CMD ,
MAC_STATS_IN_DMA , ! ! enable ,
MAC_STATS_IN_CLEAR , clear ,
MAC_STATS_IN_PERIODIC_CHANGE , change ,
MAC_STATS_IN_PERIODIC_ENABLE , enable ,
MAC_STATS_IN_PERIODIC_CLEAR , 0 ,
MAC_STATS_IN_PERIODIC_NOEVENT , 1 ,
MAC_STATS_IN_PERIOD_MS , period ) ;
MCDI_SET_DWORD ( inbuf , MAC_STATS_IN_DMA_LEN , dma_len ) ;
if ( efx_nic_rev ( efx ) > = EFX_REV_HUNT_A0 )
MCDI_SET_DWORD ( inbuf , MAC_STATS_IN_PORT_ID , efx - > vport_id ) ;
rc = efx_mcdi_rpc_quiet ( efx , MC_CMD_MAC_STATS , inbuf , sizeof ( inbuf ) ,
NULL , 0 , NULL ) ;
/* Expect ENOENT if DMA queues have not been set up */
if ( rc & & ( rc ! = - ENOENT | | atomic_read ( & efx - > active_queues ) ) )
efx_mcdi_display_error ( efx , MC_CMD_MAC_STATS , sizeof ( inbuf ) ,
NULL , 0 , rc ) ;
return rc ;
}
void efx_mcdi_mac_start_stats ( struct efx_nic * efx )
{
__le64 * dma_stats = efx - > stats_buffer . addr ;
dma_stats [ efx - > num_mac_stats - 1 ] = EFX_MC_STATS_GENERATION_INVALID ;
efx_mcdi_mac_stats ( efx , EFX_STATS_ENABLE , 0 ) ;
}
void efx_mcdi_mac_stop_stats ( struct efx_nic * efx )
{
efx_mcdi_mac_stats ( efx , EFX_STATS_DISABLE , 0 ) ;
}
# define EFX_MAC_STATS_WAIT_US 100
# define EFX_MAC_STATS_WAIT_ATTEMPTS 10
void efx_mcdi_mac_pull_stats ( struct efx_nic * efx )
{
__le64 * dma_stats = efx - > stats_buffer . addr ;
int attempts = EFX_MAC_STATS_WAIT_ATTEMPTS ;
dma_stats [ efx - > num_mac_stats - 1 ] = EFX_MC_STATS_GENERATION_INVALID ;
efx_mcdi_mac_stats ( efx , EFX_STATS_PULL , 0 ) ;
while ( dma_stats [ efx - > num_mac_stats - 1 ] = =
EFX_MC_STATS_GENERATION_INVALID & &
attempts - - ! = 0 )
udelay ( EFX_MAC_STATS_WAIT_US ) ;
}
int efx_mcdi_mac_init_stats ( struct efx_nic * efx )
{
int rc ;
if ( ! efx - > num_mac_stats )
return 0 ;
/* Allocate buffer for stats */
rc = efx_nic_alloc_buffer ( efx , & efx - > stats_buffer ,
efx - > num_mac_stats * sizeof ( u64 ) , GFP_KERNEL ) ;
if ( rc ) {
netif_warn ( efx , probe , efx - > net_dev ,
" failed to allocate DMA buffer: %d \n " , rc ) ;
return rc ;
}
netif_dbg ( efx , probe , efx - > net_dev ,
" stats buffer at %llx (virt %p phys %llx) \n " ,
( u64 ) efx - > stats_buffer . dma_addr ,
efx - > stats_buffer . addr ,
( u64 ) virt_to_phys ( efx - > stats_buffer . addr ) ) ;
return 0 ;
}
void efx_mcdi_mac_fini_stats ( struct efx_nic * efx )
{
efx_nic_free_buffer ( efx , & efx - > stats_buffer ) ;
}
2020-01-09 15:44:12 +00:00
/* Get physical port number (EF10 only; on Siena it is same as PF number) */
int efx_mcdi_port_get_number ( struct efx_nic * efx )
{
MCDI_DECLARE_BUF ( outbuf , MC_CMD_GET_PORT_ASSIGNMENT_OUT_LEN ) ;
int rc ;
rc = efx_mcdi_rpc ( efx , MC_CMD_GET_PORT_ASSIGNMENT , NULL , 0 ,
outbuf , sizeof ( outbuf ) , NULL ) ;
if ( rc )
return rc ;
return MCDI_DWORD ( outbuf , GET_PORT_ASSIGNMENT_OUT_PORT ) ;
}
2020-01-10 13:27:50 +00:00
static unsigned int efx_mcdi_event_link_speed [ ] = {
[ MCDI_EVENT_LINKCHANGE_SPEED_100M ] = 100 ,
[ MCDI_EVENT_LINKCHANGE_SPEED_1G ] = 1000 ,
[ MCDI_EVENT_LINKCHANGE_SPEED_10G ] = 10000 ,
[ MCDI_EVENT_LINKCHANGE_SPEED_40G ] = 40000 ,
[ MCDI_EVENT_LINKCHANGE_SPEED_25G ] = 25000 ,
[ MCDI_EVENT_LINKCHANGE_SPEED_50G ] = 50000 ,
[ MCDI_EVENT_LINKCHANGE_SPEED_100G ] = 100000 ,
} ;
void efx_mcdi_process_link_change ( struct efx_nic * efx , efx_qword_t * ev )
{
u32 flags , fcntl , speed , lpa ;
speed = EFX_QWORD_FIELD ( * ev , MCDI_EVENT_LINKCHANGE_SPEED ) ;
EFX_WARN_ON_PARANOID ( speed > = ARRAY_SIZE ( efx_mcdi_event_link_speed ) ) ;
speed = efx_mcdi_event_link_speed [ speed ] ;
flags = EFX_QWORD_FIELD ( * ev , MCDI_EVENT_LINKCHANGE_LINK_FLAGS ) ;
fcntl = EFX_QWORD_FIELD ( * ev , MCDI_EVENT_LINKCHANGE_FCNTL ) ;
lpa = EFX_QWORD_FIELD ( * ev , MCDI_EVENT_LINKCHANGE_LP_CAP ) ;
/* efx->link_state is only modified by efx_mcdi_phy_get_link(),
* which is only run after flushing the event queues . Therefore , it
* is safe to modify the link state outside of the mac_lock here .
*/
efx_mcdi_phy_decode_link ( efx , & efx - > link_state , speed , flags , fcntl ) ;
efx_mcdi_phy_check_fcntl ( efx , lpa ) ;
efx_link_status_changed ( efx ) ;
}