Link: https://lore.kernel.org/r/20260217200002.683975158@linuxfoundation.org Tested-by: Florian Fainelli <florian.fainelli@broadcom.com> Tested-by: Takeshi Ogasawara <takeshi.ogasawara@futuring-girl.com> Tested-by: Peter Schneider <pschneider1968@googlemail.com> Tested-by: Jon Hunter <jonathanh@nvidia.com> Tested-by: Salvatore Bonaccorso <carnil@debian.org> Tested-by: Brett A C Sheffield <bacs@librecast.net> Tested-by: Mark Brown <broonie@kernel.org> Tested-by: Luna Jernberg <droidbittin@gmail.com> Tested-by: Ronald Warsow <rwarsow@gmx.de> Tested-by: Justin M. Forbes <jforbes@fedoraproject.org> Tested-by: Ron Economos <re@w6rz.net> Tested-by: Miguel Ojeda <ojeda@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
312 lines
8.6 KiB
C
312 lines
8.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
|
|
|
|
#include <linux/pcs/pcs-xpcs.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/phylink.h>
|
|
|
|
#include "fbnic.h"
|
|
#include "fbnic_mac.h"
|
|
#include "fbnic_netdev.h"
|
|
|
|
static phy_interface_t fbnic_phylink_select_interface(u8 aui)
|
|
{
|
|
switch (aui) {
|
|
case FBNIC_AUI_100GAUI2:
|
|
return PHY_INTERFACE_MODE_100GBASEP;
|
|
case FBNIC_AUI_50GAUI1:
|
|
return PHY_INTERFACE_MODE_50GBASER;
|
|
case FBNIC_AUI_LAUI2:
|
|
return PHY_INTERFACE_MODE_LAUI;
|
|
case FBNIC_AUI_25GAUI:
|
|
return PHY_INTERFACE_MODE_25GBASER;
|
|
}
|
|
|
|
return PHY_INTERFACE_MODE_NA;
|
|
}
|
|
|
|
void fbnic_phylink_get_pauseparam(struct net_device *netdev,
|
|
struct ethtool_pauseparam *pause)
|
|
{
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
|
|
phylink_ethtool_get_pauseparam(fbn->phylink, pause);
|
|
}
|
|
|
|
int fbnic_phylink_set_pauseparam(struct net_device *netdev,
|
|
struct ethtool_pauseparam *pause)
|
|
{
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
|
|
return phylink_ethtool_set_pauseparam(fbn->phylink, pause);
|
|
}
|
|
|
|
static void
|
|
fbnic_phylink_get_supported_fec_modes(unsigned long *supported)
|
|
{
|
|
/* The NIC can support up to 8 possible combinations.
|
|
* Either 50G-CR, or 100G-CR2
|
|
* This is with RS FEC mode only
|
|
* Either 25G-CR, or 50G-CR2
|
|
* This is with No FEC, RS, or Base-R
|
|
*/
|
|
if (phylink_test(supported, 100000baseCR2_Full) ||
|
|
phylink_test(supported, 50000baseCR_Full))
|
|
phylink_set(supported, FEC_RS);
|
|
if (phylink_test(supported, 50000baseCR2_Full) ||
|
|
phylink_test(supported, 25000baseCR_Full)) {
|
|
phylink_set(supported, FEC_BASER);
|
|
phylink_set(supported, FEC_NONE);
|
|
phylink_set(supported, FEC_RS);
|
|
}
|
|
}
|
|
|
|
int fbnic_phylink_ethtool_ksettings_get(struct net_device *netdev,
|
|
struct ethtool_link_ksettings *cmd)
|
|
{
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
int err;
|
|
|
|
err = phylink_ethtool_ksettings_get(fbn->phylink, cmd);
|
|
if (!err) {
|
|
unsigned long *supp = cmd->link_modes.supported;
|
|
|
|
cmd->base.port = PORT_DA;
|
|
cmd->lanes = (fbn->aui & FBNIC_AUI_MODE_R2) ? 2 : 1;
|
|
|
|
fbnic_phylink_get_supported_fec_modes(supp);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int fbnic_phylink_get_fecparam(struct net_device *netdev,
|
|
struct ethtool_fecparam *fecparam)
|
|
{
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
|
|
if (fbn->fec & FBNIC_FEC_RS) {
|
|
fecparam->active_fec = ETHTOOL_FEC_RS;
|
|
fecparam->fec = ETHTOOL_FEC_RS;
|
|
} else if (fbn->fec & FBNIC_FEC_BASER) {
|
|
fecparam->active_fec = ETHTOOL_FEC_BASER;
|
|
fecparam->fec = ETHTOOL_FEC_BASER;
|
|
} else {
|
|
fecparam->active_fec = ETHTOOL_FEC_OFF;
|
|
fecparam->fec = ETHTOOL_FEC_OFF;
|
|
}
|
|
|
|
if (fbn->aui & FBNIC_AUI_MODE_PAM4)
|
|
fecparam->fec |= ETHTOOL_FEC_AUTO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct phylink_pcs *
|
|
fbnic_phylink_mac_select_pcs(struct phylink_config *config,
|
|
phy_interface_t interface)
|
|
{
|
|
struct net_device *netdev = to_net_dev(config->dev);
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
|
|
return fbn->pcs;
|
|
}
|
|
|
|
static int
|
|
fbnic_phylink_mac_prepare(struct phylink_config *config, unsigned int mode,
|
|
phy_interface_t iface)
|
|
{
|
|
struct net_device *netdev = to_net_dev(config->dev);
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
struct fbnic_dev *fbd = fbn->fbd;
|
|
|
|
fbd->mac->prepare(fbd, fbn->aui, fbn->fec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
fbnic_phylink_mac_config(struct phylink_config *config, unsigned int mode,
|
|
const struct phylink_link_state *state)
|
|
{
|
|
}
|
|
|
|
static int
|
|
fbnic_phylink_mac_finish(struct phylink_config *config, unsigned int mode,
|
|
phy_interface_t iface)
|
|
{
|
|
struct net_device *netdev = to_net_dev(config->dev);
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
struct fbnic_dev *fbd = fbn->fbd;
|
|
|
|
/* Retest the link state and restart interrupts */
|
|
fbd->mac->get_link(fbd, fbn->aui, fbn->fec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
fbnic_phylink_mac_link_down(struct phylink_config *config, unsigned int mode,
|
|
phy_interface_t interface)
|
|
{
|
|
struct net_device *netdev = to_net_dev(config->dev);
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
struct fbnic_dev *fbd = fbn->fbd;
|
|
|
|
fbd->mac->link_down(fbd);
|
|
|
|
fbn->link_down_events++;
|
|
}
|
|
|
|
static void
|
|
fbnic_phylink_mac_link_up(struct phylink_config *config,
|
|
struct phy_device *phy, unsigned int mode,
|
|
phy_interface_t interface, int speed, int duplex,
|
|
bool tx_pause, bool rx_pause)
|
|
{
|
|
struct net_device *netdev = to_net_dev(config->dev);
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
struct fbnic_dev *fbd = fbn->fbd;
|
|
|
|
fbn->tx_pause = tx_pause;
|
|
fbnic_config_drop_mode(fbn, tx_pause);
|
|
|
|
fbd->mac->link_up(fbd, tx_pause, rx_pause);
|
|
}
|
|
|
|
static const struct phylink_mac_ops fbnic_phylink_mac_ops = {
|
|
.mac_select_pcs = fbnic_phylink_mac_select_pcs,
|
|
.mac_prepare = fbnic_phylink_mac_prepare,
|
|
.mac_config = fbnic_phylink_mac_config,
|
|
.mac_finish = fbnic_phylink_mac_finish,
|
|
.mac_link_down = fbnic_phylink_mac_link_down,
|
|
.mac_link_up = fbnic_phylink_mac_link_up,
|
|
};
|
|
|
|
/**
|
|
* fbnic_phylink_create - Phylink device creation
|
|
* @netdev: Network Device struct to attach phylink device
|
|
*
|
|
* Initialize and attach a phylink instance to the device. The phylink
|
|
* device will make use of the netdev struct to track carrier and will
|
|
* eventually be used to expose the current state of the MAC and PCS
|
|
* setup.
|
|
*
|
|
* Return: 0 on success, negative on failure
|
|
**/
|
|
int fbnic_phylink_create(struct net_device *netdev)
|
|
{
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
struct fbnic_dev *fbd = fbn->fbd;
|
|
struct phylink_pcs *pcs;
|
|
struct phylink *phylink;
|
|
int err;
|
|
|
|
pcs = xpcs_create_pcs_mdiodev(fbd->mdio_bus, 0);
|
|
if (IS_ERR(pcs)) {
|
|
err = PTR_ERR(pcs);
|
|
dev_err(fbd->dev, "Failed to create PCS device: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
fbn->pcs = pcs;
|
|
|
|
fbn->phylink_config.dev = &netdev->dev;
|
|
fbn->phylink_config.type = PHYLINK_NETDEV;
|
|
fbn->phylink_config.mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE |
|
|
MAC_25000FD | MAC_50000FD |
|
|
MAC_100000FD;
|
|
fbn->phylink_config.default_an_inband = true;
|
|
|
|
__set_bit(PHY_INTERFACE_MODE_100GBASEP,
|
|
fbn->phylink_config.supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_50GBASER,
|
|
fbn->phylink_config.supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_LAUI,
|
|
fbn->phylink_config.supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_25GBASER,
|
|
fbn->phylink_config.supported_interfaces);
|
|
|
|
fbnic_mac_get_fw_settings(fbd, &fbn->aui, &fbn->fec);
|
|
|
|
phylink = phylink_create(&fbn->phylink_config, NULL,
|
|
fbnic_phylink_select_interface(fbn->aui),
|
|
&fbnic_phylink_mac_ops);
|
|
if (IS_ERR(phylink)) {
|
|
err = PTR_ERR(phylink);
|
|
dev_err(netdev->dev.parent,
|
|
"Failed to create Phylink interface, err: %d\n", err);
|
|
xpcs_destroy_pcs(pcs);
|
|
return err;
|
|
}
|
|
|
|
fbn->phylink = phylink;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* fbnic_phylink_destroy - Teardown phylink related interfaces
|
|
* @netdev: Network Device struct containing phylink device
|
|
*
|
|
* Detach and free resources related to phylink interface.
|
|
**/
|
|
void fbnic_phylink_destroy(struct net_device *netdev)
|
|
{
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
|
|
if (fbn->phylink)
|
|
phylink_destroy(fbn->phylink);
|
|
if (fbn->pcs)
|
|
xpcs_destroy_pcs(fbn->pcs);
|
|
}
|
|
|
|
/**
|
|
* fbnic_phylink_pmd_training_complete_notify - PMD training complete notifier
|
|
* @netdev: Netdev struct phylink device attached to
|
|
*
|
|
* When the link first comes up the PMD will have a period of 2 to 3 seconds
|
|
* where the link will flutter due to link training. To avoid spamming the
|
|
* kernel log with messages about this we add a delay of 4 seconds from the
|
|
* time of the last PCS report of link so that we can guarantee we are unlikely
|
|
* to see any further link loss events due to link training.
|
|
**/
|
|
void fbnic_phylink_pmd_training_complete_notify(struct net_device *netdev)
|
|
{
|
|
struct fbnic_net *fbn = netdev_priv(netdev);
|
|
struct fbnic_dev *fbd = fbn->fbd;
|
|
|
|
if (fbd->pmd_state != FBNIC_PMD_TRAINING)
|
|
return;
|
|
|
|
/* Prevent reading end_of_pmd_training until we verified state */
|
|
smp_rmb();
|
|
|
|
if (!time_before(READ_ONCE(fbd->end_of_pmd_training), jiffies))
|
|
return;
|
|
|
|
/* At this point we have verified that the link has been up for
|
|
* the full training duration. As a first step we will try
|
|
* transitioning to link ready.
|
|
*/
|
|
if (cmpxchg(&fbd->pmd_state, FBNIC_PMD_TRAINING,
|
|
FBNIC_PMD_LINK_READY) != FBNIC_PMD_TRAINING)
|
|
return;
|
|
|
|
/* Perform a follow-up check to verify that the link didn't flap
|
|
* just before our transition by rechecking the training timer.
|
|
*/
|
|
if (!time_before(READ_ONCE(fbd->end_of_pmd_training), jiffies))
|
|
return;
|
|
|
|
/* The training timeout has been completed. We are good to swap out
|
|
* link_ready for send_data assuming no other events have occurred
|
|
* that would have pulled us back into initialization or training.
|
|
*/
|
|
if (cmpxchg(&fbd->pmd_state, FBNIC_PMD_LINK_READY,
|
|
FBNIC_PMD_SEND_DATA) != FBNIC_PMD_LINK_READY)
|
|
return;
|
|
|
|
phylink_pcs_change(fbn->pcs, false);
|
|
}
|