return;
        }
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, hdr->addr1);
        if (!sta || !sta->rate_ctrl_priv) {
-               if (sta)
-                       sta_info_put(sta);
+               rcu_read_unlock();
                IWL_DEBUG_RATE("leave: No STA priv data to update!\n");
                return;
        }
 
        spin_unlock_irqrestore(&rs_sta->lock, flags);
 
-       sta_info_put(sta);
+       rcu_read_unlock();
 
        IWL_DEBUG_RATE("leave\n");
 
 
        IWL_DEBUG_RATE("enter\n");
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, hdr->addr1);
 
        /* Send management frames and broadcast/multicast data using lowest
            !sta || !sta->rate_ctrl_priv) {
                IWL_DEBUG_RATE("leave: No STA priv data to update!\n");
                sel->rate = rate_lowest(local, band, sta);
-               if (sta)
-                       sta_info_put(sta);
+               rcu_read_unlock();
                return;
        }
 
        else
                sta->txrate_idx = sta->last_txrate_idx;
 
-       sta_info_put(sta);
+       rcu_read_unlock();
 
        IWL_DEBUG_RATE("leave: %d\n", index);
 
        unsigned long now = jiffies;
        u32 max_time = 0;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, priv->stations[sta_id].sta.sta.addr);
        if (!sta || !sta->rate_ctrl_priv) {
-               if (sta) {
-                       sta_info_put(sta);
+               if (sta)
                        IWL_DEBUG_RATE("leave - no private rate data!\n");
-               } else
+               else
                        IWL_DEBUG_RATE("leave - no station!\n");
+               rcu_read_unlock();
                return sprintf(buf, "station %d not found\n", sta_id);
        }
 
                i = j;
        }
        spin_unlock_irqrestore(&rs_sta->lock, flags);
-       sta_info_put(sta);
+       rcu_read_unlock();
 
        /* Display the average rate of all samples taken.
         *
                return;
        }
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, priv->stations[sta_id].sta.sta.addr);
        if (!sta || !sta->rate_ctrl_priv) {
-               if (sta)
-                       sta_info_put(sta);
                IWL_DEBUG_RATE("leave - no private rate data!\n");
+               rcu_read_unlock();
                return;
        }
 
                break;
        }
 
-       sta_info_put(sta);
+       rcu_read_unlock();
        spin_unlock_irqrestore(&rs_sta->lock, flags);
 
        rssi = priv->last_rx_rssi;
 
        if (retries > 15)
                retries = 15;
 
+       rcu_read_lock();
 
        sta = sta_info_get(local, hdr->addr1);
 
        if (!sta || !sta->rate_ctrl_priv) {
-               if (sta)
-                       sta_info_put(sta);
+               rcu_read_unlock();
                return;
        }
 
        if ((rs_index < 0) || (rs_index >= IWL_RATE_COUNT)) {
                IWL_DEBUG_RATE("bad rate index at: %d rate 0x%X\n",
                             rs_index, tx_mcs.rate_n_flags);
-               sta_info_put(sta);
+               rcu_read_unlock();
                return;
        }
 
                IWL_DEBUG_RATE("initial rate does not match 0x%x 0x%x\n",
                                tx_mcs.rate_n_flags,
                                le32_to_cpu(table->rs_table[0].rate_n_flags));
-               sta_info_put(sta);
+               rcu_read_unlock();
                return;
        }
 
 
        /* See if there's a better rate or modulation mode to try. */
        rs_rate_scale_perform(priv, dev, hdr, sta);
-       sta_info_put(sta);
+       rcu_read_unlock();
        return;
 }
 
 
        IWL_DEBUG_RATE_LIMIT("rate scale calculate new rate for skb\n");
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, hdr->addr1);
 
        /* Send management frames and broadcast/multicast data using lowest
        if (!ieee80211_is_data(fc) || is_multicast_ether_addr(hdr->addr1) ||
            !sta || !sta->rate_ctrl_priv) {
                sel->rate = rate_lowest(local, sband, sta);
-               if (sta)
-                       sta_info_put(sta);
+               rcu_read_unlock();
                return;
        }
 
                sel->rate = rate_lowest(local, sband, sta);
                return;
        }
-       sta_info_put(sta);
+       rcu_read_unlock();
 
        sel->rate = &priv->ieee_rates[i];
 }
        u32 max_time = 0;
        u8 lq_type, antenna;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, priv->stations[sta_id].sta.sta.addr);
        if (!sta || !sta->rate_ctrl_priv) {
-               if (sta) {
-                       sta_info_put(sta);
+               if (sta)
                        IWL_DEBUG_RATE("leave - no private rate data!\n");
-               } else
+               else
                        IWL_DEBUG_RATE("leave - no station!\n");
+               rcu_read_unlock();
                return sprintf(buf, "station %d not found\n", sta_id);
        }
 
                         "active_search %d rate index %d\n", lq_type, antenna,
                         lq_sta->search_better_tbl, sta->last_txrate_idx);
 
-       sta_info_put(sta);
+       rcu_read_unlock();
        return cnt;
 }
 
 
        struct ieee80211_sub_if_data *sdata;
        struct sta_info *sta = NULL;
        enum ieee80211_key_alg alg;
-       int ret;
        struct ieee80211_key *key;
 
        sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 
        ieee80211_key_link(key, sdata, sta);
 
-       ret = 0;
-
-       if (sta)
-               sta_info_put(sta);
-
-       return ret;
+       return 0;
 }
 
 static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev,
        struct ieee80211_sub_if_data *sdata;
        struct sta_info *sta;
        int ret;
-       struct ieee80211_key *key;
 
        sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 
 
                ret = 0;
                if (sta->key) {
-                       key = sta->key;
-                       ieee80211_key_free(key);
+                       ieee80211_key_free(sta->key);
                        WARN_ON(sta->key);
                } else
                        ret = -ENOENT;
 
-               sta_info_put(sta);
                return ret;
        }
 
        if (!sdata->keys[key_idx])
                return -ENOENT;
 
-       key = sdata->keys[key_idx];
-       ieee80211_key_free(key);
+       ieee80211_key_free(sdata->keys[key_idx]);
        WARN_ON(sdata->keys[key_idx]);
 
        return 0;
        err = 0;
 
  out:
-       if (sta)
-               sta_info_put(sta);
        return err;
 }
 
 
 static void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo)
 {
-       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
 
        sinfo->filled = STATION_INFO_INACTIVE_TIME |
                        STATION_INFO_RX_BYTES |
 {
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
        struct sta_info *sta;
+       int ret = -ENOENT;
+
+       rcu_read_lock();
 
        sta = sta_info_get_by_idx(local, idx, dev);
-       if (!sta)
-               return -ENOENT;
+       if (sta) {
+               ret = 0;
+               memcpy(mac, sta->addr, ETH_ALEN);
+               sta_set_sinfo(sta, sinfo);
+       }
 
-       memcpy(mac, sta->addr, ETH_ALEN);
-       sta_set_sinfo(sta, sinfo);
-       sta_info_put(sta);
+       rcu_read_unlock();
 
-       return 0;
+       return ret;
 }
 
 static int ieee80211_get_station(struct wiphy *wiphy, struct net_device *dev,
 {
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
        struct sta_info *sta;
+       int ret = -ENOENT;
 
-       sta = sta_info_get(local, mac);
-       if (!sta)
-               return -ENOENT;
+       rcu_read_lock();
 
        /* XXX: verify sta->dev == dev */
-       sta_set_sinfo(sta, sinfo);
-       sta_info_put(sta);
 
-       return 0;
+       sta = sta_info_get(local, mac);
+       if (sta) {
+               ret = 0;
+               sta_set_sinfo(sta, sinfo);
+       }
+
+       rcu_read_unlock();
+
+       return ret;
 }
 
 /*
        msg->xid_info[1] = 1;   /* LLC types/classes: Type 1 LLC */
        msg->xid_info[2] = 0;   /* XID sender's receive window size (RW) */
 
-       skb->dev = sta->dev;
-       skb->protocol = eth_type_trans(skb, sta->dev);
+       skb->dev = sta->sdata->dev;
+       skb->protocol = eth_type_trans(skb, sta->sdata->dev);
        memset(skb->cb, 0, sizeof(skb->cb));
        netif_rx(skb);
 }
        u32 rates;
        int i, j;
        struct ieee80211_supported_band *sband;
-       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
 
        if (params->station_flags & STATION_FLAG_CHANGED) {
                sta->flags &= ~WLAN_STA_AUTHORIZED;
                sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 
        if (ieee80211_vif_is_mesh(&sdata->vif))
-               sta = mesh_plink_add(mac, DEFAULT_RATES, dev);
+               sta = mesh_plink_add(mac, DEFAULT_RATES, sdata);
        else
-               sta = sta_info_add(local, dev, mac, GFP_KERNEL);
+               sta = sta_info_add(sdata, mac);
 
        if (IS_ERR(sta))
                return PTR_ERR(sta);
 
-       sta->dev = sdata->dev;
        if (sdata->vif.type == IEEE80211_IF_TYPE_VLAN ||
            sdata->vif.type == IEEE80211_IF_TYPE_AP)
                ieee80211_send_layer2_update(sta);
 
        rate_control_rate_init(sta, local);
 
-       sta_info_put(sta);
-
        return 0;
 }
 
 static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev,
                                 u8 *mac)
 {
-       struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_local *local = sdata->local;
        struct sta_info *sta;
 
        if (mac) {
                if (!sta)
                        return -ENOENT;
 
-               sta_info_free(sta);
-               sta_info_put(sta);
+               sta_info_unlink(&sta);
+
+               if (sta) {
+                       synchronize_rcu();
+                       sta_info_destroy(sta);
+               }
        } else
-               sta_info_flush(local, dev);
+               sta_info_flush(local, sdata);
 
        return 0;
 }
        if (!sta)
                return -ENOENT;
 
-       if (params->vlan && params->vlan != sta->dev) {
+       if (params->vlan && params->vlan != sta->sdata->dev) {
                vlansdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);
 
                if (vlansdata->vif.type != IEEE80211_IF_TYPE_VLAN ||
                    vlansdata->vif.type != IEEE80211_IF_TYPE_AP)
                        return -EINVAL;
 
-               sta->dev = params->vlan;
+               sta->sdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);
                ieee80211_send_layer2_update(sta);
        }
 
        sta_apply_parameters(local, sta, params);
 
-       sta_info_put(sta);
-
        return 0;
 }
 
        if (sdata->vif.type != IEEE80211_IF_TYPE_MESH_POINT)
                return -ENOTSUPP;
 
+       rcu_read_lock();
        sta = sta_info_get(local, next_hop);
-       if (!sta)
+       if (!sta) {
+               rcu_read_unlock();
                return -ENOENT;
+       }
 
        err = mesh_path_add(dst, dev);
-       if (err)
+       if (err) {
+               rcu_read_unlock();
                return err;
+       }
 
-       rcu_read_lock();
        mpath = mesh_path_lookup(dst, dev);
        if (!mpath) {
                rcu_read_unlock();
-               sta_info_put(sta);
                return -ENXIO;
        }
        mesh_path_fix_nexthop(mpath, sta);
-       sta_info_put(sta);
+
        rcu_read_unlock();
        return 0;
 }
                                 u8 *dst)
 {
        if (dst)
-               return mesh_path_del(dst, dev);
+               return mesh_path_del(dst, dev, false);
 
        mesh_path_flush(dev);
        return 0;
        if (sdata->vif.type != IEEE80211_IF_TYPE_MESH_POINT)
                return -ENOTSUPP;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, next_hop);
-       if (!sta)
+       if (!sta) {
+               rcu_read_unlock();
                return -ENOENT;
+       }
 
-       rcu_read_lock();
        mpath = mesh_path_lookup(dst, dev);
        if (!mpath) {
                rcu_read_unlock();
-               sta_info_put(sta);
                return -ENOENT;
        }
 
        mesh_path_fix_nexthop(mpath, sta);
-       sta_info_put(sta);
+
        rcu_read_unlock();
        return 0;
 }
 
                STA_OPS(name)
 
 STA_FILE(aid, aid, D);
-STA_FILE(dev, dev->name, S);
+STA_FILE(dev, sdata->dev->name, S);
 STA_FILE(rx_packets, rx_packets, LU);
 STA_FILE(tx_packets, tx_packets, LU);
 STA_FILE(rx_bytes, rx_bytes, LU);
                const char __user *user_buf, size_t count, loff_t *ppos)
 {
        struct sta_info *sta = file->private_data;
-       struct net_device *dev = sta->dev;
+       struct net_device *dev = sta->sdata->dev;
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
        struct ieee80211_hw *hw = &local->hw;
        u8 *da = sta->addr;
 
 #ifndef __MAC80211_DEBUGFS_STA_H
 #define __MAC80211_DEBUGFS_STA_H
 
+#include "sta_info.h"
+
 #ifdef CONFIG_MAC80211_DEBUGFS
 void ieee80211_sta_debugfs_add(struct sta_info *sta);
 void ieee80211_sta_debugfs_remove(struct sta_info *sta);
 
 
        sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 
-       list_for_each_entry(sta, &local->sta_list, list) {
-               if (sta->dev == dev)
+       rcu_read_lock();
+
+       list_for_each_entry_rcu(sta, &local->sta_list, list) {
+               if (sta->sdata == sdata)
                        for (i = 0; i <  STA_TID_NUM; i++)
-                               ieee80211_sta_stop_rx_ba_session(sta->dev,
+                               ieee80211_sta_stop_rx_ba_session(sdata->dev,
                                                sta->addr, i,
                                                WLAN_BACK_RECIPIENT,
                                                WLAN_REASON_QSTA_LEAVE_QBSS);
        }
 
+       rcu_read_unlock();
+
        netif_stop_queue(dev);
 
        /*
                netif_tx_unlock_bh(local->mdev);
                break;
        case IEEE80211_IF_TYPE_MESH_POINT:
-               sta_info_flush(local, dev);
+               sta_info_flush(local, sdata);
                /* fall through */
        case IEEE80211_IF_TYPE_STA:
        case IEEE80211_IF_TYPE_IBSS:
                                print_mac(mac, ra), tid);
 #endif /* CONFIG_MAC80211_HT_DEBUG */
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, ra);
        if (!sta) {
                printk(KERN_DEBUG "Could not find the station\n");
+               rcu_read_unlock();
                return -ENOENT;
        }
 
                spin_unlock_bh(&local->mdev->queue_lock);
                goto start_ba_exit;
        }
-       sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       sdata = sta->sdata;
 
        /* Ok, the Addba frame hasn't been sent yet, but if the driver calls the
         * call back right away, it must see that the flow has begun */
                        sta->ampdu_mlme.dialog_token_allocator;
        sta->ampdu_mlme.tid_tx[tid].ssn = start_seq_num;
 
-       ieee80211_send_addba_request(sta->dev, ra, tid,
+       ieee80211_send_addba_request(sta->sdata->dev, ra, tid,
                         sta->ampdu_mlme.tid_tx[tid].dialog_token,
                         sta->ampdu_mlme.tid_tx[tid].ssn,
                         0x40, 5000);
 
 start_ba_exit:
        spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx);
-       sta_info_put(sta);
+       rcu_read_unlock();
        return ret;
 }
 EXPORT_SYMBOL(ieee80211_start_tx_ba_session);
                                print_mac(mac, ra), tid);
 #endif /* CONFIG_MAC80211_HT_DEBUG */
 
+       rcu_read_lock();
        sta = sta_info_get(local, ra);
-       if (!sta)
+       if (!sta) {
+               rcu_read_unlock();
                return -ENOENT;
+       }
 
        /* check if the TID is in aggregation */
        state = &sta->ampdu_mlme.tid_tx[tid].state;
 
 stop_BA_exit:
        spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx);
-       sta_info_put(sta);
+       rcu_read_unlock();
        return ret;
 }
 EXPORT_SYMBOL(ieee80211_stop_tx_ba_session);
                return;
        }
 
+       rcu_read_lock();
        sta = sta_info_get(local, ra);
        if (!sta) {
+               rcu_read_unlock();
                printk(KERN_DEBUG "Could not find station: %s\n",
                                print_mac(mac, ra));
                return;
                printk(KERN_DEBUG "addBA was not requested yet, state is %d\n",
                                *state);
                spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx);
-               sta_info_put(sta);
+               rcu_read_unlock();
                return;
        }
 
                ieee80211_wake_queue(hw, sta->tid_to_tx_q[tid]);
        }
        spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx);
-       sta_info_put(sta);
+       rcu_read_unlock();
 }
 EXPORT_SYMBOL(ieee80211_start_tx_ba_cb);
 
        printk(KERN_DEBUG "Stop a BA session requested on DA %s tid %d\n",
                                print_mac(mac, ra), tid);
 
+       rcu_read_lock();
        sta = sta_info_get(local, ra);
        if (!sta) {
                printk(KERN_DEBUG "Could not find station: %s\n",
                                print_mac(mac, ra));
+               rcu_read_unlock();
                return;
        }
        state = &sta->ampdu_mlme.tid_tx[tid].state;
        spin_lock_bh(&sta->ampdu_mlme.ampdu_tx);
        if ((*state & HT_AGG_STATE_REQ_STOP_BA_MSK) == 0) {
                printk(KERN_DEBUG "unexpected callback to A-MPDU stop\n");
-               sta_info_put(sta);
                spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx);
+               rcu_read_unlock();
                return;
        }
 
        if (*state & HT_AGG_STATE_INITIATOR_MSK)
-               ieee80211_send_delba(sta->dev, ra, tid,
+               ieee80211_send_delba(sta->sdata->dev, ra, tid,
                        WLAN_BACK_INITIATOR, WLAN_REASON_QSTA_NOT_USE);
 
        agg_queue = sta->tid_to_tx_q[tid];
        sta->ampdu_mlme.tid_tx[tid].addba_req_num = 0;
        spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx);
 
-       sta_info_put(sta);
+       rcu_read_unlock();
 }
 EXPORT_SYMBOL(ieee80211_stop_tx_ba_cb);
 
        struct sta_info *sta;
        DECLARE_MAC_BUF(mac);
 
+       might_sleep();
+
        if (compare_ether_addr(remote_addr, sdata->u.wds.remote_addr) == 0)
                return 0;
 
+       rcu_read_lock();
+
        /* Create STA entry for the new peer */
-       sta = sta_info_add(local, dev, remote_addr, GFP_KERNEL);
-       if (IS_ERR(sta))
+       sta = sta_info_add(sdata, remote_addr);
+       if (IS_ERR(sta)) {
+               rcu_read_unlock();
                return PTR_ERR(sta);
+       }
 
        sta->flags |= WLAN_STA_AUTHORIZED;
 
-       sta_info_put(sta);
-
        /* Remove STA entry for the old peer */
        sta = sta_info_get(local, sdata->u.wds.remote_addr);
-       if (sta) {
-               sta_info_free(sta);
-               sta_info_put(sta);
-       } else {
+       if (sta)
+               sta_info_unlink(&sta);
+       else
                printk(KERN_DEBUG "%s: could not find STA entry for WDS link "
                       "peer %s\n",
                       dev->name, print_mac(mac, sdata->u.wds.remote_addr));
-       }
 
        /* Update WDS link data */
        memcpy(&sdata->u.wds.remote_addr, remote_addr, ETH_ALEN);
 
+       rcu_read_unlock();
+
+       if (sta) {
+               synchronize_rcu();
+               sta_info_destroy(sta);
+       }
+
        return 0;
 }
 
                return;
        }
 
+       rcu_read_lock();
+
        if (status->excessive_retries) {
                struct sta_info *sta;
                sta = sta_info_get(local, hdr->addr1);
                                status->flags |= IEEE80211_TX_STATUS_TX_FILTERED;
                                ieee80211_handle_filtered_frame(local, sta,
                                                                skb, status);
-                               sta_info_put(sta);
+                               rcu_read_unlock();
                                return;
                        }
-                       sta_info_put(sta);
                }
        }
 
                if (sta) {
                        ieee80211_handle_filtered_frame(local, sta, skb,
                                                        status);
-                       sta_info_put(sta);
+                       rcu_read_unlock();
                        return;
                }
        } else
                rate_control_tx_status(local->mdev, skb, status);
 
+       rcu_read_unlock();
+
        ieee80211_led_tx(local, 0);
 
        /* SNMP counters
 
        unsigned int filter_flags; /* FIF_* */
        struct iw_statistics wstats;
        u8 wstats_flags;
+       bool tim_in_locked_section; /* see ieee80211_beacon_get() */
        int tx_headroom; /* required headroom for hardware/radiotap */
 
        enum {
        struct sk_buff_head skb_queue;
        struct sk_buff_head skb_queue_unreliable;
 
-       /* Station data structures */
-       rwlock_t sta_lock; /* protects STA data structures */
-       int num_sta; /* number of stations in sta_list */
+       /* Station data */
+       /*
+        * The lock only protects the list, hash, timer and counter
+        * against manipulation, reads are done in RCU. Additionally,
+        * the lock protects each BSS's TIM bitmap and a few items
+        * in a STA info structure.
+        */
+       spinlock_t sta_lock;
+       unsigned long num_sta;
        struct list_head sta_list;
        struct sta_info *sta_hash[STA_HASH_SIZE];
        struct timer_list sta_cleanup;
 
                break;
        }
        case IEEE80211_IF_TYPE_WDS:
+               rcu_read_lock();
                sta = sta_info_get(local, sdata->u.wds.remote_addr);
                if (sta) {
-                       sta_info_free(sta);
-                       sta_info_put(sta);
+                       sta_info_unlink(&sta);
                } else {
 #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
                        printk(KERN_DEBUG "%s: Someone had deleted my STA "
                               "entry for the WDS link\n", dev->name);
 #endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
                }
+               rcu_read_unlock();
+               if (sta) {
+                       synchronize_rcu();
+                       sta_info_destroy(sta);
+               }
                break;
        case IEEE80211_IF_TYPE_MESH_POINT:
        case IEEE80211_IF_TYPE_STA:
        }
 
        /* remove all STAs that are bound to this virtual interface */
-       sta_info_flush(local, dev);
+       sta_info_flush(local, sdata);
 
        memset(&sdata->u, 0, sizeof(sdata->u));
        ieee80211_if_sdata_init(sdata);
 
                                    size_t key_len)
 {
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
-       int ret;
-       struct sta_info *sta = NULL;
+       struct sta_info *sta;
        struct ieee80211_key *key;
        struct ieee80211_sub_if_data *sdata;
 
                        key = sdata->keys[idx];
                } else {
                        sta = sta_info_get(local, sta_addr);
-                       if (!sta) {
-                               ret = -ENOENT;
-                               key = NULL;
-                               goto err_out;
-                       }
-
+                       if (!sta)
+                               return -ENOENT;
                        key = sta->key;
                }
 
                if (!key)
-                       ret = -ENOENT;
-               else
-                       ret = 0;
+                       return -ENOENT;
+
+               ieee80211_key_free(key);
+               return 0;
        } else {
                key = ieee80211_key_alloc(alg, idx, key_len, _key);
                if (!key)
                        return -ENOMEM;
 
+               sta = NULL;
+
                if (!is_broadcast_ether_addr(sta_addr)) {
                        set_tx_key = 0;
                        /*
                         * work around this.
                         */
                        if (idx != 0 && alg != ALG_WEP) {
-                               ret = -EINVAL;
-                               goto err_out;
+                               ieee80211_key_free(key);
+                               return -EINVAL;
                        }
 
                        sta = sta_info_get(local, sta_addr);
                        if (!sta) {
-                               ret = -ENOENT;
-                               goto err_out;
+                               ieee80211_key_free(key);
+                               return -ENOENT;
                        }
                }
 
 
                if (set_tx_key || (!sta && !sdata->default_key && key))
                        ieee80211_set_default_key(sdata, idx);
-
-               /* don't free key later */
-               key = NULL;
-
-               ret = 0;
        }
 
- err_out:
-       if (sta)
-               sta_info_put(sta);
-       ieee80211_key_free(key);
-       return ret;
+       return 0;
 }
 
 static int ieee80211_ioctl_siwgenie(struct net_device *dev,
        else
                rate->value = 0;
        rate->value *= 100000;
-       sta_info_put(sta);
+
        return 0;
 }
 
                wstats->qual.qual = sta->last_signal;
                wstats->qual.noise = sta->last_noise;
                wstats->qual.updated = local->wstats_flags;
-               sta_info_put(sta);
        }
        return wstats;
 }
 
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
        struct rate_control_ref *ref = local->rate_ctrl;
        struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
-       struct sta_info *sta = sta_info_get(local, hdr->addr1);
+       struct sta_info *sta;
        int i;
 
+       rcu_read_lock();
+       sta = sta_info_get(local, hdr->addr1);
+
        memset(sel, 0, sizeof(struct rate_selection));
 
        ref->ops->get_rate(ref->priv, dev, sband, skb, sel);
                }
        }
 
-       if (sta)
-               sta_info_put(sta);
+       rcu_read_unlock();
 }
 
 struct rate_control_ref *rate_control_get(struct rate_control_ref *ref)
 
 #include <linux/netdevice.h>
 #include <linux/skbuff.h>
 #include <linux/types.h>
+#include <linux/kref.h>
 #include <net/mac80211.h>
 #include "ieee80211_i.h"
 #include "sta_info.h"
 
 #include <linux/wireless.h>
 #include <linux/random.h>
 #include <linux/etherdevice.h>
+#include <linux/rtnetlink.h>
 #include <net/iw_handler.h>
 #include <asm/types.h>
 
 
        ifsta->state = IEEE80211_ASSOCIATED;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, ifsta->bssid);
        if (!sta) {
                printk(KERN_DEBUG "%s: No STA entry for own AP %s\n",
                                       "range\n",
                                       dev->name, print_mac(mac, ifsta->bssid));
                                disassoc = 1;
-                               sta_info_free(sta);
+                               sta_info_unlink(&sta);
                        } else
                                ieee80211_send_probe_req(dev, ifsta->bssid,
                                                         local->scan_ssid,
                                                         ifsta->ssid_len);
                        }
                }
-               sta_info_put(sta);
        }
+
+       rcu_read_unlock();
+
+       if (disassoc && sta) {
+               synchronize_rcu();
+               rtnl_lock();
+               sta_info_destroy(sta);
+               rtnl_unlock();
+       }
+
        if (disassoc) {
                ifsta->state = IEEE80211_DISABLED;
                ieee80211_set_associated(dev, ifsta, 0);
        int ret = -EOPNOTSUPP;
        DECLARE_MAC_BUF(mac);
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, mgmt->sa);
-       if (!sta)
+       if (!sta) {
+               rcu_read_unlock();
                return;
+       }
 
        /* extract session parameters from addba request frame */
        dialog_token = mgmt->u.action.u.addba_req.dialog_token;
        spin_unlock_bh(&sta->ampdu_mlme.ampdu_rx);
 
 end_no_lock:
-       ieee80211_send_addba_resp(sta->dev, sta->addr, tid, dialog_token,
-                               status, 1, buf_size, timeout);
-       sta_info_put(sta);
+       ieee80211_send_addba_resp(sta->sdata->dev, sta->addr, tid,
+                                 dialog_token, status, 1, buf_size, timeout);
+       rcu_read_unlock();
 }
 
 static void ieee80211_sta_process_addba_resp(struct net_device *dev,
        u16 tid;
        u8 *state;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, mgmt->sa);
-       if (!sta)
+       if (!sta) {
+               rcu_read_unlock();
                return;
+       }
 
        capab = le16_to_cpu(mgmt->u.action.u.addba_resp.capab);
        tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2;
 #ifdef CONFIG_MAC80211_HT_DEBUG
                printk(KERN_DEBUG "wrong addBA response token, tid %d\n", tid);
 #endif /* CONFIG_MAC80211_HT_DEBUG */
-               sta_info_put(sta);
+               rcu_read_unlock();
                return;
        }
 
                        spin_unlock_bh(&sta->ampdu_mlme.ampdu_tx);
                        printk(KERN_DEBUG "state not HT_ADDBA_REQUESTED_MSK:"
                                "%d\n", *state);
-                       sta_info_put(sta);
+                       rcu_read_unlock();
                        return;
                }
 
                ieee80211_stop_tx_ba_session(hw, sta->addr, tid,
                                             WLAN_BACK_INITIATOR);
        }
-       sta_info_put(sta);
+       rcu_read_unlock();
 }
 
 void ieee80211_send_delba(struct net_device *dev, const u8 *da, u16 tid,
        struct sta_info *sta;
        int ret, i;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, ra);
-       if (!sta)
+       if (!sta) {
+               rcu_read_unlock();
                return;
+       }
 
        /* check if TID is in operational state */
        spin_lock_bh(&sta->ampdu_mlme.ampdu_rx);
        if (sta->ampdu_mlme.tid_rx[tid].state
                                != HT_AGG_STATE_OPERATIONAL) {
                spin_unlock_bh(&sta->ampdu_mlme.ampdu_rx);
-               sta_info_put(sta);
+               rcu_read_unlock();
                return;
        }
        sta->ampdu_mlme.tid_rx[tid].state =
        kfree(sta->ampdu_mlme.tid_rx[tid].reorder_buf);
 
        sta->ampdu_mlme.tid_rx[tid].state = HT_AGG_STATE_IDLE;
-       sta_info_put(sta);
+       rcu_read_unlock();
 }
 
 
        u16 initiator;
        DECLARE_MAC_BUF(mac);
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, mgmt->sa);
-       if (!sta)
+       if (!sta) {
+               rcu_read_unlock();
                return;
+       }
 
        params = le16_to_cpu(mgmt->u.action.u.delba.params);
        tid = (params & IEEE80211_DELBA_PARAM_TID_MASK) >> 12;
                ieee80211_stop_tx_ba_session(&local->hw, sta->addr, tid,
                                             WLAN_BACK_RECIPIENT);
        }
-       sta_info_put(sta);
+       rcu_read_unlock();
 }
 
 /*
        struct sta_info *sta;
        u8 *state;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, temp_sta->addr);
-       if (!sta)
+       if (!sta) {
+               rcu_read_unlock();
                return;
+       }
 
        state = &sta->ampdu_mlme.tid_tx[tid].state;
        /* check if the TID waits for addBA response */
                                     WLAN_BACK_INITIATOR);
 
 timer_expired_exit:
-       sta_info_put(sta);
+       rcu_read_unlock();
 }
 
 /*
                                         timer_to_tid[0]);
 
        printk(KERN_DEBUG "rx session timer expired on tid %d\n", (u16)*ptid);
-       ieee80211_sta_stop_rx_ba_session(sta->dev, sta->addr, (u16)*ptid,
-                                        WLAN_BACK_TIMER,
+       ieee80211_sta_stop_rx_ba_session(sta->sdata->dev, sta->addr,
+                                        (u16)*ptid, WLAN_BACK_TIMER,
                                         WLAN_REASON_QSTA_TIMEOUT);
 }
 
        if (ifsta->assocresp_ies)
                memcpy(ifsta->assocresp_ies, pos, ifsta->assocresp_ies_len);
 
+       rcu_read_lock();
+
        /* Add STA entry for the AP */
        sta = sta_info_get(local, ifsta->bssid);
        if (!sta) {
                struct ieee80211_sta_bss *bss;
-               sta = sta_info_add(local, dev, ifsta->bssid, GFP_KERNEL);
+
+               sta = sta_info_add(sdata, ifsta->bssid);
                if (IS_ERR(sta)) {
                        printk(KERN_DEBUG "%s: failed to add STA entry for the"
                               " AP (error %ld)\n", dev->name, PTR_ERR(sta));
+                       rcu_read_unlock();
                        return;
                }
                bss = ieee80211_rx_bss_get(dev, ifsta->bssid,
                }
        }
 
-       sta->dev = dev;
        sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC | WLAN_STA_ASSOC_AP |
                      WLAN_STA_AUTHORIZED;
 
        bss_conf->aid = aid;
        ieee80211_set_associated(dev, ifsta, 1);
 
-       sta_info_put(sta);
+       rcu_read_unlock();
 
        ieee80211_associated(dev, ifsta);
 }
                                      mesh_peer_accepts_plinks(&elems, dev));
        }
 
+       rcu_read_lock();
+
        if (sdata->vif.type == IEEE80211_IF_TYPE_IBSS && elems.supp_rates &&
            memcmp(mgmt->bssid, sdata->u.sta.bssid, ETH_ALEN) == 0 &&
            (sta = sta_info_get(local, mgmt->sa))) {
                               (unsigned long long) supp_rates,
                               (unsigned long long) sta->supp_rates[rx_status->band]);
                }
-               sta_info_put(sta);
        }
 
+       rcu_read_unlock();
+
        if (elems.ds_params && elems.ds_params_len == 1)
                freq = ieee80211_channel_to_frequency(elems.ds_params[0]);
        else
                                       "local TSF - IBSS merge with BSSID %s\n",
                                       dev->name, print_mac(mac, mgmt->bssid));
                        ieee80211_sta_join_ibss(dev, &sdata->u.sta, bss);
+                       rcu_read_lock();
                        ieee80211_ibss_add_sta(dev, NULL,
                                               mgmt->bssid, mgmt->sa);
+                       rcu_read_unlock();
                }
        }
 
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
        int active = 0;
        struct sta_info *sta;
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 
-       read_lock_bh(&local->sta_lock);
-       list_for_each_entry(sta, &local->sta_list, list) {
-               if (sta->dev == dev &&
+       rcu_read_lock();
+
+       list_for_each_entry_rcu(sta, &local->sta_list, list) {
+               if (sta->sdata == sdata &&
                    time_after(sta->last_rx + IEEE80211_IBSS_MERGE_INTERVAL,
                               jiffies)) {
                        active++;
                        break;
                }
        }
-       read_unlock_bh(&local->sta_lock);
+
+       rcu_read_unlock();
 
        return active;
 }
        struct sta_info *sta, *tmp;
        LIST_HEAD(tmp_list);
        DECLARE_MAC_BUF(mac);
+       unsigned long flags;
 
-       write_lock_bh(&local->sta_lock);
+       spin_lock_irqsave(&local->sta_lock, flags);
        list_for_each_entry_safe(sta, tmp, &local->sta_list, list)
                if (time_after(jiffies, sta->last_rx + exp_time)) {
                        printk(KERN_DEBUG "%s: expiring inactive STA %s\n",
                               dev->name, print_mac(mac, sta->addr));
-                       __sta_info_get(sta);
-                       sta_info_remove(sta);
-                       list_add(&sta->list, &tmp_list);
+                       sta_info_unlink(&sta);
+                       if (sta)
+                               list_add(&sta->list, &tmp_list);
                }
-       write_unlock_bh(&local->sta_lock);
+       spin_unlock_irqrestore(&local->sta_lock, flags);
 
-       list_for_each_entry_safe(sta, tmp, &tmp_list, list) {
-               sta_info_free(sta);
-               sta_info_put(sta);
-       }
+       synchronize_rcu();
+
+       rtnl_lock();
+       list_for_each_entry_safe(sta, tmp, &tmp_list, list)
+               sta_info_destroy(sta);
+       rtnl_unlock();
 }
 
 
 }
 
 
+/* must be called under RCU read lock */
 struct sta_info * ieee80211_ibss_add_sta(struct net_device *dev,
                                         struct sk_buff *skb, u8 *bssid,
                                         u8 *addr)
        printk(KERN_DEBUG "%s: Adding new IBSS station %s (dev=%s)\n",
               wiphy_name(local->hw.wiphy), print_mac(mac, addr), dev->name);
 
-       sta = sta_info_add(local, dev, addr, GFP_ATOMIC);
+       sta = sta_info_add(sdata, addr);
        if (IS_ERR(sta))
                return NULL;
 
 
        rate_control_rate_init(sta, local);
 
-       return sta; /* caller will call sta_info_put() */
+       return sta;
 }
 
 
 
                if (sdata->vif.type == IEEE80211_IF_TYPE_STA) {
                        struct sta_info *ap;
 
+                       rcu_read_lock();
+
                        /* same here, the AP could be using QoS */
                        ap = sta_info_get(key->local, key->sdata->u.sta.bssid);
                        if (ap) {
                                if (ap->flags & WLAN_STA_WME)
                                        key->conf.flags |=
                                                IEEE80211_KEY_FLAG_WMM_STA;
-                               sta_info_put(ap);
                        }
+
+                       rcu_read_unlock();
                }
        }
 
                        __ieee80211_key_replace(key->sdata, key->sta,
                                                key, NULL);
 
+               /*
+                * Do NOT remove this without looking at sta_info_destroy()
+                */
                synchronize_rcu();
 
                /*
 
 /**
  * mesh_accept_plinks_update: update accepting_plink in local mesh beacons
  *
- * @dev: mesh interface in which mesh beacons are going to be updated
+ * @sdata: mesh interface in which mesh beacons are going to be updated
  */
-void mesh_accept_plinks_update(struct net_device *dev)
+void mesh_accept_plinks_update(struct ieee80211_sub_if_data *sdata)
 {
-       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        bool free_plinks;
 
        /* In case mesh_plink_free_count > 0 and mesh_plinktbl_capacity == 0,
 
  * @state_lock: mesh pat state lock
  *
  *
- * The combination of dst and dev is unique in the mesh path table. A reference
- * to the next_hop sta will be kept and in case this sta is removed, the
- * mesh_path structure must be also removed or substitued in a rcu safe way
+ * The combination of dst and dev is unique in the mesh path table. Since the
+ * next_hop STA is only protected by RCU as well, deleting the STA must also
+ * remove/substitute the mesh_path structure and wait until that is no longer
+ * reachable before destroying the STA completely.
  */
 struct mesh_path {
        u8 dst[ETH_ALEN];
                bool add);
 bool mesh_peer_accepts_plinks(struct ieee802_11_elems *ie,
                              struct net_device *dev);
-void mesh_accept_plinks_update(struct net_device *dev);
-struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, struct net_device *dev);
+void mesh_accept_plinks_update(struct ieee80211_sub_if_data *sdata);
+struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates,
+                               struct ieee80211_sub_if_data *sdata);
 void mesh_plink_broken(struct sta_info *sta);
 void mesh_plink_deactivate(struct sta_info *sta);
 int mesh_plink_open(struct sta_info *sta);
 void mesh_path_tx_pending(struct mesh_path *mpath);
 int mesh_pathtbl_init(void);
 void mesh_pathtbl_unregister(void);
-int mesh_path_del(u8 *addr, struct net_device *dev);
+int mesh_path_del(u8 *addr, struct net_device *dev, bool force);
 void mesh_path_timer(unsigned long data);
 void mesh_path_flush_by_nexthop(struct sta_info *sta);
 void mesh_path_discard_frame(struct sk_buff *skb, struct net_device *dev);
 
 static inline bool mesh_plink_availables(struct ieee80211_sub_if_data *sdata)
 {
-       return (min(mesh_plink_free_count(sdata),
+       return (min_t(long, mesh_plink_free_count(sdata),
                   MESH_MAX_PLINKS - sdata->local->num_sta)) > 0;
 }
 
 
                orig_metric = PREP_IE_METRIC(hwmp_ie);
                break;
        default:
-               sta_info_put(sta);
                rcu_read_unlock();
                return 0;
        }
                        mpath = mesh_path_lookup(orig_addr, dev);
                        if (!mpath) {
                                rcu_read_unlock();
-                               sta_info_put(sta);
                                return 0;
                        }
                        spin_lock_bh(&mpath->state_lock);
                        mpath = mesh_path_lookup(ta, dev);
                        if (!mpath) {
                                rcu_read_unlock();
-                               sta_info_put(sta);
                                return 0;
                        }
                        spin_lock_bh(&mpath->state_lock);
                        spin_unlock_bh(&mpath->state_lock);
        }
 
-       sta_info_put(sta);
        rcu_read_unlock();
 
        return process ? new_metric : 0;
 endmpathtimer:
        rcu_read_unlock();
        if (delete)
-               mesh_path_del(mpath->dst, mpath->dev);
+               mesh_path_del(mpath->dst, mpath->dev, false);
 }
 
  */
 void mesh_path_assign_nexthop(struct mesh_path *mpath, struct sta_info *sta)
 {
-       __sta_info_get(sta);
-       if (mpath->next_hop)
-               sta_info_put(mpath->next_hop);
-       mpath->next_hop = sta;
+       rcu_assign_pointer(mpath->next_hop, sta);
 }
 
 
        struct mesh_path *mpath;
        struct mpath_node *node;
        struct hlist_node *p;
-       struct net_device *dev = sta->dev;
+       struct net_device *dev = sta->sdata->dev;
        int i;
 
        rcu_read_lock();
  *
  * RCU notes: this function is called when a mesh plink transitions from ESTAB
  * to any other state, since ESTAB state is the only one that allows path
- * creation. This will happen before the sta can be freed (since we hold
- * a reference to it) so any reader in a rcu read block will be protected
- * against the plink dissapearing.
+ * creation. This will happen before the sta can be freed (because
+ * sta_info_destroy() calls this) so any reader in a rcu read block will be
+ * protected against the plink disappearing.
  */
 void mesh_path_flush_by_nexthop(struct sta_info *sta)
 {
        for_each_mesh_entry(mesh_paths, p, node, i) {
                mpath = node->mpath;
                if (mpath->next_hop == sta)
-                       mesh_path_del(mpath->dst, mpath->dev);
+                       mesh_path_del(mpath->dst, mpath->dev, true);
        }
 }
 
        for_each_mesh_entry(mesh_paths, p, node, i) {
                mpath = node->mpath;
                if (mpath->dev == dev)
-                       mesh_path_del(mpath->dst, mpath->dev);
+                       mesh_path_del(mpath->dst, mpath->dev, false);
        }
 }
 
        struct mpath_node *node = container_of(rp, struct mpath_node, rcu);
        struct ieee80211_sub_if_data *sdata =
                IEEE80211_DEV_TO_SUB_IF(node->mpath->dev);
-       if (node->mpath->next_hop)
-               sta_info_put(node->mpath->next_hop);
+
+       rcu_assign_pointer(node->mpath->next_hop, NULL);
        atomic_dec(&sdata->u.sta.mpaths);
        kfree(node->mpath);
        kfree(node);
  * Returns: 0 if succesful
  *
  * State: if the path is being resolved, the deletion will be postponed until
- * the path resolution completes or times out.
+ * the path resolution completes or times out, unless the force parameter
+ * is given.
  */
-int mesh_path_del(u8 *addr, struct net_device *dev)
+int mesh_path_del(u8 *addr, struct net_device *dev, bool force)
 {
        struct mesh_path *mpath;
        struct mpath_node *node;
                if (mpath->dev == dev &&
                                memcmp(addr, mpath->dst, ETH_ALEN) == 0) {
                        spin_lock_bh(&mpath->state_lock);
-                       if (mpath->flags & MESH_PATH_RESOLVING) {
+                       if (!force && mpath->flags & MESH_PATH_RESOLVING) {
                                mpath->flags |= MESH_PATH_DELETE;
                        } else {
                                mpath->flags |= MESH_PATH_RESOLVING;
                        time_after(jiffies,
                         mpath->exp_time + MESH_PATH_EXPIRE)) {
                        spin_unlock_bh(&mpath->state_lock);
-                       mesh_path_del(mpath->dst, mpath->dev);
+                       mesh_path_del(mpath->dst, mpath->dev, false);
                } else
                        spin_unlock_bh(&mpath->state_lock);
        }
 
 void mesh_plink_inc_estab_count(struct ieee80211_sub_if_data *sdata)
 {
        atomic_inc(&sdata->u.sta.mshstats.estab_plinks);
-       mesh_accept_plinks_update(sdata->dev);
+       mesh_accept_plinks_update(sdata);
 }
 
 static inline
 void mesh_plink_dec_estab_count(struct ieee80211_sub_if_data *sdata)
 {
        atomic_dec(&sdata->u.sta.mshstats.estab_plinks);
-       mesh_accept_plinks_update(sdata->dev);
+       mesh_accept_plinks_update(sdata);
 }
 
 /**
  *
  * Returns: non-NULL on success, ERR_PTR() on error.
  */
-struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, struct net_device *dev)
+struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates,
+                               struct ieee80211_sub_if_data *sdata)
 {
-       struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
+       struct ieee80211_local *local = sdata->local;
        struct sta_info *sta;
 
-       if (memcmp(hw_addr, dev->dev_addr, ETH_ALEN) == 0)
+       if (compare_ether_addr(hw_addr, sdata->dev->dev_addr) == 0)
                /* never add ourselves as neighbours */
                return ERR_PTR(-EINVAL);
 
        if (local->num_sta >= MESH_MAX_PLINKS)
                return ERR_PTR(-ENOSPC);
 
-       sta = sta_info_add(local, dev, hw_addr, GFP_KERNEL);
+       sta = sta_info_add(sdata, hw_addr);
        if (IS_ERR(sta))
                return sta;
 
        sta->supp_rates[local->hw.conf.channel->band] = rates;
        rate_control_rate_init(sta, local);
 
-       mesh_accept_plinks_update(dev);
+       mesh_accept_plinks_update(sdata);
 
        return sta;
 }
  */
 static void __mesh_plink_deactivate(struct sta_info *sta)
 {
-       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
+
        if (sta->plink_state == ESTAB)
                mesh_plink_dec_estab_count(sdata);
        sta->plink_state = BLOCKED;
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
        struct sta_info *sta;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, hw_addr);
        if (!sta) {
-               sta = mesh_plink_add(hw_addr, rates, dev);
-               if (IS_ERR(sta))
+               sta = mesh_plink_add(hw_addr, rates, sdata);
+               if (IS_ERR(sta)) {
+                       rcu_read_unlock();
                        return;
+               }
        }
 
        sta->last_rx = jiffies;
                        sdata->u.sta.mshcfg.auto_open_plinks)
                mesh_plink_open(sta);
 
-       sta_info_put(sta);
+       rcu_read_unlock();
 }
 
 static void mesh_plink_timer(unsigned long data)
        DECLARE_MAC_BUF(mac);
 #endif
 
+       /*
+        * This STA is valid because sta_info_destroy() will
+        * del_timer_sync() this timer after having made sure
+        * it cannot be readded (by deleting the plink.)
+        */
        sta = (struct sta_info *) data;
 
        spin_lock_bh(&sta->plink_lock);
        reason = 0;
        llid = sta->llid;
        plid = sta->plid;
-       dev = sta->dev;
-       sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       sdata = sta->sdata;
+       dev = sdata->dev;
 
        switch (sta->plink_state) {
        case OPN_RCVD:
                        sta->plink_timeout = sta->plink_timeout +
                                             rand % sta->plink_timeout;
                        ++sta->plink_retries;
-                       if (!mod_plink_timer(sta, sta->plink_timeout))
-                               __sta_info_get(sta);
+                       mod_plink_timer(sta, sta->plink_timeout);
                        spin_unlock_bh(&sta->plink_lock);
                        mesh_plink_frame_tx(dev, PLINK_OPEN, sta->addr, llid,
                                            0, 0);
                if (!reason)
                        reason = cpu_to_le16(MESH_CONFIRM_TIMEOUT);
                sta->plink_state = HOLDING;
-               if (!mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata)))
-                       __sta_info_get(sta);
+               mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata));
                spin_unlock_bh(&sta->plink_lock);
                mesh_plink_frame_tx(dev, PLINK_CLOSE, sta->addr, llid, plid,
                                    reason);
                break;
        case HOLDING:
                /* holding timer */
-               if (del_timer(&sta->plink_timer))
-                       sta_info_put(sta);
+               del_timer(&sta->plink_timer);
                mesh_plink_fsm_restart(sta);
                spin_unlock_bh(&sta->plink_lock);
                break;
                spin_unlock_bh(&sta->plink_lock);
                break;
        }
-
-       sta_info_put(sta);
 }
 
 static inline void mesh_plink_timer_set(struct sta_info *sta, int timeout)
        sta->plink_timer.data = (unsigned long) sta;
        sta->plink_timer.function = mesh_plink_timer;
        sta->plink_timeout = timeout;
-       __sta_info_get(sta);
        add_timer(&sta->plink_timer);
 }
 
 int mesh_plink_open(struct sta_info *sta)
 {
        __le16 llid;
-       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
 #ifdef CONFIG_MAC80211_VERBOSE_MPL_DEBUG
        DECLARE_MAC_BUF(mac);
 #endif
        sta->llid = llid;
        if (sta->plink_state != LISTEN) {
                spin_unlock_bh(&sta->plink_lock);
-               sta_info_put(sta);
                return -EBUSY;
        }
        sta->plink_state = OPN_SNT;
        mpl_dbg("Mesh plink: starting establishment with %s\n",
                print_mac(mac, sta->addr));
 
-       return mesh_plink_frame_tx(sta->dev, PLINK_OPEN, sta->addr, llid, 0, 0);
+       return mesh_plink_frame_tx(sdata->dev, PLINK_OPEN,
+                                  sta->addr, llid, 0, 0);
 }
 
 void mesh_plink_block(struct sta_info *sta)
 
 int mesh_plink_close(struct sta_info *sta)
 {
-       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
        int llid, plid, reason;
 #ifdef CONFIG_MAC80211_VERBOSE_MPL_DEBUG
        DECLARE_MAC_BUF(mac);
        if (sta->plink_state == LISTEN || sta->plink_state == BLOCKED) {
                mesh_plink_fsm_restart(sta);
                spin_unlock_bh(&sta->plink_lock);
-               sta_info_put(sta);
                return 0;
        } else if (sta->plink_state == ESTAB) {
                __mesh_plink_deactivate(sta);
                /* The timer should not be running */
-               if (!mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata)))
-                       __sta_info_get(sta);
+               mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata));
        } else if (!mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata)))
                sta->ignore_plink_timer = true;
 
        llid = sta->llid;
        plid = sta->plid;
        spin_unlock_bh(&sta->plink_lock);
-       mesh_plink_frame_tx(sta->dev, PLINK_CLOSE, sta->addr, llid, plid,
-                           reason);
+       mesh_plink_frame_tx(sta->sdata->dev, PLINK_CLOSE, sta->addr, llid,
+                           plid, reason);
        return 0;
 }
 
 void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt,
                         size_t len, struct ieee80211_rx_status *rx_status)
 {
-       struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_local *local = sdata->local;
        struct ieee802_11_elems elems;
        struct sta_info *sta;
        enum plink_event event;
 #ifdef CONFIG_MAC80211_VERBOSE_MPL_DEBUG
        DECLARE_MAC_BUF(mac);
 #endif
-       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 
        if (is_multicast_ether_addr(mgmt->da)) {
                mpl_dbg("Mesh plink: ignore frame from multicast address");
        if (ftype == PLINK_CONFIRM || (ftype == PLINK_CLOSE && ie_len == 7))
                memcpy(&llid, PLINK_GET_PLID(elems.peer_link), 2);
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, mgmt->sa);
        if (!sta && ftype != PLINK_OPEN) {
                mpl_dbg("Mesh plink: cls or cnf from unknown peer\n");
+               rcu_read_unlock();
                return;
        }
 
        if (sta && sta->plink_state == BLOCKED) {
-               sta_info_put(sta);
+               rcu_read_unlock();
                return;
        }
 
                u64 rates;
                if (!mesh_plink_free_count(sdata)) {
                        mpl_dbg("Mesh plink error: no more free plinks\n");
+                       rcu_read_unlock();
                        return;
                }
 
                rates = ieee80211_sta_get_rates(local, &elems, rx_status->band);
-               sta = mesh_plink_add(mgmt->sa, rates, dev);
+               sta = mesh_plink_add(mgmt->sa, rates, sdata);
                if (IS_ERR(sta)) {
                        mpl_dbg("Mesh plink error: plink table full\n");
+                       rcu_read_unlock();
                        return;
                }
                event = OPN_ACPT;
                switch (ftype) {
                case PLINK_OPEN:
                        if (!mesh_plink_free_count(sdata) ||
-                                       (sta->plid && sta->plid != plid))
+                           (sta->plid && sta->plid != plid))
                                event = OPN_IGNR;
                        else
                                event = OPN_ACPT;
                        break;
                case PLINK_CONFIRM:
                        if (!mesh_plink_free_count(sdata) ||
-                               (sta->llid != llid || sta->plid != plid))
+                           (sta->llid != llid || sta->plid != plid))
                                event = CNF_IGNR;
                        else
                                event = CNF_ACPT;
                default:
                        mpl_dbg("Mesh plink: unknown frame subtype\n");
                        spin_unlock_bh(&sta->plink_lock);
-                       sta_info_put(sta);
+                       rcu_read_unlock();
                        return;
                }
        }
                                            plid, 0);
                        break;
                case CNF_ACPT:
-                       if (del_timer(&sta->plink_timer))
-                               sta_info_put(sta);
+                       del_timer(&sta->plink_timer);
                        sta->plink_state = ESTAB;
                        mesh_plink_inc_estab_count(sdata);
                        spin_unlock_bh(&sta->plink_lock);
                                            plid, reason);
                        break;
                case OPN_ACPT:
-                       if (del_timer(&sta->plink_timer))
-                               sta_info_put(sta);
+                       del_timer(&sta->plink_timer);
                        sta->plink_state = ESTAB;
                        mesh_plink_inc_estab_count(sdata);
                        spin_unlock_bh(&sta->plink_lock);
                        __mesh_plink_deactivate(sta);
                        sta->plink_state = HOLDING;
                        llid = sta->llid;
-                       if (!mod_plink_timer(sta,
-                                       dot11MeshHoldingTimeout(sdata)))
-                               __sta_info_get(sta);
+                       mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata));
                        spin_unlock_bh(&sta->plink_lock);
                        mesh_plink_frame_tx(dev, PLINK_CLOSE, sta->addr, llid,
                                            plid, reason);
        case HOLDING:
                switch (event) {
                case CLS_ACPT:
-                       if (del_timer(&sta->plink_timer)) {
+                       if (del_timer(&sta->plink_timer))
                                sta->ignore_plink_timer = 1;
-                               sta_info_put(sta);
-                       }
                        mesh_plink_fsm_restart(sta);
                        spin_unlock_bh(&sta->plink_lock);
                        break;
                spin_unlock_bh(&sta->plink_lock);
                break;
        }
-       sta_info_put(sta);
+
+       rcu_read_unlock();
 }
 
        int cur_sorted, new_sorted, probe, tmp, n_bitrates, band;
        int cur = sta->txrate_idx;
 
-       sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       sdata = sta->sdata;
        sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
        band = sband->band;
        n_bitrates = sband->n_bitrates;
                                    struct sta_info *sta)
 {
 #ifdef CONFIG_MAC80211_MESH
-       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
 #endif
        struct rc_pid_sta_info *spinfo = sta->rate_ctrl_priv;
        struct rc_pid_rateinfo *rinfo = pinfo->rinfo;
        unsigned long period;
        struct ieee80211_supported_band *sband;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, hdr->addr1);
        sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
 
        if (!sta)
-               return;
+               goto unlock;
 
        /* Don't update the state if we're not controlling the rate. */
-       sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       sdata = sta->sdata;
        if (sdata->bss && sdata->bss->force_unicast_rateidx > -1) {
                sta->txrate_idx = sdata->bss->max_ratectrl_rateidx;
-               return;
+               goto unlock;
        }
 
        /* Ignore all frames that were sent with a different rate than the rate
         * we currently advise mac80211 to use. */
        if (status->control.tx_rate != &sband->bitrates[sta->txrate_idx])
-               goto ignore;
+               goto unlock;
 
        spinfo = sta->rate_ctrl_priv;
        spinfo->tx_num_xmit++;
        if (time_after(jiffies, spinfo->last_sample + period))
                rate_control_pid_sample(pinfo, local, sta);
 
-ignore:
-       sta_info_put(sta);
+ unlock:
+       rcu_read_unlock();
 }
 
 static void rate_control_pid_get_rate(void *priv, struct net_device *dev,
        int rateidx;
        u16 fc;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, hdr->addr1);
 
        /* Send management frames and broadcast/multicast data using lowest
        if ((fc & IEEE80211_FCTL_FTYPE) != IEEE80211_FTYPE_DATA ||
            is_multicast_ether_addr(hdr->addr1) || !sta) {
                sel->rate = rate_lowest(local, sband, sta);
-               if (sta)
-                       sta_info_put(sta);
+               rcu_read_unlock();
                return;
        }
 
 
        sta->last_txrate_idx = rateidx;
 
-       sta_info_put(sta);
+       rcu_read_unlock();
 
        sel->rate = &sband->bitrates[rateidx];
 
 
        int i = sta->txrate_idx;
        int maxrate;
 
-       sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       sdata = sta->sdata;
        if (sdata->bss && sdata->bss->force_unicast_rateidx > -1) {
                /* forced unicast rate - do not change STA rate */
                return;
        struct ieee80211_supported_band *sband;
        int i = sta->txrate_idx;
 
-       sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       sdata = sta->sdata;
        if (sdata->bss && sdata->bss->force_unicast_rateidx > -1) {
                /* forced unicast rate - do not change STA rate */
                return;
        struct sta_info *sta;
        struct sta_rate_control *srctrl;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, hdr->addr1);
 
        if (!sta)
-           return;
+               goto unlock;
 
        srctrl = sta->rate_ctrl_priv;
        srctrl->tx_num_xmit++;
                }
        }
 
-       sta_info_put(sta);
+ unlock:
+       rcu_read_unlock();
 }
 
 
        int rateidx;
        u16 fc;
 
+       rcu_read_lock();
+
        sta = sta_info_get(local, hdr->addr1);
 
        /* Send management frames and broadcast/multicast data using lowest
        if ((fc & IEEE80211_FCTL_FTYPE) != IEEE80211_FTYPE_DATA ||
            is_multicast_ether_addr(hdr->addr1) || !sta) {
                sel->rate = rate_lowest(local, sband, sta);
-               if (sta)
-                       sta_info_put(sta);
+               rcu_read_unlock();
                return;
        }
 
 
        sta->last_txrate_idx = rateidx;
 
-       sta_info_put(sta);
+       rcu_read_unlock();
 
        sel->rate = &sband->bitrates[rateidx];
 }
 
        struct ieee80211_sub_if_data *sdata;
        DECLARE_MAC_BUF(mac);
 
-       sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       sdata = sta->sdata;
 
        if (sdata->bss)
                atomic_inc(&sdata->bss->num_sta_ps);
        struct ieee80211_tx_packet_data *pkt_data;
        DECLARE_MAC_BUF(mac);
 
-       sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       sdata = sta->sdata;
 
        if (sdata->bss)
                atomic_dec(&sdata->bss->num_sta_ps);
                                       "multicast frame\n", dev->name);
                } else {
                        dsta = sta_info_get(local, skb->data);
-                       if (dsta && dsta->dev == dev) {
+                       if (dsta && dsta->sdata->dev == dev) {
                                /*
                                 * The destination station is associated to
                                 * this AP (in this VLAN), so send the frame
                                xmit_skb = skb;
                                skb = NULL;
                        }
-                       if (dsta)
-                               sta_info_put(dsta);
                }
        }
 
 
        rx.sta = sta_info_get(local, hdr->addr2);
        if (rx.sta) {
-               rx.dev = rx.sta->dev;
-               rx.sdata = IEEE80211_DEV_TO_SUB_IF(rx.dev);
+               rx.sdata = rx.sta->sdata;
+               rx.dev = rx.sta->sdata->dev;
        }
 
        if ((status->flag & RX_FLAG_MMIC_ERROR)) {
                ieee80211_rx_michael_mic_report(local->mdev, hdr, &rx);
-               goto end;
+               return;
        }
 
        if (unlikely(local->sta_sw_scanning || local->sta_hw_scanning))
                ieee80211_invoke_rx_handlers(prev, &rx, skb);
        } else
                dev_kfree_skb(skb);
-
- end:
-       if (rx.sta)
-               sta_info_put(rx.sta);
 }
 
 #define SEQ_MODULO 0x1000
        /* if this mpdu is fragmented - terminate rx aggregation session */
        sc = le16_to_cpu(hdr->seq_ctrl);
        if (sc & IEEE80211_SCTL_FRAG) {
-               ieee80211_sta_stop_rx_ba_session(sta->dev, sta->addr,
+               ieee80211_sta_stop_rx_ba_session(sta->sdata->dev, sta->addr,
                        tid, 0, WLAN_REASON_QSTA_REQUIRE_SETUP);
                ret = 1;
                goto end_reorder;
        mpdu_seq_num = (sc & IEEE80211_SCTL_SEQ) >> 4;
        ret = ieee80211_sta_manage_reorder_buf(hw, tid_agg_rx, skb,
                                                mpdu_seq_num, 0);
-end_reorder:
-       if (sta)
-               sta_info_put(sta);
+ end_reorder:
        return ret;
 }
 
 
 #include <linux/skbuff.h>
 #include <linux/if_arp.h>
 #include <linux/timer.h>
+#include <linux/rtnetlink.h>
 
 #include <net/mac80211.h>
 #include "ieee80211_i.h"
 #include "debugfs_sta.h"
 #include "mesh.h"
 
-/* Caller must hold local->sta_lock */
-static void sta_info_hash_add(struct ieee80211_local *local,
-                             struct sta_info *sta)
-{
-       sta->hnext = local->sta_hash[STA_HASH(sta->addr)];
-       local->sta_hash[STA_HASH(sta->addr)] = sta;
-}
-
+/**
+ * DOC: STA information lifetime rules
+ *
+ * STA info structures (&struct sta_info) are managed in a hash table
+ * for faster lookup and a list for iteration. They are managed using
+ * RCU, i.e. access to the list and hash table is protected by RCU.
+ *
+ * STA info structures are always "alive" when they are added with
+ * @sta_info_add() [this may be changed in the future to allow allocating
+ * outside of a critical section!], they are then added to the hash
+ * table and list. Therefore, @sta_info_add() must also be RCU protected,
+ * also, the caller of @sta_info_add() cannot assume that it owns the
+ * structure.
+ *
+ * Because there are debugfs entries for each station, and adding those
+ * must be able to sleep, it is also possible to "pin" a station entry,
+ * that means it can be removed from the hash table but not be freed.
+ * See the comment in @__sta_info_unlink() for more information.
+ *
+ * In order to remove a STA info structure, the caller needs to first
+ * unlink it (@sta_info_unlink()) from the list and hash tables and
+ * then wait for an RCU synchronisation before it can be freed. Due to
+ * the pinning and the possibility of multiple callers trying to remove
+ * the same STA info at the same time, @sta_info_unlink() can clear the
+ * STA info pointer it is passed to indicate that the STA info is owned
+ * by somebody else now.
+ *
+ * If @sta_info_unlink() did not clear the pointer then the caller owns
+ * the STA info structure now and is responsible of destroying it with
+ * a call to @sta_info_destroy(), not before RCU synchronisation, of
+ * course. Note that sta_info_destroy() must be protected by the RTNL.
+ *
+ * In all other cases, there is no concept of ownership on a STA entry,
+ * each structure is owned by the global hash table/list until it is
+ * removed. All users of the structure need to be RCU protected so that
+ * the structure won't be freed before they are done using it.
+ */
 
 /* Caller must hold local->sta_lock */
 static int sta_info_hash_del(struct ieee80211_local *local,
        if (!s)
                return -ENOENT;
        if (s == sta) {
-               local->sta_hash[STA_HASH(sta->addr)] = s->hnext;
+               rcu_assign_pointer(local->sta_hash[STA_HASH(sta->addr)],
+                                  s->hnext);
                return 0;
        }
 
        while (s->hnext && s->hnext != sta)
                s = s->hnext;
        if (s->hnext) {
-               s->hnext = sta->hnext;
+               rcu_assign_pointer(s->hnext, sta->hnext);
                return 0;
        }
 
        return -ENOENT;
 }
 
-/* must hold local->sta_lock */
+/* protected by RCU */
 static struct sta_info *__sta_info_find(struct ieee80211_local *local,
                                        u8 *addr)
 {
        struct sta_info *sta;
 
-       sta = local->sta_hash[STA_HASH(addr)];
+       sta = rcu_dereference(local->sta_hash[STA_HASH(addr)]);
        while (sta) {
                if (compare_ether_addr(sta->addr, addr) == 0)
                        break;
-               sta = sta->hnext;
+               sta = rcu_dereference(sta->hnext);
        }
        return sta;
 }
 
 struct sta_info *sta_info_get(struct ieee80211_local *local, u8 *addr)
 {
-       struct sta_info *sta;
-
-       read_lock_bh(&local->sta_lock);
-       sta = __sta_info_find(local, addr);
-       if (sta)
-               __sta_info_get(sta);
-       read_unlock_bh(&local->sta_lock);
-
-       return sta;
+       return __sta_info_find(local, addr);
 }
 EXPORT_SYMBOL(sta_info_get);
 
        struct sta_info *sta;
        int i = 0;
 
-       read_lock_bh(&local->sta_lock);
-       list_for_each_entry(sta, &local->sta_list, list) {
+       list_for_each_entry_rcu(sta, &local->sta_list, list) {
                if (i < idx) {
                        ++i;
                        continue;
-               } else if (!dev || dev == sta->dev) {
-                       __sta_info_get(sta);
-                       read_unlock_bh(&local->sta_lock);
+               } else if (!dev || dev == sta->sdata->dev) {
                        return sta;
                }
        }
-       read_unlock_bh(&local->sta_lock);
 
        return NULL;
 }
 
-static void sta_info_release(struct kref *kref)
+void sta_info_destroy(struct sta_info *sta)
 {
-       struct sta_info *sta = container_of(kref, struct sta_info, kref);
        struct ieee80211_local *local = sta->local;
        struct sk_buff *skb;
        int i;
 
-       /* free sta structure; it has already been removed from
-        * hash table etc. external structures. Make sure that all
-        * buffered frames are release (one might have been added
-        * after sta_info_free() was called). */
+       ASSERT_RTNL();
+       might_sleep();
+
+       rate_control_remove_sta_debugfs(sta);
+       ieee80211_sta_debugfs_remove(sta);
+
+#ifdef CONFIG_MAC80211_MESH
+       if (ieee80211_vif_is_mesh(&sta->sdata->vif))
+               mesh_plink_deactivate(sta);
+#endif
+
+       /*
+        * NOTE: This will call synchronize_rcu() internally to
+        * make sure no key references can be in use. We rely on
+        * that here for the mesh code!
+        */
+       ieee80211_key_free(sta->key);
+       WARN_ON(sta->key);
+
+#ifdef CONFIG_MAC80211_MESH
+       if (ieee80211_vif_is_mesh(&sta->sdata->vif))
+               del_timer_sync(&sta->plink_timer);
+#endif
+
        while ((skb = skb_dequeue(&sta->ps_tx_buf)) != NULL) {
                local->total_ps_buffered--;
                dev_kfree_skb_any(skb);
        }
-       while ((skb = skb_dequeue(&sta->tx_filtered)) != NULL) {
+
+       while ((skb = skb_dequeue(&sta->tx_filtered)) != NULL)
                dev_kfree_skb_any(skb);
-       }
+
        for (i = 0; i <  STA_TID_NUM; i++) {
                del_timer_sync(&sta->ampdu_mlme.tid_rx[i].session_timer);
                del_timer_sync(&sta->ampdu_mlme.tid_tx[i].addba_resp_timer);
        }
        rate_control_free_sta(sta->rate_ctrl, sta->rate_ctrl_priv);
        rate_control_put(sta->rate_ctrl);
+
        kfree(sta);
 }
 
 
-void sta_info_put(struct sta_info *sta)
+/* Caller must hold local->sta_lock */
+static void sta_info_hash_add(struct ieee80211_local *local,
+                             struct sta_info *sta)
 {
-       kref_put(&sta->kref, sta_info_release);
+       sta->hnext = local->sta_hash[STA_HASH(sta->addr)];
+       rcu_assign_pointer(local->sta_hash[STA_HASH(sta->addr)], sta);
 }
-EXPORT_SYMBOL(sta_info_put);
-
 
-struct sta_info *sta_info_add(struct ieee80211_local *local,
-                             struct net_device *dev, u8 *addr, gfp_t gfp)
+struct sta_info *sta_info_add(struct ieee80211_sub_if_data *sdata,
+                             u8 *addr)
 {
+       struct ieee80211_local *local = sdata->local;
        struct sta_info *sta;
        int i;
        DECLARE_MAC_BUF(mac);
+       unsigned long flags;
 
-       sta = kzalloc(sizeof(*sta), gfp);
+       sta = kzalloc(sizeof(*sta), GFP_ATOMIC);
        if (!sta)
                return ERR_PTR(-ENOMEM);
 
-       kref_init(&sta->kref);
+       memcpy(sta->addr, addr, ETH_ALEN);
+       sta->local = local;
+       sta->sdata = sdata;
 
        sta->rate_ctrl = rate_control_get(local->rate_ctrl);
-       sta->rate_ctrl_priv = rate_control_alloc_sta(sta->rate_ctrl, gfp);
+       sta->rate_ctrl_priv = rate_control_alloc_sta(sta->rate_ctrl,
+                                                    GFP_ATOMIC);
        if (!sta->rate_ctrl_priv) {
                rate_control_put(sta->rate_ctrl);
                kfree(sta);
                return ERR_PTR(-ENOMEM);
        }
 
-       memcpy(sta->addr, addr, ETH_ALEN);
-       sta->local = local;
-       sta->dev = dev;
        spin_lock_init(&sta->ampdu_mlme.ampdu_rx);
        spin_lock_init(&sta->ampdu_mlme.ampdu_tx);
        for (i = 0; i < STA_TID_NUM; i++) {
        }
        skb_queue_head_init(&sta->ps_tx_buf);
        skb_queue_head_init(&sta->tx_filtered);
-       write_lock_bh(&local->sta_lock);
-       /* mark sta as used (by caller) */
-       __sta_info_get(sta);
+       spin_lock_irqsave(&local->sta_lock, flags);
        /* check if STA exists already */
        if (__sta_info_find(local, addr)) {
-               write_unlock_bh(&local->sta_lock);
-               sta_info_put(sta);
+               spin_unlock_irqrestore(&local->sta_lock, flags);
                return ERR_PTR(-EEXIST);
        }
        list_add(&sta->list, &local->sta_list);
        local->num_sta++;
        sta_info_hash_add(local, sta);
-       if (local->ops->sta_notify) {
-               struct ieee80211_sub_if_data *sdata;
 
-               sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       /* notify driver */
+       if (local->ops->sta_notify) {
                if (sdata->vif.type == IEEE80211_IF_TYPE_VLAN)
                        sdata = sdata->u.vlan.ap;
 
                local->ops->sta_notify(local_to_hw(local), &sdata->vif,
                                       STA_NOTIFY_ADD, addr);
        }
-       write_unlock_bh(&local->sta_lock);
+
+       spin_unlock_irqrestore(&local->sta_lock, flags);
 
 #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
        printk(KERN_DEBUG "%s: Added STA %s\n",
 {
        if (bss)
                __bss_tim_set(bss, sta->aid);
-       if (sta->local->ops->set_tim)
+       if (sta->local->ops->set_tim) {
+               sta->local->tim_in_locked_section = true;
                sta->local->ops->set_tim(local_to_hw(sta->local), sta->aid, 1);
+               sta->local->tim_in_locked_section = false;
+       }
 }
 
 void sta_info_set_tim_bit(struct sta_info *sta)
 {
-       struct ieee80211_sub_if_data *sdata;
-
-       sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       unsigned long flags;
 
-       read_lock_bh(&sta->local->sta_lock);
-       __sta_info_set_tim_bit(sdata->bss, sta);
-       read_unlock_bh(&sta->local->sta_lock);
+       spin_lock_irqsave(&sta->local->sta_lock, flags);
+       __sta_info_set_tim_bit(sta->sdata->bss, sta);
+       spin_unlock_irqrestore(&sta->local->sta_lock, flags);
 }
 
 static void __sta_info_clear_tim_bit(struct ieee80211_if_ap *bss,
 {
        if (bss)
                __bss_tim_clear(bss, sta->aid);
-       if (sta->local->ops->set_tim)
+       if (sta->local->ops->set_tim) {
+               sta->local->tim_in_locked_section = true;
                sta->local->ops->set_tim(local_to_hw(sta->local), sta->aid, 0);
+               sta->local->tim_in_locked_section = false;
+       }
 }
 
 void sta_info_clear_tim_bit(struct sta_info *sta)
 {
-       struct ieee80211_sub_if_data *sdata;
-
-       sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+       unsigned long flags;
 
-       read_lock_bh(&sta->local->sta_lock);
-       __sta_info_clear_tim_bit(sdata->bss, sta);
-       read_unlock_bh(&sta->local->sta_lock);
+       spin_lock_irqsave(&sta->local->sta_lock, flags);
+       __sta_info_clear_tim_bit(sta->sdata->bss, sta);
+       spin_unlock_irqrestore(&sta->local->sta_lock, flags);
 }
 
-/* Caller must hold local->sta_lock */
-void sta_info_remove(struct sta_info *sta)
+/*
+ * See comment in __sta_info_unlink,
+ * caller must hold local->sta_lock.
+ */
+static void __sta_info_pin(struct sta_info *sta)
 {
-       struct ieee80211_local *local = sta->local;
-       struct ieee80211_sub_if_data *sdata;
+       WARN_ON(sta->pin_status != STA_INFO_PIN_STAT_NORMAL);
+       sta->pin_status = STA_INFO_PIN_STAT_PINNED;
+}
 
-       /* don't do anything if we've been removed already */
-       if (sta_info_hash_del(local, sta))
-               return;
+/*
+ * See comment in __sta_info_unlink, returns sta if it
+ * needs to be destroyed.
+ */
+static struct sta_info *__sta_info_unpin(struct sta_info *sta)
+{
+       struct sta_info *ret = NULL;
+       unsigned long flags;
 
-       list_del(&sta->list);
-       sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
-       if (sta->flags & WLAN_STA_PS) {
-               sta->flags &= ~WLAN_STA_PS;
-               if (sdata->bss)
-                       atomic_dec(&sdata->bss->num_sta_ps);
-               __sta_info_clear_tim_bit(sdata->bss, sta);
-       }
-       local->num_sta--;
+       spin_lock_irqsave(&sta->local->sta_lock, flags);
+       WARN_ON(sta->pin_status != STA_INFO_PIN_STAT_DESTROY &&
+               sta->pin_status != STA_INFO_PIN_STAT_PINNED);
+       if (sta->pin_status == STA_INFO_PIN_STAT_DESTROY)
+               ret = sta;
+       sta->pin_status = STA_INFO_PIN_STAT_NORMAL;
+       spin_unlock_irqrestore(&sta->local->sta_lock, flags);
 
-       if (ieee80211_vif_is_mesh(&sdata->vif))
-               mesh_accept_plinks_update(sdata->dev);
+       return ret;
 }
 
-void sta_info_free(struct sta_info *sta)
+static void __sta_info_unlink(struct sta_info **sta)
 {
-       struct sk_buff *skb;
-       struct ieee80211_local *local = sta->local;
-       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
-
-       DECLARE_MAC_BUF(mac);
-
-       might_sleep();
+       struct ieee80211_local *local = (*sta)->local;
+       struct ieee80211_sub_if_data *sdata = (*sta)->sdata;
+#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
+       DECLARE_MAC_BUF(mbuf);
+#endif
+       /*
+        * pull caller's reference if we're already gone.
+        */
+       if (sta_info_hash_del(local, *sta)) {
+               *sta = NULL;
+               return;
+       }
 
-       write_lock_bh(&local->sta_lock);
-       sta_info_remove(sta);
-       write_unlock_bh(&local->sta_lock);
+       /*
+        * Also pull caller's reference if the STA is pinned by the
+        * task that is adding the debugfs entries. In that case, we
+        * leave the STA "to be freed".
+        *
+        * The rules are not trivial, but not too complex either:
+        *  (1) pin_status is only modified under the sta_lock
+        *  (2) sta_info_debugfs_add_work() will set the status
+        *      to PINNED when it found an item that needs a new
+        *      debugfs directory created. In that case, that item
+        *      must not be freed although all *RCU* users are done
+        *      with it. Hence, we tell the caller of _unlink()
+        *      that the item is already gone (as can happen when
+        *      two tasks try to unlink/destroy at the same time)
+        *  (3) We set the pin_status to DESTROY here when we
+        *      find such an item.
+        *  (4) sta_info_debugfs_add_work() will reset the pin_status
+        *      from PINNED to NORMAL when it is done with the item,
+        *      but will check for DESTROY before resetting it in
+        *      which case it will free the item.
+        */
+       if ((*sta)->pin_status == STA_INFO_PIN_STAT_PINNED) {
+               (*sta)->pin_status = STA_INFO_PIN_STAT_DESTROY;
+               *sta = NULL;
+               return;
+       }
 
-       if (ieee80211_vif_is_mesh(&sdata->vif))
-               mesh_plink_deactivate(sta);
+       list_del(&(*sta)->list);
 
-       while ((skb = skb_dequeue(&sta->ps_tx_buf)) != NULL) {
-               local->total_ps_buffered--;
-               dev_kfree_skb(skb);
-       }
-       while ((skb = skb_dequeue(&sta->tx_filtered)) != NULL) {
-               dev_kfree_skb(skb);
+       if ((*sta)->flags & WLAN_STA_PS) {
+               (*sta)->flags &= ~WLAN_STA_PS;
+               if (sdata->bss)
+                       atomic_dec(&sdata->bss->num_sta_ps);
+               __sta_info_clear_tim_bit(sdata->bss, *sta);
        }
 
-#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
-       printk(KERN_DEBUG "%s: Removed STA %s\n",
-              wiphy_name(local->hw.wiphy), print_mac(mac, sta->addr));
-#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
-
-       ieee80211_key_free(sta->key);
-       WARN_ON(sta->key);
+       local->num_sta--;
 
        if (local->ops->sta_notify) {
-
                if (sdata->vif.type == IEEE80211_IF_TYPE_VLAN)
                        sdata = sdata->u.vlan.ap;
 
                local->ops->sta_notify(local_to_hw(local), &sdata->vif,
-                                      STA_NOTIFY_REMOVE, sta->addr);
+                                      STA_NOTIFY_REMOVE, (*sta)->addr);
        }
 
-       rate_control_remove_sta_debugfs(sta);
-       ieee80211_sta_debugfs_remove(sta);
+       if (ieee80211_vif_is_mesh(&sdata->vif)) {
+               mesh_accept_plinks_update(sdata);
+#ifdef CONFIG_MAC80211_MESH
+               del_timer(&(*sta)->plink_timer);
+#endif
+       }
 
-       sta_info_put(sta);
+#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
+       printk(KERN_DEBUG "%s: Removed STA %s\n",
+              wiphy_name(local->hw.wiphy), print_mac(mbuf, (*sta)->addr));
+#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
 }
 
+void sta_info_unlink(struct sta_info **sta)
+{
+       struct ieee80211_local *local = (*sta)->local;
+       unsigned long flags;
+
+       spin_lock_irqsave(&local->sta_lock, flags);
+       __sta_info_unlink(sta);
+       spin_unlock_irqrestore(&local->sta_lock, flags);
+}
 
 static inline int sta_info_buffer_expired(struct ieee80211_local *local,
                                          struct sta_info *sta,
                if (!skb)
                        break;
 
-               sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+               sdata = sta->sdata;
                local->total_ps_buffered--;
                printk(KERN_DEBUG "Buffered frame expired (STA "
                       "%s)\n", print_mac(mac, sta->addr));
        struct ieee80211_local *local = (struct ieee80211_local *) data;
        struct sta_info *sta;
 
-       read_lock_bh(&local->sta_lock);
-       list_for_each_entry(sta, &local->sta_list, list) {
-               __sta_info_get(sta);
+       rcu_read_lock();
+       list_for_each_entry_rcu(sta, &local->sta_list, list)
                sta_info_cleanup_expire_buffered(local, sta);
-               sta_info_put(sta);
-       }
-       read_unlock_bh(&local->sta_lock);
+       rcu_read_unlock();
 
        local->sta_cleanup.expires =
                round_jiffies(jiffies + STA_INFO_CLEANUP_INTERVAL);
 }
 
 #ifdef CONFIG_MAC80211_DEBUGFS
-static void sta_info_debugfs_add_task(struct work_struct *work)
+static void sta_info_debugfs_add_work(struct work_struct *work)
 {
        struct ieee80211_local *local =
                container_of(work, struct ieee80211_local, sta_debugfs_add);
        struct sta_info *sta, *tmp;
+       unsigned long flags;
 
        while (1) {
                sta = NULL;
-               read_lock_bh(&local->sta_lock);
+
+               spin_lock_irqsave(&local->sta_lock, flags);
                list_for_each_entry(tmp, &local->sta_list, list) {
                        if (!tmp->debugfs.dir) {
                                sta = tmp;
-                               __sta_info_get(sta);
+                               __sta_info_pin(sta);
                                break;
                        }
                }
-               read_unlock_bh(&local->sta_lock);
+               spin_unlock_irqrestore(&local->sta_lock, flags);
 
                if (!sta)
                        break;
 
                ieee80211_sta_debugfs_add(sta);
                rate_control_add_sta_debugfs(sta);
-               sta_info_put(sta);
+
+               sta = __sta_info_unpin(sta);
+
+               if (sta) {
+                       synchronize_rcu();
+                       sta_info_destroy(sta);
+               }
        }
 }
 #endif
 
 void sta_info_init(struct ieee80211_local *local)
 {
-       rwlock_init(&local->sta_lock);
+       spin_lock_init(&local->sta_lock);
        INIT_LIST_HEAD(&local->sta_list);
 
        setup_timer(&local->sta_cleanup, sta_info_cleanup,
                round_jiffies(jiffies + STA_INFO_CLEANUP_INTERVAL);
 
 #ifdef CONFIG_MAC80211_DEBUGFS
-       INIT_WORK(&local->sta_debugfs_add, sta_info_debugfs_add_task);
+       INIT_WORK(&local->sta_debugfs_add, sta_info_debugfs_add_work);
 #endif
 }
 
 /**
  * sta_info_flush - flush matching STA entries from the STA table
  * @local: local interface data
- * @dev: matching rule for the net device (sta->dev) or %NULL to match all STAs
+ * @sdata: matching rule for the net device (sta->dev) or %NULL to match all STAs
  */
-void sta_info_flush(struct ieee80211_local *local, struct net_device *dev)
+void sta_info_flush(struct ieee80211_local *local,
+                   struct ieee80211_sub_if_data *sdata)
 {
        struct sta_info *sta, *tmp;
        LIST_HEAD(tmp_list);
+       unsigned long flags;
 
-       write_lock_bh(&local->sta_lock);
-       list_for_each_entry_safe(sta, tmp, &local->sta_list, list)
-               if (!dev || dev == sta->dev) {
-                       __sta_info_get(sta);
-                       sta_info_remove(sta);
-                       list_add_tail(&sta->list, &tmp_list);
-               }
-       write_unlock_bh(&local->sta_lock);
+       might_sleep();
 
-       list_for_each_entry_safe(sta, tmp, &tmp_list, list) {
-               sta_info_free(sta);
-               sta_info_put(sta);
+       spin_lock_irqsave(&local->sta_lock, flags);
+       list_for_each_entry_safe(sta, tmp, &local->sta_list, list) {
+               if (!sdata || sdata == sta->sdata) {
+                       __sta_info_unlink(&sta);
+                       if (sta)
+                               list_add_tail(&sta->list, &tmp_list);
+               }
        }
+       spin_unlock_irqrestore(&local->sta_lock, flags);
+
+       synchronize_rcu();
+
+       list_for_each_entry_safe(sta, tmp, &tmp_list, list)
+               sta_info_destroy(sta);
 }
 
 #include <linux/list.h>
 #include <linux/types.h>
 #include <linux/if_ether.h>
-#include <linux/kref.h>
 #include "ieee80211_key.h"
 
 /**
        u8 dialog_token_allocator;
 };
 
+
+/* see __sta_info_unlink */
+#define STA_INFO_PIN_STAT_NORMAL       0
+#define STA_INFO_PIN_STAT_PINNED       1
+#define STA_INFO_PIN_STAT_DESTROY      2
+
+
 struct sta_info {
-       struct kref kref;
        struct list_head list;
        struct sta_info *hnext; /* next entry in hash table list */
 
        /* last rates used to send a frame to this STA */
        int last_txrate_idx, last_nonerp_txrate_idx;
 
-       struct net_device *dev; /* which net device is this station associated
-                                * to */
+       /* sub_if_data this sta belongs to */
+       struct ieee80211_sub_if_data *sdata;
 
        struct ieee80211_key *key;
 
 
        u16 listen_interval;
 
+       /*
+        * for use by the internal lifetime management,
+        * see __sta_info_unlink
+        */
+       u8 pin_status;
+
        struct ieee80211_ht_info ht_info; /* 802.11n HT capabilities
                                             of this STA */
        struct sta_ampdu_mlme ampdu_mlme;
  */
 #define STA_INFO_CLEANUP_INTERVAL (10 * HZ)
 
-static inline void __sta_info_get(struct sta_info *sta)
-{
-       kref_get(&sta->kref);
-}
-
-struct sta_info * sta_info_get(struct ieee80211_local *local, u8 *addr);
+/*
+ * Get a STA info, must have be under RCU read lock.
+ */
+struct sta_info *sta_info_get(struct ieee80211_local *local, u8 *addr);
+/*
+ * Get STA info by index, BROKEN!
+ */
 struct sta_info *sta_info_get_by_idx(struct ieee80211_local *local, int idx,
                                      struct net_device *dev);
-void sta_info_put(struct sta_info *sta);
-struct sta_info *sta_info_add(struct ieee80211_local *local,
-                             struct net_device *dev, u8 *addr, gfp_t gfp);
-void sta_info_remove(struct sta_info *sta);
-void sta_info_free(struct sta_info *sta);
-void sta_info_init(struct ieee80211_local *local);
-int sta_info_start(struct ieee80211_local *local);
-void sta_info_stop(struct ieee80211_local *local);
-void sta_info_flush(struct ieee80211_local *local, struct net_device *dev);
+/*
+ * Add a new STA info, must be under RCU read lock
+ * because otherwise the returned reference isn't
+ * necessarily valid long enough.
+ */
+struct sta_info *sta_info_add(struct ieee80211_sub_if_data *sdata,
+                             u8 *addr);
+/*
+ * Unlink a STA info from the hash table/list.
+ * This can NULL the STA pointer if somebody else
+ * has already unlinked it.
+ */
+void sta_info_unlink(struct sta_info **sta);
 
+void sta_info_destroy(struct sta_info *sta);
 void sta_info_set_tim_bit(struct sta_info *sta);
 void sta_info_clear_tim_bit(struct sta_info *sta);
 
+void sta_info_init(struct ieee80211_local *local);
+int sta_info_start(struct ieee80211_local *local);
+void sta_info_stop(struct ieee80211_local *local);
+void sta_info_flush(struct ieee80211_local *local,
+                   struct ieee80211_sub_if_data *sdata);
+
 #endif /* STA_INFO_H */
 
                }
                total += skb_queue_len(&ap->ps_bc_buf);
        }
-       rcu_read_unlock();
 
-       read_lock_bh(&local->sta_lock);
-       list_for_each_entry(sta, &local->sta_list, list) {
+       list_for_each_entry_rcu(sta, &local->sta_list, list) {
                skb = skb_dequeue(&sta->ps_tx_buf);
                if (skb) {
                        purged++;
                }
                total += skb_queue_len(&sta->ps_tx_buf);
        }
-       read_unlock_bh(&local->sta_lock);
+
+       rcu_read_unlock();
 
        local->total_ps_buffered = total;
        printk(KERN_DEBUG "%s: PS buffers full - purged %d frames\n",
                return 0;
        }
 
+       rcu_read_lock();
+
        /* initialises tx */
        res_prepare = __ieee80211_tx_prepare(&tx, skb, dev, control);
 
        if (res_prepare == TX_DROP) {
                dev_kfree_skb(skb);
+               rcu_read_unlock();
                return 0;
        }
 
-       /*
-        * key references are protected using RCU and this requires that
-        * we are in a read-site RCU section during receive processing
-        */
-       rcu_read_lock();
-
        sta = tx.sta;
        tx.channel = local->hw.conf.channel;
 
 
        skb = tx.skb; /* handlers are allowed to change skb */
 
-       if (sta)
-               sta_info_put(sta);
-
        if (unlikely(res == TX_DROP)) {
                I802_DEBUG_INC(local->tx_handlers_drop);
                goto drop;
         * in AP mode)
         */
        if (!is_multicast_ether_addr(hdr.addr1)) {
+               rcu_read_lock();
                sta = sta_info_get(local, hdr.addr1);
-               if (sta) {
+               if (sta)
                        sta_flags = sta->flags;
-                       sta_info_put(sta);
-               }
+               rcu_read_unlock();
        }
 
        /* receiver is QoS enabled, use a QoS type frame */
 
        /* Generate bitmap for TIM only if there are any STAs in power save
         * mode. */
-       read_lock_bh(&local->sta_lock);
        if (atomic_read(&bss->num_sta_ps) > 0)
                /* in the hope that this is faster than
                 * checking byte-for-byte */
                *pos++ = aid0; /* Bitmap control */
                *pos++ = 0; /* Part Virt Bitmap */
        }
-       read_unlock_bh(&local->sta_lock);
 }
 
 struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
                        ieee80211_include_sequence(sdata,
                                        (struct ieee80211_hdr *)skb->data);
 
-                       ieee80211_beacon_add_tim(local, ap, skb, beacon);
+                       /*
+                        * Not very nice, but we want to allow the driver to call
+                        * ieee80211_beacon_get() as a response to the set_tim()
+                        * callback. That, however, is already invoked under the
+                        * sta_lock to guarantee consistent and race-free update
+                        * of the tim bitmap in mac80211 and the driver.
+                        */
+                       if (local->tim_in_locked_section) {
+                               ieee80211_beacon_add_tim(local, ap, skb, beacon);
+                       } else {
+                               unsigned long flags;
+
+                               spin_lock_irqsave(&local->sta_lock, flags);
+                               ieee80211_beacon_add_tim(local, ap, skb, beacon);
+                               spin_unlock_irqrestore(&local->sta_lock, flags);
+                       }
 
                        if (beacon->tail)
                                memcpy(skb_put(skb, beacon->tail_len),
                rcu_read_unlock();
                return NULL;
        }
-       rcu_read_unlock();
 
        if (bss->dtim_count != 0)
                return NULL; /* send buffered bc/mc only after DTIM beacon */
                skb = NULL;
        }
 
-       if (sta)
-               sta_info_put(sta);
+       rcu_read_unlock();
 
        return skb;
 }
 
 
        if (pkt_data->flags & IEEE80211_TXPD_REQUEUE) {
                queue = pkt_data->queue;
+               rcu_read_lock();
                sta = sta_info_get(local, hdr->addr1);
                tid = skb->priority & QOS_CONTROL_TAG1D_MASK;
                if (sta) {
                        } else {
                                pkt_data->flags &= ~IEEE80211_TXPD_AMPDU;
                        }
-                       sta_info_put(sta);
                }
+               rcu_read_unlock();
                skb_queue_tail(&q->requeued[queue], skb);
                qd->q.qlen++;
                return 0;
                p++;
                *p = 0;
 
+               rcu_read_lock();
+
                sta = sta_info_get(local, hdr->addr1);
                if (sta) {
                        int ampdu_queue = sta->tid_to_tx_q[tid];
                        } else {
                                pkt_data->flags &= ~IEEE80211_TXPD_AMPDU;
                        }
-                       sta_info_put(sta);
                }
+
+               rcu_read_unlock();
        }
 
        if (unlikely(queue >= local->hw.queues)) {