diff options
Diffstat (limited to 'drivers/net/ethernet/meta/fbnic/fbnic_phylink.c')
| -rw-r--r-- | drivers/net/ethernet/meta/fbnic/fbnic_phylink.c | 187 |
1 files changed, 121 insertions, 66 deletions
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c b/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c index 7ce3fdd25282..09c5225111be 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c @@ -1,6 +1,7 @@ // 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> @@ -101,88 +102,47 @@ int fbnic_phylink_get_fecparam(struct net_device *netdev, return 0; } -static struct fbnic_net * -fbnic_pcs_to_net(struct phylink_pcs *pcs) -{ - return container_of(pcs, struct fbnic_net, phylink_pcs); -} - -static void -fbnic_phylink_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode, - struct phylink_link_state *state) +static struct phylink_pcs * +fbnic_phylink_mac_select_pcs(struct phylink_config *config, + phy_interface_t interface) { - struct fbnic_net *fbn = fbnic_pcs_to_net(pcs); - struct fbnic_dev *fbd = fbn->fbd; - - switch (fbn->aui) { - case FBNIC_AUI_25GAUI: - state->speed = SPEED_25000; - break; - case FBNIC_AUI_LAUI2: - case FBNIC_AUI_50GAUI1: - state->speed = SPEED_50000; - break; - case FBNIC_AUI_100GAUI2: - state->speed = SPEED_100000; - break; - default: - state->link = 0; - return; - } - - state->duplex = DUPLEX_FULL; + struct net_device *netdev = to_net_dev(config->dev); + struct fbnic_net *fbn = netdev_priv(netdev); - state->link = fbd->mac->pcs_get_link(fbd); + return fbn->pcs; } static int -fbnic_phylink_pcs_enable(struct phylink_pcs *pcs) +fbnic_phylink_mac_prepare(struct phylink_config *config, unsigned int mode, + phy_interface_t iface) { - struct fbnic_net *fbn = fbnic_pcs_to_net(pcs); + struct net_device *netdev = to_net_dev(config->dev); + struct fbnic_net *fbn = netdev_priv(netdev); struct fbnic_dev *fbd = fbn->fbd; - return fbd->mac->pcs_enable(fbd); + fbd->mac->prepare(fbd, fbn->aui, fbn->fec); + + return 0; } static void -fbnic_phylink_pcs_disable(struct phylink_pcs *pcs) +fbnic_phylink_mac_config(struct phylink_config *config, unsigned int mode, + const struct phylink_link_state *state) { - struct fbnic_net *fbn = fbnic_pcs_to_net(pcs); - struct fbnic_dev *fbd = fbn->fbd; - - return fbd->mac->pcs_disable(fbd); } static int -fbnic_phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, - phy_interface_t interface, - const unsigned long *advertising, - bool permit_pause_to_mac) -{ - return 0; -} - -static const struct phylink_pcs_ops fbnic_phylink_pcs_ops = { - .pcs_config = fbnic_phylink_pcs_config, - .pcs_enable = fbnic_phylink_pcs_enable, - .pcs_disable = fbnic_phylink_pcs_disable, - .pcs_get_state = fbnic_phylink_pcs_get_state, -}; - -static struct phylink_pcs * -fbnic_phylink_mac_select_pcs(struct phylink_config *config, - phy_interface_t interface) +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; - return &fbn->phylink_pcs; -} + /* Retest the link state and restart interrupts */ + fbd->mac->get_link(fbd, fbn->aui, fbn->fec); -static void -fbnic_phylink_mac_config(struct phylink_config *config, unsigned int mode, - const struct phylink_link_state *state) -{ + return 0; } static void @@ -208,23 +168,48 @@ fbnic_phylink_mac_link_up(struct phylink_config *config, 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, }; -int fbnic_phylink_init(struct net_device *netdev) +/** + * 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->phylink_pcs.ops = &fbnic_phylink_pcs_ops; + fbn->pcs = pcs; fbn->phylink_config.dev = &netdev->dev; fbn->phylink_config.type = PHYLINK_NETDEV; @@ -247,10 +232,80 @@ int fbnic_phylink_init(struct net_device *netdev) phylink = phylink_create(&fbn->phylink_config, NULL, fbnic_phylink_select_interface(fbn->aui), &fbnic_phylink_mac_ops); - if (IS_ERR(phylink)) - return PTR_ERR(phylink); + 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); +} |
