]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/s390/net/qeth_l3_main.c
Merge tag 'mmc-v4.8' of git://git.linaro.org/people/ulf.hansson/mmc
[karo-tx-linux.git] / drivers / s390 / net / qeth_l3_main.c
index 709b52339ff9a5907812d315e65bca964ddd11be..72934666fedf0193bef37fcc5378127ed2a540c7 100644 (file)
@@ -30,6 +30,7 @@
 #include <net/ip6_fib.h>
 #include <net/ip6_checksum.h>
 #include <net/iucv/af_iucv.h>
+#include <linux/hashtable.h>
 
 #include "qeth_l3.h"
 
@@ -57,7 +58,7 @@ static int qeth_l3_isxdigit(char *buf)
 
 static void qeth_l3_ipaddr4_to_string(const __u8 *addr, char *buf)
 {
-       sprintf(buf, "%i.%i.%i.%i", addr[0], addr[1], addr[2], addr[3]);
+       sprintf(buf, "%pI4", addr);
 }
 
 static int qeth_l3_string_to_ipaddr4(const char *buf, __u8 *addr)
@@ -204,104 +205,129 @@ int qeth_l3_is_addr_covered_by_ipato(struct qeth_card *card,
        return rc;
 }
 
-/*
- * Add IP to be added to todo list. If there is already an "add todo"
- * in this list we just incremenent the reference count.
- * Returns 0 if we  just incremented reference count.
- */
-static int __qeth_l3_insert_ip_todo(struct qeth_card *card,
-                                       struct qeth_ipaddr *addr, int add)
+inline int
+qeth_l3_ipaddrs_is_equal(struct qeth_ipaddr *addr1, struct qeth_ipaddr *addr2)
 {
-       struct qeth_ipaddr *tmp, *t;
-       int found = 0;
+       return addr1->proto == addr2->proto &&
+               !memcmp(&addr1->u, &addr2->u, sizeof(addr1->u))  &&
+               !memcmp(&addr1->mac, &addr2->mac, sizeof(addr1->mac));
+}
 
-       if (card->options.sniffer)
-               return 0;
-       list_for_each_entry_safe(tmp, t, card->ip_tbd_list, entry) {
-               if ((addr->type == QETH_IP_TYPE_DEL_ALL_MC) &&
-                   (tmp->type == QETH_IP_TYPE_DEL_ALL_MC))
-                       return 0;
-               if ((tmp->proto        == QETH_PROT_IPV4)     &&
-                   (addr->proto       == QETH_PROT_IPV4)     &&
-                   (tmp->type         == addr->type)         &&
-                   (tmp->is_multicast == addr->is_multicast) &&
-                   (tmp->u.a4.addr    == addr->u.a4.addr)    &&
-                   (tmp->u.a4.mask    == addr->u.a4.mask)) {
-                       found = 1;
-                       break;
-               }
-               if ((tmp->proto        == QETH_PROT_IPV6)      &&
-                   (addr->proto       == QETH_PROT_IPV6)      &&
-                   (tmp->type         == addr->type)          &&
-                   (tmp->is_multicast == addr->is_multicast)  &&
-                   (tmp->u.a6.pfxlen  == addr->u.a6.pfxlen)   &&
-                   (memcmp(&tmp->u.a6.addr, &addr->u.a6.addr,
-                           sizeof(struct in6_addr)) == 0)) {
-                       found = 1;
-                       break;
-               }
-       }
-       if (found) {
-               if (addr->users != 0)
-                       tmp->users += addr->users;
-               else
-                       tmp->users += add ? 1 : -1;
-               if (tmp->users == 0) {
-                       list_del(&tmp->entry);
-                       kfree(tmp);
-               }
-               return 0;
+static struct qeth_ipaddr *
+qeth_l3_ip_from_hash(struct qeth_card *card, struct qeth_ipaddr *tmp_addr)
+{
+       struct qeth_ipaddr *addr;
+
+       if (tmp_addr->is_multicast) {
+               hash_for_each_possible(card->ip_mc_htable,  addr,
+                               hnode, qeth_l3_ipaddr_hash(tmp_addr))
+                       if (qeth_l3_ipaddrs_is_equal(tmp_addr, addr))
+                               return addr;
        } else {
-               if (addr->type == QETH_IP_TYPE_DEL_ALL_MC)
-                       list_add(&addr->entry, card->ip_tbd_list);
-               else {
-                       if (addr->users == 0)
-                               addr->users += add ? 1 : -1;
-                       if (add && (addr->type == QETH_IP_TYPE_NORMAL) &&
-                           qeth_l3_is_addr_covered_by_ipato(card, addr)) {
-                               QETH_CARD_TEXT(card, 2, "tkovaddr");
-                               addr->set_flags |= QETH_IPA_SETIP_TAKEOVER_FLAG;
-                       }
-                       list_add_tail(&addr->entry, card->ip_tbd_list);
-               }
-               return 1;
+               hash_for_each_possible(card->ip_htable,  addr,
+                               hnode, qeth_l3_ipaddr_hash(tmp_addr))
+                       if (qeth_l3_ipaddrs_is_equal(tmp_addr, addr))
+                               return addr;
        }
+
+       return NULL;
 }
 
-int qeth_l3_delete_ip(struct qeth_card *card, struct qeth_ipaddr *addr)
+int qeth_l3_delete_ip(struct qeth_card *card, struct qeth_ipaddr *tmp_addr)
 {
-       unsigned long flags;
        int rc = 0;
+       struct qeth_ipaddr *addr;
 
        QETH_CARD_TEXT(card, 4, "delip");
 
-       if (addr->proto == QETH_PROT_IPV4)
-               QETH_CARD_HEX(card, 4, &addr->u.a4.addr, 4);
+       if (tmp_addr->proto == QETH_PROT_IPV4)
+               QETH_CARD_HEX(card, 4, &tmp_addr->u.a4.addr, 4);
        else {
-               QETH_CARD_HEX(card, 4, &addr->u.a6.addr, 8);
-               QETH_CARD_HEX(card, 4, ((char *)&addr->u.a6.addr) + 8, 8);
+               QETH_CARD_HEX(card, 4, &tmp_addr->u.a6.addr, 8);
+               QETH_CARD_HEX(card, 4, ((char *)&tmp_addr->u.a6.addr) + 8, 8);
        }
-       spin_lock_irqsave(&card->ip_lock, flags);
-       rc = __qeth_l3_insert_ip_todo(card, addr, 0);
-       spin_unlock_irqrestore(&card->ip_lock, flags);
+
+       addr = qeth_l3_ip_from_hash(card, tmp_addr);
+       if (!addr)
+               return -ENOENT;
+
+       addr->ref_counter--;
+       if (addr->type == QETH_IP_TYPE_NORMAL && addr->ref_counter > 0)
+               return rc;
+       if (addr->in_progress)
+               return -EINPROGRESS;
+
+       rc = qeth_l3_deregister_addr_entry(card, addr);
+
+       hash_del(&addr->hnode);
+       kfree(addr);
+
        return rc;
 }
 
-int qeth_l3_add_ip(struct qeth_card *card, struct qeth_ipaddr *addr)
+int qeth_l3_add_ip(struct qeth_card *card, struct qeth_ipaddr *tmp_addr)
 {
-       unsigned long flags;
        int rc = 0;
+       struct qeth_ipaddr *addr;
 
        QETH_CARD_TEXT(card, 4, "addip");
-       if (addr->proto == QETH_PROT_IPV4)
-               QETH_CARD_HEX(card, 4, &addr->u.a4.addr, 4);
+
+       if (tmp_addr->proto == QETH_PROT_IPV4)
+               QETH_CARD_HEX(card, 4, &tmp_addr->u.a4.addr, 4);
        else {
-               QETH_CARD_HEX(card, 4, &addr->u.a6.addr, 8);
-               QETH_CARD_HEX(card, 4, ((char *)&addr->u.a6.addr) + 8, 8);
+               QETH_CARD_HEX(card, 4, &tmp_addr->u.a6.addr, 8);
+               QETH_CARD_HEX(card, 4, ((char *)&tmp_addr->u.a6.addr) + 8, 8);
        }
-       spin_lock_irqsave(&card->ip_lock, flags);
-       rc = __qeth_l3_insert_ip_todo(card, addr, 1);
-       spin_unlock_irqrestore(&card->ip_lock, flags);
+
+       addr = qeth_l3_ip_from_hash(card, tmp_addr);
+       if (!addr) {
+               addr = qeth_l3_get_addr_buffer(tmp_addr->proto);
+               if (!addr)
+                       return -ENOMEM;
+
+               memcpy(addr, tmp_addr, sizeof(struct qeth_ipaddr));
+               addr->ref_counter = 1;
+
+               if (addr->type == QETH_IP_TYPE_NORMAL  &&
+                               qeth_l3_is_addr_covered_by_ipato(card, addr)) {
+                       QETH_CARD_TEXT(card, 2, "tkovaddr");
+                       addr->set_flags |= QETH_IPA_SETIP_TAKEOVER_FLAG;
+               }
+               hash_add(card->ip_htable, &addr->hnode,
+                               qeth_l3_ipaddr_hash(addr));
+
+               /* qeth_l3_register_addr_entry can go to sleep
+                * if we add a IPV4 addr. It is caused by the reason
+                * that SETIP ipa cmd starts ARP staff for IPV4 addr.
+                * Thus we should unlock spinlock, and make a protection
+                * using in_progress variable to indicate that there is
+                * an hardware operation with this IPV4 address
+                */
+               if (addr->proto == QETH_PROT_IPV4) {
+                       addr->in_progress = 1;
+                       spin_unlock_bh(&card->ip_lock);
+                       rc = qeth_l3_register_addr_entry(card, addr);
+                       spin_lock_bh(&card->ip_lock);
+                       addr->in_progress = 0;
+               } else
+                       rc = qeth_l3_register_addr_entry(card, addr);
+
+               if (!rc || (rc == IPA_RC_DUPLICATE_IP_ADDRESS) ||
+                               (rc == IPA_RC_LAN_OFFLINE)) {
+                       addr->disp_flag = QETH_DISP_ADDR_DO_NOTHING;
+                       if (addr->ref_counter < 1) {
+                               qeth_l3_delete_ip(card, addr);
+                               kfree(addr);
+                       }
+               } else {
+                       hash_del(&addr->hnode);
+                       kfree(addr);
+               }
+       } else {
+                       if (addr->type == QETH_IP_TYPE_NORMAL)
+                               addr->ref_counter++;
+       }
+
        return rc;
 }
 
@@ -312,229 +338,88 @@ struct qeth_ipaddr *qeth_l3_get_addr_buffer(
        struct qeth_ipaddr *addr;
 
        addr = kzalloc(sizeof(struct qeth_ipaddr), GFP_ATOMIC);
-       if (addr == NULL) {
+       if (!addr)
                return NULL;
-       }
+
        addr->type = QETH_IP_TYPE_NORMAL;
+       addr->disp_flag = QETH_DISP_ADDR_DO_NOTHING;
        addr->proto = prot;
+
        return addr;
 }
 
-static void qeth_l3_delete_mc_addresses(struct qeth_card *card)
-{
-       struct qeth_ipaddr *iptodo;
-       unsigned long flags;
-
-       QETH_CARD_TEXT(card, 4, "delmc");
-       iptodo = qeth_l3_get_addr_buffer(QETH_PROT_IPV4);
-       if (!iptodo) {
-               QETH_CARD_TEXT(card, 2, "dmcnomem");
-               return;
-       }
-       iptodo->type = QETH_IP_TYPE_DEL_ALL_MC;
-       spin_lock_irqsave(&card->ip_lock, flags);
-       if (!__qeth_l3_insert_ip_todo(card, iptodo, 0))
-               kfree(iptodo);
-       spin_unlock_irqrestore(&card->ip_lock, flags);
-}
-
-/*
- * Add/remove address to/from card's ip list, i.e. try to add or remove
- * reference to/from an IP address that is already registered on the card.
- * Returns:
- *     0  address was on card and its reference count has been adjusted,
- *        but is still > 0, so nothing has to be done
- *        also returns 0 if card was not on card and the todo was to delete
- *        the address -> there is also nothing to be done
- *     1  address was not on card and the todo is to add it to the card's ip
- *        list
- *     -1 address was on card and its reference count has been decremented
- *        to <= 0 by the todo -> address must be removed from card
- */
-static int __qeth_l3_ref_ip_on_card(struct qeth_card *card,
-               struct qeth_ipaddr *todo, struct qeth_ipaddr **__addr)
+static void qeth_l3_clear_ip_htable(struct qeth_card *card, int recover)
 {
        struct qeth_ipaddr *addr;
-       int found = 0;
-
-       list_for_each_entry(addr, &card->ip_list, entry) {
-               if ((addr->proto == QETH_PROT_IPV4) &&
-                   (todo->proto == QETH_PROT_IPV4) &&
-                   (addr->type == todo->type) &&
-                   (addr->u.a4.addr == todo->u.a4.addr) &&
-                   (addr->u.a4.mask == todo->u.a4.mask)) {
-                       found = 1;
-                       break;
-               }
-               if ((addr->proto == QETH_PROT_IPV6) &&
-                   (todo->proto == QETH_PROT_IPV6) &&
-                   (addr->type == todo->type) &&
-                   (addr->u.a6.pfxlen == todo->u.a6.pfxlen) &&
-                   (memcmp(&addr->u.a6.addr, &todo->u.a6.addr,
-                           sizeof(struct in6_addr)) == 0)) {
-                       found = 1;
-                       break;
-               }
-       }
-       if (found) {
-               addr->users += todo->users;
-               if (addr->users <= 0) {
-                       *__addr = addr;
-                       return -1;
-               } else {
-                       /* for VIPA and RXIP limit refcount to 1 */
-                       if (addr->type != QETH_IP_TYPE_NORMAL)
-                               addr->users = 1;
-                       return 0;
-               }
-       }
-       if (todo->users > 0) {
-               /* for VIPA and RXIP limit refcount to 1 */
-               if (todo->type != QETH_IP_TYPE_NORMAL)
-                       todo->users = 1;
-               return 1;
-       } else
-               return 0;
-}
-
-static void __qeth_l3_delete_all_mc(struct qeth_card *card,
-                                       unsigned long *flags)
-{
-       struct list_head fail_list;
-       struct qeth_ipaddr *addr, *tmp;
-       int rc;
-
-       INIT_LIST_HEAD(&fail_list);
-again:
-       list_for_each_entry_safe(addr, tmp, &card->ip_list, entry) {
-               if (addr->is_multicast) {
-                       list_del(&addr->entry);
-                       spin_unlock_irqrestore(&card->ip_lock, *flags);
-                       rc = qeth_l3_deregister_addr_entry(card, addr);
-                       spin_lock_irqsave(&card->ip_lock, *flags);
-                       if (!rc || (rc == IPA_RC_MC_ADDR_NOT_FOUND))
-                               kfree(addr);
-                       else
-                               list_add_tail(&addr->entry, &fail_list);
-                       goto again;
-               }
-       }
-       list_splice(&fail_list, &card->ip_list);
-}
-
-void qeth_l3_set_ip_addr_list(struct qeth_card *card)
-{
-       struct list_head *tbd_list;
-       struct qeth_ipaddr *todo, *addr;
-       unsigned long flags;
-       int rc;
+       struct hlist_node *tmp;
+       int i;
 
-       QETH_CARD_TEXT(card, 2, "sdiplist");
-       QETH_CARD_HEX(card, 2, &card, sizeof(void *));
+       QETH_CARD_TEXT(card, 4, "clearip");
 
-       if (!qeth_card_hw_is_reachable(card) || card->options.sniffer)
+       if (recover && card->options.sniffer)
                return;
 
-       spin_lock_irqsave(&card->ip_lock, flags);
-       tbd_list = card->ip_tbd_list;
-       card->ip_tbd_list = kzalloc(sizeof(struct list_head), GFP_ATOMIC);
-       if (!card->ip_tbd_list) {
-               QETH_CARD_TEXT(card, 0, "silnomem");
-               card->ip_tbd_list = tbd_list;
-               spin_unlock_irqrestore(&card->ip_lock, flags);
-               return;
-       } else
-               INIT_LIST_HEAD(card->ip_tbd_list);
-
-       while (!list_empty(tbd_list)) {
-               todo = list_entry(tbd_list->next, struct qeth_ipaddr, entry);
-               list_del(&todo->entry);
-               if (todo->type == QETH_IP_TYPE_DEL_ALL_MC) {
-                       __qeth_l3_delete_all_mc(card, &flags);
-                       kfree(todo);
+       spin_lock_bh(&card->ip_lock);
+
+       hash_for_each_safe(card->ip_htable, i, tmp, addr, hnode) {
+               if (!recover) {
+                       hash_del(&addr->hnode);
+                       kfree(addr);
                        continue;
                }
-               rc = __qeth_l3_ref_ip_on_card(card, todo, &addr);
-               if (rc == 0) {
-                       /* nothing to be done; only adjusted refcount */
-                       kfree(todo);
-               } else if (rc == 1) {
-                       /* new entry to be added to on-card list */
-                       spin_unlock_irqrestore(&card->ip_lock, flags);
-                       rc = qeth_l3_register_addr_entry(card, todo);
-                       spin_lock_irqsave(&card->ip_lock, flags);
-                       if (!rc || (rc == IPA_RC_LAN_OFFLINE))
-                               list_add_tail(&todo->entry, &card->ip_list);
-                       else
-                               kfree(todo);
-               } else if (rc == -1) {
-                       /* on-card entry to be removed */
-                       list_del_init(&addr->entry);
-                       spin_unlock_irqrestore(&card->ip_lock, flags);
-                       rc = qeth_l3_deregister_addr_entry(card, addr);
-                       spin_lock_irqsave(&card->ip_lock, flags);
-                       if (!rc || (rc == IPA_RC_IP_ADDRESS_NOT_DEFINED))
-                               kfree(addr);
-                       else
-                               list_add_tail(&addr->entry, &card->ip_list);
-                       kfree(todo);
-               }
+               addr->disp_flag = QETH_DISP_ADDR_ADD;
        }
-       spin_unlock_irqrestore(&card->ip_lock, flags);
-       kfree(tbd_list);
-}
 
-static void qeth_l3_clear_ip_list(struct qeth_card *card, int recover)
-{
-       struct qeth_ipaddr *addr, *tmp;
-       unsigned long flags;
+       spin_unlock_bh(&card->ip_lock);
 
-       QETH_CARD_TEXT(card, 4, "clearip");
-       if (recover && card->options.sniffer)
-               return;
-       spin_lock_irqsave(&card->ip_lock, flags);
-       /* clear todo list */
-       list_for_each_entry_safe(addr, tmp, card->ip_tbd_list, entry) {
-               list_del(&addr->entry);
+       spin_lock_bh(&card->mclock);
+
+       hash_for_each_safe(card->ip_mc_htable, i, tmp, addr, hnode) {
+               hash_del(&addr->hnode);
                kfree(addr);
        }
 
-       while (!list_empty(&card->ip_list)) {
-               addr = list_entry(card->ip_list.next,
-                                 struct qeth_ipaddr, entry);
-               list_del_init(&addr->entry);
-               if (!recover || addr->is_multicast) {
-                       kfree(addr);
-                       continue;
-               }
-               list_add_tail(&addr->entry, card->ip_tbd_list);
-       }
-       spin_unlock_irqrestore(&card->ip_lock, flags);
-}
+       spin_unlock_bh(&card->mclock);
+
 
-static int qeth_l3_address_exists_in_list(struct list_head *list,
-               struct qeth_ipaddr *addr, int same_type)
+}
+static void qeth_l3_recover_ip(struct qeth_card *card)
 {
-       struct qeth_ipaddr *tmp;
+       struct qeth_ipaddr *addr;
+       struct hlist_node *tmp;
+       int i;
+       int rc;
 
-       list_for_each_entry(tmp, list, entry) {
-               if ((tmp->proto == QETH_PROT_IPV4) &&
-                   (addr->proto == QETH_PROT_IPV4) &&
-                   ((same_type && (tmp->type == addr->type)) ||
-                   (!same_type && (tmp->type != addr->type))) &&
-                   (tmp->u.a4.addr == addr->u.a4.addr))
-                       return 1;
+       QETH_CARD_TEXT(card, 4, "recoverip");
+
+       spin_lock_bh(&card->ip_lock);
+
+       hash_for_each_safe(card->ip_htable, i, tmp, addr, hnode) {
+               if (addr->disp_flag == QETH_DISP_ADDR_ADD) {
+                       if (addr->proto == QETH_PROT_IPV4) {
+                               addr->in_progress = 1;
+                               spin_unlock_bh(&card->ip_lock);
+                               rc = qeth_l3_register_addr_entry(card, addr);
+                               spin_lock_bh(&card->ip_lock);
+                               addr->in_progress = 0;
+                       } else
+                               rc = qeth_l3_register_addr_entry(card, addr);
+
+                       if (!rc) {
+                               addr->disp_flag = QETH_DISP_ADDR_DO_NOTHING;
+                               if (addr->ref_counter < 1) {
+                                       qeth_l3_delete_ip(card, addr);
+                                       kfree(addr);
+                               }
+                       } else {
+                               hash_del(&addr->hnode);
+                               kfree(addr);
+                       }
+               }
+       }
 
-               if ((tmp->proto == QETH_PROT_IPV6) &&
-                   (addr->proto == QETH_PROT_IPV6) &&
-                   ((same_type && (tmp->type == addr->type)) ||
-                   (!same_type && (tmp->type != addr->type))) &&
-                   (memcmp(&tmp->u.a6.addr, &addr->u.a6.addr,
-                           sizeof(struct in6_addr)) == 0))
-                       return 1;
+       spin_unlock_bh(&card->ip_lock);
 
-       }
-       return 0;
 }
 
 static int qeth_l3_send_setdelmc(struct qeth_card *card,
@@ -712,27 +597,28 @@ int qeth_l3_setrouting_v6(struct qeth_card *card)
  */
 static void qeth_l3_clear_ipato_list(struct qeth_card *card)
 {
-
        struct qeth_ipato_entry *ipatoe, *tmp;
-       unsigned long flags;
 
-       spin_lock_irqsave(&card->ip_lock, flags);
+       spin_lock_bh(&card->ip_lock);
+
        list_for_each_entry_safe(ipatoe, tmp, &card->ipato.entries, entry) {
                list_del(&ipatoe->entry);
                kfree(ipatoe);
        }
-       spin_unlock_irqrestore(&card->ip_lock, flags);
+
+       spin_unlock_bh(&card->ip_lock);
 }
 
 int qeth_l3_add_ipato_entry(struct qeth_card *card,
                                struct qeth_ipato_entry *new)
 {
        struct qeth_ipato_entry *ipatoe;
-       unsigned long flags;
        int rc = 0;
 
        QETH_CARD_TEXT(card, 2, "addipato");
-       spin_lock_irqsave(&card->ip_lock, flags);
+
+       spin_lock_bh(&card->ip_lock);
+
        list_for_each_entry(ipatoe, &card->ipato.entries, entry) {
                if (ipatoe->proto != new->proto)
                        continue;
@@ -743,10 +629,12 @@ int qeth_l3_add_ipato_entry(struct qeth_card *card,
                        break;
                }
        }
+
        if (!rc)
                list_add_tail(&new->entry, &card->ipato.entries);
 
-       spin_unlock_irqrestore(&card->ip_lock, flags);
+       spin_unlock_bh(&card->ip_lock);
+
        return rc;
 }
 
@@ -754,10 +642,11 @@ void qeth_l3_del_ipato_entry(struct qeth_card *card,
                enum qeth_prot_versions proto, u8 *addr, int mask_bits)
 {
        struct qeth_ipato_entry *ipatoe, *tmp;
-       unsigned long flags;
 
        QETH_CARD_TEXT(card, 2, "delipato");
-       spin_lock_irqsave(&card->ip_lock, flags);
+
+       spin_lock_bh(&card->ip_lock);
+
        list_for_each_entry_safe(ipatoe, tmp, &card->ipato.entries, entry) {
                if (ipatoe->proto != proto)
                        continue;
@@ -768,7 +657,8 @@ void qeth_l3_del_ipato_entry(struct qeth_card *card,
                        kfree(ipatoe);
                }
        }
-       spin_unlock_irqrestore(&card->ip_lock, flags);
+
+       spin_unlock_bh(&card->ip_lock);
 }
 
 /*
@@ -778,7 +668,6 @@ int qeth_l3_add_vipa(struct qeth_card *card, enum qeth_prot_versions proto,
              const u8 *addr)
 {
        struct qeth_ipaddr *ipaddr;
-       unsigned long flags;
        int rc = 0;
 
        ipaddr = qeth_l3_get_addr_buffer(proto);
@@ -797,18 +686,18 @@ int qeth_l3_add_vipa(struct qeth_card *card, enum qeth_prot_versions proto,
                ipaddr->del_flags = QETH_IPA_DELIP_VIPA_FLAG;
        } else
                return -ENOMEM;
-       spin_lock_irqsave(&card->ip_lock, flags);
-       if (qeth_l3_address_exists_in_list(&card->ip_list, ipaddr, 0) ||
-           qeth_l3_address_exists_in_list(card->ip_tbd_list, ipaddr, 0))
+
+       spin_lock_bh(&card->ip_lock);
+
+       if (!qeth_l3_ip_from_hash(card, ipaddr))
                rc = -EEXIST;
-       spin_unlock_irqrestore(&card->ip_lock, flags);
-       if (rc) {
-               kfree(ipaddr);
-               return rc;
-       }
-       if (!qeth_l3_add_ip(card, ipaddr))
-               kfree(ipaddr);
-       qeth_l3_set_ip_addr_list(card);
+       else
+               qeth_l3_add_ip(card, ipaddr);
+
+       spin_unlock_bh(&card->ip_lock);
+
+       kfree(ipaddr);
+
        return rc;
 }
 
@@ -831,9 +720,12 @@ void qeth_l3_del_vipa(struct qeth_card *card, enum qeth_prot_versions proto,
                ipaddr->type = QETH_IP_TYPE_VIPA;
        } else
                return;
-       if (!qeth_l3_delete_ip(card, ipaddr))
-               kfree(ipaddr);
-       qeth_l3_set_ip_addr_list(card);
+
+       spin_lock_bh(&card->ip_lock);
+       qeth_l3_delete_ip(card, ipaddr);
+       spin_unlock_bh(&card->ip_lock);
+
+       kfree(ipaddr);
 }
 
 /*
@@ -843,7 +735,6 @@ int qeth_l3_add_rxip(struct qeth_card *card, enum qeth_prot_versions proto,
              const u8 *addr)
 {
        struct qeth_ipaddr *ipaddr;
-       unsigned long flags;
        int rc = 0;
 
        ipaddr = qeth_l3_get_addr_buffer(proto);
@@ -857,24 +748,25 @@ int qeth_l3_add_rxip(struct qeth_card *card, enum qeth_prot_versions proto,
                        memcpy(&ipaddr->u.a6.addr, addr, 16);
                        ipaddr->u.a6.pfxlen = 0;
                }
+
                ipaddr->type = QETH_IP_TYPE_RXIP;
                ipaddr->set_flags = QETH_IPA_SETIP_TAKEOVER_FLAG;
                ipaddr->del_flags = 0;
        } else
                return -ENOMEM;
-       spin_lock_irqsave(&card->ip_lock, flags);
-       if (qeth_l3_address_exists_in_list(&card->ip_list, ipaddr, 0) ||
-           qeth_l3_address_exists_in_list(card->ip_tbd_list, ipaddr, 0))
+
+       spin_lock_bh(&card->ip_lock);
+
+       if (!qeth_l3_ip_from_hash(card, ipaddr))
                rc = -EEXIST;
-       spin_unlock_irqrestore(&card->ip_lock, flags);
-       if (rc) {
-               kfree(ipaddr);
-               return rc;
-       }
-       if (!qeth_l3_add_ip(card, ipaddr))
-               kfree(ipaddr);
-       qeth_l3_set_ip_addr_list(card);
-       return 0;
+       else
+               qeth_l3_add_ip(card, ipaddr);
+
+       spin_unlock_bh(&card->ip_lock);
+
+       kfree(ipaddr);
+
+       return rc;
 }
 
 void qeth_l3_del_rxip(struct qeth_card *card, enum qeth_prot_versions proto,
@@ -896,9 +788,12 @@ void qeth_l3_del_rxip(struct qeth_card *card, enum qeth_prot_versions proto,
                ipaddr->type = QETH_IP_TYPE_RXIP;
        } else
                return;
-       if (!qeth_l3_delete_ip(card, ipaddr))
-               kfree(ipaddr);
-       qeth_l3_set_ip_addr_list(card);
+
+       spin_lock_bh(&card->ip_lock);
+       qeth_l3_delete_ip(card, ipaddr);
+       spin_unlock_bh(&card->ip_lock);
+
+       kfree(ipaddr);
 }
 
 static int qeth_l3_register_addr_entry(struct qeth_card *card,
@@ -908,6 +803,7 @@ static int qeth_l3_register_addr_entry(struct qeth_card *card,
        int rc = 0;
        int cnt = 3;
 
+
        if (addr->proto == QETH_PROT_IPV4) {
                QETH_CARD_TEXT(card, 2, "setaddr4");
                QETH_CARD_HEX(card, 3, &addr->u.a4.addr, sizeof(int));
@@ -1013,36 +909,6 @@ static int qeth_l3_setadapter_parms(struct qeth_card *card)
        return rc;
 }
 
-static int qeth_l3_default_setassparms_cb(struct qeth_card *card,
-                       struct qeth_reply *reply, unsigned long data)
-{
-       struct qeth_ipa_cmd *cmd;
-
-       QETH_CARD_TEXT(card, 4, "defadpcb");
-
-       cmd = (struct qeth_ipa_cmd *) data;
-       if (cmd->hdr.return_code == 0) {
-               cmd->hdr.return_code = cmd->data.setassparms.hdr.return_code;
-               if (cmd->hdr.prot_version == QETH_PROT_IPV4)
-                       card->options.ipa4.enabled_funcs = cmd->hdr.ipa_enabled;
-               if (cmd->hdr.prot_version == QETH_PROT_IPV6)
-                       card->options.ipa6.enabled_funcs = cmd->hdr.ipa_enabled;
-       }
-       if (cmd->data.setassparms.hdr.assist_no == IPA_INBOUND_CHECKSUM &&
-           cmd->data.setassparms.hdr.command_code == IPA_CMD_ASS_START) {
-               card->info.csum_mask = cmd->data.setassparms.data.flags_32bit;
-               QETH_CARD_TEXT_(card, 3, "csum:%d", card->info.csum_mask);
-       }
-       if (cmd->data.setassparms.hdr.assist_no == IPA_OUTBOUND_CHECKSUM &&
-           cmd->data.setassparms.hdr.command_code == IPA_CMD_ASS_START) {
-               card->info.tx_csum_mask =
-                       cmd->data.setassparms.data.flags_32bit;
-               QETH_CARD_TEXT_(card, 3, "tcsu:%d", card->info.tx_csum_mask);
-       }
-
-       return 0;
-}
-
 #ifdef CONFIG_QETH_IPV6
 static int qeth_l3_send_simple_setassparms_ipv6(struct qeth_card *card,
                enum qeth_ipa_funcs ipa_func, __u16 cmd_code)
@@ -1056,7 +922,7 @@ static int qeth_l3_send_simple_setassparms_ipv6(struct qeth_card *card,
        if (!iob)
                return -ENOMEM;
        rc = qeth_send_setassparms(card, iob, 0, 0,
-                                  qeth_l3_default_setassparms_cb, NULL);
+                                  qeth_setassparms_cb, NULL);
        return rc;
 }
 #endif
@@ -1291,47 +1157,6 @@ out:
        return rc;
 }
 
-static void qeth_l3_start_ipa_checksum(struct qeth_card *card)
-{
-       QETH_CARD_TEXT(card, 3, "strtcsum");
-       if (qeth_is_supported(card, IPA_INBOUND_CHECKSUM)
-           && (card->dev->features & NETIF_F_RXCSUM))
-               qeth_set_rx_csum(card, 1);
-}
-
-static void qeth_l3_start_ipa_tx_checksum(struct qeth_card *card)
-{
-       QETH_CARD_TEXT(card, 3, "strttxcs");
-       qeth_start_ipa_tx_checksum(card);
-}
-
-static int qeth_l3_start_ipa_tso(struct qeth_card *card)
-{
-       int rc;
-
-       QETH_CARD_TEXT(card, 3, "sttso");
-
-       if (!qeth_is_supported(card, IPA_OUTBOUND_TSO)) {
-               dev_info(&card->gdev->dev,
-                       "Outbound TSO not supported on %s\n",
-                       QETH_CARD_IFNAME(card));
-               rc = -EOPNOTSUPP;
-       } else {
-               rc = qeth_send_simple_setassparms(card, IPA_OUTBOUND_TSO,
-                                                 IPA_CMD_ASS_START, 0);
-               if (rc)
-                       dev_warn(&card->gdev->dev, "Starting outbound TCP "
-                               "segmentation offload for %s failed\n",
-                               QETH_CARD_IFNAME(card));
-               else
-                       dev_info(&card->gdev->dev,
-                               "Outbound TSO enabled\n");
-       }
-       if (rc)
-               card->dev->features &= ~NETIF_F_TSO;
-       return rc;
-}
-
 static int qeth_l3_start_ipassists(struct qeth_card *card)
 {
        QETH_CARD_TEXT(card, 3, "strtipas");
@@ -1345,9 +1170,6 @@ static int qeth_l3_start_ipassists(struct qeth_card *card)
        qeth_l3_start_ipa_multicast(card);              /* go on*/
        qeth_l3_start_ipa_ipv6(card);           /* go on*/
        qeth_l3_start_ipa_broadcast(card);              /* go on*/
-       qeth_l3_start_ipa_checksum(card);               /* go on*/
-       qeth_l3_start_ipa_tx_checksum(card);
-       qeth_l3_start_ipa_tso(card);            /* go on*/
        return 0;
 }
 
@@ -1507,31 +1329,99 @@ qeth_diags_trace(struct qeth_card *card, enum qeth_diags_trace_cmds diags_cmd)
        return qeth_send_ipa_cmd(card, iob, qeth_diags_trace_cb, NULL);
 }
 
-static void qeth_l3_get_mac_for_ipm(__u32 ipm, char *mac,
-                               struct net_device *dev)
+static void qeth_l3_get_mac_for_ipm(__u32 ipm, char *mac)
 {
        ip_eth_mc_map(ipm, mac);
 }
 
-static void qeth_l3_add_mc(struct qeth_card *card, struct in_device *in4_dev)
+static void qeth_l3_mark_all_mc_to_be_deleted(struct qeth_card *card)
+{
+       struct qeth_ipaddr *addr;
+       int i;
+
+       hash_for_each(card->ip_mc_htable, i, addr, hnode)
+               addr->disp_flag = QETH_DISP_ADDR_DELETE;
+
+}
+
+static void qeth_l3_add_all_new_mc(struct qeth_card *card)
+{
+       struct qeth_ipaddr *addr;
+       struct hlist_node *tmp;
+       int i;
+       int rc;
+
+       hash_for_each_safe(card->ip_mc_htable, i, tmp, addr, hnode) {
+               if (addr->disp_flag == QETH_DISP_ADDR_ADD) {
+                       rc = qeth_l3_register_addr_entry(card, addr);
+                       if (!rc || (rc == IPA_RC_LAN_OFFLINE))
+                               addr->ref_counter = 1;
+                       else {
+                               hash_del(&addr->hnode);
+                               kfree(addr);
+                       }
+               }
+       }
+
+}
+
+static void qeth_l3_delete_nonused_mc(struct qeth_card *card)
+{
+       struct qeth_ipaddr *addr;
+       struct hlist_node *tmp;
+       int i;
+       int rc;
+
+       hash_for_each_safe(card->ip_mc_htable, i, tmp, addr, hnode) {
+               if (addr->disp_flag == QETH_DISP_ADDR_DELETE) {
+                       rc = qeth_l3_deregister_addr_entry(card, addr);
+                       if (!rc || (rc == IPA_RC_MC_ADDR_NOT_FOUND)) {
+                               hash_del(&addr->hnode);
+                               kfree(addr);
+                       }
+               }
+       }
+
+}
+
+
+static void
+qeth_l3_add_mc_to_hash(struct qeth_card *card, struct in_device *in4_dev)
 {
-       struct qeth_ipaddr *ipm;
        struct ip_mc_list *im4;
+       struct qeth_ipaddr *tmp, *ipm;
        char buf[MAX_ADDR_LEN];
 
        QETH_CARD_TEXT(card, 4, "addmc");
+
+       tmp = qeth_l3_get_addr_buffer(QETH_PROT_IPV4);
+               if (!tmp)
+                       return;
+
        for (im4 = rcu_dereference(in4_dev->mc_list); im4 != NULL;
             im4 = rcu_dereference(im4->next_rcu)) {
-               qeth_l3_get_mac_for_ipm(im4->multiaddr, buf, in4_dev->dev);
-               ipm = qeth_l3_get_addr_buffer(QETH_PROT_IPV4);
-               if (!ipm)
-                       continue;
-               ipm->u.a4.addr = im4->multiaddr;
-               memcpy(ipm->mac, buf, OSA_ADDR_LEN);
-               ipm->is_multicast = 1;
-               if (!qeth_l3_add_ip(card, ipm))
-                       kfree(ipm);
+               qeth_l3_get_mac_for_ipm(im4->multiaddr, buf);
+
+               tmp->u.a4.addr = im4->multiaddr;
+               memcpy(tmp->mac, buf, sizeof(tmp->mac));
+
+               ipm = qeth_l3_ip_from_hash(card, tmp);
+               if (ipm) {
+                       ipm->disp_flag = QETH_DISP_ADDR_DO_NOTHING;
+               } else {
+                       ipm = qeth_l3_get_addr_buffer(QETH_PROT_IPV4);
+                       if (!ipm)
+                               continue;
+                       memcpy(ipm->mac, buf, sizeof(tmp->mac));
+                       ipm->u.a4.addr = im4->multiaddr;
+                       ipm->is_multicast = 1;
+                       ipm->disp_flag = QETH_DISP_ADDR_ADD;
+                       hash_add(card->ip_mc_htable,
+                                       &ipm->hnode, qeth_l3_ipaddr_hash(ipm));
+               }
        }
+
+       kfree(tmp);
 }
 
 /* called with rcu_read_lock */
@@ -1541,6 +1431,7 @@ static void qeth_l3_add_vlan_mc(struct qeth_card *card)
        u16 vid;
 
        QETH_CARD_TEXT(card, 4, "addmcvl");
+
        if (!qeth_is_supported(card, IPA_FULL_VLAN))
                return;
 
@@ -1555,7 +1446,7 @@ static void qeth_l3_add_vlan_mc(struct qeth_card *card)
                in_dev = __in_dev_get_rcu(netdev);
                if (!in_dev)
                        continue;
-               qeth_l3_add_mc(card, in_dev);
+               qeth_l3_add_mc_to_hash(card, in_dev);
        }
 }
 
@@ -1564,36 +1455,60 @@ static void qeth_l3_add_multicast_ipv4(struct qeth_card *card)
        struct in_device *in4_dev;
 
        QETH_CARD_TEXT(card, 4, "chkmcv4");
+
        rcu_read_lock();
        in4_dev = __in_dev_get_rcu(card->dev);
        if (in4_dev == NULL)
                goto unlock;
-       qeth_l3_add_mc(card, in4_dev);
+       qeth_l3_add_mc_to_hash(card, in4_dev);
        qeth_l3_add_vlan_mc(card);
 unlock:
        rcu_read_unlock();
 }
 
 #ifdef CONFIG_QETH_IPV6
-static void qeth_l3_add_mc6(struct qeth_card *card, struct inet6_dev *in6_dev)
+static void
+qeth_l3_add_mc6_to_hash(struct qeth_card *card, struct inet6_dev *in6_dev)
 {
        struct qeth_ipaddr *ipm;
        struct ifmcaddr6 *im6;
+       struct qeth_ipaddr *tmp;
        char buf[MAX_ADDR_LEN];
 
        QETH_CARD_TEXT(card, 4, "addmc6");
+
+       tmp = qeth_l3_get_addr_buffer(QETH_PROT_IPV6);
+               if (!tmp)
+                       return;
+
        for (im6 = in6_dev->mc_list; im6 != NULL; im6 = im6->next) {
                ndisc_mc_map(&im6->mca_addr, buf, in6_dev->dev, 0);
+
+               memcpy(tmp->mac, buf, sizeof(tmp->mac));
+               memcpy(&tmp->u.a6.addr, &im6->mca_addr.s6_addr,
+                      sizeof(struct in6_addr));
+               tmp->is_multicast = 1;
+
+               ipm = qeth_l3_ip_from_hash(card, tmp);
+               if (ipm) {
+                       ipm->disp_flag = QETH_DISP_ADDR_DO_NOTHING;
+                       continue;
+               }
+
                ipm = qeth_l3_get_addr_buffer(QETH_PROT_IPV6);
                if (!ipm)
                        continue;
-               ipm->is_multicast = 1;
+
                memcpy(ipm->mac, buf, OSA_ADDR_LEN);
                memcpy(&ipm->u.a6.addr, &im6->mca_addr.s6_addr,
                       sizeof(struct in6_addr));
-               if (!qeth_l3_add_ip(card, ipm))
-                       kfree(ipm);
+               ipm->is_multicast = 1;
+               ipm->disp_flag = QETH_DISP_ADDR_ADD;
+               hash_add(card->ip_mc_htable,
+                               &ipm->hnode, qeth_l3_ipaddr_hash(ipm));
+
        }
+       kfree(tmp);
 }
 
 /* called with rcu_read_lock */
@@ -1603,6 +1518,7 @@ static void qeth_l3_add_vlan_mc6(struct qeth_card *card)
        u16 vid;
 
        QETH_CARD_TEXT(card, 4, "admc6vl");
+
        if (!qeth_is_supported(card, IPA_FULL_VLAN))
                return;
 
@@ -1618,7 +1534,7 @@ static void qeth_l3_add_vlan_mc6(struct qeth_card *card)
                if (!in_dev)
                        continue;
                read_lock_bh(&in_dev->lock);
-               qeth_l3_add_mc6(card, in_dev);
+               qeth_l3_add_mc6_to_hash(card, in_dev);
                read_unlock_bh(&in_dev->lock);
                in6_dev_put(in_dev);
        }
@@ -1629,14 +1545,16 @@ static void qeth_l3_add_multicast_ipv6(struct qeth_card *card)
        struct inet6_dev *in6_dev;
 
        QETH_CARD_TEXT(card, 4, "chkmcv6");
+
        if (!qeth_is_supported(card, IPA_IPV6))
                return ;
        in6_dev = in6_dev_get(card->dev);
-       if (in6_dev == NULL)
+       if (!in6_dev)
                return;
+
        rcu_read_lock();
        read_lock_bh(&in6_dev->lock);
-       qeth_l3_add_mc6(card, in6_dev);
+       qeth_l3_add_mc6_to_hash(card, in6_dev);
        qeth_l3_add_vlan_mc6(card);
        read_unlock_bh(&in6_dev->lock);
        rcu_read_unlock();
@@ -1660,16 +1578,23 @@ static void qeth_l3_free_vlan_addresses4(struct qeth_card *card,
        in_dev = in_dev_get(netdev);
        if (!in_dev)
                return;
+
+       addr = qeth_l3_get_addr_buffer(QETH_PROT_IPV4);
+       if (!addr)
+               return;
+
+       spin_lock_bh(&card->ip_lock);
+
        for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) {
-               addr = qeth_l3_get_addr_buffer(QETH_PROT_IPV4);
-               if (addr) {
-                       addr->u.a4.addr = ifa->ifa_address;
-                       addr->u.a4.mask = ifa->ifa_mask;
-                       addr->type = QETH_IP_TYPE_NORMAL;
-                       if (!qeth_l3_delete_ip(card, addr))
-                               kfree(addr);
-               }
+               addr->u.a4.addr = ifa->ifa_address;
+               addr->u.a4.mask = ifa->ifa_mask;
+               addr->type = QETH_IP_TYPE_NORMAL;
+               qeth_l3_delete_ip(card, addr);
        }
+
+       spin_unlock_bh(&card->ip_lock);
+
+       kfree(addr);
        in_dev_put(in_dev);
 }
 
@@ -1687,20 +1612,28 @@ static void qeth_l3_free_vlan_addresses6(struct qeth_card *card,
        netdev = __vlan_find_dev_deep_rcu(card->dev, htons(ETH_P_8021Q), vid);
        if (!netdev)
                return;
+
        in6_dev = in6_dev_get(netdev);
        if (!in6_dev)
                return;
+
+       addr = qeth_l3_get_addr_buffer(QETH_PROT_IPV6);
+       if (!addr)
+               return;
+
+       spin_lock_bh(&card->ip_lock);
+
        list_for_each_entry(ifa, &in6_dev->addr_list, if_list) {
-               addr = qeth_l3_get_addr_buffer(QETH_PROT_IPV6);
-               if (addr) {
-                       memcpy(&addr->u.a6.addr, &ifa->addr,
-                              sizeof(struct in6_addr));
-                       addr->u.a6.pfxlen = ifa->prefix_len;
-                       addr->type = QETH_IP_TYPE_NORMAL;
-                       if (!qeth_l3_delete_ip(card, addr))
-                               kfree(addr);
-               }
+               memcpy(&addr->u.a6.addr, &ifa->addr,
+                      sizeof(struct in6_addr));
+               addr->u.a6.pfxlen = ifa->prefix_len;
+               addr->type = QETH_IP_TYPE_NORMAL;
+               qeth_l3_delete_ip(card, addr);
        }
+
+       spin_unlock_bh(&card->ip_lock);
+
+       kfree(addr);
        in6_dev_put(in6_dev);
 #endif /* CONFIG_QETH_IPV6 */
 }
@@ -1727,18 +1660,16 @@ static int qeth_l3_vlan_rx_kill_vid(struct net_device *dev,
                                    __be16 proto, u16 vid)
 {
        struct qeth_card *card = dev->ml_priv;
-       unsigned long flags;
 
        QETH_CARD_TEXT_(card, 4, "kid:%d", vid);
+
        if (qeth_wait_for_threads(card, QETH_RECOVER_THREAD)) {
                QETH_CARD_TEXT(card, 3, "kidREC");
                return 0;
        }
-       spin_lock_irqsave(&card->vlanlock, flags);
        /* unregister IP addresses of vlan device */
        qeth_l3_free_vlan_addresses(card, vid);
        clear_bit(vid, card->active_vlans);
-       spin_unlock_irqrestore(&card->vlanlock, flags);
        qeth_l3_set_multicast_list(card->dev);
        return 0;
 }
@@ -1994,8 +1925,8 @@ static int qeth_l3_verify_vlan_dev(struct net_device *dev,
 static int qeth_l3_verify_dev(struct net_device *dev)
 {
        struct qeth_card *card;
-       unsigned long flags;
        int rc = 0;
+       unsigned long flags;
 
        read_lock_irqsave(&qeth_core_card_list.rwlock, flags);
        list_for_each_entry(card, &qeth_core_card_list.list, list) {
@@ -2051,7 +1982,7 @@ static void qeth_l3_stop_card(struct qeth_card *card, int recovery_mode)
                card->state = CARD_STATE_SOFTSETUP;
        }
        if (card->state == CARD_STATE_SOFTSETUP) {
-               qeth_l3_clear_ip_list(card, 1);
+               qeth_l3_clear_ip_htable(card, 1);
                qeth_clear_ipacmd_list(card);
                card->state = CARD_STATE_HARDSETUP;
        }
@@ -2106,12 +2037,20 @@ static void qeth_l3_set_multicast_list(struct net_device *dev)
            (card->state != CARD_STATE_UP))
                return;
        if (!card->options.sniffer) {
-               qeth_l3_delete_mc_addresses(card);
+
+               spin_lock_bh(&card->mclock);
+
+               qeth_l3_mark_all_mc_to_be_deleted(card);
+
                qeth_l3_add_multicast_ipv4(card);
 #ifdef CONFIG_QETH_IPV6
                qeth_l3_add_multicast_ipv6(card);
 #endif
-               qeth_l3_set_ip_addr_list(card);
+               qeth_l3_delete_nonused_mc(card);
+               qeth_l3_add_all_new_mc(card);
+
+               spin_unlock_bh(&card->mclock);
+
                if (!qeth_adp_supported(card, IPA_SETADP_SET_PROMISC_MODE))
                        return;
        }
@@ -2375,22 +2314,21 @@ static int qeth_l3_arp_query(struct qeth_card *card, char __user *udata)
        if (rc) {
                if (copy_to_user(udata, qinfo.udata, 4))
                        rc = -EFAULT;
-                       goto free_and_out;
-       } else {
+               goto free_and_out;
+       }
 #ifdef CONFIG_QETH_IPV6
-               if (qinfo.mask_bits & QETH_QARP_WITH_IPV6) {
-                       /* fails in case of GuestLAN QDIO mode */
-                       qeth_l3_query_arp_cache_info(card, QETH_PROT_IPV6,
-                               &qinfo);
-               }
+       if (qinfo.mask_bits & QETH_QARP_WITH_IPV6) {
+               /* fails in case of GuestLAN QDIO mode */
+               qeth_l3_query_arp_cache_info(card, QETH_PROT_IPV6, &qinfo);
+       }
 #endif
-               if (copy_to_user(udata, qinfo.udata, qinfo.udata_len)) {
-                       QETH_CARD_TEXT(card, 4, "qactf");
-                       rc = -EFAULT;
-                       goto free_and_out;
-               }
-               QETH_CARD_TEXT(card, 4, "qacts");
+       if (copy_to_user(udata, qinfo.udata, qinfo.udata_len)) {
+               QETH_CARD_TEXT(card, 4, "qactf");
+               rc = -EFAULT;
+               goto free_and_out;
        }
+       QETH_CARD_TEXT(card, 4, "qacts");
+
 free_and_out:
        kfree(qinfo.udata);
 out:
@@ -2427,7 +2365,7 @@ static int qeth_l3_arp_add_entry(struct qeth_card *card,
        rc = qeth_send_setassparms(card, iob,
                                   sizeof(struct qeth_arp_cache_entry),
                                   (unsigned long) entry,
-                                  qeth_l3_default_setassparms_cb, NULL);
+                                  qeth_setassparms_cb, NULL);
        if (rc) {
                tmp = rc;
                qeth_l3_ipaddr4_to_string((u8 *)entry->ipaddr, buf);
@@ -2467,7 +2405,7 @@ static int qeth_l3_arp_remove_entry(struct qeth_card *card,
                return -ENOMEM;
        rc = qeth_send_setassparms(card, iob,
                                   12, (unsigned long)buf,
-                                  qeth_l3_default_setassparms_cb, NULL);
+                                  qeth_setassparms_cb, NULL);
        if (rc) {
                tmp = rc;
                memset(buf, 0, 16);
@@ -2793,15 +2731,34 @@ static void qeth_tso_fill_header(struct qeth_card *card,
        }
 }
 
-static inline int qeth_l3_tso_elements(struct sk_buff *skb)
+/**
+ * qeth_l3_get_elements_no_tso() - find number of SBALEs for skb data for tso
+ * @card:                         qeth card structure, to check max. elems.
+ * @skb:                          SKB address
+ * @extra_elems:                  extra elems needed, to check against max.
+ *
+ * Returns the number of pages, and thus QDIO buffer elements, needed to cover
+ * skb data, including linear part and fragments, but excluding TCP header.
+ * (Exclusion of TCP header distinguishes it from qeth_get_elements_no().)
+ * Checks if the result plus extra_elems fits under the limit for the card.
+ * Returns 0 if it does not.
+ * Note: extra_elems is not included in the returned result.
+ */
+static int qeth_l3_get_elements_no_tso(struct qeth_card *card,
+                       struct sk_buff *skb, int extra_elems)
 {
-       unsigned long tcpd = (unsigned long)tcp_hdr(skb) +
-               tcp_hdr(skb)->doff * 4;
-       int tcpd_len = skb_headlen(skb) - (tcpd - (unsigned long)skb->data);
-       int elements = PFN_UP(tcpd + tcpd_len - 1) - PFN_DOWN(tcpd);
-
-       elements += qeth_get_elements_for_frags(skb);
+       addr_t tcpdptr = (addr_t)tcp_hdr(skb) + tcp_hdrlen(skb);
+       int elements = qeth_get_elements_for_range(
+                               tcpdptr,
+                               (addr_t)skb->data + skb_headlen(skb)) +
+                               qeth_get_elements_for_frags(skb);
 
+       if ((elements + extra_elems) > QETH_MAX_BUFFER_ELEMENTS(card)) {
+               QETH_DBF_MESSAGE(2,
+       "Invalid size of TSO IP packet (Number=%d / Length=%d). Discarded.\n",
+                               elements + extra_elems, skb->len);
+               return 0;
+       }
        return elements;
 }
 
@@ -2810,8 +2767,8 @@ static int qeth_l3_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
        int rc;
        u16 *tag;
        struct qeth_hdr *hdr = NULL;
-       int elements_needed = 0;
-       int elems;
+       int hdr_elements = 0;
+       int elements;
        struct qeth_card *card = dev->ml_priv;
        struct sk_buff *new_skb = NULL;
        int ipv = qeth_get_ip_version(skb);
@@ -2822,7 +2779,7 @@ static int qeth_l3_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
                        qeth_get_priority_queue(card, skb, ipv, cast_type) :
                        card->qdio.default_out_queue];
        int tx_bytes = skb->len;
-       bool large_send;
+       bool use_tso;
        int data_offset = -1;
        int nr_frags;
 
@@ -2847,10 +2804,12 @@ static int qeth_l3_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
                card->perf_stats.outbound_start_time = qeth_get_micros();
        }
 
-       large_send = skb_is_gso(skb);
+       /* Ignore segment size from skb_is_gso(), 1 page is always used. */
+       use_tso = skb_is_gso(skb) &&
+                 (qeth_get_ip_protocol(skb) == IPPROTO_TCP) && (ipv == 4);
 
-       if ((card->info.type == QETH_CARD_TYPE_IQD) && (!large_send) &&
-           (skb_shinfo(skb)->nr_frags == 0)) {
+       if ((card->info.type == QETH_CARD_TYPE_IQD) &&
+           !skb_is_nonlinear(skb)) {
                new_skb = skb;
                if (new_skb->protocol == ETH_P_AF_IUCV)
                        data_offset = 0;
@@ -2859,7 +2818,7 @@ static int qeth_l3_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
                hdr = kmem_cache_alloc(qeth_core_header_cache, GFP_ATOMIC);
                if (!hdr)
                        goto tx_drop;
-               elements_needed++;
+               hdr_elements++;
        } else {
                /* create a clone with writeable headroom */
                new_skb = skb_realloc_headroom(skb, sizeof(struct qeth_hdr_tso)
@@ -2894,22 +2853,28 @@ static int qeth_l3_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
        /* fix hardware limitation: as long as we do not have sbal
         * chaining we can not send long frag lists
         */
-       if (large_send) {
-               if (qeth_l3_tso_elements(new_skb) + 1 > 16) {
-                       if (skb_linearize(new_skb))
-                               goto tx_drop;
-                       if (card->options.performance_stats)
+       if ((card->info.type != QETH_CARD_TYPE_IQD) &&
+           ((use_tso && !qeth_l3_get_elements_no_tso(card, new_skb, 1)) ||
+            (!use_tso && !qeth_get_elements_no(card, new_skb, 0)))) {
+               int lin_rc = skb_linearize(new_skb);
+
+               if (card->options.performance_stats) {
+                       if (lin_rc)
+                               card->perf_stats.tx_linfail++;
+                       else
                                card->perf_stats.tx_lin++;
                }
+               if (lin_rc)
+                       goto tx_drop;
        }
 
-       if (large_send && (cast_type == RTN_UNSPEC)) {
+       if (use_tso) {
                hdr = (struct qeth_hdr *)skb_push(new_skb,
                                                sizeof(struct qeth_hdr_tso));
                memset(hdr, 0, sizeof(struct qeth_hdr_tso));
                qeth_l3_fill_header(card, hdr, new_skb, ipv, cast_type);
                qeth_tso_fill_header(card, hdr, new_skb);
-               elements_needed++;
+               hdr_elements++;
        } else {
                if (data_offset < 0) {
                        hdr = (struct qeth_hdr *)skb_push(new_skb,
@@ -2930,31 +2895,31 @@ static int qeth_l3_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
                        qeth_l3_hdr_csum(card, hdr, new_skb);
        }
 
-       elems = qeth_get_elements_no(card, new_skb, elements_needed);
-       if (!elems) {
+       elements = use_tso ?
+                  qeth_l3_get_elements_no_tso(card, new_skb, hdr_elements) :
+                  qeth_get_elements_no(card, new_skb, hdr_elements);
+       if (!elements) {
                if (data_offset >= 0)
                        kmem_cache_free(qeth_core_header_cache, hdr);
                goto tx_drop;
        }
-       elements_needed += elems;
-       nr_frags = skb_shinfo(new_skb)->nr_frags;
+       elements += hdr_elements;
 
        if (card->info.type != QETH_CARD_TYPE_IQD) {
                int len;
-               if (large_send)
+               if (use_tso)
                        len = ((unsigned long)tcp_hdr(new_skb) +
-                               tcp_hdr(new_skb)->doff * 4) -
+                               tcp_hdrlen(new_skb)) -
                                (unsigned long)new_skb->data;
                else
                        len = sizeof(struct qeth_hdr_layer3);
 
                if (qeth_hdr_chk_and_bounce(new_skb, &hdr, len))
                        goto tx_drop;
-               rc = qeth_do_send_packet(card, queue, new_skb, hdr,
-                                        elements_needed);
+               rc = qeth_do_send_packet(card, queue, new_skb, hdr, elements);
        } else
                rc = qeth_do_send_packet_fast(card, queue, new_skb, hdr,
-                                       elements_needed, data_offset, 0);
+                                       elements, data_offset, 0);
 
        if (!rc) {
                card->stats.tx_packets++;
@@ -2962,7 +2927,8 @@ static int qeth_l3_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
                if (new_skb != skb)
                        dev_kfree_skb_any(skb);
                if (card->options.performance_stats) {
-                       if (large_send) {
+                       nr_frags = skb_shinfo(new_skb)->nr_frags;
+                       if (use_tso) {
                                card->perf_stats.large_send_bytes += tx_bytes;
                                card->perf_stats.large_send_cnt++;
                        }
@@ -3048,36 +3014,6 @@ static int qeth_l3_stop(struct net_device *dev)
        return 0;
 }
 
-static netdev_features_t qeth_l3_fix_features(struct net_device *dev,
-       netdev_features_t features)
-{
-       struct qeth_card *card = dev->ml_priv;
-
-       if (!qeth_is_supported(card, IPA_OUTBOUND_CHECKSUM))
-               features &= ~NETIF_F_IP_CSUM;
-       if (!qeth_is_supported(card, IPA_OUTBOUND_TSO))
-               features &= ~NETIF_F_TSO;
-       if (!qeth_is_supported(card, IPA_INBOUND_CHECKSUM))
-               features &= ~NETIF_F_RXCSUM;
-       return features;
-}
-
-static int qeth_l3_set_features(struct net_device *dev,
-       netdev_features_t features)
-{
-       struct qeth_card *card = dev->ml_priv;
-       netdev_features_t changed = dev->features ^ features;
-
-       if (!(changed & NETIF_F_RXCSUM))
-               return 0;
-
-       if (card->state == CARD_STATE_DOWN ||
-           card->state == CARD_STATE_RECOVER)
-               return 0;
-
-       return qeth_set_rx_csum(card, features & NETIF_F_RXCSUM ? 1 : 0);
-}
-
 static const struct ethtool_ops qeth_l3_ethtool_ops = {
        .get_link = ethtool_op_get_link,
        .get_strings = qeth_core_get_strings,
@@ -3120,8 +3056,8 @@ static const struct net_device_ops qeth_l3_netdev_ops = {
        .ndo_set_rx_mode        = qeth_l3_set_multicast_list,
        .ndo_do_ioctl           = qeth_l3_do_ioctl,
        .ndo_change_mtu         = qeth_change_mtu,
-       .ndo_fix_features       = qeth_l3_fix_features,
-       .ndo_set_features       = qeth_l3_set_features,
+       .ndo_fix_features       = qeth_fix_features,
+       .ndo_set_features       = qeth_set_features,
        .ndo_vlan_rx_add_vid    = qeth_l3_vlan_rx_add_vid,
        .ndo_vlan_rx_kill_vid   = qeth_l3_vlan_rx_kill_vid,
        .ndo_tx_timeout         = qeth_tx_timeout,
@@ -3136,8 +3072,8 @@ static const struct net_device_ops qeth_l3_osa_netdev_ops = {
        .ndo_set_rx_mode        = qeth_l3_set_multicast_list,
        .ndo_do_ioctl           = qeth_l3_do_ioctl,
        .ndo_change_mtu         = qeth_change_mtu,
-       .ndo_fix_features       = qeth_l3_fix_features,
-       .ndo_set_features       = qeth_l3_set_features,
+       .ndo_fix_features       = qeth_fix_features,
+       .ndo_set_features       = qeth_set_features,
        .ndo_vlan_rx_add_vid    = qeth_l3_vlan_rx_add_vid,
        .ndo_vlan_rx_kill_vid   = qeth_l3_vlan_rx_kill_vid,
        .ndo_tx_timeout         = qeth_tx_timeout,
@@ -3169,7 +3105,10 @@ static int qeth_l3_setup_netdev(struct qeth_card *card)
                                card->dev->hw_features = NETIF_F_SG |
                                        NETIF_F_RXCSUM | NETIF_F_IP_CSUM |
                                        NETIF_F_TSO;
-                               card->dev->features = NETIF_F_RXCSUM;
+                               card->dev->vlan_features = NETIF_F_SG |
+                                       NETIF_F_RXCSUM | NETIF_F_IP_CSUM |
+                                       NETIF_F_TSO;
+                               card->dev->features = NETIF_F_SG;
                        }
                }
        } else if (card->info.type == QETH_CARD_TYPE_IQD) {
@@ -3195,7 +3134,9 @@ static int qeth_l3_setup_netdev(struct qeth_card *card)
                                NETIF_F_HW_VLAN_CTAG_RX |
                                NETIF_F_HW_VLAN_CTAG_FILTER;
        netif_keep_dst(card->dev);
-       card->dev->gso_max_size = 15 * PAGE_SIZE;
+       card->dev->gso_max_size = (QETH_MAX_BUFFER_ELEMENTS(card) - 1) *
+                                 PAGE_SIZE;
+       card->dev->gso_max_segs = (QETH_MAX_BUFFER_ELEMENTS(card) - 1);
 
        SET_NETDEV_DEV(card->dev, &card->gdev->dev);
        netif_napi_add(card->dev, &card->napi, qeth_l3_poll, QETH_NAPI_WEIGHT);
@@ -3231,7 +3172,7 @@ static void qeth_l3_remove_device(struct ccwgroup_device *cgdev)
                card->dev = NULL;
        }
 
-       qeth_l3_clear_ip_list(card, 0);
+       qeth_l3_clear_ip_htable(card, 0);
        qeth_l3_clear_ipato_list(card);
        return;
 }
@@ -3316,7 +3257,7 @@ contin:
        card->state = CARD_STATE_SOFTSETUP;
 
        qeth_set_allowed_threads(card, 0xffffffff, 0);
-       qeth_l3_set_ip_addr_list(card);
+       qeth_l3_recover_ip(card);
        if (card->lan_online)
                netif_carrier_on(card->dev);
        else
@@ -3517,6 +3458,7 @@ EXPORT_SYMBOL_GPL(qeth_l3_discipline);
 static int qeth_l3_ip_event(struct notifier_block *this,
                            unsigned long event, void *ptr)
 {
+
        struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
        struct net_device *dev = (struct net_device *)ifa->ifa_dev->dev;
        struct qeth_ipaddr *addr;
@@ -3531,27 +3473,27 @@ static int qeth_l3_ip_event(struct notifier_block *this,
        QETH_CARD_TEXT(card, 3, "ipevent");
 
        addr = qeth_l3_get_addr_buffer(QETH_PROT_IPV4);
-       if (addr != NULL) {
+       if (addr) {
                addr->u.a4.addr = ifa->ifa_address;
                addr->u.a4.mask = ifa->ifa_mask;
                addr->type = QETH_IP_TYPE_NORMAL;
        } else
-               goto out;
+               return NOTIFY_DONE;
 
        switch (event) {
        case NETDEV_UP:
-               if (!qeth_l3_add_ip(card, addr))
-                       kfree(addr);
+               spin_lock_bh(&card->ip_lock);
+               qeth_l3_add_ip(card, addr);
+               spin_unlock_bh(&card->ip_lock);
                break;
        case NETDEV_DOWN:
-               if (!qeth_l3_delete_ip(card, addr))
-                       kfree(addr);
-               break;
-       default:
+               spin_lock_bh(&card->ip_lock);
+               qeth_l3_delete_ip(card, addr);
+               spin_unlock_bh(&card->ip_lock);
                break;
        }
-       qeth_l3_set_ip_addr_list(card);
-out:
+
+       kfree(addr);
        return NOTIFY_DONE;
 }
 
@@ -3580,27 +3522,27 @@ static int qeth_l3_ip6_event(struct notifier_block *this,
                return NOTIFY_DONE;
 
        addr = qeth_l3_get_addr_buffer(QETH_PROT_IPV6);
-       if (addr != NULL) {
+       if (addr) {
                memcpy(&addr->u.a6.addr, &ifa->addr, sizeof(struct in6_addr));
                addr->u.a6.pfxlen = ifa->prefix_len;
                addr->type = QETH_IP_TYPE_NORMAL;
        } else
-               goto out;
+               return NOTIFY_DONE;
 
        switch (event) {
        case NETDEV_UP:
-               if (!qeth_l3_add_ip(card, addr))
-                       kfree(addr);
+               spin_lock_bh(&card->ip_lock);
+               qeth_l3_add_ip(card, addr);
+               spin_unlock_bh(&card->ip_lock);
                break;
        case NETDEV_DOWN:
-               if (!qeth_l3_delete_ip(card, addr))
-                       kfree(addr);
-               break;
-       default:
+               spin_lock_bh(&card->ip_lock);
+               qeth_l3_delete_ip(card, addr);
+               spin_unlock_bh(&card->ip_lock);
                break;
        }
-       qeth_l3_set_ip_addr_list(card);
-out:
+
+       kfree(addr);
        return NOTIFY_DONE;
 }