From 0426fc74e2794d0bceb09c2446bbe58733cb34d0 Mon Sep 17 00:00:00 2001 From: Narayan Reddy Date: Sat, 4 May 2019 19:22:13 +0530 Subject: [PATCH] nvethernet: add pause frame support Pause frame is a flow control mechanism for temporarily stopping the transmission of data on Ethernet. The goal of this mechanism is to ensure zero packet loss in case of network congestion. Bug 200516459 Change-Id: I7b6373bfbb9572c4ac2635f1c4c91011f4244380 Signed-off-by: Narayan Reddy Reviewed-on: https://git-master.nvidia.com/r/2111933 Reviewed-by: Bitan Biswas Reviewed-by: mobile promotions Tested-by: mobile promotions --- .../ethernet/nvidia/nvethernet/ether_linux.c | 33 ++++- .../net/ethernet/nvidia/nvethernet/ethtool.c | 115 ++++++++++++++++++ 2 files changed, 144 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c index 0ce727e0..3d337282 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c +++ b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c @@ -40,6 +40,12 @@ static void ether_adjust_link(struct net_device *dev) return; } if (phydev->link) { + if ((pdata->osi_core->pause_frames == OSI_PAUSE_FRAMES_ENABLE) + && (phydev->pause || phydev->asym_pause)) { + osi_configure_flow_control(pdata->osi_core, + pdata->osi_core->flow_ctrl); + } + if (phydev->duplex != pdata->oldduplex) { new_state = 1; osi_set_mode(pdata->osi_core, phydev->duplex); @@ -129,6 +135,16 @@ static int ether_phy_init(struct net_device *dev) return -ENODEV; } + /* In marvel phy driver pause is disabled. Instead of enabling + * in PHY driver, handle this in eqos driver so that enabling/disabling + * of the pause frame feature can be controlled based on the platform + */ + phydev->supported |= (SUPPORTED_Pause | SUPPORTED_Asym_Pause); + if (pdata->osi_core->pause_frames == OSI_PAUSE_FRAMES_DISABLE) + phydev->supported &= ~(SUPPORTED_Pause | SUPPORTED_Asym_Pause); + + phydev->advertising = phydev->supported; + pdata->phydev = phydev; return 0; @@ -1995,8 +2011,8 @@ static void ether_parse_queue_prio(struct ether_priv_data *pdata, ret = of_property_read_u32_array(pnode, pdt_prop, pval, num_entries); if (ret < 0) { - dev_err(pdata->dev, "%s(): \"%s\" read failed %d. Using default\n", - __func__, pdt_prop, ret); + dev_err(pdata->dev, "%s(): \"%s\" read failed %d." + "Using default\n", __func__, pdt_prop, ret); for (i = 0; i < num_entries; i++) { pval[i] = val_def; } @@ -2008,8 +2024,8 @@ static void ether_parse_queue_prio(struct ether_priv_data *pdata, */ for (i = 0; i < num_entries; i++) { if ((pval[i] > val_max) || ((pmask & (1U << pval[i])) != 0U)) { - dev_err(pdata->dev, "%s():Wrong or duplicate priority in DT entry for Q(%d)\n", - __func__, i); + dev_err(pdata->dev, "%s():Wrong or duplicate priority" + " in DT entry for Q(%d)\n", __func__, i); pval[i] = val_def; } pmask |= 1U << pval[i]; @@ -2037,6 +2053,15 @@ static int ether_parse_dt(struct ether_priv_data *pdata) struct platform_device *pdev = to_platform_device(dev); int ret = -EINVAL; + /* Read Pause frame feature support */ + ret = of_property_read_u32(np, "nvidia,pause_frames", + &pdata->osi_core->pause_frames); + if (ret < 0) { + dev_err(dev, "Failed to read nvida,pause_frames, so" + " setting to default support as enable\n"); + pdata->osi_core->pause_frames = OSI_PAUSE_FRAMES_ENABLE; + } + /* Check if IOMMU is enabled */ if (pdev->dev.archdata.iommu != NULL) { /* Read and set dma-mask from DT only if IOMMU is enabled*/ diff --git a/drivers/net/ethernet/nvidia/nvethernet/ethtool.c b/drivers/net/ethernet/nvidia/nvethernet/ethtool.c index de4e35b0..3dc1303c 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/ethtool.c +++ b/drivers/net/ethernet/nvidia/nvethernet/ethtool.c @@ -16,10 +16,125 @@ #include "ether_linux.h" +/** + * ether_get_pauseparam - Get pause frame settings + * @ndev: network device instance + * @pause: Pause parameters that are set currently + * + * Algorithm: Gets pause frame configuration + * + * Dependencies: Network device needs to created. + * + * Protection: None. + * + * Return: None. + */ +static void ether_get_pauseparam(struct net_device *ndev, + struct ethtool_pauseparam *pause) +{ + struct ether_priv_data *pdata = netdev_priv(ndev); + struct phy_device *phydev = pdata->phydev; + + if (!netif_running(ndev)) { + netdev_err(pdata->ndev, "interface must be up\n"); + return; + } + + /* return if pause frame is not supported */ + if ((pdata->osi_core->pause_frames == OSI_PAUSE_FRAMES_DISABLE) || + (!(phydev->supported & SUPPORTED_Pause) || + !(phydev->supported & SUPPORTED_Asym_Pause))) { + dev_err(pdata->dev, "FLOW control not supported\n"); + return; + } + + /* update auto negotiation */ + pause->autoneg = phydev->autoneg; + + /* update rx pause parameter */ + if ((pdata->osi_core->flow_ctrl & OSI_FLOW_CTRL_RX) == + OSI_FLOW_CTRL_RX) { + pause->rx_pause = 1; + } + + /* update tx pause parameter */ + if ((pdata->osi_core->flow_ctrl & OSI_FLOW_CTRL_TX) == + OSI_FLOW_CTRL_TX) { + pause->tx_pause = 1; + } +} + +/** + * ether_set_pauseparam - Set pause frame settings + * @ndev: network device instance + * @pause: Pause frame settings + * + * Algorithm: Sets pause frame settings + * + * Dependencies: Network device needs to created. + * + * Protection: None. + * + * Return: 0 - success, negative value - failure. + */ +static int ether_set_pauseparam(struct net_device *ndev, + struct ethtool_pauseparam *pause) +{ + struct ether_priv_data *pdata = netdev_priv(ndev); + struct phy_device *phydev = pdata->phydev; + int curflow_ctrl = OSI_FLOW_CTRL_DISABLE; + int ret; + + if (!netif_running(ndev)) { + netdev_err(pdata->ndev, "interface must be up\n"); + return -EINVAL; + } + + /* return if pause frame is not supported */ + if ((pdata->osi_core->pause_frames == OSI_PAUSE_FRAMES_DISABLE) || + (!(phydev->supported & SUPPORTED_Pause) || + !(phydev->supported & SUPPORTED_Asym_Pause))) { + dev_err(pdata->dev, "FLOW control not supported\n"); + return -EOPNOTSUPP; + } + + dev_err(pdata->dev, "autoneg = %d tx_pause = %d rx_pause = %d\n", + pause->autoneg, pause->tx_pause, pause->rx_pause); + + if (pause->tx_pause) + curflow_ctrl |= OSI_FLOW_CTRL_TX; + + if (pause->rx_pause) + curflow_ctrl |= OSI_FLOW_CTRL_RX; + + /* update flow control setting */ + pdata->osi_core->flow_ctrl = curflow_ctrl; + /* update autonegotiation */ + phydev->autoneg = pause->autoneg; + + /*If autonegotiation is enabled,start auto-negotiation + * for this PHY device and return, so that flow control + * settings will be done once we receive the link changed + * event i.e in ether_adjust_link + */ + if (phydev->autoneg && netif_running(ndev)) { + return phy_start_aneg(phydev); + } + + /* Configure current flow control settings */ + ret = osi_configure_flow_control(pdata->osi_core, + pdata->osi_core->flow_ctrl); + + return ret; +} + + static const struct ethtool_ops ether_ethtool_ops = { .get_link = ethtool_op_get_link, .get_link_ksettings = phy_ethtool_get_link_ksettings, .set_link_ksettings = phy_ethtool_set_link_ksettings, + .get_pauseparam = ether_get_pauseparam, + .set_pauseparam = ether_set_pauseparam, }; /**