sky2: add support for receive hashing
Stephen Hemminger [Sun, 25 Apr 2010 03:04:12 +0000 (20:04 -0700)]
Sky2 hardware supports hardware receive hash calculation.
Now that Receive Packet Steering is available, add support
to enable it.

This version does not depend on CONFIG_RPS. Also set_flags rejects
all values except RXHASH, so driver won't have to change next time
somebody adds a new one.

Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

drivers/net/sky2.c
drivers/net/sky2.h

index 4f83f11..a1b17fa 100644 (file)
@@ -1193,6 +1193,39 @@ static void rx_set_checksum(struct sky2_port *sky2)
                     ? BMU_ENA_RX_CHKSUM : BMU_DIS_RX_CHKSUM);
 }
 
+/* Enable/disable receive hash calculation (RSS) */
+static void rx_set_rss(struct net_device *dev)
+{
+       struct sky2_port *sky2 = netdev_priv(dev);
+       struct sky2_hw *hw = sky2->hw;
+       int i, nkeys = 4;
+
+       /* Supports IPv6 and other modes */
+       if (hw->flags & SKY2_HW_NEW_LE) {
+               nkeys = 10;
+               sky2_write32(hw, SK_REG(sky2->port, RSS_CFG), HASH_ALL);
+       }
+
+       /* Program RSS initial values */
+       if (dev->features & NETIF_F_RXHASH) {
+               u32 key[nkeys];
+
+               get_random_bytes(key, nkeys * sizeof(u32));
+               for (i = 0; i < nkeys; i++)
+                       sky2_write32(hw, SK_REG(sky2->port, RSS_KEY + i * 4),
+                                    key[i]);
+
+               /* Need to turn on (undocumented) flag to make hashing work  */
+               sky2_write32(hw, SK_REG(sky2->port, RX_GMF_CTRL_T),
+                            RX_STFW_ENA);
+
+               sky2_write32(hw, Q_ADDR(rxqaddr[sky2->port], Q_CSR),
+                            BMU_ENA_RX_RSS_HASH);
+       } else
+               sky2_write32(hw, Q_ADDR(rxqaddr[sky2->port], Q_CSR),
+                            BMU_DIS_RX_RSS_HASH);
+}
+
 /*
  * The RX Stop command will not work for Yukon-2 if the BMU does not
  * reach the end of packet and since we can't make sure that we have
@@ -1425,6 +1458,9 @@ static void sky2_rx_start(struct sky2_port *sky2)
        if (!(hw->flags & SKY2_HW_NEW_LE))
                rx_set_checksum(sky2);
 
+       if (!(hw->flags & SKY2_HW_RSS_BROKEN))
+               rx_set_rss(sky2->netdev);
+
        /* submit Rx ring */
        for (i = 0; i < sky2->rx_pending; i++) {
                re = sky2->rx_ring + i;
@@ -2534,6 +2570,14 @@ static void sky2_rx_checksum(struct sky2_port *sky2, u32 status)
        }
 }
 
+static void sky2_rx_hash(struct sky2_port *sky2, u32 status)
+{
+       struct sk_buff *skb;
+
+       skb = sky2->rx_ring[sky2->rx_next].skb;
+       skb->rxhash = le32_to_cpu(status);
+}
+
 /* Process status response ring */
 static int sky2_status_intr(struct sky2_hw *hw, int to_do, u16 idx)
 {
@@ -2606,6 +2650,10 @@ static int sky2_status_intr(struct sky2_hw *hw, int to_do, u16 idx)
                                sky2_rx_checksum(sky2, status);
                        break;
 
+               case OP_RSS_HASH:
+                       sky2_rx_hash(sky2, status);
+                       break;
+
                case OP_TXINDEXLE:
                        /* TX index reports status for both ports */
                        sky2_tx_done(hw->dev[0], status & 0xfff);
@@ -2960,6 +3008,8 @@ static int __devinit sky2_init(struct sky2_hw *hw)
        switch(hw->chip_id) {
        case CHIP_ID_YUKON_XL:
                hw->flags = SKY2_HW_GIGABIT | SKY2_HW_NEWER_PHY;
+               if (hw->chip_rev < CHIP_REV_YU_XL_A2)
+                       hw->flags |= SKY2_HW_RSS_BROKEN;
                break;
 
        case CHIP_ID_YUKON_EC_U:
@@ -2985,10 +3035,11 @@ static int __devinit sky2_init(struct sky2_hw *hw)
                        dev_err(&hw->pdev->dev, "unsupported revision Yukon-EC rev A1\n");
                        return -EOPNOTSUPP;
                }
-               hw->flags = SKY2_HW_GIGABIT;
+               hw->flags = SKY2_HW_GIGABIT | SKY2_HW_RSS_BROKEN;
                break;
 
        case CHIP_ID_YUKON_FE:
+               hw->flags = SKY2_HW_RSS_BROKEN;
                break;
 
        case CHIP_ID_YUKON_FE_P:
@@ -4112,6 +4163,25 @@ static int sky2_set_eeprom(struct net_device *dev, struct ethtool_eeprom *eeprom
        return sky2_vpd_write(sky2->hw, cap, data, eeprom->offset, eeprom->len);
 }
 
+static int sky2_set_flags(struct net_device *dev, u32 data)
+{
+       struct sky2_port *sky2 = netdev_priv(dev);
+
+       if (data & ~ETH_FLAG_RXHASH)
+               return -EOPNOTSUPP;
+
+       if (data & ETH_FLAG_RXHASH) {
+               if (sky2->hw->flags & SKY2_HW_RSS_BROKEN)
+                       return -EINVAL;
+
+               dev->features |= NETIF_F_RXHASH;
+       } else
+               dev->features &= ~NETIF_F_RXHASH;
+
+       rx_set_rss(dev);
+
+       return 0;
+}
 
 static const struct ethtool_ops sky2_ethtool_ops = {
        .get_settings   = sky2_get_settings,
@@ -4143,6 +4213,7 @@ static const struct ethtool_ops sky2_ethtool_ops = {
        .phys_id        = sky2_phys_id,
        .get_sset_count = sky2_get_sset_count,
        .get_ethtool_stats = sky2_get_ethtool_stats,
+       .set_flags      = sky2_set_flags,
 };
 
 #ifdef CONFIG_SKY2_DEBUG
@@ -4496,6 +4567,10 @@ static __devinit struct net_device *sky2_init_netdev(struct sky2_hw *hw,
        if (highmem)
                dev->features |= NETIF_F_HIGHDMA;
 
+       /* Enable receive hashing unless hardware is known broken */
+       if (!(hw->flags & SKY2_HW_RSS_BROKEN))
+               dev->features |= NETIF_F_RXHASH;
+
 #ifdef SKY2_VLAN_TAG_USED
        /* The workaround for FE+ status conflicts with VLAN tag detection. */
        if (!(sky2->hw->chip_id == CHIP_ID_YUKON_FE_P &&
@@ -4692,7 +4767,7 @@ static int __devinit sky2_probe(struct pci_dev *pdev,
                goto err_out_iounmap;
 
        /* ring for status responses */
-       hw->st_size = hw->ports * roundup_pow_of_two(2*RX_MAX_PENDING + TX_MAX_PENDING);
+       hw->st_size = hw->ports * roundup_pow_of_two(3*RX_MAX_PENDING + TX_MAX_PENDING);
        hw->st_le = pci_alloc_consistent(pdev, hw->st_size * sizeof(struct sky2_status_le),
                                         &hw->st_dma);
        if (!hw->st_le)
index 125b5bd..545a3f4 100644 (file)
@@ -694,8 +694,21 @@ enum {
        TXA_CTRL        = 0x0210,/*  8 bit      Tx Arbiter Control Register */
        TXA_TEST        = 0x0211,/*  8 bit      Tx Arbiter Test Register */
        TXA_STAT        = 0x0212,/*  8 bit      Tx Arbiter Status Register */
+
+       RSS_KEY         = 0x0220, /* RSS Key setup */
+       RSS_CFG         = 0x0248, /* RSS Configuration */
 };
 
+enum {
+       HASH_TCP_IPV6_EX_CTRL   = 1<<5,
+       HASH_IPV6_EX_CTRL       = 1<<4,
+       HASH_TCP_IPV6_CTRL      = 1<<3,
+       HASH_IPV6_CTRL          = 1<<2,
+       HASH_TCP_IPV4_CTRL      = 1<<1,
+       HASH_IPV4_CTRL          = 1<<0,
+
+       HASH_ALL                = 0x3f,
+};
 
 enum {
        B6_EXT_REG      = 0x0300,/* External registers (GENESIS only) */
@@ -2261,6 +2274,7 @@ struct sky2_hw {
 #define SKY2_HW_NEW_LE         0x00000020      /* new LSOv2 format */
 #define SKY2_HW_AUTO_TX_SUM    0x00000040      /* new IP decode for Tx */
 #define SKY2_HW_ADV_POWER_CTL  0x00000080      /* additional PHY power regs */
+#define SKY2_HW_RSS_BROKEN     0x00000100
 
        u8                   chip_id;
        u8                   chip_rev;