netxen: hw multicast filtering

Enable multicast address filtering capabilities in the hardware.
Upto 16 multicast addresses can be programmed for each physical
port. Support "allmulti" mode, if enabled.

Signed-off-by: Dhananjay Phadke <dhananjay@netxen.com>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
diff --git a/drivers/net/netxen/netxen_nic_hw.c b/drivers/net/netxen/netxen_nic_hw.c
index fa6d034..93466ec 100644
--- a/drivers/net/netxen/netxen_nic_hw.c
+++ b/drivers/net/netxen/netxen_nic_hw.c
@@ -154,7 +154,6 @@
 	if (!is_valid_ether_addr(addr->sa_data))
 		return -EADDRNOTAVAIL;
 
-	DPRINTK(INFO, "valid ether addr\n");
 	memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len);
 
 	if (adapter->macaddr_set)
@@ -163,6 +162,91 @@
 	return 0;
 }
 
+#define NETXEN_UNICAST_ADDR(port, index) \
+	(NETXEN_UNICAST_ADDR_BASE+(port*32)+(index*8))
+#define NETXEN_MCAST_ADDR(port, index) \
+	(NETXEN_MULTICAST_ADDR_BASE+(port*0x80)+(index*8))
+#define MAC_HI(addr) \
+	((addr[2] << 16) | (addr[1] << 8) | (addr[0]))
+#define MAC_LO(addr) \
+	((addr[5] << 16) | (addr[4] << 8) | (addr[3]))
+
+static int
+netxen_nic_enable_mcast_filter(struct netxen_adapter *adapter)
+{
+	u32	val = 0;
+	u16 port = adapter->physical_port;
+	u8 *addr = adapter->netdev->dev_addr;
+
+	if (adapter->mc_enabled)
+		return 0;
+
+	netxen_nic_hw_read_wx(adapter, NETXEN_MAC_ADDR_CNTL_REG, &val, 4);
+	val |= (1UL << (28+port));
+	netxen_nic_hw_write_wx(adapter, NETXEN_MAC_ADDR_CNTL_REG, &val, 4);
+
+	/* add broadcast addr to filter */
+	val = 0xffffff;
+	netxen_crb_writelit_adapter(adapter, NETXEN_UNICAST_ADDR(port, 0), val);
+	netxen_crb_writelit_adapter(adapter,
+			NETXEN_UNICAST_ADDR(port, 0)+4, val);
+
+	/* add station addr to filter */
+	val = MAC_HI(addr);
+	netxen_crb_writelit_adapter(adapter, NETXEN_UNICAST_ADDR(port, 1), val);
+	val = MAC_LO(addr);
+	netxen_crb_writelit_adapter(adapter,
+			NETXEN_UNICAST_ADDR(port, 1)+4, val);
+
+	adapter->mc_enabled = 1;
+	return 0;
+}
+
+static int
+netxen_nic_disable_mcast_filter(struct netxen_adapter *adapter)
+{
+	u32	val = 0;
+	u16 port = adapter->physical_port;
+	u8 *addr = adapter->netdev->dev_addr;
+
+	if (!adapter->mc_enabled)
+		return 0;
+
+	netxen_nic_hw_read_wx(adapter, NETXEN_MAC_ADDR_CNTL_REG, &val, 4);
+	val &= ~(1UL << (28+port));
+	netxen_nic_hw_write_wx(adapter, NETXEN_MAC_ADDR_CNTL_REG, &val, 4);
+
+	val = MAC_HI(addr);
+	netxen_crb_writelit_adapter(adapter, NETXEN_UNICAST_ADDR(port, 0), val);
+	val = MAC_LO(addr);
+	netxen_crb_writelit_adapter(adapter,
+			NETXEN_UNICAST_ADDR(port, 0)+4, val);
+
+	netxen_crb_writelit_adapter(adapter, NETXEN_UNICAST_ADDR(port, 1), 0);
+	netxen_crb_writelit_adapter(adapter, NETXEN_UNICAST_ADDR(port, 1)+4, 0);
+
+	adapter->mc_enabled = 0;
+	return 0;
+}
+
+static int
+netxen_nic_set_mcast_addr(struct netxen_adapter *adapter,
+		int index, u8 *addr)
+{
+	u32 hi = 0, lo = 0;
+	u16 port = adapter->physical_port;
+
+	lo = MAC_LO(addr);
+	hi = MAC_HI(addr);
+
+	netxen_crb_writelit_adapter(adapter,
+			NETXEN_MCAST_ADDR(port, index), hi);
+	netxen_crb_writelit_adapter(adapter,
+			NETXEN_MCAST_ADDR(port, index)+4, lo);
+
+	return 0;
+}
+
 /*
  * netxen_nic_set_multi - Multicast
  */
@@ -170,17 +254,48 @@
 {
 	struct netxen_adapter *adapter = netdev_priv(netdev);
 	struct dev_mc_list *mc_ptr;
+	u8 null_addr[6];
+	int index = 0;
 
-	mc_ptr = netdev->mc_list;
+	memset(null_addr, 0, 6);
+
 	if (netdev->flags & IFF_PROMISC) {
-		if (adapter->set_promisc)
-			adapter->set_promisc(adapter,
-					     NETXEN_NIU_PROMISC_MODE);
-	} else {
-		if (adapter->unset_promisc)
-			adapter->unset_promisc(adapter,
-					       NETXEN_NIU_NON_PROMISC_MODE);
+
+		adapter->set_promisc(adapter,
+				NETXEN_NIU_PROMISC_MODE);
+
+		/* Full promiscuous mode */
+		netxen_nic_disable_mcast_filter(adapter);
+
+		return;
 	}
+
+	if (netdev->mc_count == 0) {
+		adapter->set_promisc(adapter,
+				NETXEN_NIU_NON_PROMISC_MODE);
+		netxen_nic_disable_mcast_filter(adapter);
+		return;
+	}
+
+	adapter->set_promisc(adapter, NETXEN_NIU_ALLMULTI_MODE);
+	if (netdev->flags & IFF_ALLMULTI ||
+			netdev->mc_count > adapter->max_mc_count) {
+		netxen_nic_disable_mcast_filter(adapter);
+		return;
+	}
+
+	netxen_nic_enable_mcast_filter(adapter);
+
+	for (mc_ptr = netdev->mc_list; mc_ptr; mc_ptr = mc_ptr->next, index++)
+		netxen_nic_set_mcast_addr(adapter, index, mc_ptr->dmi_addr);
+
+	if (index != netdev->mc_count)
+		printk(KERN_WARNING "%s: %s multicast address count mismatch\n",
+			netxen_nic_driver_name, netdev->name);
+
+	/* Clear out remaining addresses */
+	for (; index < adapter->max_mc_count; index++)
+		netxen_nic_set_mcast_addr(adapter, index, null_addr);
 }
 
 /*