[PATCH] forcedeth config: flow control
authorAyaz Abdulla <aabdulla@nvidia.com>
Sun, 11 Jun 2006 02:47:42 +0000 (22:47 -0400)
committerJeff Garzik <jeff@garzik.org>
Sun, 11 Jun 2006 13:25:15 +0000 (09:25 -0400)
This patch allows for configurable flow control through ethtool support.

Signed-Off-By: Ayaz Abdulla <aabdulla@nvidia.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
drivers/net/forcedeth.c

index f29a5b6..14e6da2 100644 (file)
@@ -519,6 +519,9 @@ typedef union _ring_type {
 #define NV_PAUSEFRAME_TX_CAPABLE 0x0002
 #define NV_PAUSEFRAME_RX_ENABLE  0x0004
 #define NV_PAUSEFRAME_TX_ENABLE  0x0008
+#define NV_PAUSEFRAME_RX_REQ     0x0010
+#define NV_PAUSEFRAME_TX_REQ     0x0020
+#define NV_PAUSEFRAME_AUTONEG    0x0040
 
 /* MSI/MSI-X defines */
 #define NV_MSI_X_MAX_VECTORS  8
@@ -1868,16 +1871,16 @@ static void nv_set_multicast(struct net_device *dev)
        u8 __iomem *base = get_hwbase(dev);
        u32 addr[2];
        u32 mask[2];
-       u32 pff;
+       u32 pff = readl(base + NvRegPacketFilterFlags) & NVREG_PFF_PAUSE_RX;
 
        memset(addr, 0, sizeof(addr));
        memset(mask, 0, sizeof(mask));
 
        if (dev->flags & IFF_PROMISC) {
                printk(KERN_NOTICE "%s: Promiscuous mode enabled.\n", dev->name);
-               pff = NVREG_PFF_PROMISC;
+               pff |= NVREG_PFF_PROMISC;
        } else {
-               pff = NVREG_PFF_MYADDR;
+               pff |= NVREG_PFF_MYADDR;
 
                if (dev->flags & IFF_ALLMULTI || dev->mc_list) {
                        u32 alwaysOff[2];
@@ -1922,6 +1925,35 @@ static void nv_set_multicast(struct net_device *dev)
        spin_unlock_irq(&np->lock);
 }
 
+void nv_update_pause(struct net_device *dev, u32 pause_flags)
+{
+       struct fe_priv *np = netdev_priv(dev);
+       u8 __iomem *base = get_hwbase(dev);
+
+       np->pause_flags &= ~(NV_PAUSEFRAME_TX_ENABLE | NV_PAUSEFRAME_RX_ENABLE);
+
+       if (np->pause_flags & NV_PAUSEFRAME_RX_CAPABLE) {
+               u32 pff = readl(base + NvRegPacketFilterFlags) & ~NVREG_PFF_PAUSE_RX;
+               if (pause_flags & NV_PAUSEFRAME_RX_ENABLE) {
+                       writel(pff|NVREG_PFF_PAUSE_RX, base + NvRegPacketFilterFlags);
+                       np->pause_flags |= NV_PAUSEFRAME_RX_ENABLE;
+               } else {
+                       writel(pff, base + NvRegPacketFilterFlags);
+               }
+       }
+       if (np->pause_flags & NV_PAUSEFRAME_TX_CAPABLE) {
+               u32 regmisc = readl(base + NvRegMisc1) & ~NVREG_MISC1_PAUSE_TX;
+               if (pause_flags & NV_PAUSEFRAME_TX_ENABLE) {
+                       writel(NVREG_TX_PAUSEFRAME_ENABLE,  base + NvRegTxPauseFrame);
+                       writel(regmisc|NVREG_MISC1_PAUSE_TX, base + NvRegMisc1);
+                       np->pause_flags |= NV_PAUSEFRAME_TX_ENABLE;
+               } else {
+                       writel(NVREG_TX_PAUSEFRAME_DISABLE,  base + NvRegTxPauseFrame);
+                       writel(regmisc, base + NvRegMisc1);
+               }
+       }
+}
+
 /**
  * nv_update_linkspeed: Setup the MAC according to the link partner
  * @dev: Network device to be configured
@@ -1944,7 +1976,7 @@ static int nv_update_linkspeed(struct net_device *dev)
        int newdup = np->duplex;
        int mii_status;
        int retval = 0;
-       u32 control_1000, status_1000, phyreg;
+       u32 control_1000, status_1000, phyreg, pause_flags;
 
        /* BMSR_LSTATUS is latched, read it twice:
         * we want the current value.
@@ -1990,6 +2022,11 @@ static int nv_update_linkspeed(struct net_device *dev)
                goto set_speed;
        }
 
+       adv = mii_rw(dev, np->phyaddr, MII_ADVERTISE, MII_READ);
+       lpa = mii_rw(dev, np->phyaddr, MII_LPA, MII_READ);
+       dprintk(KERN_DEBUG "%s: nv_update_linkspeed: PHY advertises 0x%04x, lpa 0x%04x.\n",
+                               dev->name, adv, lpa);
+
        retval = 1;
        if (np->gigabit == PHY_GIGABIT) {
                control_1000 = mii_rw(dev, np->phyaddr, MII_CTRL1000, MII_READ);
@@ -2005,11 +2042,6 @@ static int nv_update_linkspeed(struct net_device *dev)
                }
        }
 
-       adv = mii_rw(dev, np->phyaddr, MII_ADVERTISE, MII_READ);
-       lpa = mii_rw(dev, np->phyaddr, MII_LPA, MII_READ);
-       dprintk(KERN_DEBUG "%s: nv_update_linkspeed: PHY advertises 0x%04x, lpa 0x%04x.\n",
-                               dev->name, adv, lpa);
-
        /* FIXME: handle parallel detection properly */
        adv_lpa = lpa & adv;
        if (adv_lpa & LPA_100FULL) {
@@ -2068,55 +2100,45 @@ set_speed:
        writel(np->linkspeed, base + NvRegLinkSpeed);
        pci_push(base);
 
-       /* setup pause frame based on advertisement and link partner */
-       np->pause_flags &= ~(NV_PAUSEFRAME_TX_ENABLE | NV_PAUSEFRAME_RX_ENABLE);
-
+       pause_flags = 0;
+       /* setup pause frame */
        if (np->duplex != 0) {
-               adv_pause = adv & (ADVERTISE_PAUSE_CAP| ADVERTISE_PAUSE_ASYM);
-               lpa_pause = lpa & (LPA_PAUSE_CAP| LPA_PAUSE_ASYM);
-
-               switch (adv_pause) {
-               case (ADVERTISE_PAUSE_CAP):
-                       if (lpa_pause & LPA_PAUSE_CAP) {
-                               np->pause_flags |= NV_PAUSEFRAME_TX_ENABLE | NV_PAUSEFRAME_RX_ENABLE;
-                       }
-                       break;
-               case (ADVERTISE_PAUSE_ASYM):
-                       if (lpa_pause == (LPA_PAUSE_CAP| LPA_PAUSE_ASYM))
-                       {
-                               np->pause_flags |= NV_PAUSEFRAME_TX_ENABLE;
-                       }
-                       break;
-               case (ADVERTISE_PAUSE_CAP| ADVERTISE_PAUSE_ASYM):
-                       if (lpa_pause & LPA_PAUSE_CAP)
-                       {
-                               np->pause_flags |= NV_PAUSEFRAME_TX_ENABLE | NV_PAUSEFRAME_RX_ENABLE;
-                       }
-                       if (lpa_pause == LPA_PAUSE_ASYM)
-                       {
-                               np->pause_flags |= NV_PAUSEFRAME_RX_ENABLE;
+               if (np->autoneg && np->pause_flags & NV_PAUSEFRAME_AUTONEG) {
+                       adv_pause = adv & (ADVERTISE_PAUSE_CAP| ADVERTISE_PAUSE_ASYM);
+                       lpa_pause = lpa & (LPA_PAUSE_CAP| LPA_PAUSE_ASYM);
+
+                       switch (adv_pause) {
+                       case (ADVERTISE_PAUSE_CAP):
+                               if (lpa_pause & LPA_PAUSE_CAP) {
+                                       pause_flags |= NV_PAUSEFRAME_RX_ENABLE;
+                                       if (np->pause_flags & NV_PAUSEFRAME_TX_REQ)
+                                               pause_flags |= NV_PAUSEFRAME_TX_ENABLE;
+                               }
+                               break;
+                       case (ADVERTISE_PAUSE_ASYM):
+                               if (lpa_pause == (LPA_PAUSE_CAP| LPA_PAUSE_ASYM))
+                               {
+                                       pause_flags |= NV_PAUSEFRAME_TX_ENABLE;
+                               }
+                               break;
+                       case (ADVERTISE_PAUSE_CAP| ADVERTISE_PAUSE_ASYM):
+                               if (lpa_pause & LPA_PAUSE_CAP)
+                               {
+                                       pause_flags |=  NV_PAUSEFRAME_RX_ENABLE;
+                                       if (np->pause_flags & NV_PAUSEFRAME_TX_REQ)
+                                               pause_flags |= NV_PAUSEFRAME_TX_ENABLE;
+                               }
+                               if (lpa_pause == LPA_PAUSE_ASYM)
+                               {
+                                       pause_flags |= NV_PAUSEFRAME_RX_ENABLE;
+                               }
+                               break;
                        }
-                       break;
-               }
-       }
-
-       if (np->pause_flags & NV_PAUSEFRAME_RX_CAPABLE) {
-               u32 pff = readl(base + NvRegPacketFilterFlags) & ~NVREG_PFF_PAUSE_RX;
-               if (np->pause_flags & NV_PAUSEFRAME_RX_ENABLE)
-                       writel(pff|NVREG_PFF_PAUSE_RX, base + NvRegPacketFilterFlags);
-               else
-                       writel(pff, base + NvRegPacketFilterFlags);
-       }
-       if (np->pause_flags & NV_PAUSEFRAME_TX_CAPABLE) {
-               u32 regmisc = readl(base + NvRegMisc1) & ~NVREG_MISC1_PAUSE_TX;
-               if (np->pause_flags & NV_PAUSEFRAME_TX_ENABLE) {
-                       writel(NVREG_TX_PAUSEFRAME_ENABLE,  base + NvRegTxPauseFrame);
-                       writel(regmisc|NVREG_MISC1_PAUSE_TX, base + NvRegMisc1);
                } else {
-                       writel(NVREG_TX_PAUSEFRAME_DISABLE,  base + NvRegTxPauseFrame);
-                       writel(regmisc, base + NvRegMisc1);
+                       pause_flags = np->pause_flags;
                }
        }
+       nv_update_pause(dev, pause_flags);
 
        return retval;
 }
@@ -2597,11 +2619,15 @@ static int nv_set_settings(struct net_device *dev, struct ethtool_cmd *ecmd)
                if (ecmd->advertising & ADVERTISED_10baseT_Half)
                        adv |= ADVERTISE_10HALF;
                if (ecmd->advertising & ADVERTISED_10baseT_Full)
-                       adv |= ADVERTISE_10FULL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
+                       adv |= ADVERTISE_10FULL;
                if (ecmd->advertising & ADVERTISED_100baseT_Half)
                        adv |= ADVERTISE_100HALF;
                if (ecmd->advertising & ADVERTISED_100baseT_Full)
-                       adv |= ADVERTISE_100FULL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
+                       adv |= ADVERTISE_100FULL;
+               if (np->pause_flags & NV_PAUSEFRAME_RX_REQ)  /* for rx we set both advertisments but disable tx pause */
+                       adv |=  ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
+               if (np->pause_flags & NV_PAUSEFRAME_TX_REQ)
+                       adv |=  ADVERTISE_PAUSE_ASYM;
                mii_rw(dev, np->phyaddr, MII_ADVERTISE, adv);
 
                if (np->gigabit == PHY_GIGABIT) {
@@ -2626,11 +2652,20 @@ static int nv_set_settings(struct net_device *dev, struct ethtool_cmd *ecmd)
                if (ecmd->speed == SPEED_10 && ecmd->duplex == DUPLEX_HALF)
                        adv |= ADVERTISE_10HALF;
                if (ecmd->speed == SPEED_10 && ecmd->duplex == DUPLEX_FULL)
-                       adv |= ADVERTISE_10FULL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
+                       adv |= ADVERTISE_10FULL;
                if (ecmd->speed == SPEED_100 && ecmd->duplex == DUPLEX_HALF)
                        adv |= ADVERTISE_100HALF;
                if (ecmd->speed == SPEED_100 && ecmd->duplex == DUPLEX_FULL)
-                       adv |= ADVERTISE_100FULL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
+                       adv |= ADVERTISE_100FULL;
+               np->pause_flags &= ~(NV_PAUSEFRAME_AUTONEG|NV_PAUSEFRAME_RX_ENABLE|NV_PAUSEFRAME_TX_ENABLE);
+               if (np->pause_flags & NV_PAUSEFRAME_RX_REQ) {/* for rx we set both advertisments but disable tx pause */
+                       adv |=  ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
+                       np->pause_flags |= NV_PAUSEFRAME_RX_ENABLE;
+               }
+               if (np->pause_flags & NV_PAUSEFRAME_TX_REQ) {
+                       adv |=  ADVERTISE_PAUSE_ASYM;
+                       np->pause_flags |= NV_PAUSEFRAME_TX_ENABLE;
+               }
                mii_rw(dev, np->phyaddr, MII_ADVERTISE, adv);
                np->fixed_mode = adv;
 
@@ -2856,6 +2891,86 @@ exit:
        return -ENOMEM;
 }
 
+static void nv_get_pauseparam(struct net_device *dev, struct ethtool_pauseparam* pause)
+{
+       struct fe_priv *np = netdev_priv(dev);
+
+       pause->autoneg = (np->pause_flags & NV_PAUSEFRAME_AUTONEG) != 0;
+       pause->rx_pause = (np->pause_flags & NV_PAUSEFRAME_RX_ENABLE) != 0;
+       pause->tx_pause = (np->pause_flags & NV_PAUSEFRAME_TX_ENABLE) != 0;
+}
+
+static int nv_set_pauseparam(struct net_device *dev, struct ethtool_pauseparam* pause)
+{
+       struct fe_priv *np = netdev_priv(dev);
+       int adv, bmcr;
+
+       if ((!np->autoneg && np->duplex == 0) ||
+           (np->autoneg && !pause->autoneg && np->duplex == 0)) {
+               printk(KERN_INFO "%s: can not set pause settings when forced link is in half duplex.\n",
+                      dev->name);
+               return -EINVAL;
+       }
+       if (pause->tx_pause && !(np->pause_flags & NV_PAUSEFRAME_TX_CAPABLE)) {
+               printk(KERN_INFO "%s: hardware does not support tx pause frames.\n", dev->name);
+               return -EINVAL;
+       }
+
+       netif_carrier_off(dev);
+       if (netif_running(dev)) {
+               nv_disable_irq(dev);
+               spin_lock_bh(&dev->xmit_lock);
+               spin_lock(&np->lock);
+               /* stop engines */
+               nv_stop_rx(dev);
+               nv_stop_tx(dev);
+               spin_unlock(&np->lock);
+               spin_unlock_bh(&dev->xmit_lock);
+       }
+
+       np->pause_flags &= ~(NV_PAUSEFRAME_RX_REQ|NV_PAUSEFRAME_TX_REQ);
+       if (pause->rx_pause)
+               np->pause_flags |= NV_PAUSEFRAME_RX_REQ;
+       if (pause->tx_pause)
+               np->pause_flags |= NV_PAUSEFRAME_TX_REQ;
+
+       if (np->autoneg && pause->autoneg) {
+               np->pause_flags |= NV_PAUSEFRAME_AUTONEG;
+
+               adv = mii_rw(dev, np->phyaddr, MII_ADVERTISE, MII_READ);
+               adv &= ~(ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
+               if (np->pause_flags & NV_PAUSEFRAME_RX_REQ) /* for rx we set both advertisments but disable tx pause */
+                       adv |=  ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
+               if (np->pause_flags & NV_PAUSEFRAME_TX_REQ)
+                       adv |=  ADVERTISE_PAUSE_ASYM;
+               mii_rw(dev, np->phyaddr, MII_ADVERTISE, adv);
+
+               if (netif_running(dev))
+                       printk(KERN_INFO "%s: link down.\n", dev->name);
+               bmcr = mii_rw(dev, np->phyaddr, MII_BMCR, MII_READ);
+               bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART);
+               mii_rw(dev, np->phyaddr, MII_BMCR, bmcr);
+       } else {
+               np->pause_flags &= ~(NV_PAUSEFRAME_AUTONEG|NV_PAUSEFRAME_RX_ENABLE|NV_PAUSEFRAME_TX_ENABLE);
+               if (pause->rx_pause)
+                       np->pause_flags |= NV_PAUSEFRAME_RX_ENABLE;
+               if (pause->tx_pause)
+                       np->pause_flags |= NV_PAUSEFRAME_TX_ENABLE;
+
+               if (!netif_running(dev))
+                       nv_update_linkspeed(dev);
+               else
+                       nv_update_pause(dev, np->pause_flags);
+       }
+
+       if (netif_running(dev)) {
+               nv_start_rx(dev);
+               nv_start_tx(dev);
+               nv_enable_irq(dev);
+       }
+       return 0;
+}
+
 static struct ethtool_ops ops = {
        .get_drvinfo = nv_get_drvinfo,
        .get_link = ethtool_op_get_link,
@@ -2871,6 +2986,8 @@ static struct ethtool_ops ops = {
        .set_tso = nv_set_tso,
        .get_ringparam = nv_get_ringparam,
        .set_ringparam = nv_set_ringparam,
+       .get_pauseparam = nv_get_pauseparam,
+       .set_pauseparam = nv_set_pauseparam,
 };
 
 static void nv_vlan_rx_register(struct net_device *dev, struct vlan_group *grp)
@@ -3346,9 +3463,9 @@ static int __devinit nv_probe(struct pci_dev *pci_dev, const struct pci_device_i
                np->msi_flags |= NV_MSI_X_CAPABLE;
        }
 
-       np->pause_flags = NV_PAUSEFRAME_RX_CAPABLE;
+       np->pause_flags = NV_PAUSEFRAME_RX_CAPABLE | NV_PAUSEFRAME_RX_REQ | NV_PAUSEFRAME_AUTONEG;
        if (id->driver_data & DEV_HAS_PAUSEFRAME_TX) {
-               np->pause_flags |= NV_PAUSEFRAME_TX_CAPABLE;
+               np->pause_flags |= NV_PAUSEFRAME_TX_CAPABLE | NV_PAUSEFRAME_TX_REQ;
        }