net: calxedaxgmac: fix various errors in xgmac_set_rx_mode
authorRob Herring <rob.herring@calxeda.com>
Fri, 30 Aug 2013 21:49:26 +0000 (16:49 -0500)
committerDavid S. Miller <davem@davemloft.net>
Wed, 4 Sep 2013 02:21:15 +0000 (22:21 -0400)
Fix xgmac_set_rx_mode to handle several conditions that were not handled
correctly as Lennert Buytenhek describes:

If we have, say, 100 unicast addresses, and 5 multicast addresses, the
unicast address count check will evaluate to true, and set use_hash to
true.  The multicast address check will however evaluate to false, and
use_hash won't be set to true again, and XGMAC_FRAME_FILTER_HMC won't
be OR'd into XGMAC_FRAME_FILTER, but since use_hash was still true
from the unicast check, netdev_for_each_mc_addr() will program the
multicast addresses into the hash table instead of using the MAC
address registers, but since the HMC bit wasn't set, the hash table
won't be checked for multicast addresses on receive, and we'll stop
receiving multicast packets entirely.

Also, there is no code that zeroes out MAC address registers reg..31
at the end of this function, meaning that under the right conditions,
unicast/multicast addresses that were previously in the filter but
were then deleted won't be cleared out.

Reported-by: Lennert Buytenhek <buytenh@wantstofly.org>
Signed-off-by: Rob Herring <rob.herring@calxeda.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/calxeda/xgmac.c

index 73d3f83468c401406b581648f6b0f26c33208eff..0cff9e3e0cb7d9c28b02daa09e4e51ad07346308 100644 (file)
@@ -618,10 +618,15 @@ static void xgmac_set_mac_addr(void __iomem *ioaddr, unsigned char *addr,
 {
        u32 data;
 
-       data = (addr[5] << 8) | addr[4] | (num ? XGMAC_ADDR_AE : 0);
-       writel(data, ioaddr + XGMAC_ADDR_HIGH(num));
-       data = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
-       writel(data, ioaddr + XGMAC_ADDR_LOW(num));
+       if (addr) {
+               data = (addr[5] << 8) | addr[4] | (num ? XGMAC_ADDR_AE : 0);
+               writel(data, ioaddr + XGMAC_ADDR_HIGH(num));
+               data = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
+               writel(data, ioaddr + XGMAC_ADDR_LOW(num));
+       } else {
+               writel(0, ioaddr + XGMAC_ADDR_HIGH(num));
+               writel(0, ioaddr + XGMAC_ADDR_LOW(num));
+       }
 }
 
 static void xgmac_get_mac_addr(void __iomem *ioaddr, unsigned char *addr,
@@ -1294,6 +1299,8 @@ static void xgmac_set_rx_mode(struct net_device *dev)
        if ((netdev_mc_count(dev) + reg - 1) > XGMAC_MAX_FILTER_ADDR) {
                use_hash = true;
                value |= XGMAC_FRAME_FILTER_HMC | XGMAC_FRAME_FILTER_HPF;
+       } else {
+               use_hash = false;
        }
        netdev_for_each_mc_addr(ha, dev) {
                if (use_hash) {
@@ -1310,6 +1317,8 @@ static void xgmac_set_rx_mode(struct net_device *dev)
        }
 
 out:
+       for (i = reg; i < XGMAC_MAX_FILTER_ADDR; i++)
+               xgmac_set_mac_addr(ioaddr, NULL, reg);
        for (i = 0; i < XGMAC_NUM_HASH; i++)
                writel(hash_filter[i], ioaddr + XGMAC_HASH(i));