Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net
[linux-2.6-microblaze.git] / drivers / net / ethernet / stmicro / stmmac / stmmac_selftests.c
index e4ac3c4..f3d8b93 100644 (file)
@@ -6,7 +6,9 @@
  * Author: Jose Abreu <joabreu@synopsys.com>
  */
 
+#include <linux/bitrev.h>
 #include <linux/completion.h>
+#include <linux/crc32.h>
 #include <linux/ethtool.h>
 #include <linux/ip.h>
 #include <linux/phy.h>
@@ -485,12 +487,48 @@ static int stmmac_filter_check(struct stmmac_priv *priv)
        return -EOPNOTSUPP;
 }
 
+static bool stmmac_hash_check(struct stmmac_priv *priv, unsigned char *addr)
+{
+       int mc_offset = 32 - priv->hw->mcast_bits_log2;
+       struct netdev_hw_addr *ha;
+       u32 hash, hash_nr;
+
+       /* First compute the hash for desired addr */
+       hash = bitrev32(~crc32_le(~0, addr, 6)) >> mc_offset;
+       hash_nr = hash >> 5;
+       hash = 1 << (hash & 0x1f);
+
+       /* Now, check if it collides with any existing one */
+       netdev_for_each_mc_addr(ha, priv->dev) {
+               u32 nr = bitrev32(~crc32_le(~0, ha->addr, ETH_ALEN)) >> mc_offset;
+               if (((nr >> 5) == hash_nr) && ((1 << (nr & 0x1f)) == hash))
+                       return false;
+       }
+
+       /* No collisions, address is good to go */
+       return true;
+}
+
+static bool stmmac_perfect_check(struct stmmac_priv *priv, unsigned char *addr)
+{
+       struct netdev_hw_addr *ha;
+
+       /* Check if it collides with any existing one */
+       netdev_for_each_uc_addr(ha, priv->dev) {
+               if (!memcmp(ha->addr, addr, ETH_ALEN))
+                       return false;
+       }
+
+       /* No collisions, address is good to go */
+       return true;
+}
+
 static int stmmac_test_hfilt(struct stmmac_priv *priv)
 {
-       unsigned char gd_addr[ETH_ALEN] = {0x01, 0xee, 0xdd, 0xcc, 0xbb, 0xaa};
-       unsigned char bd_addr[ETH_ALEN] = {0x01, 0x01, 0x02, 0x03, 0x04, 0x05};
+       unsigned char gd_addr[ETH_ALEN] = {0xf1, 0xee, 0xdd, 0xcc, 0xbb, 0xaa};
+       unsigned char bd_addr[ETH_ALEN] = {0xf1, 0xff, 0xff, 0xff, 0xff, 0xff};
        struct stmmac_packet_attrs attr = { };
-       int ret;
+       int ret, tries = 256;
 
        ret = stmmac_filter_check(priv);
        if (ret)
@@ -499,6 +537,16 @@ static int stmmac_test_hfilt(struct stmmac_priv *priv)
        if (netdev_mc_count(priv->dev) >= priv->hw->multicast_filter_bins)
                return -EOPNOTSUPP;
 
+       while (--tries) {
+               /* We only need to check the bd_addr for collisions */
+               bd_addr[ETH_ALEN - 1] = tries;
+               if (stmmac_hash_check(priv, bd_addr))
+                       break;
+       }
+
+       if (!tries)
+               return -EOPNOTSUPP;
+
        ret = dev_mc_add(priv->dev, gd_addr);
        if (ret)
                return ret;
@@ -523,13 +571,25 @@ cleanup:
 
 static int stmmac_test_pfilt(struct stmmac_priv *priv)
 {
-       unsigned char gd_addr[ETH_ALEN] = {0x00, 0x01, 0x44, 0x55, 0x66, 0x77};
-       unsigned char bd_addr[ETH_ALEN] = {0x08, 0x00, 0x22, 0x33, 0x44, 0x55};
+       unsigned char gd_addr[ETH_ALEN] = {0xf0, 0x01, 0x44, 0x55, 0x66, 0x77};
+       unsigned char bd_addr[ETH_ALEN] = {0xf0, 0xff, 0xff, 0xff, 0xff, 0xff};
        struct stmmac_packet_attrs attr = { };
-       int ret;
+       int ret, tries = 256;
 
        if (stmmac_filter_check(priv))
                return -EOPNOTSUPP;
+       if (netdev_uc_count(priv->dev) >= priv->hw->unicast_filter_entries)
+               return -EOPNOTSUPP;
+
+       while (--tries) {
+               /* We only need to check the bd_addr for collisions */
+               bd_addr[ETH_ALEN - 1] = tries;
+               if (stmmac_perfect_check(priv, bd_addr))
+                       break;
+       }
+
+       if (!tries)
+               return -EOPNOTSUPP;
 
        ret = dev_uc_add(priv->dev, gd_addr);
        if (ret)
@@ -553,39 +613,31 @@ cleanup:
        return ret;
 }
 
-static int stmmac_dummy_sync(struct net_device *netdev, const u8 *addr)
-{
-       return 0;
-}
-
-static void stmmac_test_set_rx_mode(struct net_device *netdev)
-{
-       /* As we are in test mode of ethtool we already own the rtnl lock
-        * so no address will change from user. We can just call the
-        * ndo_set_rx_mode() callback directly */
-       if (netdev->netdev_ops->ndo_set_rx_mode)
-               netdev->netdev_ops->ndo_set_rx_mode(netdev);
-}
-
 static int stmmac_test_mcfilt(struct stmmac_priv *priv)
 {
-       unsigned char uc_addr[ETH_ALEN] = {0x00, 0x01, 0x44, 0x55, 0x66, 0x77};
-       unsigned char mc_addr[ETH_ALEN] = {0x01, 0x01, 0x44, 0x55, 0x66, 0x77};
+       unsigned char uc_addr[ETH_ALEN] = {0xf0, 0xff, 0xff, 0xff, 0xff, 0xff};
+       unsigned char mc_addr[ETH_ALEN] = {0xf1, 0xff, 0xff, 0xff, 0xff, 0xff};
        struct stmmac_packet_attrs attr = { };
-       int ret;
+       int ret, tries = 256;
 
        if (stmmac_filter_check(priv))
                return -EOPNOTSUPP;
-       if (!priv->hw->multicast_filter_bins)
+       if (netdev_uc_count(priv->dev) >= priv->hw->unicast_filter_entries)
                return -EOPNOTSUPP;
 
-       /* Remove all MC addresses */
-       __dev_mc_unsync(priv->dev, NULL);
-       stmmac_test_set_rx_mode(priv->dev);
+       while (--tries) {
+               /* We only need to check the mc_addr for collisions */
+               mc_addr[ETH_ALEN - 1] = tries;
+               if (stmmac_hash_check(priv, mc_addr))
+                       break;
+       }
+
+       if (!tries)
+               return -EOPNOTSUPP;
 
        ret = dev_uc_add(priv->dev, uc_addr);
        if (ret)
-               goto cleanup;
+               return ret;
 
        attr.dst = uc_addr;
 
@@ -602,30 +654,34 @@ static int stmmac_test_mcfilt(struct stmmac_priv *priv)
 
 cleanup:
        dev_uc_del(priv->dev, uc_addr);
-       __dev_mc_sync(priv->dev, stmmac_dummy_sync, NULL);
-       stmmac_test_set_rx_mode(priv->dev);
        return ret;
 }
 
 static int stmmac_test_ucfilt(struct stmmac_priv *priv)
 {
-       unsigned char uc_addr[ETH_ALEN] = {0x00, 0x01, 0x44, 0x55, 0x66, 0x77};
-       unsigned char mc_addr[ETH_ALEN] = {0x01, 0x01, 0x44, 0x55, 0x66, 0x77};
+       unsigned char uc_addr[ETH_ALEN] = {0xf0, 0xff, 0xff, 0xff, 0xff, 0xff};
+       unsigned char mc_addr[ETH_ALEN] = {0xf1, 0xff, 0xff, 0xff, 0xff, 0xff};
        struct stmmac_packet_attrs attr = { };
-       int ret;
+       int ret, tries = 256;
 
        if (stmmac_filter_check(priv))
                return -EOPNOTSUPP;
-       if (!priv->hw->multicast_filter_bins)
+       if (netdev_mc_count(priv->dev) >= priv->hw->multicast_filter_bins)
                return -EOPNOTSUPP;
 
-       /* Remove all UC addresses */
-       __dev_uc_unsync(priv->dev, NULL);
-       stmmac_test_set_rx_mode(priv->dev);
+       while (--tries) {
+               /* We only need to check the uc_addr for collisions */
+               uc_addr[ETH_ALEN - 1] = tries;
+               if (stmmac_perfect_check(priv, uc_addr))
+                       break;
+       }
+
+       if (!tries)
+               return -EOPNOTSUPP;
 
        ret = dev_mc_add(priv->dev, mc_addr);
        if (ret)
-               goto cleanup;
+               return ret;
 
        attr.dst = mc_addr;
 
@@ -642,8 +698,6 @@ static int stmmac_test_ucfilt(struct stmmac_priv *priv)
 
 cleanup:
        dev_mc_del(priv->dev, mc_addr);
-       __dev_uc_sync(priv->dev, stmmac_dummy_sync, NULL);
-       stmmac_test_set_rx_mode(priv->dev);
        return ret;
 }
 
@@ -823,16 +877,13 @@ out:
        return 0;
 }
 
-static int stmmac_test_vlanfilt(struct stmmac_priv *priv)
+static int __stmmac_test_vlanfilt(struct stmmac_priv *priv)
 {
        struct stmmac_packet_attrs attr = { };
        struct stmmac_test_priv *tpriv;
        struct sk_buff *skb = NULL;
        int ret = 0, i;
 
-       if (!priv->dma_cap.vlhash)
-               return -EOPNOTSUPP;
-
        tpriv = kzalloc(sizeof(*tpriv), GFP_KERNEL);
        if (!tpriv)
                return -ENOMEM;
@@ -898,16 +949,32 @@ cleanup:
        return ret;
 }
 
-static int stmmac_test_dvlanfilt(struct stmmac_priv *priv)
+static int stmmac_test_vlanfilt(struct stmmac_priv *priv)
+{
+       if (!priv->dma_cap.vlhash)
+               return -EOPNOTSUPP;
+
+       return __stmmac_test_vlanfilt(priv);
+}
+
+static int stmmac_test_vlanfilt_perfect(struct stmmac_priv *priv)
+{
+       int ret, prev_cap = priv->dma_cap.vlhash;
+
+       priv->dma_cap.vlhash = 0;
+       ret = __stmmac_test_vlanfilt(priv);
+       priv->dma_cap.vlhash = prev_cap;
+
+       return ret;
+}
+
+static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv)
 {
        struct stmmac_packet_attrs attr = { };
        struct stmmac_test_priv *tpriv;
        struct sk_buff *skb = NULL;
        int ret = 0, i;
 
-       if (!priv->dma_cap.vlhash)
-               return -EOPNOTSUPP;
-
        tpriv = kzalloc(sizeof(*tpriv), GFP_KERNEL);
        if (!tpriv)
                return -ENOMEM;
@@ -974,6 +1041,25 @@ cleanup:
        return ret;
 }
 
+static int stmmac_test_dvlanfilt(struct stmmac_priv *priv)
+{
+       if (!priv->dma_cap.vlhash)
+               return -EOPNOTSUPP;
+
+       return __stmmac_test_dvlanfilt(priv);
+}
+
+static int stmmac_test_dvlanfilt_perfect(struct stmmac_priv *priv)
+{
+       int ret, prev_cap = priv->dma_cap.vlhash;
+
+       priv->dma_cap.vlhash = 0;
+       ret = __stmmac_test_dvlanfilt(priv);
+       priv->dma_cap.vlhash = prev_cap;
+
+       return ret;
+}
+
 #ifdef CONFIG_NET_CLS_ACT
 static int stmmac_test_rxp(struct stmmac_priv *priv)
 {
@@ -1648,119 +1734,127 @@ static const struct stmmac_test {
        int (*fn)(struct stmmac_priv *priv);
 } stmmac_selftests[] = {
        {
-               .name = "MAC Loopback         ",
+               .name = "MAC Loopback               ",
                .lb = STMMAC_LOOPBACK_MAC,
                .fn = stmmac_test_mac_loopback,
        }, {
-               .name = "PHY Loopback         ",
+               .name = "PHY Loopback               ",
                .lb = STMMAC_LOOPBACK_NONE, /* Test will handle it */
                .fn = stmmac_test_phy_loopback,
        }, {
-               .name = "MMC Counters         ",
+               .name = "MMC Counters               ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_mmc,
        }, {
-               .name = "EEE                  ",
+               .name = "EEE                        ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_eee,
        }, {
-               .name = "Hash Filter MC       ",
+               .name = "Hash Filter MC             ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_hfilt,
        }, {
-               .name = "Perfect Filter UC    ",
+               .name = "Perfect Filter UC          ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_pfilt,
        }, {
-               .name = "MC Filter            ",
+               .name = "MC Filter                  ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_mcfilt,
        }, {
-               .name = "UC Filter            ",
+               .name = "UC Filter                  ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_ucfilt,
        }, {
-               .name = "Flow Control         ",
+               .name = "Flow Control               ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_flowctrl,
        }, {
-               .name = "RSS                  ",
+               .name = "RSS                        ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_rss,
        }, {
-               .name = "VLAN Filtering       ",
+               .name = "VLAN Filtering             ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_vlanfilt,
        }, {
-               .name = "Double VLAN Filtering",
+               .name = "VLAN Filtering (perf)      ",
+               .lb = STMMAC_LOOPBACK_PHY,
+               .fn = stmmac_test_vlanfilt_perfect,
+       }, {
+               .name = "Double VLAN Filter         ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_dvlanfilt,
        }, {
-               .name = "Flexible RX Parser   ",
+               .name = "Double VLAN Filter (perf)  ",
+               .lb = STMMAC_LOOPBACK_PHY,
+               .fn = stmmac_test_dvlanfilt_perfect,
+       }, {
+               .name = "Flexible RX Parser         ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_rxp,
        }, {
-               .name = "SA Insertion (desc)  ",
+               .name = "SA Insertion (desc)        ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_desc_sai,
        }, {
-               .name = "SA Replacement (desc)",
+               .name = "SA Replacement (desc)      ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_desc_sar,
        }, {
-               .name = "SA Insertion (reg)  ",
+               .name = "SA Insertion (reg)         ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_reg_sai,
        }, {
-               .name = "SA Replacement (reg)",
+               .name = "SA Replacement (reg)       ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_reg_sar,
        }, {
-               .name = "VLAN TX Insertion   ",
+               .name = "VLAN TX Insertion          ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_vlanoff,
        }, {
-               .name = "SVLAN TX Insertion  ",
+               .name = "SVLAN TX Insertion         ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_svlanoff,
        }, {
-               .name = "L3 DA Filtering     ",
+               .name = "L3 DA Filtering            ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_l3filt_da,
        }, {
-               .name = "L3 SA Filtering     ",
+               .name = "L3 SA Filtering            ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_l3filt_sa,
        }, {
-               .name = "L4 DA TCP Filtering ",
+               .name = "L4 DA TCP Filtering        ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_l4filt_da_tcp,
        }, {
-               .name = "L4 SA TCP Filtering ",
+               .name = "L4 SA TCP Filtering        ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_l4filt_sa_tcp,
        }, {
-               .name = "L4 DA UDP Filtering ",
+               .name = "L4 DA UDP Filtering        ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_l4filt_da_udp,
        }, {
-               .name = "L4 SA UDP Filtering ",
+               .name = "L4 SA UDP Filtering        ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_l4filt_sa_udp,
        }, {
-               .name = "ARP Offload         ",
+               .name = "ARP Offload                ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_arpoffload,
        }, {
-               .name = "Jumbo Frame         ",
+               .name = "Jumbo Frame                ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_jumbo,
        }, {
-               .name = "Multichannel Jumbo  ",
+               .name = "Multichannel Jumbo         ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_mjumbo,
        }, {
-               .name = "Split Header        ",
+               .name = "Split Header               ",
                .lb = STMMAC_LOOPBACK_PHY,
                .fn = stmmac_test_sph,
        },