summaryrefslogtreecommitdiffstats
path: root/net/mac80211
diff options
context:
space:
mode:
Diffstat (limited to 'net/mac80211')
-rw-r--r--net/mac80211/cfg.c110
-rw-r--r--net/mac80211/debugfs_sta.c4
-rw-r--r--net/mac80211/debugfs_sta.h2
-rw-r--r--net/mac80211/ieee80211.c78
-rw-r--r--net/mac80211/ieee80211_i.h13
-rw-r--r--net/mac80211/ieee80211_iface.c11
-rw-r--r--net/mac80211/ieee80211_ioctl.c42
-rw-r--r--net/mac80211/ieee80211_rate.c8
-rw-r--r--net/mac80211/ieee80211_rate.h1
-rw-r--r--net/mac80211/ieee80211_sta.c123
-rw-r--r--net/mac80211/key.c8
-rw-r--r--net/mac80211/mesh.c5
-rw-r--r--net/mac80211/mesh.h16
-rw-r--r--net/mac80211/mesh_hwmp.c6
-rw-r--r--net/mac80211/mesh_pathtbl.c30
-rw-r--r--net/mac80211/mesh_plink.c101
-rw-r--r--net/mac80211/rc80211_pid_algo.c25
-rw-r--r--net/mac80211/rc80211_simple.c18
-rw-r--r--net/mac80211/rx.c24
-rw-r--r--net/mac80211/sta_info.c387
-rw-r--r--net/mac80211/sta_info.h61
-rw-r--r--net/mac80211/tx.c48
-rw-r--r--net/mac80211/wme.c8
23 files changed, 668 insertions, 461 deletions
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 6ac49231efa..e9ba6fcc0e4 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -136,7 +136,6 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
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);
@@ -170,12 +169,7 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *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,
@@ -184,7 +178,6 @@ 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);
@@ -195,21 +188,18 @@ static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *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;
@@ -292,8 +282,6 @@ static int ieee80211_get_key(struct wiphy *wiphy, struct net_device *dev,
err = 0;
out:
- if (sta)
- sta_info_put(sta);
return err;
}
@@ -311,7 +299,7 @@ static int ieee80211_config_default_key(struct wiphy *wiphy,
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 |
@@ -340,16 +328,20 @@ static int ieee80211_dump_station(struct wiphy *wiphy, struct net_device *dev,
{
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,
@@ -357,16 +349,21 @@ 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;
}
/*
@@ -559,8 +556,8 @@ static void ieee80211_send_layer2_update(struct sta_info *sta)
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);
}
@@ -572,7 +569,7 @@ static void sta_apply_parameters(struct ieee80211_local *local,
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;
@@ -644,14 +641,13 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
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);
@@ -662,15 +658,14 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
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) {
@@ -679,10 +674,14 @@ static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev,
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;
}
@@ -701,21 +700,19 @@ static int ieee80211_change_station(struct wiphy *wiphy,
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;
}
@@ -735,23 +732,26 @@ static int ieee80211_add_mpath(struct wiphy *wiphy, struct net_device *dev,
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;
}
@@ -760,7 +760,7 @@ static int ieee80211_del_mpath(struct wiphy *wiphy, struct net_device *dev,
u8 *dst)
{
if (dst)
- return mesh_path_del(dst, dev);
+ return mesh_path_del(dst, dev, false);
mesh_path_flush(dev);
return 0;
@@ -781,20 +781,22 @@ static int ieee80211_change_mpath(struct wiphy *wiphy,
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;
}
diff --git a/net/mac80211/debugfs_sta.c b/net/mac80211/debugfs_sta.c
index ed7c9f3b460..73cfb4da464 100644
--- a/net/mac80211/debugfs_sta.c
+++ b/net/mac80211/debugfs_sta.c
@@ -51,7 +51,7 @@ static const struct file_operations sta_ ##name## _ops = { \
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);
@@ -200,7 +200,7 @@ static ssize_t sta_agg_status_write(struct file *file,
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;
diff --git a/net/mac80211/debugfs_sta.h b/net/mac80211/debugfs_sta.h
index 574a1cd54b9..8b608903259 100644
--- a/net/mac80211/debugfs_sta.h
+++ b/net/mac80211/debugfs_sta.h
@@ -1,6 +1,8 @@
#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);
diff --git a/net/mac80211/ieee80211.c b/net/mac80211/ieee80211.c
index 727af295c96..85b1391375c 100644
--- a/net/mac80211/ieee80211.c
+++ b/net/mac80211/ieee80211.c
@@ -375,15 +375,19 @@ static int ieee80211_stop(struct net_device *dev)
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);
/*
@@ -449,7 +453,7 @@ static int ieee80211_stop(struct net_device *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:
@@ -522,9 +526,12 @@ int ieee80211_start_tx_ba_session(struct ieee80211_hw *hw, u8 *ra, u16 tid)
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;
}
@@ -564,7 +571,7 @@ int ieee80211_start_tx_ba_session(struct ieee80211_hw *hw, u8 *ra, u16 tid)
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 */
@@ -601,7 +608,7 @@ int ieee80211_start_tx_ba_session(struct ieee80211_hw *hw, u8 *ra, u16 tid)
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);
@@ -614,7 +621,7 @@ int ieee80211_start_tx_ba_session(struct ieee80211_hw *hw, u8 *ra, u16 tid)
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);
@@ -637,9 +644,12 @@ int ieee80211_stop_tx_ba_session(struct ieee80211_hw *hw,
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;
@@ -673,7 +683,7 @@ int ieee80211_stop_tx_ba_session(struct ieee80211_hw *hw,
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);
@@ -691,8 +701,10 @@ void ieee80211_start_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u16 tid)
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;
@@ -705,7 +717,7 @@ void ieee80211_start_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u16 tid)
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;
}
@@ -718,7 +730,7 @@ void ieee80211_start_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u16 tid)
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);
@@ -739,10 +751,12 @@ void ieee80211_stop_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u8 tid)
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;
@@ -750,13 +764,13 @@ void ieee80211_stop_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u8 tid)
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];
@@ -777,7 +791,7 @@ void ieee80211_stop_tx_ba_cb(struct ieee80211_hw *hw, u8 *ra, u8 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);
@@ -887,32 +901,41 @@ int ieee80211_if_update_wds(struct net_device *dev, u8 *remote_addr)
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;
}
@@ -1330,6 +1353,8 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb,
return;
}
+ rcu_read_lock();
+
if (status->excessive_retries) {
struct sta_info *sta;
sta = sta_info_get(local, hdr->addr1);
@@ -1343,10 +1368,9 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb,
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);
}
}
@@ -1356,12 +1380,14 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb,
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
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index d3b5cc57af4..8e440c5706d 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -574,6 +574,7 @@ struct ieee80211_local {
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 {
@@ -591,9 +592,15 @@ struct ieee80211_local {
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;
diff --git a/net/mac80211/ieee80211_iface.c b/net/mac80211/ieee80211_iface.c
index b0f17a2b1a4..98b22736e88 100644
--- a/net/mac80211/ieee80211_iface.c
+++ b/net/mac80211/ieee80211_iface.c
@@ -240,16 +240,21 @@ void ieee80211_if_reinit(struct net_device *dev)
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:
@@ -275,7 +280,7 @@ void ieee80211_if_reinit(struct net_device *dev)
}
/* 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);
diff --git a/net/mac80211/ieee80211_ioctl.c b/net/mac80211/ieee80211_ioctl.c
index 38e2d83e15f..5147152b926 100644
--- a/net/mac80211/ieee80211_ioctl.c
+++ b/net/mac80211/ieee80211_ioctl.c
@@ -33,8 +33,7 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
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;
@@ -51,24 +50,23 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
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;
/*
@@ -78,14 +76,14 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
* 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;
}
}
@@ -93,18 +91,9 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
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,
@@ -625,7 +614,7 @@ static int ieee80211_ioctl_giwrate(struct net_device *dev,
else
rate->value = 0;
rate->value *= 100000;
- sta_info_put(sta);
+
return 0;
}
@@ -1000,7 +989,6 @@ static struct iw_statistics *ieee80211_get_wireless_stats(struct net_device *dev
wstats->qual.qual = sta->last_signal;
wstats->qual.noise = sta->last_noise;
wstats->qual.updated = local->wstats_flags;
- sta_info_put(sta);
}
return wstats;
}
diff --git a/net/mac80211/ieee80211_rate.c b/net/mac80211/ieee80211_rate.c
index ebe29b716b2..4de06f128d9 100644
--- a/net/mac80211/ieee80211_rate.c
+++ b/net/mac80211/ieee80211_rate.c
@@ -170,9 +170,12 @@ void rate_control_get_rate(struct net_device *dev,
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);
@@ -190,8 +193,7 @@ void rate_control_get_rate(struct net_device *dev,
}
}
- if (sta)
- sta_info_put(sta);
+ rcu_read_unlock();
}
struct rate_control_ref *rate_control_get(struct rate_control_ref *ref)
diff --git a/net/mac80211/ieee80211_rate.h b/net/mac80211/ieee80211_rate.h
index 5f9a2ca49a5..bfd0a1982e4 100644
--- a/net/mac80211/ieee80211_rate.h
+++ b/net/mac80211/ieee80211_rate.h
@@ -14,6 +14,7 @@
#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"
diff --git a/net/mac80211/ieee80211_sta.c b/net/mac80211/ieee80211_sta.c
index 9f933aeca71..a3e96eb59eb 100644
--- a/net/mac80211/ieee80211_sta.c
+++ b/net/mac80211/ieee80211_sta.c
@@ -24,6 +24,7 @@
#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>
@@ -845,6 +846,8 @@ static void ieee80211_associated(struct net_device *dev,
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",
@@ -860,7 +863,7 @@ static void ieee80211_associated(struct net_device *dev,
"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,
@@ -876,8 +879,17 @@ static void ieee80211_associated(struct net_device *dev,
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);
@@ -1103,9 +1115,13 @@ static void ieee80211_sta_process_addba_request(struct net_device *dev,
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;
@@ -1197,9 +1213,9 @@ end:
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,
@@ -1213,9 +1229,13 @@ 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;
@@ -1230,7 +1250,7 @@ static void ieee80211_sta_process_addba_resp(struct net_device *dev,
#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;
}
@@ -1244,7 +1264,7 @@ static void ieee80211_sta_process_addba_resp(struct net_device *dev,
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;
}
@@ -1271,7 +1291,7 @@ static void ieee80211_sta_process_addba_resp(struct net_device *dev,
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,
@@ -1326,16 +1346,20 @@ void ieee80211_sta_stop_rx_ba_session(struct net_device *dev, u8 *ra, 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 =
@@ -1374,7 +1398,7 @@ void ieee80211_sta_stop_rx_ba_session(struct net_device *dev, u8 *ra, u16 tid,
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();
}
@@ -1387,9 +1411,13 @@ static void ieee80211_sta_process_delba(struct net_device *dev,
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;
@@ -1414,7 +1442,7 @@ static void ieee80211_sta_process_delba(struct net_device *dev,
ieee80211_stop_tx_ba_session(&local->hw, sta->addr, tid,
WLAN_BACK_RECIPIENT);
}
- sta_info_put(sta);
+ rcu_read_unlock();
}
/*
@@ -1437,9 +1465,13 @@ void sta_addba_resp_timer_expired(unsigned long data)
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 */
@@ -1461,7 +1493,7 @@ void sta_addba_resp_timer_expired(unsigned long data)
WLAN_BACK_INITIATOR);
timer_expired_exit:
- sta_info_put(sta);
+ rcu_read_unlock();
}
/*
@@ -1481,8 +1513,8 @@ void sta_rx_agg_session_timer_expired(unsigned long data)
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);
}
@@ -1791,14 +1823,18 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
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,
@@ -1812,7 +1848,6 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
}
}
- sta->dev = dev;
sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC | WLAN_STA_ASSOC_AP |
WLAN_STA_AUTHORIZED;
@@ -1883,7 +1918,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
bss_conf->aid = aid;
ieee80211_set_associated(dev, ifsta, 1);
- sta_info_put(sta);
+ rcu_read_unlock();
ieee80211_associated(dev, ifsta);
}
@@ -2329,6 +2364,8 @@ static void ieee80211_rx_bss_info(struct net_device *dev,
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))) {
@@ -2354,9 +2391,10 @@ static void ieee80211_rx_bss_info(struct net_device *dev,
(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
@@ -2550,8 +2588,10 @@ static void ieee80211_rx_bss_info(struct net_device *dev,
"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();
}
}
@@ -2893,17 +2933,20 @@ static int ieee80211_sta_active_ibss(struct net_device *dev)
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;
}
@@ -2915,22 +2958,25 @@ static void ieee80211_sta_expire(struct net_device *dev, unsigned long exp_time)
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();
}
@@ -3977,6 +4023,7 @@ int ieee80211_sta_set_extra_ie(struct net_device *dev, char *ie, size_t len)
}
+/* 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)
@@ -3999,7 +4046,7 @@ struct sta_info * ieee80211_ibss_add_sta(struct net_device *dev,
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;
@@ -4010,7 +4057,7 @@ struct sta_info * ieee80211_ibss_add_sta(struct net_device *dev,
rate_control_rate_init(sta, local);
- return sta; /* caller will call sta_info_put() */
+ return sta;
}
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index eac9c59dbc4..df0c04cedbe 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -240,14 +240,17 @@ void ieee80211_key_link(struct ieee80211_key *key,
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();
}
}
@@ -290,6 +293,9 @@ void ieee80211_key_free(struct ieee80211_key *key)
__ieee80211_key_replace(key->sdata, key->sta,
key, NULL);
+ /*
+ * Do NOT remove this without looking at sta_info_destroy()
+ */
synchronize_rcu();
/*
diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c
index ebe1a7a80ba..9de1ccc11cf 100644
--- a/net/mac80211/mesh.c
+++ b/net/mac80211/mesh.c
@@ -83,11 +83,10 @@ bool mesh_peer_accepts_plinks(struct ieee802_11_elems *ie,
/**
* 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,
diff --git a/net/mac80211/mesh.h b/net/mac80211/mesh.h
index d565b3fb9e6..576eee83d85 100644
--- a/net/mac80211/mesh.h
+++ b/net/mac80211/mesh.h
@@ -65,9 +65,10 @@ enum mesh_path_flags {
* @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];
@@ -230,8 +231,9 @@ void mesh_neighbour_update(u8 *hw_addr, u64 rates, struct net_device *dev,
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);
@@ -254,7 +256,7 @@ void mesh_path_flush_pending(struct mesh_path *mpath);
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);
@@ -270,7 +272,7 @@ static inline int mesh_plink_free_count(struct ieee80211_sub_if_data *sdata)
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;
}
diff --git a/net/mac80211/mesh_hwmp.c b/net/mac80211/mesh_hwmp.c
index c2f40ef418c..d8530fe7a0b 100644
--- a/net/mac80211/mesh_hwmp.c
+++ b/net/mac80211/mesh_hwmp.c
@@ -294,7 +294,6 @@ static u32 hwmp_route_info_get(struct net_device *dev,
orig_metric = PREP_IE_METRIC(hwmp_ie);
break;
default:
- sta_info_put(sta);
rcu_read_unlock();
return 0;
}
@@ -330,7 +329,6 @@ static u32 hwmp_route_info_get(struct net_device *dev,
mpath = mesh_path_lookup(orig_addr, dev);
if (!mpath) {
rcu_read_unlock();
- sta_info_put(sta);
return 0;
}
spin_lock_bh(&mpath->state_lock);
@@ -372,7 +370,6 @@ static u32 hwmp_route_info_get(struct net_device *dev,
mpath = mesh_path_lookup(ta, dev);
if (!mpath) {
rcu_read_unlock();
- sta_info_put(sta);
return 0;
}
spin_lock_bh(&mpath->state_lock);
@@ -391,7 +388,6 @@ static u32 hwmp_route_info_get(struct net_device *dev,
spin_unlock_bh(&mpath->state_lock);
}
- sta_info_put(sta);
rcu_read_unlock();
return process ? new_metric : 0;
@@ -861,5 +857,5 @@ void mesh_path_timer(unsigned long data)
endmpathtimer:
rcu_read_unlock();
if (delete)
- mesh_path_del(mpath->dst, mpath->dev);
+ mesh_path_del(mpath->dst, mpath->dev, false);
}
diff --git a/net/mac80211/mesh_pathtbl.c b/net/mac80211/mesh_pathtbl.c
index 3cbdbb23d75..a17f2b29904 100644
--- a/net/mac80211/mesh_pathtbl.c
+++ b/net/mac80211/mesh_pathtbl.c
@@ -55,10 +55,7 @@ static DEFINE_RWLOCK(pathtbl_resize_lock);
*/
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);
}
@@ -236,7 +233,7 @@ void mesh_plink_broken(struct sta_info *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();
@@ -266,9 +263,9 @@ EXPORT_SYMBOL(mesh_plink_broken);
*
* 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)
{
@@ -280,7 +277,7 @@ 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);
}
}
@@ -294,7 +291,7 @@ void mesh_path_flush(struct net_device *dev)
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);
}
}
@@ -303,8 +300,8 @@ static void mesh_path_node_reclaim(struct rcu_head *rp)
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);
@@ -319,9 +316,10 @@ static void mesh_path_node_reclaim(struct rcu_head *rp)
* 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;
@@ -340,7 +338,7 @@ int mesh_path_del(u8 *addr, struct net_device *dev)
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;
@@ -510,7 +508,7 @@ void mesh_path_expire(struct net_device *dev)
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);
}
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index b5fbe970e48..c2b80500ae7 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -65,14 +65,14 @@ static inline
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);
}
/**
@@ -99,12 +99,13 @@ static inline void mesh_plink_fsm_restart(struct sta_info *sta)
*
* 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);
@@ -114,7 +115,7 @@ struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, struct net_device *dev)
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;
@@ -125,7 +126,7 @@ struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, struct net_device *dev)
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;
}
@@ -141,7 +142,8 @@ struct sta_info *mesh_plink_add(u8 *hw_addr, u64 rates, struct net_device *dev)
*/
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;
@@ -246,11 +248,15 @@ void mesh_neighbour_update(u8 *hw_addr, u64 rates, struct net_device *dev,
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;
@@ -260,7 +266,7 @@ void mesh_neighbour_update(u8 *hw_addr, u64 rates, struct net_device *dev,
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)
@@ -273,6 +279,11 @@ 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);
@@ -286,8 +297,8 @@ static void mesh_plink_timer(unsigned long data)
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:
@@ -302,8 +313,7 @@ static void mesh_plink_timer(unsigned long data)
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);
@@ -316,16 +326,14 @@ static void mesh_plink_timer(unsigned long data)
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;
@@ -333,8 +341,6 @@ static void mesh_plink_timer(unsigned long data)
spin_unlock_bh(&sta->plink_lock);
break;
}
-
- sta_info_put(sta);
}
static inline void mesh_plink_timer_set(struct sta_info *sta, int timeout)
@@ -343,14 +349,13 @@ 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
@@ -360,7 +365,6 @@ int mesh_plink_open(struct sta_info *sta)
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;
@@ -369,7 +373,8 @@ int mesh_plink_open(struct sta_info *sta)
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)
@@ -386,7 +391,7 @@ 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);
@@ -401,13 +406,11 @@ int mesh_plink_close(struct sta_info *sta)
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;
@@ -415,15 +418,16 @@ int mesh_plink_close(struct sta_info *sta)
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;
@@ -435,7 +439,6 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt,
#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");
@@ -474,14 +477,17 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt,
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;
}
@@ -505,13 +511,15 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt,
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;
@@ -521,14 +529,14 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt,
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;
@@ -555,7 +563,7 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt,
default:
mpl_dbg("Mesh plink: unknown frame subtype\n");
spin_unlock_bh(&sta->plink_lock);
- sta_info_put(sta);
+ rcu_read_unlock();
return;
}
}
@@ -659,8 +667,7 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt,
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);
@@ -693,8 +700,7 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt,
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);
@@ -717,9 +723,7 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt,
__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);
@@ -738,10 +742,8 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt,
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;
@@ -766,5 +768,6 @@ void mesh_rx_plink_frame(struct net_device *dev, struct ieee80211_mgmt *mgmt,
spin_unlock_bh(&sta->plink_lock);
break;
}
- sta_info_put(sta);
+
+ rcu_read_unlock();
}
diff --git a/net/mac80211/rc80211_pid_algo.c b/net/mac80211/rc80211_pid_algo.c
index 217c0f487bb..a1993161de9 100644
--- a/net/mac80211/rc80211_pid_algo.c
+++ b/net/mac80211/rc80211_pid_algo.c
@@ -77,7 +77,7 @@ static void rate_control_pid_adjust_rate(struct ieee80211_local *local,
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;
@@ -149,7 +149,7 @@ static void rate_control_pid_sample(struct rc_pid_info *pinfo,
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;
@@ -249,23 +249,25 @@ static void rate_control_pid_tx_status(void *priv, struct net_device *dev,
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++;
@@ -303,8 +305,8 @@ static void rate_control_pid_tx_status(void *priv, struct net_device *dev,
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,
@@ -319,6 +321,8 @@ 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
@@ -327,8 +331,7 @@ static void rate_control_pid_get_rate(void *priv, struct net_device *dev,
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;
}
@@ -344,7 +347,7 @@ static void rate_control_pid_get_rate(void *priv, struct net_device *dev,
sta->last_txrate_idx = rateidx;
- sta_info_put(sta);
+ rcu_read_unlock();
sel->rate = &sband->bitrates[rateidx];
diff --git a/net/mac80211/rc80211_simple.c b/net/mac80211/rc80211_simple.c
index bcc541d4b95..4f72fdca7f1 100644
--- a/net/mac80211/rc80211_simple.c
+++ b/net/mac80211/rc80211_simple.c
@@ -40,7 +40,7 @@ static void rate_control_rate_inc(struct ieee80211_local *local,
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;
@@ -70,7 +70,7 @@ static void rate_control_rate_dec(struct ieee80211_local *local,
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;
@@ -118,10 +118,12 @@ static void rate_control_simple_tx_status(void *priv, struct net_device *dev,
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++;
@@ -191,7 +193,8 @@ static void rate_control_simple_tx_status(void *priv, struct net_device *dev,
}
}
- sta_info_put(sta);
+ unlock:
+ rcu_read_unlock();
}
@@ -208,6 +211,8 @@ rate_control_simple_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
@@ -216,8 +221,7 @@ rate_control_simple_get_rate(void *priv, struct net_device *dev,
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;
}
@@ -233,7 +237,7 @@ rate_control_simple_get_rate(void *priv, struct net_device *dev,
sta->last_txrate_idx = rateidx;
- sta_info_put(sta);
+ rcu_read_unlock();
sel->rate = &sband->bitrates[rateidx];
}
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 2e65ca1cd1a..8e1e2859bfd 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -631,7 +631,7 @@ static void ap_sta_ps_start(struct net_device *dev, struct sta_info *sta)
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);
@@ -652,7 +652,7 @@ static int ap_sta_ps_end(struct net_device *dev, struct sta_info *sta)
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);
@@ -1287,7 +1287,7 @@ ieee80211_deliver_skb(struct ieee80211_rx_data *rx)
"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
@@ -1297,8 +1297,6 @@ ieee80211_deliver_skb(struct ieee80211_rx_data *rx)
xmit_skb = skb;
skb = NULL;
}
- if (dsta)
- sta_info_put(dsta);
}
}
@@ -1905,13 +1903,13 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
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))
@@ -1970,10 +1968,6 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
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
@@ -2150,7 +2144,7 @@ static u8 ieee80211_rx_reorder_ampdu(struct ieee80211_local *local,
/* 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;
@@ -2160,9 +2154,7 @@ static u8 ieee80211_rx_reorder_ampdu(struct ieee80211_local *local,
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;
}
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 81c4e3392f4..ee5b66abc0f 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -15,6 +15,7 @@
#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"
@@ -23,14 +24,43 @@
#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,
@@ -42,46 +72,39 @@ 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);
@@ -91,81 +114,101 @@ struct sta_info *sta_info_get_by_idx(struct ieee80211_local *local, int idx,
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++) {
@@ -190,29 +233,26 @@ struct sta_info *sta_info_add(struct ieee80211_local *local,
}
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",
@@ -252,19 +292,20 @@ static void __sta_info_set_tim_bit(struct ieee80211_if_ap *bss,
{
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,
@@ -272,93 +313,135 @@ 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,
@@ -404,7 +487,7 @@ static void sta_info_cleanup_expire_buffered(struct ieee80211_local *local,
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));
@@ -421,13 +504,10 @@ static void sta_info_cleanup(unsigned long data)
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);
@@ -435,37 +515,45 @@ static void sta_info_cleanup(unsigned long data)
}
#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,
@@ -474,7 +562,7 @@ void sta_info_init(struct ieee80211_local *local)
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
}
@@ -493,24 +581,29 @@ void sta_info_stop(struct ieee80211_local *local)
/**
* 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);
}
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index b9dfb6fa893..787124c253a 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -12,7 +12,6 @@
#include <linux/list.h>
#include <linux/types.h>
#include <linux/if_ether.h>
-#include <linux/kref.h>
#include "ieee80211_key.h"
/**
@@ -134,8 +133,14 @@ struct sta_ampdu_mlme {
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 */
@@ -166,8 +171,8 @@ struct sta_info {
/* 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;
@@ -199,6 +204,12 @@ struct sta_info {
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;
@@ -262,25 +273,37 @@ static inline enum plink_state sta_plink_state(struct sta_info *sta)
*/
#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 */
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 33e314f3aab..80f4343a300 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -327,10 +327,8 @@ static void purge_old_ps_buffers(struct ieee80211_local *local)
}
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++;
@@ -338,7 +336,8 @@ static void purge_old_ps_buffers(struct ieee80211_local *local)
}
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",
@@ -1141,20 +1140,17 @@ static int ieee80211_tx(struct net_device *dev, struct sk_buff *skb,
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;
@@ -1167,9 +1163,6 @@ static int ieee80211_tx(struct net_device *dev, struct sk_buff *skb,
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;
@@ -1489,11 +1482,11 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb,
* 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 */
@@ -1722,7 +1715,6 @@ static void ieee80211_beacon_add_tim(struct ieee80211_local *local,
/* 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 */
@@ -1773,7 +1765,6 @@ static void ieee80211_beacon_add_tim(struct ieee80211_local *local,
*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,
@@ -1821,7 +1812,22 @@ 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),
@@ -1965,7 +1971,6 @@ ieee80211_get_buffered_bc(struct ieee80211_hw *hw,
rcu_read_unlock();
return NULL;
}
- rcu_read_unlock();
if (bss->dtim_count != 0)
return NULL; /* send buffered bc/mc only after DTIM beacon */
@@ -2010,8 +2015,7 @@ ieee80211_get_buffered_bc(struct ieee80211_hw *hw,
skb = NULL;
}
- if (sta)
- sta_info_put(sta);
+ rcu_read_unlock();
return skb;
}
diff --git a/net/mac80211/wme.c b/net/mac80211/wme.c
index 8cc036decc8..4e94e4026e7 100644
--- a/net/mac80211/wme.c
+++ b/net/mac80211/wme.c
@@ -153,6 +153,7 @@ static int wme_qdiscop_enqueue(struct sk_buff *skb, struct Qdisc* qd)
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) {
@@ -164,8 +165,8 @@ static int wme_qdiscop_enqueue(struct sk_buff *skb, struct Qdisc* qd)
} 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;
@@ -187,6 +188,8 @@ static int wme_qdiscop_enqueue(struct sk_buff *skb, struct Qdisc* qd)
p++;
*p = 0;
+ rcu_read_lock();
+
sta = sta_info_get(local, hdr->addr1);
if (sta) {
int ampdu_queue = sta->tid_to_tx_q[tid];
@@ -197,8 +200,9 @@ static int wme_qdiscop_enqueue(struct sk_buff *skb, struct Qdisc* qd)
} else {
pkt_data->flags &= ~IEEE80211_TXPD_AMPDU;
}
- sta_info_put(sta);
}
+
+ rcu_read_unlock();
}
if (unlikely(queue >= local->hw.queues)) {