summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2008-04-16 19:37:51 -0700
committerDavid S. Miller <davem@davemloft.net>2008-04-16 19:37:51 -0700
commit8c95b4773dd8d0415269ffad7301ef96d75be8ee (patch)
tree996401ce91c2a652fcff690e2c8bf88f2014d307 /net
parentbcff122d478b774f4fd5262f35eedebe2f2fb274 (diff)
parent30b89b0f5e1313c8a5a039abeaa89248b6338d81 (diff)
Merge branch 'master' of master.kernel.org:/pub/scm/linux/kernel/git/linville/wireless-2.6.26
Diffstat (limited to 'net')
-rw-r--r--net/mac80211/cfg.c22
-rw-r--r--net/mac80211/ieee80211_i.h12
-rw-r--r--net/mac80211/key.c101
-rw-r--r--net/mac80211/key.h11
-rw-r--r--net/mac80211/main.c2
-rw-r--r--net/mac80211/mlme.c144
-rw-r--r--net/mac80211/wext.c8
7 files changed, 204 insertions, 96 deletions
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 8af576c1d2f..699d97b8de5 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -33,6 +33,8 @@ nl80211_type_to_mac80211_type(enum nl80211_iftype type)
case NL80211_IFTYPE_MESH_POINT:
return IEEE80211_IF_TYPE_MESH_POINT;
#endif
+ case NL80211_IFTYPE_WDS:
+ return IEEE80211_IF_TYPE_WDS;
default:
return IEEE80211_IF_TYPE_INVALID;
}
@@ -718,12 +720,18 @@ static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev,
struct sta_info *sta;
if (mac) {
+ rcu_read_lock();
+
/* XXX: get sta belonging to dev */
sta = sta_info_get(local, mac);
- if (!sta)
+ if (!sta) {
+ rcu_read_unlock();
return -ENOENT;
+ }
sta_info_unlink(&sta);
+ rcu_read_unlock();
+
sta_info_destroy(sta);
} else
sta_info_flush(local, sdata);
@@ -740,17 +748,23 @@ static int ieee80211_change_station(struct wiphy *wiphy,
struct sta_info *sta;
struct ieee80211_sub_if_data *vlansdata;
+ rcu_read_lock();
+
/* XXX: get sta belonging to dev */
sta = sta_info_get(local, mac);
- if (!sta)
+ if (!sta) {
+ rcu_read_unlock();
return -ENOENT;
+ }
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)
+ vlansdata->vif.type != IEEE80211_IF_TYPE_AP) {
+ rcu_read_unlock();
return -EINVAL;
+ }
sta->sdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);
ieee80211_send_layer2_update(sta);
@@ -758,6 +772,8 @@ static int ieee80211_change_station(struct wiphy *wiphy,
sta_apply_parameters(local, sta, params);
+ rcu_read_unlock();
+
return 0;
}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index c642538e828..8e53ce7ed44 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -102,7 +102,7 @@ struct ieee80211_sta_bss {
u64 timestamp;
int beacon_int;
- int probe_resp;
+ bool probe_resp;
unsigned long last_update;
/* during assocation, we save an ERP value from a probe response so
@@ -600,8 +600,7 @@ struct ieee80211_local {
/*
* 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, a few items in
- * STA info structures and various key pointers.
+ * the lock protects each BSS's TIM bitmap.
*/
spinlock_t sta_lock;
unsigned long num_sta;
@@ -635,6 +634,13 @@ struct ieee80211_local {
struct list_head interfaces;
+ /*
+ * Key lock, protects sdata's key_list and sta_info's
+ * key pointers (write access, they're RCU.)
+ */
+ spinlock_t key_lock;
+
+
bool sta_sw_scanning;
bool sta_hw_scanning;
int scan_channel_idx;
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index 711e36e54ff..150d66dbda9 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -74,9 +74,12 @@ static void add_todo(struct ieee80211_key *key, u32 flag)
spin_lock(&todo_lock);
key->flags |= flag;
- /* only add if not already added */
- if (list_empty(&key->todo))
- list_add(&key->todo, &todo_list);
+ /*
+ * Remove again if already on the list so that we move it to the end.
+ */
+ if (!list_empty(&key->todo))
+ list_del(&key->todo);
+ list_add_tail(&key->todo, &todo_list);
schedule_work(&todo_work);
spin_unlock(&todo_lock);
}
@@ -210,9 +213,9 @@ void ieee80211_set_default_key(struct ieee80211_sub_if_data *sdata, int idx)
{
unsigned long flags;
- spin_lock_irqsave(&sdata->local->sta_lock, flags);
+ spin_lock_irqsave(&sdata->local->key_lock, flags);
__ieee80211_set_default_key(sdata, idx);
- spin_unlock_irqrestore(&sdata->local->sta_lock, flags);
+ spin_unlock_irqrestore(&sdata->local->key_lock, flags);
}
@@ -339,7 +342,7 @@ void ieee80211_key_link(struct ieee80211_key *key,
}
}
- spin_lock_irqsave(&sdata->local->sta_lock, flags);
+ spin_lock_irqsave(&sdata->local->key_lock, flags);
if (sta)
old_key = sta->key;
@@ -348,68 +351,81 @@ void ieee80211_key_link(struct ieee80211_key *key,
__ieee80211_key_replace(sdata, sta, old_key, key);
- spin_unlock_irqrestore(&sdata->local->sta_lock, flags);
+ spin_unlock_irqrestore(&sdata->local->key_lock, flags);
/* free old key later */
add_todo(old_key, KEY_FLAG_TODO_DELETE);
add_todo(key, KEY_FLAG_TODO_ADD_DEBUGFS);
if (netif_running(sdata->dev))
- add_todo(key, KEY_FLAG_TODO_HWACCEL);
+ add_todo(key, KEY_FLAG_TODO_HWACCEL_ADD);
}
-void ieee80211_key_free(struct ieee80211_key *key)
+static void __ieee80211_key_free(struct ieee80211_key *key)
{
- unsigned long flags;
-
- if (!key)
- return;
-
/*
* Replace key with nothingness if it was ever used.
*/
- if (key->sdata) {
- spin_lock_irqsave(&key->sdata->local->sta_lock, flags);
+ if (key->sdata)
__ieee80211_key_replace(key->sdata, key->sta,
key, NULL);
- spin_unlock_irqrestore(&key->sdata->local->sta_lock, flags);
- }
add_todo(key, KEY_FLAG_TODO_DELETE);
}
-void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata)
+void ieee80211_key_free(struct ieee80211_key *key)
{
- struct ieee80211_key *key;
-
- might_sleep();
+ unsigned long flags;
- if (WARN_ON(!netif_running(sdata->dev)))
+ if (!key)
return;
- ieee80211_key_lock();
+ spin_lock_irqsave(&key->sdata->local->key_lock, flags);
+ __ieee80211_key_free(key);
+ spin_unlock_irqrestore(&key->sdata->local->key_lock, flags);
+}
+
+/*
+ * To be safe against concurrent manipulations of the list (which shouldn't
+ * actually happen) we need to hold the spinlock. But under the spinlock we
+ * can't actually do much, so we defer processing to the todo list. Then run
+ * the todo list to be sure the operation and possibly previously pending
+ * operations are completed.
+ */
+static void ieee80211_todo_for_each_key(struct ieee80211_sub_if_data *sdata,
+ u32 todo_flags)
+{
+ struct ieee80211_key *key;
+ unsigned long flags;
+ might_sleep();
+
+ spin_lock_irqsave(&sdata->local->key_lock, flags);
list_for_each_entry(key, &sdata->key_list, list)
- ieee80211_key_enable_hw_accel(key);
+ add_todo(key, todo_flags);
+ spin_unlock_irqrestore(&sdata->local->key_lock, flags);
- ieee80211_key_unlock();
+ ieee80211_key_todo();
}
-void ieee80211_disable_keys(struct ieee80211_sub_if_data *sdata)
+void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata)
{
- struct ieee80211_key *key;
+ ASSERT_RTNL();
- might_sleep();
+ if (WARN_ON(!netif_running(sdata->dev)))
+ return;
- ieee80211_key_lock();
+ ieee80211_todo_for_each_key(sdata, KEY_FLAG_TODO_HWACCEL_ADD);
+}
- list_for_each_entry(key, &sdata->key_list, list)
- ieee80211_key_disable_hw_accel(key);
+void ieee80211_disable_keys(struct ieee80211_sub_if_data *sdata)
+{
+ ASSERT_RTNL();
- ieee80211_key_unlock();
+ ieee80211_todo_for_each_key(sdata, KEY_FLAG_TODO_HWACCEL_REMOVE);
}
-static void __ieee80211_key_free(struct ieee80211_key *key)
+static void __ieee80211_key_destroy(struct ieee80211_key *key)
{
if (!key)
return;
@@ -440,7 +456,8 @@ static void __ieee80211_key_todo(void)
list_del_init(&key->todo);
todoflags = key->flags & (KEY_FLAG_TODO_ADD_DEBUGFS |
KEY_FLAG_TODO_DEFKEY |
- KEY_FLAG_TODO_HWACCEL |
+ KEY_FLAG_TODO_HWACCEL_ADD |
+ KEY_FLAG_TODO_HWACCEL_REMOVE |
KEY_FLAG_TODO_DELETE);
key->flags &= ~todoflags;
spin_unlock(&todo_lock);
@@ -456,12 +473,16 @@ static void __ieee80211_key_todo(void)
ieee80211_debugfs_key_add_default(key->sdata);
work_done = true;
}
- if (todoflags & KEY_FLAG_TODO_HWACCEL) {
+ if (todoflags & KEY_FLAG_TODO_HWACCEL_ADD) {
ieee80211_key_enable_hw_accel(key);
work_done = true;
}
+ if (todoflags & KEY_FLAG_TODO_HWACCEL_REMOVE) {
+ ieee80211_key_disable_hw_accel(key);
+ work_done = true;
+ }
if (todoflags & KEY_FLAG_TODO_DELETE) {
- __ieee80211_key_free(key);
+ __ieee80211_key_destroy(key);
work_done = true;
}
@@ -482,14 +503,16 @@ void ieee80211_key_todo(void)
void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_key *key, *tmp;
- LIST_HEAD(tmp_list);
+ unsigned long flags;
ieee80211_key_lock();
ieee80211_debugfs_key_remove_default(sdata);
+ spin_lock_irqsave(&sdata->local->key_lock, flags);
list_for_each_entry_safe(key, tmp, &sdata->key_list, list)
- ieee80211_key_free(key);
+ __ieee80211_key_free(key);
+ spin_unlock_irqrestore(&sdata->local->key_lock, flags);
__ieee80211_key_todo();
diff --git a/net/mac80211/key.h b/net/mac80211/key.h
index 5d48518985b..f52c3df1fe9 100644
--- a/net/mac80211/key.h
+++ b/net/mac80211/key.h
@@ -54,16 +54,19 @@ struct sta_info;
* @KEY_FLAG_TODO_DELETE: Key is marked for deletion and will, after an
* RCU grace period, no longer be reachable other than from the
* todo list.
- * @KEY_FLAG_TODO_HWACCEL: Key needs to be added to hardware acceleration.
+ * @KEY_FLAG_TODO_HWACCEL_ADD: Key needs to be added to hardware acceleration.
+ * @KEY_FLAG_TODO_HWACCEL_REMOVE: Key needs to be removed from hardware
+ * acceleration.
* @KEY_FLAG_TODO_DEFKEY: Key is default key and debugfs needs to be updated.
* @KEY_FLAG_TODO_ADD_DEBUGFS: Key needs to be added to debugfs.
*/
enum ieee80211_internal_key_flags {
KEY_FLAG_UPLOADED_TO_HARDWARE = BIT(0),
KEY_FLAG_TODO_DELETE = BIT(1),
- KEY_FLAG_TODO_HWACCEL = BIT(2),
- KEY_FLAG_TODO_DEFKEY = BIT(3),
- KEY_FLAG_TODO_ADD_DEBUGFS = BIT(4),
+ KEY_FLAG_TODO_HWACCEL_ADD = BIT(2),
+ KEY_FLAG_TODO_HWACCEL_REMOVE = BIT(3),
+ KEY_FLAG_TODO_DEFKEY = BIT(4),
+ KEY_FLAG_TODO_ADD_DEBUGFS = BIT(5),
};
struct ieee80211_key {
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index bfcbcf5353a..e9a978979d3 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -1587,6 +1587,8 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
INIT_LIST_HEAD(&local->interfaces);
+ spin_lock_init(&local->key_lock);
+
INIT_DELAYED_WORK(&local->scan_work, ieee80211_sta_scan_work);
sta_info_init(local);
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index bdaab1391d4..6b75cb6c630 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -350,14 +350,12 @@ static void ieee80211_sta_wmm_params(struct net_device *dev,
}
}
-
-static u32 ieee80211_handle_erp_ie(struct ieee80211_sub_if_data *sdata,
- u8 erp_value)
+static u32 ieee80211_handle_protect_preamb(struct ieee80211_sub_if_data *sdata,
+ bool use_protection,
+ bool use_short_preamble)
{
struct ieee80211_bss_conf *bss_conf = &sdata->bss_conf;
struct ieee80211_if_sta *ifsta = &sdata->u.sta;
- bool use_protection = (erp_value & WLAN_ERP_USE_PROTECTION) != 0;
- bool use_short_preamble = (erp_value & WLAN_ERP_BARKER_PREAMBLE) == 0;
DECLARE_MAC_BUF(mac);
u32 changed = 0;
@@ -388,6 +386,32 @@ static u32 ieee80211_handle_erp_ie(struct ieee80211_sub_if_data *sdata,
return changed;
}
+static u32 ieee80211_handle_erp_ie(struct ieee80211_sub_if_data *sdata,
+ u8 erp_value)
+{
+ bool use_protection = (erp_value & WLAN_ERP_USE_PROTECTION) != 0;
+ bool use_short_preamble = (erp_value & WLAN_ERP_BARKER_PREAMBLE) == 0;
+
+ return ieee80211_handle_protect_preamb(sdata,
+ use_protection, use_short_preamble);
+}
+
+static u32 ieee80211_handle_bss_capability(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta_bss *bss)
+{
+ u32 changed = 0;
+
+ if (bss->has_erp_value)
+ changed |= ieee80211_handle_erp_ie(sdata, bss->erp_value);
+ else {
+ u16 capab = bss->capability;
+ changed |= ieee80211_handle_protect_preamb(sdata, false,
+ (capab & WLAN_CAPABILITY_SHORT_PREAMBLE) != 0);
+ }
+
+ return changed;
+}
+
int ieee80211_ht_cap_ie_to_ht_info(struct ieee80211_ht_cap *ht_cap_ie,
struct ieee80211_ht_info *ht_info)
{
@@ -511,9 +535,7 @@ static void ieee80211_set_associated(struct net_device *dev,
sdata->bss_conf.beacon_int = bss->beacon_int;
sdata->bss_conf.timestamp = bss->timestamp;
- if (bss->has_erp_value)
- changed |= ieee80211_handle_erp_ie(
- sdata, bss->erp_value);
+ changed |= ieee80211_handle_bss_capability(sdata, bss);
ieee80211_rx_bss_put(dev, bss);
}
@@ -2566,22 +2588,29 @@ static void ieee80211_rx_bss_info(struct net_device *dev,
#endif
}
- bss->band = rx_status->band;
-
- if (sdata->vif.type != IEEE80211_IF_TYPE_IBSS &&
- bss->probe_resp && beacon) {
- /* STA mode:
- * Do not allow beacon to override data from Probe Response. */
- ieee80211_rx_bss_put(dev, bss);
- return;
- }
-
/* save the ERP value so that it is available at association time */
if (elems.erp_info && elems.erp_info_len >= 1) {
bss->erp_value = elems.erp_info[0];
bss->has_erp_value = 1;
}
+ if (elems.ht_cap_elem &&
+ (!bss->ht_ie || bss->ht_ie_len != elems.ht_cap_elem_len ||
+ memcmp(bss->ht_ie, elems.ht_cap_elem, elems.ht_cap_elem_len))) {
+ kfree(bss->ht_ie);
+ bss->ht_ie = kmalloc(elems.ht_cap_elem_len + 2, GFP_ATOMIC);
+ if (bss->ht_ie) {
+ memcpy(bss->ht_ie, elems.ht_cap_elem - 2,
+ elems.ht_cap_elem_len + 2);
+ bss->ht_ie_len = elems.ht_cap_elem_len + 2;
+ } else
+ bss->ht_ie_len = 0;
+ } else if (!elems.ht_cap_elem && bss->ht_ie) {
+ kfree(bss->ht_ie);
+ bss->ht_ie = NULL;
+ bss->ht_ie_len = 0;
+ }
+
bss->beacon_int = le16_to_cpu(mgmt->u.beacon.beacon_int);
bss->capability = le16_to_cpu(mgmt->u.beacon.capab_info);
@@ -2603,6 +2632,26 @@ static void ieee80211_rx_bss_info(struct net_device *dev,
bss->supp_rates_len += clen;
}
+ bss->band = rx_status->band;
+
+ bss->timestamp = beacon_timestamp;
+ bss->last_update = jiffies;
+ bss->rssi = rx_status->ssi;
+ bss->signal = rx_status->signal;
+ bss->noise = rx_status->noise;
+ if (!beacon && !bss->probe_resp)
+ bss->probe_resp = true;
+
+ /*
+ * In STA mode, the remaining parameters should not be overridden
+ * by beacons because they're not necessarily accurate there.
+ */
+ if (sdata->vif.type != IEEE80211_IF_TYPE_IBSS &&
+ bss->probe_resp && beacon) {
+ ieee80211_rx_bss_put(dev, bss);
+ return;
+ }
+
if (elems.wpa &&
(!bss->wpa_ie || bss->wpa_ie_len != elems.wpa_len ||
memcmp(bss->wpa_ie, elems.wpa, elems.wpa_len))) {
@@ -2635,6 +2684,20 @@ static void ieee80211_rx_bss_info(struct net_device *dev,
bss->rsn_ie_len = 0;
}
+ /*
+ * Cf.
+ * http://www.wipo.int/pctdb/en/wo.jsp?wo=2007047181&IA=WO2007047181&DISPLAY=DESC
+ *
+ * quoting:
+ *
+ * In particular, "Wi-Fi CERTIFIED for WMM - Support for Multimedia
+ * Applications with Quality of Service in Wi-Fi Networks," Wi- Fi
+ * Alliance (September 1, 2004) is incorporated by reference herein.
+ * The inclusion of the WMM Parameters in probe responses and
+ * association responses is mandatory for WMM enabled networks. The
+ * inclusion of the WMM Parameters in beacons, however, is optional.
+ */
+
if (elems.wmm_param &&
(!bss->wmm_ie || bss->wmm_ie_len != elems.wmm_param_len ||
memcmp(bss->wmm_ie, elems.wmm_param, elems.wmm_param_len))) {
@@ -2651,30 +2714,6 @@ static void ieee80211_rx_bss_info(struct net_device *dev,
bss->wmm_ie = NULL;
bss->wmm_ie_len = 0;
}
- if (elems.ht_cap_elem &&
- (!bss->ht_ie || bss->ht_ie_len != elems.ht_cap_elem_len ||
- memcmp(bss->ht_ie, elems.ht_cap_elem, elems.ht_cap_elem_len))) {
- kfree(bss->ht_ie);
- bss->ht_ie = kmalloc(elems.ht_cap_elem_len + 2, GFP_ATOMIC);
- if (bss->ht_ie) {
- memcpy(bss->ht_ie, elems.ht_cap_elem - 2,
- elems.ht_cap_elem_len + 2);
- bss->ht_ie_len = elems.ht_cap_elem_len + 2;
- } else
- bss->ht_ie_len = 0;
- } else if (!elems.ht_cap_elem && bss->ht_ie) {
- kfree(bss->ht_ie);
- bss->ht_ie = NULL;
- bss->ht_ie_len = 0;
- }
-
- bss->timestamp = beacon_timestamp;
- bss->last_update = jiffies;
- bss->rssi = rx_status->ssi;
- bss->signal = rx_status->signal;
- bss->noise = rx_status->noise;
- if (!beacon)
- bss->probe_resp++;
/* check if we need to merge IBSS */
if (sdata->vif.type == IEEE80211_IF_TYPE_IBSS && beacon &&
@@ -2775,8 +2814,24 @@ static void ieee80211_rx_mgmt_beacon(struct net_device *dev,
ieee802_11_parse_elems(mgmt->u.beacon.variable, len - baselen, &elems);
+ if (elems.wmm_param && (ifsta->flags & IEEE80211_STA_WMM_ENABLED)) {
+ ieee80211_sta_wmm_params(dev, ifsta, elems.wmm_param,
+ elems.wmm_param_len);
+ }
+
+ /* Do not send changes to driver if we are scanning. This removes
+ * requirement that driver's bss_info_changed function needs to be
+ * atomic. */
+ if (local->sta_sw_scanning || local->sta_hw_scanning)
+ return;
+
if (elems.erp_info && elems.erp_info_len >= 1)
changed |= ieee80211_handle_erp_ie(sdata, elems.erp_info[0]);
+ else {
+ u16 capab = le16_to_cpu(mgmt->u.beacon.capab_info);
+ changed |= ieee80211_handle_protect_preamb(sdata, false,
+ (capab & WLAN_CAPABILITY_SHORT_PREAMBLE) != 0);
+ }
if (elems.ht_cap_elem && elems.ht_info_elem &&
elems.wmm_param && conf->flags & IEEE80211_CONF_SUPPORT_HT_MODE) {
@@ -2789,11 +2844,6 @@ static void ieee80211_rx_mgmt_beacon(struct net_device *dev,
&bss_info);
}
- if (elems.wmm_param && (ifsta->flags & IEEE80211_STA_WMM_ENABLED)) {
- ieee80211_sta_wmm_params(dev, ifsta, elems.wmm_param,
- elems.wmm_param_len);
- }
-
ieee80211_bss_info_change_notify(sdata, changed);
}
diff --git a/net/mac80211/wext.c b/net/mac80211/wext.c
index 69aed16faff..76e1de1dc73 100644
--- a/net/mac80211/wext.c
+++ b/net/mac80211/wext.c
@@ -236,6 +236,9 @@ static int ieee80211_ioctl_siwmode(struct net_device *dev,
case IW_MODE_ADHOC:
type = IEEE80211_IF_TYPE_IBSS;
break;
+ case IW_MODE_REPEAT:
+ type = IEEE80211_IF_TYPE_WDS;
+ break;
case IW_MODE_MONITOR:
type = IEEE80211_IF_TYPE_MNTR;
break;
@@ -980,6 +983,8 @@ static struct iw_statistics *ieee80211_get_wireless_stats(struct net_device *dev
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct sta_info *sta = NULL;
+ rcu_read_lock();
+
if (sdata->vif.type == IEEE80211_IF_TYPE_STA ||
sdata->vif.type == IEEE80211_IF_TYPE_IBSS)
sta = sta_info_get(local, sdata->u.sta.bssid);
@@ -996,6 +1001,9 @@ static struct iw_statistics *ieee80211_get_wireless_stats(struct net_device *dev
wstats->qual.noise = sta->last_noise;
wstats->qual.updated = local->wstats_flags;
}
+
+ rcu_read_unlock();
+
return wstats;
}