summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-rw-r--r--net/mac80211/agg-tx.c8
-rw-r--r--net/mac80211/cfg.c25
-rw-r--r--net/mac80211/debugfs.c33
-rw-r--r--net/mac80211/driver-ops.h42
-rw-r--r--net/mac80211/driver-trace.h73
-rw-r--r--net/mac80211/ibss.c24
-rw-r--r--net/mac80211/ieee80211_i.h19
-rw-r--r--net/mac80211/mesh_plink.c17
-rw-r--r--net/mac80211/mlme.c60
-rw-r--r--net/mac80211/pm.c10
-rw-r--r--net/mac80211/rate.c2
-rw-r--r--net/mac80211/rate.h7
-rw-r--r--net/mac80211/rx.c12
-rw-r--r--net/mac80211/scan.c27
-rw-r--r--net/mac80211/sta_info.c731
-rw-r--r--net/mac80211/sta_info.h36
-rw-r--r--net/mac80211/status.c17
-rw-r--r--net/mac80211/tx.c11
-rw-r--r--net/mac80211/util.c24
-rw-r--r--net/wireless/radiotap.c305
20 files changed, 847 insertions, 636 deletions
diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c
index 718fbcff84d2..5538e1b4a697 100644
--- a/net/mac80211/agg-tx.c
+++ b/net/mac80211/agg-tx.c
@@ -237,6 +237,14 @@ int ieee80211_start_tx_ba_session(struct ieee80211_sta *pubsta, u16 tid)
sdata->vif.type != NL80211_IFTYPE_AP)
return -EINVAL;
+ if (test_sta_flags(sta, WLAN_STA_DISASSOC)) {
+#ifdef CONFIG_MAC80211_HT_DEBUG
+ printk(KERN_DEBUG "Disassociation is in progress. "
+ "Denying BA session request\n");
+#endif
+ return -EINVAL;
+ }
+
if (test_sta_flags(sta, WLAN_STA_SUSPEND)) {
#ifdef CONFIG_MAC80211_HT_DEBUG
printk(KERN_DEBUG "Suspend in progress. "
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index facf233843e0..e1731b7c2523 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -515,6 +515,8 @@ static int ieee80211_config_beacon(struct ieee80211_sub_if_data *sdata,
if (old)
memcpy(new->tail, old->tail, new_tail_len);
+ sdata->vif.bss_conf.dtim_period = new->dtim_period;
+
rcu_assign_pointer(sdata->u.ap.beacon, new);
synchronize_rcu();
@@ -747,9 +749,7 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
layer2_update = sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
sdata->vif.type == NL80211_IFTYPE_AP;
- rcu_read_lock();
-
- err = sta_info_insert(sta);
+ err = sta_info_insert_rcu(sta);
if (err) {
rcu_read_unlock();
return err;
@@ -768,26 +768,13 @@ static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev,
{
struct ieee80211_local *local = wiphy_priv(wiphy);
struct ieee80211_sub_if_data *sdata;
- struct sta_info *sta;
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
- if (mac) {
- rcu_read_lock();
-
- sta = sta_info_get_bss(sdata, mac);
- if (!sta) {
- rcu_read_unlock();
- return -ENOENT;
- }
-
- sta_info_unlink(&sta);
- rcu_read_unlock();
-
- sta_info_destroy(sta);
- } else
- sta_info_flush(local, sdata);
+ if (mac)
+ return sta_info_destroy_addr_bss(sdata, mac);
+ sta_info_flush(local, sdata);
return 0;
}
diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c
index b3bc32b62a5a..637929b65ccc 100644
--- a/net/mac80211/debugfs.c
+++ b/net/mac80211/debugfs.c
@@ -250,6 +250,38 @@ static const struct file_operations uapsd_max_sp_len_ops = {
.open = mac80211_open_file_generic
};
+static ssize_t channel_type_read(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct ieee80211_local *local = file->private_data;
+ const char *buf;
+
+ switch (local->hw.conf.channel_type) {
+ case NL80211_CHAN_NO_HT:
+ buf = "no ht\n";
+ break;
+ case NL80211_CHAN_HT20:
+ buf = "ht20\n";
+ break;
+ case NL80211_CHAN_HT40MINUS:
+ buf = "ht40-\n";
+ break;
+ case NL80211_CHAN_HT40PLUS:
+ buf = "ht40+\n";
+ break;
+ default:
+ buf = "???";
+ break;
+ }
+
+ return simple_read_from_buffer(user_buf, count, ppos, buf, strlen(buf));
+}
+
+static const struct file_operations channel_type_ops = {
+ .read = channel_type_read,
+ .open = mac80211_open_file_generic
+};
+
static ssize_t queues_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
@@ -408,6 +440,7 @@ void debugfs_hw_add(struct ieee80211_local *local)
DEBUGFS_ADD(noack);
DEBUGFS_ADD(uapsd_queues);
DEBUGFS_ADD(uapsd_max_sp_len);
+ DEBUGFS_ADD(channel_type);
statsd = debugfs_create_dir("statistics", phyd);
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 6c31f38ac7f5..c3d844093a2f 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -243,6 +243,40 @@ static inline void drv_sta_notify(struct ieee80211_local *local,
trace_drv_sta_notify(local, sdata, cmd, sta);
}
+static inline int drv_sta_add(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta)
+{
+ int ret = 0;
+
+ might_sleep();
+
+ if (local->ops->sta_add)
+ ret = local->ops->sta_add(&local->hw, &sdata->vif, sta);
+ else if (local->ops->sta_notify)
+ local->ops->sta_notify(&local->hw, &sdata->vif,
+ STA_NOTIFY_ADD, sta);
+
+ trace_drv_sta_add(local, sdata, sta, ret);
+
+ return ret;
+}
+
+static inline void drv_sta_remove(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta)
+{
+ might_sleep();
+
+ if (local->ops->sta_remove)
+ local->ops->sta_remove(&local->hw, &sdata->vif, sta);
+ else if (local->ops->sta_notify)
+ local->ops->sta_notify(&local->hw, &sdata->vif,
+ STA_NOTIFY_REMOVE, sta);
+
+ trace_drv_sta_remove(local, sdata, sta);
+}
+
static inline int drv_conf_tx(struct ieee80211_local *local, u16 queue,
const struct ieee80211_tx_queue_params *params)
{
@@ -256,14 +290,6 @@ static inline int drv_conf_tx(struct ieee80211_local *local, u16 queue,
return ret;
}
-static inline int drv_get_tx_stats(struct ieee80211_local *local,
- struct ieee80211_tx_queue_stats *stats)
-{
- int ret = local->ops->get_tx_stats(&local->hw, stats);
- trace_drv_get_tx_stats(local, stats, ret);
- return ret;
-}
-
static inline u64 drv_get_tsf(struct ieee80211_local *local)
{
u64 ret = -1ULL;
diff --git a/net/mac80211/driver-trace.h b/net/mac80211/driver-trace.h
index 502424b2538a..41baf730a5c7 100644
--- a/net/mac80211/driver-trace.h
+++ b/net/mac80211/driver-trace.h
@@ -545,59 +545,88 @@ TRACE_EVENT(drv_sta_notify,
)
);
-TRACE_EVENT(drv_conf_tx,
- TP_PROTO(struct ieee80211_local *local, u16 queue,
- const struct ieee80211_tx_queue_params *params,
- int ret),
+TRACE_EVENT(drv_sta_add,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta, int ret),
- TP_ARGS(local, queue, params, ret),
+ TP_ARGS(local, sdata, sta, ret),
TP_STRUCT__entry(
LOCAL_ENTRY
- __field(u16, queue)
- __field(u16, txop)
- __field(u16, cw_min)
- __field(u16, cw_max)
- __field(u8, aifs)
+ VIF_ENTRY
+ STA_ENTRY
__field(int, ret)
),
TP_fast_assign(
LOCAL_ASSIGN;
- __entry->queue = queue;
+ VIF_ASSIGN;
+ STA_ASSIGN;
__entry->ret = ret;
- __entry->txop = params->txop;
- __entry->cw_max = params->cw_max;
- __entry->cw_min = params->cw_min;
- __entry->aifs = params->aifs;
),
TP_printk(
- LOCAL_PR_FMT " queue:%d ret:%d",
- LOCAL_PR_ARG, __entry->queue, __entry->ret
+ LOCAL_PR_FMT VIF_PR_FMT STA_PR_FMT " ret:%d",
+ LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG, __entry->ret
)
);
-TRACE_EVENT(drv_get_tx_stats,
+TRACE_EVENT(drv_sta_remove,
TP_PROTO(struct ieee80211_local *local,
- struct ieee80211_tx_queue_stats *stats,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta),
+
+ TP_ARGS(local, sdata, sta),
+
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ VIF_ENTRY
+ STA_ENTRY
+ ),
+
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ VIF_ASSIGN;
+ STA_ASSIGN;
+ ),
+
+ TP_printk(
+ LOCAL_PR_FMT VIF_PR_FMT STA_PR_FMT,
+ LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG
+ )
+);
+
+TRACE_EVENT(drv_conf_tx,
+ TP_PROTO(struct ieee80211_local *local, u16 queue,
+ const struct ieee80211_tx_queue_params *params,
int ret),
- TP_ARGS(local, stats, ret),
+ TP_ARGS(local, queue, params, ret),
TP_STRUCT__entry(
LOCAL_ENTRY
+ __field(u16, queue)
+ __field(u16, txop)
+ __field(u16, cw_min)
+ __field(u16, cw_max)
+ __field(u8, aifs)
__field(int, ret)
),
TP_fast_assign(
LOCAL_ASSIGN;
+ __entry->queue = queue;
__entry->ret = ret;
+ __entry->txop = params->txop;
+ __entry->cw_max = params->cw_max;
+ __entry->cw_min = params->cw_min;
+ __entry->aifs = params->aifs;
),
TP_printk(
- LOCAL_PR_FMT " ret:%d",
- LOCAL_PR_ARG, __entry->ret
+ LOCAL_PR_FMT " queue:%d ret:%d",
+ LOCAL_PR_ARG, __entry->queue, __entry->ret
)
);
diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c
index f95750b423e3..f3e942486749 100644
--- a/net/mac80211/ibss.c
+++ b/net/mac80211/ibss.c
@@ -275,10 +275,12 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
(unsigned long long) supp_rates,
(unsigned long long) sta->sta.supp_rates[band]);
#endif
- } else
- ieee80211_ibss_add_sta(sdata, mgmt->bssid, mgmt->sa, supp_rates);
-
- rcu_read_unlock();
+ rcu_read_unlock();
+ } else {
+ rcu_read_unlock();
+ ieee80211_ibss_add_sta(sdata, mgmt->bssid, mgmt->sa,
+ supp_rates, GFP_KERNEL);
+ }
}
bss = ieee80211_bss_info_update(local, rx_status, mgmt, len, elems,
@@ -368,7 +370,8 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
sdata->name, mgmt->bssid);
#endif
ieee80211_sta_join_ibss(sdata, bss);
- ieee80211_ibss_add_sta(sdata, mgmt->bssid, mgmt->sa, supp_rates);
+ ieee80211_ibss_add_sta(sdata, mgmt->bssid, mgmt->sa,
+ supp_rates, GFP_KERNEL);
}
put_bss:
@@ -381,7 +384,8 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
* must be callable in atomic context.
*/
struct sta_info *ieee80211_ibss_add_sta(struct ieee80211_sub_if_data *sdata,
- u8 *bssid,u8 *addr, u32 supp_rates)
+ u8 *bssid,u8 *addr, u32 supp_rates,
+ gfp_t gfp)
{
struct ieee80211_if_ibss *ifibss = &sdata->u.ibss;
struct ieee80211_local *local = sdata->local;
@@ -410,7 +414,7 @@ struct sta_info *ieee80211_ibss_add_sta(struct ieee80211_sub_if_data *sdata,
wiphy_name(local->hw.wiphy), addr, sdata->name);
#endif
- sta = sta_info_alloc(sdata, addr, GFP_ATOMIC);
+ sta = sta_info_alloc(sdata, addr, gfp);
if (!sta)
return NULL;
@@ -422,9 +426,9 @@ struct sta_info *ieee80211_ibss_add_sta(struct ieee80211_sub_if_data *sdata,
rate_control_rate_init(sta);
+ /* If it fails, maybe we raced another insertion? */
if (sta_info_insert(sta))
- return NULL;
-
+ return sta_info_get(sdata, addr);
return sta;
}
@@ -652,7 +656,7 @@ static void ieee80211_rx_mgmt_probe_req(struct ieee80211_sub_if_data *sdata,
}
if (pos[1] != 0 &&
(pos[1] != ifibss->ssid_len ||
- !memcmp(pos + 2, ifibss->ssid, ifibss->ssid_len))) {
+ memcmp(pos + 2, ifibss->ssid, ifibss->ssid_len))) {
/* Ignore ProbeReq for foreign SSID */
return;
}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 3067fbd69d63..9dd98b674cbc 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -316,6 +316,7 @@ enum ieee80211_sta_flags {
IEEE80211_STA_CSA_RECEIVED = BIT(5),
IEEE80211_STA_MFP_ENABLED = BIT(6),
IEEE80211_STA_UAPSD_ENABLED = BIT(7),
+ IEEE80211_STA_NULLFUNC_ACKED = BIT(8),
};
struct ieee80211_if_managed {
@@ -688,15 +689,18 @@ struct ieee80211_local {
/* 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.
+ * The mutex only protects the list and counter,
+ * reads are done in RCU.
+ * Additionally, the lock protects the hash table,
+ * the pending list and each BSS's TIM bitmap.
*/
+ struct mutex sta_mtx;
spinlock_t sta_lock;
unsigned long num_sta;
- struct list_head sta_list;
+ struct list_head sta_list, sta_pending_list;
struct sta_info *sta_hash[STA_HASH_SIZE];
struct timer_list sta_cleanup;
+ struct work_struct sta_finish_work;
int sta_generation;
struct sk_buff_head pending[IEEE80211_MAX_QUEUES];
@@ -770,10 +774,6 @@ struct ieee80211_local {
assoc_led_name[32], radio_led_name[32];
#endif
-#ifdef CONFIG_MAC80211_DEBUGFS
- struct work_struct sta_debugfs_add;
-#endif
-
#ifdef CONFIG_MAC80211_DEBUG_COUNTERS
/* TX/RX handler statistics */
unsigned int tx_handlers_drop;
@@ -985,7 +985,8 @@ void ieee80211_ibss_setup_sdata(struct ieee80211_sub_if_data *sdata);
ieee80211_rx_result
ieee80211_ibss_rx_mgmt(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb);
struct sta_info *ieee80211_ibss_add_sta(struct ieee80211_sub_if_data *sdata,
- u8 *bssid, u8 *addr, u32 supp_rates);
+ u8 *bssid, u8 *addr, u32 supp_rates,
+ gfp_t gfp);
int ieee80211_ibss_join(struct ieee80211_sub_if_data *sdata,
struct cfg80211_ibss_params *params);
int ieee80211_ibss_leave(struct ieee80211_sub_if_data *sdata);
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 7985e5150898..bc4e20e57ff5 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -102,7 +102,7 @@ static struct sta_info *mesh_plink_alloc(struct ieee80211_sub_if_data *sdata,
if (local->num_sta >= MESH_MAX_PLINKS)
return NULL;
- sta = sta_info_alloc(sdata, hw_addr, GFP_ATOMIC);
+ sta = sta_info_alloc(sdata, hw_addr, GFP_KERNEL);
if (!sta)
return NULL;
@@ -236,12 +236,12 @@ void mesh_neighbour_update(u8 *hw_addr, u32 rates, struct ieee80211_sub_if_data
sta = sta_info_get(sdata, hw_addr);
if (!sta) {
+ rcu_read_unlock();
+
sta = mesh_plink_alloc(sdata, hw_addr, rates);
- if (!sta) {
- rcu_read_unlock();
+ if (!sta)
return;
- }
- if (sta_info_insert(sta)) {
+ if (sta_info_insert_rcu(sta)) {
rcu_read_unlock();
return;
}
@@ -485,9 +485,11 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
} else if (!sta) {
/* ftype == PLINK_OPEN */
u32 rates;
+
+ rcu_read_unlock();
+
if (!mesh_plink_free_count(sdata)) {
mpl_dbg("Mesh plink error: no more free plinks\n");
- rcu_read_unlock();
return;
}
@@ -495,10 +497,9 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
sta = mesh_plink_alloc(sdata, mgmt->sa, rates);
if (!sta) {
mpl_dbg("Mesh plink error: plink table full\n");
- rcu_read_unlock();
return;
}
- if (sta_info_insert(sta)) {
+ if (sta_info_insert_rcu(sta)) {
rcu_read_unlock();
return;
}
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 86c6ad1b058d..bfc4a5070013 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -27,10 +27,6 @@
#include "rate.h"
#include "led.h"
-#define IEEE80211_AUTH_TIMEOUT (HZ / 5)
-#define IEEE80211_AUTH_MAX_TRIES 3
-#define IEEE80211_ASSOC_TIMEOUT (HZ / 5)
-#define IEEE80211_ASSOC_MAX_TRIES 3
#define IEEE80211_MAX_PROBE_TRIES 5
/*
@@ -438,8 +434,11 @@ static void ieee80211_enable_ps(struct ieee80211_local *local,
} else {
if (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK)
ieee80211_send_nullfunc(local, sdata, 1);
- conf->flags |= IEEE80211_CONF_PS;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+
+ if (!(local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS)) {
+ conf->flags |= IEEE80211_CONF_PS;
+ ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+ }
}
}
@@ -545,6 +544,7 @@ void ieee80211_dynamic_ps_enable_work(struct work_struct *work)
container_of(work, struct ieee80211_local,
dynamic_ps_enable_work);
struct ieee80211_sub_if_data *sdata = local->ps_sdata;
+ struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
/* can only happen when PS was just disabled anyway */
if (!sdata)
@@ -553,11 +553,16 @@ void ieee80211_dynamic_ps_enable_work(struct work_struct *work)
if (local->hw.conf.flags & IEEE80211_CONF_PS)
return;
- if (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK)
+ if ((local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK) &&
+ (!(ifmgd->flags & IEEE80211_STA_NULLFUNC_ACKED)))
ieee80211_send_nullfunc(local, sdata, 1);
- local->hw.conf.flags |= IEEE80211_CONF_PS;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+ if (!(local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS) ||
+ (ifmgd->flags & IEEE80211_STA_NULLFUNC_ACKED)) {
+ ifmgd->flags &= ~IEEE80211_STA_NULLFUNC_ACKED;
+ local->hw.conf.flags |= IEEE80211_CONF_PS;
+ ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+ }
}
void ieee80211_dynamic_ps_timer(unsigned long data)
@@ -792,8 +797,10 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata)
rcu_read_lock();
sta = sta_info_get(sdata, bssid);
- if (sta)
+ if (sta) {
+ set_sta_flags(sta, WLAN_STA_DISASSOC);
ieee80211_sta_tear_down_BA_sessions(sta);
+ }
rcu_read_unlock();
changed |= ieee80211_reset_erp_info(sdata);
@@ -826,19 +833,7 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata)
changed |= BSS_CHANGED_BSSID;
ieee80211_bss_info_change_notify(sdata, changed);
- rcu_read_lock();
-
- sta = sta_info_get(sdata, bssid);
- if (!sta) {
- rcu_read_unlock();
- return;
- }
-
- sta_info_unlink(&sta);
-
- rcu_read_unlock();
-
- sta_info_destroy(sta);
+ sta_info_destroy_addr(sdata, bssid);
}
void ieee80211_sta_rx_notify(struct ieee80211_sub_if_data *sdata,
@@ -1844,7 +1839,11 @@ int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata,
wk->probe_auth.algorithm = auth_alg;
wk->probe_auth.privacy = req->bss->capability & WLAN_CAPABILITY_PRIVACY;
- wk->type = IEEE80211_WORK_DIRECT_PROBE;
+ /* if we already have a probe, don't probe again */
+ if (req->bss->proberesp_ies)
+ wk->type = IEEE80211_WORK_AUTH;
+ else
+ wk->type = IEEE80211_WORK_DIRECT_PROBE;
wk->chan = req->bss->channel;
wk->sdata = sdata;
wk->done = ieee80211_probe_auth_done;
@@ -1904,6 +1903,7 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
return -ENOMEM;
ifmgd->flags &= ~IEEE80211_STA_DISABLE_11N;
+ ifmgd->flags &= ~IEEE80211_STA_NULLFUNC_ACKED;
for (i = 0; i < req->crypto.n_ciphers_pairwise; i++)
if (req->crypto.ciphers_pairwise[i] == WLAN_CIPHER_SUITE_WEP40 ||
@@ -2007,12 +2007,18 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata,
mutex_lock(&local->work_mtx);
list_for_each_entry(wk, &local->work_list, list) {
- if (wk->type != IEEE80211_WORK_DIRECT_PROBE)
+ if (wk->sdata != sdata)
+ continue;
+
+ if (wk->type != IEEE80211_WORK_DIRECT_PROBE &&
+ wk->type != IEEE80211_WORK_AUTH)
continue;
+
if (memcmp(req->bss->bssid, wk->filter_ta, ETH_ALEN))
continue;
- not_auth_yet = true;
- list_del(&wk->list);
+
+ not_auth_yet = wk->type == IEEE80211_WORK_DIRECT_PROBE;
+ list_del_rcu(&wk->list);
free_work(wk);
break;
}
diff --git a/net/mac80211/pm.c b/net/mac80211/pm.c
index 47f818959ad7..0e64484e861c 100644
--- a/net/mac80211/pm.c
+++ b/net/mac80211/pm.c
@@ -11,7 +11,6 @@ int __ieee80211_suspend(struct ieee80211_hw *hw)
struct ieee80211_local *local = hw_to_local(hw);
struct ieee80211_sub_if_data *sdata;
struct sta_info *sta;
- unsigned long flags;
ieee80211_scan_cancel(local);
@@ -55,22 +54,21 @@ int __ieee80211_suspend(struct ieee80211_hw *hw)
rcu_read_unlock();
/* remove STAs */
- spin_lock_irqsave(&local->sta_lock, flags);
+ mutex_lock(&local->sta_mtx);
list_for_each_entry(sta, &local->sta_list, list) {
- if (local->ops->sta_notify) {
+ if (sta->uploaded) {
sdata = sta->sdata;
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
sdata = container_of(sdata->bss,
struct ieee80211_sub_if_data,
u.ap);
- drv_sta_notify(local, sdata, STA_NOTIFY_REMOVE,
- &sta->sta);
+ drv_sta_remove(local, sdata, &sta->sta);
}
mesh_plink_quiesce(sta);
}
- spin_unlock_irqrestore(&local->sta_lock, flags);
+ mutex_unlock(&local->sta_mtx);
/* remove all interfaces */
list_for_each_entry(sdata, &local->interfaces, list) {
diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c
index c74b7c85403c..99ab24cc9783 100644
--- a/net/mac80211/rate.c
+++ b/net/mac80211/rate.c
@@ -145,7 +145,7 @@ static const struct file_operations rcname_ops = {
};
#endif
-struct rate_control_ref *rate_control_alloc(const char *name,
+static struct rate_control_ref *rate_control_alloc(const char *name,
struct ieee80211_local *local)
{
struct dentry *debugfsdir = NULL;
diff --git a/net/mac80211/rate.h b/net/mac80211/rate.h
index 998cf7a935b6..b6108bca96d4 100644
--- a/net/mac80211/rate.h
+++ b/net/mac80211/rate.h
@@ -26,10 +26,6 @@ struct rate_control_ref {
struct kref kref;
};
-/* Get a reference to the rate control algorithm. If `name' is NULL, get the
- * first available algorithm. */
-struct rate_control_ref *rate_control_alloc(const char *name,
- struct ieee80211_local *local);
void rate_control_get_rate(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta,
struct ieee80211_tx_rate_control *txrc);
@@ -116,7 +112,8 @@ static inline void rate_control_remove_sta_debugfs(struct sta_info *sta)
#endif
}
-/* functions for rate control related to a device */
+/* Get a reference to the rate control algorithm. If `name' is NULL, get the
+ * first available algorithm. */
int ieee80211_init_rate_ctrl_alg(struct ieee80211_local *local,
const char *name);
void rate_control_deinitialize(struct ieee80211_local *local);
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 5709307fcb9b..c9755f3d986c 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -1719,6 +1719,7 @@ static ieee80211_rx_result debug_noinline
ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
{
struct ieee80211_sub_if_data *sdata = rx->sdata;
+ struct ieee80211_local *local = rx->local;
struct net_device *dev = sdata->dev;
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data;
__le16 fc = hdr->frame_control;
@@ -1750,6 +1751,13 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
dev->stats.rx_packets++;
dev->stats.rx_bytes += rx->skb->len;
+ if (ieee80211_is_data(hdr->frame_control) &&
+ !is_multicast_ether_addr(hdr->addr1) &&
+ local->hw.conf.dynamic_ps_timeout > 0 && local->ps_sdata) {
+ mod_timer(&local->dynamic_ps_timer, jiffies +
+ msecs_to_jiffies(local->hw.conf.dynamic_ps_timeout));
+ }
+
ieee80211_deliver_skb(rx);
return RX_QUEUED;
@@ -2244,8 +2252,8 @@ static int prepare_for_handlers(struct ieee80211_sub_if_data *sdata,
rate_idx = 0; /* TODO: HT rates */
else
rate_idx = status->rate_idx;
- rx->sta = ieee80211_ibss_add_sta(sdata, bssid, hdr->addr2,
- BIT(rate_idx));
+ rx->sta = ieee80211_ibss_add_sta(sdata, bssid,
+ hdr->addr2, BIT(rate_idx), GFP_ATOMIC);
}
break;
case NL80211_IFTYPE_MESH_POINT:
diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c
index bc061f629674..b822dce97867 100644
--- a/net/mac80211/scan.c
+++ b/net/mac80211/scan.c
@@ -345,6 +345,13 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
if (local->scan_req)
return -EBUSY;
+ if (!list_empty(&local->work_list)) {
+ /* wait for the work to finish/time out */
+ local->scan_req = req;
+ local->scan_sdata = sdata;
+ return 0;
+ }
+
if (local->ops->hw_scan) {
u8 *ies;
@@ -364,29 +371,33 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
local->hw_scan_req->ie = ies;
local->hw_scan_band = 0;
+
+ /*
+ * After allocating local->hw_scan_req, we must
+ * go through until ieee80211_prep_hw_scan(), so
+ * anything that might be changed here and leave
+ * this function early must not go after this
+ * allocation.
+ */
}
local->scan_req = req;
local->scan_sdata = sdata;
- if (!list_empty(&local->work_list)) {
- /* wait for the work to finish/time out */
- return 0;
- }
-
if (local->ops->hw_scan)
__set_bit(SCAN_HW_SCANNING, &local->scanning);
else
__set_bit(SCAN_SW_SCANNING, &local->scanning);
+
/*
* Kicking off the scan need not be protected,
* only the scan variable stuff, since now
* local->scan_req is assigned and other callers
* will abort their scan attempts.
*
- * This avoids getting a scan_mtx -> iflist_mtx
- * dependency, so that the scan completed calls
- * have more locking freedom.
+ * This avoids too many locking dependencies
+ * so that the scan completed calls have more
+ * locking freedom.
*/
ieee80211_recalc_idle(local);
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index f735826f055c..211c475f73c6 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -32,49 +32,33 @@
* 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.
*
- * Upon allocating a STA info structure with sta_info_alloc(), the caller owns
- * that structure. It must then either destroy it using sta_info_destroy()
- * (which is pretty useless) or insert it into the hash table using
- * sta_info_insert() which demotes the reference from ownership to a regular
- * RCU-protected reference; if the function is called without protection by an
- * RCU critical section the reference is instantly invalidated. Note that the
- * caller may not do much with the STA info before inserting it, in particular,
- * it may not start any mesh peer link management or add encryption keys.
+ * Upon allocating a STA info structure with sta_info_alloc(), the caller
+ * owns that structure. It must then insert it into the hash table using
+ * either sta_info_insert() or sta_info_insert_rcu(); only in the latter
+ * case (which acquires an rcu read section but must not be called from
+ * within one) will the pointer still be valid after the call. Note that
+ * the caller may not do much with the STA info before inserting it, in
+ * particular, it may not start any mesh peer link management or add
+ * encryption keys.
*
* When the insertion fails (sta_info_insert()) returns non-zero), the
* structure will have been freed by sta_info_insert()!
*
- * sta entries are added by mac80211 when you establish a link with a
+ * Station entries are added by mac80211 when you establish a link with a
* peer. This means different things for the different type of interfaces
* we support. For a regular station this mean we add the AP sta when we
* receive an assocation response from the AP. For IBSS this occurs when
- * we receive a probe response or a beacon from target IBSS network. For
- * WDS we add the sta for the peer imediately upon device open. When using
- * AP mode we add stations for each respective station upon request from
- * userspace through nl80211.
+ * get to know about a peer on the same IBSS. For WDS we add the sta for
+ * the peer imediately upon device open. When using AP mode we add stations
+ * for each respective station upon request from userspace through nl80211.
*
- * 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, this is
- * an internal capability only.
+ * In order to remove a STA info structure, various sta_info_destroy_*()
+ * calls are available.
*
- * 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 destroy it; sta_info_destroy() will wait for an RCU grace period
- * to elapse before actually freeing it. 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().
- *
- * 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.
+ * 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 */
@@ -185,101 +169,6 @@ static void __sta_info_free(struct ieee80211_local *local,
kfree(sta);
}
-void sta_info_destroy(struct sta_info *sta)
-{
- struct ieee80211_local *local;
- struct sk_buff *skb;
- int i;
-
- might_sleep();
-
- if (!sta)
- return;
-
- local = sta->local;
-
- cancel_work_sync(&sta->drv_unblock_wk);
-
- 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
-
- /*
- * We have only unlinked the key, and actually destroying it
- * may mean it is removed from hardware which requires that
- * the key->sta pointer is still valid, so flush the key todo
- * list here.
- *
- * ieee80211_key_todo() will synchronize_rcu() so after this
- * nothing can reference this sta struct any more.
- */
- ieee80211_key_todo();
-
-#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)
- dev_kfree_skb_any(skb);
-
- for (i = 0; i < STA_TID_NUM; i++) {
- struct tid_ampdu_rx *tid_rx;
- struct tid_ampdu_tx *tid_tx;
-
- spin_lock_bh(&sta->lock);
- tid_rx = sta->ampdu_mlme.tid_rx[i];
- /* Make sure timer won't free the tid_rx struct, see below */
- if (tid_rx)
- tid_rx->shutdown = true;
-
- spin_unlock_bh(&sta->lock);
-
- /*
- * Outside spinlock - shutdown is true now so that the timer
- * won't free tid_rx, we have to do that now. Can't let the
- * timer do it because we have to sync the timer outside the
- * lock that it takes itself.
- */
- if (tid_rx) {
- del_timer_sync(&tid_rx->session_timer);
- kfree(tid_rx);
- }
-
- /*
- * No need to do such complications for TX agg sessions, the
- * path leading to freeing the tid_tx struct goes via a call
- * from the driver, and thus needs to look up the sta struct
- * again, which cannot be found when we get here. Hence, we
- * just need to delete the timer and free the aggregation
- * info; we won't be telling the peer about it then but that
- * doesn't matter if we're not talking to it again anyway.
- */
- tid_tx = sta->ampdu_mlme.tid_tx[i];
- if (tid_tx) {
- del_timer_sync(&tid_tx->addba_resp_timer);
- /*
- * STA removed while aggregation session being
- * started? Bit odd, but purge frames anyway.
- */
- skb_queue_purge(&tid_tx->pending);
- kfree(tid_tx);
- }
- }
-
- __sta_info_free(local, sta);
-}
-
-
/* Caller must hold local->sta_lock */
static void sta_info_hash_add(struct ieee80211_local *local,
struct sta_info *sta)
@@ -376,7 +265,7 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
return sta;
}
-int sta_info_insert(struct sta_info *sta)
+static int sta_info_finish_insert(struct sta_info *sta, bool async)
{
struct ieee80211_local *local = sta->local;
struct ieee80211_sub_if_data *sdata = sta->sdata;
@@ -384,6 +273,91 @@ int sta_info_insert(struct sta_info *sta)
unsigned long flags;
int err = 0;
+ WARN_ON(!mutex_is_locked(&local->sta_mtx));
+
+ /* notify driver */
+ if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+ sdata = container_of(sdata->bss,
+ struct ieee80211_sub_if_data,
+ u.ap);
+ err = drv_sta_add(local, sdata, &sta->sta);
+ if (err) {
+ if (!async)
+ return err;
+ printk(KERN_DEBUG "%s: failed to add IBSS STA %pM to driver (%d)"
+ " - keeping it anyway.\n",
+ sdata->name, sta->sta.addr, err);
+ } else {
+ sta->uploaded = true;
+#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
+ if (async)
+ printk(KERN_DEBUG "%s: Finished adding IBSS STA %pM\n",
+ wiphy_name(local->hw.wiphy), sta->sta.addr);
+#endif
+ }
+
+ sdata = sta->sdata;
+
+ if (!async) {
+ local->num_sta++;
+ local->sta_generation++;
+ smp_mb();
+
+ /* make the station visible */
+ spin_lock_irqsave(&local->sta_lock, flags);
+ sta_info_hash_add(local, sta);
+ spin_unlock_irqrestore(&local->sta_lock, flags);
+ }
+
+ list_add(&sta->list, &local->sta_list);
+
+ ieee80211_sta_debugfs_add(sta);
+ rate_control_add_sta_debugfs(sta);
+
+ sinfo.filled = 0;
+ sinfo.generation = local->sta_generation;
+ cfg80211_new_sta(sdata->dev, sta->sta.addr, &sinfo, GFP_KERNEL);
+
+
+ return 0;
+}
+
+static void sta_info_finish_pending(struct ieee80211_local *local)
+{
+ struct sta_info *sta;
+ unsigned long flags;
+
+ spin_lock_irqsave(&local->sta_lock, flags);
+ while (!list_empty(&local->sta_pending_list)) {
+ sta = list_first_entry(&local->sta_pending_list,
+ struct sta_info, list);
+ list_del(&sta->list);
+ spin_unlock_irqrestore(&local->sta_lock, flags);
+
+ sta_info_finish_insert(sta, true);
+
+ spin_lock_irqsave(&local->sta_lock, flags);
+ }
+ spin_unlock_irqrestore(&local->sta_lock, flags);
+}
+
+static void sta_info_finish_work(struct work_struct *work)
+{
+ struct ieee80211_local *local =
+ container_of(work, struct ieee80211_local, sta_finish_work);
+
+ mutex_lock(&local->sta_mtx);
+ sta_info_finish_pending(local);
+ mutex_unlock(&local->sta_mtx);
+}
+
+int sta_info_insert_rcu(struct sta_info *sta) __acquires(RCU)
+{
+ struct ieee80211_local *local = sta->local;
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+ unsigned long flags;
+ int err = 0;
+
/*
* Can't be a WARN_ON because it can be triggered through a race:
* something inserts a STA (on one CPU) without holding the RTNL
@@ -391,36 +365,87 @@ int sta_info_insert(struct sta_info *sta)
*/
if (unlikely(!ieee80211_sdata_running(sdata))) {
err = -ENETDOWN;
+ rcu_read_lock();
goto out_free;
}
if (WARN_ON(compare_ether_addr(sta->sta.addr, sdata->vif.addr) == 0 ||
is_multicast_ether_addr(sta->sta.addr))) {
err = -EINVAL;
+ rcu_read_lock();
goto out_free;
}
+ /*
+ * In ad-hoc mode, we sometimes need to insert stations
+ * from tasklet context from the RX path. To avoid races,
+ * always do so in that case -- see the comment below.
+ */
+ if (sdata->vif.type == NL80211_IFTYPE_ADHOC) {
+ spin_lock_irqsave(&local->sta_lock, flags);
+ /* check if STA exists already */
+ if (sta_info_get_bss(sdata, sta->sta.addr)) {
+ spin_unlock_irqrestore(&local->sta_lock, flags);
+ rcu_read_lock();
+ err = -EEXIST;
+ goto out_free;
+ }
+
+ local->num_sta++;
+ local->sta_generation++;
+ smp_mb();
+ sta_info_hash_add(local, sta);
+
+ list_add_tail(&sta->list, &local->sta_pending_list);
+
+ rcu_read_lock();
+ spin_unlock_irqrestore(&local->sta_lock, flags);
+
+#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
+ printk(KERN_DEBUG "%s: Added IBSS STA %pM\n",
+ wiphy_name(local->hw.wiphy), sta->sta.addr);
+#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
+
+ ieee80211_queue_work(&local->hw, &local->sta_finish_work);
+
+ return 0;
+ }
+
+ /*
+ * On first glance, this will look racy, because the code
+ * below this point, which inserts a station with sleeping,
+ * unlocks the sta_lock between checking existence in the
+ * hash table and inserting into it.
+ *
+ * However, it is not racy against itself because it keeps
+ * the mutex locked. It still seems to race against the
+ * above code that atomically inserts the station... That,
+ * however, is not true because the above code can only
+ * be invoked for IBSS interfaces, and the below code will
+ * not be -- and the two do not race against each other as
+ * the hash table also keys off the interface.
+ */
+
+ might_sleep();
+
+ mutex_lock(&local->sta_mtx);
+
spin_lock_irqsave(&local->sta_lock, flags);
/* check if STA exists already */
- if (sta_info_get(sdata, sta->sta.addr)) {
+ if (sta_info_get_bss(sdata, sta->sta.addr)) {
spin_unlock_irqrestore(&local->sta_lock, flags);
+ rcu_read_lock();
err = -EEXIST;
goto out_free;
}
- list_add(&sta->list, &local->sta_list);
- local->sta_generation++;
- local->num_sta++;
- sta_info_hash_add(local, sta);
- /* notify driver */
- if (local->ops->sta_notify) {
- if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
- sdata = container_of(sdata->bss,
- struct ieee80211_sub_if_data,
- u.ap);
+ spin_unlock_irqrestore(&local->sta_lock, flags);
- drv_sta_notify(local, sdata, STA_NOTIFY_ADD, &sta->sta);
- sdata = sta->sdata;
+ err = sta_info_finish_insert(sta, false);
+ if (err) {
+ mutex_unlock(&local->sta_mtx);
+ rcu_read_lock();
+ goto out_free;
}
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
@@ -428,22 +453,9 @@ int sta_info_insert(struct sta_info *sta)
wiphy_name(local->hw.wiphy), sta->sta.addr);
#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
- spin_unlock_irqrestore(&local->sta_lock, flags);
-
- sinfo.filled = 0;
- sinfo.generation = local->sta_generation;
- cfg80211_new_sta(sdata->dev, sta->sta.addr, &sinfo, GFP_ATOMIC);
-
-#ifdef CONFIG_MAC80211_DEBUGFS
- /*
- * Debugfs entry adding might sleep, so schedule process
- * context task for adding entry for STAs that do not yet
- * have one.
- * NOTE: due to auto-freeing semantics this may only be done
- * if the insertion is successful!
- */
- schedule_work(&local->sta_debugfs_add);
-#endif
+ /* move reference to rcu-protected */
+ rcu_read_lock();
+ mutex_unlock(&local->sta_mtx);
if (ieee80211_vif_is_mesh(&sdata->vif))
mesh_accept_plinks_update(sdata);
@@ -455,6 +467,15 @@ int sta_info_insert(struct sta_info *sta)
return err;
}
+int sta_info_insert(struct sta_info *sta)
+{
+ int err = sta_info_insert_rcu(sta);
+
+ rcu_read_unlock();
+
+ return err;
+}
+
static inline void __bss_tim_set(struct ieee80211_if_ap *bss, u16 aid)
{
/*
@@ -523,108 +544,6 @@ void sta_info_clear_tim_bit(struct sta_info *sta)
spin_unlock_irqrestore(&sta->local->sta_lock, flags);
}
-static void __sta_info_unlink(struct sta_info **sta)
-{
- struct ieee80211_local *local = (*sta)->local;
- struct ieee80211_sub_if_data *sdata = (*sta)->sdata;
- /*
- * pull caller's reference if we're already gone.
- */
- if (sta_info_hash_del(local, *sta)) {
- *sta = NULL;
- return;
- }
-
- if ((*sta)->key) {
- ieee80211_key_free((*sta)->key);
- WARN_ON((*sta)->key);
- }
-
- list_del(&(*sta)->list);
- (*sta)->dead = true;
-
- if (test_and_clear_sta_flags(*sta,
- WLAN_STA_PS_STA | WLAN_STA_PS_DRIVER)) {
- BUG_ON(!sdata->bss);
-
- atomic_dec(&sdata->bss->num_sta_ps);
- __sta_info_clear_tim_bit(sdata->bss, *sta);
- }
-
- local->num_sta--;
- local->sta_generation++;
-
- if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
- rcu_assign_pointer(sdata->u.vlan.sta, NULL);
-
- if (local->ops->sta_notify) {
- if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
- sdata = container_of(sdata->bss,
- struct ieee80211_sub_if_data,
- u.ap);
-
- drv_sta_notify(local, sdata, STA_NOTIFY_REMOVE,
- &(*sta)->sta);
- sdata = (*sta)->sdata;
- }
-
- if (ieee80211_vif_is_mesh(&sdata->vif)) {
- mesh_accept_plinks_update(sdata);
-#ifdef CONFIG_MAC80211_MESH
- del_timer(&(*sta)->plink_timer);
-#endif
- }
-
-#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
- printk(KERN_DEBUG "%s: Removed STA %pM\n",
- wiphy_name(local->hw.wiphy), (*sta)->sta.addr);
-#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
-
- /*
- * Finally, 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) STAs may only be pinned under the RTNL so that
- * sta_info_flush() is guaranteed to actually destroy
- * all STAs that are active for a given interface, this
- * is required for correctness because otherwise we
- * could notify a driver that an interface is going
- * away and only after that (!) notify it about a STA
- * on that interface going away.
- * (3) 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)
- * (4) We set the pin_status to DESTROY here when we
- * find such an item.
- * (5) 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;
- }
-}
-
-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 int sta_info_buffer_expired(struct sta_info *sta,
struct sk_buff *skb)
{
@@ -681,109 +600,209 @@ static void sta_info_cleanup_expire_buffered(struct ieee80211_local *local,
}
}
-
-static void sta_info_cleanup(unsigned long data)
+static int __must_check __sta_info_destroy(struct sta_info *sta)
{
- struct ieee80211_local *local = (struct ieee80211_local *) data;
- struct sta_info *sta;
+ struct ieee80211_local *local;
+ struct ieee80211_sub_if_data *sdata;
+ struct sk_buff *skb;
+ unsigned long flags;
+ int ret, i;
- rcu_read_lock();
- list_for_each_entry_rcu(sta, &local->sta_list, list)
- sta_info_cleanup_expire_buffered(local, sta);
- rcu_read_unlock();
+ might_sleep();
- if (local->quiescing)
- return;
+ if (!sta)
+ return -ENOENT;
- local->sta_cleanup.expires =
- round_jiffies(jiffies + STA_INFO_CLEANUP_INTERVAL);
- add_timer(&local->sta_cleanup);
-}
+ local = sta->local;
+ sdata = sta->sdata;
-#ifdef CONFIG_MAC80211_DEBUGFS
-/*
- * See comment in __sta_info_unlink,
- * caller must hold local->sta_lock.
- */
-static void __sta_info_pin(struct sta_info *sta)
-{
- WARN_ON(sta->pin_status != STA_INFO_PIN_STAT_NORMAL);
- sta->pin_status = STA_INFO_PIN_STAT_PINNED;
+ spin_lock_irqsave(&local->sta_lock, flags);
+ ret = sta_info_hash_del(local, sta);
+ /* this might still be the pending list ... which is fine */
+ if (!ret)
+ list_del(&sta->list);
+ spin_unlock_irqrestore(&local->sta_lock, flags);
+ if (ret)
+ return ret;
+
+ if (sta->key) {
+ ieee80211_key_free(sta->key);
+ /*
+ * We have only unlinked the key, and actually destroying it
+ * may mean it is removed from hardware which requires that
+ * the key->sta pointer is still valid, so flush the key todo
+ * list here.
+ *
+ * ieee80211_key_todo() will synchronize_rcu() so after this
+ * nothing can reference this sta struct any more.
+ */
+ ieee80211_key_todo();
+
+ WARN_ON(sta->key);
+ }
+
+ sta->dead = true;
+
+ if (test_and_clear_sta_flags(sta,
+ WLAN_STA_PS_STA | WLAN_STA_PS_DRIVER)) {
+ BUG_ON(!sdata->bss);
+
+ atomic_dec(&sdata->bss->num_sta_ps);
+ __sta_info_clear_tim_bit(sdata->bss, sta);
+ }
+
+ local->num_sta--;
+ local->sta_generation++;
+
+ if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+ rcu_assign_pointer(sdata->u.vlan.sta, NULL);
+
+ if (sta->uploaded) {
+ if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+ sdata = container_of(sdata->bss,
+ struct ieee80211_sub_if_data,
+ u.ap);
+ drv_sta_remove(local, sdata, &sta->sta);
+ sdata = sta->sdata;
+ }
+
+#ifdef CONFIG_MAC80211_MESH
+ if (ieee80211_vif_is_mesh(&sdata->vif)) {
+ mesh_accept_plinks_update(sdata);
+ del_timer(&sta->plink_timer);
+ }
+#endif
+
+#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
+ printk(KERN_DEBUG "%s: Removed STA %pM\n",
+ wiphy_name(local->hw.wiphy), sta->sta.addr);
+#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
+ cancel_work_sync(&sta->drv_unblock_wk);
+
+ 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);
+ 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)
+ dev_kfree_skb_any(skb);
+
+ for (i = 0; i < STA_TID_NUM; i++) {
+ struct tid_ampdu_rx *tid_rx;
+ struct tid_ampdu_tx *tid_tx;
+
+ spin_lock_bh(&sta->lock);
+ tid_rx = sta->ampdu_mlme.tid_rx[i];
+ /* Make sure timer won't free the tid_rx struct, see below */
+ if (tid_rx)
+ tid_rx->shutdown = true;
+
+ spin_unlock_bh(&sta->lock);
+
+ /*
+ * Outside spinlock - shutdown is true now so that the timer
+ * won't free tid_rx, we have to do that now. Can't let the
+ * timer do it because we have to sync the timer outside the
+ * lock that it takes itself.
+ */
+ if (tid_rx) {
+ del_timer_sync(&tid_rx->session_timer);
+ kfree(tid_rx);
+ }
+
+ /*
+ * No need to do such complications for TX agg sessions, the
+ * path leading to freeing the tid_tx struct goes via a call
+ * from the driver, and thus needs to look up the sta struct
+ * again, which cannot be found when we get here. Hence, we
+ * just need to delete the timer and free the aggregation
+ * info; we won't be telling the peer about it then but that
+ * doesn't matter if we're not talking to it again anyway.
+ */
+ tid_tx = sta->ampdu_mlme.tid_tx[i];
+ if (tid_tx) {
+ del_timer_sync(&tid_tx->addba_resp_timer);
+ /*
+ * STA removed while aggregation session being
+ * started? Bit odd, but purge frames anyway.
+ */
+ skb_queue_purge(&tid_tx->pending);
+ kfree(tid_tx);
+ }
+ }
+
+ __sta_info_free(local, sta);
+
+ return 0;
}
-/*
- * 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)
+int sta_info_destroy_addr(struct ieee80211_sub_if_data *sdata, const u8 *addr)
{
- struct sta_info *ret = NULL;
- unsigned long flags;
+ struct sta_info *sta;
+ int ret;
- 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);
+ mutex_lock(&sdata->local->sta_mtx);
+ sta = sta_info_get(sdata, addr);
+ ret = __sta_info_destroy(sta);
+ mutex_unlock(&sdata->local->sta_mtx);
return ret;
}
-static void sta_info_debugfs_add_work(struct work_struct *work)
+int sta_info_destroy_addr_bss(struct ieee80211_sub_if_data *sdata,
+ const u8 *addr)
{
- struct ieee80211_local *local =
- container_of(work, struct ieee80211_local, sta_debugfs_add);
- struct sta_info *sta, *tmp;
- unsigned long flags;
+ struct sta_info *sta;
+ int ret;
- /* We need to keep the RTNL across the whole pinned status. */
- rtnl_lock();
- while (1) {
- sta = NULL;
+ mutex_lock(&sdata->local->sta_mtx);
+ sta = sta_info_get_bss(sdata, addr);
+ ret = __sta_info_destroy(sta);
+ mutex_unlock(&sdata->local->sta_mtx);
- spin_lock_irqsave(&local->sta_lock, flags);
- list_for_each_entry(tmp, &local->sta_list, list) {
- /*
- * debugfs.add_has_run will be set by
- * ieee80211_sta_debugfs_add regardless
- * of what else it does.
- */
- if (!tmp->debugfs.add_has_run) {
- sta = tmp;
- __sta_info_pin(sta);
- break;
- }
- }
- spin_unlock_irqrestore(&local->sta_lock, flags);
+ return ret;
+}
- if (!sta)
- break;
+static void sta_info_cleanup(unsigned long data)
+{
+ struct ieee80211_local *local = (struct ieee80211_local *) data;
+ struct sta_info *sta;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(sta, &local->sta_list, list)
+ sta_info_cleanup_expire_buffered(local, sta);
+ rcu_read_unlock();
- ieee80211_sta_debugfs_add(sta);
- rate_control_add_sta_debugfs(sta);
+ if (local->quiescing)
+ return;
- sta = __sta_info_unpin(sta);
- sta_info_destroy(sta);
- }
- rtnl_unlock();
+ local->sta_cleanup.expires =
+ round_jiffies(jiffies + STA_INFO_CLEANUP_INTERVAL);
+ add_timer(&local->sta_cleanup);
}
-#endif
void sta_info_init(struct ieee80211_local *local)
{
spin_lock_init(&local->sta_lock);
+ mutex_init(&local->sta_mtx);
INIT_LIST_HEAD(&local->sta_list);
+ INIT_LIST_HEAD(&local->sta_pending_list);
+ INIT_WORK(&local->sta_finish_work, sta_info_finish_work);
setup_timer(&local->sta_cleanup, sta_info_cleanup,
(unsigned long)local);
local->sta_cleanup.expires =
round_jiffies(jiffies + STA_INFO_CLEANUP_INTERVAL);
-
-#ifdef CONFIG_MAC80211_DEBUGFS
- INIT_WORK(&local->sta_debugfs_add, sta_info_debugfs_add_work);
-#endif
}
int sta_info_start(struct ieee80211_local *local)
@@ -795,16 +814,6 @@ int sta_info_start(struct ieee80211_local *local)
void sta_info_stop(struct ieee80211_local *local)
{
del_timer(&local->sta_cleanup);
-#ifdef CONFIG_MAC80211_DEBUGFS
- /*
- * Make sure the debugfs adding work isn't pending after this
- * because we're about to be destroyed. It doesn't matter
- * whether it ran or not since we're going to flush all STAs
- * anyway.
- */
- cancel_work_sync(&local->sta_debugfs_add);
-#endif
-
sta_info_flush(local, NULL);
}
@@ -820,26 +829,19 @@ int sta_info_flush(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata)
{
struct sta_info *sta, *tmp;
- LIST_HEAD(tmp_list);
int ret = 0;
- unsigned long flags;
might_sleep();
- spin_lock_irqsave(&local->sta_lock, flags);
+ mutex_lock(&local->sta_mtx);
+
+ sta_info_finish_pending(local);
+
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);
- ret++;
- }
- }
+ if (!sdata || sdata == sta->sdata)
+ WARN_ON(__sta_info_destroy(sta));
}
- spin_unlock_irqrestore(&local->sta_lock, flags);
-
- list_for_each_entry_safe(sta, tmp, &tmp_list, list)
- sta_info_destroy(sta);
+ mutex_unlock(&local->sta_mtx);
return ret;
}
@@ -849,24 +851,17 @@ void ieee80211_sta_expire(struct ieee80211_sub_if_data *sdata,
{
struct ieee80211_local *local = sdata->local;
struct sta_info *sta, *tmp;
- LIST_HEAD(tmp_list);
- unsigned long flags;
- spin_lock_irqsave(&local->sta_lock, flags);
+ mutex_lock(&local->sta_mtx);
list_for_each_entry_safe(sta, tmp, &local->sta_list, list)
if (time_after(jiffies, sta->last_rx + exp_time)) {
#ifdef CONFIG_MAC80211_IBSS_DEBUG
printk(KERN_DEBUG "%s: expiring inactive STA %pM\n",
sdata->name, sta->sta.addr);
#endif
- __sta_info_unlink(&sta);
- if (sta)
- list_add(&sta->list, &tmp_list);
+ WARN_ON(__sta_info_destroy(sta));
}
- spin_unlock_irqrestore(&local->sta_lock, flags);
-
- list_for_each_entry_safe(sta, tmp, &tmp_list, list)
- sta_info_destroy(sta);
+ mutex_unlock(&local->sta_mtx);
}
struct ieee80211_sta *ieee80211_find_sta_by_hw(struct ieee80211_hw *hw,
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 6f79bba5706e..822d84522937 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -42,6 +42,9 @@
* be in the queues
* @WLAN_STA_PSPOLL: Station sent PS-poll while driver was keeping
* station in power-save mode, reply when the driver unblocks.
+ * @WLAN_STA_DISASSOC: Disassociation in progress.
+ * This is used to reject TX BA session requests when disassociation
+ * is in progress.
*/
enum ieee80211_sta_info_flags {
WLAN_STA_AUTH = 1<<0,
@@ -57,6 +60,7 @@ enum ieee80211_sta_info_flags {
WLAN_STA_SUSPEND = 1<<11,
WLAN_STA_PS_DRIVER = 1<<12,
WLAN_STA_PSPOLL = 1<<13,
+ WLAN_STA_DISASSOC = 1<<14,
};
#define STA_TID_NUM 16
@@ -162,11 +166,6 @@ struct sta_ampdu_mlme {
};
-/* 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 - STA information
*
@@ -187,7 +186,6 @@ struct sta_ampdu_mlme {
* @flaglock: spinlock for flags accesses
* @drv_unblock_wk: used for driver PS unblocking
* @listen_interval: listen interval of this station, when we're acting as AP
- * @pin_status: used internally for pinning a STA struct into memory
* @flags: STA flags, see &enum ieee80211_sta_info_flags
* @ps_tx_buf: buffer of frames to transmit to this station
* when it leaves power saving state
@@ -226,6 +224,7 @@ struct sta_ampdu_mlme {
* @debugfs: debug filesystem info
* @sta: station information we share with the driver
* @dead: set to true when sta is unlinked
+ * @uploaded: set to true when sta is uploaded to the driver
*/
struct sta_info {
/* General information, mostly static */
@@ -245,11 +244,7 @@ struct sta_info {
bool dead;
- /*
- * for use by the internal lifetime management,
- * see __sta_info_unlink
- */
- u8 pin_status;
+ bool uploaded;
/*
* frequently updated, locked with own spinlock (flaglock),
@@ -449,18 +444,19 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
* Insert STA info into hash table/list, returns zero or a
* -EEXIST if (if the same MAC address is already present).
*
- * Calling this without RCU protection makes the caller
- * relinquish its reference to @sta.
+ * Calling the non-rcu version makes the caller relinquish,
+ * the _rcu version calls read_lock_rcu() and must be called
+ * without it held.
*/
int sta_info_insert(struct sta_info *sta);
-/*
- * 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);
+int sta_info_insert_rcu(struct sta_info *sta) __acquires(RCU);
+int sta_info_insert_atomic(struct sta_info *sta);
+
+int sta_info_destroy_addr(struct ieee80211_sub_if_data *sdata,
+ const u8 *addr);
+int sta_info_destroy_addr_bss(struct ieee80211_sub_if_data *sdata,
+ const u8 *addr);
-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);
diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index e57ad6b1d7ea..ded98730c111 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -188,6 +188,7 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
rcu_read_lock();
sband = local->hw.wiphy->bands[info->band];
+ fc = hdr->frame_control;
for_each_sta_info(local, hdr->addr1, sta, tmp) {
/* skip wrong virtual interface */
@@ -205,8 +206,6 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
return;
}
- fc = hdr->frame_control;
-
if ((info->flags & IEEE80211_TX_STAT_AMPDU_NO_BACK) &&
(ieee80211_is_data_qos(fc))) {
u16 tid, ssn;
@@ -275,6 +274,20 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
local->dot11FailedCount++;
}
+ if (ieee80211_is_nullfunc(fc) && ieee80211_has_pm(fc) &&
+ (local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS) &&
+ !(info->flags & IEEE80211_TX_CTL_INJECTED) &&
+ local->ps_sdata && !(local->scanning)) {
+ if (info->flags & IEEE80211_TX_STAT_ACK) {
+ local->ps_sdata->u.mgd.flags |=
+ IEEE80211_STA_NULLFUNC_ACKED;
+ ieee80211_queue_work(&local->hw,
+ &local->dynamic_ps_enable_work);
+ } else
+ mod_timer(&local->dynamic_ps_timer, jiffies +
+ msecs_to_jiffies(10));
+ }
+
/* this was a transmitted frame, but now we want to reuse it */
skb_orphan(skb);
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 85e382aa894e..cbe53ed4fb0b 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -571,7 +571,7 @@ ieee80211_tx_h_sta(struct ieee80211_tx_data *tx)
{
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
- if (tx->sta)
+ if (tx->sta && tx->sta->uploaded)
info->control.sta = &tx->sta->sta;
return TX_CONTINUE;
@@ -1010,7 +1010,8 @@ static bool __ieee80211_parse_tx_radiotap(struct ieee80211_tx_data *tx,
(struct ieee80211_radiotap_header *) skb->data;
struct ieee80211_supported_band *sband;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
- int ret = ieee80211_radiotap_iterator_init(&iterator, rthdr, skb->len);
+ int ret = ieee80211_radiotap_iterator_init(&iterator, rthdr, skb->len,
+ NULL);
sband = tx->local->hw.wiphy->bands[tx->channel->band];
@@ -1046,7 +1047,7 @@ static bool __ieee80211_parse_tx_radiotap(struct ieee80211_tx_data *tx,
* because it will be recomputed and added
* on transmission
*/
- if (skb->len < (iterator.max_length + FCS_LEN))
+ if (skb->len < (iterator._max_length + FCS_LEN))
return false;
skb_trim(skb, skb->len - FCS_LEN);
@@ -1073,10 +1074,10 @@ static bool __ieee80211_parse_tx_radiotap(struct ieee80211_tx_data *tx,
/*
* remove the radiotap header
- * iterator->max_length was sanity-checked against
+ * iterator->_max_length was sanity-checked against
* skb->len by iterator init
*/
- skb_pull(skb, iterator.max_length);
+ skb_pull(skb, iterator._max_length);
return true;
}
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index ca170b417da6..c453226f06b2 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1082,7 +1082,6 @@ int ieee80211_reconfig(struct ieee80211_local *local)
struct ieee80211_hw *hw = &local->hw;
struct ieee80211_sub_if_data *sdata;
struct sta_info *sta;
- unsigned long flags;
int res;
if (local->suspended)
@@ -1116,20 +1115,19 @@ int ieee80211_reconfig(struct ieee80211_local *local)
}
/* add STAs back */
- if (local->ops->sta_notify) {
- spin_lock_irqsave(&local->sta_lock, flags);
- list_for_each_entry(sta, &local->sta_list, list) {
+ mutex_lock(&local->sta_mtx);
+ list_for_each_entry(sta, &local->sta_list, list) {
+ if (sta->uploaded) {
sdata = sta->sdata;
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
sdata = container_of(sdata->bss,
struct ieee80211_sub_if_data,
u.ap);
- drv_sta_notify(local, sdata, STA_NOTIFY_ADD,
- &sta->sta);
+ WARN_ON(drv_sta_add(local, sdata, &sta->sta));
}
- spin_unlock_irqrestore(&local->sta_lock, flags);
}
+ mutex_unlock(&local->sta_mtx);
/* Clear Suspend state so that ADDBA requests can be processed */
@@ -1180,6 +1178,14 @@ int ieee80211_reconfig(struct ieee80211_local *local)
}
}
+ rcu_read_lock();
+ if (hw->flags & IEEE80211_HW_AMPDU_AGGREGATION) {
+ list_for_each_entry_rcu(sta, &local->sta_list, list) {
+ ieee80211_sta_tear_down_BA_sessions(sta);
+ }
+ }
+ rcu_read_unlock();
+
/* add back keys */
list_for_each_entry(sdata, &local->interfaces, list)
if (ieee80211_sdata_running(sdata))
@@ -1219,10 +1225,10 @@ int ieee80211_reconfig(struct ieee80211_local *local)
add_timer(&local->sta_cleanup);
- spin_lock_irqsave(&local->sta_lock, flags);
+ mutex_lock(&local->sta_mtx);
list_for_each_entry(sta, &local->sta_list, list)
mesh_plink_restart(sta);
- spin_unlock_irqrestore(&local->sta_lock, flags);
+ mutex_unlock(&local->sta_mtx);
#else
WARN_ON(1);
#endif
diff --git a/net/wireless/radiotap.c b/net/wireless/radiotap.c
index f591871a7b4f..1332c445d1c7 100644
--- a/net/wireless/radiotap.c
+++ b/net/wireless/radiotap.c
@@ -2,6 +2,16 @@
* Radiotap parser
*
* Copyright 2007 Andy Green <andy@warmcat.com>
+ * Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See COPYING for more details.
*/
#include <net/cfg80211.h>
@@ -10,6 +20,35 @@
/* function prototypes and related defs are in include/net/cfg80211.h */
+static const struct radiotap_align_size rtap_namespace_sizes[] = {
+ [IEEE80211_RADIOTAP_TSFT] = { .align = 8, .size = 8, },
+ [IEEE80211_RADIOTAP_FLAGS] = { .align = 1, .size = 1, },
+ [IEEE80211_RADIOTAP_RATE] = { .align = 1, .size = 1, },
+ [IEEE80211_RADIOTAP_CHANNEL] = { .align = 2, .size = 4, },
+ [IEEE80211_RADIOTAP_FHSS] = { .align = 2, .size = 2, },
+ [IEEE80211_RADIOTAP_DBM_ANTSIGNAL] = { .align = 1, .size = 1, },
+ [IEEE80211_RADIOTAP_DBM_ANTNOISE] = { .align = 1, .size = 1, },
+ [IEEE80211_RADIOTAP_LOCK_QUALITY] = { .align = 2, .size = 2, },
+ [IEEE80211_RADIOTAP_TX_ATTENUATION] = { .align = 2, .size = 2, },
+ [IEEE80211_RADIOTAP_DB_TX_ATTENUATION] = { .align = 2, .size = 2, },
+ [IEEE80211_RADIOTAP_DBM_TX_POWER] = { .align = 1, .size = 1, },
+ [IEEE80211_RADIOTAP_ANTENNA] = { .align = 1, .size = 1, },
+ [IEEE80211_RADIOTAP_DB_ANTSIGNAL] = { .align = 1, .size = 1, },
+ [IEEE80211_RADIOTAP_DB_ANTNOISE] = { .align = 1, .size = 1, },
+ [IEEE80211_RADIOTAP_RX_FLAGS] = { .align = 2, .size = 2, },
+ [IEEE80211_RADIOTAP_TX_FLAGS] = { .align = 2, .size = 2, },
+ [IEEE80211_RADIOTAP_RTS_RETRIES] = { .align = 1, .size = 1, },
+ [IEEE80211_RADIOTAP_DATA_RETRIES] = { .align = 1, .size = 1, },
+ /*
+ * add more here as they are defined in radiotap.h
+ */
+};
+
+static const struct ieee80211_radiotap_namespace radiotap_ns = {
+ .n_bits = sizeof(rtap_namespace_sizes) / sizeof(rtap_namespace_sizes[0]),
+ .align_size = rtap_namespace_sizes,
+};
+
/**
* ieee80211_radiotap_iterator_init - radiotap parser iterator initialization
* @iterator: radiotap_iterator to initialize
@@ -50,9 +89,9 @@
*/
int ieee80211_radiotap_iterator_init(
- struct ieee80211_radiotap_iterator *iterator,
- struct ieee80211_radiotap_header *radiotap_header,
- int max_length)
+ struct ieee80211_radiotap_iterator *iterator,
+ struct ieee80211_radiotap_header *radiotap_header,
+ int max_length, const struct ieee80211_radiotap_vendor_namespaces *vns)
{
/* Linux only supports version 0 radiotap format */
if (radiotap_header->it_version)
@@ -62,19 +101,24 @@ int ieee80211_radiotap_iterator_init(
if (max_length < get_unaligned_le16(&radiotap_header->it_len))
return -EINVAL;
- iterator->rtheader = radiotap_header;
- iterator->max_length = get_unaligned_le16(&radiotap_header->it_len);
- iterator->arg_index = 0;
- iterator->bitmap_shifter = get_unaligned_le32(&radiotap_header->it_present);
- iterator->arg = (u8 *)radiotap_header + sizeof(*radiotap_header);
- iterator->this_arg = NULL;
+ iterator->_rtheader = radiotap_header;
+ iterator->_max_length = get_unaligned_le16(&radiotap_header->it_len);
+ iterator->_arg_index = 0;
+ iterator->_bitmap_shifter = get_unaligned_le32(&radiotap_header->it_present);
+ iterator->_arg = (uint8_t *)radiotap_header + sizeof(*radiotap_header);
+ iterator->_reset_on_ext = 0;
+ iterator->_next_bitmap = &radiotap_header->it_present;
+ iterator->_next_bitmap++;
+ iterator->_vns = vns;
+ iterator->current_namespace = &radiotap_ns;
+ iterator->is_radiotap_ns = 1;
/* find payload start allowing for extended bitmap(s) */
- if (unlikely(iterator->bitmap_shifter & (1<<IEEE80211_RADIOTAP_EXT))) {
- while (get_unaligned_le32(iterator->arg) &
- (1 << IEEE80211_RADIOTAP_EXT)) {
- iterator->arg += sizeof(u32);
+ if (iterator->_bitmap_shifter & (1<<IEEE80211_RADIOTAP_EXT)) {
+ while (get_unaligned_le32(iterator->_arg) &
+ (1 << IEEE80211_RADIOTAP_EXT)) {
+ iterator->_arg += sizeof(uint32_t);
/*
* check for insanity where the present bitmaps
@@ -82,12 +126,13 @@ int ieee80211_radiotap_iterator_init(
* stated radiotap header length
*/
- if (((ulong)iterator->arg -
- (ulong)iterator->rtheader) > iterator->max_length)
+ if ((unsigned long)iterator->_arg -
+ (unsigned long)iterator->_rtheader >
+ (unsigned long)iterator->_max_length)
return -EINVAL;
}
- iterator->arg += sizeof(u32);
+ iterator->_arg += sizeof(uint32_t);
/*
* no need to check again for blowing past stated radiotap
@@ -96,12 +141,36 @@ int ieee80211_radiotap_iterator_init(
*/
}
+ iterator->this_arg = iterator->_arg;
+
/* we are all initialized happily */
return 0;
}
EXPORT_SYMBOL(ieee80211_radiotap_iterator_init);
+static void find_ns(struct ieee80211_radiotap_iterator *iterator,
+ uint32_t oui, uint8_t subns)
+{
+ int i;
+
+ iterator->current_namespace = NULL;
+
+ if (!iterator->_vns)
+ return;
+
+ for (i = 0; i < iterator->_vns->n_ns; i++) {
+ if (iterator->_vns->ns[i].oui != oui)
+ continue;
+ if (iterator->_vns->ns[i].subns != subns)
+ continue;
+
+ iterator->current_namespace = &iterator->_vns->ns[i];
+ break;
+ }
+}
+
+
/**
* ieee80211_radiotap_iterator_next - return next radiotap parser iterator arg
@@ -127,99 +196,80 @@ EXPORT_SYMBOL(ieee80211_radiotap_iterator_init);
*/
int ieee80211_radiotap_iterator_next(
- struct ieee80211_radiotap_iterator *iterator)
+ struct ieee80211_radiotap_iterator *iterator)
{
-
- /*
- * small length lookup table for all radiotap types we heard of
- * starting from b0 in the bitmap, so we can walk the payload
- * area of the radiotap header
- *
- * There is a requirement to pad args, so that args
- * of a given length must begin at a boundary of that length
- * -- but note that compound args are allowed (eg, 2 x u16
- * for IEEE80211_RADIOTAP_CHANNEL) so total arg length is not
- * a reliable indicator of alignment requirement.
- *
- * upper nybble: content alignment for arg
- * lower nybble: content length for arg
- */
-
- static const u8 rt_sizes[] = {
- [IEEE80211_RADIOTAP_TSFT] = 0x88,
- [IEEE80211_RADIOTAP_FLAGS] = 0x11,
- [IEEE80211_RADIOTAP_RATE] = 0x11,
- [IEEE80211_RADIOTAP_CHANNEL] = 0x24,
- [IEEE80211_RADIOTAP_FHSS] = 0x22,
- [IEEE80211_RADIOTAP_DBM_ANTSIGNAL] = 0x11,
- [IEEE80211_RADIOTAP_DBM_ANTNOISE] = 0x11,
- [IEEE80211_RADIOTAP_LOCK_QUALITY] = 0x22,
- [IEEE80211_RADIOTAP_TX_ATTENUATION] = 0x22,
- [IEEE80211_RADIOTAP_DB_TX_ATTENUATION] = 0x22,
- [IEEE80211_RADIOTAP_DBM_TX_POWER] = 0x11,
- [IEEE80211_RADIOTAP_ANTENNA] = 0x11,
- [IEEE80211_RADIOTAP_DB_ANTSIGNAL] = 0x11,
- [IEEE80211_RADIOTAP_DB_ANTNOISE] = 0x11,
- [IEEE80211_RADIOTAP_RX_FLAGS] = 0x22,
- [IEEE80211_RADIOTAP_TX_FLAGS] = 0x22,
- [IEEE80211_RADIOTAP_RTS_RETRIES] = 0x11,
- [IEEE80211_RADIOTAP_DATA_RETRIES] = 0x11,
- /*
- * add more here as they are defined in
- * include/net/ieee80211_radiotap.h
- */
- };
-
- /*
- * for every radiotap entry we can at
- * least skip (by knowing the length)...
- */
-
- while (iterator->arg_index < sizeof(rt_sizes)) {
+ while (1) {
int hit = 0;
- int pad;
+ int pad, align, size, subns, vnslen;
+ uint32_t oui;
- if (!(iterator->bitmap_shifter & 1))
+ /* if no more EXT bits, that's it */
+ if ((iterator->_arg_index % 32) == IEEE80211_RADIOTAP_EXT &&
+ !(iterator->_bitmap_shifter & 1))
+ return -ENOENT;
+
+ if (!(iterator->_bitmap_shifter & 1))
goto next_entry; /* arg not present */
+ /* get alignment/size of data */
+ switch (iterator->_arg_index % 32) {
+ case IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE:
+ case IEEE80211_RADIOTAP_EXT:
+ align = 1;
+ size = 0;
+ break;
+ case IEEE80211_RADIOTAP_VENDOR_NAMESPACE:
+ align = 2;
+ size = 6;
+ break;
+ default:
+ if (!iterator->current_namespace ||
+ iterator->_arg_index >= iterator->current_namespace->n_bits) {
+ if (iterator->current_namespace == &radiotap_ns)
+ return -ENOENT;
+ align = 0;
+ } else {
+ align = iterator->current_namespace->align_size[iterator->_arg_index].align;
+ size = iterator->current_namespace->align_size[iterator->_arg_index].size;
+ }
+ if (!align) {
+ /* skip all subsequent data */
+ iterator->_arg = iterator->_next_ns_data;
+ /* give up on this namespace */
+ iterator->current_namespace = NULL;
+ goto next_entry;
+ }
+ break;
+ }
+
/*
* arg is present, account for alignment padding
- * 8-bit args can be at any alignment
- * 16-bit args must start on 16-bit boundary
- * 32-bit args must start on 32-bit boundary
- * 64-bit args must start on 64-bit boundary
*
- * note that total arg size can differ from alignment of
- * elements inside arg, so we use upper nybble of length
- * table to base alignment on
- *
- * also note: these alignments are ** relative to the
- * start of the radiotap header **. There is no guarantee
+ * Note that these alignments are relative to the start
+ * of the radiotap header. There is no guarantee
* that the radiotap header itself is aligned on any
* kind of boundary.
*
- * the above is why get_unaligned() is used to dereference
- * multibyte elements from the radiotap area
+ * The above is why get_unaligned() is used to dereference
+ * multibyte elements from the radiotap area.
*/
- pad = (((ulong)iterator->arg) -
- ((ulong)iterator->rtheader)) &
- ((rt_sizes[iterator->arg_index] >> 4) - 1);
+ pad = ((unsigned long)iterator->_arg -
+ (unsigned long)iterator->_rtheader) & (align - 1);
if (pad)
- iterator->arg +=
- (rt_sizes[iterator->arg_index] >> 4) - pad;
+ iterator->_arg += align - pad;
/*
* this is what we will return to user, but we need to
* move on first so next call has something fresh to test
*/
- iterator->this_arg_index = iterator->arg_index;
- iterator->this_arg = iterator->arg;
- hit = 1;
+ iterator->this_arg_index = iterator->_arg_index;
+ iterator->this_arg = iterator->_arg;
+ iterator->this_arg_size = size;
/* internally move on the size of this arg */
- iterator->arg += rt_sizes[iterator->arg_index] & 0x0f;
+ iterator->_arg += size;
/*
* check for insanity where we are given a bitmap that
@@ -228,32 +278,73 @@ int ieee80211_radiotap_iterator_next(
* max_length on the last arg, never exceeding it.
*/
- if (((ulong)iterator->arg - (ulong)iterator->rtheader) >
- iterator->max_length)
+ if ((unsigned long)iterator->_arg -
+ (unsigned long)iterator->_rtheader >
+ (unsigned long)iterator->_max_length)
return -EINVAL;
- next_entry:
- iterator->arg_index++;
- if (unlikely((iterator->arg_index & 31) == 0)) {
- /* completed current u32 bitmap */
- if (iterator->bitmap_shifter & 1) {
- /* b31 was set, there is more */
- /* move to next u32 bitmap */
- iterator->bitmap_shifter =
- get_unaligned_le32(iterator->next_bitmap);
- iterator->next_bitmap++;
- } else
- /* no more bitmaps: end */
- iterator->arg_index = sizeof(rt_sizes);
- } else /* just try the next bit */
- iterator->bitmap_shifter >>= 1;
+ /* these special ones are valid in each bitmap word */
+ switch (iterator->_arg_index % 32) {
+ case IEEE80211_RADIOTAP_VENDOR_NAMESPACE:
+ iterator->_bitmap_shifter >>= 1;
+ iterator->_arg_index++;
+
+ iterator->_reset_on_ext = 1;
+
+ vnslen = get_unaligned_le16(iterator->this_arg + 4);
+ iterator->_next_ns_data = iterator->_arg + vnslen;
+ oui = (*iterator->this_arg << 16) |
+ (*(iterator->this_arg + 1) << 8) |
+ *(iterator->this_arg + 2);
+ subns = *(iterator->this_arg + 3);
+
+ find_ns(iterator, oui, subns);
+
+ iterator->is_radiotap_ns = 0;
+ /* allow parsers to show this information */
+ iterator->this_arg_index =
+ IEEE80211_RADIOTAP_VENDOR_NAMESPACE;
+ iterator->this_arg_size += vnslen;
+ if ((unsigned long)iterator->this_arg +
+ iterator->this_arg_size -
+ (unsigned long)iterator->_rtheader >
+ (unsigned long)(unsigned long)iterator->_max_length)
+ return -EINVAL;
+ hit = 1;
+ break;
+ case IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE:
+ iterator->_bitmap_shifter >>= 1;
+ iterator->_arg_index++;
+
+ iterator->_reset_on_ext = 1;
+ iterator->current_namespace = &radiotap_ns;
+ iterator->is_radiotap_ns = 1;
+ break;
+ case IEEE80211_RADIOTAP_EXT:
+ /*
+ * bit 31 was set, there is more
+ * -- move to next u32 bitmap
+ */
+ iterator->_bitmap_shifter =
+ get_unaligned_le32(iterator->_next_bitmap);
+ iterator->_next_bitmap++;
+ if (iterator->_reset_on_ext)
+ iterator->_arg_index = 0;
+ else
+ iterator->_arg_index++;
+ iterator->_reset_on_ext = 0;
+ break;
+ default:
+ /* we've got a hit! */
+ hit = 1;
+ next_entry:
+ iterator->_bitmap_shifter >>= 1;
+ iterator->_arg_index++;
+ }
/* if we found a valid arg earlier, return it now */
if (hit)
return 0;
}
-
- /* we don't know how to handle any more args, we're done */
- return -ENOENT;
}
EXPORT_SYMBOL(ieee80211_radiotap_iterator_next);