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 0b5db52..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;
 }