s390/qeth: optimize MAC handling in rx_mode callback
[linux-2.6-block.git] / drivers / s390 / net / qeth_l2_main.c
index b6eabfa13762fc16dd6a2378ed87b6f51de11286..8f1b091e173277a718c232bdbf707fb03db21945 100644 (file)
@@ -19,7 +19,9 @@
 #include <linux/mii.h>
 #include <linux/ip.h>
 #include <linux/list.h>
-
+#include <linux/hash.h>
+#include <linux/hashtable.h>
+#include <linux/string.h>
 #include "qeth_core.h"
 #include "qeth_l2.h"
 
@@ -28,7 +30,7 @@ static int qeth_l2_stop(struct net_device *);
 static int qeth_l2_send_delmac(struct qeth_card *, __u8 *);
 static int qeth_l2_send_setdelmac(struct qeth_card *, __u8 *,
                           enum qeth_ipa_cmds);
-static void qeth_l2_set_multicast_list(struct net_device *);
+static void qeth_l2_set_rx_mode(struct net_device *);
 static int qeth_l2_recover(void *);
 static void qeth_bridgeport_query_support(struct qeth_card *card);
 static void qeth_bridge_state_change(struct qeth_card *card,
@@ -193,49 +195,44 @@ static int qeth_l2_send_delgroupmac(struct qeth_card *card, __u8 *mac)
        return rc;
 }
 
-static void qeth_l2_add_mc(struct qeth_card *card, __u8 *mac, int vmac)
+static inline u32 qeth_l2_mac_hash(const u8 *addr)
 {
-       struct qeth_mc_mac *mc;
-       int rc;
-
-       mc = kmalloc(sizeof(struct qeth_mc_mac), GFP_ATOMIC);
+       return get_unaligned((u32 *)(&addr[2]));
+}
 
-       if (!mc)
-               return;
+static int qeth_l2_write_mac(struct qeth_card *card, struct qeth_mac *mac)
+{
 
-       memcpy(mc->mc_addr, mac, OSA_ADDR_LEN);
-       mc->mc_addrlen = OSA_ADDR_LEN;
-       mc->is_vmac = vmac;
+       int rc;
 
-       if (vmac) {
+       if (mac->is_uc) {
                rc = qeth_setdel_makerc(card,
-                       qeth_l2_send_setdelmac(card, mac, IPA_CMD_SETVMAC));
+                               qeth_l2_send_setdelmac(card, mac->mac_addr,
+                                               IPA_CMD_SETVMAC));
        } else {
                rc = qeth_setdel_makerc(card,
-                       qeth_l2_send_setgroupmac(card, mac));
+                               qeth_l2_send_setgroupmac(card, mac->mac_addr));
        }
-
-       if (!rc)
-               list_add_tail(&mc->list, &card->mc_list);
-       else
-               kfree(mc);
+       return rc;
 }
 
-static void qeth_l2_del_all_mc(struct qeth_card *card, int del)
+static void qeth_l2_del_all_macs(struct qeth_card *card, int del)
 {
-       struct qeth_mc_mac *mc, *tmp;
+       struct qeth_mac *mac;
+       struct hlist_node *tmp;
+       int i;
 
        spin_lock_bh(&card->mclock);
-       list_for_each_entry_safe(mc, tmp, &card->mc_list, list) {
+       hash_for_each_safe(card->mac_htable, i, tmp, mac, hnode) {
                if (del) {
-                       if (mc->is_vmac)
-                               qeth_l2_send_setdelmac(card, mc->mc_addr,
-                                       IPA_CMD_DELVMAC);
+                       if (mac->is_uc)
+                               qeth_l2_send_setdelmac(card, mac->mac_addr,
+                                               IPA_CMD_DELVMAC);
                        else
-                               qeth_l2_send_delgroupmac(card, mc->mc_addr);
+                               qeth_l2_send_delgroupmac(card, mac->mac_addr);
                }
-               list_del(&mc->list);
-               kfree(mc);
+               hash_del(&mac->hnode);
+               kfree(mac);
        }
        spin_unlock_bh(&card->mclock);
 }
@@ -403,7 +400,7 @@ static int qeth_l2_vlan_rx_kill_vid(struct net_device *dev,
                rc = qeth_l2_send_setdelvlan(card, vid, IPA_CMD_DELVLAN);
                kfree(tmpid);
        }
-       qeth_l2_set_multicast_list(card->dev);
+       qeth_l2_set_rx_mode(card->dev);
        return rc;
 }
 
@@ -460,7 +457,7 @@ static void qeth_l2_stop_card(struct qeth_card *card, int recovery_mode)
                card->state = CARD_STATE_SOFTSETUP;
        }
        if (card->state == CARD_STATE_SOFTSETUP) {
-               qeth_l2_del_all_mc(card, 0);
+               qeth_l2_del_all_macs(card, 0);
                qeth_clear_ipacmd_list(card);
                card->state = CARD_STATE_HARDSETUP;
        }
@@ -768,29 +765,91 @@ static void qeth_promisc_to_bridge(struct qeth_card *card)
                card->options.sbp.role = role;
                card->info.promisc_mode = promisc_mode;
        }
+
+}
+/* New MAC address is added to the hash table and marked to be written on card
+ * only if there is not in the hash table storage already
+ *
+*/
+static void
+qeth_l2_add_mac(struct qeth_card *card, struct netdev_hw_addr *ha, u8 is_uc)
+{
+       struct qeth_mac *mac;
+
+       hash_for_each_possible(card->mac_htable, mac, hnode,
+                       qeth_l2_mac_hash(ha->addr)) {
+               if (is_uc == mac->is_uc &&
+                   !memcmp(ha->addr, mac->mac_addr, OSA_ADDR_LEN)) {
+                       mac->disp_flag = QETH_DISP_MAC_DO_NOTHING;
+                       return;
+               }
+       }
+
+       mac = kzalloc(sizeof(struct qeth_mac), GFP_ATOMIC);
+
+       if (!mac)
+               return;
+
+       memcpy(mac->mac_addr, ha->addr, OSA_ADDR_LEN);
+       mac->is_uc = is_uc;
+       mac->disp_flag = QETH_DISP_MAC_ADD;
+
+       hash_add(card->mac_htable, &mac->hnode,
+                       qeth_l2_mac_hash(mac->mac_addr));
+
 }
 
-static void qeth_l2_set_multicast_list(struct net_device *dev)
+static void qeth_l2_set_rx_mode(struct net_device *dev)
 {
        struct qeth_card *card = dev->ml_priv;
        struct netdev_hw_addr *ha;
+       struct qeth_mac *mac;
+       struct hlist_node *tmp;
+       int i;
+       int rc;
 
        if (card->info.type == QETH_CARD_TYPE_OSN)
-               return ;
+               return;
 
        QETH_CARD_TEXT(card, 3, "setmulti");
        if (qeth_threads_running(card, QETH_RECOVER_THREAD) &&
            (card->state != CARD_STATE_UP))
                return;
-       qeth_l2_del_all_mc(card, 1);
+
        spin_lock_bh(&card->mclock);
+
        netdev_for_each_mc_addr(ha, dev)
-               qeth_l2_add_mc(card, ha->addr, 0);
+               qeth_l2_add_mac(card, ha, 0);
 
        netdev_for_each_uc_addr(ha, dev)
-               qeth_l2_add_mc(card, ha->addr, 1);
+               qeth_l2_add_mac(card, ha, 1);
+
+       hash_for_each_safe(card->mac_htable, i, tmp, mac, hnode) {
+               if (mac->disp_flag == QETH_DISP_MAC_DELETE) {
+                       if (!mac->is_uc)
+                               rc = qeth_l2_send_delgroupmac(card,
+                                               mac->mac_addr);
+                       else {
+                               rc = qeth_l2_send_setdelmac(card, mac->mac_addr,
+                                               IPA_CMD_DELVMAC);
+                       }
+
+                       hash_del(&mac->hnode);
+                       kfree(mac);
+
+               } else if (mac->disp_flag == QETH_DISP_MAC_ADD) {
+                       rc = qeth_l2_write_mac(card, mac);
+                       if (rc) {
+                               hash_del(&mac->hnode);
+                               kfree(mac);
+                       } else
+                               mac->disp_flag = QETH_DISP_MAC_DELETE;
+               } else
+                       mac->disp_flag = QETH_DISP_MAC_DELETE;
+       }
 
        spin_unlock_bh(&card->mclock);
+
        if (qeth_adp_supported(card, IPA_SETADP_SET_PROMISC_MODE))
                qeth_setadp_promisc_mode(card);
        else
@@ -974,7 +1033,7 @@ static int qeth_l2_probe_device(struct ccwgroup_device *gdev)
 
        qeth_l2_create_device_attributes(&gdev->dev);
        INIT_LIST_HEAD(&card->vid_list);
-       INIT_LIST_HEAD(&card->mc_list);
+       hash_init(card->mac_htable);
        card->options.layer2 = 1;
        card->info.hwtrap = 0;
        return 0;
@@ -1020,7 +1079,7 @@ static const struct net_device_ops qeth_l2_netdev_ops = {
        .ndo_get_stats          = qeth_get_stats,
        .ndo_start_xmit         = qeth_l2_hard_start_xmit,
        .ndo_validate_addr      = eth_validate_addr,
-       .ndo_set_rx_mode        = qeth_l2_set_multicast_list,
+       .ndo_set_rx_mode        = qeth_l2_set_rx_mode,
        .ndo_do_ioctl           = qeth_l2_do_ioctl,
        .ndo_set_mac_address    = qeth_l2_set_mac_address,
        .ndo_change_mtu         = qeth_change_mtu,
@@ -1179,7 +1238,7 @@ contin:
                        rtnl_unlock();
                }
                /* this also sets saved unicast addresses */
-               qeth_l2_set_multicast_list(card->dev);
+               qeth_l2_set_rx_mode(card->dev);
        }
        /* let user_space know that device is online */
        kobject_uevent(&gdev->dev.kobj, KOBJ_CHANGE);