// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) Meta Platforms, Inc. and affiliates. */ #include #include #include #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); }