#include <linux/list.h>
 #include <linux/spinlock.h>
 #include <linux/rculist_nulls.h>
+#include <linux/hash.h>
+#include <linux/jhash.h>
 
 #include <asm/atomic.h>
 
 #define LLC_SK_DEV_HASH_BITS 6
 #define LLC_SK_DEV_HASH_ENTRIES (1<<LLC_SK_DEV_HASH_BITS)
 
+#define LLC_SK_LADDR_HASH_BITS 6
+#define LLC_SK_LADDR_HASH_ENTRIES (1<<LLC_SK_LADDR_HASH_BITS)
+
 /**
  * struct llc_sap - Defines the SAP component
  *
        struct llc_addr  laddr;
        struct list_head node;
        spinlock_t sk_lock;
-       struct hlist_nulls_head sk_list;
+       int sk_count;
+       struct hlist_nulls_head sk_laddr_hash[LLC_SK_LADDR_HASH_ENTRIES];
        struct hlist_head sk_dev_hash[LLC_SK_DEV_HASH_ENTRIES];
 };
 
        return &sap->sk_dev_hash[ifindex % LLC_SK_DEV_HASH_ENTRIES];
 }
 
+static inline
+u32 llc_sk_laddr_hashfn(struct llc_sap *sap, const struct llc_addr *laddr)
+{
+       return hash_32(jhash(laddr->mac, sizeof(laddr->mac), 0),
+                      LLC_SK_LADDR_HASH_BITS);
+}
+
+static inline
+struct hlist_nulls_head *llc_sk_laddr_hash(struct llc_sap *sap,
+                                          const struct llc_addr *laddr)
+{
+       return &sap->sk_laddr_hash[llc_sk_laddr_hashfn(sap, laddr)];
+}
 
 #define LLC_DEST_INVALID         0      /* Invalid LLC PDU type */
 #define LLC_DEST_SAP             1      /* Type 1 goes here */
 
 {
        struct sock *rc;
        struct hlist_nulls_node *node;
+       int slot = llc_sk_laddr_hashfn(sap, laddr);
+       struct hlist_nulls_head *laddr_hb = &sap->sk_laddr_hash[slot];
 
        rcu_read_lock();
 again:
-       sk_nulls_for_each_rcu(rc, node, &sap->sk_list) {
+       sk_nulls_for_each_rcu(rc, node, laddr_hb) {
                if (llc_estab_match(sap, daddr, laddr, rc)) {
                        /* Extra checks required by SLAB_DESTROY_BY_RCU */
                        if (unlikely(!atomic_inc_not_zero(&rc->sk_refcnt)))
                }
        }
        rc = NULL;
+       /*
+        * if the nulls value we got at the end of this lookup is
+        * not the expected one, we must restart lookup.
+        * We probably met an item that was moved to another chain.
+        */
+       if (unlikely(get_nulls_value(node) != slot))
+               goto again;
 found:
        rcu_read_unlock();
        return rc;
 
        return sk->sk_type == SOCK_STREAM && sk->sk_state == TCP_LISTEN &&
                llc->laddr.lsap == laddr->lsap &&
-               (llc_mac_match(llc->laddr.mac, laddr->mac) ||
-                llc_mac_null(llc->laddr.mac));
+               llc_mac_match(llc->laddr.mac, laddr->mac);
 }
 
-/**
- *     llc_lookup_listener - Finds listener for local MAC + SAP
- *     @sap: SAP
- *     @laddr: address of local LLC (MAC + SAP)
- *
- *     Search connection list of the SAP and finds connection listening on
- *     local mac, and local sap. Returns pointer for parent socket found,
- *     %NULL otherwise.
- *     Caller has to make sure local_bh is disabled.
- */
-static struct sock *llc_lookup_listener(struct llc_sap *sap,
-                                       struct llc_addr *laddr)
+static struct sock *__llc_lookup_listener(struct llc_sap *sap,
+                                         struct llc_addr *laddr)
 {
        struct sock *rc;
        struct hlist_nulls_node *node;
+       int slot = llc_sk_laddr_hashfn(sap, laddr);
+       struct hlist_nulls_head *laddr_hb = &sap->sk_laddr_hash[slot];
 
        rcu_read_lock();
 again:
-       sk_nulls_for_each_rcu(rc, node, &sap->sk_list) {
+       sk_nulls_for_each_rcu(rc, node, laddr_hb) {
                if (llc_listener_match(sap, laddr, rc)) {
                        /* Extra checks required by SLAB_DESTROY_BY_RCU */
                        if (unlikely(!atomic_inc_not_zero(&rc->sk_refcnt)))
                }
        }
        rc = NULL;
+       /*
+        * if the nulls value we got at the end of this lookup is
+        * not the expected one, we must restart lookup.
+        * We probably met an item that was moved to another chain.
+        */
+       if (unlikely(get_nulls_value(node) != slot))
+               goto again;
 found:
        rcu_read_unlock();
        return rc;
 }
 
+/**
+ *     llc_lookup_listener - Finds listener for local MAC + SAP
+ *     @sap: SAP
+ *     @laddr: address of local LLC (MAC + SAP)
+ *
+ *     Search connection list of the SAP and finds connection listening on
+ *     local mac, and local sap. Returns pointer for parent socket found,
+ *     %NULL otherwise.
+ *     Caller has to make sure local_bh is disabled.
+ */
+static struct sock *llc_lookup_listener(struct llc_sap *sap,
+                                       struct llc_addr *laddr)
+{
+       static struct llc_addr null_addr;
+       struct sock *rc = __llc_lookup_listener(sap, laddr);
+
+       if (!rc)
+               rc = __llc_lookup_listener(sap, &null_addr);
+
+       return rc;
+}
+
 static struct sock *__llc_lookup(struct llc_sap *sap,
                                 struct llc_addr *daddr,
                                 struct llc_addr *laddr)
  *     @sap: SAP
  *     @sk: socket
  *
- *     This function adds a socket to sk_list of a SAP.
+ *     This function adds a socket to the hash tables of a SAP.
  */
 void llc_sap_add_socket(struct llc_sap *sap, struct sock *sk)
 {
        struct llc_sock *llc = llc_sk(sk);
        struct hlist_head *dev_hb = llc_sk_dev_hash(sap, llc->dev->ifindex);
+       struct hlist_nulls_head *laddr_hb = llc_sk_laddr_hash(sap, &llc->laddr);
 
        llc_sap_hold(sap);
        llc_sk(sk)->sap = sap;
 
        spin_lock_bh(&sap->sk_lock);
-       sk_nulls_add_node_rcu(sk, &sap->sk_list);
+       sap->sk_count++;
+       sk_nulls_add_node_rcu(sk, laddr_hb);
        hlist_add_head(&llc->dev_hash_node, dev_hb);
        spin_unlock_bh(&sap->sk_lock);
 }
  *     @sap: SAP
  *     @sk: socket
  *
- *     This function removes a connection from sk_list of a SAP if
+ *     This function removes a connection from the hash tables of a SAP if
  *     the connection was in this list.
  */
 void llc_sap_remove_socket(struct llc_sap *sap, struct sock *sk)
        spin_lock_bh(&sap->sk_lock);
        sk_nulls_del_node_init_rcu(sk);
        hlist_del(&llc->dev_hash_node);
+       sap->sk_count--;
        spin_unlock_bh(&sap->sk_lock);
        llc_sap_put(sap);
 }
 
 static struct llc_sap *llc_sap_alloc(void)
 {
        struct llc_sap *sap = kzalloc(sizeof(*sap), GFP_ATOMIC);
+       int i;
 
        if (sap) {
                /* sap->laddr.mac - leave as a null, it's filled by bind */
                sap->state = LLC_SAP_STATE_ACTIVE;
                spin_lock_init(&sap->sk_lock);
-               INIT_HLIST_NULLS_HEAD(&sap->sk_list, 0);
+               for (i = 0; i < LLC_SK_LADDR_HASH_ENTRIES; i++)
+                       INIT_HLIST_NULLS_HEAD(&sap->sk_laddr_hash[i], i);
                atomic_set(&sap->refcnt, 1);
        }
        return sap;
  */
 void llc_sap_close(struct llc_sap *sap)
 {
-       WARN_ON(!hlist_nulls_empty(&sap->sk_list));
+       WARN_ON(sap->sk_count);
        llc_del_sap(sap);
        kfree(sap);
 }
 
 {
        struct list_head *sap_entry;
        struct llc_sap *sap;
-       struct hlist_nulls_node *node;
        struct sock *sk = NULL;
+       int i;
 
        list_for_each(sap_entry, &llc_sap_list) {
                sap = list_entry(sap_entry, struct llc_sap, node);
 
                spin_lock_bh(&sap->sk_lock);
-               sk_nulls_for_each(sk, node, &sap->sk_list) {
-                       if (!pos)
-                               goto found;
-                       --pos;
+               for (i = 0; i < LLC_SK_LADDR_HASH_ENTRIES; i++) {
+                       struct hlist_nulls_head *head = &sap->sk_laddr_hash[i];
+                       struct hlist_nulls_node *node;
+
+                       sk_nulls_for_each(sk, node, head) {
+                               if (!pos)
+                                       goto found; /* keep the lock */
+                               --pos;
+                       }
                }
                spin_unlock_bh(&sap->sk_lock);
        }
        return l ? llc_get_sk_idx(--l) : SEQ_START_TOKEN;
 }
 
+static struct sock *laddr_hash_next(struct llc_sap *sap, int bucket)
+{
+       struct hlist_nulls_node *node;
+       struct sock *sk = NULL;
+
+       while (++bucket < LLC_SK_LADDR_HASH_ENTRIES)
+               sk_nulls_for_each(sk, node, &sap->sk_laddr_hash[bucket])
+                       goto out;
+
+out:
+       return sk;
+}
+
 static void *llc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 {
        struct sock* sk, *next;
        }
        llc = llc_sk(sk);
        sap = llc->sap;
+       sk = laddr_hash_next(sap, llc_sk_laddr_hashfn(sap, &llc->laddr));
+       if (sk)
+               goto out;
        spin_unlock_bh(&sap->sk_lock);
-       sk = NULL;
-       for (;;) {
-               if (sap->node.next == &llc_sap_list)
-                       break;
-               sap = list_entry(sap->node.next, struct llc_sap, node);
+       list_for_each_entry_continue(sap, &llc_sap_list, node) {
                spin_lock_bh(&sap->sk_lock);
-               if (!hlist_nulls_empty(&sap->sk_list)) {
-                       sk = sk_nulls_head(&sap->sk_list);
-                       break;
-               }
+               sk = laddr_hash_next(sap, -1);
+               if (sk)
+                       break; /* keep the lock */
                spin_unlock_bh(&sap->sk_lock);
        }
 out:
 
 {
        struct sock *rc;
        struct hlist_nulls_node *node;
+       int slot = llc_sk_laddr_hashfn(sap, laddr);
+       struct hlist_nulls_head *laddr_hb = &sap->sk_laddr_hash[slot];
 
        rcu_read_lock_bh();
 again:
-       sk_nulls_for_each_rcu(rc, node, &sap->sk_list) {
+       sk_nulls_for_each_rcu(rc, node, laddr_hb) {
                if (llc_dgram_match(sap, laddr, rc)) {
                        /* Extra checks required by SLAB_DESTROY_BY_RCU */
                        if (unlikely(!atomic_inc_not_zero(&rc->sk_refcnt)))
                }
        }
        rc = NULL;
+       /*
+        * if the nulls value we got at the end of this lookup is
+        * not the expected one, we must restart lookup.
+        * We probably met an item that was moved to another chain.
+        */
+       if (unlikely(get_nulls_value(node) != slot))
+               goto again;
 found:
        rcu_read_unlock_bh();
        return rc;