diff options
Diffstat (limited to 'net/mac80211')
58 files changed, 7182 insertions, 2827 deletions
diff --git a/net/mac80211/Kconfig b/net/mac80211/Kconfig index 62535fe9f57..97b5dcad502 100644 --- a/net/mac80211/Kconfig +++ b/net/mac80211/Kconfig @@ -4,6 +4,7 @@ config MAC80211 select CRYPTO select CRYPTO_ARC4 select CRYPTO_AES + select CRYPTO_CCM select CRC32 select AVERAGE ---help--- @@ -258,6 +259,17 @@ config MAC80211_MESH_SYNC_DEBUG Do not select this option. +config MAC80211_MESH_CSA_DEBUG + bool "Verbose mesh channel switch debugging" + depends on MAC80211_DEBUG_MENU + depends on MAC80211_MESH + ---help--- + Selecting this option causes mac80211 to print out very verbose mesh + channel switch debugging messages (when mac80211 is taking part in a + mesh network). + + Do not select this option. + config MAC80211_MESH_PS_DEBUG bool "Verbose mesh powersave debugging" depends on MAC80211_DEBUG_MENU diff --git a/net/mac80211/Makefile b/net/mac80211/Makefile index 9d7d840aac6..1e46ffa6916 100644 --- a/net/mac80211/Makefile +++ b/net/mac80211/Makefile @@ -25,7 +25,8 @@ mac80211-y := \ wme.o \ event.o \ chan.o \ - trace.o mlme.o + trace.o mlme.o \ + tdls.o mac80211-$(CONFIG_MAC80211_LEDS) += led.o mac80211-$(CONFIG_MAC80211_DEBUGFS) += \ diff --git a/net/mac80211/aes_ccm.c b/net/mac80211/aes_ccm.c index be7614b9ed2..ec24378caaa 100644 --- a/net/mac80211/aes_ccm.c +++ b/net/mac80211/aes_ccm.c @@ -2,6 +2,8 @@ * Copyright 2003-2004, Instant802 Networks, Inc. * Copyright 2005-2006, Devicescape Software, Inc. * + * Rewrite: Copyright (C) 2013 Linaro Ltd <ard.biesheuvel@linaro.org> + * * 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. @@ -17,134 +19,76 @@ #include "key.h" #include "aes_ccm.h" -static void aes_ccm_prepare(struct crypto_cipher *tfm, u8 *scratch, u8 *a) +void ieee80211_aes_ccm_encrypt(struct crypto_aead *tfm, u8 *b_0, u8 *aad, + u8 *data, size_t data_len, u8 *mic) { - int i; - u8 *b_0, *aad, *b, *s_0; - - b_0 = scratch + 3 * AES_BLOCK_SIZE; - aad = scratch + 4 * AES_BLOCK_SIZE; - b = scratch; - s_0 = scratch + AES_BLOCK_SIZE; - - crypto_cipher_encrypt_one(tfm, b, b_0); + struct scatterlist assoc, pt, ct[2]; - /* Extra Authenticate-only data (always two AES blocks) */ - for (i = 0; i < AES_BLOCK_SIZE; i++) - aad[i] ^= b[i]; - crypto_cipher_encrypt_one(tfm, b, aad); + char aead_req_data[sizeof(struct aead_request) + + crypto_aead_reqsize(tfm)] + __aligned(__alignof__(struct aead_request)); + struct aead_request *aead_req = (void *) aead_req_data; - aad += AES_BLOCK_SIZE; + memset(aead_req, 0, sizeof(aead_req_data)); - for (i = 0; i < AES_BLOCK_SIZE; i++) - aad[i] ^= b[i]; - crypto_cipher_encrypt_one(tfm, a, aad); + sg_init_one(&pt, data, data_len); + sg_init_one(&assoc, &aad[2], be16_to_cpup((__be16 *)aad)); + sg_init_table(ct, 2); + sg_set_buf(&ct[0], data, data_len); + sg_set_buf(&ct[1], mic, IEEE80211_CCMP_MIC_LEN); - /* Mask out bits from auth-only-b_0 */ - b_0[0] &= 0x07; + aead_request_set_tfm(aead_req, tfm); + aead_request_set_assoc(aead_req, &assoc, assoc.length); + aead_request_set_crypt(aead_req, &pt, ct, data_len, b_0); - /* S_0 is used to encrypt T (= MIC) */ - b_0[14] = 0; - b_0[15] = 0; - crypto_cipher_encrypt_one(tfm, s_0, b_0); + crypto_aead_encrypt(aead_req); } - -void ieee80211_aes_ccm_encrypt(struct crypto_cipher *tfm, u8 *scratch, - u8 *data, size_t data_len, - u8 *cdata, u8 *mic) +int ieee80211_aes_ccm_decrypt(struct crypto_aead *tfm, u8 *b_0, u8 *aad, + u8 *data, size_t data_len, u8 *mic) { - int i, j, last_len, num_blocks; - u8 *pos, *cpos, *b, *s_0, *e, *b_0; - - b = scratch; - s_0 = scratch + AES_BLOCK_SIZE; - e = scratch + 2 * AES_BLOCK_SIZE; - b_0 = scratch + 3 * AES_BLOCK_SIZE; - - num_blocks = DIV_ROUND_UP(data_len, AES_BLOCK_SIZE); - last_len = data_len % AES_BLOCK_SIZE; - aes_ccm_prepare(tfm, scratch, b); - - /* Process payload blocks */ - pos = data; - cpos = cdata; - for (j = 1; j <= num_blocks; j++) { - int blen = (j == num_blocks && last_len) ? - last_len : AES_BLOCK_SIZE; - - /* Authentication followed by encryption */ - for (i = 0; i < blen; i++) - b[i] ^= pos[i]; - crypto_cipher_encrypt_one(tfm, b, b); - - b_0[14] = (j >> 8) & 0xff; - b_0[15] = j & 0xff; - crypto_cipher_encrypt_one(tfm, e, b_0); - for (i = 0; i < blen; i++) - *cpos++ = *pos++ ^ e[i]; - } - - for (i = 0; i < IEEE80211_CCMP_MIC_LEN; i++) - mic[i] = b[i] ^ s_0[i]; + struct scatterlist assoc, pt, ct[2]; + char aead_req_data[sizeof(struct aead_request) + + crypto_aead_reqsize(tfm)] + __aligned(__alignof__(struct aead_request)); + struct aead_request *aead_req = (void *) aead_req_data; + + memset(aead_req, 0, sizeof(aead_req_data)); + + sg_init_one(&pt, data, data_len); + sg_init_one(&assoc, &aad[2], be16_to_cpup((__be16 *)aad)); + sg_init_table(ct, 2); + sg_set_buf(&ct[0], data, data_len); + sg_set_buf(&ct[1], mic, IEEE80211_CCMP_MIC_LEN); + + aead_request_set_tfm(aead_req, tfm); + aead_request_set_assoc(aead_req, &assoc, assoc.length); + aead_request_set_crypt(aead_req, ct, &pt, + data_len + IEEE80211_CCMP_MIC_LEN, b_0); + + return crypto_aead_decrypt(aead_req); } - -int ieee80211_aes_ccm_decrypt(struct crypto_cipher *tfm, u8 *scratch, - u8 *cdata, size_t data_len, u8 *mic, u8 *data) +struct crypto_aead *ieee80211_aes_key_setup_encrypt(const u8 key[]) { - int i, j, last_len, num_blocks; - u8 *pos, *cpos, *b, *s_0, *a, *b_0; - - b = scratch; - s_0 = scratch + AES_BLOCK_SIZE; - a = scratch + 2 * AES_BLOCK_SIZE; - b_0 = scratch + 3 * AES_BLOCK_SIZE; - - num_blocks = DIV_ROUND_UP(data_len, AES_BLOCK_SIZE); - last_len = data_len % AES_BLOCK_SIZE; - aes_ccm_prepare(tfm, scratch, a); - - /* Process payload blocks */ - cpos = cdata; - pos = data; - for (j = 1; j <= num_blocks; j++) { - int blen = (j == num_blocks && last_len) ? - last_len : AES_BLOCK_SIZE; - - /* Decryption followed by authentication */ - b_0[14] = (j >> 8) & 0xff; - b_0[15] = j & 0xff; - crypto_cipher_encrypt_one(tfm, b, b_0); - for (i = 0; i < blen; i++) { - *pos = *cpos++ ^ b[i]; - a[i] ^= *pos++; - } - crypto_cipher_encrypt_one(tfm, a, a); - } - - for (i = 0; i < IEEE80211_CCMP_MIC_LEN; i++) { - if ((mic[i] ^ s_0[i]) != a[i]) - return -1; - } - - return 0; -} - + struct crypto_aead *tfm; + int err; -struct crypto_cipher *ieee80211_aes_key_setup_encrypt(const u8 key[]) -{ - struct crypto_cipher *tfm; + tfm = crypto_alloc_aead("ccm(aes)", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) + return tfm; - tfm = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC); - if (!IS_ERR(tfm)) - crypto_cipher_setkey(tfm, key, WLAN_KEY_LEN_CCMP); + err = crypto_aead_setkey(tfm, key, WLAN_KEY_LEN_CCMP); + if (!err) + err = crypto_aead_setauthsize(tfm, IEEE80211_CCMP_MIC_LEN); + if (!err) + return tfm; - return tfm; + crypto_free_aead(tfm); + return ERR_PTR(err); } - -void ieee80211_aes_key_free(struct crypto_cipher *tfm) +void ieee80211_aes_key_free(struct crypto_aead *tfm) { - crypto_free_cipher(tfm); + crypto_free_aead(tfm); } diff --git a/net/mac80211/aes_ccm.h b/net/mac80211/aes_ccm.h index 5b7d744e237..2c7ab1948a2 100644 --- a/net/mac80211/aes_ccm.h +++ b/net/mac80211/aes_ccm.h @@ -12,13 +12,11 @@ #include <linux/crypto.h> -struct crypto_cipher *ieee80211_aes_key_setup_encrypt(const u8 key[]); -void ieee80211_aes_ccm_encrypt(struct crypto_cipher *tfm, u8 *scratch, - u8 *data, size_t data_len, - u8 *cdata, u8 *mic); -int ieee80211_aes_ccm_decrypt(struct crypto_cipher *tfm, u8 *scratch, - u8 *cdata, size_t data_len, - u8 *mic, u8 *data); -void ieee80211_aes_key_free(struct crypto_cipher *tfm); +struct crypto_aead *ieee80211_aes_key_setup_encrypt(const u8 key[]); +void ieee80211_aes_ccm_encrypt(struct crypto_aead *tfm, u8 *b_0, u8 *aad, + u8 *data, size_t data_len, u8 *mic); +int ieee80211_aes_ccm_decrypt(struct crypto_aead *tfm, u8 *b_0, u8 *aad, + u8 *data, size_t data_len, u8 *mic); +void ieee80211_aes_key_free(struct crypto_aead *tfm); #endif /* AES_CCM_H */ diff --git a/net/mac80211/aes_cmac.c b/net/mac80211/aes_cmac.c index 537488cbf94..9b9009f9955 100644 --- a/net/mac80211/aes_cmac.c +++ b/net/mac80211/aes_cmac.c @@ -111,7 +111,7 @@ void ieee80211_aes_cmac(struct crypto_cipher *tfm, const u8 *aad, } -struct crypto_cipher * ieee80211_aes_cmac_key_setup(const u8 key[]) +struct crypto_cipher *ieee80211_aes_cmac_key_setup(const u8 key[]) { struct crypto_cipher *tfm; diff --git a/net/mac80211/aes_cmac.h b/net/mac80211/aes_cmac.h index 20785a64725..0ce6487af79 100644 --- a/net/mac80211/aes_cmac.h +++ b/net/mac80211/aes_cmac.h @@ -11,7 +11,7 @@ #include <linux/crypto.h> -struct crypto_cipher * ieee80211_aes_cmac_key_setup(const u8 key[]); +struct crypto_cipher *ieee80211_aes_cmac_key_setup(const u8 key[]); void ieee80211_aes_cmac(struct crypto_cipher *tfm, const u8 *aad, const u8 *data, size_t data_len, u8 *mic); void ieee80211_aes_cmac_key_free(struct crypto_cipher *tfm); diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c index 13b7683de5a..ce9633a3cfb 100644 --- a/net/mac80211/agg-tx.c +++ b/net/mac80211/agg-tx.c @@ -107,7 +107,7 @@ static void ieee80211_send_addba_request(struct ieee80211_sub_if_data *sdata, mgmt->u.action.u.addba_req.start_seq_num = cpu_to_le16(start_seq_num << 4); - ieee80211_tx_skb_tid(sdata, skb, tid); + ieee80211_tx_skb(sdata, skb); } void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 2e7855a1b10..592f4b152ba 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -109,6 +109,15 @@ static int ieee80211_change_iface(struct wiphy *wiphy, static int ieee80211_start_p2p_device(struct wiphy *wiphy, struct wireless_dev *wdev) { + struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); + int ret; + + mutex_lock(&sdata->local->chanctx_mtx); + ret = ieee80211_check_combinations(sdata, NULL, 0, 0); + mutex_unlock(&sdata->local->chanctx_mtx); + if (ret < 0) + return ret; + return ieee80211_do_open(wdev, true); } @@ -133,7 +142,9 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev, struct key_params *params) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; struct sta_info *sta = NULL; + const struct ieee80211_cipher_scheme *cs = NULL; struct ieee80211_key *key; int err; @@ -145,22 +156,28 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev, case WLAN_CIPHER_SUITE_WEP40: case WLAN_CIPHER_SUITE_TKIP: case WLAN_CIPHER_SUITE_WEP104: - if (IS_ERR(sdata->local->wep_tx_tfm)) + if (IS_ERR(local->wep_tx_tfm)) return -EINVAL; break; + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_AES_CMAC: + case WLAN_CIPHER_SUITE_GCMP: + break; default: + cs = ieee80211_cs_get(local, params->cipher, sdata->vif.type); break; } key = ieee80211_key_alloc(params->cipher, key_idx, params->key_len, - params->key, params->seq_len, params->seq); + params->key, params->seq_len, params->seq, + cs); if (IS_ERR(key)) return PTR_ERR(key); if (pairwise) key->conf.flags |= IEEE80211_KEY_FLAG_PAIRWISE; - mutex_lock(&sdata->local->sta_mtx); + mutex_lock(&local->sta_mtx); if (mac_addr) { if (ieee80211_vif_is_mesh(&sdata->vif)) @@ -216,10 +233,13 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev, break; } + if (sta) + sta->cipher_scheme = cs; + err = ieee80211_key_link(key, sdata, sta); out_unlock: - mutex_unlock(&sdata->local->sta_mtx); + mutex_unlock(&local->sta_mtx); return err; } @@ -244,7 +264,7 @@ static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev, goto out_unlock; if (pairwise) - key = key_mtx_dereference(local, sta->ptk); + key = key_mtx_dereference(local, sta->ptk[key_idx]); else key = key_mtx_dereference(local, sta->gtk[key_idx]); } else @@ -290,9 +310,10 @@ static int ieee80211_get_key(struct wiphy *wiphy, struct net_device *dev, if (!sta) goto out; - if (pairwise) - key = rcu_dereference(sta->ptk); - else if (key_idx < NUM_DEFAULT_KEYS) + if (pairwise && key_idx < NUM_DEFAULT_KEYS) + key = rcu_dereference(sta->ptk[key_idx]); + else if (!pairwise && + key_idx < NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS) key = rcu_dereference(sta->gtk[key_idx]); } else key = rcu_dereference(sdata->keys[key_idx]); @@ -439,11 +460,11 @@ void sta_set_rate_info_rx(struct sta_info *sta, struct rate_info *rinfo) rinfo->flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH; if (sta->last_rx_rate_flag & RX_FLAG_SHORT_GI) rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI; - if (sta->last_rx_rate_flag & RX_FLAG_80MHZ) + if (sta->last_rx_rate_vht_flag & RX_VHT_FLAG_80MHZ) rinfo->flags |= RATE_INFO_FLAGS_80_MHZ_WIDTH; - if (sta->last_rx_rate_flag & RX_FLAG_80P80MHZ) + if (sta->last_rx_rate_vht_flag & RX_VHT_FLAG_80P80MHZ) rinfo->flags |= RATE_INFO_FLAGS_80P80_MHZ_WIDTH; - if (sta->last_rx_rate_flag & RX_FLAG_160MHZ) + if (sta->last_rx_rate_vht_flag & RX_VHT_FLAG_160MHZ) rinfo->flags |= RATE_INFO_FLAGS_160_MHZ_WIDTH; } @@ -451,10 +472,15 @@ static void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo) { struct ieee80211_sub_if_data *sdata = sta->sdata; struct ieee80211_local *local = sdata->local; + struct rate_control_ref *ref = NULL; struct timespec uptime; u64 packets = 0; + u32 thr = 0; int i, ac; + if (test_sta_flag(sta, WLAN_STA_RATE_CONTROL)) + ref = local->rate_ctrl; + sinfo->generation = sdata->local->sta_generation; sinfo->filled = STATION_INFO_INACTIVE_TIME | @@ -521,8 +547,8 @@ static void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo) STATION_INFO_PEER_PM | STATION_INFO_NONPEER_PM; - sinfo->llid = le16_to_cpu(sta->llid); - sinfo->plid = le16_to_cpu(sta->plid); + sinfo->llid = sta->llid; + sinfo->plid = sta->plid; sinfo->plink_state = sta->plink_state; if (test_sta_flag(sta, WLAN_STA_TOFFSET_KNOWN)) { sinfo->filled |= STATION_INFO_T_OFFSET; @@ -566,6 +592,17 @@ static void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo) sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_ASSOCIATED); if (test_sta_flag(sta, WLAN_STA_TDLS_PEER)) sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_TDLS_PEER); + + /* check if the driver has a SW RC implementation */ + if (ref && ref->ops->get_expected_throughput) + thr = ref->ops->get_expected_throughput(sta->rate_ctrl_priv); + else + thr = drv_get_expected_throughput(local, &sta->sta); + + if (thr != 0) { + sinfo->filled |= STATION_INFO_EXPECTED_THROUGHPUT; + sinfo->expected_throughput = thr; + } } static const char ieee80211_gstrings_sta_stats[][ETH_GSTRING_LEN] = { @@ -756,7 +793,7 @@ static void ieee80211_get_et_strings(struct wiphy *wiphy, } static int ieee80211_dump_station(struct wiphy *wiphy, struct net_device *dev, - int idx, u8 *mac, struct station_info *sinfo) + int idx, u8 *mac, struct station_info *sinfo) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; @@ -786,7 +823,7 @@ static int ieee80211_dump_survey(struct wiphy *wiphy, struct net_device *dev, } static int ieee80211_get_station(struct wiphy *wiphy, struct net_device *dev, - u8 *mac, struct station_info *sinfo) + const u8 *mac, struct station_info *sinfo) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; @@ -816,6 +853,7 @@ static int ieee80211_set_monitor_channel(struct wiphy *wiphy, if (cfg80211_chandef_identical(&local->monitor_chandef, chandef)) return 0; + mutex_lock(&local->mtx); mutex_lock(&local->iflist_mtx); if (local->use_chanctx) { sdata = rcu_dereference_protected( @@ -834,6 +872,7 @@ static int ieee80211_set_monitor_channel(struct wiphy *wiphy, if (ret == 0) local->monitor_chandef = *chandef; mutex_unlock(&local->iflist_mtx); + mutex_unlock(&local->mtx); return ret; } @@ -846,7 +885,7 @@ static int ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata, if (!resp || !resp_len) return 1; - old = rtnl_dereference(sdata->u.ap.probe_resp); + old = sdata_dereference(sdata->u.ap.probe_resp, sdata); new = kzalloc(sizeof(struct probe_resp) + resp_len, GFP_KERNEL); if (!new) @@ -862,15 +901,16 @@ static int ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata, return 0; } -int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata, - struct cfg80211_beacon_data *params) +static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata, + struct cfg80211_beacon_data *params) { struct beacon_data *new, *old; int new_head_len, new_tail_len; int size, err; u32 changed = BSS_CHANGED_BEACON; - old = rtnl_dereference(sdata->u.ap.beacon); + old = sdata_dereference(sdata->u.ap.beacon, sdata); + /* Need to have a beacon head if we don't have one yet */ if (!params->head && !old) @@ -938,6 +978,7 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_ap_settings *params) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; struct beacon_data *old; struct ieee80211_sub_if_data *vlan; u32 changed = BSS_CHANGED_BEACON_INT | @@ -947,20 +988,22 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, BSS_CHANGED_P2P_PS; int err; - old = rtnl_dereference(sdata->u.ap.beacon); + old = sdata_dereference(sdata->u.ap.beacon, sdata); if (old) return -EALREADY; /* TODO: make hostapd tell us what it wants */ sdata->smps_mode = IEEE80211_SMPS_OFF; sdata->needed_rx_chains = sdata->local->rx_chains; - sdata->radar_required = params->radar_required; + mutex_lock(&local->mtx); err = ieee80211_vif_use_channel(sdata, ¶ms->chandef, IEEE80211_CHANCTX_SHARED); + if (!err) + ieee80211_vif_copy_chanctx_to_vlans(sdata, false); + mutex_unlock(&local->mtx); if (err) return err; - ieee80211_vif_copy_chanctx_to_vlans(sdata, false); /* * Apply control port protocol, this allows us to @@ -968,11 +1011,19 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, */ sdata->control_port_protocol = params->crypto.control_port_ethertype; sdata->control_port_no_encrypt = params->crypto.control_port_no_encrypt; + sdata->encrypt_headroom = ieee80211_cs_headroom(sdata->local, + ¶ms->crypto, + sdata->vif.type); + list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) { vlan->control_port_protocol = params->crypto.control_port_ethertype; vlan->control_port_no_encrypt = params->crypto.control_port_no_encrypt; + vlan->encrypt_headroom = + ieee80211_cs_headroom(sdata->local, + ¶ms->crypto, + vlan->vif.type); } sdata->vif.bss_conf.beacon_int = params->beacon_interval; @@ -995,19 +1046,24 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, IEEE80211_P2P_OPPPS_ENABLE_BIT; err = ieee80211_assign_beacon(sdata, ¶ms->beacon); - if (err < 0) + if (err < 0) { + ieee80211_vif_release_channel(sdata); return err; + } changed |= err; err = drv_start_ap(sdata->local, sdata); if (err) { - old = rtnl_dereference(sdata->u.ap.beacon); + old = sdata_dereference(sdata->u.ap.beacon, sdata); + if (old) kfree_rcu(old, rcu_head); RCU_INIT_POINTER(sdata->u.ap.beacon, NULL); + ieee80211_vif_release_channel(sdata); return err; } + ieee80211_recalc_dtim(local, sdata); ieee80211_bss_info_change_notify(sdata, changed); netif_carrier_on(dev); @@ -1025,6 +1081,7 @@ static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev, int err; sdata = IEEE80211_DEV_TO_SUB_IF(dev); + sdata_assert_lock(sdata); /* don't allow changing the beacon while CSA is in place - offset * of channel switch counter may change @@ -1032,7 +1089,7 @@ static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev, if (sdata->vif.csa_active) return -EBUSY; - old = rtnl_dereference(sdata->u.ap.beacon); + old = sdata_dereference(sdata->u.ap.beacon, sdata); if (!old) return -ENOENT; @@ -1043,6 +1100,31 @@ static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev, return 0; } +bool ieee80211_csa_needs_block_tx(struct ieee80211_local *local) +{ + struct ieee80211_sub_if_data *sdata; + + lockdep_assert_held(&local->mtx); + + rcu_read_lock(); + list_for_each_entry_rcu(sdata, &local->interfaces, list) { + if (!ieee80211_sdata_running(sdata)) + continue; + + if (!sdata->vif.csa_active) + continue; + + if (!sdata->csa_block_tx) + continue; + + rcu_read_unlock(); + return true; + } + rcu_read_unlock(); + + return false; +} + static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); @@ -1050,15 +1132,26 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev) struct ieee80211_local *local = sdata->local; struct beacon_data *old_beacon; struct probe_resp *old_probe_resp; + struct cfg80211_chan_def chandef; - old_beacon = rtnl_dereference(sdata->u.ap.beacon); + sdata_assert_lock(sdata); + + old_beacon = sdata_dereference(sdata->u.ap.beacon, sdata); if (!old_beacon) return -ENOENT; - old_probe_resp = rtnl_dereference(sdata->u.ap.probe_resp); + old_probe_resp = sdata_dereference(sdata->u.ap.probe_resp, sdata); /* abort any running channel switch */ + mutex_lock(&local->mtx); sdata->vif.csa_active = false; - cancel_work_sync(&sdata->csa_finalize_work); + if (!ieee80211_csa_needs_block_tx(local)) + ieee80211_wake_queues_by_reason(&local->hw, + IEEE80211_MAX_QUEUE_MAP, + IEEE80211_QUEUE_STOP_REASON_CSA); + mutex_unlock(&local->mtx); + + kfree(sdata->u.ap.next_beacon); + sdata->u.ap.next_beacon = NULL; /* turn off carrier for this interface and dependent VLANs */ list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) @@ -1071,18 +1164,10 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev) kfree_rcu(old_beacon, rcu_head); if (old_probe_resp) kfree_rcu(old_probe_resp, rcu_head); + sdata->u.ap.driver_smps_mode = IEEE80211_SMPS_OFF; - list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) - sta_info_flush_defer(vlan); - sta_info_flush_defer(sdata); - synchronize_net(); - rcu_barrier(); - list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) { - sta_info_flush_cleanup(vlan); - ieee80211_free_keys(vlan); - } - sta_info_flush_cleanup(sdata); - ieee80211_free_keys(sdata); + __sta_info_flush(sdata, true); + ieee80211_free_keys(sdata, true); sdata->vif.bss_conf.enable_beacon = false; sdata->vif.bss_conf.ssid_len = 0; @@ -1090,8 +1175,10 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev) ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON_ENABLED); if (sdata->wdev.cac_started) { + chandef = sdata->vif.bss_conf.chandef; cancel_delayed_work_sync(&sdata->dfs_cac_timer_work); - cfg80211_cac_event(sdata->dev, NL80211_RADAR_CAC_ABORTED, + cfg80211_cac_event(sdata->dev, &chandef, + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL); } @@ -1101,8 +1188,10 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev) local->total_ps_buffered -= skb_queue_len(&sdata->u.ap.ps.bc_buf); skb_queue_purge(&sdata->u.ap.ps.bc_buf); + mutex_lock(&local->mtx); ieee80211_vif_copy_chanctx_to_vlans(sdata, true); ieee80211_vif_release_channel(sdata); + mutex_unlock(&local->mtx); return 0; } @@ -1314,6 +1403,15 @@ static int sta_apply_parameters(struct ieee80211_local *local, ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband, params->vht_capa, sta); + if (params->opmode_notif_used) { + /* returned value is only needed for rc update, but the + * rc isn't initialized here yet, so ignore it + */ + __ieee80211_vht_handle_opmode(sdata, sta, + params->opmode_notif, + band, false); + } + if (ieee80211_vif_is_mesh(&sdata->vif)) { #ifdef CONFIG_MAC80211_MESH u32 changed = 0; @@ -1342,8 +1440,8 @@ static int sta_apply_parameters(struct ieee80211_local *local, sta->plink_state = params->plink_state; ieee80211_mps_sta_status_update(sta); - changed |= - ieee80211_mps_local_status_update(sdata); + changed |= ieee80211_mps_set_sta_local_pm(sta, + NL80211_MESH_POWER_UNKNOWN); break; default: /* nothing */ @@ -1367,7 +1465,7 @@ static int sta_apply_parameters(struct ieee80211_local *local, changed |= ieee80211_mps_set_sta_local_pm(sta, params->local_pm); - ieee80211_bss_info_change_notify(sdata, changed); + ieee80211_mbss_info_change_notify(sdata, changed); #endif } @@ -1375,7 +1473,8 @@ static int sta_apply_parameters(struct ieee80211_local *local, } static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev, - u8 *mac, struct station_parameters *params) + const u8 *mac, + struct station_parameters *params) { struct ieee80211_local *local = wiphy_priv(wiphy); struct sta_info *sta; @@ -1409,6 +1508,8 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev, if (!(params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))) { sta_info_pre_move_state(sta, IEEE80211_STA_AUTH); sta_info_pre_move_state(sta, IEEE80211_STA_ASSOC); + } else { + sta->sta.tdls = true; } err = sta_apply_parameters(local, sta, params); @@ -1442,7 +1543,7 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev, } static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev, - u8 *mac) + const u8 *mac) { struct ieee80211_sub_if_data *sdata; @@ -1456,7 +1557,7 @@ static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev, } static int ieee80211_change_station(struct wiphy *wiphy, - struct net_device *dev, u8 *mac, + struct net_device *dev, const u8 *mac, struct station_parameters *params) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); @@ -1525,7 +1626,7 @@ static int ieee80211_change_station(struct wiphy *wiphy, if (sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN && sta->sdata->u.vlan.sta) { - rcu_assign_pointer(sta->sdata->u.vlan.sta, NULL); + RCU_INIT_POINTER(sta->sdata->u.vlan.sta, NULL); prev_4addr = true; } @@ -1553,6 +1654,20 @@ static int ieee80211_change_station(struct wiphy *wiphy, mutex_unlock(&local->sta_mtx); + if ((sdata->vif.type == NL80211_IFTYPE_AP || + sdata->vif.type == NL80211_IFTYPE_AP_VLAN) && + sta->known_smps_mode != sta->sdata->bss->req_smps && + test_sta_flag(sta, WLAN_STA_AUTHORIZED) && + sta_info_tx_streams(sta) != 1) { + ht_dbg(sta->sdata, + "%pM just authorized and MIMO capable - update SMPS\n", + sta->sta.addr); + ieee80211_send_smps_action(sta->sdata, + sta->sdata->bss->req_smps, + sta->sta.addr, + sta->sdata->vif.bss_conf.bssid); + } + if (sdata->vif.type == NL80211_IFTYPE_STATION && params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) { ieee80211_recalc_ps(local, -1); @@ -1567,7 +1682,7 @@ out_err: #ifdef CONFIG_MAC80211_MESH static int ieee80211_add_mpath(struct wiphy *wiphy, struct net_device *dev, - u8 *dst, u8 *next_hop) + const u8 *dst, const u8 *next_hop) { struct ieee80211_sub_if_data *sdata; struct mesh_path *mpath; @@ -1595,7 +1710,7 @@ static int ieee80211_add_mpath(struct wiphy *wiphy, struct net_device *dev, } static int ieee80211_del_mpath(struct wiphy *wiphy, struct net_device *dev, - u8 *dst) + const u8 *dst) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); @@ -1606,9 +1721,8 @@ static int ieee80211_del_mpath(struct wiphy *wiphy, struct net_device *dev, return 0; } -static int ieee80211_change_mpath(struct wiphy *wiphy, - struct net_device *dev, - u8 *dst, u8 *next_hop) +static int ieee80211_change_mpath(struct wiphy *wiphy, struct net_device *dev, + const u8 *dst, const u8 *next_hop) { struct ieee80211_sub_if_data *sdata; struct mesh_path *mpath; @@ -1700,8 +1814,8 @@ static int ieee80211_get_mpath(struct wiphy *wiphy, struct net_device *dev, } static int ieee80211_dump_mpath(struct wiphy *wiphy, struct net_device *dev, - int idx, u8 *dst, u8 *next_hop, - struct mpath_info *pinfo) + int idx, u8 *dst, u8 *next_hop, + struct mpath_info *pinfo) { struct ieee80211_sub_if_data *sdata; struct mesh_path *mpath; @@ -1911,8 +2025,10 @@ static int ieee80211_join_mesh(struct wiphy *wiphy, struct net_device *dev, sdata->smps_mode = IEEE80211_SMPS_OFF; sdata->needed_rx_chains = sdata->local->rx_chains; + mutex_lock(&sdata->local->mtx); err = ieee80211_vif_use_channel(sdata, &setup->chandef, IEEE80211_CHANCTX_SHARED); + mutex_unlock(&sdata->local->mtx); if (err) return err; @@ -1924,7 +2040,9 @@ static int ieee80211_leave_mesh(struct wiphy *wiphy, struct net_device *dev) struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); ieee80211_stop_mesh(sdata); + mutex_lock(&sdata->local->mtx); ieee80211_vif_release_channel(sdata); + mutex_unlock(&sdata->local->mtx); return 0; } @@ -1938,7 +2056,7 @@ static int ieee80211_change_bss(struct wiphy *wiphy, enum ieee80211_band band; u32 changed = 0; - if (!rtnl_dereference(sdata->u.ap.beacon)) + if (!sdata_dereference(sdata->u.ap.beacon, sdata)) return -ENOENT; band = ieee80211_get_sdata_band(sdata); @@ -2337,8 +2455,92 @@ static int ieee80211_testmode_dump(struct wiphy *wiphy, } #endif -int __ieee80211_request_smps(struct ieee80211_sub_if_data *sdata, - enum ieee80211_smps_mode smps_mode) +int __ieee80211_request_smps_ap(struct ieee80211_sub_if_data *sdata, + enum ieee80211_smps_mode smps_mode) +{ + struct sta_info *sta; + enum ieee80211_smps_mode old_req; + int i; + + if (WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_AP)) + return -EINVAL; + + if (sdata->vif.bss_conf.chandef.width == NL80211_CHAN_WIDTH_20_NOHT) + return 0; + + old_req = sdata->u.ap.req_smps; + sdata->u.ap.req_smps = smps_mode; + + /* AUTOMATIC doesn't mean much for AP - don't allow it */ + if (old_req == smps_mode || + smps_mode == IEEE80211_SMPS_AUTOMATIC) + return 0; + + /* If no associated stations, there's no need to do anything */ + if (!atomic_read(&sdata->u.ap.num_mcast_sta)) { + sdata->smps_mode = smps_mode; + ieee80211_queue_work(&sdata->local->hw, &sdata->recalc_smps); + return 0; + } + + ht_dbg(sdata, + "SMSP %d requested in AP mode, sending Action frame to %d stations\n", + smps_mode, atomic_read(&sdata->u.ap.num_mcast_sta)); + + mutex_lock(&sdata->local->sta_mtx); + for (i = 0; i < STA_HASH_SIZE; i++) { + for (sta = rcu_dereference_protected(sdata->local->sta_hash[i], + lockdep_is_held(&sdata->local->sta_mtx)); + sta; + sta = rcu_dereference_protected(sta->hnext, + lockdep_is_held(&sdata->local->sta_mtx))) { + /* + * Only stations associated to our AP and + * associated VLANs + */ + if (sta->sdata->bss != &sdata->u.ap) + continue; + + /* This station doesn't support MIMO - skip it */ + if (sta_info_tx_streams(sta) == 1) + continue; + + /* + * Don't wake up a STA just to send the action frame + * unless we are getting more restrictive. + */ + if (test_sta_flag(sta, WLAN_STA_PS_STA) && + !ieee80211_smps_is_restrictive(sta->known_smps_mode, + smps_mode)) { + ht_dbg(sdata, + "Won't send SMPS to sleeping STA %pM\n", + sta->sta.addr); + continue; + } + + /* + * If the STA is not authorized, wait until it gets + * authorized and the action frame will be sent then. + */ + if (!test_sta_flag(sta, WLAN_STA_AUTHORIZED)) + continue; + + ht_dbg(sdata, "Sending SMPS to %pM\n", sta->sta.addr); + ieee80211_send_smps_action(sdata, smps_mode, + sta->sta.addr, + sdata->vif.bss_conf.bssid); + } + } + mutex_unlock(&sdata->local->sta_mtx); + + sdata->smps_mode = smps_mode; + ieee80211_queue_work(&sdata->local->hw, &sdata->recalc_smps); + + return 0; +} + +int __ieee80211_request_smps_mgd(struct ieee80211_sub_if_data *sdata, + enum ieee80211_smps_mode smps_mode) { const u8 *ap; enum ieee80211_smps_mode old_req; @@ -2346,6 +2548,9 @@ int __ieee80211_request_smps(struct ieee80211_sub_if_data *sdata, lockdep_assert_held(&sdata->wdev.mtx); + if (WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_STATION)) + return -EINVAL; + old_req = sdata->u.mgd.req_smps; sdata->u.mgd.req_smps = smps_mode; @@ -2386,8 +2591,7 @@ static int ieee80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); - if (sdata->vif.type != NL80211_IFTYPE_STATION && - sdata->vif.type != NL80211_IFTYPE_MESH_POINT) + if (sdata->vif.type != NL80211_IFTYPE_STATION) return -EOPNOTSUPP; if (!(local->hw.flags & IEEE80211_HW_SUPPORTS_PS)) @@ -2402,7 +2606,7 @@ static int ieee80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, /* no change, but if automatic follow powersave */ sdata_lock(sdata); - __ieee80211_request_smps(sdata, sdata->u.mgd.req_smps); + __ieee80211_request_smps_mgd(sdata, sdata->u.mgd.req_smps); sdata_unlock(sdata); if (local->hw.flags & IEEE80211_HW_SUPPORTS_DYNAMIC_PS) @@ -2460,8 +2664,8 @@ static int ieee80211_set_bitrate_mask(struct wiphy *wiphy, int j; sdata->rc_rateidx_mask[i] = mask->control[i].legacy; - memcpy(sdata->rc_rateidx_mcs_mask[i], mask->control[i].mcs, - sizeof(mask->control[i].mcs)); + memcpy(sdata->rc_rateidx_mcs_mask[i], mask->control[i].ht_mcs, + sizeof(mask->control[i].ht_mcs)); sdata->rc_has_mcs_mask[i] = false; if (!sband) @@ -2497,6 +2701,18 @@ static int ieee80211_start_roc_work(struct ieee80211_local *local, if (!roc) return -ENOMEM; + /* + * If the duration is zero, then the driver + * wouldn't actually do anything. Set it to + * 10 for now. + * + * TODO: cancel the off-channel operation + * when we get the SKB's TX status and + * the wait time was zero before. + */ + if (!duration) + duration = 10; + roc->chan = channel; roc->duration = duration; roc->req_duration = duration; @@ -2507,6 +2723,24 @@ static int ieee80211_start_roc_work(struct ieee80211_local *local, INIT_DELAYED_WORK(&roc->work, ieee80211_sw_roc_work); INIT_LIST_HEAD(&roc->dependents); + /* + * cookie is either the roc cookie (for normal roc) + * or the SKB (for mgmt TX) + */ + if (!txskb) { + /* local->mtx protects this */ + local->roc_cookie_counter++; + roc->cookie = local->roc_cookie_counter; + /* wow, you wrapped 64 bits ... more likely a bug */ + if (WARN_ON(roc->cookie == 0)) { + roc->cookie = 1; + local->roc_cookie_counter++; + } + *cookie = roc->cookie; + } else { + *cookie = (unsigned long)txskb; + } + /* if there's one pending or we're scanning, queue this one */ if (!list_empty(&local->roc_list) || local->scanning || local->radar_detect_enabled) @@ -2520,18 +2754,6 @@ static int ieee80211_start_roc_work(struct ieee80211_local *local, /* otherwise actually kick it off here (for error handling) */ - /* - * If the duration is zero, then the driver - * wouldn't actually do anything. Set it to - * 10 for now. - * - * TODO: cancel the off-channel operation - * when we get the SKB's TX status and - * the wait time was zero before. - */ - if (!duration) - duration = 10; - ret = drv_remain_on_channel(local, sdata, channel, duration, type); if (ret) { kfree(roc); @@ -2641,24 +2863,6 @@ static int ieee80211_start_roc_work(struct ieee80211_local *local, if (!queued) list_add_tail(&roc->list, &local->roc_list); - /* - * cookie is either the roc cookie (for normal roc) - * or the SKB (for mgmt TX) - */ - if (!txskb) { - /* local->mtx protects this */ - local->roc_cookie_counter++; - roc->cookie = local->roc_cookie_counter; - /* wow, you wrapped 64 bits ... more likely a bug */ - if (WARN_ON(roc->cookie == 0)) { - roc->cookie = 1; - local->roc_cookie_counter++; - } - *cookie = roc->cookie; - } else { - *cookie = (unsigned long)txskb; - } - return 0; } @@ -2769,33 +2973,35 @@ static int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy, static int ieee80211_start_radar_detection(struct wiphy *wiphy, struct net_device *dev, - struct cfg80211_chan_def *chandef) + struct cfg80211_chan_def *chandef, + u32 cac_time_ms) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; - unsigned long timeout; int err; - if (!list_empty(&local->roc_list) || local->scanning) - return -EBUSY; + mutex_lock(&local->mtx); + if (!list_empty(&local->roc_list) || local->scanning) { + err = -EBUSY; + goto out_unlock; + } /* whatever, but channel contexts should not complain about that one */ sdata->smps_mode = IEEE80211_SMPS_OFF; sdata->needed_rx_chains = local->rx_chains; - sdata->radar_required = true; - mutex_lock(&local->iflist_mtx); err = ieee80211_vif_use_channel(sdata, chandef, IEEE80211_CHANCTX_SHARED); - mutex_unlock(&local->iflist_mtx); if (err) - return err; + goto out_unlock; - timeout = msecs_to_jiffies(IEEE80211_DFS_MIN_CAC_TIME_MS); ieee80211_queue_delayed_work(&sdata->local->hw, - &sdata->dfs_cac_timer_work, timeout); + &sdata->dfs_cac_timer_work, + msecs_to_jiffies(cac_time_ms)); - return 0; + out_unlock: + mutex_unlock(&local->mtx); + return err; } static struct cfg80211_beacon_data * @@ -2854,52 +3060,272 @@ cfg80211_beacon_dup(struct cfg80211_beacon_data *beacon) return new_beacon; } +void ieee80211_csa_finish(struct ieee80211_vif *vif) +{ + struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); + + ieee80211_queue_work(&sdata->local->hw, + &sdata->csa_finalize_work); +} +EXPORT_SYMBOL(ieee80211_csa_finish); + +static int ieee80211_set_after_csa_beacon(struct ieee80211_sub_if_data *sdata, + u32 *changed) +{ + int err; + + switch (sdata->vif.type) { + case NL80211_IFTYPE_AP: + err = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon); + kfree(sdata->u.ap.next_beacon); + sdata->u.ap.next_beacon = NULL; + + if (err < 0) + return err; + *changed |= err; + break; + case NL80211_IFTYPE_ADHOC: + err = ieee80211_ibss_finish_csa(sdata); + if (err < 0) + return err; + *changed |= err; + break; +#ifdef CONFIG_MAC80211_MESH + case NL80211_IFTYPE_MESH_POINT: + err = ieee80211_mesh_finish_csa(sdata); + if (err < 0) + return err; + *changed |= err; + break; +#endif + default: + WARN_ON(1); + return -EINVAL; + } + + return 0; +} + +static int __ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_local *local = sdata->local; + u32 changed = 0; + int err; + + sdata_assert_lock(sdata); + lockdep_assert_held(&local->mtx); + + sdata->radar_required = sdata->csa_radar_required; + err = ieee80211_vif_change_channel(sdata, &changed); + if (err < 0) + return err; + + if (!local->use_chanctx) { + local->_oper_chandef = sdata->csa_chandef; + ieee80211_hw_config(local, 0); + } + + sdata->vif.csa_active = false; + + err = ieee80211_set_after_csa_beacon(sdata, &changed); + if (err) + return err; + + ieee80211_bss_info_change_notify(sdata, changed); + cfg80211_ch_switch_notify(sdata->dev, &sdata->csa_chandef); + + if (!ieee80211_csa_needs_block_tx(local)) + ieee80211_wake_queues_by_reason(&local->hw, + IEEE80211_MAX_QUEUE_MAP, + IEEE80211_QUEUE_STOP_REASON_CSA); + + return 0; +} + +static void ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata) +{ + if (__ieee80211_csa_finalize(sdata)) { + sdata_info(sdata, "failed to finalize CSA, disconnecting\n"); + cfg80211_stop_iface(sdata->local->hw.wiphy, &sdata->wdev, + GFP_KERNEL); + } +} + void ieee80211_csa_finalize_work(struct work_struct *work) { struct ieee80211_sub_if_data *sdata = container_of(work, struct ieee80211_sub_if_data, csa_finalize_work); struct ieee80211_local *local = sdata->local; - int err, changed; + + sdata_lock(sdata); + mutex_lock(&local->mtx); + + /* AP might have been stopped while waiting for the lock. */ + if (!sdata->vif.csa_active) + goto unlock; if (!ieee80211_sdata_running(sdata)) - return; + goto unlock; - if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_AP)) - return; + ieee80211_csa_finalize(sdata); - sdata->radar_required = sdata->csa_radar_required; - err = ieee80211_vif_change_channel(sdata, &local->csa_chandef, - &changed); - if (WARN_ON(err < 0)) - return; +unlock: + mutex_unlock(&local->mtx); + sdata_unlock(sdata); +} - err = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon); - if (err < 0) - return; +static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata, + struct cfg80211_csa_settings *params, + u32 *changed) +{ + int err; - changed |= err; - kfree(sdata->u.ap.next_beacon); - sdata->u.ap.next_beacon = NULL; - sdata->vif.csa_active = false; + switch (sdata->vif.type) { + case NL80211_IFTYPE_AP: + sdata->u.ap.next_beacon = + cfg80211_beacon_dup(¶ms->beacon_after); + if (!sdata->u.ap.next_beacon) + return -ENOMEM; - ieee80211_wake_queues_by_reason(&sdata->local->hw, - IEEE80211_MAX_QUEUE_MAP, - IEEE80211_QUEUE_STOP_REASON_CSA); + /* + * With a count of 0, we don't have to wait for any + * TBTT before switching, so complete the CSA + * immediately. In theory, with a count == 1 we + * should delay the switch until just before the next + * TBTT, but that would complicate things so we switch + * immediately too. If we would delay the switch + * until the next TBTT, we would have to set the probe + * response here. + * + * TODO: A channel switch with count <= 1 without + * sending a CSA action frame is kind of useless, + * because the clients won't know we're changing + * channels. The action frame must be implemented + * either here or in the userspace. + */ + if (params->count <= 1) + break; - ieee80211_bss_info_change_notify(sdata, changed); + if ((params->n_counter_offsets_beacon > + IEEE80211_MAX_CSA_COUNTERS_NUM) || + (params->n_counter_offsets_presp > + IEEE80211_MAX_CSA_COUNTERS_NUM)) + return -EINVAL; + + /* make sure we don't have garbage in other counters */ + memset(sdata->csa_counter_offset_beacon, 0, + sizeof(sdata->csa_counter_offset_beacon)); + memset(sdata->csa_counter_offset_presp, 0, + sizeof(sdata->csa_counter_offset_presp)); + + memcpy(sdata->csa_counter_offset_beacon, + params->counter_offsets_beacon, + params->n_counter_offsets_beacon * sizeof(u16)); + memcpy(sdata->csa_counter_offset_presp, + params->counter_offsets_presp, + params->n_counter_offsets_presp * sizeof(u16)); + + err = ieee80211_assign_beacon(sdata, ¶ms->beacon_csa); + if (err < 0) { + kfree(sdata->u.ap.next_beacon); + return err; + } + *changed |= err; + + break; + case NL80211_IFTYPE_ADHOC: + if (!sdata->vif.bss_conf.ibss_joined) + return -EINVAL; + + if (params->chandef.width != sdata->u.ibss.chandef.width) + return -EINVAL; + + switch (params->chandef.width) { + case NL80211_CHAN_WIDTH_40: + if (cfg80211_get_chandef_type(¶ms->chandef) != + cfg80211_get_chandef_type(&sdata->u.ibss.chandef)) + return -EINVAL; + case NL80211_CHAN_WIDTH_5: + case NL80211_CHAN_WIDTH_10: + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + break; + default: + return -EINVAL; + } + + /* changes into another band are not supported */ + if (sdata->u.ibss.chandef.chan->band != + params->chandef.chan->band) + return -EINVAL; + + /* see comments in the NL80211_IFTYPE_AP block */ + if (params->count > 1) { + err = ieee80211_ibss_csa_beacon(sdata, params); + if (err < 0) + return err; + *changed |= err; + } + + ieee80211_send_action_csa(sdata, params); + + break; +#ifdef CONFIG_MAC80211_MESH + case NL80211_IFTYPE_MESH_POINT: { + struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + + if (params->chandef.width != sdata->vif.bss_conf.chandef.width) + return -EINVAL; + + /* changes into another band are not supported */ + if (sdata->vif.bss_conf.chandef.chan->band != + params->chandef.chan->band) + return -EINVAL; + + if (ifmsh->csa_role == IEEE80211_MESH_CSA_ROLE_NONE) { + ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_INIT; + if (!ifmsh->pre_value) + ifmsh->pre_value = 1; + else + ifmsh->pre_value++; + } + + /* see comments in the NL80211_IFTYPE_AP block */ + if (params->count > 1) { + err = ieee80211_mesh_csa_beacon(sdata, params); + if (err < 0) { + ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE; + return err; + } + *changed |= err; + } + + if (ifmsh->csa_role == IEEE80211_MESH_CSA_ROLE_INIT) + ieee80211_send_action_csa(sdata, params); + + break; + } +#endif + default: + return -EOPNOTSUPP; + } - cfg80211_ch_switch_notify(sdata->dev, &local->csa_chandef); + return 0; } -static int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, - struct cfg80211_csa_settings *params) +static int +__ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_csa_settings *params) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; - struct ieee80211_chanctx_conf *chanctx_conf; + struct ieee80211_chanctx_conf *conf; struct ieee80211_chanctx *chanctx; - int err, num_chanctx; + int err, num_chanctx, changed = 0; + + sdata_assert_lock(sdata); + lockdep_assert_held(&local->mtx); if (!list_empty(&local->roc_list) || local->scanning) return -EBUSY; @@ -2911,23 +3337,24 @@ static int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, &sdata->vif.bss_conf.chandef)) return -EINVAL; - rcu_read_lock(); - chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); - if (!chanctx_conf) { - rcu_read_unlock(); + mutex_lock(&local->chanctx_mtx); + conf = rcu_dereference_protected(sdata->vif.chanctx_conf, + lockdep_is_held(&local->chanctx_mtx)); + if (!conf) { + mutex_unlock(&local->chanctx_mtx); return -EBUSY; } /* don't handle for multi-VIF cases */ - chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf); - if (chanctx->refcount > 1) { - rcu_read_unlock(); + chanctx = container_of(conf, struct ieee80211_chanctx, conf); + if (ieee80211_chanctx_refcount(local, chanctx) > 1) { + mutex_unlock(&local->chanctx_mtx); return -EBUSY; } num_chanctx = 0; list_for_each_entry_rcu(chanctx, &local->chanctx_list, list) num_chanctx++; - rcu_read_unlock(); + mutex_unlock(&local->chanctx_mtx); if (num_chanctx > 1) return -EBUSY; @@ -2936,61 +3363,67 @@ static int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, if (sdata->vif.csa_active) return -EBUSY; - /* only handle AP for now. */ - switch (sdata->vif.type) { - case NL80211_IFTYPE_AP: - break; - default: - return -EOPNOTSUPP; - } - - sdata->u.ap.next_beacon = cfg80211_beacon_dup(¶ms->beacon_after); - if (!sdata->u.ap.next_beacon) - return -ENOMEM; + err = ieee80211_set_csa_beacon(sdata, params, &changed); + if (err) + return err; - sdata->csa_counter_offset_beacon = params->counter_offset_beacon; - sdata->csa_counter_offset_presp = params->counter_offset_presp; sdata->csa_radar_required = params->radar_required; + sdata->csa_chandef = params->chandef; + sdata->csa_block_tx = params->block_tx; + sdata->csa_current_counter = params->count; + sdata->vif.csa_active = true; - if (params->block_tx) + if (sdata->csa_block_tx) ieee80211_stop_queues_by_reason(&local->hw, - IEEE80211_MAX_QUEUE_MAP, - IEEE80211_QUEUE_STOP_REASON_CSA); + IEEE80211_MAX_QUEUE_MAP, + IEEE80211_QUEUE_STOP_REASON_CSA); - err = ieee80211_assign_beacon(sdata, ¶ms->beacon_csa); - if (err < 0) - return err; + if (changed) { + ieee80211_bss_info_change_notify(sdata, changed); + drv_channel_switch_beacon(sdata, ¶ms->chandef); + } else { + /* if the beacon didn't change, we can finalize immediately */ + ieee80211_csa_finalize(sdata); + } - local->csa_chandef = params->chandef; - sdata->vif.csa_active = true; + return 0; +} - ieee80211_bss_info_change_notify(sdata, err); - drv_channel_switch_beacon(sdata, ¶ms->chandef); +int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_csa_settings *params) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; + int err; - return 0; + mutex_lock(&local->mtx); + err = __ieee80211_channel_switch(wiphy, dev, params); + mutex_unlock(&local->mtx); + + return err; } static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, - struct ieee80211_channel *chan, bool offchan, - unsigned int wait, const u8 *buf, size_t len, - bool no_cck, bool dont_wait_for_ack, u64 *cookie) + struct cfg80211_mgmt_tx_params *params, + u64 *cookie) { struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); struct ieee80211_local *local = sdata->local; struct sk_buff *skb; struct sta_info *sta; - const struct ieee80211_mgmt *mgmt = (void *)buf; + const struct ieee80211_mgmt *mgmt = (void *)params->buf; bool need_offchan = false; u32 flags; int ret; + u8 *data; - if (dont_wait_for_ack) + if (params->dont_wait_for_ack) flags = IEEE80211_TX_CTL_NO_ACK; else flags = IEEE80211_TX_INTFL_NL80211_FRAME_TX | IEEE80211_TX_CTL_REQ_TX_STATUS; - if (no_cck) + if (params->no_cck) flags |= IEEE80211_TX_CTL_NO_CCK_RATE; switch (sdata->vif.type) { @@ -3014,7 +3447,8 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, need_offchan = true; if (!ieee80211_is_action(mgmt->frame_control) || mgmt->u.action.category == WLAN_CATEGORY_PUBLIC || - mgmt->u.action.category == WLAN_CATEGORY_SELF_PROTECTED) + mgmt->u.action.category == WLAN_CATEGORY_SELF_PROTECTED || + mgmt->u.action.category == WLAN_CATEGORY_SPECTRUM_MGMT) break; rcu_read_lock(); sta = sta_info_get(sdata, mgmt->da); @@ -3037,7 +3471,7 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, /* configurations requiring offchan cannot work if no channel has been * specified */ - if (need_offchan && !chan) + if (need_offchan && !params->chan) return -EINVAL; mutex_lock(&local->mtx); @@ -3050,8 +3484,10 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); if (chanctx_conf) { - need_offchan = chan && (chan != chanctx_conf->def.chan); - } else if (!chan) { + need_offchan = params->chan && + (params->chan != + chanctx_conf->def.chan); + } else if (!params->chan) { ret = -EINVAL; rcu_read_unlock(); goto out_unlock; @@ -3061,19 +3497,32 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, rcu_read_unlock(); } - if (need_offchan && !offchan) { + if (need_offchan && !params->offchan) { ret = -EBUSY; goto out_unlock; } - skb = dev_alloc_skb(local->hw.extra_tx_headroom + len); + skb = dev_alloc_skb(local->hw.extra_tx_headroom + params->len); if (!skb) { ret = -ENOMEM; goto out_unlock; } skb_reserve(skb, local->hw.extra_tx_headroom); - memcpy(skb_put(skb, len), buf, len); + data = skb_put(skb, params->len); + memcpy(data, params->buf, params->len); + + /* Update CSA counters */ + if (sdata->vif.csa_active && + (sdata->vif.type == NL80211_IFTYPE_AP || + sdata->vif.type == NL80211_IFTYPE_ADHOC) && + params->n_csa_offsets) { + int i; + u8 c = sdata->csa_current_counter; + + for (i = 0; i < params->n_csa_offsets; i++) + data[params->csa_offsets[i]] = c; + } IEEE80211_SKB_CB(skb)->flags = flags; @@ -3093,8 +3542,8 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, local->hw.offchannel_tx_hw_queue; /* This will handle all kinds of coalescing and immediate TX */ - ret = ieee80211_start_roc_work(local, sdata, chan, - wait, cookie, skb, + ret = ieee80211_start_roc_work(local, sdata, params->chan, + params->wait, cookie, skb, IEEE80211_ROC_TYPE_MGMT_TX); if (ret) kfree_skb(skb); @@ -3182,320 +3631,6 @@ static int ieee80211_set_rekey_data(struct wiphy *wiphy, return 0; } -static void ieee80211_tdls_add_ext_capab(struct sk_buff *skb) -{ - u8 *pos = (void *)skb_put(skb, 7); - - *pos++ = WLAN_EID_EXT_CAPABILITY; - *pos++ = 5; /* len */ - *pos++ = 0x0; - *pos++ = 0x0; - *pos++ = 0x0; - *pos++ = 0x0; - *pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED; -} - -static u16 ieee80211_get_tdls_sta_capab(struct ieee80211_sub_if_data *sdata) -{ - struct ieee80211_local *local = sdata->local; - u16 capab; - - capab = 0; - if (ieee80211_get_sdata_band(sdata) != IEEE80211_BAND_2GHZ) - return capab; - - if (!(local->hw.flags & IEEE80211_HW_2GHZ_SHORT_SLOT_INCAPABLE)) - capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME; - if (!(local->hw.flags & IEEE80211_HW_2GHZ_SHORT_PREAMBLE_INCAPABLE)) - capab |= WLAN_CAPABILITY_SHORT_PREAMBLE; - - return capab; -} - -static void ieee80211_tdls_add_link_ie(struct sk_buff *skb, u8 *src_addr, - u8 *peer, u8 *bssid) -{ - struct ieee80211_tdls_lnkie *lnkid; - - lnkid = (void *)skb_put(skb, sizeof(struct ieee80211_tdls_lnkie)); - - lnkid->ie_type = WLAN_EID_LINK_ID; - lnkid->ie_len = sizeof(struct ieee80211_tdls_lnkie) - 2; - - memcpy(lnkid->bssid, bssid, ETH_ALEN); - memcpy(lnkid->init_sta, src_addr, ETH_ALEN); - memcpy(lnkid->resp_sta, peer, ETH_ALEN); -} - -static int -ieee80211_prep_tdls_encap_data(struct wiphy *wiphy, struct net_device *dev, - u8 *peer, u8 action_code, u8 dialog_token, - u16 status_code, struct sk_buff *skb) -{ - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); - enum ieee80211_band band = ieee80211_get_sdata_band(sdata); - struct ieee80211_tdls_data *tf; - - tf = (void *)skb_put(skb, offsetof(struct ieee80211_tdls_data, u)); - - memcpy(tf->da, peer, ETH_ALEN); - memcpy(tf->sa, sdata->vif.addr, ETH_ALEN); - tf->ether_type = cpu_to_be16(ETH_P_TDLS); - tf->payload_type = WLAN_TDLS_SNAP_RFTYPE; - - switch (action_code) { - case WLAN_TDLS_SETUP_REQUEST: - tf->category = WLAN_CATEGORY_TDLS; - tf->action_code = WLAN_TDLS_SETUP_REQUEST; - - skb_put(skb, sizeof(tf->u.setup_req)); - tf->u.setup_req.dialog_token = dialog_token; - tf->u.setup_req.capability = - cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); - - ieee80211_add_srates_ie(sdata, skb, false, band); - ieee80211_add_ext_srates_ie(sdata, skb, false, band); - ieee80211_tdls_add_ext_capab(skb); - break; - case WLAN_TDLS_SETUP_RESPONSE: - tf->category = WLAN_CATEGORY_TDLS; - tf->action_code = WLAN_TDLS_SETUP_RESPONSE; - - skb_put(skb, sizeof(tf->u.setup_resp)); - tf->u.setup_resp.status_code = cpu_to_le16(status_code); - tf->u.setup_resp.dialog_token = dialog_token; - tf->u.setup_resp.capability = - cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); - - ieee80211_add_srates_ie(sdata, skb, false, band); - ieee80211_add_ext_srates_ie(sdata, skb, false, band); - ieee80211_tdls_add_ext_capab(skb); - break; - case WLAN_TDLS_SETUP_CONFIRM: - tf->category = WLAN_CATEGORY_TDLS; - tf->action_code = WLAN_TDLS_SETUP_CONFIRM; - - skb_put(skb, sizeof(tf->u.setup_cfm)); - tf->u.setup_cfm.status_code = cpu_to_le16(status_code); - tf->u.setup_cfm.dialog_token = dialog_token; - break; - case WLAN_TDLS_TEARDOWN: - tf->category = WLAN_CATEGORY_TDLS; - tf->action_code = WLAN_TDLS_TEARDOWN; - - skb_put(skb, sizeof(tf->u.teardown)); - tf->u.teardown.reason_code = cpu_to_le16(status_code); - break; - case WLAN_TDLS_DISCOVERY_REQUEST: - tf->category = WLAN_CATEGORY_TDLS; - tf->action_code = WLAN_TDLS_DISCOVERY_REQUEST; - - skb_put(skb, sizeof(tf->u.discover_req)); - tf->u.discover_req.dialog_token = dialog_token; - break; - default: - return -EINVAL; - } - - return 0; -} - -static int -ieee80211_prep_tdls_direct(struct wiphy *wiphy, struct net_device *dev, - u8 *peer, u8 action_code, u8 dialog_token, - u16 status_code, struct sk_buff *skb) -{ - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); - enum ieee80211_band band = ieee80211_get_sdata_band(sdata); - struct ieee80211_mgmt *mgmt; - - mgmt = (void *)skb_put(skb, 24); - memset(mgmt, 0, 24); - memcpy(mgmt->da, peer, ETH_ALEN); - memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN); - memcpy(mgmt->bssid, sdata->u.mgd.bssid, ETH_ALEN); - - mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | - IEEE80211_STYPE_ACTION); - - switch (action_code) { - case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: - skb_put(skb, 1 + sizeof(mgmt->u.action.u.tdls_discover_resp)); - mgmt->u.action.category = WLAN_CATEGORY_PUBLIC; - mgmt->u.action.u.tdls_discover_resp.action_code = - WLAN_PUB_ACTION_TDLS_DISCOVER_RES; - mgmt->u.action.u.tdls_discover_resp.dialog_token = - dialog_token; - mgmt->u.action.u.tdls_discover_resp.capability = - cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); - - ieee80211_add_srates_ie(sdata, skb, false, band); - ieee80211_add_ext_srates_ie(sdata, skb, false, band); - ieee80211_tdls_add_ext_capab(skb); - break; - default: - return -EINVAL; - } - - return 0; -} - -static int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, - u8 *peer, u8 action_code, u8 dialog_token, - u16 status_code, const u8 *extra_ies, - size_t extra_ies_len) -{ - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); - struct ieee80211_local *local = sdata->local; - struct sk_buff *skb = NULL; - bool send_direct; - int ret; - - if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) - return -ENOTSUPP; - - /* make sure we are in managed mode, and associated */ - if (sdata->vif.type != NL80211_IFTYPE_STATION || - !sdata->u.mgd.associated) - return -EINVAL; - - tdls_dbg(sdata, "TDLS mgmt action %d peer %pM\n", - action_code, peer); - - skb = dev_alloc_skb(local->hw.extra_tx_headroom + - max(sizeof(struct ieee80211_mgmt), - sizeof(struct ieee80211_tdls_data)) + - 50 + /* supported rates */ - 7 + /* ext capab */ - extra_ies_len + - sizeof(struct ieee80211_tdls_lnkie)); - if (!skb) - return -ENOMEM; - - skb_reserve(skb, local->hw.extra_tx_headroom); - - switch (action_code) { - case WLAN_TDLS_SETUP_REQUEST: - case WLAN_TDLS_SETUP_RESPONSE: - case WLAN_TDLS_SETUP_CONFIRM: - case WLAN_TDLS_TEARDOWN: - case WLAN_TDLS_DISCOVERY_REQUEST: - ret = ieee80211_prep_tdls_encap_data(wiphy, dev, peer, - action_code, dialog_token, - status_code, skb); - send_direct = false; - break; - case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: - ret = ieee80211_prep_tdls_direct(wiphy, dev, peer, action_code, - dialog_token, status_code, - skb); - send_direct = true; - break; - default: - ret = -ENOTSUPP; - break; - } - - if (ret < 0) - goto fail; - - if (extra_ies_len) - memcpy(skb_put(skb, extra_ies_len), extra_ies, extra_ies_len); - - /* the TDLS link IE is always added last */ - switch (action_code) { - case WLAN_TDLS_SETUP_REQUEST: - case WLAN_TDLS_SETUP_CONFIRM: - case WLAN_TDLS_TEARDOWN: - case WLAN_TDLS_DISCOVERY_REQUEST: - /* we are the initiator */ - ieee80211_tdls_add_link_ie(skb, sdata->vif.addr, peer, - sdata->u.mgd.bssid); - break; - case WLAN_TDLS_SETUP_RESPONSE: - case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: - /* we are the responder */ - ieee80211_tdls_add_link_ie(skb, peer, sdata->vif.addr, - sdata->u.mgd.bssid); - break; - default: - ret = -ENOTSUPP; - goto fail; - } - - if (send_direct) { - ieee80211_tx_skb(sdata, skb); - return 0; - } - - /* - * According to 802.11z: Setup req/resp are sent in AC_BK, otherwise - * we should default to AC_VI. - */ - switch (action_code) { - case WLAN_TDLS_SETUP_REQUEST: - case WLAN_TDLS_SETUP_RESPONSE: - skb_set_queue_mapping(skb, IEEE80211_AC_BK); - skb->priority = 2; - break; - default: - skb_set_queue_mapping(skb, IEEE80211_AC_VI); - skb->priority = 5; - break; - } - - /* disable bottom halves when entering the Tx path */ - local_bh_disable(); - ret = ieee80211_subif_start_xmit(skb, dev); - local_bh_enable(); - - return ret; - -fail: - dev_kfree_skb(skb); - return ret; -} - -static int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, - u8 *peer, enum nl80211_tdls_operation oper) -{ - struct sta_info *sta; - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); - - if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) - return -ENOTSUPP; - - if (sdata->vif.type != NL80211_IFTYPE_STATION) - return -EINVAL; - - tdls_dbg(sdata, "TDLS oper %d peer %pM\n", oper, peer); - - switch (oper) { - case NL80211_TDLS_ENABLE_LINK: - rcu_read_lock(); - sta = sta_info_get(sdata, peer); - if (!sta) { - rcu_read_unlock(); - return -ENOLINK; - } - - set_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH); - rcu_read_unlock(); - break; - case NL80211_TDLS_DISABLE_LINK: - return sta_info_destroy_addr(sdata, peer); - case NL80211_TDLS_TEARDOWN: - case NL80211_TDLS_SETUP: - case NL80211_TDLS_DISCOVERY_REQ: - /* We don't support in-driver setup/teardown/discovery */ - return -ENOTSUPP; - default: - return -ENOTSUPP; - } - - return 0; -} - static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev, const u8 *peer, u64 *cookie) { @@ -3518,7 +3653,7 @@ static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev, return -EINVAL; } band = chanctx_conf->def.chan->band; - sta = sta_info_get(sdata, peer); + sta = sta_info_get_bss(sdata, peer); if (sta) { qos = test_sta_flag(sta, WLAN_STA_WME); } else { @@ -3609,7 +3744,47 @@ static void ieee80211_set_wakeup(struct wiphy *wiphy, bool enabled) } #endif -struct cfg80211_ops mac80211_config_ops = { +static int ieee80211_set_qos_map(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_qos_map *qos_map) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct mac80211_qos_map *new_qos_map, *old_qos_map; + + if (qos_map) { + new_qos_map = kzalloc(sizeof(*new_qos_map), GFP_KERNEL); + if (!new_qos_map) + return -ENOMEM; + memcpy(&new_qos_map->qos_map, qos_map, sizeof(*qos_map)); + } else { + /* A NULL qos_map was passed to disable QoS mapping */ + new_qos_map = NULL; + } + + old_qos_map = sdata_dereference(sdata->qos_map, sdata); + rcu_assign_pointer(sdata->qos_map, new_qos_map); + if (old_qos_map) + kfree_rcu(old_qos_map, rcu_head); + + return 0; +} + +static int ieee80211_set_ap_chanwidth(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_chan_def *chandef) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + int ret; + u32 changed = 0; + + ret = ieee80211_vif_change_bandwidth(sdata, chandef, &changed); + if (ret == 0) + ieee80211_bss_info_change_notify(sdata, changed); + + return ret; +} + +const struct cfg80211_ops mac80211_config_ops = { .add_virtual_intf = ieee80211_add_iface, .del_virtual_intf = ieee80211_del_iface, .change_virtual_intf = ieee80211_change_iface, @@ -3688,4 +3863,6 @@ struct cfg80211_ops mac80211_config_ops = { .get_channel = ieee80211_cfg_get_channel, .start_radar_detection = ieee80211_start_radar_detection, .channel_switch = ieee80211_channel_switch, + .set_qos_map = ieee80211_set_qos_map, + .set_ap_chanwidth = ieee80211_set_ap_chanwidth, }; diff --git a/net/mac80211/cfg.h b/net/mac80211/cfg.h index 7d7879f5b00..2d51f62dc76 100644 --- a/net/mac80211/cfg.h +++ b/net/mac80211/cfg.h @@ -4,6 +4,6 @@ #ifndef __CFG_H #define __CFG_H -extern struct cfg80211_ops mac80211_config_ops; +extern const struct cfg80211_ops mac80211_config_ops; #endif /* __CFG_H */ diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 3a4764b2869..a310e33972d 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -9,6 +9,310 @@ #include "ieee80211_i.h" #include "driver-ops.h" +static int ieee80211_chanctx_num_assigned(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx) +{ + struct ieee80211_sub_if_data *sdata; + int num = 0; + + lockdep_assert_held(&local->chanctx_mtx); + + list_for_each_entry(sdata, &ctx->assigned_vifs, assigned_chanctx_list) + num++; + + return num; +} + +static int ieee80211_chanctx_num_reserved(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx) +{ + struct ieee80211_sub_if_data *sdata; + int num = 0; + + lockdep_assert_held(&local->chanctx_mtx); + + list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) + num++; + + return num; +} + +int ieee80211_chanctx_refcount(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx) +{ + return ieee80211_chanctx_num_assigned(local, ctx) + + ieee80211_chanctx_num_reserved(local, ctx); +} + +static int ieee80211_num_chanctx(struct ieee80211_local *local) +{ + struct ieee80211_chanctx *ctx; + int num = 0; + + lockdep_assert_held(&local->chanctx_mtx); + + list_for_each_entry(ctx, &local->chanctx_list, list) + num++; + + return num; +} + +static bool ieee80211_can_create_new_chanctx(struct ieee80211_local *local) +{ + lockdep_assert_held(&local->chanctx_mtx); + return ieee80211_num_chanctx(local) < ieee80211_max_num_channels(local); +} + +static const struct cfg80211_chan_def * +ieee80211_chanctx_reserved_chandef(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx, + const struct cfg80211_chan_def *compat) +{ + struct ieee80211_sub_if_data *sdata; + + lockdep_assert_held(&local->chanctx_mtx); + + list_for_each_entry(sdata, &ctx->reserved_vifs, + reserved_chanctx_list) { + if (!compat) + compat = &sdata->reserved_chandef; + + compat = cfg80211_chandef_compatible(&sdata->reserved_chandef, + compat); + if (!compat) + break; + } + + return compat; +} + +static const struct cfg80211_chan_def * +ieee80211_chanctx_non_reserved_chandef(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx, + const struct cfg80211_chan_def *compat) +{ + struct ieee80211_sub_if_data *sdata; + + lockdep_assert_held(&local->chanctx_mtx); + + list_for_each_entry(sdata, &ctx->assigned_vifs, + assigned_chanctx_list) { + if (sdata->reserved_chanctx != NULL) + continue; + + if (!compat) + compat = &sdata->vif.bss_conf.chandef; + + compat = cfg80211_chandef_compatible( + &sdata->vif.bss_conf.chandef, compat); + if (!compat) + break; + } + + return compat; +} + +static const struct cfg80211_chan_def * +ieee80211_chanctx_combined_chandef(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx, + const struct cfg80211_chan_def *compat) +{ + lockdep_assert_held(&local->chanctx_mtx); + + compat = ieee80211_chanctx_reserved_chandef(local, ctx, compat); + if (!compat) + return NULL; + + compat = ieee80211_chanctx_non_reserved_chandef(local, ctx, compat); + if (!compat) + return NULL; + + return compat; +} + +static bool +ieee80211_chanctx_can_reserve_chandef(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx, + const struct cfg80211_chan_def *def) +{ + lockdep_assert_held(&local->chanctx_mtx); + + if (ieee80211_chanctx_combined_chandef(local, ctx, def)) + return true; + + if (!list_empty(&ctx->reserved_vifs) && + ieee80211_chanctx_reserved_chandef(local, ctx, def)) + return true; + + return false; +} + +static struct ieee80211_chanctx * +ieee80211_find_reservation_chanctx(struct ieee80211_local *local, + const struct cfg80211_chan_def *chandef, + enum ieee80211_chanctx_mode mode) +{ + struct ieee80211_chanctx *ctx; + + lockdep_assert_held(&local->chanctx_mtx); + + if (mode == IEEE80211_CHANCTX_EXCLUSIVE) + return NULL; + + list_for_each_entry(ctx, &local->chanctx_list, list) { + if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) + continue; + + if (!ieee80211_chanctx_can_reserve_chandef(local, ctx, + chandef)) + continue; + + return ctx; + } + + return NULL; +} + +static enum nl80211_chan_width ieee80211_get_sta_bw(struct ieee80211_sta *sta) +{ + switch (sta->bandwidth) { + case IEEE80211_STA_RX_BW_20: + if (sta->ht_cap.ht_supported) + return NL80211_CHAN_WIDTH_20; + else + return NL80211_CHAN_WIDTH_20_NOHT; + case IEEE80211_STA_RX_BW_40: + return NL80211_CHAN_WIDTH_40; + case IEEE80211_STA_RX_BW_80: + return NL80211_CHAN_WIDTH_80; + case IEEE80211_STA_RX_BW_160: + /* + * This applied for both 160 and 80+80. since we use + * the returned value to consider degradation of + * ctx->conf.min_def, we have to make sure to take + * the bigger one (NL80211_CHAN_WIDTH_160). + * Otherwise we might try degrading even when not + * needed, as the max required sta_bw returned (80+80) + * might be smaller than the configured bw (160). + */ + return NL80211_CHAN_WIDTH_160; + default: + WARN_ON(1); + return NL80211_CHAN_WIDTH_20; + } +} + +static enum nl80211_chan_width +ieee80211_get_max_required_bw(struct ieee80211_sub_if_data *sdata) +{ + enum nl80211_chan_width max_bw = NL80211_CHAN_WIDTH_20_NOHT; + struct sta_info *sta; + + rcu_read_lock(); + list_for_each_entry_rcu(sta, &sdata->local->sta_list, list) { + if (sdata != sta->sdata && + !(sta->sdata->bss && sta->sdata->bss == sdata->bss)) + continue; + + if (!sta->uploaded) + continue; + + max_bw = max(max_bw, ieee80211_get_sta_bw(&sta->sta)); + } + rcu_read_unlock(); + + return max_bw; +} + +static enum nl80211_chan_width +ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local, + struct ieee80211_chanctx_conf *conf) +{ + struct ieee80211_sub_if_data *sdata; + enum nl80211_chan_width max_bw = NL80211_CHAN_WIDTH_20_NOHT; + + rcu_read_lock(); + list_for_each_entry_rcu(sdata, &local->interfaces, list) { + struct ieee80211_vif *vif = &sdata->vif; + enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20_NOHT; + + if (!ieee80211_sdata_running(sdata)) + continue; + + if (rcu_access_pointer(sdata->vif.chanctx_conf) != conf) + continue; + + switch (vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_AP_VLAN: + width = ieee80211_get_max_required_bw(sdata); + break; + case NL80211_IFTYPE_P2P_DEVICE: + continue; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_WDS: + case NL80211_IFTYPE_MESH_POINT: + width = vif->bss_conf.chandef.width; + break; + case NL80211_IFTYPE_UNSPECIFIED: + case NUM_NL80211_IFTYPES: + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: + WARN_ON_ONCE(1); + } + max_bw = max(max_bw, width); + } + + /* use the configured bandwidth in case of monitor interface */ + sdata = rcu_dereference(local->monitor_sdata); + if (sdata && rcu_access_pointer(sdata->vif.chanctx_conf) == conf) + max_bw = max(max_bw, conf->def.width); + + rcu_read_unlock(); + + return max_bw; +} + +/* + * recalc the min required chan width of the channel context, which is + * the max of min required widths of all the interfaces bound to this + * channel context. + */ +void ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx) +{ + enum nl80211_chan_width max_bw; + struct cfg80211_chan_def min_def; + + lockdep_assert_held(&local->chanctx_mtx); + + /* don't optimize 5MHz, 10MHz, and radar_enabled confs */ + if (ctx->conf.def.width == NL80211_CHAN_WIDTH_5 || + ctx->conf.def.width == NL80211_CHAN_WIDTH_10 || + ctx->conf.radar_enabled) { + ctx->conf.min_def = ctx->conf.def; + return; + } + + max_bw = ieee80211_get_chanctx_max_required_bw(local, &ctx->conf); + + /* downgrade chandef up to max_bw */ + min_def = ctx->conf.def; + while (min_def.width > max_bw) + ieee80211_chandef_downgrade(&min_def); + + if (cfg80211_chandef_identical(&ctx->conf.min_def, &min_def)) + return; + + ctx->conf.min_def = min_def; + if (!ctx->driver_present) + return; + + drv_change_chanctx(local, ctx, IEEE80211_CHANCTX_CHANGE_MIN_WIDTH); +} + static void ieee80211_change_chanctx(struct ieee80211_local *local, struct ieee80211_chanctx *ctx, const struct cfg80211_chan_def *chandef) @@ -20,6 +324,7 @@ static void ieee80211_change_chanctx(struct ieee80211_local *local, ctx->conf.def = *chandef; drv_change_chanctx(local, ctx, IEEE80211_CHANCTX_CHANGE_WIDTH); + ieee80211_recalc_chanctx_min_def(local, ctx); if (!local->use_chanctx) { local->_oper_chandef = *chandef; @@ -49,6 +354,11 @@ ieee80211_find_chanctx(struct ieee80211_local *local, if (!compat) continue; + compat = ieee80211_chanctx_reserved_chandef(local, ctx, + compat); + if (!compat) + continue; + ieee80211_change_chanctx(local, ctx, compat); return ctx; @@ -61,6 +371,8 @@ static bool ieee80211_is_radar_required(struct ieee80211_local *local) { struct ieee80211_sub_if_data *sdata; + lockdep_assert_held(&local->mtx); + rcu_read_lock(); list_for_each_entry_rcu(sdata, &local->interfaces, list) { if (sdata->radar_required) { @@ -74,66 +386,91 @@ static bool ieee80211_is_radar_required(struct ieee80211_local *local) } static struct ieee80211_chanctx * -ieee80211_new_chanctx(struct ieee80211_local *local, - const struct cfg80211_chan_def *chandef, - enum ieee80211_chanctx_mode mode) +ieee80211_alloc_chanctx(struct ieee80211_local *local, + const struct cfg80211_chan_def *chandef, + enum ieee80211_chanctx_mode mode) { struct ieee80211_chanctx *ctx; - u32 changed; - int err; lockdep_assert_held(&local->chanctx_mtx); ctx = kzalloc(sizeof(*ctx) + local->hw.chanctx_data_size, GFP_KERNEL); if (!ctx) - return ERR_PTR(-ENOMEM); + return NULL; + INIT_LIST_HEAD(&ctx->assigned_vifs); + INIT_LIST_HEAD(&ctx->reserved_vifs); ctx->conf.def = *chandef; ctx->conf.rx_chains_static = 1; ctx->conf.rx_chains_dynamic = 1; ctx->mode = mode; ctx->conf.radar_enabled = ieee80211_is_radar_required(local); + ieee80211_recalc_chanctx_min_def(local, ctx); + + return ctx; +} + +static int ieee80211_add_chanctx(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx) +{ + u32 changed; + int err; + + lockdep_assert_held(&local->mtx); + lockdep_assert_held(&local->chanctx_mtx); + if (!local->use_chanctx) local->hw.conf.radar_enabled = ctx->conf.radar_enabled; - /* acquire mutex to prevent idle from changing */ - mutex_lock(&local->mtx); /* turn idle off *before* setting channel -- some drivers need that */ changed = ieee80211_idle_off(local); if (changed) ieee80211_hw_config(local, changed); if (!local->use_chanctx) { - local->_oper_chandef = *chandef; - ieee80211_hw_config(local, 0); + local->_oper_chandef = ctx->conf.def; + ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); } else { err = drv_add_chanctx(local, ctx); if (err) { - kfree(ctx); - ctx = ERR_PTR(err); - ieee80211_recalc_idle(local); - goto out; + return err; } } - /* and keep the mutex held until the new chanctx is on the list */ - list_add_rcu(&ctx->list, &local->chanctx_list); + return 0; +} - out: - mutex_unlock(&local->mtx); +static struct ieee80211_chanctx * +ieee80211_new_chanctx(struct ieee80211_local *local, + const struct cfg80211_chan_def *chandef, + enum ieee80211_chanctx_mode mode) +{ + struct ieee80211_chanctx *ctx; + int err; + + lockdep_assert_held(&local->mtx); + lockdep_assert_held(&local->chanctx_mtx); + ctx = ieee80211_alloc_chanctx(local, chandef, mode); + if (!ctx) + return ERR_PTR(-ENOMEM); + + err = ieee80211_add_chanctx(local, ctx); + if (err) { + kfree(ctx); + return ERR_PTR(err); + } + + list_add_rcu(&ctx->list, &local->chanctx_list); return ctx; } -static void ieee80211_free_chanctx(struct ieee80211_local *local, - struct ieee80211_chanctx *ctx) +static void ieee80211_del_chanctx(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx) { - bool check_single_channel = false; lockdep_assert_held(&local->chanctx_mtx); - WARN_ON_ONCE(ctx->refcount != 0); - if (!local->use_chanctx) { struct cfg80211_chan_def *chandef = &local->_oper_chandef; chandef->width = NL80211_CHAN_WIDTH_20_NOHT; @@ -143,49 +480,29 @@ static void ieee80211_free_chanctx(struct ieee80211_local *local, /* NOTE: Disabling radar is only valid here for * single channel context. To be sure, check it ... */ - if (local->hw.conf.radar_enabled) - check_single_channel = true; + WARN_ON(local->hw.conf.radar_enabled && + !list_empty(&local->chanctx_list)); + local->hw.conf.radar_enabled = false; - ieee80211_hw_config(local, 0); + ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); } else { drv_remove_chanctx(local, ctx); } - list_del_rcu(&ctx->list); - kfree_rcu(ctx, rcu_head); - - /* throw a warning if this wasn't the only channel context. */ - WARN_ON(check_single_channel && !list_empty(&local->chanctx_list)); - - mutex_lock(&local->mtx); ieee80211_recalc_idle(local); - mutex_unlock(&local->mtx); } -static int ieee80211_assign_vif_chanctx(struct ieee80211_sub_if_data *sdata, - struct ieee80211_chanctx *ctx) +static void ieee80211_free_chanctx(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx) { - struct ieee80211_local *local = sdata->local; - int ret; - lockdep_assert_held(&local->chanctx_mtx); - ret = drv_assign_vif_chanctx(local, sdata, ctx); - if (ret) - return ret; - - rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf); - ctx->refcount++; - - ieee80211_recalc_txpower(sdata); - sdata->vif.bss_conf.idle = false; - - if (sdata->vif.type != NL80211_IFTYPE_P2P_DEVICE && - sdata->vif.type != NL80211_IFTYPE_MONITOR) - ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_IDLE); + WARN_ON_ONCE(ieee80211_chanctx_refcount(local, ctx) != 0); - return 0; + list_del_rcu(&ctx->list); + ieee80211_del_chanctx(local, ctx); + kfree_rcu(ctx, rcu_head); } static void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local, @@ -221,72 +538,106 @@ static void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local, ieee80211_change_chanctx(local, ctx, compat); } -static void ieee80211_unassign_vif_chanctx(struct ieee80211_sub_if_data *sdata, - struct ieee80211_chanctx *ctx) +static void ieee80211_recalc_radar_chanctx(struct ieee80211_local *local, + struct ieee80211_chanctx *chanctx) { - struct ieee80211_local *local = sdata->local; + bool radar_enabled; lockdep_assert_held(&local->chanctx_mtx); + /* for setting local->radar_detect_enabled */ + lockdep_assert_held(&local->mtx); - ctx->refcount--; - rcu_assign_pointer(sdata->vif.chanctx_conf, NULL); - - sdata->vif.bss_conf.idle = true; + radar_enabled = ieee80211_is_radar_required(local); - if (sdata->vif.type != NL80211_IFTYPE_P2P_DEVICE && - sdata->vif.type != NL80211_IFTYPE_MONITOR) - ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_IDLE); + if (radar_enabled == chanctx->conf.radar_enabled) + return; - drv_unassign_vif_chanctx(local, sdata, ctx); + chanctx->conf.radar_enabled = radar_enabled; + local->radar_detect_enabled = chanctx->conf.radar_enabled; - if (ctx->refcount > 0) { - ieee80211_recalc_chanctx_chantype(sdata->local, ctx); - ieee80211_recalc_smps_chanctx(local, ctx); - ieee80211_recalc_radar_chanctx(local, ctx); + if (!local->use_chanctx) { + local->hw.conf.radar_enabled = chanctx->conf.radar_enabled; + ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); } + + drv_change_chanctx(local, chanctx, IEEE80211_CHANCTX_CHANGE_RADAR); } -static void __ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata) +static int ieee80211_assign_vif_chanctx(struct ieee80211_sub_if_data *sdata, + struct ieee80211_chanctx *new_ctx) { struct ieee80211_local *local = sdata->local; struct ieee80211_chanctx_conf *conf; - struct ieee80211_chanctx *ctx; - - lockdep_assert_held(&local->chanctx_mtx); + struct ieee80211_chanctx *curr_ctx = NULL; + int ret = 0; conf = rcu_dereference_protected(sdata->vif.chanctx_conf, lockdep_is_held(&local->chanctx_mtx)); - if (!conf) - return; - ctx = container_of(conf, struct ieee80211_chanctx, conf); + if (conf) { + curr_ctx = container_of(conf, struct ieee80211_chanctx, conf); - ieee80211_unassign_vif_chanctx(sdata, ctx); - if (ctx->refcount == 0) - ieee80211_free_chanctx(local, ctx); + drv_unassign_vif_chanctx(local, sdata, curr_ctx); + conf = NULL; + list_del(&sdata->assigned_chanctx_list); + } + + if (new_ctx) { + ret = drv_assign_vif_chanctx(local, sdata, new_ctx); + if (ret) + goto out; + + conf = &new_ctx->conf; + list_add(&sdata->assigned_chanctx_list, + &new_ctx->assigned_vifs); + } + +out: + rcu_assign_pointer(sdata->vif.chanctx_conf, conf); + + sdata->vif.bss_conf.idle = !conf; + + if (curr_ctx && ieee80211_chanctx_num_assigned(local, curr_ctx) > 0) { + ieee80211_recalc_chanctx_chantype(local, curr_ctx); + ieee80211_recalc_smps_chanctx(local, curr_ctx); + ieee80211_recalc_radar_chanctx(local, curr_ctx); + ieee80211_recalc_chanctx_min_def(local, curr_ctx); + } + + if (new_ctx && ieee80211_chanctx_num_assigned(local, new_ctx) > 0) { + ieee80211_recalc_txpower(sdata); + ieee80211_recalc_chanctx_min_def(local, new_ctx); + } + + if (sdata->vif.type != NL80211_IFTYPE_P2P_DEVICE && + sdata->vif.type != NL80211_IFTYPE_MONITOR) + ieee80211_bss_info_change_notify(sdata, + BSS_CHANGED_IDLE); + + return ret; } -void ieee80211_recalc_radar_chanctx(struct ieee80211_local *local, - struct ieee80211_chanctx *chanctx) +static void __ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata) { - bool radar_enabled; + struct ieee80211_local *local = sdata->local; + struct ieee80211_chanctx_conf *conf; + struct ieee80211_chanctx *ctx; lockdep_assert_held(&local->chanctx_mtx); - radar_enabled = ieee80211_is_radar_required(local); - - if (radar_enabled == chanctx->conf.radar_enabled) + conf = rcu_dereference_protected(sdata->vif.chanctx_conf, + lockdep_is_held(&local->chanctx_mtx)); + if (!conf) return; - chanctx->conf.radar_enabled = radar_enabled; - local->radar_detect_enabled = chanctx->conf.radar_enabled; + ctx = container_of(conf, struct ieee80211_chanctx, conf); - if (!local->use_chanctx) { - local->hw.conf.radar_enabled = chanctx->conf.radar_enabled; - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); - } + if (sdata->reserved_chanctx) + ieee80211_vif_unreserve_chanctx(sdata); - drv_change_chanctx(local, chanctx, IEEE80211_CHANCTX_CHANGE_RADAR); + ieee80211_assign_vif_chanctx(sdata, NULL); + if (ieee80211_chanctx_refcount(local, ctx) == 0) + ieee80211_free_chanctx(local, ctx); } void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local, @@ -351,6 +702,13 @@ void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local, rx_chains_static = max(rx_chains_static, needed_static); rx_chains_dynamic = max(rx_chains_dynamic, needed_dynamic); } + + /* Disable SMPS for the monitor interface */ + sdata = rcu_dereference(local->monitor_sdata); + if (sdata && + rcu_access_pointer(sdata->vif.chanctx_conf) == &chanctx->conf) + rx_chains_dynamic = rx_chains_static = local->rx_chains; + rcu_read_unlock(); if (!local->use_chanctx) { @@ -378,11 +736,30 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata, { struct ieee80211_local *local = sdata->local; struct ieee80211_chanctx *ctx; + u8 radar_detect_width = 0; int ret; + lockdep_assert_held(&local->mtx); + WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev)); mutex_lock(&local->chanctx_mtx); + + ret = cfg80211_chandef_dfs_required(local->hw.wiphy, + chandef, + sdata->wdev.iftype); + if (ret < 0) + goto out; + if (ret > 0) + radar_detect_width = BIT(chandef->width); + + sdata->radar_required = ret; + + ret = ieee80211_check_combinations(sdata, chandef, mode, + radar_detect_width); + if (ret < 0) + goto out; + __ieee80211_vif_release_channel(sdata); ctx = ieee80211_find_chanctx(local, chandef, mode); @@ -398,7 +775,7 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata, ret = ieee80211_assign_vif_chanctx(sdata, ctx); if (ret) { /* if assign fails refcount stays the same */ - if (ctx->refcount == 0) + if (ieee80211_chanctx_refcount(local, ctx) == 0) ieee80211_free_chanctx(local, ctx); goto out; } @@ -410,25 +787,142 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata, return ret; } +static int __ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata, + struct ieee80211_chanctx *ctx, + u32 *changed) +{ + struct ieee80211_local *local = sdata->local; + const struct cfg80211_chan_def *chandef = &sdata->csa_chandef; + u32 chanctx_changed = 0; + + if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef, + IEEE80211_CHAN_DISABLED)) + return -EINVAL; + + if (ieee80211_chanctx_refcount(local, ctx) != 1) + return -EINVAL; + + if (sdata->vif.bss_conf.chandef.width != chandef->width) { + chanctx_changed = IEEE80211_CHANCTX_CHANGE_WIDTH; + *changed |= BSS_CHANGED_BANDWIDTH; + } + + sdata->vif.bss_conf.chandef = *chandef; + ctx->conf.def = *chandef; + + chanctx_changed |= IEEE80211_CHANCTX_CHANGE_CHANNEL; + drv_change_chanctx(local, ctx, chanctx_changed); + + ieee80211_recalc_chanctx_chantype(local, ctx); + ieee80211_recalc_smps_chanctx(local, ctx); + ieee80211_recalc_radar_chanctx(local, ctx); + ieee80211_recalc_chanctx_min_def(local, ctx); + + return 0; +} + int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata, - const struct cfg80211_chan_def *chandef, u32 *changed) { struct ieee80211_local *local = sdata->local; struct ieee80211_chanctx_conf *conf; struct ieee80211_chanctx *ctx; int ret; - u32 chanctx_changed = 0; + + lockdep_assert_held(&local->mtx); /* should never be called if not performing a channel switch. */ if (WARN_ON(!sdata->vif.csa_active)) return -EINVAL; - if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef, - IEEE80211_CHAN_DISABLED)) + mutex_lock(&local->chanctx_mtx); + conf = rcu_dereference_protected(sdata->vif.chanctx_conf, + lockdep_is_held(&local->chanctx_mtx)); + if (!conf) { + ret = -EINVAL; + goto out; + } + + ctx = container_of(conf, struct ieee80211_chanctx, conf); + + ret = __ieee80211_vif_change_channel(sdata, ctx, changed); + out: + mutex_unlock(&local->chanctx_mtx); + return ret; +} + +static void +__ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata, + bool clear) +{ + struct ieee80211_local *local __maybe_unused = sdata->local; + struct ieee80211_sub_if_data *vlan; + struct ieee80211_chanctx_conf *conf; + + if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_AP)) + return; + + lockdep_assert_held(&local->mtx); + + /* Check that conf exists, even when clearing this function + * must be called with the AP's channel context still there + * as it would otherwise cause VLANs to have an invalid + * channel context pointer for a while, possibly pointing + * to a channel context that has already been freed. + */ + conf = rcu_dereference_protected(sdata->vif.chanctx_conf, + lockdep_is_held(&local->chanctx_mtx)); + WARN_ON(!conf); + + if (clear) + conf = NULL; + + list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) + rcu_assign_pointer(vlan->vif.chanctx_conf, conf); +} + +void ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata, + bool clear) +{ + struct ieee80211_local *local = sdata->local; + + mutex_lock(&local->chanctx_mtx); + + __ieee80211_vif_copy_chanctx_to_vlans(sdata, clear); + + mutex_unlock(&local->chanctx_mtx); +} + +int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_chanctx *ctx = sdata->reserved_chanctx; + + lockdep_assert_held(&sdata->local->chanctx_mtx); + + if (WARN_ON(!ctx)) return -EINVAL; + list_del(&sdata->reserved_chanctx_list); + sdata->reserved_chanctx = NULL; + + if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0) + ieee80211_free_chanctx(sdata->local, ctx); + + return 0; +} + +int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata, + const struct cfg80211_chan_def *chandef, + enum ieee80211_chanctx_mode mode, + bool radar_required) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_chanctx_conf *conf; + struct ieee80211_chanctx *new_ctx, *curr_ctx; + int ret = 0; + mutex_lock(&local->chanctx_mtx); + conf = rcu_dereference_protected(sdata->vif.chanctx_conf, lockdep_is_held(&local->chanctx_mtx)); if (!conf) { @@ -436,34 +930,108 @@ int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata, goto out; } - ctx = container_of(conf, struct ieee80211_chanctx, conf); - if (ctx->refcount != 1) { + curr_ctx = container_of(conf, struct ieee80211_chanctx, conf); + + new_ctx = ieee80211_find_reservation_chanctx(local, chandef, mode); + if (!new_ctx) { + if (ieee80211_chanctx_refcount(local, curr_ctx) == 1 && + (local->hw.flags & IEEE80211_HW_CHANGE_RUNNING_CHANCTX)) { + /* if we're the only users of the chanctx and + * the driver supports changing a running + * context, reserve our current context + */ + new_ctx = curr_ctx; + } else if (ieee80211_can_create_new_chanctx(local)) { + /* create a new context and reserve it */ + new_ctx = ieee80211_new_chanctx(local, chandef, mode); + if (IS_ERR(new_ctx)) { + ret = PTR_ERR(new_ctx); + goto out; + } + } else { + ret = -EBUSY; + goto out; + } + } + + list_add(&sdata->reserved_chanctx_list, &new_ctx->reserved_vifs); + sdata->reserved_chanctx = new_ctx; + sdata->reserved_chandef = *chandef; + sdata->reserved_radar_required = radar_required; +out: + mutex_unlock(&local->chanctx_mtx); + return ret; +} + +int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata, + u32 *changed) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_chanctx *ctx; + struct ieee80211_chanctx *old_ctx; + struct ieee80211_chanctx_conf *conf; + int ret; + u32 tmp_changed = *changed; + + /* TODO: need to recheck if the chandef is usable etc.? */ + + lockdep_assert_held(&local->mtx); + + mutex_lock(&local->chanctx_mtx); + + ctx = sdata->reserved_chanctx; + if (WARN_ON(!ctx)) { ret = -EINVAL; goto out; } - if (sdata->vif.bss_conf.chandef.width != chandef->width) { - chanctx_changed = IEEE80211_CHANCTX_CHANGE_WIDTH; - *changed |= BSS_CHANGED_BANDWIDTH; + conf = rcu_dereference_protected(sdata->vif.chanctx_conf, + lockdep_is_held(&local->chanctx_mtx)); + if (!conf) { + ret = -EINVAL; + goto out; } - sdata->vif.bss_conf.chandef = *chandef; - ctx->conf.def = *chandef; + old_ctx = container_of(conf, struct ieee80211_chanctx, conf); - chanctx_changed |= IEEE80211_CHANCTX_CHANGE_CHANNEL; - drv_change_chanctx(local, ctx, chanctx_changed); + if (sdata->vif.bss_conf.chandef.width != sdata->reserved_chandef.width) + tmp_changed |= BSS_CHANGED_BANDWIDTH; - if (!local->use_chanctx) { - local->_oper_chandef = *chandef; - ieee80211_hw_config(local, 0); + sdata->vif.bss_conf.chandef = sdata->reserved_chandef; + + /* unref our reservation */ + sdata->reserved_chanctx = NULL; + sdata->radar_required = sdata->reserved_radar_required; + list_del(&sdata->reserved_chanctx_list); + + if (old_ctx == ctx) { + /* This is our own context, just change it */ + ret = __ieee80211_vif_change_channel(sdata, old_ctx, + &tmp_changed); + if (ret) + goto out; + } else { + ret = ieee80211_assign_vif_chanctx(sdata, ctx); + if (ieee80211_chanctx_refcount(local, old_ctx) == 0) + ieee80211_free_chanctx(local, old_ctx); + if (ret) { + /* if assign fails refcount stays the same */ + if (ieee80211_chanctx_refcount(local, ctx) == 0) + ieee80211_free_chanctx(local, ctx); + goto out; + } + + if (sdata->vif.type == NL80211_IFTYPE_AP) + __ieee80211_vif_copy_chanctx_to_vlans(sdata, false); } + *changed = tmp_changed; + ieee80211_recalc_chanctx_chantype(local, ctx); ieee80211_recalc_smps_chanctx(local, ctx); ieee80211_recalc_radar_chanctx(local, ctx); - - ret = 0; - out: + ieee80211_recalc_chanctx_min_def(local, ctx); +out: mutex_unlock(&local->chanctx_mtx); return ret; } @@ -521,6 +1089,8 @@ void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata) { WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev)); + lockdep_assert_held(&sdata->local->mtx); + mutex_lock(&sdata->local->chanctx_mtx); __ieee80211_vif_release_channel(sdata); mutex_unlock(&sdata->local->chanctx_mtx); @@ -545,40 +1115,6 @@ void ieee80211_vif_vlan_copy_chanctx(struct ieee80211_sub_if_data *sdata) mutex_unlock(&local->chanctx_mtx); } -void ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata, - bool clear) -{ - struct ieee80211_local *local = sdata->local; - struct ieee80211_sub_if_data *vlan; - struct ieee80211_chanctx_conf *conf; - - ASSERT_RTNL(); - - if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_AP)) - return; - - mutex_lock(&local->chanctx_mtx); - - /* - * Check that conf exists, even when clearing this function - * must be called with the AP's channel context still there - * as it would otherwise cause VLANs to have an invalid - * channel context pointer for a while, possibly pointing - * to a channel context that has already been freed. - */ - conf = rcu_dereference_protected(sdata->vif.chanctx_conf, - lockdep_is_held(&local->chanctx_mtx)); - WARN_ON(!conf); - - if (clear) - conf = NULL; - - list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) - rcu_assign_pointer(vlan->vif.chanctx_conf, conf); - - mutex_unlock(&local->chanctx_mtx); -} - void ieee80211_iter_chan_contexts_atomic( struct ieee80211_hw *hw, void (*iter)(struct ieee80211_hw *hw, diff --git a/net/mac80211/debug.h b/net/mac80211/debug.h index 4ccc5ed6237..493d68061f0 100644 --- a/net/mac80211/debug.h +++ b/net/mac80211/debug.h @@ -44,6 +44,12 @@ #define MAC80211_MESH_SYNC_DEBUG 0 #endif +#ifdef CONFIG_MAC80211_MESH_CSA_DEBUG +#define MAC80211_MESH_CSA_DEBUG 1 +#else +#define MAC80211_MESH_CSA_DEBUG 0 +#endif + #ifdef CONFIG_MAC80211_MESH_PS_DEBUG #define MAC80211_MESH_PS_DEBUG 1 #else @@ -157,6 +163,10 @@ do { \ _sdata_dbg(MAC80211_MESH_SYNC_DEBUG, \ sdata, fmt, ##__VA_ARGS__) +#define mcsa_dbg(sdata, fmt, ...) \ + _sdata_dbg(MAC80211_MESH_CSA_DEBUG, \ + sdata, fmt, ##__VA_ARGS__) + #define mps_dbg(sdata, fmt, ...) \ _sdata_dbg(MAC80211_MESH_PS_DEBUG, \ sdata, fmt, ##__VA_ARGS__) diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index b0e32d62811..0e963bc1cea 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -17,6 +17,172 @@ #define DEBUGFS_FORMAT_BUFFER_SIZE 100 +#define TX_LATENCY_BIN_DELIMTER_C ',' +#define TX_LATENCY_BIN_DELIMTER_S "," +#define TX_LATENCY_BINS_DISABLED "enable(bins disabled)\n" +#define TX_LATENCY_DISABLED "disable\n" + + +/* + * Display if Tx latency statistics & bins are enabled/disabled + */ +static ssize_t sta_tx_latency_stat_read(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ieee80211_local *local = file->private_data; + struct ieee80211_tx_latency_bin_ranges *tx_latency; + char *buf; + int bufsz, i, ret; + int pos = 0; + + rcu_read_lock(); + + tx_latency = rcu_dereference(local->tx_latency); + + if (tx_latency && tx_latency->n_ranges) { + bufsz = tx_latency->n_ranges * 15; + buf = kzalloc(bufsz, GFP_ATOMIC); + if (!buf) + goto err; + + for (i = 0; i < tx_latency->n_ranges; i++) + pos += scnprintf(buf + pos, bufsz - pos, "%d,", + tx_latency->ranges[i]); + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + } else if (tx_latency) { + bufsz = sizeof(TX_LATENCY_BINS_DISABLED) + 1; + buf = kzalloc(bufsz, GFP_ATOMIC); + if (!buf) + goto err; + + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", + TX_LATENCY_BINS_DISABLED); + } else { + bufsz = sizeof(TX_LATENCY_DISABLED) + 1; + buf = kzalloc(bufsz, GFP_ATOMIC); + if (!buf) + goto err; + + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", + TX_LATENCY_DISABLED); + } + + rcu_read_unlock(); + + ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + kfree(buf); + + return ret; +err: + rcu_read_unlock(); + return -ENOMEM; +} + +/* + * Receive input from user regarding Tx latency statistics + * The input should indicate if Tx latency statistics and bins are + * enabled/disabled. + * If bins are enabled input should indicate the amount of different bins and + * their ranges. Each bin will count how many Tx frames transmitted within the + * appropriate latency. + * Legal input is: + * a) "enable(bins disabled)" - to enable only general statistics + * b) "a,b,c,d,...z" - to enable general statistics and bins, where all are + * numbers and a < b < c < d.. < z + * c) "disable" - disable all statistics + * NOTE: must configure Tx latency statistics bins before stations connected. + */ + +static ssize_t sta_tx_latency_stat_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ieee80211_local *local = file->private_data; + char buf[128] = {}; + char *bins = buf; + char *token; + int buf_size, i, alloc_size; + int prev_bin = 0; + int n_ranges = 0; + int ret = count; + struct ieee80211_tx_latency_bin_ranges *tx_latency; + + if (sizeof(buf) <= count) + return -EINVAL; + buf_size = count; + if (copy_from_user(buf, userbuf, buf_size)) + return -EFAULT; + + mutex_lock(&local->sta_mtx); + + /* cannot change config once we have stations */ + if (local->num_sta) + goto unlock; + + tx_latency = + rcu_dereference_protected(local->tx_latency, + lockdep_is_held(&local->sta_mtx)); + + /* disable Tx statistics */ + if (!strcmp(buf, TX_LATENCY_DISABLED)) { + if (!tx_latency) + goto unlock; + RCU_INIT_POINTER(local->tx_latency, NULL); + synchronize_rcu(); + kfree(tx_latency); + goto unlock; + } + + /* Tx latency already enabled */ + if (tx_latency) + goto unlock; + + if (strcmp(TX_LATENCY_BINS_DISABLED, buf)) { + /* check how many bins and between what ranges user requested */ + token = buf; + while (*token != '\0') { + if (*token == TX_LATENCY_BIN_DELIMTER_C) + n_ranges++; + token++; + } + n_ranges++; + } + + alloc_size = sizeof(struct ieee80211_tx_latency_bin_ranges) + + n_ranges * sizeof(u32); + tx_latency = kzalloc(alloc_size, GFP_ATOMIC); + if (!tx_latency) { + ret = -ENOMEM; + goto unlock; + } + tx_latency->n_ranges = n_ranges; + for (i = 0; i < n_ranges; i++) { /* setting bin ranges */ + token = strsep(&bins, TX_LATENCY_BIN_DELIMTER_S); + sscanf(token, "%d", &tx_latency->ranges[i]); + /* bins values should be in ascending order */ + if (prev_bin >= tx_latency->ranges[i]) { + ret = -EINVAL; + kfree(tx_latency); + goto unlock; + } + prev_bin = tx_latency->ranges[i]; + } + rcu_assign_pointer(local->tx_latency, tx_latency); + +unlock: + mutex_unlock(&local->sta_mtx); + + return ret; +} + +static const struct file_operations stats_tx_latency_ops = { + .write = sta_tx_latency_stat_write, + .read = sta_tx_latency_stat_read, + .open = simple_open, + .llseek = generic_file_llseek, +}; + int mac80211_format_buffer(char __user *userbuf, size_t count, loff_t *ppos, char *fmt, ...) { @@ -103,54 +269,57 @@ static ssize_t hwflags_read(struct file *file, char __user *user_buf, if (!buf) return 0; - sf += snprintf(buf, mxln - sf, "0x%x\n", local->hw.flags); + sf += scnprintf(buf, mxln - sf, "0x%x\n", local->hw.flags); if (local->hw.flags & IEEE80211_HW_HAS_RATE_CONTROL) - sf += snprintf(buf + sf, mxln - sf, "HAS_RATE_CONTROL\n"); + sf += scnprintf(buf + sf, mxln - sf, "HAS_RATE_CONTROL\n"); if (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS) - sf += snprintf(buf + sf, mxln - sf, "RX_INCLUDES_FCS\n"); + sf += scnprintf(buf + sf, mxln - sf, "RX_INCLUDES_FCS\n"); if (local->hw.flags & IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING) - sf += snprintf(buf + sf, mxln - sf, - "HOST_BCAST_PS_BUFFERING\n"); + sf += scnprintf(buf + sf, mxln - sf, + "HOST_BCAST_PS_BUFFERING\n"); if (local->hw.flags & IEEE80211_HW_2GHZ_SHORT_SLOT_INCAPABLE) - sf += snprintf(buf + sf, mxln - sf, - "2GHZ_SHORT_SLOT_INCAPABLE\n"); + sf += scnprintf(buf + sf, mxln - sf, + "2GHZ_SHORT_SLOT_INCAPABLE\n"); if (local->hw.flags & IEEE80211_HW_2GHZ_SHORT_PREAMBLE_INCAPABLE) - sf += snprintf(buf + sf, mxln - sf, - "2GHZ_SHORT_PREAMBLE_INCAPABLE\n"); + sf += scnprintf(buf + sf, mxln - sf, + "2GHZ_SHORT_PREAMBLE_INCAPABLE\n"); if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC) - sf += snprintf(buf + sf, mxln - sf, "SIGNAL_UNSPEC\n"); + sf += scnprintf(buf + sf, mxln - sf, "SIGNAL_UNSPEC\n"); if (local->hw.flags & IEEE80211_HW_SIGNAL_DBM) - sf += snprintf(buf + sf, mxln - sf, "SIGNAL_DBM\n"); + sf += scnprintf(buf + sf, mxln - sf, "SIGNAL_DBM\n"); if (local->hw.flags & IEEE80211_HW_NEED_DTIM_BEFORE_ASSOC) - sf += snprintf(buf + sf, mxln - sf, "NEED_DTIM_BEFORE_ASSOC\n"); + sf += scnprintf(buf + sf, mxln - sf, + "NEED_DTIM_BEFORE_ASSOC\n"); if (local->hw.flags & IEEE80211_HW_SPECTRUM_MGMT) - sf += snprintf(buf + sf, mxln - sf, "SPECTRUM_MGMT\n"); + sf += scnprintf(buf + sf, mxln - sf, "SPECTRUM_MGMT\n"); if (local->hw.flags & IEEE80211_HW_AMPDU_AGGREGATION) - sf += snprintf(buf + sf, mxln - sf, "AMPDU_AGGREGATION\n"); + sf += scnprintf(buf + sf, mxln - sf, "AMPDU_AGGREGATION\n"); if (local->hw.flags & IEEE80211_HW_SUPPORTS_PS) - sf += snprintf(buf + sf, mxln - sf, "SUPPORTS_PS\n"); + sf += scnprintf(buf + sf, mxln - sf, "SUPPORTS_PS\n"); if (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK) - sf += snprintf(buf + sf, mxln - sf, "PS_NULLFUNC_STACK\n"); + sf += scnprintf(buf + sf, mxln - sf, "PS_NULLFUNC_STACK\n"); if (local->hw.flags & IEEE80211_HW_SUPPORTS_DYNAMIC_PS) - sf += snprintf(buf + sf, mxln - sf, "SUPPORTS_DYNAMIC_PS\n"); + sf += scnprintf(buf + sf, mxln - sf, "SUPPORTS_DYNAMIC_PS\n"); if (local->hw.flags & IEEE80211_HW_MFP_CAPABLE) - sf += snprintf(buf + sf, mxln - sf, "MFP_CAPABLE\n"); + sf += scnprintf(buf + sf, mxln - sf, "MFP_CAPABLE\n"); if (local->hw.flags & IEEE80211_HW_SUPPORTS_STATIC_SMPS) - sf += snprintf(buf + sf, mxln - sf, "SUPPORTS_STATIC_SMPS\n"); + sf += scnprintf(buf + sf, mxln - sf, "SUPPORTS_STATIC_SMPS\n"); if (local->hw.flags & IEEE80211_HW_SUPPORTS_DYNAMIC_SMPS) - sf += snprintf(buf + sf, mxln - sf, "SUPPORTS_DYNAMIC_SMPS\n"); + sf += scnprintf(buf + sf, mxln - sf, + "SUPPORTS_DYNAMIC_SMPS\n"); if (local->hw.flags & IEEE80211_HW_SUPPORTS_UAPSD) - sf += snprintf(buf + sf, mxln - sf, "SUPPORTS_UAPSD\n"); + sf += scnprintf(buf + sf, mxln - sf, "SUPPORTS_UAPSD\n"); if (local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS) - sf += snprintf(buf + sf, mxln - sf, "REPORTS_TX_ACK_STATUS\n"); + sf += scnprintf(buf + sf, mxln - sf, + "REPORTS_TX_ACK_STATUS\n"); if (local->hw.flags & IEEE80211_HW_CONNECTION_MONITOR) - sf += snprintf(buf + sf, mxln - sf, "CONNECTION_MONITOR\n"); + sf += scnprintf(buf + sf, mxln - sf, "CONNECTION_MONITOR\n"); if (local->hw.flags & IEEE80211_HW_SUPPORTS_PER_STA_GTK) - sf += snprintf(buf + sf, mxln - sf, "SUPPORTS_PER_STA_GTK\n"); + sf += scnprintf(buf + sf, mxln - sf, "SUPPORTS_PER_STA_GTK\n"); if (local->hw.flags & IEEE80211_HW_AP_LINK_PS) - sf += snprintf(buf + sf, mxln - sf, "AP_LINK_PS\n"); + sf += scnprintf(buf + sf, mxln - sf, "AP_LINK_PS\n"); if (local->hw.flags & IEEE80211_HW_TX_AMPDU_SETUP_IN_HW) - sf += snprintf(buf + sf, mxln - sf, "TX_AMPDU_SETUP_IN_HW\n"); + sf += scnprintf(buf + sf, mxln - sf, "TX_AMPDU_SETUP_IN_HW\n"); rv = simple_read_from_buffer(user_buf, count, ppos, buf, strlen(buf)); kfree(buf); @@ -312,4 +481,6 @@ void debugfs_hw_add(struct ieee80211_local *local) DEBUGFS_DEVSTATS_ADD(dot11RTSFailureCount); DEBUGFS_DEVSTATS_ADD(dot11FCSErrorCount); DEBUGFS_DEVSTATS_ADD(dot11RTSSuccessCount); + + DEBUGFS_DEVSTATS_ADD(tx_latency); } diff --git a/net/mac80211/debugfs.h b/net/mac80211/debugfs.h index 214ed4ecd73..60c35afee29 100644 --- a/net/mac80211/debugfs.h +++ b/net/mac80211/debugfs.h @@ -1,6 +1,8 @@ #ifndef __MAC80211_DEBUGFS_H #define __MAC80211_DEBUGFS_H +#include "ieee80211_i.h" + #ifdef CONFIG_MAC80211_DEBUGFS void debugfs_hw_add(struct ieee80211_local *local); int __printf(4, 5) mac80211_format_buffer(char __user *userbuf, size_t count, diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c index cafe614ef93..e205ebabfa5 100644 --- a/net/mac80211/debugfs_netdev.c +++ b/net/mac80211/debugfs_netdev.c @@ -34,8 +34,7 @@ static ssize_t ieee80211_if_read( ssize_t ret = -EINVAL; read_lock(&dev_base_lock); - if (sdata->dev->reg_state == NETREG_REGISTERED) - ret = (*format)(sdata, buf, sizeof(buf)); + ret = (*format)(sdata, buf, sizeof(buf)); read_unlock(&dev_base_lock); if (ret >= 0) @@ -62,8 +61,7 @@ static ssize_t ieee80211_if_write( ret = -ENODEV; rtnl_lock(); - if (sdata->dev->reg_state == NETREG_REGISTERED) - ret = (*write)(sdata, buf, count); + ret = (*write)(sdata, buf, count); rtnl_unlock(); return ret; @@ -133,7 +131,15 @@ static ssize_t ieee80211_if_fmt_##name( \ jiffies_to_msecs(sdata->field)); \ } -#define __IEEE80211_IF_FILE(name, _write) \ +#define _IEEE80211_IF_FILE_OPS(name, _read, _write) \ +static const struct file_operations name##_ops = { \ + .read = (_read), \ + .write = (_write), \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +} + +#define _IEEE80211_IF_FILE_R_FN(name) \ static ssize_t ieee80211_if_read_##name(struct file *file, \ char __user *userbuf, \ size_t count, loff_t *ppos) \ @@ -141,28 +147,34 @@ static ssize_t ieee80211_if_read_##name(struct file *file, \ return ieee80211_if_read(file->private_data, \ userbuf, count, ppos, \ ieee80211_if_fmt_##name); \ -} \ -static const struct file_operations name##_ops = { \ - .read = ieee80211_if_read_##name, \ - .write = (_write), \ - .open = simple_open, \ - .llseek = generic_file_llseek, \ } -#define __IEEE80211_IF_FILE_W(name) \ +#define _IEEE80211_IF_FILE_W_FN(name) \ static ssize_t ieee80211_if_write_##name(struct file *file, \ const char __user *userbuf, \ size_t count, loff_t *ppos) \ { \ return ieee80211_if_write(file->private_data, userbuf, count, \ ppos, ieee80211_if_parse_##name); \ -} \ -__IEEE80211_IF_FILE(name, ieee80211_if_write_##name) +} + +#define IEEE80211_IF_FILE_R(name) \ + _IEEE80211_IF_FILE_R_FN(name) \ + _IEEE80211_IF_FILE_OPS(name, ieee80211_if_read_##name, NULL) +#define IEEE80211_IF_FILE_W(name) \ + _IEEE80211_IF_FILE_W_FN(name) \ + _IEEE80211_IF_FILE_OPS(name, NULL, ieee80211_if_write_##name) + +#define IEEE80211_IF_FILE_RW(name) \ + _IEEE80211_IF_FILE_R_FN(name) \ + _IEEE80211_IF_FILE_W_FN(name) \ + _IEEE80211_IF_FILE_OPS(name, ieee80211_if_read_##name, \ + ieee80211_if_write_##name) #define IEEE80211_IF_FILE(name, field, format) \ - IEEE80211_IF_FMT_##format(name, field) \ - __IEEE80211_IF_FILE(name, NULL) + IEEE80211_IF_FMT_##format(name, field) \ + IEEE80211_IF_FILE_R(name) /* common attributes */ IEEE80211_IF_FILE(drop_unencrypted, drop_unencrypted, DEC); @@ -199,7 +211,7 @@ ieee80211_if_fmt_hw_queues(const struct ieee80211_sub_if_data *sdata, return len; } -__IEEE80211_IF_FILE(hw_queues, NULL); +IEEE80211_IF_FILE_R(hw_queues); /* STA attributes */ IEEE80211_IF_FILE(bssid, u.mgd.bssid, MAC); @@ -224,12 +236,15 @@ static int ieee80211_set_smps(struct ieee80211_sub_if_data *sdata, smps_mode == IEEE80211_SMPS_AUTOMATIC)) return -EINVAL; - /* supported only on managed interfaces for now */ - if (sdata->vif.type != NL80211_IFTYPE_STATION) + if (sdata->vif.type != NL80211_IFTYPE_STATION && + sdata->vif.type != NL80211_IFTYPE_AP) return -EOPNOTSUPP; sdata_lock(sdata); - err = __ieee80211_request_smps(sdata, smps_mode); + if (sdata->vif.type == NL80211_IFTYPE_STATION) + err = __ieee80211_request_smps_mgd(sdata, smps_mode); + else + err = __ieee80211_request_smps_ap(sdata, smps_mode); sdata_unlock(sdata); return err; @@ -245,12 +260,15 @@ static const char *smps_modes[IEEE80211_SMPS_NUM_MODES] = { static ssize_t ieee80211_if_fmt_smps(const struct ieee80211_sub_if_data *sdata, char *buf, int buflen) { - if (sdata->vif.type != NL80211_IFTYPE_STATION) - return -EOPNOTSUPP; - - return snprintf(buf, buflen, "request: %s\nused: %s\n", - smps_modes[sdata->u.mgd.req_smps], - smps_modes[sdata->smps_mode]); + if (sdata->vif.type == NL80211_IFTYPE_STATION) + return snprintf(buf, buflen, "request: %s\nused: %s\n", + smps_modes[sdata->u.mgd.req_smps], + smps_modes[sdata->smps_mode]); + if (sdata->vif.type == NL80211_IFTYPE_AP) + return snprintf(buf, buflen, "request: %s\nused: %s\n", + smps_modes[sdata->u.ap.req_smps], + smps_modes[sdata->smps_mode]); + return -EINVAL; } static ssize_t ieee80211_if_parse_smps(struct ieee80211_sub_if_data *sdata, @@ -269,14 +287,7 @@ static ssize_t ieee80211_if_parse_smps(struct ieee80211_sub_if_data *sdata, return -EINVAL; } - -__IEEE80211_IF_FILE_W(smps); - -static ssize_t ieee80211_if_fmt_tkip_mic_test( - const struct ieee80211_sub_if_data *sdata, char *buf, int buflen) -{ - return -EOPNOTSUPP; -} +IEEE80211_IF_FILE_RW(smps); static ssize_t ieee80211_if_parse_tkip_mic_test( struct ieee80211_sub_if_data *sdata, const char *buf, int buflen) @@ -343,8 +354,19 @@ static ssize_t ieee80211_if_parse_tkip_mic_test( return buflen; } +IEEE80211_IF_FILE_W(tkip_mic_test); -__IEEE80211_IF_FILE_W(tkip_mic_test); +static ssize_t ieee80211_if_parse_beacon_loss( + struct ieee80211_sub_if_data *sdata, const char *buf, int buflen) +{ + if (!ieee80211_sdata_running(sdata) || !sdata->vif.bss_conf.assoc) + return -ENOTCONN; + + ieee80211_beacon_loss(&sdata->vif); + + return buflen; +} +IEEE80211_IF_FILE_W(beacon_loss); static ssize_t ieee80211_if_fmt_uapsd_queues( const struct ieee80211_sub_if_data *sdata, char *buf, int buflen) @@ -372,7 +394,7 @@ static ssize_t ieee80211_if_parse_uapsd_queues( return buflen; } -__IEEE80211_IF_FILE_W(uapsd_queues); +IEEE80211_IF_FILE_RW(uapsd_queues); static ssize_t ieee80211_if_fmt_uapsd_max_sp_len( const struct ieee80211_sub_if_data *sdata, char *buf, int buflen) @@ -400,7 +422,7 @@ static ssize_t ieee80211_if_parse_uapsd_max_sp_len( return buflen; } -__IEEE80211_IF_FILE_W(uapsd_max_sp_len); +IEEE80211_IF_FILE_RW(uapsd_max_sp_len); /* AP attributes */ IEEE80211_IF_FILE(num_mcast_sta, u.ap.num_mcast_sta, ATOMIC); @@ -413,7 +435,7 @@ static ssize_t ieee80211_if_fmt_num_buffered_multicast( return scnprintf(buf, buflen, "%u\n", skb_queue_len(&sdata->u.ap.ps.bc_buf)); } -__IEEE80211_IF_FILE(num_buffered_multicast, NULL); +IEEE80211_IF_FILE_R(num_buffered_multicast); /* IBSS attributes */ static ssize_t ieee80211_if_fmt_tsf( @@ -462,9 +484,10 @@ static ssize_t ieee80211_if_parse_tsf( } } + ieee80211_recalc_dtim(local, sdata); return buflen; } -__IEEE80211_IF_FILE_W(tsf); +IEEE80211_IF_FILE_RW(tsf); /* WDS attributes */ @@ -556,6 +579,7 @@ static void add_sta_files(struct ieee80211_sub_if_data *sdata) DEBUGFS_ADD(beacon_timeout); DEBUGFS_ADD_MODE(smps, 0600); DEBUGFS_ADD_MODE(tkip_mic_test, 0200); + DEBUGFS_ADD_MODE(beacon_loss, 0200); DEBUGFS_ADD_MODE(uapsd_queues, 0600); DEBUGFS_ADD_MODE(uapsd_max_sp_len, 0600); } @@ -563,6 +587,7 @@ static void add_sta_files(struct ieee80211_sub_if_data *sdata) static void add_ap_files(struct ieee80211_sub_if_data *sdata) { DEBUGFS_ADD(num_mcast_sta); + DEBUGFS_ADD_MODE(smps, 0600); DEBUGFS_ADD(num_sta_ps); DEBUGFS_ADD(dtim_count); DEBUGFS_ADD(num_buffered_multicast); diff --git a/net/mac80211/debugfs_netdev.h b/net/mac80211/debugfs_netdev.h index 79025e79f4d..9f5501a9a79 100644 --- a/net/mac80211/debugfs_netdev.h +++ b/net/mac80211/debugfs_netdev.h @@ -3,6 +3,8 @@ #ifndef __IEEE80211_DEBUGFS_NETDEV_H #define __IEEE80211_DEBUGFS_NETDEV_H +#include "ieee80211_i.h" + #ifdef CONFIG_MAC80211_DEBUGFS void ieee80211_debugfs_add_netdev(struct ieee80211_sub_if_data *sdata); void ieee80211_debugfs_remove_netdev(struct ieee80211_sub_if_data *sdata); diff --git a/net/mac80211/debugfs_sta.c b/net/mac80211/debugfs_sta.c index 19c54a44ed4..2ecb4deddb5 100644 --- a/net/mac80211/debugfs_sta.c +++ b/net/mac80211/debugfs_sta.c @@ -38,6 +38,13 @@ static const struct file_operations sta_ ##name## _ops = { \ .llseek = generic_file_llseek, \ } +#define STA_OPS_W(name) \ +static const struct file_operations sta_ ##name## _ops = { \ + .write = sta_##name##_write, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +} + #define STA_OPS_RW(name) \ static const struct file_operations sta_ ##name## _ops = { \ .read = sta_##name##_read, \ @@ -188,7 +195,7 @@ static ssize_t sta_agg_status_read(struct file *file, char __user *userbuf, static ssize_t sta_agg_status_write(struct file *file, const char __user *userbuf, size_t count, loff_t *ppos) { - char _buf[12], *buf = _buf; + char _buf[12] = {}, *buf = _buf; struct sta_info *sta = file->private_data; bool start, tx; unsigned long tid; @@ -388,6 +395,131 @@ static ssize_t sta_last_rx_rate_read(struct file *file, char __user *userbuf, } STA_OPS(last_rx_rate); +static int +sta_tx_latency_stat_header(struct ieee80211_tx_latency_bin_ranges *tx_latency, + char *buf, int pos, int bufsz) +{ + int i; + int range_count = tx_latency->n_ranges; + u32 *bin_ranges = tx_latency->ranges; + + pos += scnprintf(buf + pos, bufsz - pos, + "Station\t\t\tTID\tMax\tAvg"); + if (range_count) { + pos += scnprintf(buf + pos, bufsz - pos, + "\t<=%d", bin_ranges[0]); + for (i = 0; i < range_count - 1; i++) + pos += scnprintf(buf + pos, bufsz - pos, "\t%d-%d", + bin_ranges[i], bin_ranges[i+1]); + pos += scnprintf(buf + pos, bufsz - pos, + "\t%d<", bin_ranges[range_count - 1]); + } + + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + + return pos; +} + +static int +sta_tx_latency_stat_table(struct ieee80211_tx_latency_bin_ranges *tx_lat_range, + struct ieee80211_tx_latency_stat *tx_lat, + char *buf, int pos, int bufsz, int tid) +{ + u32 avg = 0; + int j; + int bin_count = tx_lat->bin_count; + + pos += scnprintf(buf + pos, bufsz - pos, "\t\t\t%d", tid); + /* make sure you don't divide in 0 */ + if (tx_lat->counter) + avg = tx_lat->sum / tx_lat->counter; + + pos += scnprintf(buf + pos, bufsz - pos, "\t%d\t%d", + tx_lat->max, avg); + + if (tx_lat_range->n_ranges && tx_lat->bins) + for (j = 0; j < bin_count; j++) + pos += scnprintf(buf + pos, bufsz - pos, + "\t%d", tx_lat->bins[j]); + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + + return pos; +} + +/* + * Output Tx latency statistics station && restart all statistics information + */ +static ssize_t sta_tx_latency_stat_read(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct sta_info *sta = file->private_data; + struct ieee80211_local *local = sta->local; + struct ieee80211_tx_latency_bin_ranges *tx_latency; + char *buf; + int bufsz, ret, i; + int pos = 0; + + bufsz = 20 * IEEE80211_NUM_TIDS * + sizeof(struct ieee80211_tx_latency_stat); + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + rcu_read_lock(); + + tx_latency = rcu_dereference(local->tx_latency); + + if (!sta->tx_lat) { + pos += scnprintf(buf + pos, bufsz - pos, + "Tx latency statistics are not enabled\n"); + goto unlock; + } + + pos = sta_tx_latency_stat_header(tx_latency, buf, pos, bufsz); + + pos += scnprintf(buf + pos, bufsz - pos, "%pM\n", sta->sta.addr); + for (i = 0; i < IEEE80211_NUM_TIDS; i++) + pos = sta_tx_latency_stat_table(tx_latency, &sta->tx_lat[i], + buf, pos, bufsz, i); +unlock: + rcu_read_unlock(); + + ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + kfree(buf); + + return ret; +} +STA_OPS(tx_latency_stat); + +static ssize_t sta_tx_latency_stat_reset_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + u32 *bins; + int bin_count; + struct sta_info *sta = file->private_data; + int i; + + if (!sta->tx_lat) + return -EINVAL; + + for (i = 0; i < IEEE80211_NUM_TIDS; i++) { + bins = sta->tx_lat[i].bins; + bin_count = sta->tx_lat[i].bin_count; + + sta->tx_lat[i].max = 0; + sta->tx_lat[i].sum = 0; + sta->tx_lat[i].counter = 0; + + if (bin_count) + memset(bins, 0, bin_count * sizeof(u32)); + } + + return count; +} +STA_OPS_W(tx_latency_stat_reset); + #define DEBUGFS_ADD(name) \ debugfs_create_file(#name, 0400, \ sta->debugfs.dir, sta, &sta_ ##name## _ops); @@ -441,6 +573,8 @@ void ieee80211_sta_debugfs_add(struct sta_info *sta) DEBUGFS_ADD(last_ack_signal); DEBUGFS_ADD(current_tx_rate); DEBUGFS_ADD(last_rx_rate); + DEBUGFS_ADD(tx_latency_stat); + DEBUGFS_ADD(tx_latency_stat_reset); DEBUGFS_ADD_COUNTER(rx_packets, rx_packets); DEBUGFS_ADD_COUNTER(tx_packets, tx_packets); diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h index b3ea11f3d52..bd782dcffcc 100644 --- a/net/mac80211/driver-ops.h +++ b/net/mac80211/driver-ops.h @@ -5,11 +5,11 @@ #include "ieee80211_i.h" #include "trace.h" -static inline void check_sdata_in_driver(struct ieee80211_sub_if_data *sdata) +static inline bool check_sdata_in_driver(struct ieee80211_sub_if_data *sdata) { - WARN(!(sdata->flags & IEEE80211_SDATA_IN_DRIVER), - "%s: Failed check-sdata-in-driver check, flags: 0x%x\n", - sdata->dev ? sdata->dev->name : sdata->name, sdata->flags); + return !WARN(!(sdata->flags & IEEE80211_SDATA_IN_DRIVER), + "%s: Failed check-sdata-in-driver check, flags: 0x%x\n", + sdata->dev ? sdata->dev->name : sdata->name, sdata->flags); } static inline struct ieee80211_sub_if_data * @@ -168,7 +168,8 @@ static inline int drv_change_interface(struct ieee80211_local *local, might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_change_interface(local, sdata, type, p2p); ret = local->ops->change_interface(&local->hw, &sdata->vif, type, p2p); @@ -181,7 +182,8 @@ static inline void drv_remove_interface(struct ieee80211_local *local, { might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; trace_drv_remove_interface(local, sdata); local->ops->remove_interface(&local->hw, &sdata->vif); @@ -219,7 +221,8 @@ static inline void drv_bss_info_changed(struct ieee80211_local *local, sdata->vif.type == NL80211_IFTYPE_MONITOR)) return; - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; trace_drv_bss_info_changed(local, sdata, info, changed); if (local->ops->bss_info_changed) @@ -242,22 +245,6 @@ static inline u64 drv_prepare_multicast(struct ieee80211_local *local, return ret; } -static inline void drv_set_multicast_list(struct ieee80211_local *local, - struct ieee80211_sub_if_data *sdata, - struct netdev_hw_addr_list *mc_list) -{ - bool allmulti = sdata->flags & IEEE80211_SDATA_ALLMULTI; - - trace_drv_set_multicast_list(local, sdata, mc_list->count); - - check_sdata_in_driver(sdata); - - if (local->ops->set_multicast_list) - local->ops->set_multicast_list(&local->hw, &sdata->vif, - allmulti, mc_list); - trace_drv_return_void(local); -} - static inline void drv_configure_filter(struct ieee80211_local *local, unsigned int changed_flags, unsigned int *total_flags, @@ -294,7 +281,8 @@ static inline int drv_set_key(struct ieee80211_local *local, might_sleep(); sdata = get_bss_sdata(sdata); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_set_key(local, cmd, sdata, sta, key); ret = local->ops->set_key(&local->hw, cmd, &sdata->vif, sta, key); @@ -314,7 +302,8 @@ static inline void drv_update_tkip_key(struct ieee80211_local *local, ista = &sta->sta; sdata = get_bss_sdata(sdata); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; trace_drv_update_tkip_key(local, sdata, conf, ista, iv32); if (local->ops->update_tkip_key) @@ -331,7 +320,8 @@ static inline int drv_hw_scan(struct ieee80211_local *local, might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_hw_scan(local, sdata); ret = local->ops->hw_scan(&local->hw, &sdata->vif, req); @@ -344,7 +334,8 @@ static inline void drv_cancel_hw_scan(struct ieee80211_local *local, { might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; trace_drv_cancel_hw_scan(local, sdata); local->ops->cancel_hw_scan(&local->hw, &sdata->vif); @@ -361,7 +352,8 @@ drv_sched_scan_start(struct ieee80211_local *local, might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_sched_scan_start(local, sdata); ret = local->ops->sched_scan_start(&local->hw, &sdata->vif, @@ -370,16 +362,21 @@ drv_sched_scan_start(struct ieee80211_local *local, return ret; } -static inline void drv_sched_scan_stop(struct ieee80211_local *local, - struct ieee80211_sub_if_data *sdata) +static inline int drv_sched_scan_stop(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata) { + int ret; + might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_sched_scan_stop(local, sdata); - local->ops->sched_scan_stop(&local->hw, &sdata->vif); - trace_drv_return_void(local); + ret = local->ops->sched_scan_stop(&local->hw, &sdata->vif); + trace_drv_return_int(local, ret); + + return ret; } static inline void drv_sw_scan_start(struct ieee80211_local *local) @@ -474,7 +471,8 @@ static inline void drv_sta_notify(struct ieee80211_local *local, struct ieee80211_sta *sta) { sdata = get_bss_sdata(sdata); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; trace_drv_sta_notify(local, sdata, cmd, sta); if (local->ops->sta_notify) @@ -491,7 +489,8 @@ static inline int drv_sta_add(struct ieee80211_local *local, might_sleep(); sdata = get_bss_sdata(sdata); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_sta_add(local, sdata, sta); if (local->ops->sta_add) @@ -509,7 +508,8 @@ static inline void drv_sta_remove(struct ieee80211_local *local, might_sleep(); sdata = get_bss_sdata(sdata); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; trace_drv_sta_remove(local, sdata, sta); if (local->ops->sta_remove) @@ -527,7 +527,8 @@ static inline void drv_sta_add_debugfs(struct ieee80211_local *local, might_sleep(); sdata = get_bss_sdata(sdata); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; if (local->ops->sta_add_debugfs) local->ops->sta_add_debugfs(&local->hw, &sdata->vif, @@ -550,6 +551,23 @@ static inline void drv_sta_remove_debugfs(struct ieee80211_local *local, } #endif +static inline void drv_sta_pre_rcu_remove(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct sta_info *sta) +{ + might_sleep(); + + sdata = get_bss_sdata(sdata); + if (!check_sdata_in_driver(sdata)) + return; + + trace_drv_sta_pre_rcu_remove(local, sdata, &sta->sta); + if (local->ops->sta_pre_rcu_remove) + local->ops->sta_pre_rcu_remove(&local->hw, &sdata->vif, + &sta->sta); + trace_drv_return_void(local); +} + static inline __must_check int drv_sta_state(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, @@ -562,7 +580,8 @@ int drv_sta_state(struct ieee80211_local *local, might_sleep(); sdata = get_bss_sdata(sdata); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_sta_state(local, sdata, &sta->sta, old_state, new_state); if (local->ops->sta_state) { @@ -586,7 +605,8 @@ static inline void drv_sta_rc_update(struct ieee80211_local *local, struct ieee80211_sta *sta, u32 changed) { sdata = get_bss_sdata(sdata); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; WARN_ON(changed & IEEE80211_RC_SUPP_RATES_CHANGED && (sdata->vif.type != NL80211_IFTYPE_ADHOC && @@ -608,7 +628,8 @@ static inline int drv_conf_tx(struct ieee80211_local *local, might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_conf_tx(local, sdata, ac, params); if (local->ops->conf_tx) @@ -625,7 +646,8 @@ static inline u64 drv_get_tsf(struct ieee80211_local *local, might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return ret; trace_drv_get_tsf(local, sdata); if (local->ops->get_tsf) @@ -640,7 +662,8 @@ static inline void drv_set_tsf(struct ieee80211_local *local, { might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; trace_drv_set_tsf(local, sdata, tsf); if (local->ops->set_tsf) @@ -653,7 +676,8 @@ static inline void drv_reset_tsf(struct ieee80211_local *local, { might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; trace_drv_reset_tsf(local, sdata); if (local->ops->reset_tsf) @@ -685,7 +709,8 @@ static inline int drv_ampdu_action(struct ieee80211_local *local, might_sleep(); sdata = get_bss_sdata(sdata); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_ampdu_action(local, sdata, action, sta, tid, ssn, buf_size); @@ -722,13 +747,19 @@ static inline void drv_rfkill_poll(struct ieee80211_local *local) } static inline void drv_flush(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, u32 queues, bool drop) { + struct ieee80211_vif *vif = sdata ? &sdata->vif : NULL; + might_sleep(); + if (sdata && !check_sdata_in_driver(sdata)) + return; + trace_drv_flush(local, queues, drop); if (local->ops->flush) - local->ops->flush(&local->hw, queues, drop); + local->ops->flush(&local->hw, vif, queues, drop); trace_drv_return_void(local); } @@ -844,7 +875,8 @@ static inline int drv_set_bitrate_mask(struct ieee80211_local *local, might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_set_bitrate_mask(local, sdata, mask); if (local->ops->set_bitrate_mask) @@ -859,7 +891,8 @@ static inline void drv_set_rekey_data(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, struct cfg80211_gtk_rekey_data *data) { - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; trace_drv_set_rekey_data(local, sdata, data); if (local->ops->set_rekey_data) @@ -927,7 +960,8 @@ static inline void drv_mgd_prepare_tx(struct ieee80211_local *local, { might_sleep(); - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_STATION); trace_drv_mgd_prepare_tx(local, sdata); @@ -954,6 +988,9 @@ static inline int drv_add_chanctx(struct ieee80211_local *local, static inline void drv_remove_chanctx(struct ieee80211_local *local, struct ieee80211_chanctx *ctx) { + if (WARN_ON(!ctx->driver_present)) + return; + trace_drv_remove_chanctx(local, ctx); if (local->ops->remove_chanctx) local->ops->remove_chanctx(&local->hw, &ctx->conf); @@ -979,7 +1016,8 @@ static inline int drv_assign_vif_chanctx(struct ieee80211_local *local, { int ret = 0; - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_assign_vif_chanctx(local, sdata, ctx); if (local->ops->assign_vif_chanctx) { @@ -997,7 +1035,8 @@ static inline void drv_unassign_vif_chanctx(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, struct ieee80211_chanctx *ctx) { - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; trace_drv_unassign_vif_chanctx(local, sdata, ctx); if (local->ops->unassign_vif_chanctx) { @@ -1009,12 +1048,66 @@ static inline void drv_unassign_vif_chanctx(struct ieee80211_local *local, trace_drv_return_void(local); } +static inline int +drv_switch_vif_chanctx(struct ieee80211_local *local, + struct ieee80211_vif_chanctx_switch *vifs, + int n_vifs, + enum ieee80211_chanctx_switch_mode mode) +{ + int ret = 0; + int i; + + if (!local->ops->switch_vif_chanctx) + return -EOPNOTSUPP; + + for (i = 0; i < n_vifs; i++) { + struct ieee80211_chanctx *new_ctx = + container_of(vifs[i].new_ctx, + struct ieee80211_chanctx, + conf); + struct ieee80211_chanctx *old_ctx = + container_of(vifs[i].old_ctx, + struct ieee80211_chanctx, + conf); + + WARN_ON_ONCE(!old_ctx->driver_present); + WARN_ON_ONCE((mode == CHANCTX_SWMODE_SWAP_CONTEXTS && + new_ctx->driver_present) || + (mode == CHANCTX_SWMODE_REASSIGN_VIF && + !new_ctx->driver_present)); + } + + trace_drv_switch_vif_chanctx(local, vifs, n_vifs, mode); + ret = local->ops->switch_vif_chanctx(&local->hw, + vifs, n_vifs, mode); + trace_drv_return_int(local, ret); + + if (!ret && mode == CHANCTX_SWMODE_SWAP_CONTEXTS) { + for (i = 0; i < n_vifs; i++) { + struct ieee80211_chanctx *new_ctx = + container_of(vifs[i].new_ctx, + struct ieee80211_chanctx, + conf); + struct ieee80211_chanctx *old_ctx = + container_of(vifs[i].old_ctx, + struct ieee80211_chanctx, + conf); + + new_ctx->driver_present = true; + old_ctx->driver_present = false; + } + } + + return ret; +} + static inline int drv_start_ap(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata) { int ret = 0; - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return -EIO; trace_drv_start_ap(local, sdata, &sdata->vif.bss_conf); if (local->ops->start_ap) @@ -1026,7 +1119,8 @@ static inline int drv_start_ap(struct ieee80211_local *local, static inline void drv_stop_ap(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata) { - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; trace_drv_stop_ap(local, sdata); if (local->ops->stop_ap) @@ -1049,7 +1143,8 @@ drv_set_default_unicast_key(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, int key_idx) { - check_sdata_in_driver(sdata); + if (!check_sdata_in_driver(sdata)) + return; WARN_ON_ONCE(key_idx < -1 || key_idx > 3); @@ -1085,4 +1180,46 @@ drv_channel_switch_beacon(struct ieee80211_sub_if_data *sdata, } } +static inline int drv_join_ibss(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata) +{ + int ret = 0; + + might_sleep(); + if (!check_sdata_in_driver(sdata)) + return -EIO; + + trace_drv_join_ibss(local, sdata, &sdata->vif.bss_conf); + if (local->ops->join_ibss) + ret = local->ops->join_ibss(&local->hw, &sdata->vif); + trace_drv_return_int(local, ret); + return ret; +} + +static inline void drv_leave_ibss(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata) +{ + might_sleep(); + if (!check_sdata_in_driver(sdata)) + return; + + trace_drv_leave_ibss(local, sdata); + if (local->ops->leave_ibss) + local->ops->leave_ibss(&local->hw, &sdata->vif); + trace_drv_return_void(local); +} + +static inline u32 drv_get_expected_throughput(struct ieee80211_local *local, + struct ieee80211_sta *sta) +{ + u32 ret = 0; + + trace_drv_get_expected_throughput(sta); + if (local->ops->get_expected_throughput) + ret = local->ops->get_expected_throughput(sta); + trace_drv_return_u32(local, ret); + + return ret; +} + #endif /* __MAC80211_DRIVER_OPS */ diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c index 529bf58bc14..15702ff64a4 100644 --- a/net/mac80211/ht.c +++ b/net/mac80211/ht.c @@ -31,6 +31,18 @@ static void __check_htcap_disable(struct ieee80211_ht_cap *ht_capa, } } +static void __check_htcap_enable(struct ieee80211_ht_cap *ht_capa, + struct ieee80211_ht_cap *ht_capa_mask, + struct ieee80211_sta_ht_cap *ht_cap, + u16 flag) +{ + __le16 le_flag = cpu_to_le16(flag); + + if ((ht_capa_mask->cap_info & le_flag) && + (ht_capa->cap_info & le_flag)) + ht_cap->cap |= flag; +} + void ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata, struct ieee80211_sta_ht_cap *ht_cap) { @@ -59,7 +71,7 @@ void ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata, smask = (u8 *)(&ht_capa_mask->mcs.rx_mask); /* NOTE: If you add more over-rides here, update register_hw - * ht_capa_mod_msk logic in main.c as well. + * ht_capa_mod_mask logic in main.c as well. * And, if this method can ever change ht_cap.ht_supported, fix * the check in ieee80211_add_ht_ie. */ @@ -86,6 +98,14 @@ void ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata, __check_htcap_disable(ht_capa, ht_capa_mask, ht_cap, IEEE80211_HT_CAP_MAX_AMSDU); + /* Allow user to disable LDPC */ + __check_htcap_disable(ht_capa, ht_capa_mask, ht_cap, + IEEE80211_HT_CAP_LDPC_CODING); + + /* Allow user to enable 40 MHz intolerant bit. */ + __check_htcap_enable(ht_capa, ht_capa_mask, ht_cap, + IEEE80211_HT_CAP_40MHZ_INTOLERANT); + /* Allow user to decrease AMPDU factor */ if (ht_capa_mask->ampdu_params_info & IEEE80211_HT_AMPDU_PARM_FACTOR) { @@ -375,7 +395,7 @@ void ieee80211_send_delba(struct ieee80211_sub_if_data *sdata, mgmt->u.action.u.delba.params = cpu_to_le16(params); mgmt->u.action.u.delba.reason_code = cpu_to_le16(reason_code); - ieee80211_tx_skb_tid(sdata, skb, tid); + ieee80211_tx_skb(sdata, skb); } void ieee80211_process_delba(struct ieee80211_sub_if_data *sdata, @@ -448,14 +468,27 @@ int ieee80211_send_smps_action(struct ieee80211_sub_if_data *sdata, return 0; } -void ieee80211_request_smps_work(struct work_struct *work) +void ieee80211_request_smps_mgd_work(struct work_struct *work) { struct ieee80211_sub_if_data *sdata = container_of(work, struct ieee80211_sub_if_data, u.mgd.request_smps_work); sdata_lock(sdata); - __ieee80211_request_smps(sdata, sdata->u.mgd.driver_smps_mode); + __ieee80211_request_smps_mgd(sdata, sdata->u.mgd.driver_smps_mode); + sdata_unlock(sdata); +} + +void ieee80211_request_smps_ap_work(struct work_struct *work) +{ + struct ieee80211_sub_if_data *sdata = + container_of(work, struct ieee80211_sub_if_data, + u.ap.request_smps_work); + + sdata_lock(sdata); + if (sdata_dereference(sdata->u.ap.beacon, sdata)) + __ieee80211_request_smps_ap(sdata, + sdata->u.ap.driver_smps_mode); sdata_unlock(sdata); } @@ -464,19 +497,26 @@ void ieee80211_request_smps(struct ieee80211_vif *vif, { struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); - if (WARN_ON(vif->type != NL80211_IFTYPE_STATION)) - return; - - if (WARN_ON(smps_mode == IEEE80211_SMPS_OFF)) - smps_mode = IEEE80211_SMPS_AUTOMATIC; - - if (sdata->u.mgd.driver_smps_mode == smps_mode) + if (WARN_ON_ONCE(vif->type != NL80211_IFTYPE_STATION && + vif->type != NL80211_IFTYPE_AP)) return; - sdata->u.mgd.driver_smps_mode = smps_mode; - - ieee80211_queue_work(&sdata->local->hw, - &sdata->u.mgd.request_smps_work); + if (vif->type == NL80211_IFTYPE_STATION) { + if (sdata->u.mgd.driver_smps_mode == smps_mode) + return; + sdata->u.mgd.driver_smps_mode = smps_mode; + ieee80211_queue_work(&sdata->local->hw, + &sdata->u.mgd.request_smps_work); + } else { + /* AUTOMATIC is meaningless in AP mode */ + if (WARN_ON_ONCE(smps_mode == IEEE80211_SMPS_AUTOMATIC)) + return; + if (sdata->u.ap.driver_smps_mode == smps_mode) + return; + sdata->u.ap.driver_smps_mode = smps_mode; + ieee80211_queue_work(&sdata->local->hw, + &sdata->u.ap.request_smps_work); + } } /* this might change ... don't want non-open drivers using it */ EXPORT_SYMBOL_GPL(ieee80211_request_smps); diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c index a12afe77bb2..18ee0a256b1 100644 --- a/net/mac80211/ibss.c +++ b/net/mac80211/ibss.c @@ -39,7 +39,8 @@ ieee80211_ibss_build_presp(struct ieee80211_sub_if_data *sdata, const int beacon_int, const u32 basic_rates, const u16 capability, u64 tsf, struct cfg80211_chan_def *chandef, - bool *have_higher_than_11mbit) + bool *have_higher_than_11mbit, + struct cfg80211_csa_settings *csa_settings) { struct ieee80211_if_ibss *ifibss = &sdata->u.ibss; struct ieee80211_local *local = sdata->local; @@ -59,6 +60,7 @@ ieee80211_ibss_build_presp(struct ieee80211_sub_if_data *sdata, 2 + 8 /* max Supported Rates */ + 3 /* max DS params */ + 4 /* IBSS params */ + + 5 /* Channel Switch Announcement */ + 2 + (IEEE80211_MAX_SUPP_RATES - 8) + 2 + sizeof(struct ieee80211_ht_cap) + 2 + sizeof(struct ieee80211_ht_operation) + @@ -135,6 +137,16 @@ ieee80211_ibss_build_presp(struct ieee80211_sub_if_data *sdata, *pos++ = 0; *pos++ = 0; + if (csa_settings) { + *pos++ = WLAN_EID_CHANNEL_SWITCH; + *pos++ = 3; + *pos++ = csa_settings->block_tx ? 1 : 0; + *pos++ = ieee80211_frequency_to_channel( + csa_settings->chandef.chan->center_freq); + sdata->csa_counter_offset_beacon[0] = (pos - presp->head); + *pos++ = csa_settings->count; + } + /* put the remaining rates in WLAN_EID_EXT_SUPP_RATES */ if (rates_n > 8) { *pos++ = WLAN_EID_EXT_SUPP_RATES; @@ -208,7 +220,6 @@ static void __ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata, { struct ieee80211_if_ibss *ifibss = &sdata->u.ibss; struct ieee80211_local *local = sdata->local; - struct ieee80211_supported_band *sband; struct ieee80211_mgmt *mgmt; struct cfg80211_bss *bss; u32 bss_change; @@ -217,6 +228,8 @@ static void __ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata, struct beacon_data *presp; enum nl80211_bss_scan_width scan_width; bool have_higher_than_11mbit; + bool radar_required; + int err; sdata_assert_lock(sdata); @@ -235,11 +248,12 @@ static void __ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata, ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_IBSS | BSS_CHANGED_BEACON_ENABLED); + drv_leave_ibss(local, sdata); } presp = rcu_dereference_protected(ifibss->presp, lockdep_is_held(&sdata->wdev.mtx)); - rcu_assign_pointer(ifibss->presp, NULL); + RCU_INIT_POINTER(ifibss->presp, NULL); if (presp) kfree_rcu(presp, rcu_head); @@ -248,7 +262,8 @@ static void __ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata, /* make a copy of the chandef, it could be modified below. */ chandef = *req_chandef; chan = chandef.chan; - if (!cfg80211_reg_can_beacon(local->hw.wiphy, &chandef)) { + if (!cfg80211_reg_can_beacon(local->hw.wiphy, &chandef, + NL80211_IFTYPE_ADHOC)) { if (chandef.width == NL80211_CHAN_WIDTH_5 || chandef.width == NL80211_CHAN_WIDTH_10 || chandef.width == NL80211_CHAN_WIDTH_20_NOHT || @@ -259,24 +274,47 @@ static void __ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata, } chandef.width = NL80211_CHAN_WIDTH_20; chandef.center_freq1 = chan->center_freq; + /* check again for downgraded chandef */ + if (!cfg80211_reg_can_beacon(local->hw.wiphy, &chandef, + NL80211_IFTYPE_ADHOC)) { + sdata_info(sdata, + "Failed to join IBSS, beacons forbidden\n"); + return; + } } - ieee80211_vif_release_channel(sdata); + err = cfg80211_chandef_dfs_required(sdata->local->hw.wiphy, + &chandef, NL80211_IFTYPE_ADHOC); + if (err < 0) { + sdata_info(sdata, + "Failed to join IBSS, invalid chandef\n"); + return; + } + if (err > 0 && !ifibss->userspace_handles_dfs) { + sdata_info(sdata, + "Failed to join IBSS, DFS channel without control program\n"); + return; + } + + radar_required = err; + + mutex_lock(&local->mtx); if (ieee80211_vif_use_channel(sdata, &chandef, ifibss->fixed_channel ? IEEE80211_CHANCTX_SHARED : IEEE80211_CHANCTX_EXCLUSIVE)) { sdata_info(sdata, "Failed to join IBSS, no channel context\n"); + mutex_unlock(&local->mtx); return; } + sdata->radar_required = radar_required; + mutex_unlock(&local->mtx); memcpy(ifibss->bssid, bssid, ETH_ALEN); - sband = local->hw.wiphy->bands[chan->band]; - presp = ieee80211_ibss_build_presp(sdata, beacon_int, basic_rates, capability, tsf, &chandef, - &have_higher_than_11mbit); + &have_higher_than_11mbit, NULL); if (!presp) return; @@ -317,11 +355,28 @@ static void __ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata, else sdata->flags &= ~IEEE80211_SDATA_OPERATING_GMODE; + ieee80211_set_wmm_default(sdata, true); + sdata->vif.bss_conf.ibss_joined = true; sdata->vif.bss_conf.ibss_creator = creator; - ieee80211_bss_info_change_notify(sdata, bss_change); - ieee80211_set_wmm_default(sdata, true); + err = drv_join_ibss(local, sdata); + if (err) { + sdata->vif.bss_conf.ibss_joined = false; + sdata->vif.bss_conf.ibss_creator = false; + sdata->vif.bss_conf.enable_beacon = false; + sdata->vif.bss_conf.ssid_len = 0; + RCU_INIT_POINTER(ifibss->presp, NULL); + kfree_rcu(presp, rcu_head); + mutex_lock(&local->mtx); + ieee80211_vif_release_channel(sdata); + mutex_unlock(&local->mtx); + sdata_info(sdata, "Failed to join IBSS, driver failure: %d\n", + err); + return; + } + + ieee80211_bss_info_change_notify(sdata, bss_change); ifibss->state = IEEE80211_IBSS_MLME_JOINED; mod_timer(&ifibss->timer, @@ -333,7 +388,7 @@ static void __ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata, presp->head_len, 0, GFP_KERNEL); cfg80211_put_bss(local->hw.wiphy, bss); netif_carrier_on(sdata->dev); - cfg80211_ibss_joined(sdata->dev, ifibss->bssid, GFP_KERNEL); + cfg80211_ibss_joined(sdata->dev, ifibss->bssid, chan, GFP_KERNEL); } static void ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata, @@ -416,6 +471,111 @@ static void ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata, tsf, false); } +int ieee80211_ibss_csa_beacon(struct ieee80211_sub_if_data *sdata, + struct cfg80211_csa_settings *csa_settings) +{ + struct ieee80211_if_ibss *ifibss = &sdata->u.ibss; + struct beacon_data *presp, *old_presp; + struct cfg80211_bss *cbss; + const struct cfg80211_bss_ies *ies; + u16 capability; + u64 tsf; + int ret = 0; + + sdata_assert_lock(sdata); + + capability = WLAN_CAPABILITY_IBSS; + + if (ifibss->privacy) + capability |= WLAN_CAPABILITY_PRIVACY; + + cbss = cfg80211_get_bss(sdata->local->hw.wiphy, ifibss->chandef.chan, + ifibss->bssid, ifibss->ssid, + ifibss->ssid_len, WLAN_CAPABILITY_IBSS | + WLAN_CAPABILITY_PRIVACY, + capability); + + if (WARN_ON(!cbss)) { + ret = -EINVAL; + goto out; + } + + rcu_read_lock(); + ies = rcu_dereference(cbss->ies); + tsf = ies->tsf; + rcu_read_unlock(); + cfg80211_put_bss(sdata->local->hw.wiphy, cbss); + + old_presp = rcu_dereference_protected(ifibss->presp, + lockdep_is_held(&sdata->wdev.mtx)); + + presp = ieee80211_ibss_build_presp(sdata, + sdata->vif.bss_conf.beacon_int, + sdata->vif.bss_conf.basic_rates, + capability, tsf, &ifibss->chandef, + NULL, csa_settings); + if (!presp) { + ret = -ENOMEM; + goto out; + } + + rcu_assign_pointer(ifibss->presp, presp); + if (old_presp) + kfree_rcu(old_presp, rcu_head); + + return BSS_CHANGED_BEACON; + out: + return ret; +} + +int ieee80211_ibss_finish_csa(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_ibss *ifibss = &sdata->u.ibss; + struct cfg80211_bss *cbss; + int err, changed = 0; + u16 capability; + + sdata_assert_lock(sdata); + + /* update cfg80211 bss information with the new channel */ + if (!is_zero_ether_addr(ifibss->bssid)) { + capability = WLAN_CAPABILITY_IBSS; + + if (ifibss->privacy) + capability |= WLAN_CAPABILITY_PRIVACY; + + cbss = cfg80211_get_bss(sdata->local->hw.wiphy, + ifibss->chandef.chan, + ifibss->bssid, ifibss->ssid, + ifibss->ssid_len, WLAN_CAPABILITY_IBSS | + WLAN_CAPABILITY_PRIVACY, + capability); + /* XXX: should not really modify cfg80211 data */ + if (cbss) { + cbss->channel = sdata->csa_chandef.chan; + cfg80211_put_bss(sdata->local->hw.wiphy, cbss); + } + } + + ifibss->chandef = sdata->csa_chandef; + + /* generate the beacon */ + err = ieee80211_ibss_csa_beacon(sdata, NULL); + if (err < 0) + return err; + + changed |= err; + + return changed; +} + +void ieee80211_ibss_stop(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_ibss *ifibss = &sdata->u.ibss; + + cancel_work_sync(&ifibss->csa_connection_drop_work); +} + static struct sta_info *ieee80211_ibss_finish_sta(struct sta_info *sta) __acquires(RCU) { @@ -499,6 +659,290 @@ ieee80211_ibss_add_sta(struct ieee80211_sub_if_data *sdata, const u8 *bssid, return ieee80211_ibss_finish_sta(sta); } +static int ieee80211_sta_active_ibss(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_local *local = sdata->local; + int active = 0; + struct sta_info *sta; + + sdata_assert_lock(sdata); + + 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; + } + } + + rcu_read_unlock(); + + return active; +} + +static void ieee80211_ibss_disconnect(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_ibss *ifibss = &sdata->u.ibss; + struct ieee80211_local *local = sdata->local; + struct cfg80211_bss *cbss; + struct beacon_data *presp; + struct sta_info *sta; + u16 capability; + + if (!is_zero_ether_addr(ifibss->bssid)) { + capability = WLAN_CAPABILITY_IBSS; + + if (ifibss->privacy) + capability |= WLAN_CAPABILITY_PRIVACY; + + cbss = cfg80211_get_bss(local->hw.wiphy, ifibss->chandef.chan, + ifibss->bssid, ifibss->ssid, + ifibss->ssid_len, WLAN_CAPABILITY_IBSS | + WLAN_CAPABILITY_PRIVACY, + capability); + + if (cbss) { + cfg80211_unlink_bss(local->hw.wiphy, cbss); + cfg80211_put_bss(sdata->local->hw.wiphy, cbss); + } + } + + ifibss->state = IEEE80211_IBSS_MLME_SEARCH; + + sta_info_flush(sdata); + + spin_lock_bh(&ifibss->incomplete_lock); + while (!list_empty(&ifibss->incomplete_stations)) { + sta = list_first_entry(&ifibss->incomplete_stations, + struct sta_info, list); + list_del(&sta->list); + spin_unlock_bh(&ifibss->incomplete_lock); + + sta_info_free(local, sta); + spin_lock_bh(&ifibss->incomplete_lock); + } + spin_unlock_bh(&ifibss->incomplete_lock); + + netif_carrier_off(sdata->dev); + + sdata->vif.bss_conf.ibss_joined = false; + sdata->vif.bss_conf.ibss_creator = false; + sdata->vif.bss_conf.enable_beacon = false; + sdata->vif.bss_conf.ssid_len = 0; + + /* remove beacon */ + presp = rcu_dereference_protected(ifibss->presp, + lockdep_is_held(&sdata->wdev.mtx)); + RCU_INIT_POINTER(sdata->u.ibss.presp, NULL); + if (presp) + kfree_rcu(presp, rcu_head); + + clear_bit(SDATA_STATE_OFFCHANNEL_BEACON_STOPPED, &sdata->state); + ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON_ENABLED | + BSS_CHANGED_IBSS); + drv_leave_ibss(local, sdata); + mutex_lock(&local->mtx); + ieee80211_vif_release_channel(sdata); + mutex_unlock(&local->mtx); +} + +static void ieee80211_csa_connection_drop_work(struct work_struct *work) +{ + struct ieee80211_sub_if_data *sdata = + container_of(work, struct ieee80211_sub_if_data, + u.ibss.csa_connection_drop_work); + + sdata_lock(sdata); + + ieee80211_ibss_disconnect(sdata); + synchronize_rcu(); + skb_queue_purge(&sdata->skb_queue); + + /* trigger a scan to find another IBSS network to join */ + ieee80211_queue_work(&sdata->local->hw, &sdata->work); + + sdata_unlock(sdata); +} + +static void ieee80211_ibss_csa_mark_radar(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_ibss *ifibss = &sdata->u.ibss; + int err; + + /* if the current channel is a DFS channel, mark the channel as + * unavailable. + */ + err = cfg80211_chandef_dfs_required(sdata->local->hw.wiphy, + &ifibss->chandef, + NL80211_IFTYPE_ADHOC); + if (err > 0) + cfg80211_radar_event(sdata->local->hw.wiphy, &ifibss->chandef, + GFP_ATOMIC); +} + +static bool +ieee80211_ibss_process_chanswitch(struct ieee80211_sub_if_data *sdata, + struct ieee802_11_elems *elems, + bool beacon) +{ + struct cfg80211_csa_settings params; + struct ieee80211_csa_ie csa_ie; + struct ieee80211_if_ibss *ifibss = &sdata->u.ibss; + enum nl80211_channel_type ch_type; + int err; + u32 sta_flags; + + sdata_assert_lock(sdata); + + sta_flags = IEEE80211_STA_DISABLE_VHT; + switch (ifibss->chandef.width) { + case NL80211_CHAN_WIDTH_5: + case NL80211_CHAN_WIDTH_10: + case NL80211_CHAN_WIDTH_20_NOHT: + sta_flags |= IEEE80211_STA_DISABLE_HT; + /* fall through */ + case NL80211_CHAN_WIDTH_20: + sta_flags |= IEEE80211_STA_DISABLE_40MHZ; + break; + default: + break; + } + + memset(¶ms, 0, sizeof(params)); + memset(&csa_ie, 0, sizeof(csa_ie)); + err = ieee80211_parse_ch_switch_ie(sdata, elems, beacon, + ifibss->chandef.chan->band, + sta_flags, ifibss->bssid, &csa_ie); + /* can't switch to destination channel, fail */ + if (err < 0) + goto disconnect; + + /* did not contain a CSA */ + if (err) + return false; + + /* channel switch is not supported, disconnect */ + if (!(sdata->local->hw.wiphy->flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH)) + goto disconnect; + + params.count = csa_ie.count; + params.chandef = csa_ie.chandef; + + switch (ifibss->chandef.width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + case NL80211_CHAN_WIDTH_40: + /* keep our current HT mode (HT20/HT40+/HT40-), even if + * another mode has been announced. The mode is not adopted + * within the beacon while doing CSA and we should therefore + * keep the mode which we announce. + */ + ch_type = cfg80211_get_chandef_type(&ifibss->chandef); + cfg80211_chandef_create(¶ms.chandef, params.chandef.chan, + ch_type); + break; + case NL80211_CHAN_WIDTH_5: + case NL80211_CHAN_WIDTH_10: + if (params.chandef.width != ifibss->chandef.width) { + sdata_info(sdata, + "IBSS %pM received channel switch from incompatible channel width (%d MHz, width:%d, CF1/2: %d/%d MHz), disconnecting\n", + ifibss->bssid, + params.chandef.chan->center_freq, + params.chandef.width, + params.chandef.center_freq1, + params.chandef.center_freq2); + goto disconnect; + } + break; + default: + /* should not happen, sta_flags should prevent VHT modes. */ + WARN_ON(1); + goto disconnect; + } + + if (!cfg80211_reg_can_beacon(sdata->local->hw.wiphy, ¶ms.chandef, + NL80211_IFTYPE_ADHOC)) { + sdata_info(sdata, + "IBSS %pM switches to unsupported channel (%d MHz, width:%d, CF1/2: %d/%d MHz), disconnecting\n", + ifibss->bssid, + params.chandef.chan->center_freq, + params.chandef.width, + params.chandef.center_freq1, + params.chandef.center_freq2); + goto disconnect; + } + + err = cfg80211_chandef_dfs_required(sdata->local->hw.wiphy, + ¶ms.chandef, + NL80211_IFTYPE_ADHOC); + if (err < 0) + goto disconnect; + if (err > 0 && !ifibss->userspace_handles_dfs) { + /* IBSS-DFS only allowed with a control program */ + goto disconnect; + } + + params.radar_required = err; + + if (cfg80211_chandef_identical(¶ms.chandef, + &sdata->vif.bss_conf.chandef)) { + ibss_dbg(sdata, + "received csa with an identical chandef, ignoring\n"); + return true; + } + + /* all checks done, now perform the channel switch. */ + ibss_dbg(sdata, + "received channel switch announcement to go to channel %d MHz\n", + params.chandef.chan->center_freq); + + params.block_tx = !!csa_ie.mode; + + if (ieee80211_channel_switch(sdata->local->hw.wiphy, sdata->dev, + ¶ms)) + goto disconnect; + + ieee80211_ibss_csa_mark_radar(sdata); + + return true; +disconnect: + ibss_dbg(sdata, "Can't handle channel switch, disconnect\n"); + ieee80211_queue_work(&sdata->local->hw, + &ifibss->csa_connection_drop_work); + + ieee80211_ibss_csa_mark_radar(sdata); + + return true; +} + +static void +ieee80211_rx_mgmt_spectrum_mgmt(struct ieee80211_sub_if_data *sdata, + struct ieee80211_mgmt *mgmt, size_t len, + struct ieee80211_rx_status *rx_status, + struct ieee802_11_elems *elems) +{ + int required_len; + + if (len < IEEE80211_MIN_ACTION_SIZE + 1) + return; + + /* CSA is the only action we handle for now */ + if (mgmt->u.action.u.measurement.action_code != + WLAN_ACTION_SPCT_CHL_SWITCH) + return; + + required_len = IEEE80211_MIN_ACTION_SIZE + + sizeof(mgmt->u.action.u.chan_switch); + if (len < required_len) + return; + + if (!sdata->vif.csa_active) + ieee80211_ibss_process_chanswitch(sdata, elems, false); +} + static void ieee80211_rx_mgmt_deauth_ibss(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, size_t len) @@ -550,7 +994,6 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata, struct ieee802_11_elems *elems) { struct ieee80211_local *local = sdata->local; - int freq; struct cfg80211_bss *cbss; struct ieee80211_bss *bss; struct sta_info *sta; @@ -562,15 +1005,8 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata, struct ieee80211_supported_band *sband = local->hw.wiphy->bands[band]; bool rates_updated = false; - if (elems->ds_params) - freq = ieee80211_channel_to_frequency(elems->ds_params[0], - band); - else - freq = rx_status->freq; - - channel = ieee80211_get_channel(local->hw.wiphy, freq); - - if (!channel || channel->flags & IEEE80211_CHAN_DISABLED) + channel = ieee80211_get_channel(local->hw.wiphy, rx_status->freq); + if (!channel) return; if (sdata->vif.type == NL80211_IFTYPE_ADHOC && @@ -661,10 +1097,6 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata, /* check if we need to merge IBSS */ - /* we use a fixed BSSID */ - if (sdata->u.ibss.fixed_bssid) - goto put_bss; - /* not an IBSS */ if (!(cbss->capability & WLAN_CAPABILITY_IBSS)) goto put_bss; @@ -680,10 +1112,19 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata, sdata->u.ibss.ssid_len)) goto put_bss; + /* process channel switch */ + if (sdata->vif.csa_active || + ieee80211_ibss_process_chanswitch(sdata, elems, true)) + goto put_bss; + /* same BSSID */ if (ether_addr_equal(cbss->bssid, sdata->u.ibss.bssid)) goto put_bss; + /* we use a fixed BSSID */ + if (sdata->u.ibss.fixed_bssid) + goto put_bss; + if (ieee80211_have_rx_timestamp(rx_status)) { /* time when timestamp field was received */ rx_timestamp = @@ -775,30 +1216,6 @@ void ieee80211_ibss_rx_no_sta(struct ieee80211_sub_if_data *sdata, ieee80211_queue_work(&local->hw, &sdata->work); } -static int ieee80211_sta_active_ibss(struct ieee80211_sub_if_data *sdata) -{ - struct ieee80211_local *local = sdata->local; - int active = 0; - struct sta_info *sta; - - sdata_assert_lock(sdata); - - 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; - } - } - - rcu_read_unlock(); - - return active; -} - static void ieee80211_ibss_sta_expire(struct ieee80211_sub_if_data *sdata) { struct ieee80211_local *local = sdata->local; @@ -1042,6 +1459,11 @@ static void ieee80211_rx_mgmt_probe_req(struct ieee80211_sub_if_data *sdata, memcpy(((struct ieee80211_mgmt *) skb->data)->da, mgmt->sa, ETH_ALEN); ibss_dbg(sdata, "Sending ProbeResp to %pM\n", mgmt->sa); IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT; + + /* avoid excessive retries for probe request to wildcard SSIDs */ + if (pos[1] == 0) + IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_NO_ACK; + ieee80211_tx_skb(sdata, skb); } @@ -1076,6 +1498,8 @@ void ieee80211_ibss_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata, struct ieee80211_rx_status *rx_status; struct ieee80211_mgmt *mgmt; u16 fc; + struct ieee802_11_elems elems; + int ies_len; rx_status = IEEE80211_SKB_RXCB(skb); mgmt = (struct ieee80211_mgmt *) skb->data; @@ -1101,6 +1525,27 @@ void ieee80211_ibss_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata, case IEEE80211_STYPE_DEAUTH: ieee80211_rx_mgmt_deauth_ibss(sdata, mgmt, skb->len); break; + case IEEE80211_STYPE_ACTION: + switch (mgmt->u.action.category) { + case WLAN_CATEGORY_SPECTRUM_MGMT: + ies_len = skb->len - + offsetof(struct ieee80211_mgmt, + u.action.u.chan_switch.variable); + + if (ies_len < 0) + break; + + ieee802_11_parse_elems( + mgmt->u.action.u.chan_switch.variable, + ies_len, true, &elems); + + if (elems.parse_error) + break; + + ieee80211_rx_mgmt_spectrum_mgmt(sdata, mgmt, skb->len, + rx_status, &elems); + break; + } } mgmt_out: @@ -1167,6 +1612,8 @@ void ieee80211_ibss_setup_sdata(struct ieee80211_sub_if_data *sdata) (unsigned long) sdata); INIT_LIST_HEAD(&ifibss->incomplete_stations); spin_lock_init(&ifibss->incomplete_lock); + INIT_WORK(&ifibss->csa_connection_drop_work, + ieee80211_csa_connection_drop_work); } /* scan finished notification */ @@ -1192,7 +1639,33 @@ int ieee80211_ibss_join(struct ieee80211_sub_if_data *sdata, u32 changed = 0; u32 rate_flags; struct ieee80211_supported_band *sband; + enum ieee80211_chanctx_mode chanmode; + struct ieee80211_local *local = sdata->local; + int radar_detect_width = 0; int i; + int ret; + + ret = cfg80211_chandef_dfs_required(local->hw.wiphy, + ¶ms->chandef, + sdata->wdev.iftype); + if (ret < 0) + return ret; + + if (ret > 0) { + if (!params->userspace_handles_dfs) + return -EINVAL; + radar_detect_width = BIT(params->chandef.width); + } + + chanmode = (params->channel_fixed && !ret) ? + IEEE80211_CHANCTX_SHARED : IEEE80211_CHANCTX_EXCLUSIVE; + + mutex_lock(&local->chanctx_mtx); + ret = ieee80211_check_combinations(sdata, ¶ms->chandef, chanmode, + radar_detect_width); + mutex_unlock(&local->chanctx_mtx); + if (ret < 0) + return ret; if (params->bssid) { memcpy(sdata->u.ibss.bssid, params->bssid, ETH_ALEN); @@ -1202,11 +1675,13 @@ int ieee80211_ibss_join(struct ieee80211_sub_if_data *sdata, sdata->u.ibss.privacy = params->privacy; sdata->u.ibss.control_port = params->control_port; + sdata->u.ibss.userspace_handles_dfs = params->userspace_handles_dfs; sdata->u.ibss.basic_rates = params->basic_rates; + sdata->u.ibss.last_scan_completed = jiffies; /* fix basic_rates if channel does not support these rates */ rate_flags = ieee80211_chandef_rate_flags(¶ms->chandef); - sband = sdata->local->hw.wiphy->bands[params->chandef.chan->band]; + sband = local->hw.wiphy->bands[params->chandef.chan->band]; for (i = 0; i < sband->n_bitrates; i++) { if ((rate_flags & sband->bitrates[i].flags) != rate_flags) sdata->u.ibss.basic_rates &= ~BIT(i); @@ -1255,9 +1730,9 @@ int ieee80211_ibss_join(struct ieee80211_sub_if_data *sdata, ieee80211_bss_info_change_notify(sdata, changed); sdata->smps_mode = IEEE80211_SMPS_OFF; - sdata->needed_rx_chains = sdata->local->rx_chains; + sdata->needed_rx_chains = local->rx_chains; - ieee80211_queue_work(&sdata->local->hw, &sdata->work); + ieee80211_queue_work(&local->hw, &sdata->work); return 0; } @@ -1265,73 +1740,19 @@ int ieee80211_ibss_join(struct ieee80211_sub_if_data *sdata, int ieee80211_ibss_leave(struct ieee80211_sub_if_data *sdata) { struct ieee80211_if_ibss *ifibss = &sdata->u.ibss; - struct ieee80211_local *local = sdata->local; - struct cfg80211_bss *cbss; - u16 capability; - int active_ibss; - struct sta_info *sta; - struct beacon_data *presp; - - active_ibss = ieee80211_sta_active_ibss(sdata); - - if (!active_ibss && !is_zero_ether_addr(ifibss->bssid)) { - capability = WLAN_CAPABILITY_IBSS; - if (ifibss->privacy) - capability |= WLAN_CAPABILITY_PRIVACY; - - cbss = cfg80211_get_bss(local->hw.wiphy, ifibss->chandef.chan, - ifibss->bssid, ifibss->ssid, - ifibss->ssid_len, WLAN_CAPABILITY_IBSS | - WLAN_CAPABILITY_PRIVACY, - capability); - - if (cbss) { - cfg80211_unlink_bss(local->hw.wiphy, cbss); - cfg80211_put_bss(local->hw.wiphy, cbss); - } - } - - ifibss->state = IEEE80211_IBSS_MLME_SEARCH; - memset(ifibss->bssid, 0, ETH_ALEN); + ieee80211_ibss_disconnect(sdata); ifibss->ssid_len = 0; - - sta_info_flush(sdata); - - spin_lock_bh(&ifibss->incomplete_lock); - while (!list_empty(&ifibss->incomplete_stations)) { - sta = list_first_entry(&ifibss->incomplete_stations, - struct sta_info, list); - list_del(&sta->list); - spin_unlock_bh(&ifibss->incomplete_lock); - - sta_info_free(local, sta); - spin_lock_bh(&ifibss->incomplete_lock); - } - spin_unlock_bh(&ifibss->incomplete_lock); - - netif_carrier_off(sdata->dev); + memset(ifibss->bssid, 0, ETH_ALEN); /* remove beacon */ kfree(sdata->u.ibss.ie); - presp = rcu_dereference_protected(ifibss->presp, - lockdep_is_held(&sdata->wdev.mtx)); - RCU_INIT_POINTER(sdata->u.ibss.presp, NULL); /* on the next join, re-program HT parameters */ memset(&ifibss->ht_capa, 0, sizeof(ifibss->ht_capa)); memset(&ifibss->ht_capa_mask, 0, sizeof(ifibss->ht_capa_mask)); - sdata->vif.bss_conf.ibss_joined = false; - sdata->vif.bss_conf.ibss_creator = false; - sdata->vif.bss_conf.enable_beacon = false; - sdata->vif.bss_conf.ssid_len = 0; - clear_bit(SDATA_STATE_OFFCHANNEL_BEACON_STOPPED, &sdata->state); - ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON_ENABLED | - BSS_CHANGED_IBSS); - ieee80211_vif_release_channel(sdata); synchronize_rcu(); - kfree(presp); skb_queue_purge(&sdata->skb_queue); diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index b6186517ec5..ac9836e0aab 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -232,6 +232,7 @@ struct ieee80211_rx_data { struct beacon_data { u8 *head, *tail; int head_len, tail_len; + struct ieee80211_meshconf_ie *meshconf; struct rcu_head rcu_head; }; @@ -245,7 +246,8 @@ struct ps_data { /* yes, this looks ugly, but guarantees that we can later use * bitmap_empty :) * NB: don't touch this bitmap, use sta_info_{set,clear}_tim_bit */ - u8 tim[sizeof(unsigned long) * BITS_TO_LONGS(IEEE80211_MAX_AID + 1)]; + u8 tim[sizeof(unsigned long) * BITS_TO_LONGS(IEEE80211_MAX_AID + 1)] + __aligned(__alignof__(unsigned long)); struct sk_buff_head bc_buf; atomic_t num_sta_ps; /* number of stations in PS mode */ int dtim_count; @@ -258,10 +260,14 @@ struct ieee80211_if_ap { /* to be used after channel switch. */ struct cfg80211_beacon_data *next_beacon; - struct list_head vlans; + struct list_head vlans; /* write-protected with RTNL and local->mtx */ struct ps_data ps; atomic_t num_mcast_sta; /* number of stations receiving multicast */ + enum ieee80211_smps_mode req_smps, /* requested smps mode */ + driver_smps_mode; /* smps mode request */ + + struct work_struct request_smps_work; }; struct ieee80211_if_wds { @@ -270,7 +276,7 @@ struct ieee80211_if_wds { }; struct ieee80211_if_vlan { - struct list_head list; + struct list_head list; /* write-protected with RTNL and local->mtx */ /* used for all tx if the VLAN is configured to 4-addr mode */ struct sta_info __rcu *sta; @@ -311,6 +317,7 @@ struct ieee80211_roc_work { bool started, abort, hw_begun, notified; bool to_be_freed; + bool on_channel; unsigned long hw_start_time; @@ -322,7 +329,6 @@ struct ieee80211_roc_work { /* flags used in struct ieee80211_if_managed.flags */ enum ieee80211_sta_flags { - IEEE80211_STA_BEACON_POLL = BIT(0), IEEE80211_STA_CONNECTION_POLL = BIT(1), IEEE80211_STA_CONTROL_PORT = BIT(2), IEEE80211_STA_DISABLE_HT = BIT(4), @@ -335,6 +341,7 @@ enum ieee80211_sta_flags { IEEE80211_STA_DISABLE_VHT = BIT(11), IEEE80211_STA_DISABLE_80P80MHZ = BIT(12), IEEE80211_STA_DISABLE_160MHZ = BIT(13), + IEEE80211_STA_DISABLE_WMM = BIT(14), }; struct ieee80211_mgd_auth_data { @@ -487,6 +494,7 @@ struct ieee80211_if_managed { struct ieee80211_if_ibss { struct timer_list timer; + struct work_struct csa_connection_drop_work; unsigned long last_scan_completed; @@ -497,6 +505,7 @@ struct ieee80211_if_ibss { bool privacy; bool control_port; + bool userspace_handles_dfs; u8 bssid[ETH_ALEN] __aligned(2); u8 ssid[IEEE80211_MAX_SSID_LEN]; @@ -534,10 +543,18 @@ struct ieee80211_mesh_sync_ops { struct ieee80211_mgmt *mgmt, struct ieee802_11_elems *elems, struct ieee80211_rx_status *rx_status); - void (*adjust_tbtt)(struct ieee80211_sub_if_data *sdata); + + /* should be called with beacon_data under RCU read lock */ + void (*adjust_tbtt)(struct ieee80211_sub_if_data *sdata, + struct beacon_data *beacon); /* add other framework functions here */ }; +struct mesh_csa_settings { + struct rcu_head rcu_head; + struct cfg80211_csa_settings settings; +}; + struct ieee80211_if_mesh { struct timer_list housekeeping_timer; struct timer_list mesh_path_timer; @@ -598,6 +615,18 @@ struct ieee80211_if_mesh { int ps_peers_light_sleep; int ps_peers_deep_sleep; struct ps_data ps; + /* Channel Switching Support */ + struct mesh_csa_settings __rcu *csa; + enum { + IEEE80211_MESH_CSA_ROLE_NONE, + IEEE80211_MESH_CSA_ROLE_INIT, + IEEE80211_MESH_CSA_ROLE_REPEATER, + } csa_role; + u8 chsw_ttl; + u16 pre_value; + + /* offset from skb->data while building IE */ + int meshconf_offset; }; #ifdef CONFIG_MAC80211_MESH @@ -663,13 +692,20 @@ struct ieee80211_chanctx { struct list_head list; struct rcu_head rcu_head; + struct list_head assigned_vifs; + struct list_head reserved_vifs; + enum ieee80211_chanctx_mode mode; - int refcount; bool driver_present; struct ieee80211_chanctx_conf conf; }; +struct mac80211_qos_map { + struct cfg80211_qos_map qos_map; + struct rcu_head rcu_head; +}; + struct ieee80211_sub_if_data { struct list_head list; @@ -712,13 +748,26 @@ struct ieee80211_sub_if_data { u16 sequence_number; __be16 control_port_protocol; bool control_port_no_encrypt; + int encrypt_headroom; struct ieee80211_tx_queue_params tx_conf[IEEE80211_NUM_ACS]; + struct mac80211_qos_map __rcu *qos_map; struct work_struct csa_finalize_work; - int csa_counter_offset_beacon; - int csa_counter_offset_presp; + u16 csa_counter_offset_beacon[IEEE80211_MAX_CSA_COUNTERS_NUM]; + u16 csa_counter_offset_presp[IEEE80211_MAX_CSA_COUNTERS_NUM]; bool csa_radar_required; + bool csa_block_tx; /* write-protected by sdata_lock and local->mtx */ + struct cfg80211_chan_def csa_chandef; + + struct list_head assigned_chanctx_list; /* protected by chanctx_mtx */ + struct list_head reserved_chanctx_list; /* protected by chanctx_mtx */ + + /* context reservation -- protected with chanctx_mtx */ + struct ieee80211_chanctx *reserved_chanctx; + struct cfg80211_chan_def reserved_chandef; + bool reserved_radar_required; + u8 csa_current_counter; /* used to reconfigure hardware SM PS */ struct work_struct recalc_smps; @@ -758,10 +807,6 @@ struct ieee80211_sub_if_data { u32 mntr_flags; } u; - spinlock_t cleanup_stations_lock; - struct list_head cleanup_stations; - struct work_struct cleanup_stations_wk; - #ifdef CONFIG_MAC80211_DEBUGFS struct { struct dentry *subdir_stations; @@ -795,6 +840,9 @@ static inline void sdata_unlock(struct ieee80211_sub_if_data *sdata) __release(&sdata->wdev.mtx); } +#define sdata_dereference(p, sdata) \ + rcu_dereference_protected(p, lockdep_is_held(&sdata->wdev.mtx)) + static inline void sdata_assert_lock(struct ieee80211_sub_if_data *sdata) { @@ -880,6 +928,24 @@ struct tpt_led_trigger { }; #endif +/* + * struct ieee80211_tx_latency_bin_ranges - Tx latency statistics bins ranges + * + * Measuring Tx latency statistics. Counts how many Tx frames transmitted in a + * certain latency range (in Milliseconds). Each station that uses these + * ranges will have bins to count the amount of frames received in that range. + * The user can configure the ranges via debugfs. + * If ranges is NULL then Tx latency statistics bins are disabled for all + * stations. + * + * @n_ranges: number of ranges that are taken in account + * @ranges: the ranges that the user requested or NULL if disabled. + */ +struct ieee80211_tx_latency_bin_ranges { + int n_ranges; + u32 ranges[]; +}; + /** * mac80211 scan flags - currently active scan mode * @@ -893,6 +959,8 @@ struct tpt_led_trigger { * that the scan completed. * @SCAN_ABORTED: Set for our scan work function when the driver reported * a scan complete for an aborted scan. + * @SCAN_HW_CANCELLED: Set for our scan work function when the scan is being + * cancelled. */ enum { SCAN_SW_SCANNING, @@ -900,6 +968,7 @@ enum { SCAN_ONCHANNEL_SCANNING, SCAN_COMPLETED, SCAN_ABORTED, + SCAN_HW_CANCELLED, }; /** @@ -1029,6 +1098,12 @@ struct ieee80211_local { struct timer_list sta_cleanup; int sta_generation; + /* + * Tx latency statistics parameters for all stations. + * Can enable via debugfs (NULL when disabled). + */ + struct ieee80211_tx_latency_bin_ranges __rcu *tx_latency; + struct sk_buff_head pending[IEEE80211_MAX_QUEUES]; struct tasklet_struct tx_pending_tasklet; @@ -1069,12 +1144,12 @@ struct ieee80211_local { struct work_struct sched_scan_stopped_work; struct ieee80211_sub_if_data __rcu *sched_scan_sdata; + struct cfg80211_sched_scan_request *sched_scan_req; unsigned long leave_oper_channel_time; enum mac80211_scan_state next_scan_state; struct delayed_work scan_work; struct ieee80211_sub_if_data __rcu *scan_sdata; - struct cfg80211_chan_def csa_chandef; /* For backward compatibility only -- do not use */ struct cfg80211_chan_def _oper_chandef; @@ -1180,6 +1255,8 @@ struct ieee80211_local { struct ieee80211_sub_if_data __rcu *p2p_sdata; + struct napi_struct *napi; + /* virtual monitor interface */ struct ieee80211_sub_if_data __rcu *monitor_sdata; struct cfg80211_chan_def monitor_chandef; @@ -1203,6 +1280,15 @@ struct ieee80211_ra_tid { u16 tid; }; +/* this struct holds the value parsing from channel switch IE */ +struct ieee80211_csa_ie { + struct cfg80211_chan_def chandef; + u8 mode; + u8 count; + u8 ttl; + u16 pre_value; +}; + /* Parsed Information Elements */ struct ieee802_11_elems { const u8 *ie_start; @@ -1239,6 +1325,7 @@ struct ieee802_11_elems { const struct ieee80211_timeout_interval_ie *timeout_int; const u8 *opmode_notif; const struct ieee80211_sec_chan_offs_ie *sec_chan_offs; + const struct ieee80211_mesh_chansw_params_ie *mesh_chansw_params_ie; /* length of them, respectively */ u8 ssid_len; @@ -1317,6 +1404,7 @@ void ieee80211_sta_reset_conn_monitor(struct ieee80211_sub_if_data *sdata); void ieee80211_mgd_stop(struct ieee80211_sub_if_data *sdata); void ieee80211_mgd_conn_tx_status(struct ieee80211_sub_if_data *sdata, __le16 fc, bool acked); +void ieee80211_mgd_quiesce(struct ieee80211_sub_if_data *sdata); void ieee80211_sta_restart(struct ieee80211_sub_if_data *sdata); /* IBSS code */ @@ -1330,11 +1418,18 @@ int ieee80211_ibss_leave(struct ieee80211_sub_if_data *sdata); void ieee80211_ibss_work(struct ieee80211_sub_if_data *sdata); void ieee80211_ibss_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb); +int ieee80211_ibss_csa_beacon(struct ieee80211_sub_if_data *sdata, + struct cfg80211_csa_settings *csa_settings); +int ieee80211_ibss_finish_csa(struct ieee80211_sub_if_data *sdata); +void ieee80211_ibss_stop(struct ieee80211_sub_if_data *sdata); /* mesh code */ void ieee80211_mesh_work(struct ieee80211_sub_if_data *sdata); void ieee80211_mesh_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb); +int ieee80211_mesh_csa_beacon(struct ieee80211_sub_if_data *sdata, + struct cfg80211_csa_settings *csa_settings); +int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata); /* scan/BSS handling */ void ieee80211_scan_work(struct work_struct *work); @@ -1360,9 +1455,13 @@ void ieee80211_rx_bss_put(struct ieee80211_local *local, struct ieee80211_bss *bss); /* scheduled scan handling */ +int +__ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, + struct cfg80211_sched_scan_request *req); int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, struct cfg80211_sched_scan_request *req); int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata); +void ieee80211_sched_scan_end(struct ieee80211_local *local); void ieee80211_sched_scan_stopped_work(struct work_struct *work); /* off-channel helpers */ @@ -1377,7 +1476,10 @@ void ieee80211_sw_roc_work(struct work_struct *work); void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc); /* channel switch handling */ +bool ieee80211_csa_needs_block_tx(struct ieee80211_local *local); void ieee80211_csa_finalize_work(struct work_struct *work); +int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_csa_settings *params); /* interface handling */ int ieee80211_iface_init(void); @@ -1400,8 +1502,6 @@ void ieee80211_del_virtual_monitor(struct ieee80211_local *local); bool __ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata); void ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata); -int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata, - struct cfg80211_beacon_data *params); static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata) { @@ -1431,7 +1531,10 @@ void ieee80211_send_delba(struct ieee80211_sub_if_data *sdata, int ieee80211_send_smps_action(struct ieee80211_sub_if_data *sdata, enum ieee80211_smps_mode smps, const u8 *da, const u8 *bssid); -void ieee80211_request_smps_work(struct work_struct *work); +void ieee80211_request_smps_ap_work(struct work_struct *work); +void ieee80211_request_smps_mgd_work(struct work_struct *work); +bool ieee80211_smps_is_restrictive(enum ieee80211_smps_mode smps_mode_old, + enum ieee80211_smps_mode smps_mode_new); void ___ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid, u16 initiator, u16 reason, bool stop); @@ -1471,6 +1574,9 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata, struct sta_info *sta); enum ieee80211_sta_rx_bandwidth ieee80211_sta_cur_vht_bw(struct sta_info *sta); void ieee80211_sta_set_rx_nss(struct sta_info *sta); +u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata, + struct sta_info *sta, u8 opmode, + enum ieee80211_band band, bool nss_only); void ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata, struct sta_info *sta, u8 opmode, enum ieee80211_band band, bool nss_only); @@ -1481,6 +1587,28 @@ void ieee80211_apply_vhtcap_overrides(struct ieee80211_sub_if_data *sdata, void ieee80211_process_measurement_req(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, size_t len); +/** + * ieee80211_parse_ch_switch_ie - parses channel switch IEs + * @sdata: the sdata of the interface which has received the frame + * @elems: parsed 802.11 elements received with the frame + * @beacon: indicates if the frame was a beacon or probe response + * @current_band: indicates the current band + * @sta_flags: contains information about own capabilities and restrictions + * to decide which channel switch announcements can be accepted. Only the + * following subset of &enum ieee80211_sta_flags are evaluated: + * %IEEE80211_STA_DISABLE_HT, %IEEE80211_STA_DISABLE_VHT, + * %IEEE80211_STA_DISABLE_40MHZ, %IEEE80211_STA_DISABLE_80P80MHZ, + * %IEEE80211_STA_DISABLE_160MHZ. + * @bssid: the currently connected bssid (for reporting) + * @csa_ie: parsed 802.11 csa elements on count, mode, chandef and mesh ttl. + All of them will be filled with if success only. + * Return: 0 on success, <0 on error and >0 if there is nothing to parse. + */ +int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata, + struct ieee802_11_elems *elems, bool beacon, + enum ieee80211_band current_band, + u32 sta_flags, u8 *bssid, + struct ieee80211_csa_ie *csa_ie); /* Suspend/resume and hw reconfiguration */ int ieee80211_reconfig(struct ieee80211_local *local); @@ -1501,7 +1629,7 @@ static inline int __ieee80211_resume(struct ieee80211_hw *hw) } /* utility functions/constants */ -extern void *mac80211_wiphy_privid; /* for wiphy privid */ +extern const void *const mac80211_wiphy_privid; /* for wiphy privid */ u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len, enum nl80211_iftype type); int ieee80211_frame_duration(enum ieee80211_band band, size_t len, @@ -1588,14 +1716,8 @@ void ieee80211_stop_queue_by_reason(struct ieee80211_hw *hw, int queue, void ieee80211_propagate_queue_wake(struct ieee80211_local *local, int queue); void ieee80211_add_pending_skb(struct ieee80211_local *local, struct sk_buff *skb); -void ieee80211_add_pending_skbs_fn(struct ieee80211_local *local, - struct sk_buff_head *skbs, - void (*fn)(void *data), void *data); -static inline void ieee80211_add_pending_skbs(struct ieee80211_local *local, - struct sk_buff_head *skbs) -{ - ieee80211_add_pending_skbs_fn(local, skbs, NULL, NULL); -} +void ieee80211_add_pending_skbs(struct ieee80211_local *local, + struct sk_buff_head *skbs); void ieee80211_flush_queues(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata); @@ -1626,9 +1748,12 @@ void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst, u32 ieee80211_sta_get_rates(struct ieee80211_sub_if_data *sdata, struct ieee802_11_elems *elems, enum ieee80211_band band, u32 *basic_rates); -int __ieee80211_request_smps(struct ieee80211_sub_if_data *sdata, - enum ieee80211_smps_mode smps_mode); +int __ieee80211_request_smps_mgd(struct ieee80211_sub_if_data *sdata, + enum ieee80211_smps_mode smps_mode); +int __ieee80211_request_smps_ap(struct ieee80211_sub_if_data *sdata, + enum ieee80211_smps_mode smps_mode); void ieee80211_recalc_smps(struct ieee80211_sub_if_data *sdata); +void ieee80211_recalc_min_chandef(struct ieee80211_sub_if_data *sdata); size_t ieee80211_ie_split(const u8 *ies, size_t ielen, const u8 *ids, int n_ids, size_t offset); @@ -1654,34 +1779,73 @@ int ieee80211_add_ext_srates_ie(struct ieee80211_sub_if_data *sdata, void ieee80211_ht_oper_to_chandef(struct ieee80211_channel *control_chan, const struct ieee80211_ht_operation *ht_oper, struct cfg80211_chan_def *chandef); +u32 ieee80211_chandef_downgrade(struct cfg80211_chan_def *c); int __must_check ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata, const struct cfg80211_chan_def *chandef, enum ieee80211_chanctx_mode mode); int __must_check +ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata, + const struct cfg80211_chan_def *chandef, + enum ieee80211_chanctx_mode mode, + bool radar_required); +int __must_check +ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata, + u32 *changed); +int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata); + +int __must_check ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata, const struct cfg80211_chan_def *chandef, u32 *changed); /* NOTE: only use ieee80211_vif_change_channel() for channel switch */ int __must_check ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata, - const struct cfg80211_chan_def *chandef, u32 *changed); void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata); void ieee80211_vif_vlan_copy_chanctx(struct ieee80211_sub_if_data *sdata); void ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata, bool clear); +int ieee80211_chanctx_refcount(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx); void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local, struct ieee80211_chanctx *chanctx); -void ieee80211_recalc_radar_chanctx(struct ieee80211_local *local, - struct ieee80211_chanctx *chanctx); +void ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx); void ieee80211_dfs_cac_timer(unsigned long data); void ieee80211_dfs_cac_timer_work(struct work_struct *work); void ieee80211_dfs_cac_cancel(struct ieee80211_local *local); void ieee80211_dfs_radar_detected_work(struct work_struct *work); +int ieee80211_send_action_csa(struct ieee80211_sub_if_data *sdata, + struct cfg80211_csa_settings *csa_settings); + +bool ieee80211_cs_valid(const struct ieee80211_cipher_scheme *cs); +bool ieee80211_cs_list_valid(const struct ieee80211_cipher_scheme *cs, int n); +const struct ieee80211_cipher_scheme * +ieee80211_cs_get(struct ieee80211_local *local, u32 cipher, + enum nl80211_iftype iftype); +int ieee80211_cs_headroom(struct ieee80211_local *local, + struct cfg80211_crypto_settings *crypto, + enum nl80211_iftype iftype); +void ieee80211_recalc_dtim(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata); +int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, + const struct cfg80211_chan_def *chandef, + enum ieee80211_chanctx_mode chanmode, + u8 radar_detect); +int ieee80211_max_num_channels(struct ieee80211_local *local); + +/* TDLS */ +int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, u32 peer_capability, + const u8 *extra_ies, size_t extra_ies_len); +int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, enum nl80211_tdls_operation oper); + #ifdef CONFIG_MAC80211_NOINLINE #define debug_noinline noinline diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index fcecd633514..388b863e821 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -101,9 +101,8 @@ static u32 __ieee80211_idle_on(struct ieee80211_local *local) static u32 __ieee80211_recalc_idle(struct ieee80211_local *local, bool force_active) { - bool working = false, scanning, active; + bool working, scanning, active; unsigned int led_trig_start = 0, led_trig_stop = 0; - struct ieee80211_roc_work *roc; lockdep_assert_held(&local->mtx); @@ -111,12 +110,8 @@ static u32 __ieee80211_recalc_idle(struct ieee80211_local *local, !list_empty(&local->chanctx_list) || local->monitors; - if (!local->ops->remain_on_channel) { - list_for_each_entry(roc, &local->roc_list, list) { - working = true; - break; - } - } + working = !local->ops->remain_on_channel && + !list_empty(&local->roc_list); scanning = test_bit(SCAN_SW_SCANNING, &local->scanning) || test_bit(SCAN_ONCHANNEL_SCANNING, &local->scanning); @@ -255,6 +250,7 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata, { struct ieee80211_local *local = sdata->local; struct ieee80211_sub_if_data *nsdata; + int ret; ASSERT_RTNL(); @@ -305,7 +301,10 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata, } } - return 0; + mutex_lock(&local->chanctx_mtx); + ret = ieee80211_check_combinations(sdata, NULL, 0, 0); + mutex_unlock(&local->chanctx_mtx); + return ret; } static int ieee80211_check_queues(struct ieee80211_sub_if_data *sdata, @@ -400,6 +399,9 @@ int ieee80211_add_virtual_monitor(struct ieee80211_local *local) sdata->vif.type = NL80211_IFTYPE_MONITOR; snprintf(sdata->name, IFNAMSIZ, "%s-monitor", wiphy_name(local->hw.wiphy)); + sdata->wdev.iftype = NL80211_IFTYPE_MONITOR; + + sdata->encrypt_headroom = IEEE80211_ENCRYPT_HEADROOM; ieee80211_set_default_queues(sdata); @@ -416,18 +418,24 @@ int ieee80211_add_virtual_monitor(struct ieee80211_local *local) return ret; } + mutex_lock(&local->iflist_mtx); + rcu_assign_pointer(local->monitor_sdata, sdata); + mutex_unlock(&local->iflist_mtx); + + mutex_lock(&local->mtx); ret = ieee80211_vif_use_channel(sdata, &local->monitor_chandef, IEEE80211_CHANCTX_EXCLUSIVE); + mutex_unlock(&local->mtx); if (ret) { + mutex_lock(&local->iflist_mtx); + RCU_INIT_POINTER(local->monitor_sdata, NULL); + mutex_unlock(&local->iflist_mtx); + synchronize_net(); drv_remove_interface(local, sdata); kfree(sdata); return ret; } - mutex_lock(&local->iflist_mtx); - rcu_assign_pointer(local->monitor_sdata, sdata); - mutex_unlock(&local->iflist_mtx); - return 0; } @@ -449,12 +457,14 @@ void ieee80211_del_virtual_monitor(struct ieee80211_local *local) return; } - rcu_assign_pointer(local->monitor_sdata, NULL); + RCU_INIT_POINTER(local->monitor_sdata, NULL); mutex_unlock(&local->iflist_mtx); synchronize_net(); + mutex_lock(&local->mtx); ieee80211_vif_release_channel(sdata); + mutex_unlock(&local->mtx); drv_remove_interface(local, sdata); @@ -487,7 +497,9 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up) if (!sdata->bss) return -ENOLINK; + mutex_lock(&local->mtx); list_add(&sdata->u.vlan.list, &sdata->bss->vlans); + mutex_unlock(&local->mtx); master = container_of(sdata->bss, struct ieee80211_sub_if_data, u.ap); @@ -717,8 +729,11 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up) drv_stop(local); err_del_bss: sdata->bss = NULL; - if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) + if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) { + mutex_lock(&local->mtx); list_del(&sdata->u.vlan.list); + mutex_unlock(&local->mtx); + } /* might already be clear but that doesn't matter */ clear_bit(SDATA_STATE_RUNNING, &sdata->state); return res; @@ -749,6 +764,7 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, u32 hw_reconf_flags = 0; int i, flushed; struct ps_data *ps; + struct cfg80211_chan_def chandef; clear_bit(SDATA_STATE_RUNNING, &sdata->state); @@ -763,8 +779,19 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, ieee80211_roc_purge(local, sdata); - if (sdata->vif.type == NL80211_IFTYPE_STATION) + switch (sdata->vif.type) { + case NL80211_IFTYPE_STATION: ieee80211_mgd_stop(sdata); + break; + case NL80211_IFTYPE_ADHOC: + ieee80211_ibss_stop(sdata); + break; + case NL80211_IFTYPE_AP: + cancel_work_sync(&sdata->u.ap.request_smps_work); + break; + default: + break; + } /* * Remove all stations associated with this interface. @@ -779,10 +806,8 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, * This is relevant only in WDS mode, in all other modes we've * already removed all stations when disconnecting or similar, * so warn otherwise. - * - * We call sta_info_flush_cleanup() later, to combine RCU waits. */ - flushed = sta_info_flush_defer(sdata); + flushed = sta_info_flush(sdata); WARN_ON_ONCE((sdata->vif.type != NL80211_IFTYPE_WDS && flushed > 0) || (sdata->vif.type == NL80211_IFTYPE_WDS && flushed != 1)); @@ -813,17 +838,28 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, cancel_work_sync(&local->dynamic_ps_enable_work); cancel_work_sync(&sdata->recalc_smps); + sdata_lock(sdata); + mutex_lock(&local->mtx); sdata->vif.csa_active = false; + if (!ieee80211_csa_needs_block_tx(local)) + ieee80211_wake_queues_by_reason(&local->hw, + IEEE80211_MAX_QUEUE_MAP, + IEEE80211_QUEUE_STOP_REASON_CSA); + mutex_unlock(&local->mtx); + sdata_unlock(sdata); + cancel_work_sync(&sdata->csa_finalize_work); cancel_delayed_work_sync(&sdata->dfs_cac_timer_work); if (sdata->wdev.cac_started) { + chandef = sdata->vif.bss_conf.chandef; WARN_ON(local->suspended); - mutex_lock(&local->iflist_mtx); + mutex_lock(&local->mtx); ieee80211_vif_release_channel(sdata); - mutex_unlock(&local->iflist_mtx); - cfg80211_cac_event(sdata->dev, NL80211_RADAR_CAC_ABORTED, + mutex_unlock(&local->mtx); + cfg80211_cac_event(sdata->dev, &chandef, + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL); } @@ -856,8 +892,10 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, switch (sdata->vif.type) { case NL80211_IFTYPE_AP_VLAN: + mutex_lock(&local->mtx); list_del(&sdata->u.vlan.list); - rcu_assign_pointer(sdata->vif.chanctx_conf, NULL); + mutex_unlock(&local->mtx); + RCU_INIT_POINTER(sdata->vif.chanctx_conf, NULL); /* no need to tell driver */ break; case NL80211_IFTYPE_MONITOR: @@ -876,29 +914,21 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, break; case NL80211_IFTYPE_P2P_DEVICE: /* relies on synchronize_rcu() below */ - rcu_assign_pointer(local->p2p_sdata, NULL); + RCU_INIT_POINTER(local->p2p_sdata, NULL); /* fall through */ default: cancel_work_sync(&sdata->work); /* * When we get here, the interface is marked down. + * Free the remaining keys, if there are any + * (shouldn't be, except maybe in WDS mode?) * - * sta_info_flush_cleanup() requires rcu_barrier() - * first to wait for the station call_rcu() calls - * to complete, and we also need synchronize_rcu() - * to wait for the RX path in case it is using the - * interface and enqueuing frames at this very time on + * Force the key freeing to always synchronize_net() + * to wait for the RX path in case it is using this + * interface enqueuing frames * at this very time on * another CPU. */ - synchronize_rcu(); - rcu_barrier(); - sta_info_flush_cleanup(sdata); - - /* - * Free all remaining keys, there shouldn't be any, - * except maybe in WDS mode? - */ - ieee80211_free_keys(sdata); + ieee80211_free_keys(sdata, true); /* fall through */ case NL80211_IFTYPE_AP: @@ -1009,17 +1039,6 @@ static void ieee80211_set_multicast_list(struct net_device *dev) atomic_dec(&local->iff_promiscs); sdata->flags ^= IEEE80211_SDATA_PROMISC; } - - /* - * TODO: If somebody needs this on AP interfaces, - * it can be enabled easily but multicast - * addresses from VLANs need to be synced. - */ - if (sdata->vif.type != NL80211_IFTYPE_MONITOR && - sdata->vif.type != NL80211_IFTYPE_AP_VLAN && - sdata->vif.type != NL80211_IFTYPE_AP) - drv_set_multicast_list(local, sdata, &dev->mc); - spin_lock_bh(&local->filter_lock); __hw_addr_sync(&local->mc_list, &dev->mc, dev->addr_len); spin_unlock_bh(&local->filter_lock); @@ -1032,11 +1051,10 @@ static void ieee80211_set_multicast_list(struct net_device *dev) */ static void ieee80211_teardown_sdata(struct ieee80211_sub_if_data *sdata) { - int flushed; int i; /* free extra data */ - ieee80211_free_keys(sdata); + ieee80211_free_keys(sdata, false); ieee80211_debugfs_remove_netdev(sdata); @@ -1046,9 +1064,6 @@ static void ieee80211_teardown_sdata(struct ieee80211_sub_if_data *sdata) if (ieee80211_vif_is_mesh(&sdata->vif)) mesh_rmc_free(sdata); - - flushed = sta_info_flush(sdata); - WARN_ON(flushed); } static void ieee80211_uninit(struct net_device *dev) @@ -1057,7 +1072,9 @@ static void ieee80211_uninit(struct net_device *dev) } static u16 ieee80211_netdev_select_queue(struct net_device *dev, - struct sk_buff *skb) + struct sk_buff *skb, + void *accel_priv, + select_queue_fallback_t fallback) { return ieee80211_select_queue(IEEE80211_DEV_TO_SUB_IF(dev), skb); } @@ -1074,7 +1091,9 @@ static const struct net_device_ops ieee80211_dataif_ops = { }; static u16 ieee80211_monitor_select_queue(struct net_device *dev, - struct sk_buff *skb) + struct sk_buff *skb, + void *accel_priv, + select_queue_fallback_t fallback) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; @@ -1266,6 +1285,8 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata, sdata->control_port_protocol = cpu_to_be16(ETH_P_PAE); sdata->control_port_no_encrypt = false; + sdata->encrypt_headroom = IEEE80211_ENCRYPT_HEADROOM; + sdata->vif.bss_conf.idle = true; sdata->noack_map = 0; @@ -1279,6 +1300,8 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata, INIT_WORK(&sdata->work, ieee80211_iface_work); INIT_WORK(&sdata->recalc_smps, ieee80211_recalc_smps_work); INIT_WORK(&sdata->csa_finalize_work, ieee80211_csa_finalize_work); + INIT_LIST_HEAD(&sdata->assigned_chanctx_list); + INIT_LIST_HEAD(&sdata->reserved_chanctx_list); switch (type) { case NL80211_IFTYPE_P2P_GO: @@ -1289,7 +1312,10 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata, case NL80211_IFTYPE_AP: skb_queue_head_init(&sdata->u.ap.ps.bc_buf); INIT_LIST_HEAD(&sdata->u.ap.vlans); + INIT_WORK(&sdata->u.ap.request_smps_work, + ieee80211_request_smps_ap_work); sdata->vif.bss_conf.bssid = sdata->vif.addr; + sdata->u.ap.req_smps = IEEE80211_SMPS_OFF; break; case NL80211_IFTYPE_P2P_CLIENT: type = NL80211_IFTYPE_STATION; @@ -1318,7 +1344,6 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata, sdata->vif.bss_conf.bssid = NULL; break; case NL80211_IFTYPE_AP_VLAN: - break; case NL80211_IFTYPE_P2P_DEVICE: sdata->vif.bss_conf.bssid = sdata->vif.addr; break; @@ -1489,8 +1514,8 @@ static void ieee80211_assign_perm_addr(struct ieee80211_local *local, bool used = false; list_for_each_entry(sdata, &local->interfaces, list) { - if (memcmp(local->hw.wiphy->addresses[i].addr, - sdata->vif.addr, ETH_ALEN) == 0) { + if (ether_addr_equal(local->hw.wiphy->addresses[i].addr, + sdata->vif.addr)) { used = true; break; } @@ -1550,8 +1575,7 @@ static void ieee80211_assign_perm_addr(struct ieee80211_local *local, val += inc; list_for_each_entry(sdata, &local->interfaces, list) { - if (memcmp(tmp_addr, sdata->vif.addr, - ETH_ALEN) == 0) { + if (ether_addr_equal(tmp_addr, sdata->vif.addr)) { used = true; break; } @@ -1571,15 +1595,6 @@ static void ieee80211_assign_perm_addr(struct ieee80211_local *local, mutex_unlock(&local->iflist_mtx); } -static void ieee80211_cleanup_sdata_stas_wk(struct work_struct *wk) -{ - struct ieee80211_sub_if_data *sdata; - - sdata = container_of(wk, struct ieee80211_sub_if_data, cleanup_stations_wk); - - ieee80211_cleanup_sdata_stas(sdata); -} - int ieee80211_if_add(struct ieee80211_local *local, const char *name, struct wireless_dev **new_wdev, enum nl80211_iftype type, struct vif_params *params) @@ -1652,9 +1667,6 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name, INIT_LIST_HEAD(&sdata->key_list); - spin_lock_init(&sdata->cleanup_stations_lock); - INIT_LIST_HEAD(&sdata->cleanup_stations); - INIT_WORK(&sdata->cleanup_stations_wk, ieee80211_cleanup_sdata_stas_wk); INIT_DELAYED_WORK(&sdata->dfs_cac_timer_work, ieee80211_dfs_cac_timer_work); INIT_DELAYED_WORK(&sdata->dec_tailroom_needed_wk, @@ -1679,6 +1691,8 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name, sdata->ap_power_level = IEEE80211_UNSET_POWER_LEVEL; sdata->user_power_level = local->user_power_level; + sdata->encrypt_headroom = IEEE80211_ENCRYPT_HEADROOM; + /* setup type-dependent data */ ieee80211_setup_sdata(sdata, type); @@ -1766,7 +1780,6 @@ void ieee80211_remove_interfaces(struct ieee80211_local *local) } mutex_unlock(&local->iflist_mtx); unregister_netdevice_many(&unreg_list); - list_del(&unreg_list); list_for_each_entry_safe(sdata, tmp, &wdev_list, list) { list_del(&sdata->list); @@ -1782,20 +1795,19 @@ static int netdev_notify(struct notifier_block *nb, struct ieee80211_sub_if_data *sdata; if (state != NETDEV_CHANGENAME) - return 0; + return NOTIFY_DONE; if (!dev->ieee80211_ptr || !dev->ieee80211_ptr->wiphy) - return 0; + return NOTIFY_DONE; if (dev->ieee80211_ptr->wiphy->privid != mac80211_wiphy_privid) - return 0; + return NOTIFY_DONE; sdata = IEEE80211_DEV_TO_SUB_IF(dev); - memcpy(sdata->name, dev->name, IFNAMSIZ); - ieee80211_debugfs_rename_netdev(sdata); - return 0; + + return NOTIFY_OK; } static struct notifier_block mac80211_netdev_notifier = { diff --git a/net/mac80211/key.c b/net/mac80211/key.c index 620677e897b..16d97f044a2 100644 --- a/net/mac80211/key.c +++ b/net/mac80211/key.c @@ -260,25 +260,29 @@ static void ieee80211_key_replace(struct ieee80211_sub_if_data *sdata, int idx; bool defunikey, defmultikey, defmgmtkey; + /* caller must provide at least one old/new */ + if (WARN_ON(!new && !old)) + return; + if (new) list_add_tail(&new->list, &sdata->key_list); - if (sta && pairwise) { - rcu_assign_pointer(sta->ptk, new); - } else if (sta) { - if (old) - idx = old->conf.keyidx; - else - idx = new->conf.keyidx; - rcu_assign_pointer(sta->gtk[idx], new); - } else { - WARN_ON(new && old && new->conf.keyidx != old->conf.keyidx); + WARN_ON(new && old && new->conf.keyidx != old->conf.keyidx); - if (old) - idx = old->conf.keyidx; - else - idx = new->conf.keyidx; + if (old) + idx = old->conf.keyidx; + else + idx = new->conf.keyidx; + if (sta) { + if (pairwise) { + rcu_assign_pointer(sta->ptk[idx], new); + sta->ptk_idx = idx; + } else { + rcu_assign_pointer(sta->gtk[idx], new); + sta->gtk_idx = idx; + } + } else { defunikey = old && old == key_mtx_dereference(sdata->local, sdata->default_unicast_key); @@ -312,14 +316,17 @@ static void ieee80211_key_replace(struct ieee80211_sub_if_data *sdata, list_del(&old->list); } -struct ieee80211_key *ieee80211_key_alloc(u32 cipher, int idx, size_t key_len, - const u8 *key_data, - size_t seq_len, const u8 *seq) +struct ieee80211_key * +ieee80211_key_alloc(u32 cipher, int idx, size_t key_len, + const u8 *key_data, + size_t seq_len, const u8 *seq, + const struct ieee80211_cipher_scheme *cs) { struct ieee80211_key *key; int i, j, err; - BUG_ON(idx < 0 || idx >= NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS); + if (WARN_ON(idx < 0 || idx >= NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS)) + return ERR_PTR(-EINVAL); key = kzalloc(sizeof(struct ieee80211_key) + key_len, GFP_KERNEL); if (!key) @@ -393,6 +400,18 @@ struct ieee80211_key *ieee80211_key_alloc(u32 cipher, int idx, size_t key_len, return ERR_PTR(err); } break; + default: + if (cs) { + size_t len = (seq_len > MAX_PN_LEN) ? + MAX_PN_LEN : seq_len; + + key->conf.iv_len = cs->hdr_len; + key->conf.icv_len = cs->mic_len; + for (i = 0; i < IEEE80211_NUM_TIDS + 1; i++) + for (j = 0; j < len; j++) + key->u.gen.rx_pn[i][j] = + seq[len - j - 1]; + } } memcpy(key->conf.key, key_data, key_len); INIT_LIST_HEAD(&key->list); @@ -463,8 +482,8 @@ int ieee80211_key_link(struct ieee80211_key *key, int idx, ret; bool pairwise; - BUG_ON(!sdata); - BUG_ON(!key); + if (WARN_ON(!sdata || !key)) + return -EINVAL; pairwise = key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE; idx = key->conf.keyidx; @@ -475,7 +494,7 @@ int ieee80211_key_link(struct ieee80211_key *key, mutex_lock(&sdata->local->key_mtx); if (sta && pairwise) - old_key = key_mtx_dereference(sdata->local, sta->ptk); + old_key = key_mtx_dereference(sdata->local, sta->ptk[idx]); else if (sta) old_key = key_mtx_dereference(sdata->local, sta->gtk[idx]); else @@ -571,14 +590,10 @@ void ieee80211_iter_keys(struct ieee80211_hw *hw, } EXPORT_SYMBOL(ieee80211_iter_keys); -void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata) +static void ieee80211_free_keys_iface(struct ieee80211_sub_if_data *sdata, + struct list_head *keys) { struct ieee80211_key *key, *tmp; - LIST_HEAD(keys); - - cancel_delayed_work_sync(&sdata->dec_tailroom_needed_wk); - - mutex_lock(&sdata->local->key_mtx); sdata->crypto_tx_tailroom_needed_cnt -= sdata->crypto_tx_tailroom_pending_dec; @@ -590,28 +605,51 @@ void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata) ieee80211_key_replace(key->sdata, key->sta, key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE, key, NULL); - list_add_tail(&key->list, &keys); + list_add_tail(&key->list, keys); } ieee80211_debugfs_key_update_default(sdata); +} - if (!list_empty(&keys)) { - synchronize_net(); - list_for_each_entry_safe(key, tmp, &keys, list) - __ieee80211_key_destroy(key, false); +void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata, + bool force_synchronize) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_sub_if_data *vlan; + struct ieee80211_key *key, *tmp; + LIST_HEAD(keys); + + cancel_delayed_work_sync(&sdata->dec_tailroom_needed_wk); + + mutex_lock(&local->key_mtx); + + ieee80211_free_keys_iface(sdata, &keys); + + if (sdata->vif.type == NL80211_IFTYPE_AP) { + list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) + ieee80211_free_keys_iface(vlan, &keys); } + if (!list_empty(&keys) || force_synchronize) + synchronize_net(); + list_for_each_entry_safe(key, tmp, &keys, list) + __ieee80211_key_destroy(key, false); + WARN_ON_ONCE(sdata->crypto_tx_tailroom_needed_cnt || sdata->crypto_tx_tailroom_pending_dec); + if (sdata->vif.type == NL80211_IFTYPE_AP) { + list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) + WARN_ON_ONCE(vlan->crypto_tx_tailroom_needed_cnt || + vlan->crypto_tx_tailroom_pending_dec); + } - mutex_unlock(&sdata->local->key_mtx); + mutex_unlock(&local->key_mtx); } void ieee80211_free_sta_keys(struct ieee80211_local *local, struct sta_info *sta) { - struct ieee80211_key *key, *tmp; - LIST_HEAD(keys); + struct ieee80211_key *key; int i; mutex_lock(&local->key_mtx); @@ -622,25 +660,18 @@ void ieee80211_free_sta_keys(struct ieee80211_local *local, ieee80211_key_replace(key->sdata, key->sta, key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE, key, NULL); - list_add(&key->list, &keys); + __ieee80211_key_destroy(key, true); } - key = key_mtx_dereference(local, sta->ptk); - if (key) { + for (i = 0; i < NUM_DEFAULT_KEYS; i++) { + key = key_mtx_dereference(local, sta->ptk[i]); + if (!key) + continue; ieee80211_key_replace(key->sdata, key->sta, key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE, key, NULL); - list_add(&key->list, &keys); - } - - /* - * NB: the station code relies on this being - * done even if there aren't any keys - */ - synchronize_net(); - - list_for_each_entry_safe(key, tmp, &keys, list) __ieee80211_key_destroy(key, true); + } mutex_unlock(&local->key_mtx); } @@ -877,9 +908,9 @@ ieee80211_gtk_rekey_add(struct ieee80211_vif *vif, key = ieee80211_key_alloc(keyconf->cipher, keyconf->keyidx, keyconf->keylen, keyconf->key, - 0, NULL); + 0, NULL, NULL); if (IS_ERR(key)) - return ERR_PTR(PTR_ERR(key)); + return ERR_CAST(key); if (sdata->u.mgd.mfp != IEEE80211_MFP_DISABLED) key->conf.flags |= IEEE80211_KEY_FLAG_RX_MGMT; diff --git a/net/mac80211/key.h b/net/mac80211/key.h index 036d57e76a5..19db68663d7 100644 --- a/net/mac80211/key.h +++ b/net/mac80211/key.h @@ -18,6 +18,7 @@ #define NUM_DEFAULT_KEYS 4 #define NUM_DEFAULT_MGMT_KEYS 2 +#define MAX_PN_LEN 16 struct ieee80211_local; struct ieee80211_sub_if_data; @@ -83,7 +84,7 @@ struct ieee80211_key { * Management frames. */ u8 rx_pn[IEEE80211_NUM_TIDS + 1][IEEE80211_CCMP_PN_LEN]; - struct crypto_cipher *tfm; + struct crypto_aead *tfm; u32 replays; /* dot11RSNAStatsCCMPReplays */ } ccmp; struct { @@ -93,6 +94,10 @@ struct ieee80211_key { u32 replays; /* dot11RSNAStatsCMACReplays */ u32 icverrors; /* dot11RSNAStatsCMACICVErrors */ } aes_cmac; + struct { + /* generic cipher scheme */ + u8 rx_pn[IEEE80211_NUM_TIDS + 1][MAX_PN_LEN]; + } gen; } u; /* number of times this key has been used */ @@ -113,9 +118,11 @@ struct ieee80211_key { struct ieee80211_key_conf conf; }; -struct ieee80211_key *ieee80211_key_alloc(u32 cipher, int idx, size_t key_len, - const u8 *key_data, - size_t seq_len, const u8 *seq); +struct ieee80211_key * +ieee80211_key_alloc(u32 cipher, int idx, size_t key_len, + const u8 *key_data, + size_t seq_len, const u8 *seq, + const struct ieee80211_cipher_scheme *cs); /* * Insert a key into data structures (sdata, sta if necessary) * to make it used, free old key. On failure, also free the new key. @@ -129,7 +136,8 @@ void ieee80211_set_default_key(struct ieee80211_sub_if_data *sdata, int idx, bool uni, bool multi); void ieee80211_set_default_mgmt_key(struct ieee80211_sub_if_data *sdata, int idx); -void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata); +void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata, + bool force_synchronize); void ieee80211_free_sta_keys(struct ieee80211_local *local, struct sta_info *sta); void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata); diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 21d5d44444d..d17c26d6e36 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -148,6 +148,8 @@ static u32 ieee80211_hw_conf_chan(struct ieee80211_local *local) list_for_each_entry_rcu(sdata, &local->interfaces, list) { if (!rcu_access_pointer(sdata->vif.chanctx_conf)) continue; + if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) + continue; power = min(power, sdata->vif.bss_conf.txpower); } rcu_read_unlock(); @@ -199,7 +201,7 @@ void ieee80211_bss_info_change_notify(struct ieee80211_sub_if_data *sdata, { struct ieee80211_local *local = sdata->local; - if (!changed) + if (!changed || sdata->vif.type == NL80211_IFTYPE_AP_VLAN) return; drv_bss_info_changed(local, sdata, &sdata->vif.bss_conf, changed); @@ -250,12 +252,8 @@ static void ieee80211_restart_work(struct work_struct *work) /* wait for scan work complete */ flush_workqueue(local->workqueue); - mutex_lock(&local->mtx); - WARN(test_bit(SCAN_HW_SCANNING, &local->scanning) || - rcu_dereference_protected(local->sched_scan_sdata, - lockdep_is_held(&local->mtx)), - "%s called with hardware scan in progress\n", __func__); - mutex_unlock(&local->mtx); + WARN(test_bit(SCAN_HW_SCANNING, &local->scanning), + "%s called with hardware scan in progress\n", __func__); rtnl_lock(); ieee80211_scan_cancel(local); @@ -342,7 +340,7 @@ static int ieee80211_ifa_changed(struct notifier_block *nb, sdata_unlock(sdata); - return NOTIFY_DONE; + return NOTIFY_OK; } #endif @@ -373,7 +371,7 @@ static int ieee80211_ifa6_changed(struct notifier_block *nb, drv_ipv6_addr_change(local, sdata, idev); - return NOTIFY_DONE; + return NOTIFY_OK; } #endif @@ -448,7 +446,9 @@ static const struct ieee80211_ht_cap mac80211_ht_capa_mod_mask = { .cap_info = cpu_to_le16(IEEE80211_HT_CAP_SUP_WIDTH_20_40 | IEEE80211_HT_CAP_MAX_AMSDU | IEEE80211_HT_CAP_SGI_20 | - IEEE80211_HT_CAP_SGI_40), + IEEE80211_HT_CAP_SGI_40 | + IEEE80211_HT_CAP_LDPC_CODING | + IEEE80211_HT_CAP_40MHZ_INTOLERANT), .mcs = { .rx_mask = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }, @@ -651,15 +651,14 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len, } EXPORT_SYMBOL(ieee80211_alloc_hw); -int ieee80211_register_hw(struct ieee80211_hw *hw) +static int ieee80211_init_cipher_suites(struct ieee80211_local *local) { - struct ieee80211_local *local = hw_to_local(hw); - int result, i; - enum ieee80211_band band; - int channels, max_bitrates; - bool supp_ht, supp_vht; - netdev_features_t feature_whitelist; - struct cfg80211_chan_def dflt_chandef = {}; + bool have_wep = !(IS_ERR(local->wep_tx_tfm) || + IS_ERR(local->wep_rx_tfm)); + bool have_mfp = local->hw.flags & IEEE80211_HW_MFP_CAPABLE; + const struct ieee80211_cipher_scheme *cs = local->hw.cipher_schemes; + int n_suites = 0, r = 0, w = 0; + u32 *suites; static const u32 cipher_suites[] = { /* keep WEP first, it may be removed below */ WLAN_CIPHER_SUITE_WEP40, @@ -671,6 +670,93 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) WLAN_CIPHER_SUITE_AES_CMAC }; + /* Driver specifies the ciphers, we have nothing to do... */ + if (local->hw.wiphy->cipher_suites && have_wep) + return 0; + + /* Set up cipher suites if driver relies on mac80211 cipher defs */ + if (!local->hw.wiphy->cipher_suites && !cs) { + local->hw.wiphy->cipher_suites = cipher_suites; + local->hw.wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites); + + if (!have_mfp) + local->hw.wiphy->n_cipher_suites--; + + if (!have_wep) { + local->hw.wiphy->cipher_suites += 2; + local->hw.wiphy->n_cipher_suites -= 2; + } + + return 0; + } + + if (!local->hw.wiphy->cipher_suites) { + /* + * Driver specifies cipher schemes only + * We start counting ciphers defined by schemes, TKIP and CCMP + */ + n_suites = local->hw.n_cipher_schemes + 2; + + /* check if we have WEP40 and WEP104 */ + if (have_wep) + n_suites += 2; + + /* check if we have AES_CMAC */ + if (have_mfp) + n_suites++; + + suites = kmalloc(sizeof(u32) * n_suites, GFP_KERNEL); + if (!suites) + return -ENOMEM; + + suites[w++] = WLAN_CIPHER_SUITE_CCMP; + suites[w++] = WLAN_CIPHER_SUITE_TKIP; + + if (have_wep) { + suites[w++] = WLAN_CIPHER_SUITE_WEP40; + suites[w++] = WLAN_CIPHER_SUITE_WEP104; + } + + if (have_mfp) + suites[w++] = WLAN_CIPHER_SUITE_AES_CMAC; + + for (r = 0; r < local->hw.n_cipher_schemes; r++) + suites[w++] = cs[r].cipher; + } else { + /* Driver provides cipher suites, but we need to exclude WEP */ + suites = kmemdup(local->hw.wiphy->cipher_suites, + sizeof(u32) * local->hw.wiphy->n_cipher_suites, + GFP_KERNEL); + if (!suites) + return -ENOMEM; + + for (r = 0; r < local->hw.wiphy->n_cipher_suites; r++) { + u32 suite = local->hw.wiphy->cipher_suites[r]; + + if (suite == WLAN_CIPHER_SUITE_WEP40 || + suite == WLAN_CIPHER_SUITE_WEP104) + continue; + suites[w++] = suite; + } + } + + local->hw.wiphy->cipher_suites = suites; + local->hw.wiphy->n_cipher_suites = w; + local->wiphy_ciphers_allocated = true; + + return 0; +} + +int ieee80211_register_hw(struct ieee80211_hw *hw) +{ + struct ieee80211_local *local = hw_to_local(hw); + int result, i; + enum ieee80211_band band; + int channels, max_bitrates; + bool supp_ht, supp_vht; + netdev_features_t feature_whitelist; + struct cfg80211_chan_def dflt_chandef = {}; + if (hw->flags & IEEE80211_HW_QUEUE_CONTROL && (local->hw.offchannel_tx_hw_queue == IEEE80211_INVAL_HW_QUEUE || local->hw.offchannel_tx_hw_queue >= local->hw.queues)) @@ -764,17 +850,6 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) /* TODO: consider VHT for RX chains, hopefully it's the same */ } - local->int_scan_req = kzalloc(sizeof(*local->int_scan_req) + - sizeof(void *) * channels, GFP_KERNEL); - if (!local->int_scan_req) - return -ENOMEM; - - for (band = 0; band < IEEE80211_NUM_BANDS; band++) { - if (!local->hw.wiphy->bands[band]) - continue; - local->int_scan_req->rates[band] = (u32) -1; - } - /* if low-level driver supports AP, we also support VLAN */ if (local->hw.wiphy->interface_modes & BIT(NL80211_IFTYPE_AP)) { hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP_VLAN); @@ -798,6 +873,17 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) return -EINVAL; } + local->int_scan_req = kzalloc(sizeof(*local->int_scan_req) + + sizeof(void *) * channels, GFP_KERNEL); + if (!local->int_scan_req) + return -ENOMEM; + + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + if (!local->hw.wiphy->bands[band]) + continue; + local->int_scan_req->rates[band] = (u32) -1; + } + #ifndef CONFIG_MAC80211_MESH /* mesh depends on Kconfig, but drivers should set it if they want */ local->hw.wiphy->interface_modes &= ~BIT(NL80211_IFTYPE_MESH_POINT); @@ -811,10 +897,15 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) /* mac80211 supports control port protocol changing */ local->hw.wiphy->flags |= WIPHY_FLAG_CONTROL_PORT_PROTOCOL; - if (local->hw.flags & IEEE80211_HW_SIGNAL_DBM) + if (local->hw.flags & IEEE80211_HW_SIGNAL_DBM) { local->hw.wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; - else if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC) + } else if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC) { local->hw.wiphy->signal_type = CFG80211_SIGNAL_TYPE_UNSPEC; + if (hw->max_signal <= 0) { + result = -EINVAL; + goto fail_wiphy_register; + } + } WARN((local->hw.flags & IEEE80211_HW_SUPPORTS_UAPSD) && (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK), @@ -851,43 +942,12 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) if (local->hw.wiphy->max_scan_ie_len) local->hw.wiphy->max_scan_ie_len -= local->scan_ies_len; - /* Set up cipher suites unless driver already did */ - if (!local->hw.wiphy->cipher_suites) { - local->hw.wiphy->cipher_suites = cipher_suites; - local->hw.wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites); - if (!(local->hw.flags & IEEE80211_HW_MFP_CAPABLE)) - local->hw.wiphy->n_cipher_suites--; - } - if (IS_ERR(local->wep_tx_tfm) || IS_ERR(local->wep_rx_tfm)) { - if (local->hw.wiphy->cipher_suites == cipher_suites) { - local->hw.wiphy->cipher_suites += 2; - local->hw.wiphy->n_cipher_suites -= 2; - } else { - u32 *suites; - int r, w = 0; - - /* Filter out WEP */ - - suites = kmemdup( - local->hw.wiphy->cipher_suites, - sizeof(u32) * local->hw.wiphy->n_cipher_suites, - GFP_KERNEL); - if (!suites) { - result = -ENOMEM; - goto fail_wiphy_register; - } - for (r = 0; r < local->hw.wiphy->n_cipher_suites; r++) { - u32 suite = local->hw.wiphy->cipher_suites[r]; - if (suite == WLAN_CIPHER_SUITE_WEP40 || - suite == WLAN_CIPHER_SUITE_WEP104) - continue; - suites[w++] = suite; - } - local->hw.wiphy->cipher_suites = suites; - local->hw.wiphy->n_cipher_suites = w; - local->wiphy_ciphers_allocated = true; - } - } + WARN_ON(!ieee80211_cs_list_valid(local->hw.cipher_schemes, + local->hw.n_cipher_schemes)); + + result = ieee80211_init_cipher_suites(local); + if (result < 0) + goto fail_wiphy_register; if (!local->ops->remain_on_channel) local->hw.wiphy->max_remain_on_channel_duration = 5000; @@ -896,6 +956,8 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) if (local->hw.wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS) local->hw.wiphy->flags |= WIPHY_FLAG_TDLS_EXTERNAL_SETUP; + local->hw.wiphy->max_num_csa_counters = IEEE80211_MAX_CSA_COUNTERS_NUM; + result = wiphy_register(local->hw.wiphy); if (result < 0) goto fail_wiphy_register; @@ -940,6 +1002,8 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) wiphy_debug(local->hw.wiphy, "Failed to initialize wep: %d\n", result); + local->hw.conf.flags = IEEE80211_CONF_IDLE; + ieee80211_led_init(local); rtnl_lock(); @@ -1018,6 +1082,18 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) } EXPORT_SYMBOL(ieee80211_register_hw); +void ieee80211_napi_add(struct ieee80211_hw *hw, struct napi_struct *napi, + struct net_device *napi_dev, + int (*poll)(struct napi_struct *, int), + int weight) +{ + struct ieee80211_local *local = hw_to_local(hw); + + netif_napi_add(napi_dev, napi, poll, weight); + local->napi = napi; +} +EXPORT_SYMBOL_GPL(ieee80211_napi_add); + void ieee80211_unregister_hw(struct ieee80211_hw *hw) { struct ieee80211_local *local = hw_to_local(hw); @@ -1047,6 +1123,7 @@ void ieee80211_unregister_hw(struct ieee80211_hw *hw) cancel_work_sync(&local->restart_work); cancel_work_sync(&local->reconfig_filter); + flush_work(&local->sched_scan_stopped_work); ieee80211_clear_tx_pending(local); rate_control_deinitialize(local); @@ -1087,6 +1164,8 @@ void ieee80211_free_hw(struct ieee80211_hw *hw) ieee80211_free_ack_frame, NULL); idr_destroy(&local->ack_status_frames); + kfree(rcu_access_pointer(local->tx_latency)); + wiphy_free(local->hw.wiphy); } EXPORT_SYMBOL(ieee80211_free_hw); diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index 707ac61d63e..6495a3f0428 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -12,6 +12,7 @@ #include <asm/unaligned.h> #include "ieee80211_i.h" #include "mesh.h" +#include "driver-ops.h" static int mesh_allocated; static struct kmem_cache *rm_cache; @@ -258,6 +259,9 @@ int mesh_add_meshconf_ie(struct ieee80211_sub_if_data *sdata, *pos++ = WLAN_EID_MESH_CONFIG; *pos++ = meshconf_len; + /* save a pointer for quick updates in pre-tbtt */ + ifmsh->meshconf_offset = pos - skb->data; + /* Active path selection protocol ID */ *pos++ = ifmsh->mesh_pp_id; /* Active path selection metric ID */ @@ -362,20 +366,15 @@ int mesh_add_rsn_ie(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb) return 0; /* find RSN IE */ - data = ifmsh->ie; - while (data < ifmsh->ie + ifmsh->ie_len) { - if (*data == WLAN_EID_RSN) { - len = data[1] + 2; - break; - } - data++; - } + data = cfg80211_find_ie(WLAN_EID_RSN, ifmsh->ie, ifmsh->ie_len); + if (!data) + return 0; - if (len) { - if (skb_tailroom(skb) < len) - return -ENOMEM; - memcpy(skb_put(skb, len), data, len); - } + len = data[1] + 2; + + if (skb_tailroom(skb) < len) + return -ENOMEM; + memcpy(skb_put(skb, len), data, len); return 0; } @@ -610,6 +609,7 @@ ieee80211_mesh_build_beacon(struct ieee80211_if_mesh *ifmsh) struct sk_buff *skb; struct ieee80211_mgmt *mgmt; struct ieee80211_chanctx_conf *chanctx_conf; + struct mesh_csa_settings *csa; enum ieee80211_band band; u8 *pos; struct ieee80211_sub_if_data *sdata; @@ -624,6 +624,10 @@ ieee80211_mesh_build_beacon(struct ieee80211_if_mesh *ifmsh) head_len = hdr_len + 2 + /* NULL SSID */ + /* Channel Switch Announcement */ + 2 + sizeof(struct ieee80211_channel_sw_ie) + + /* Mesh Channel Swith Parameters */ + 2 + sizeof(struct ieee80211_mesh_chansw_params_ie) + 2 + 8 + /* supported rates */ 2 + 3; /* DS params */ tail_len = 2 + (IEEE80211_MAX_SUPP_RATES - 8) + @@ -665,6 +669,35 @@ ieee80211_mesh_build_beacon(struct ieee80211_if_mesh *ifmsh) *pos++ = WLAN_EID_SSID; *pos++ = 0x0; + rcu_read_lock(); + csa = rcu_dereference(ifmsh->csa); + if (csa) { + pos = skb_put(skb, 13); + memset(pos, 0, 13); + *pos++ = WLAN_EID_CHANNEL_SWITCH; + *pos++ = 3; + *pos++ = 0x0; + *pos++ = ieee80211_frequency_to_channel( + csa->settings.chandef.chan->center_freq); + sdata->csa_counter_offset_beacon[0] = hdr_len + 6; + *pos++ = csa->settings.count; + *pos++ = WLAN_EID_CHAN_SWITCH_PARAM; + *pos++ = 6; + if (ifmsh->csa_role == IEEE80211_MESH_CSA_ROLE_INIT) { + *pos++ = ifmsh->mshcfg.dot11MeshTTL; + *pos |= WLAN_EID_CHAN_SWITCH_PARAM_INITIATOR; + } else { + *pos++ = ifmsh->chsw_ttl; + } + *pos++ |= csa->settings.block_tx ? + WLAN_EID_CHAN_SWITCH_PARAM_TX_RESTRICT : 0x00; + put_unaligned_le16(WLAN_REASON_MESH_CHAN, pos); + pos += 2; + put_unaligned_le16(ifmsh->pre_value, pos); + pos += 2; + } + rcu_read_unlock(); + if (ieee80211_add_srates_ie(sdata, skb, true, band) || mesh_add_ds_params_ie(sdata, skb)) goto out_free; @@ -688,6 +721,8 @@ ieee80211_mesh_build_beacon(struct ieee80211_if_mesh *ifmsh) bcn->tail_len = skb->len; memcpy(bcn->tail, skb->data, bcn->tail_len); + bcn->meshconf = (struct ieee80211_meshconf_ie *) + (bcn->tail + ifmsh->meshconf_offset); dev_kfree_skb(skb); rcu_assign_pointer(ifmsh->beacon, bcn); @@ -767,6 +802,7 @@ int ieee80211_start_mesh(struct ieee80211_sub_if_data *sdata) return -ENOMEM; } + ieee80211_recalc_dtim(local, sdata); ieee80211_bss_info_change_notify(sdata, changed); netif_carrier_on(sdata->dev); @@ -788,7 +824,7 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata) ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON_ENABLED); bcn = rcu_dereference_protected(ifmsh->beacon, lockdep_is_held(&sdata->wdev.mtx)); - rcu_assign_pointer(ifmsh->beacon, NULL); + RCU_INIT_POINTER(ifmsh->beacon, NULL); kfree_rcu(bcn, rcu_head); /* flush STAs and mpaths on this iface */ @@ -812,6 +848,97 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata) ieee80211_configure_filter(local); } +static bool +ieee80211_mesh_process_chnswitch(struct ieee80211_sub_if_data *sdata, + struct ieee802_11_elems *elems, bool beacon) +{ + struct cfg80211_csa_settings params; + struct ieee80211_csa_ie csa_ie; + struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + enum ieee80211_band band = ieee80211_get_sdata_band(sdata); + int err; + u32 sta_flags; + + sdata_assert_lock(sdata); + + sta_flags = IEEE80211_STA_DISABLE_VHT; + switch (sdata->vif.bss_conf.chandef.width) { + case NL80211_CHAN_WIDTH_20_NOHT: + sta_flags |= IEEE80211_STA_DISABLE_HT; + case NL80211_CHAN_WIDTH_20: + sta_flags |= IEEE80211_STA_DISABLE_40MHZ; + break; + default: + break; + } + + memset(¶ms, 0, sizeof(params)); + memset(&csa_ie, 0, sizeof(csa_ie)); + err = ieee80211_parse_ch_switch_ie(sdata, elems, beacon, band, + sta_flags, sdata->vif.addr, + &csa_ie); + if (err < 0) + return false; + if (err) + return false; + + params.chandef = csa_ie.chandef; + params.count = csa_ie.count; + + if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, ¶ms.chandef, + IEEE80211_CHAN_DISABLED)) { + sdata_info(sdata, + "mesh STA %pM switches to unsupported channel (%d MHz, width:%d, CF1/2: %d/%d MHz), aborting\n", + sdata->vif.addr, + params.chandef.chan->center_freq, + params.chandef.width, + params.chandef.center_freq1, + params.chandef.center_freq2); + return false; + } + + err = cfg80211_chandef_dfs_required(sdata->local->hw.wiphy, + ¶ms.chandef, + NL80211_IFTYPE_MESH_POINT); + if (err < 0) + return false; + if (err > 0) + /* TODO: DFS not (yet) supported */ + return false; + + params.radar_required = err; + + if (cfg80211_chandef_identical(¶ms.chandef, + &sdata->vif.bss_conf.chandef)) { + mcsa_dbg(sdata, + "received csa with an identical chandef, ignoring\n"); + return true; + } + + mcsa_dbg(sdata, + "received channel switch announcement to go to channel %d MHz\n", + params.chandef.chan->center_freq); + + params.block_tx = csa_ie.mode & WLAN_EID_CHAN_SWITCH_PARAM_TX_RESTRICT; + if (beacon) { + ifmsh->chsw_ttl = csa_ie.ttl - 1; + if (ifmsh->pre_value >= csa_ie.pre_value) + return false; + ifmsh->pre_value = csa_ie.pre_value; + } + + if (ifmsh->chsw_ttl >= ifmsh->mshcfg.dot11MeshTTL) + return false; + + ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_REPEATER; + + if (ieee80211_channel_switch(sdata->local->hw.wiphy, sdata->dev, + ¶ms) < 0) + return false; + + return true; +} + static void ieee80211_mesh_rx_probe_req(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, size_t len) @@ -918,6 +1045,139 @@ static void ieee80211_mesh_rx_bcn_presp(struct ieee80211_sub_if_data *sdata, if (ifmsh->sync_ops) ifmsh->sync_ops->rx_bcn_presp(sdata, stype, mgmt, &elems, rx_status); + + if (ifmsh->csa_role != IEEE80211_MESH_CSA_ROLE_INIT && + !sdata->vif.csa_active) + ieee80211_mesh_process_chnswitch(sdata, &elems, true); +} + +int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + struct mesh_csa_settings *tmp_csa_settings; + int ret = 0; + int changed = 0; + + /* Reset the TTL value and Initiator flag */ + ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE; + ifmsh->chsw_ttl = 0; + + /* Remove the CSA and MCSP elements from the beacon */ + tmp_csa_settings = rcu_dereference(ifmsh->csa); + RCU_INIT_POINTER(ifmsh->csa, NULL); + if (tmp_csa_settings) + kfree_rcu(tmp_csa_settings, rcu_head); + ret = ieee80211_mesh_rebuild_beacon(sdata); + if (ret) + return -EINVAL; + + changed |= BSS_CHANGED_BEACON; + + mcsa_dbg(sdata, "complete switching to center freq %d MHz", + sdata->vif.bss_conf.chandef.chan->center_freq); + return changed; +} + +int ieee80211_mesh_csa_beacon(struct ieee80211_sub_if_data *sdata, + struct cfg80211_csa_settings *csa_settings) +{ + struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + struct mesh_csa_settings *tmp_csa_settings; + int ret = 0; + + tmp_csa_settings = kmalloc(sizeof(*tmp_csa_settings), + GFP_ATOMIC); + if (!tmp_csa_settings) + return -ENOMEM; + + memcpy(&tmp_csa_settings->settings, csa_settings, + sizeof(struct cfg80211_csa_settings)); + + rcu_assign_pointer(ifmsh->csa, tmp_csa_settings); + + ret = ieee80211_mesh_rebuild_beacon(sdata); + if (ret) { + tmp_csa_settings = rcu_dereference(ifmsh->csa); + RCU_INIT_POINTER(ifmsh->csa, NULL); + kfree_rcu(tmp_csa_settings, rcu_head); + return ret; + } + + return BSS_CHANGED_BEACON; +} + +static int mesh_fwd_csa_frame(struct ieee80211_sub_if_data *sdata, + struct ieee80211_mgmt *mgmt, size_t len) +{ + struct ieee80211_mgmt *mgmt_fwd; + struct sk_buff *skb; + struct ieee80211_local *local = sdata->local; + u8 *pos = mgmt->u.action.u.chan_switch.variable; + size_t offset_ttl; + + skb = dev_alloc_skb(local->tx_headroom + len); + if (!skb) + return -ENOMEM; + skb_reserve(skb, local->tx_headroom); + mgmt_fwd = (struct ieee80211_mgmt *) skb_put(skb, len); + + /* offset_ttl is based on whether the secondary channel + * offset is available or not. Substract 1 from the mesh TTL + * and disable the initiator flag before forwarding. + */ + offset_ttl = (len < 42) ? 7 : 10; + *(pos + offset_ttl) -= 1; + *(pos + offset_ttl + 1) &= ~WLAN_EID_CHAN_SWITCH_PARAM_INITIATOR; + + memcpy(mgmt_fwd, mgmt, len); + eth_broadcast_addr(mgmt_fwd->da); + memcpy(mgmt_fwd->sa, sdata->vif.addr, ETH_ALEN); + memcpy(mgmt_fwd->bssid, sdata->vif.addr, ETH_ALEN); + + ieee80211_tx_skb(sdata, skb); + return 0; +} + +static void mesh_rx_csa_frame(struct ieee80211_sub_if_data *sdata, + struct ieee80211_mgmt *mgmt, size_t len) +{ + struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + struct ieee802_11_elems elems; + u16 pre_value; + bool fwd_csa = true; + size_t baselen; + u8 *pos; + + if (mgmt->u.action.u.measurement.action_code != + WLAN_ACTION_SPCT_CHL_SWITCH) + return; + + pos = mgmt->u.action.u.chan_switch.variable; + baselen = offsetof(struct ieee80211_mgmt, + u.action.u.chan_switch.variable); + ieee802_11_parse_elems(pos, len - baselen, false, &elems); + + ifmsh->chsw_ttl = elems.mesh_chansw_params_ie->mesh_ttl; + if (!--ifmsh->chsw_ttl) + fwd_csa = false; + + pre_value = le16_to_cpu(elems.mesh_chansw_params_ie->mesh_pre_value); + if (ifmsh->pre_value >= pre_value) + return; + + ifmsh->pre_value = pre_value; + + if (!sdata->vif.csa_active && + !ieee80211_mesh_process_chnswitch(sdata, &elems, false)) { + mcsa_dbg(sdata, "Failed to process CSA action frame"); + return; + } + + /* forward or re-broadcast the CSA frame */ + if (fwd_csa) { + if (mesh_fwd_csa_frame(sdata, mgmt, len) < 0) + mcsa_dbg(sdata, "Failed to forward the CSA frame"); + } } static void ieee80211_mesh_rx_mgmt_action(struct ieee80211_sub_if_data *sdata, @@ -939,6 +1199,9 @@ static void ieee80211_mesh_rx_mgmt_action(struct ieee80211_sub_if_data *sdata, if (mesh_action_is_path_sel(mgmt)) mesh_rx_path_sel_frame(sdata, mgmt, len); break; + case WLAN_CATEGORY_SPECTRUM_MGMT: + mesh_rx_csa_frame(sdata, mgmt, len); + break; } } @@ -952,7 +1215,7 @@ void ieee80211_mesh_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata, sdata_lock(sdata); /* mesh already went down */ - if (!sdata->wdev.mesh_id_len) + if (!sdata->u.mesh.mesh_id_len) goto out; rx_status = IEEE80211_SKB_RXCB(skb); @@ -1005,7 +1268,7 @@ void ieee80211_mesh_work(struct ieee80211_sub_if_data *sdata) sdata_lock(sdata); /* mesh already went down */ - if (!sdata->wdev.mesh_id_len) + if (!sdata->u.mesh.mesh_id_len) goto out; if (ifmsh->preq_queue_len && @@ -1056,13 +1319,11 @@ void ieee80211_mesh_init_sdata(struct ieee80211_sub_if_data *sdata) (unsigned long) sdata); ifmsh->accepting_plinks = true; - ifmsh->preq_id = 0; - ifmsh->sn = 0; - ifmsh->num_gates = 0; atomic_set(&ifmsh->mpaths, 0); mesh_rmc_init(sdata); ifmsh->last_preq = jiffies; ifmsh->next_perr = jiffies; + ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE; /* Allocate all mesh structures when creating the first mesh interface. */ if (!mesh_allocated) ieee80211s_init(); diff --git a/net/mac80211/mesh.h b/net/mac80211/mesh.h index 2bc7fd2f787..f39a19f9090 100644 --- a/net/mac80211/mesh.h +++ b/net/mac80211/mesh.h @@ -215,8 +215,6 @@ int mesh_rmc_check(struct ieee80211_sub_if_data *sdata, bool mesh_matches_local(struct ieee80211_sub_if_data *sdata, struct ieee802_11_elems *ie); void mesh_ids_set_default(struct ieee80211_if_mesh *mesh); -void mesh_mgmt_ies_add(struct ieee80211_sub_if_data *sdata, - struct sk_buff *skb); int mesh_add_meshconf_ie(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb); int mesh_add_meshid_ie(struct ieee80211_sub_if_data *sdata, @@ -303,8 +301,8 @@ void mesh_mpath_table_grow(void); void mesh_mpp_table_grow(void); /* Mesh paths */ int mesh_path_error_tx(struct ieee80211_sub_if_data *sdata, - u8 ttl, const u8 *target, __le32 target_sn, - __le16 target_rcode, const u8 *ra); + u8 ttl, const u8 *target, u32 target_sn, + u16 target_rcode, const u8 *ra); void mesh_path_assign_nexthop(struct mesh_path *mpath, struct sta_info *sta); void mesh_path_flush_pending(struct mesh_path *mpath); void mesh_path_tx_pending(struct mesh_path *mpath); diff --git a/net/mac80211/mesh_hwmp.c b/net/mac80211/mesh_hwmp.c index 486819cd02c..94758b9c9ed 100644 --- a/net/mac80211/mesh_hwmp.c +++ b/net/mac80211/mesh_hwmp.c @@ -37,7 +37,7 @@ static inline u32 u32_field_get(const u8 *preq_elem, int offset, bool ae) return get_unaligned_le32(preq_elem + offset); } -static inline u32 u16_field_get(const u8 *preq_elem, int offset, bool ae) +static inline u16 u16_field_get(const u8 *preq_elem, int offset, bool ae) { if (ae) offset += 6; @@ -102,12 +102,11 @@ enum mpath_frame_type { static const u8 broadcast_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; static int mesh_path_sel_frame_tx(enum mpath_frame_type action, u8 flags, - const u8 *orig_addr, __le32 orig_sn, + const u8 *orig_addr, u32 orig_sn, u8 target_flags, const u8 *target, - __le32 target_sn, const u8 *da, + u32 target_sn, const u8 *da, u8 hop_count, u8 ttl, - __le32 lifetime, __le32 metric, - __le32 preq_id, + u32 lifetime, u32 metric, u32 preq_id, struct ieee80211_sub_if_data *sdata) { struct ieee80211_local *local = sdata->local; @@ -167,33 +166,33 @@ static int mesh_path_sel_frame_tx(enum mpath_frame_type action, u8 flags, if (action == MPATH_PREP) { memcpy(pos, target, ETH_ALEN); pos += ETH_ALEN; - memcpy(pos, &target_sn, 4); + put_unaligned_le32(target_sn, pos); pos += 4; } else { if (action == MPATH_PREQ) { - memcpy(pos, &preq_id, 4); + put_unaligned_le32(preq_id, pos); pos += 4; } memcpy(pos, orig_addr, ETH_ALEN); pos += ETH_ALEN; - memcpy(pos, &orig_sn, 4); + put_unaligned_le32(orig_sn, pos); pos += 4; } - memcpy(pos, &lifetime, 4); /* interval for RANN */ + put_unaligned_le32(lifetime, pos); /* interval for RANN */ pos += 4; - memcpy(pos, &metric, 4); + put_unaligned_le32(metric, pos); pos += 4; if (action == MPATH_PREQ) { *pos++ = 1; /* destination count */ *pos++ = target_flags; memcpy(pos, target, ETH_ALEN); pos += ETH_ALEN; - memcpy(pos, &target_sn, 4); + put_unaligned_le32(target_sn, pos); pos += 4; } else if (action == MPATH_PREP) { memcpy(pos, orig_addr, ETH_ALEN); pos += ETH_ALEN; - memcpy(pos, &orig_sn, 4); + put_unaligned_le32(orig_sn, pos); pos += 4; } @@ -239,8 +238,8 @@ static void prepare_frame_for_deferred_tx(struct ieee80211_sub_if_data *sdata, * frame directly but add it to the pending queue instead. */ int mesh_path_error_tx(struct ieee80211_sub_if_data *sdata, - u8 ttl, const u8 *target, __le32 target_sn, - __le16 target_rcode, const u8 *ra) + u8 ttl, const u8 *target, u32 target_sn, + u16 target_rcode, const u8 *ra) { struct ieee80211_local *local = sdata->local; struct sk_buff *skb; @@ -254,13 +253,13 @@ int mesh_path_error_tx(struct ieee80211_sub_if_data *sdata, return -EAGAIN; skb = dev_alloc_skb(local->tx_headroom + - IEEE80211_ENCRYPT_HEADROOM + + sdata->encrypt_headroom + IEEE80211_ENCRYPT_TAILROOM + hdr_len + 2 + 15 /* PERR IE */); if (!skb) return -1; - skb_reserve(skb, local->tx_headroom + IEEE80211_ENCRYPT_HEADROOM); + skb_reserve(skb, local->tx_headroom + sdata->encrypt_headroom); mgmt = (struct ieee80211_mgmt *) skb_put(skb, hdr_len); memset(mgmt, 0, hdr_len); mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | @@ -293,9 +292,9 @@ int mesh_path_error_tx(struct ieee80211_sub_if_data *sdata, pos++; memcpy(pos, target, ETH_ALEN); pos += ETH_ALEN; - memcpy(pos, &target_sn, 4); + put_unaligned_le32(target_sn, pos); pos += 4; - memcpy(pos, &target_rcode, 2); + put_unaligned_le16(target_rcode, pos); /* see note in function header */ prepare_frame_for_deferred_tx(sdata, skb); @@ -545,9 +544,10 @@ static void hwmp_preq_frame_process(struct ieee80211_sub_if_data *sdata, if (time_after(jiffies, ifmsh->last_sn_update + net_traversal_jiffies(sdata)) || time_before(jiffies, ifmsh->last_sn_update)) { - target_sn = ++ifmsh->sn; + ++ifmsh->sn; ifmsh->last_sn_update = jiffies; } + target_sn = ifmsh->sn; } else if (is_broadcast_ether_addr(target_addr) && (target_flags & IEEE80211_PREQ_TO_FLAG)) { rcu_read_lock(); @@ -592,10 +592,9 @@ static void hwmp_preq_frame_process(struct ieee80211_sub_if_data *sdata, if (ttl != 0) { mhwmp_dbg(sdata, "replying to the PREQ\n"); mesh_path_sel_frame_tx(MPATH_PREP, 0, orig_addr, - cpu_to_le32(orig_sn), 0, target_addr, - cpu_to_le32(target_sn), mgmt->sa, 0, ttl, - cpu_to_le32(lifetime), cpu_to_le32(metric), - 0, sdata); + orig_sn, 0, target_addr, + target_sn, mgmt->sa, 0, ttl, + lifetime, metric, 0, sdata); } else { ifmsh->mshstats.dropped_frames_ttl++; } @@ -625,11 +624,9 @@ static void hwmp_preq_frame_process(struct ieee80211_sub_if_data *sdata, } mesh_path_sel_frame_tx(MPATH_PREQ, flags, orig_addr, - cpu_to_le32(orig_sn), target_flags, target_addr, - cpu_to_le32(target_sn), da, - hopcount, ttl, cpu_to_le32(lifetime), - cpu_to_le32(metric), cpu_to_le32(preq_id), - sdata); + orig_sn, target_flags, target_addr, + target_sn, da, hopcount, ttl, lifetime, + metric, preq_id, sdata); if (!is_multicast_ether_addr(da)) ifmsh->mshstats.fwded_unicast++; else @@ -695,11 +692,9 @@ static void hwmp_prep_frame_process(struct ieee80211_sub_if_data *sdata, target_sn = PREP_IE_TARGET_SN(prep_elem); orig_sn = PREP_IE_ORIG_SN(prep_elem); - mesh_path_sel_frame_tx(MPATH_PREP, flags, orig_addr, - cpu_to_le32(orig_sn), 0, target_addr, - cpu_to_le32(target_sn), next_hop, hopcount, - ttl, cpu_to_le32(lifetime), cpu_to_le32(metric), - 0, sdata); + mesh_path_sel_frame_tx(MPATH_PREP, flags, orig_addr, orig_sn, 0, + target_addr, target_sn, next_hop, hopcount, + ttl, lifetime, metric, 0, sdata); rcu_read_unlock(); sdata->u.mesh.mshstats.fwded_unicast++; @@ -750,8 +745,7 @@ static void hwmp_perr_frame_process(struct ieee80211_sub_if_data *sdata, if (!ifmsh->mshcfg.dot11MeshForwarding) goto endperr; mesh_path_error_tx(sdata, ttl, target_addr, - cpu_to_le32(target_sn), - cpu_to_le16(target_rcode), + target_sn, target_rcode, broadcast_addr); } else spin_unlock_bh(&mpath->state_lock); @@ -847,11 +841,9 @@ static void hwmp_rann_frame_process(struct ieee80211_sub_if_data *sdata, if (ifmsh->mshcfg.dot11MeshForwarding) { mesh_path_sel_frame_tx(MPATH_RANN, flags, orig_addr, - cpu_to_le32(orig_sn), - 0, NULL, 0, broadcast_addr, - hopcount, ttl, cpu_to_le32(interval), - cpu_to_le32(metric + metric_txsta), - 0, sdata); + orig_sn, 0, NULL, 0, broadcast_addr, + hopcount, ttl, interval, + metric + metric_txsta, 0, sdata); } rcu_read_unlock(); @@ -1049,11 +1041,9 @@ void mesh_path_start_discovery(struct ieee80211_sub_if_data *sdata) spin_unlock_bh(&mpath->state_lock); da = (mpath->is_root) ? mpath->rann_snd_addr : broadcast_addr; - mesh_path_sel_frame_tx(MPATH_PREQ, 0, sdata->vif.addr, - cpu_to_le32(ifmsh->sn), target_flags, mpath->dst, - cpu_to_le32(mpath->sn), da, 0, - ttl, cpu_to_le32(lifetime), 0, - cpu_to_le32(ifmsh->preq_id++), sdata); + mesh_path_sel_frame_tx(MPATH_PREQ, 0, sdata->vif.addr, ifmsh->sn, + target_flags, mpath->dst, mpath->sn, da, 0, + ttl, lifetime, 0, ifmsh->preq_id++, sdata); mod_timer(&mpath->timer, jiffies + mpath->discovery_timeout); enddiscovery: @@ -1212,10 +1202,9 @@ void mesh_path_tx_root_frame(struct ieee80211_sub_if_data *sdata) switch (ifmsh->mshcfg.dot11MeshHWMPRootMode) { case IEEE80211_PROACTIVE_RANN: mesh_path_sel_frame_tx(MPATH_RANN, flags, sdata->vif.addr, - cpu_to_le32(++ifmsh->sn), - 0, NULL, 0, broadcast_addr, - 0, ifmsh->mshcfg.element_ttl, - cpu_to_le32(interval), 0, 0, sdata); + ++ifmsh->sn, 0, NULL, 0, broadcast_addr, + 0, ifmsh->mshcfg.element_ttl, + interval, 0, 0, sdata); break; case IEEE80211_PROACTIVE_PREQ_WITH_PREP: flags |= IEEE80211_PREQ_PROACTIVE_PREP_FLAG; @@ -1224,11 +1213,10 @@ void mesh_path_tx_root_frame(struct ieee80211_sub_if_data *sdata) target_flags |= IEEE80211_PREQ_TO_FLAG | IEEE80211_PREQ_USN_FLAG; mesh_path_sel_frame_tx(MPATH_PREQ, flags, sdata->vif.addr, - cpu_to_le32(++ifmsh->sn), target_flags, - (u8 *) broadcast_addr, 0, broadcast_addr, - 0, ifmsh->mshcfg.element_ttl, - cpu_to_le32(interval), - 0, cpu_to_le32(ifmsh->preq_id++), sdata); + ++ifmsh->sn, target_flags, + (u8 *) broadcast_addr, 0, broadcast_addr, + 0, ifmsh->mshcfg.element_ttl, interval, + 0, ifmsh->preq_id++, sdata); break; default: mhwmp_dbg(sdata, "Proactive mechanism not supported\n"); diff --git a/net/mac80211/mesh_pathtbl.c b/net/mac80211/mesh_pathtbl.c index 89aacfd2756..cf032a8db9d 100644 --- a/net/mac80211/mesh_pathtbl.c +++ b/net/mac80211/mesh_pathtbl.c @@ -287,8 +287,10 @@ static void mesh_path_move_to_queue(struct mesh_path *gate_mpath, struct sk_buff_head failq; unsigned long flags; - BUG_ON(gate_mpath == from_mpath); - BUG_ON(!gate_mpath->next_hop); + if (WARN_ON(gate_mpath == from_mpath)) + return; + if (WARN_ON(!gate_mpath->next_hop)) + return; __skb_queue_head_init(&failq); @@ -722,7 +724,6 @@ void mesh_plink_broken(struct sta_info *sta) struct mpath_node *node; struct ieee80211_sub_if_data *sdata = sta->sdata; int i; - __le16 reason = cpu_to_le16(WLAN_REASON_MESH_PATH_DEST_UNREACHABLE); rcu_read_lock(); tbl = rcu_dereference(mesh_paths); @@ -736,9 +737,9 @@ void mesh_plink_broken(struct sta_info *sta) ++mpath->sn; spin_unlock_bh(&mpath->state_lock); mesh_path_error_tx(sdata, - sdata->u.mesh.mshcfg.element_ttl, - mpath->dst, cpu_to_le32(mpath->sn), - reason, bcast); + sdata->u.mesh.mshcfg.element_ttl, + mpath->dst, mpath->sn, + WLAN_REASON_MESH_PATH_DEST_UNREACHABLE, bcast); } } rcu_read_unlock(); diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c index 6b65d5055f5..e8f60aa2e84 100644 --- a/net/mac80211/mesh_plink.c +++ b/net/mac80211/mesh_plink.c @@ -19,12 +19,6 @@ #define mod_plink_timer(s, t) (mod_timer(&s->plink_timer, \ jiffies + HZ * t / 1000)) -/* We only need a valid sta if user configured a minimum rssi_threshold. */ -#define rssi_threshold_check(sta, sdata) \ - (sdata->u.mesh.mshcfg.rssi_threshold == 0 ||\ - (sta && (s8) -ewma_read(&sta->avg_signal) > \ - sdata->u.mesh.mshcfg.rssi_threshold)) - enum plink_event { PLINK_UNDEFINED, OPN_ACPT, @@ -61,7 +55,17 @@ static const char * const mplevents[] = { static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata, enum ieee80211_self_protected_actioncode action, - u8 *da, __le16 llid, __le16 plid, __le16 reason); + u8 *da, u16 llid, u16 plid, u16 reason); + + +/* We only need a valid sta if user configured a minimum rssi_threshold. */ +static bool rssi_threshold_check(struct ieee80211_sub_if_data *sdata, + struct sta_info *sta) +{ + s32 rssi_threshold = sdata->u.mesh.mshcfg.rssi_threshold; + return rssi_threshold == 0 || + (sta && (s8) -ewma_read(&sta->avg_signal) > rssi_threshold); +} /** * mesh_plink_fsm_restart - restart a mesh peer link finite state machine @@ -222,7 +226,8 @@ static u32 __mesh_plink_deactivate(struct sta_info *sta) mesh_path_flush_by_nexthop(sta); ieee80211_mps_sta_status_update(sta); - changed |= ieee80211_mps_local_status_update(sdata); + changed |= ieee80211_mps_set_sta_local_pm(sta, + NL80211_MESH_POWER_UNKNOWN); return changed; } @@ -241,7 +246,7 @@ u32 mesh_plink_deactivate(struct sta_info *sta) spin_lock_bh(&sta->lock); changed = __mesh_plink_deactivate(sta); - sta->reason = cpu_to_le16(WLAN_REASON_MESH_PEER_CANCELED); + sta->reason = WLAN_REASON_MESH_PEER_CANCELED; mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE, sta->sta.addr, sta->llid, sta->plid, sta->reason); @@ -252,7 +257,7 @@ u32 mesh_plink_deactivate(struct sta_info *sta) static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata, enum ieee80211_self_protected_actioncode action, - u8 *da, __le16 llid, __le16 plid, __le16 reason) + u8 *da, u16 llid, u16 plid, u16 reason) { struct ieee80211_local *local = sdata->local; struct sk_buff *skb; @@ -278,7 +283,7 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata, 2 + 8 + /* peering IE */ sdata->u.mesh.ie_len); if (!skb) - return -1; + return err; info = IEEE80211_SKB_CB(skb); skb_reserve(skb, local->tx_headroom); mgmt = (struct ieee80211_mgmt *) skb_put(skb, hdr_len); @@ -300,7 +305,7 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata, if (action == WLAN_SP_MESH_PEERING_CONFIRM) { /* AID */ pos = skb_put(skb, 2); - memcpy(pos + 2, &plid, 2); + put_unaligned_le16(plid, pos + 2); } if (ieee80211_add_srates_ie(sdata, skb, true, band) || ieee80211_add_ext_srates_ie(sdata, skb, true, band) || @@ -342,14 +347,14 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata, *pos++ = ie_len; memcpy(pos, &peering_proto, 2); pos += 2; - memcpy(pos, &llid, 2); + put_unaligned_le16(llid, pos); pos += 2; if (include_plid) { - memcpy(pos, &plid, 2); + put_unaligned_le16(plid, pos); pos += 2; } if (action == WLAN_SP_MESH_PEERING_CLOSE) { - memcpy(pos, &reason, 2); + put_unaligned_le16(reason, pos); pos += 2; } @@ -432,6 +437,7 @@ __mesh_sta_info_alloc(struct ieee80211_sub_if_data *sdata, u8 *hw_addr) sta_info_pre_move_state(sta, IEEE80211_STA_AUTHORIZED); set_sta_flag(sta, WLAN_STA_WME); + sta->sta.wme = true; return sta; } @@ -517,7 +523,7 @@ void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata, sta->plink_state == NL80211_PLINK_LISTEN && sdata->u.mesh.accepting_plinks && sdata->u.mesh.mshcfg.auto_open_plinks && - rssi_threshold_check(sta, sdata)) + rssi_threshold_check(sdata, sta)) changed = mesh_plink_open(sta); ieee80211_mps_frame_release(sta, elems); @@ -529,9 +535,10 @@ out: static void mesh_plink_timer(unsigned long data) { struct sta_info *sta; - __le16 llid, plid, reason; + u16 reason = 0; struct ieee80211_sub_if_data *sdata; struct mesh_config *mshcfg; + enum ieee80211_self_protected_actioncode action = 0; /* * This STA is valid because sta_info_destroy() will @@ -552,9 +559,6 @@ static void mesh_plink_timer(unsigned long data) mpl_dbg(sta->sdata, "Mesh plink timer for %pM fired on state %s\n", sta->sta.addr, mplstates[sta->plink_state]); - reason = 0; - llid = sta->llid; - plid = sta->plid; sdata = sta->sdata; mshcfg = &sdata->u.mesh.mshcfg; @@ -573,33 +577,31 @@ static void mesh_plink_timer(unsigned long data) rand % sta->plink_timeout; ++sta->plink_retries; mod_plink_timer(sta, sta->plink_timeout); - spin_unlock_bh(&sta->lock); - mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_OPEN, - sta->sta.addr, llid, 0, 0); + action = WLAN_SP_MESH_PEERING_OPEN; break; } - reason = cpu_to_le16(WLAN_REASON_MESH_MAX_RETRIES); + reason = WLAN_REASON_MESH_MAX_RETRIES; /* fall through on else */ case NL80211_PLINK_CNF_RCVD: /* confirm timer */ if (!reason) - reason = cpu_to_le16(WLAN_REASON_MESH_CONFIRM_TIMEOUT); + reason = WLAN_REASON_MESH_CONFIRM_TIMEOUT; sta->plink_state = NL80211_PLINK_HOLDING; mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout); - spin_unlock_bh(&sta->lock); - mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE, - sta->sta.addr, llid, plid, reason); + action = WLAN_SP_MESH_PEERING_CLOSE; break; case NL80211_PLINK_HOLDING: /* holding timer */ del_timer(&sta->plink_timer); mesh_plink_fsm_restart(sta); - spin_unlock_bh(&sta->lock); break; default: - spin_unlock_bh(&sta->lock); break; } + spin_unlock_bh(&sta->lock); + if (action) + mesh_plink_frame_tx(sdata, action, sta->sta.addr, + sta->llid, sta->plid, reason); } static inline void mesh_plink_timer_set(struct sta_info *sta, int timeout) @@ -611,9 +613,40 @@ static inline void mesh_plink_timer_set(struct sta_info *sta, int timeout) add_timer(&sta->plink_timer); } +static bool llid_in_use(struct ieee80211_sub_if_data *sdata, + u16 llid) +{ + struct ieee80211_local *local = sdata->local; + bool in_use = false; + struct sta_info *sta; + + rcu_read_lock(); + list_for_each_entry_rcu(sta, &local->sta_list, list) { + if (!memcmp(&sta->llid, &llid, sizeof(llid))) { + in_use = true; + break; + } + } + rcu_read_unlock(); + + return in_use; +} + +static u16 mesh_get_new_llid(struct ieee80211_sub_if_data *sdata) +{ + u16 llid; + + do { + get_random_bytes(&llid, sizeof(llid)); + /* for mesh PS we still only have the AID range for TIM bits */ + llid = (llid % IEEE80211_MAX_AID) + 1; + } while (llid_in_use(sdata, llid)); + + return llid; +} + u32 mesh_plink_open(struct sta_info *sta) { - __le16 llid; struct ieee80211_sub_if_data *sdata = sta->sdata; u32 changed; @@ -621,8 +654,7 @@ u32 mesh_plink_open(struct sta_info *sta) return 0; spin_lock_bh(&sta->lock); - get_random_bytes(&llid, 2); - sta->llid = llid; + sta->llid = mesh_get_new_llid(sdata); if (sta->plink_state != NL80211_PLINK_LISTEN && sta->plink_state != NL80211_PLINK_BLOCKED) { spin_unlock_bh(&sta->lock); @@ -639,7 +671,7 @@ u32 mesh_plink_open(struct sta_info *sta) changed = ieee80211_mps_local_status_update(sdata); mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_OPEN, - sta->sta.addr, llid, 0, 0); + sta->sta.addr, sta->llid, 0, 0); return changed; } @@ -655,390 +687,147 @@ u32 mesh_plink_block(struct sta_info *sta) return changed; } - -void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, - struct ieee80211_mgmt *mgmt, size_t len, - struct ieee80211_rx_status *rx_status) +static void mesh_plink_close(struct ieee80211_sub_if_data *sdata, + struct sta_info *sta, + enum plink_event event) { struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg; - struct ieee802_11_elems elems; - struct sta_info *sta; - enum plink_event event; - enum ieee80211_self_protected_actioncode ftype; - size_t baselen; - bool matches_local = true; - u8 ie_len; - u8 *baseaddr; - u32 changed = 0; - __le16 plid, llid, reason; - - /* need action_code, aux */ - if (len < IEEE80211_MIN_ACTION_SIZE + 3) - return; - - if (sdata->u.mesh.user_mpm) - /* userspace must register for these */ - return; - - if (is_multicast_ether_addr(mgmt->da)) { - mpl_dbg(sdata, - "Mesh plink: ignore frame from multicast address\n"); - return; - } - - baseaddr = mgmt->u.action.u.self_prot.variable; - baselen = (u8 *) mgmt->u.action.u.self_prot.variable - (u8 *) mgmt; - if (mgmt->u.action.u.self_prot.action_code == - WLAN_SP_MESH_PEERING_CONFIRM) { - baseaddr += 4; - baselen += 4; - } - ieee802_11_parse_elems(baseaddr, len - baselen, true, &elems); - - if (!elems.peering) { - mpl_dbg(sdata, - "Mesh plink: missing necessary peer link ie\n"); - return; - } - if (elems.rsn_len && - sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) { - mpl_dbg(sdata, - "Mesh plink: can't establish link with secure peer\n"); - return; - } + u16 reason = (event == CLS_ACPT) ? + WLAN_REASON_MESH_CLOSE : WLAN_REASON_MESH_CONFIG; - ftype = mgmt->u.action.u.self_prot.action_code; - ie_len = elems.peering_len; - if ((ftype == WLAN_SP_MESH_PEERING_OPEN && ie_len != 4) || - (ftype == WLAN_SP_MESH_PEERING_CONFIRM && ie_len != 6) || - (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len != 6 - && ie_len != 8)) { - mpl_dbg(sdata, - "Mesh plink: incorrect plink ie length %d %d\n", - ftype, ie_len); - return; - } - - if (ftype != WLAN_SP_MESH_PEERING_CLOSE && - (!elems.mesh_id || !elems.mesh_config)) { - mpl_dbg(sdata, "Mesh plink: missing necessary ie\n"); - return; - } - /* Note the lines below are correct, the llid in the frame is the plid - * from the point of view of this host. - */ - memcpy(&plid, PLINK_GET_LLID(elems.peering), 2); - if (ftype == WLAN_SP_MESH_PEERING_CONFIRM || - (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len == 8)) - memcpy(&llid, PLINK_GET_PLID(elems.peering), 2); - - /* WARNING: Only for sta pointer, is dropped & re-acquired */ - rcu_read_lock(); - - sta = sta_info_get(sdata, mgmt->sa); - if (!sta && ftype != WLAN_SP_MESH_PEERING_OPEN) { - mpl_dbg(sdata, "Mesh plink: cls or cnf from unknown peer\n"); - rcu_read_unlock(); - return; - } - - if (ftype == WLAN_SP_MESH_PEERING_OPEN && - !rssi_threshold_check(sta, sdata)) { - mpl_dbg(sdata, "Mesh plink: %pM does not meet rssi threshold\n", - mgmt->sa); - rcu_read_unlock(); - return; - } - - if (sta && !test_sta_flag(sta, WLAN_STA_AUTH)) { - mpl_dbg(sdata, "Mesh plink: Action frame from non-authed peer\n"); - rcu_read_unlock(); - return; - } + sta->reason = reason; + sta->plink_state = NL80211_PLINK_HOLDING; + mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout); +} - if (sta && sta->plink_state == NL80211_PLINK_BLOCKED) { - rcu_read_unlock(); - return; - } +static u32 mesh_plink_establish(struct ieee80211_sub_if_data *sdata, + struct sta_info *sta) +{ + struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg; + u32 changed = 0; - /* Now we will figure out the appropriate event... */ - event = PLINK_UNDEFINED; - if (ftype != WLAN_SP_MESH_PEERING_CLOSE && - !mesh_matches_local(sdata, &elems)) { - matches_local = false; - switch (ftype) { - case WLAN_SP_MESH_PEERING_OPEN: - event = OPN_RJCT; - break; - case WLAN_SP_MESH_PEERING_CONFIRM: - event = CNF_RJCT; - break; - default: - break; - } - } + del_timer(&sta->plink_timer); + sta->plink_state = NL80211_PLINK_ESTAB; + changed |= mesh_plink_inc_estab_count(sdata); + changed |= mesh_set_ht_prot_mode(sdata); + changed |= mesh_set_short_slot_time(sdata); + mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n", sta->sta.addr); + ieee80211_mps_sta_status_update(sta); + changed |= ieee80211_mps_set_sta_local_pm(sta, mshcfg->power_mode); + return changed; +} - if (!sta && !matches_local) { - rcu_read_unlock(); - reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG); - llid = 0; - mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE, - mgmt->sa, llid, plid, reason); - return; - } else if (!sta) { - /* ftype == WLAN_SP_MESH_PEERING_OPEN */ - if (!mesh_plink_free_count(sdata)) { - mpl_dbg(sdata, "Mesh plink error: no more free plinks\n"); - rcu_read_unlock(); - return; - } - event = OPN_ACPT; - } else if (matches_local) { - switch (ftype) { - case WLAN_SP_MESH_PEERING_OPEN: - if (!mesh_plink_free_count(sdata) || - (sta->plid && sta->plid != plid)) - event = OPN_IGNR; - else - event = OPN_ACPT; - break; - case WLAN_SP_MESH_PEERING_CONFIRM: - if (!mesh_plink_free_count(sdata) || - (sta->llid != llid || sta->plid != plid)) - event = CNF_IGNR; - else - event = CNF_ACPT; - break; - case WLAN_SP_MESH_PEERING_CLOSE: - if (sta->plink_state == NL80211_PLINK_ESTAB) - /* Do not check for llid or plid. This does not - * follow the standard but since multiple plinks - * per sta are not supported, it is necessary in - * order to avoid a livelock when MP A sees an - * establish peer link to MP B but MP B does not - * see it. This can be caused by a timeout in - * B's peer link establishment or B beign - * restarted. - */ - event = CLS_ACPT; - else if (sta->plid != plid) - event = CLS_IGNR; - else if (ie_len == 7 && sta->llid != llid) - event = CLS_IGNR; - else - event = CLS_ACPT; - break; - default: - mpl_dbg(sdata, "Mesh plink: unknown frame subtype\n"); - rcu_read_unlock(); - return; - } - } +/** + * mesh_plink_fsm - step @sta MPM based on @event + * + * @sdata: interface + * @sta: mesh neighbor + * @event: peering event + * + * Return: changed MBSS flags + */ +static u32 mesh_plink_fsm(struct ieee80211_sub_if_data *sdata, + struct sta_info *sta, enum plink_event event) +{ + struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg; + enum ieee80211_self_protected_actioncode action = 0; + u32 changed = 0; - if (event == OPN_ACPT) { - rcu_read_unlock(); - /* allocate sta entry if necessary and update info */ - sta = mesh_sta_info_get(sdata, mgmt->sa, &elems); - if (!sta) { - mpl_dbg(sdata, "Mesh plink: failed to init peer!\n"); - rcu_read_unlock(); - return; - } - } + mpl_dbg(sdata, "peer %pM in state %s got event %s\n", sta->sta.addr, + mplstates[sta->plink_state], mplevents[event]); - mpl_dbg(sdata, "peer %pM in state %s got event %s\n", mgmt->sa, - mplstates[sta->plink_state], mplevents[event]); - reason = 0; spin_lock_bh(&sta->lock); switch (sta->plink_state) { - /* spin_unlock as soon as state is updated at each case */ case NL80211_PLINK_LISTEN: switch (event) { case CLS_ACPT: mesh_plink_fsm_restart(sta); - spin_unlock_bh(&sta->lock); break; case OPN_ACPT: sta->plink_state = NL80211_PLINK_OPN_RCVD; - sta->plid = plid; - get_random_bytes(&llid, 2); - sta->llid = llid; + sta->llid = mesh_get_new_llid(sdata); mesh_plink_timer_set(sta, mshcfg->dot11MeshRetryTimeout); /* set the non-peer mode to active during peering */ changed |= ieee80211_mps_local_status_update(sdata); - - spin_unlock_bh(&sta->lock); - mesh_plink_frame_tx(sdata, - WLAN_SP_MESH_PEERING_OPEN, - sta->sta.addr, llid, 0, 0); - mesh_plink_frame_tx(sdata, - WLAN_SP_MESH_PEERING_CONFIRM, - sta->sta.addr, llid, plid, 0); + action = WLAN_SP_MESH_PEERING_OPEN; break; default: - spin_unlock_bh(&sta->lock); break; } break; - case NL80211_PLINK_OPN_SNT: switch (event) { case OPN_RJCT: case CNF_RJCT: - reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG); case CLS_ACPT: - if (!reason) - reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE); - sta->reason = reason; - sta->plink_state = NL80211_PLINK_HOLDING; - if (!mod_plink_timer(sta, - mshcfg->dot11MeshHoldingTimeout)) - sta->ignore_plink_timer = true; - - llid = sta->llid; - spin_unlock_bh(&sta->lock); - mesh_plink_frame_tx(sdata, - WLAN_SP_MESH_PEERING_CLOSE, - sta->sta.addr, llid, plid, reason); + mesh_plink_close(sdata, sta, event); + action = WLAN_SP_MESH_PEERING_CLOSE; break; case OPN_ACPT: /* retry timer is left untouched */ sta->plink_state = NL80211_PLINK_OPN_RCVD; - sta->plid = plid; - llid = sta->llid; - spin_unlock_bh(&sta->lock); - mesh_plink_frame_tx(sdata, - WLAN_SP_MESH_PEERING_CONFIRM, - sta->sta.addr, llid, plid, 0); + action = WLAN_SP_MESH_PEERING_CONFIRM; break; case CNF_ACPT: sta->plink_state = NL80211_PLINK_CNF_RCVD; if (!mod_plink_timer(sta, mshcfg->dot11MeshConfirmTimeout)) sta->ignore_plink_timer = true; - - spin_unlock_bh(&sta->lock); break; default: - spin_unlock_bh(&sta->lock); break; } break; - case NL80211_PLINK_OPN_RCVD: switch (event) { case OPN_RJCT: case CNF_RJCT: - reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG); case CLS_ACPT: - if (!reason) - reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE); - sta->reason = reason; - sta->plink_state = NL80211_PLINK_HOLDING; - if (!mod_plink_timer(sta, - mshcfg->dot11MeshHoldingTimeout)) - sta->ignore_plink_timer = true; - - llid = sta->llid; - spin_unlock_bh(&sta->lock); - mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE, - sta->sta.addr, llid, plid, reason); + mesh_plink_close(sdata, sta, event); + action = WLAN_SP_MESH_PEERING_CLOSE; break; case OPN_ACPT: - llid = sta->llid; - spin_unlock_bh(&sta->lock); - mesh_plink_frame_tx(sdata, - WLAN_SP_MESH_PEERING_CONFIRM, - sta->sta.addr, llid, plid, 0); + action = WLAN_SP_MESH_PEERING_CONFIRM; break; case CNF_ACPT: - del_timer(&sta->plink_timer); - sta->plink_state = NL80211_PLINK_ESTAB; - spin_unlock_bh(&sta->lock); - changed |= mesh_plink_inc_estab_count(sdata); - changed |= mesh_set_ht_prot_mode(sdata); - changed |= mesh_set_short_slot_time(sdata); - mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n", - sta->sta.addr); - ieee80211_mps_sta_status_update(sta); - changed |= ieee80211_mps_set_sta_local_pm(sta, - mshcfg->power_mode); + changed |= mesh_plink_establish(sdata, sta); break; default: - spin_unlock_bh(&sta->lock); break; } break; - case NL80211_PLINK_CNF_RCVD: switch (event) { case OPN_RJCT: case CNF_RJCT: - reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG); case CLS_ACPT: - if (!reason) - reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE); - sta->reason = reason; - sta->plink_state = NL80211_PLINK_HOLDING; - if (!mod_plink_timer(sta, - mshcfg->dot11MeshHoldingTimeout)) - sta->ignore_plink_timer = true; - - llid = sta->llid; - spin_unlock_bh(&sta->lock); - mesh_plink_frame_tx(sdata, - WLAN_SP_MESH_PEERING_CLOSE, - sta->sta.addr, llid, plid, reason); + mesh_plink_close(sdata, sta, event); + action = WLAN_SP_MESH_PEERING_CLOSE; break; case OPN_ACPT: - del_timer(&sta->plink_timer); - sta->plink_state = NL80211_PLINK_ESTAB; - spin_unlock_bh(&sta->lock); - changed |= mesh_plink_inc_estab_count(sdata); - changed |= mesh_set_ht_prot_mode(sdata); - changed |= mesh_set_short_slot_time(sdata); - mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n", - sta->sta.addr); - mesh_plink_frame_tx(sdata, - WLAN_SP_MESH_PEERING_CONFIRM, - sta->sta.addr, llid, plid, 0); - ieee80211_mps_sta_status_update(sta); - changed |= ieee80211_mps_set_sta_local_pm(sta, - mshcfg->power_mode); + changed |= mesh_plink_establish(sdata, sta); + action = WLAN_SP_MESH_PEERING_CONFIRM; break; default: - spin_unlock_bh(&sta->lock); break; } break; - case NL80211_PLINK_ESTAB: switch (event) { case CLS_ACPT: - reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE); - sta->reason = reason; changed |= __mesh_plink_deactivate(sta); - sta->plink_state = NL80211_PLINK_HOLDING; - llid = sta->llid; - mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout); - spin_unlock_bh(&sta->lock); changed |= mesh_set_ht_prot_mode(sdata); changed |= mesh_set_short_slot_time(sdata); - mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE, - sta->sta.addr, llid, plid, reason); + mesh_plink_close(sdata, sta, event); + action = WLAN_SP_MESH_PEERING_CLOSE; break; case OPN_ACPT: - llid = sta->llid; - spin_unlock_bh(&sta->lock); - mesh_plink_frame_tx(sdata, - WLAN_SP_MESH_PEERING_CONFIRM, - sta->sta.addr, llid, plid, 0); + action = WLAN_SP_MESH_PEERING_CONFIRM; break; default: - spin_unlock_bh(&sta->lock); break; } break; @@ -1048,32 +837,271 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, if (del_timer(&sta->plink_timer)) sta->ignore_plink_timer = 1; mesh_plink_fsm_restart(sta); - spin_unlock_bh(&sta->lock); break; case OPN_ACPT: case CNF_ACPT: case OPN_RJCT: case CNF_RJCT: - llid = sta->llid; - reason = sta->reason; - spin_unlock_bh(&sta->lock); - mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE, - sta->sta.addr, llid, plid, reason); + action = WLAN_SP_MESH_PEERING_CLOSE; break; default: - spin_unlock_bh(&sta->lock); + break; } break; default: /* should not get here, PLINK_BLOCKED is dealt with at the * beginning of the function */ - spin_unlock_bh(&sta->lock); break; } + spin_unlock_bh(&sta->lock); + if (action) { + mesh_plink_frame_tx(sdata, action, sta->sta.addr, + sta->llid, sta->plid, sta->reason); + + /* also send confirm in open case */ + if (action == WLAN_SP_MESH_PEERING_OPEN) { + mesh_plink_frame_tx(sdata, + WLAN_SP_MESH_PEERING_CONFIRM, + sta->sta.addr, sta->llid, + sta->plid, 0); + } + } + + return changed; +} + +/* + * mesh_plink_get_event - get correct MPM event + * + * @sdata: interface + * @sta: peer, leave NULL if processing a frame from a new suitable peer + * @elems: peering management IEs + * @ftype: frame type + * @llid: peer's peer link ID + * @plid: peer's local link ID + * + * Return: new peering event for @sta, but PLINK_UNDEFINED should be treated as + * an error. + */ +static enum plink_event +mesh_plink_get_event(struct ieee80211_sub_if_data *sdata, + struct sta_info *sta, + struct ieee802_11_elems *elems, + enum ieee80211_self_protected_actioncode ftype, + u16 llid, u16 plid) +{ + enum plink_event event = PLINK_UNDEFINED; + u8 ie_len = elems->peering_len; + bool matches_local; + + matches_local = (ftype == WLAN_SP_MESH_PEERING_CLOSE || + mesh_matches_local(sdata, elems)); + + /* deny open request from non-matching peer */ + if (!matches_local && !sta) { + event = OPN_RJCT; + goto out; + } + + if (!sta) { + if (ftype != WLAN_SP_MESH_PEERING_OPEN) { + mpl_dbg(sdata, "Mesh plink: cls or cnf from unknown peer\n"); + goto out; + } + /* ftype == WLAN_SP_MESH_PEERING_OPEN */ + if (!mesh_plink_free_count(sdata)) { + mpl_dbg(sdata, "Mesh plink error: no more free plinks\n"); + goto out; + } + } else { + if (!test_sta_flag(sta, WLAN_STA_AUTH)) { + mpl_dbg(sdata, "Mesh plink: Action frame from non-authed peer\n"); + goto out; + } + if (sta->plink_state == NL80211_PLINK_BLOCKED) + goto out; + } + + /* new matching peer */ + if (!sta) { + event = OPN_ACPT; + goto out; + } + + switch (ftype) { + case WLAN_SP_MESH_PEERING_OPEN: + if (!matches_local) + event = OPN_RJCT; + if (!mesh_plink_free_count(sdata) || + (sta->plid && sta->plid != plid)) + event = OPN_IGNR; + else + event = OPN_ACPT; + break; + case WLAN_SP_MESH_PEERING_CONFIRM: + if (!matches_local) + event = CNF_RJCT; + if (!mesh_plink_free_count(sdata) || + (sta->llid != llid || sta->plid != plid)) + event = CNF_IGNR; + else + event = CNF_ACPT; + break; + case WLAN_SP_MESH_PEERING_CLOSE: + if (sta->plink_state == NL80211_PLINK_ESTAB) + /* Do not check for llid or plid. This does not + * follow the standard but since multiple plinks + * per sta are not supported, it is necessary in + * order to avoid a livelock when MP A sees an + * establish peer link to MP B but MP B does not + * see it. This can be caused by a timeout in + * B's peer link establishment or B beign + * restarted. + */ + event = CLS_ACPT; + else if (sta->plid != plid) + event = CLS_IGNR; + else if (ie_len == 8 && sta->llid != llid) + event = CLS_IGNR; + else + event = CLS_ACPT; + break; + default: + mpl_dbg(sdata, "Mesh plink: unknown frame subtype\n"); + break; + } + +out: + return event; +} +static void +mesh_process_plink_frame(struct ieee80211_sub_if_data *sdata, + struct ieee80211_mgmt *mgmt, + struct ieee802_11_elems *elems) +{ + + struct sta_info *sta; + enum plink_event event; + enum ieee80211_self_protected_actioncode ftype; + u32 changed = 0; + u8 ie_len = elems->peering_len; + __le16 _plid, _llid; + u16 plid, llid = 0; + + if (!elems->peering) { + mpl_dbg(sdata, + "Mesh plink: missing necessary peer link ie\n"); + return; + } + + if (elems->rsn_len && + sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) { + mpl_dbg(sdata, + "Mesh plink: can't establish link with secure peer\n"); + return; + } + + ftype = mgmt->u.action.u.self_prot.action_code; + if ((ftype == WLAN_SP_MESH_PEERING_OPEN && ie_len != 4) || + (ftype == WLAN_SP_MESH_PEERING_CONFIRM && ie_len != 6) || + (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len != 6 + && ie_len != 8)) { + mpl_dbg(sdata, + "Mesh plink: incorrect plink ie length %d %d\n", + ftype, ie_len); + return; + } + + if (ftype != WLAN_SP_MESH_PEERING_CLOSE && + (!elems->mesh_id || !elems->mesh_config)) { + mpl_dbg(sdata, "Mesh plink: missing necessary ie\n"); + return; + } + /* Note the lines below are correct, the llid in the frame is the plid + * from the point of view of this host. + */ + memcpy(&_plid, PLINK_GET_LLID(elems->peering), sizeof(__le16)); + plid = le16_to_cpu(_plid); + if (ftype == WLAN_SP_MESH_PEERING_CONFIRM || + (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len == 8)) { + memcpy(&_llid, PLINK_GET_PLID(elems->peering), sizeof(__le16)); + llid = le16_to_cpu(_llid); + } + + /* WARNING: Only for sta pointer, is dropped & re-acquired */ + rcu_read_lock(); + + sta = sta_info_get(sdata, mgmt->sa); + + if (ftype == WLAN_SP_MESH_PEERING_OPEN && + !rssi_threshold_check(sdata, sta)) { + mpl_dbg(sdata, "Mesh plink: %pM does not meet rssi threshold\n", + mgmt->sa); + goto unlock_rcu; + } + + /* Now we will figure out the appropriate event... */ + event = mesh_plink_get_event(sdata, sta, elems, ftype, llid, plid); + + if (event == OPN_ACPT) { + rcu_read_unlock(); + /* allocate sta entry if necessary and update info */ + sta = mesh_sta_info_get(sdata, mgmt->sa, elems); + if (!sta) { + mpl_dbg(sdata, "Mesh plink: failed to init peer!\n"); + goto unlock_rcu; + } + sta->plid = plid; + } else if (!sta && event == OPN_RJCT) { + mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE, + mgmt->sa, 0, plid, + WLAN_REASON_MESH_CONFIG); + goto unlock_rcu; + } else if (!sta || event == PLINK_UNDEFINED) { + /* something went wrong */ + goto unlock_rcu; + } + + changed |= mesh_plink_fsm(sdata, sta, event); + +unlock_rcu: rcu_read_unlock(); if (changed) ieee80211_mbss_info_change_notify(sdata, changed); } + +void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, + struct ieee80211_mgmt *mgmt, size_t len, + struct ieee80211_rx_status *rx_status) +{ + struct ieee802_11_elems elems; + size_t baselen; + u8 *baseaddr; + + /* need action_code, aux */ + if (len < IEEE80211_MIN_ACTION_SIZE + 3) + return; + + if (sdata->u.mesh.user_mpm) + /* userspace must register for these */ + return; + + if (is_multicast_ether_addr(mgmt->da)) { + mpl_dbg(sdata, + "Mesh plink: ignore frame from multicast address\n"); + return; + } + + baseaddr = mgmt->u.action.u.self_prot.variable; + baselen = (u8 *) mgmt->u.action.u.self_prot.variable - (u8 *) mgmt; + if (mgmt->u.action.u.self_prot.action_code == + WLAN_SP_MESH_PEERING_CONFIRM) { + baseaddr += 4; + baselen += 4; + } + ieee802_11_parse_elems(baseaddr, len - baselen, true, &elems); + mesh_process_plink_frame(sdata, mgmt, &elems); +} diff --git a/net/mac80211/mesh_ps.c b/net/mac80211/mesh_ps.c index 22290a929b9..ad8b377b4b9 100644 --- a/net/mac80211/mesh_ps.c +++ b/net/mac80211/mesh_ps.c @@ -36,6 +36,7 @@ static struct sk_buff *mps_qos_null_get(struct sta_info *sta) sdata->vif.addr); nullfunc->frame_control = fc; nullfunc->duration_id = 0; + nullfunc->seq_ctrl = 0; /* no address resolution for this frame -> set addr 1 immediately */ memcpy(nullfunc->addr1, sta->sta.addr, ETH_ALEN); memset(skb_put(skb, 2), 0, 2); /* append QoS control field */ @@ -152,6 +153,9 @@ u32 ieee80211_mps_set_sta_local_pm(struct sta_info *sta, { struct ieee80211_sub_if_data *sdata = sta->sdata; + if (sta->local_pm == pm) + return 0; + mps_dbg(sdata, "local STA operates in mode %d with %pM\n", pm, sta->sta.addr); @@ -245,6 +249,14 @@ void ieee80211_mps_sta_status_update(struct sta_info *sta) do_buffer = (pm != NL80211_MESH_POWER_ACTIVE); + /* clear the MPSP flags for non-peers or active STA */ + if (sta->plink_state != NL80211_PLINK_ESTAB) { + clear_sta_flag(sta, WLAN_STA_MPSP_OWNER); + clear_sta_flag(sta, WLAN_STA_MPSP_RECIPIENT); + } else if (!do_buffer) { + clear_sta_flag(sta, WLAN_STA_MPSP_OWNER); + } + /* Don't let the same PS state be set twice */ if (test_sta_flag(sta, WLAN_STA_PS_STA) == do_buffer) return; @@ -257,14 +269,6 @@ void ieee80211_mps_sta_status_update(struct sta_info *sta) } else { ieee80211_sta_ps_deliver_wakeup(sta); } - - /* clear the MPSP flags for non-peers or active STA */ - if (sta->plink_state != NL80211_PLINK_ESTAB) { - clear_sta_flag(sta, WLAN_STA_MPSP_OWNER); - clear_sta_flag(sta, WLAN_STA_MPSP_RECIPIENT); - } else if (!do_buffer) { - clear_sta_flag(sta, WLAN_STA_MPSP_OWNER); - } } static void mps_set_sta_peer_pm(struct sta_info *sta, @@ -444,8 +448,7 @@ static void mpsp_qos_null_append(struct sta_info *sta, */ static void mps_frame_deliver(struct sta_info *sta, int n_frames) { - struct ieee80211_sub_if_data *sdata = sta->sdata; - struct ieee80211_local *local = sdata->local; + struct ieee80211_local *local = sta->sdata->local; int ac; struct sk_buff_head frames; struct sk_buff *skb; @@ -558,10 +561,10 @@ void ieee80211_mpsp_trigger_process(u8 *qc, struct sta_info *sta, } /** - * ieee80211_mps_frame_release - release buffered frames in response to beacon + * ieee80211_mps_frame_release - release frames buffered due to mesh power save * * @sta: mesh STA - * @elems: beacon IEs + * @elems: IEs of beacon or probe response * * For peers if we have individually-addressed frames buffered or the peer * indicates buffered frames, send a corresponding MPSP trigger frame. Since @@ -574,10 +577,9 @@ void ieee80211_mps_frame_release(struct sta_info *sta, int ac, buffer_local = 0; bool has_buffered = false; - /* TIM map only for LLID <= IEEE80211_MAX_AID */ if (sta->plink_state == NL80211_PLINK_ESTAB) has_buffered = ieee80211_check_tim(elems->tim, elems->tim_len, - le16_to_cpu(sta->llid) % IEEE80211_MAX_AID); + sta->llid); if (has_buffered) mps_dbg(sta->sdata, "%pM indicates buffered frames\n", @@ -588,9 +590,10 @@ void ieee80211_mps_frame_release(struct sta_info *sta, (!elems->awake_window || !le16_to_cpu(*elems->awake_window))) return; - for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) - buffer_local += skb_queue_len(&sta->ps_tx_buf[ac]) + - skb_queue_len(&sta->tx_filtered[ac]); + if (!test_sta_flag(sta, WLAN_STA_MPSP_OWNER)) + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + buffer_local += skb_queue_len(&sta->ps_tx_buf[ac]) + + skb_queue_len(&sta->tx_filtered[ac]); if (!has_buffered && !buffer_local) return; diff --git a/net/mac80211/mesh_sync.c b/net/mac80211/mesh_sync.c index 05a256b38e2..09625d6205c 100644 --- a/net/mac80211/mesh_sync.c +++ b/net/mac80211/mesh_sync.c @@ -92,12 +92,20 @@ static void mesh_sync_offset_rx_bcn_presp(struct ieee80211_sub_if_data *sdata, if (stype != IEEE80211_STYPE_BEACON) return; - /* The current tsf is a first approximation for the timestamp - * for the received beacon. Further down we try to get a - * better value from the rx_status->mactime field if - * available. Also we have to call drv_get_tsf() before - * entering the rcu-read section.*/ - t_r = drv_get_tsf(local, sdata); + /* + * Get time when timestamp field was received. If we don't + * have rx timestamps, then use current tsf as an approximation. + * drv_get_tsf() must be called before entering the rcu-read + * section. + */ + if (ieee80211_have_rx_timestamp(rx_status)) + t_r = ieee80211_calculate_rx_timestamp(local, rx_status, + 24 + 12 + + elems->total_len + + FCS_LEN, + 24); + else + t_r = drv_get_tsf(local, sdata); rcu_read_lock(); sta = sta_info_get(sdata, mgmt->sa); @@ -117,14 +125,6 @@ static void mesh_sync_offset_rx_bcn_presp(struct ieee80211_sub_if_data *sdata, goto no_sync; } - if (ieee80211_have_rx_timestamp(rx_status)) - /* time when timestamp field was received */ - t_r = ieee80211_calculate_rx_timestamp(local, rx_status, - 24 + 12 + - elems->total_len + - FCS_LEN, - 24); - /* Timing offset calculation (see 13.13.2.2.2) */ t_t = le64_to_cpu(mgmt->u.beacon.timestamp); sta->t_offset = t_t - t_r; @@ -164,12 +164,15 @@ no_sync: rcu_read_unlock(); } -static void mesh_sync_offset_adjust_tbtt(struct ieee80211_sub_if_data *sdata) +static void mesh_sync_offset_adjust_tbtt(struct ieee80211_sub_if_data *sdata, + struct beacon_data *beacon) { struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + u8 cap; WARN_ON(ifmsh->mesh_sp_id != IEEE80211_SYNC_METHOD_NEIGHBOR_OFFSET); - BUG_ON(!rcu_read_lock_held()); + WARN_ON(!rcu_read_lock_held()); + cap = beacon->meshconf->meshconf_cap; spin_lock_bh(&ifmsh->sync_offset_lock); @@ -194,6 +197,10 @@ static void mesh_sync_offset_adjust_tbtt(struct ieee80211_sub_if_data *sdata) ifmsh->adjusting_tbtt = false; } spin_unlock_bh(&ifmsh->sync_offset_lock); + + beacon->meshconf->meshconf_cap = ifmsh->adjusting_tbtt ? + IEEE80211_MESHCONF_CAPAB_TBTT_ADJUSTING | cap : + ~IEEE80211_MESHCONF_CAPAB_TBTT_ADJUSTING & cap; } static const struct sync_method sync_methods[] = { diff --git a/net/mac80211/michael.h b/net/mac80211/michael.h index 3b848dad958..0e4886f881f 100644 --- a/net/mac80211/michael.h +++ b/net/mac80211/michael.h @@ -11,6 +11,7 @@ #define MICHAEL_H #include <linux/types.h> +#include <linux/ieee80211.h> #define MICHAEL_MIC_LEN 8 diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 86e4ad56b57..3345401be1b 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -131,13 +131,13 @@ void ieee80211_sta_reset_conn_monitor(struct ieee80211_sub_if_data *sdata) if (unlikely(!sdata->u.mgd.associated)) return; + ifmgd->probe_send_count = 0; + if (sdata->local->hw.flags & IEEE80211_HW_CONNECTION_MONITOR) return; mod_timer(&sdata->u.mgd.conn_mon_timer, round_jiffies_up(jiffies + IEEE80211_CONNECTION_IDLE_TIME)); - - ifmgd->probe_send_count = 0; } static int ecw2cw(int ecw) @@ -145,66 +145,6 @@ static int ecw2cw(int ecw) return (1 << ecw) - 1; } -static u32 chandef_downgrade(struct cfg80211_chan_def *c) -{ - u32 ret; - int tmp; - - switch (c->width) { - case NL80211_CHAN_WIDTH_20: - c->width = NL80211_CHAN_WIDTH_20_NOHT; - ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT; - break; - case NL80211_CHAN_WIDTH_40: - c->width = NL80211_CHAN_WIDTH_20; - c->center_freq1 = c->chan->center_freq; - ret = IEEE80211_STA_DISABLE_40MHZ | - IEEE80211_STA_DISABLE_VHT; - break; - case NL80211_CHAN_WIDTH_80: - tmp = (30 + c->chan->center_freq - c->center_freq1)/20; - /* n_P40 */ - tmp /= 2; - /* freq_P40 */ - c->center_freq1 = c->center_freq1 - 20 + 40 * tmp; - c->width = NL80211_CHAN_WIDTH_40; - ret = IEEE80211_STA_DISABLE_VHT; - break; - case NL80211_CHAN_WIDTH_80P80: - c->center_freq2 = 0; - c->width = NL80211_CHAN_WIDTH_80; - ret = IEEE80211_STA_DISABLE_80P80MHZ | - IEEE80211_STA_DISABLE_160MHZ; - break; - case NL80211_CHAN_WIDTH_160: - /* n_P20 */ - tmp = (70 + c->chan->center_freq - c->center_freq1)/20; - /* n_P80 */ - tmp /= 4; - c->center_freq1 = c->center_freq1 - 40 + 80 * tmp; - c->width = NL80211_CHAN_WIDTH_80; - ret = IEEE80211_STA_DISABLE_80P80MHZ | - IEEE80211_STA_DISABLE_160MHZ; - break; - default: - case NL80211_CHAN_WIDTH_20_NOHT: - WARN_ON_ONCE(1); - c->width = NL80211_CHAN_WIDTH_20_NOHT; - ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT; - break; - case NL80211_CHAN_WIDTH_5: - case NL80211_CHAN_WIDTH_10: - WARN_ON_ONCE(1); - /* keep c->width */ - ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT; - break; - } - - WARN_ON_ONCE(!cfg80211_chandef_valid(c)); - - return ret; -} - static u32 ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata, struct ieee80211_supported_band *sband, @@ -282,6 +222,7 @@ ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata, switch (vht_oper->chan_width) { case IEEE80211_VHT_CHANWIDTH_USE_HT: vht_chandef.width = chandef->width; + vht_chandef.center_freq1 = chandef->center_freq1; break; case IEEE80211_VHT_CHANWIDTH_80MHZ: vht_chandef.width = NL80211_CHAN_WIDTH_80; @@ -331,6 +272,28 @@ ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata, ret = 0; out: + /* + * When tracking the current AP, don't do any further checks if the + * new chandef is identical to the one we're currently using for the + * connection. This keeps us from playing ping-pong with regulatory, + * without it the following can happen (for example): + * - connect to an AP with 80 MHz, world regdom allows 80 MHz + * - AP advertises regdom US + * - CRDA loads regdom US with 80 MHz prohibited (old database) + * - the code below detects an unsupported channel, downgrades, and + * we disconnect from the AP in the caller + * - disconnect causes CRDA to reload world regdomain and the game + * starts anew. + * (see https://bugzilla.kernel.org/show_bug.cgi?id=70881) + * + * It seems possible that there are still scenarios with CSA or real + * bandwidth changes where a this could happen, but those cases are + * less common and wouldn't completely prevent using the AP. + */ + if (tracking && + cfg80211_chandef_identical(chandef, &sdata->vif.bss_conf.chandef)) + return ret; + /* don't print the message below for VHT mismatch if VHT is disabled */ if (ret & IEEE80211_STA_DISABLE_VHT) vht_chandef = *chandef; @@ -352,7 +315,7 @@ out: break; } - ret |= chandef_downgrade(chandef); + ret |= ieee80211_chandef_downgrade(chandef); } if (chandef->width != vht_chandef.width && !tracking) @@ -390,6 +353,16 @@ static int ieee80211_config_bw(struct ieee80211_sub_if_data *sdata, if (WARN_ON_ONCE(!sta)) return -EINVAL; + /* + * if bss configuration changed store the new one - + * this may be applicable even if channel is identical + */ + ht_opmode = le16_to_cpu(ht_oper->operation_mode); + if (sdata->vif.bss_conf.ht_operation_mode != ht_opmode) { + *changed |= BSS_CHANGED_HT; + sdata->vif.bss_conf.ht_operation_mode = ht_opmode; + } + chan = sdata->vif.bss_conf.chandef.chan; sband = local->hw.wiphy->bands[chan->band]; @@ -406,13 +379,13 @@ static int ieee80211_config_bw(struct ieee80211_sub_if_data *sdata, */ if (ifmgd->flags & IEEE80211_STA_DISABLE_80P80MHZ && chandef.width == NL80211_CHAN_WIDTH_80P80) - flags |= chandef_downgrade(&chandef); + flags |= ieee80211_chandef_downgrade(&chandef); if (ifmgd->flags & IEEE80211_STA_DISABLE_160MHZ && chandef.width == NL80211_CHAN_WIDTH_160) - flags |= chandef_downgrade(&chandef); + flags |= ieee80211_chandef_downgrade(&chandef); if (ifmgd->flags & IEEE80211_STA_DISABLE_40MHZ && chandef.width > NL80211_CHAN_WIDTH_20) - flags |= chandef_downgrade(&chandef); + flags |= ieee80211_chandef_downgrade(&chandef); if (cfg80211_chandef_identical(&chandef, &sdata->vif.bss_conf.chandef)) return 0; @@ -476,14 +449,6 @@ static int ieee80211_config_bw(struct ieee80211_sub_if_data *sdata, IEEE80211_RC_BW_CHANGED); } - ht_opmode = le16_to_cpu(ht_oper->operation_mode); - - /* if bss configuration changed store the new one */ - if (sdata->vif.bss_conf.ht_operation_mode != ht_opmode) { - *changed |= BSS_CHANGED_HT; - sdata->vif.bss_conf.ht_operation_mode = ht_opmode; - } - return 0; } @@ -566,6 +531,7 @@ static void ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata, u8 *pos; u32 cap; struct ieee80211_sta_vht_cap vht_cap; + u32 mask, ap_bf_sts, our_bf_sts; BUILD_BUG_ON(sizeof(vht_cap) != sizeof(sband->vht_cap)); @@ -593,6 +559,16 @@ static void ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata, cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE))) cap &= ~IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; + mask = IEEE80211_VHT_CAP_BEAMFORMEE_STS_MASK; + + ap_bf_sts = le32_to_cpu(ap_vht_cap->vht_cap_info) & mask; + our_bf_sts = cap & mask; + + if (ap_bf_sts < our_bf_sts) { + cap &= ~mask; + cap |= ap_bf_sts; + } + /* reserve and fill IE */ pos = skb_put(skb, sizeof(struct ieee80211_vht_cap) + 2); ieee80211_ie_build_vht_cap(pos, &vht_cap, cap); @@ -774,7 +750,7 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata) } /* if present, add any custom IEs that go before HT */ - if (assoc_data->ie_len && assoc_data->ie) { + if (assoc_data->ie_len) { static const u8 before_ht[] = { WLAN_EID_SSID, WLAN_EID_SUPP_RATES, @@ -803,12 +779,40 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata) ieee80211_add_ht_ie(sdata, skb, assoc_data->ap_ht_param, sband, chan, sdata->smps_mode); + /* if present, add any custom IEs that go before VHT */ + if (assoc_data->ie_len) { + static const u8 before_vht[] = { + WLAN_EID_SSID, + WLAN_EID_SUPP_RATES, + WLAN_EID_EXT_SUPP_RATES, + WLAN_EID_PWR_CAPABILITY, + WLAN_EID_SUPPORTED_CHANNELS, + WLAN_EID_RSN, + WLAN_EID_QOS_CAPA, + WLAN_EID_RRM_ENABLED_CAPABILITIES, + WLAN_EID_MOBILITY_DOMAIN, + WLAN_EID_SUPPORTED_REGULATORY_CLASSES, + WLAN_EID_HT_CAPABILITY, + WLAN_EID_BSS_COEX_2040, + WLAN_EID_EXT_CAPABILITY, + WLAN_EID_QOS_TRAFFIC_CAPA, + WLAN_EID_TIM_BCAST_REQ, + WLAN_EID_INTERWORKING, + }; + noffset = ieee80211_ie_split(assoc_data->ie, assoc_data->ie_len, + before_vht, ARRAY_SIZE(before_vht), + offset); + pos = skb_put(skb, noffset - offset); + memcpy(pos, assoc_data->ie + offset, noffset - offset); + offset = noffset; + } + if (!(ifmgd->flags & IEEE80211_STA_DISABLE_VHT)) ieee80211_add_vht_ie(sdata, skb, sband, &assoc_data->ap_vht_cap); /* if present, add any custom non-vendor IEs that go after HT */ - if (assoc_data->ie_len && assoc_data->ie) { + if (assoc_data->ie_len) { noffset = ieee80211_ie_split_vendor(assoc_data->ie, assoc_data->ie_len, offset); @@ -839,7 +843,7 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata) } /* add any remaining custom (i.e. vendor specific here) IEs */ - if (assoc_data->ie_len && assoc_data->ie) { + if (assoc_data->ie_len) { noffset = assoc_data->ie_len; pos = skb_put(skb, noffset - offset); memcpy(pos, assoc_data->ie + offset, noffset - offset); @@ -893,8 +897,7 @@ void ieee80211_send_nullfunc(struct ieee80211_local *local, if (local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS) IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; - if (ifmgd->flags & (IEEE80211_STA_BEACON_POLL | - IEEE80211_STA_CONNECTION_POLL)) + if (ifmgd->flags & IEEE80211_STA_CONNECTION_POLL) IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_USE_MINRATE; ieee80211_tx_skb(sdata, skb); @@ -937,6 +940,8 @@ static void ieee80211_chswitch_work(struct work_struct *work) container_of(work, struct ieee80211_sub_if_data, u.mgd.chswitch_work); struct ieee80211_local *local = sdata->local; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + u32 changed = 0; + int ret; if (!ieee80211_sdata_running(sdata)) return; @@ -945,25 +950,48 @@ static void ieee80211_chswitch_work(struct work_struct *work) if (!ifmgd->associated) goto out; - local->_oper_chandef = local->csa_chandef; + mutex_lock(&local->mtx); + ret = ieee80211_vif_change_channel(sdata, &changed); + mutex_unlock(&local->mtx); + if (ret) { + sdata_info(sdata, + "vif channel switch failed, disconnecting\n"); + ieee80211_queue_work(&sdata->local->hw, + &ifmgd->csa_connection_drop_work); + goto out; + } - if (!local->ops->channel_switch) { - /* call "hw_config" only if doing sw channel switch */ - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); - } else { - /* update the device channel directly */ - local->hw.conf.chandef = local->_oper_chandef; + if (!local->use_chanctx) { + local->_oper_chandef = sdata->csa_chandef; + /* Call "hw_config" only if doing sw channel switch. + * Otherwise update the channel directly + */ + if (!local->ops->channel_switch) + ieee80211_hw_config(local, 0); + else + local->hw.conf.chandef = local->_oper_chandef; } /* XXX: shouldn't really modify cfg80211-owned data! */ - ifmgd->associated->channel = local->_oper_chandef.chan; + ifmgd->associated->channel = sdata->csa_chandef.chan; + ieee80211_bss_info_change_notify(sdata, changed); + + mutex_lock(&local->mtx); + sdata->vif.csa_active = false; /* XXX: wait for a beacon first? */ - ieee80211_wake_queues_by_reason(&local->hw, + if (!ieee80211_csa_needs_block_tx(local)) + ieee80211_wake_queues_by_reason(&local->hw, IEEE80211_MAX_QUEUE_MAP, IEEE80211_QUEUE_STOP_REASON_CSA); - out: + mutex_unlock(&local->mtx); + ifmgd->flags &= ~IEEE80211_STA_CSA_RECEIVED; + + ieee80211_sta_reset_beacon_monitor(sdata); + ieee80211_sta_reset_conn_monitor(sdata); + +out: sdata_unlock(sdata); } @@ -1000,20 +1028,10 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata, struct ieee80211_local *local = sdata->local; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct cfg80211_bss *cbss = ifmgd->associated; - struct ieee80211_bss *bss; struct ieee80211_chanctx *chanctx; - enum ieee80211_band new_band; - int new_freq; - u8 new_chan_no; - u8 count; - u8 mode; - struct ieee80211_channel *new_chan; - struct cfg80211_chan_def new_chandef = {}; - struct cfg80211_chan_def new_vht_chandef = {}; - const struct ieee80211_sec_chan_offs_ie *sec_chan_offs; - const struct ieee80211_wide_bw_chansw_ie *wide_bw_chansw_ie; - const struct ieee80211_ht_operation *ht_oper; - int secondary_channel_offset = -1; + enum ieee80211_band current_band; + struct ieee80211_csa_ie csa_ie; + int res; sdata_assert_lock(sdata); @@ -1027,187 +1045,58 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata, if (ifmgd->flags & IEEE80211_STA_CSA_RECEIVED) return; - sec_chan_offs = elems->sec_chan_offs; - wide_bw_chansw_ie = elems->wide_bw_chansw_ie; - ht_oper = elems->ht_operation; - - if (ifmgd->flags & (IEEE80211_STA_DISABLE_HT | - IEEE80211_STA_DISABLE_40MHZ)) { - sec_chan_offs = NULL; - wide_bw_chansw_ie = NULL; - /* only used for bandwidth here */ - ht_oper = NULL; - } - - if (ifmgd->flags & IEEE80211_STA_DISABLE_VHT) - wide_bw_chansw_ie = NULL; - - if (elems->ext_chansw_ie) { - if (!ieee80211_operating_class_to_band( - elems->ext_chansw_ie->new_operating_class, - &new_band)) { - sdata_info(sdata, - "cannot understand ECSA IE operating class %d, disconnecting\n", - elems->ext_chansw_ie->new_operating_class); - ieee80211_queue_work(&local->hw, - &ifmgd->csa_connection_drop_work); - } - new_chan_no = elems->ext_chansw_ie->new_ch_num; - count = elems->ext_chansw_ie->count; - mode = elems->ext_chansw_ie->mode; - } else if (elems->ch_switch_ie) { - new_band = cbss->channel->band; - new_chan_no = elems->ch_switch_ie->new_ch_num; - count = elems->ch_switch_ie->count; - mode = elems->ch_switch_ie->mode; - } else { - /* nothing here we understand */ + current_band = cbss->channel->band; + memset(&csa_ie, 0, sizeof(csa_ie)); + res = ieee80211_parse_ch_switch_ie(sdata, elems, beacon, current_band, + ifmgd->flags, + ifmgd->associated->bssid, &csa_ie); + if (res < 0) + ieee80211_queue_work(&local->hw, + &ifmgd->csa_connection_drop_work); + if (res) return; - } - - bss = (void *)cbss->priv; - new_freq = ieee80211_channel_to_frequency(new_chan_no, new_band); - new_chan = ieee80211_get_channel(sdata->local->hw.wiphy, new_freq); - if (!new_chan || new_chan->flags & IEEE80211_CHAN_DISABLED) { + if (!cfg80211_chandef_usable(local->hw.wiphy, &csa_ie.chandef, + IEEE80211_CHAN_DISABLED)) { sdata_info(sdata, - "AP %pM switches to unsupported channel (%d MHz), disconnecting\n", - ifmgd->associated->bssid, new_freq); + "AP %pM switches to unsupported channel (%d MHz, width:%d, CF1/2: %d/%d MHz), disconnecting\n", + ifmgd->associated->bssid, + csa_ie.chandef.chan->center_freq, + csa_ie.chandef.width, csa_ie.chandef.center_freq1, + csa_ie.chandef.center_freq2); ieee80211_queue_work(&local->hw, &ifmgd->csa_connection_drop_work); return; } - if (!beacon && sec_chan_offs) { - secondary_channel_offset = sec_chan_offs->sec_chan_offs; - } else if (beacon && ht_oper) { - secondary_channel_offset = - ht_oper->ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET; - } else if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT)) { - /* - * If it's not a beacon, HT is enabled and the IE not present, - * it's 20 MHz, 802.11-2012 8.5.2.6: - * This element [the Secondary Channel Offset Element] is - * present when switching to a 40 MHz channel. It may be - * present when switching to a 20 MHz channel (in which - * case the secondary channel offset is set to SCN). - */ - secondary_channel_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; - } - - switch (secondary_channel_offset) { - default: - /* secondary_channel_offset was present but is invalid */ - case IEEE80211_HT_PARAM_CHA_SEC_NONE: - cfg80211_chandef_create(&new_chandef, new_chan, - NL80211_CHAN_HT20); - break; - case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: - cfg80211_chandef_create(&new_chandef, new_chan, - NL80211_CHAN_HT40PLUS); - break; - case IEEE80211_HT_PARAM_CHA_SEC_BELOW: - cfg80211_chandef_create(&new_chandef, new_chan, - NL80211_CHAN_HT40MINUS); - break; - case -1: - cfg80211_chandef_create(&new_chandef, new_chan, - NL80211_CHAN_NO_HT); - /* keep width for 5/10 MHz channels */ - switch (sdata->vif.bss_conf.chandef.width) { - case NL80211_CHAN_WIDTH_5: - case NL80211_CHAN_WIDTH_10: - new_chandef.width = sdata->vif.bss_conf.chandef.width; - break; - default: - break; - } - break; - } + ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED; - if (wide_bw_chansw_ie) { - new_vht_chandef.chan = new_chan; - new_vht_chandef.center_freq1 = - ieee80211_channel_to_frequency( - wide_bw_chansw_ie->new_center_freq_seg0, - new_band); + mutex_lock(&local->chanctx_mtx); + if (local->use_chanctx) { + u32 num_chanctx = 0; + list_for_each_entry(chanctx, &local->chanctx_list, list) + num_chanctx++; - switch (wide_bw_chansw_ie->new_channel_width) { - default: - /* hmmm, ignore VHT and use HT if present */ - case IEEE80211_VHT_CHANWIDTH_USE_HT: - new_vht_chandef.chan = NULL; - break; - case IEEE80211_VHT_CHANWIDTH_80MHZ: - new_vht_chandef.width = NL80211_CHAN_WIDTH_80; - break; - case IEEE80211_VHT_CHANWIDTH_160MHZ: - new_vht_chandef.width = NL80211_CHAN_WIDTH_160; - break; - case IEEE80211_VHT_CHANWIDTH_80P80MHZ: - /* field is otherwise reserved */ - new_vht_chandef.center_freq2 = - ieee80211_channel_to_frequency( - wide_bw_chansw_ie->new_center_freq_seg1, - new_band); - new_vht_chandef.width = NL80211_CHAN_WIDTH_80P80; - break; - } - if (ifmgd->flags & IEEE80211_STA_DISABLE_80P80MHZ && - new_vht_chandef.width == NL80211_CHAN_WIDTH_80P80) - chandef_downgrade(&new_vht_chandef); - if (ifmgd->flags & IEEE80211_STA_DISABLE_160MHZ && - new_vht_chandef.width == NL80211_CHAN_WIDTH_160) - chandef_downgrade(&new_vht_chandef); - if (ifmgd->flags & IEEE80211_STA_DISABLE_40MHZ && - new_vht_chandef.width > NL80211_CHAN_WIDTH_20) - chandef_downgrade(&new_vht_chandef); - } - - /* if VHT data is there validate & use it */ - if (new_vht_chandef.chan) { - if (!cfg80211_chandef_compatible(&new_vht_chandef, - &new_chandef)) { + if (num_chanctx > 1 || + !(local->hw.flags & IEEE80211_HW_CHANCTX_STA_CSA)) { sdata_info(sdata, - "AP %pM CSA has inconsistent channel data, disconnecting\n", - ifmgd->associated->bssid); + "not handling chan-switch with channel contexts\n"); ieee80211_queue_work(&local->hw, &ifmgd->csa_connection_drop_work); + mutex_unlock(&local->chanctx_mtx); return; } - new_chandef = new_vht_chandef; } - if (!cfg80211_chandef_usable(local->hw.wiphy, &new_chandef, - IEEE80211_CHAN_DISABLED)) { - sdata_info(sdata, - "AP %pM switches to unsupported channel (%d MHz, width:%d, CF1/2: %d/%d MHz), disconnecting\n", - ifmgd->associated->bssid, new_freq, - new_chandef.width, new_chandef.center_freq1, - new_chandef.center_freq2); - ieee80211_queue_work(&local->hw, - &ifmgd->csa_connection_drop_work); - return; - } - - ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED; - - if (local->use_chanctx) { - sdata_info(sdata, - "not handling channel switch with channel contexts\n"); + if (WARN_ON(!rcu_access_pointer(sdata->vif.chanctx_conf))) { ieee80211_queue_work(&local->hw, &ifmgd->csa_connection_drop_work); - return; - } - - mutex_lock(&local->chanctx_mtx); - if (WARN_ON(!rcu_access_pointer(sdata->vif.chanctx_conf))) { mutex_unlock(&local->chanctx_mtx); return; } chanctx = container_of(rcu_access_pointer(sdata->vif.chanctx_conf), struct ieee80211_chanctx, conf); - if (chanctx->refcount > 1) { + if (ieee80211_chanctx_refcount(local, chanctx) > 1) { sdata_info(sdata, "channel switch with multiple interfaces on the same channel, disconnecting\n"); ieee80211_queue_work(&local->hw, @@ -1217,20 +1106,25 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata, } mutex_unlock(&local->chanctx_mtx); - local->csa_chandef = new_chandef; + sdata->csa_chandef = csa_ie.chandef; + + mutex_lock(&local->mtx); + sdata->vif.csa_active = true; + sdata->csa_block_tx = csa_ie.mode; - if (mode) + if (sdata->csa_block_tx) ieee80211_stop_queues_by_reason(&local->hw, - IEEE80211_MAX_QUEUE_MAP, - IEEE80211_QUEUE_STOP_REASON_CSA); + IEEE80211_MAX_QUEUE_MAP, + IEEE80211_QUEUE_STOP_REASON_CSA); + mutex_unlock(&local->mtx); if (local->ops->channel_switch) { /* use driver's channel switch callback */ struct ieee80211_channel_switch ch_switch = { .timestamp = timestamp, - .block_tx = mode, - .chandef = new_chandef, - .count = count, + .block_tx = csa_ie.mode, + .chandef = csa_ie.chandef, + .count = csa_ie.count, }; drv_channel_switch(local, &ch_switch); @@ -1238,11 +1132,11 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata, } /* channel switch handled in software */ - if (count <= 1) + if (csa_ie.count <= 1) ieee80211_queue_work(&local->hw, &ifmgd->chswitch_work); else mod_timer(&ifmgd->chswitch_timer, - TU_TO_EXP_TIME(count * cbss->beacon_interval)); + TU_TO_EXP_TIME(csa_ie.count * cbss->beacon_interval)); } static u32 ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata, @@ -1374,8 +1268,7 @@ static bool ieee80211_powersave_allowed(struct ieee80211_sub_if_data *sdata) if (!mgd->associated) return false; - if (mgd->flags & (IEEE80211_STA_BEACON_POLL | - IEEE80211_STA_CONNECTION_POLL)) + if (mgd->flags & IEEE80211_STA_CONNECTION_POLL) return false; if (!mgd->have_beacon) @@ -1581,10 +1474,16 @@ void ieee80211_dfs_cac_timer_work(struct work_struct *work) struct ieee80211_sub_if_data *sdata = container_of(delayed_work, struct ieee80211_sub_if_data, dfs_cac_timer_work); + struct cfg80211_chan_def chandef = sdata->vif.bss_conf.chandef; - ieee80211_vif_release_channel(sdata); - - cfg80211_cac_event(sdata->dev, NL80211_RADAR_CAC_FINISHED, GFP_KERNEL); + mutex_lock(&sdata->local->mtx); + if (sdata->wdev.cac_started) { + ieee80211_vif_release_channel(sdata); + cfg80211_cac_event(sdata->dev, &chandef, + NL80211_RADAR_CAC_FINISHED, + GFP_KERNEL); + } + mutex_unlock(&sdata->local->mtx); } /* MLME */ @@ -1691,8 +1590,7 @@ static void __ieee80211_stop_poll(struct ieee80211_sub_if_data *sdata) { lockdep_assert_held(&sdata->local->mtx); - sdata->u.mgd.flags &= ~(IEEE80211_STA_CONNECTION_POLL | - IEEE80211_STA_BEACON_POLL); + sdata->u.mgd.flags &= ~IEEE80211_STA_CONNECTION_POLL; ieee80211_run_deferred_scan(sdata->local); } @@ -1879,7 +1777,7 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata, memset(ifmgd->bssid, 0, ETH_ALEN); /* remove AP and TDLS peers */ - sta_info_flush_defer(sdata); + sta_info_flush(sdata); /* finally reset all BSS / config parameters */ changed |= ieee80211_reset_erp_info(sdata); @@ -1928,7 +1826,17 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata, ifmgd->have_beacon = false; ifmgd->flags = 0; + mutex_lock(&local->mtx); ieee80211_vif_release_channel(sdata); + + sdata->vif.csa_active = false; + if (!ieee80211_csa_needs_block_tx(local)) + ieee80211_wake_queues_by_reason(&local->hw, + IEEE80211_MAX_QUEUE_MAP, + IEEE80211_QUEUE_STOP_REASON_CSA); + mutex_unlock(&local->mtx); + + sdata->encrypt_headroom = IEEE80211_ENCRYPT_HEADROOM; } void ieee80211_sta_rx_notify(struct ieee80211_sub_if_data *sdata, @@ -1954,11 +1862,8 @@ static void ieee80211_reset_ap_probe(struct ieee80211_sub_if_data *sdata) struct ieee80211_local *local = sdata->local; mutex_lock(&local->mtx); - if (!(ifmgd->flags & (IEEE80211_STA_BEACON_POLL | - IEEE80211_STA_CONNECTION_POLL))) { - mutex_unlock(&local->mtx); - return; - } + if (!(ifmgd->flags & IEEE80211_STA_CONNECTION_POLL)) + goto out; __ieee80211_stop_poll(sdata); @@ -2094,14 +1999,10 @@ static void ieee80211_mgd_probe_ap(struct ieee80211_sub_if_data *sdata, * because otherwise we would reset the timer every time and * never check whether we received a probe response! */ - if (ifmgd->flags & (IEEE80211_STA_BEACON_POLL | - IEEE80211_STA_CONNECTION_POLL)) + if (ifmgd->flags & IEEE80211_STA_CONNECTION_POLL) already = true; - if (beacon) - ifmgd->flags |= IEEE80211_STA_BEACON_POLL; - else - ifmgd->flags |= IEEE80211_STA_CONNECTION_POLL; + ifmgd->flags |= IEEE80211_STA_CONNECTION_POLL; mutex_unlock(&sdata->local->mtx); @@ -2161,6 +2062,7 @@ EXPORT_SYMBOL(ieee80211_ap_probereq_get); static void __ieee80211_disconnect(struct ieee80211_sub_if_data *sdata) { + struct ieee80211_local *local = sdata->local; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; u8 frame_buf[IEEE80211_DEAUTH_FRAME_LEN]; @@ -2174,9 +2076,14 @@ static void __ieee80211_disconnect(struct ieee80211_sub_if_data *sdata) WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY, true, frame_buf); ifmgd->flags &= ~IEEE80211_STA_CSA_RECEIVED; - ieee80211_wake_queues_by_reason(&sdata->local->hw, + + mutex_lock(&local->mtx); + sdata->vif.csa_active = false; + if (!ieee80211_csa_needs_block_tx(local)) + ieee80211_wake_queues_by_reason(&local->hw, IEEE80211_MAX_QUEUE_MAP, IEEE80211_QUEUE_STOP_REASON_CSA); + mutex_unlock(&local->mtx); cfg80211_tx_mlme_mgmt(sdata->dev, frame_buf, IEEE80211_DEAUTH_FRAME_LEN); @@ -2255,7 +2162,9 @@ static void ieee80211_destroy_auth_data(struct ieee80211_sub_if_data *sdata, memset(sdata->u.mgd.bssid, 0, ETH_ALEN); ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BSSID); sdata->u.mgd.flags = 0; + mutex_lock(&sdata->local->mtx); ieee80211_vif_release_channel(sdata); + mutex_unlock(&sdata->local->mtx); } cfg80211_put_bss(sdata->local->hw.wiphy, auth_data->bss); @@ -2385,6 +2294,62 @@ static void ieee80211_rx_mgmt_auth(struct ieee80211_sub_if_data *sdata, /* ignore frame -- wait for timeout */ } +#define case_WLAN(type) \ + case WLAN_REASON_##type: return #type + +static const char *ieee80211_get_reason_code_string(u16 reason_code) +{ + switch (reason_code) { + case_WLAN(UNSPECIFIED); + case_WLAN(PREV_AUTH_NOT_VALID); + case_WLAN(DEAUTH_LEAVING); + case_WLAN(DISASSOC_DUE_TO_INACTIVITY); + case_WLAN(DISASSOC_AP_BUSY); + case_WLAN(CLASS2_FRAME_FROM_NONAUTH_STA); + case_WLAN(CLASS3_FRAME_FROM_NONASSOC_STA); + case_WLAN(DISASSOC_STA_HAS_LEFT); + case_WLAN(STA_REQ_ASSOC_WITHOUT_AUTH); + case_WLAN(DISASSOC_BAD_POWER); + case_WLAN(DISASSOC_BAD_SUPP_CHAN); + case_WLAN(INVALID_IE); + case_WLAN(MIC_FAILURE); + case_WLAN(4WAY_HANDSHAKE_TIMEOUT); + case_WLAN(GROUP_KEY_HANDSHAKE_TIMEOUT); + case_WLAN(IE_DIFFERENT); + case_WLAN(INVALID_GROUP_CIPHER); + case_WLAN(INVALID_PAIRWISE_CIPHER); + case_WLAN(INVALID_AKMP); + case_WLAN(UNSUPP_RSN_VERSION); + case_WLAN(INVALID_RSN_IE_CAP); + case_WLAN(IEEE8021X_FAILED); + case_WLAN(CIPHER_SUITE_REJECTED); + case_WLAN(DISASSOC_UNSPECIFIED_QOS); + case_WLAN(DISASSOC_QAP_NO_BANDWIDTH); + case_WLAN(DISASSOC_LOW_ACK); + case_WLAN(DISASSOC_QAP_EXCEED_TXOP); + case_WLAN(QSTA_LEAVE_QBSS); + case_WLAN(QSTA_NOT_USE); + case_WLAN(QSTA_REQUIRE_SETUP); + case_WLAN(QSTA_TIMEOUT); + case_WLAN(QSTA_CIPHER_NOT_SUPP); + case_WLAN(MESH_PEER_CANCELED); + case_WLAN(MESH_MAX_PEERS); + case_WLAN(MESH_CONFIG); + case_WLAN(MESH_CLOSE); + case_WLAN(MESH_MAX_RETRIES); + case_WLAN(MESH_CONFIRM_TIMEOUT); + case_WLAN(MESH_INVALID_GTK); + case_WLAN(MESH_INCONSISTENT_PARAM); + case_WLAN(MESH_INVALID_SECURITY); + case_WLAN(MESH_PATH_ERROR); + case_WLAN(MESH_PATH_NOFORWARD); + case_WLAN(MESH_PATH_DEST_UNREACHABLE); + case_WLAN(MAC_EXISTS_IN_MBSS); + case_WLAN(MESH_CHAN_REGULATORY); + case_WLAN(MESH_CHAN); + default: return "<unknown>"; + } +} static void ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, size_t len) @@ -2406,8 +2371,8 @@ static void ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata, reason_code = le16_to_cpu(mgmt->u.deauth.reason_code); - sdata_info(sdata, "deauthenticated from %pM (Reason: %u)\n", - bssid, reason_code); + sdata_info(sdata, "deauthenticated from %pM (Reason: %u=%s)\n", + bssid, reason_code, ieee80211_get_reason_code_string(reason_code)); ieee80211_set_disassoc(sdata, 0, 0, false, NULL); @@ -2504,7 +2469,9 @@ static void ieee80211_destroy_assoc_data(struct ieee80211_sub_if_data *sdata, memset(sdata->u.mgd.bssid, 0, ETH_ALEN); ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BSSID); sdata->u.mgd.flags = 0; + mutex_lock(&sdata->local->mtx); ieee80211_vif_release_channel(sdata); + mutex_unlock(&sdata->local->mtx); } kfree(assoc_data); @@ -2717,7 +2684,7 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata, */ ifmgd->wmm_last_param_set = -1; - if (elems.wmm_param) + if (!(ifmgd->flags & IEEE80211_STA_DISABLE_WMM) && elems.wmm_param) ieee80211_sta_wmm_params(local, sdata, elems.wmm_param, elems.wmm_param_len); else @@ -2838,28 +2805,20 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata, struct ieee802_11_elems *elems) { struct ieee80211_local *local = sdata->local; - int freq; struct ieee80211_bss *bss; struct ieee80211_channel *channel; sdata_assert_lock(sdata); - if (elems->ds_params) - freq = ieee80211_channel_to_frequency(elems->ds_params[0], - rx_status->band); - else - freq = rx_status->freq; - - channel = ieee80211_get_channel(local->hw.wiphy, freq); - - if (!channel || channel->flags & IEEE80211_CHAN_DISABLED) + channel = ieee80211_get_channel(local->hw.wiphy, rx_status->freq); + if (!channel) return; bss = ieee80211_bss_info_update(local, rx_status, mgmt, len, elems, channel); if (bss) { - ieee80211_rx_bss_put(local, bss); sdata->vif.bss_conf.beacon_rate = bss->beacon_rate; + ieee80211_rx_bss_put(local, bss); } } @@ -3061,17 +3020,10 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata, } } - if (ifmgd->flags & IEEE80211_STA_BEACON_POLL) { + if (ifmgd->flags & IEEE80211_STA_CONNECTION_POLL) { mlme_dbg_ratelimited(sdata, "cancelling AP probe due to a received beacon\n"); - mutex_lock(&local->mtx); - ifmgd->flags &= ~IEEE80211_STA_BEACON_POLL; - ieee80211_run_deferred_scan(local); - mutex_unlock(&local->mtx); - - mutex_lock(&local->iflist_mtx); - ieee80211_recalc_ps(local, -1); - mutex_unlock(&local->iflist_mtx); + ieee80211_reset_ap_probe(sdata); } /* @@ -3152,7 +3104,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata, ieee80211_sta_process_chanswitch(sdata, rx_status->mactime, &elems, true); - if (ieee80211_sta_wmm_params(local, sdata, elems.wmm_param, + if (!(ifmgd->flags & IEEE80211_STA_DISABLE_WMM) && + ieee80211_sta_wmm_params(local, sdata, elems.wmm_param, elems.wmm_param_len)) changed |= BSS_CHANGED_QOS; @@ -3543,8 +3496,7 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata) } else if (ifmgd->assoc_data && ifmgd->assoc_data->timeout_started) run_again(sdata, ifmgd->assoc_data->timeout); - if (ifmgd->flags & (IEEE80211_STA_BEACON_POLL | - IEEE80211_STA_CONNECTION_POLL) && + if (ifmgd->flags & IEEE80211_STA_CONNECTION_POLL && ifmgd->associated) { u8 bssid[ETH_ALEN]; int max_tries; @@ -3616,6 +3568,9 @@ static void ieee80211_sta_bcn_mon_timer(unsigned long data) if (local->quiescing) return; + if (sdata->vif.csa_active) + return; + sdata->u.mgd.connection_loss = false; ieee80211_queue_work(&sdata->local->hw, &sdata->u.mgd.beacon_connection_loss_work); @@ -3631,6 +3586,9 @@ static void ieee80211_sta_conn_mon_timer(unsigned long data) if (local->quiescing) return; + if (sdata->vif.csa_active) + return; + ieee80211_queue_work(&local->hw, &ifmgd->monitor_work); } @@ -3661,6 +3619,38 @@ static void ieee80211_restart_sta_timer(struct ieee80211_sub_if_data *sdata) } #ifdef CONFIG_PM +void ieee80211_mgd_quiesce(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + u8 frame_buf[IEEE80211_DEAUTH_FRAME_LEN]; + + sdata_lock(sdata); + + if (ifmgd->auth_data || ifmgd->assoc_data) { + const u8 *bssid = ifmgd->auth_data ? + ifmgd->auth_data->bss->bssid : + ifmgd->assoc_data->bss->bssid; + + /* + * If we are trying to authenticate / associate while suspending, + * cfg80211 won't know and won't actually abort those attempts, + * thus we need to do that ourselves. + */ + ieee80211_send_deauth_disassoc(sdata, bssid, + IEEE80211_STYPE_DEAUTH, + WLAN_REASON_DEAUTH_LEAVING, + false, frame_buf); + if (ifmgd->assoc_data) + ieee80211_destroy_assoc_data(sdata, false); + if (ifmgd->auth_data) + ieee80211_destroy_auth_data(sdata, false); + cfg80211_tx_mlme_mgmt(sdata->dev, frame_buf, + IEEE80211_DEAUTH_FRAME_LEN); + } + + sdata_unlock(sdata); +} + void ieee80211_sta_restart(struct ieee80211_sub_if_data *sdata) { struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; @@ -3697,7 +3687,7 @@ void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata) ieee80211_beacon_connection_loss_work); INIT_WORK(&ifmgd->csa_connection_drop_work, ieee80211_csa_connection_drop_work); - INIT_WORK(&ifmgd->request_smps_work, ieee80211_request_smps_work); + INIT_WORK(&ifmgd->request_smps_work, ieee80211_request_smps_mgd_work); setup_timer(&ifmgd->timer, ieee80211_sta_timer, (unsigned long) sdata); setup_timer(&ifmgd->bcn_mon_timer, ieee80211_sta_bcn_mon_timer, @@ -3745,7 +3735,7 @@ int ieee80211_max_network_latency(struct notifier_block *nb, ieee80211_recalc_ps(local, latency_usec); mutex_unlock(&local->iflist_mtx); - return 0; + return NOTIFY_OK; } static u8 ieee80211_ht_vht_rx_chains(struct ieee80211_sub_if_data *sdata, @@ -3862,6 +3852,7 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, /* will change later if needed */ sdata->smps_mode = IEEE80211_SMPS_OFF; + mutex_lock(&local->mtx); /* * If this fails (possibly due to channel context sharing * on incompatible channels, e.g. 80+80 and 160 sharing the @@ -3873,13 +3864,15 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, /* don't downgrade for 5 and 10 MHz channels, though. */ if (chandef.width == NL80211_CHAN_WIDTH_5 || chandef.width == NL80211_CHAN_WIDTH_10) - return ret; + goto out; while (ret && chandef.width != NL80211_CHAN_WIDTH_20_NOHT) { - ifmgd->flags |= chandef_downgrade(&chandef); + ifmgd->flags |= ieee80211_chandef_downgrade(&chandef); ret = ieee80211_vif_use_channel(sdata, &chandef, IEEE80211_CHANCTX_SHARED); } + out: + mutex_unlock(&local->mtx); return ret; } @@ -3930,6 +3923,7 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata, chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); if (WARN_ON(!chanctx_conf)) { rcu_read_unlock(); + sta_info_free(local, new_sta); return -EINVAL; } rate_flags = ieee80211_chandef_rate_flags(&chanctx_conf->def); @@ -4135,6 +4129,44 @@ int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata, return err; } +static bool ieee80211_usable_wmm_params(struct ieee80211_sub_if_data *sdata, + const u8 *wmm_param, int len) +{ + const u8 *pos; + size_t left; + + if (len < 8) + return false; + + if (wmm_param[5] != 1 /* version */) + return false; + + pos = wmm_param + 8; + left = len - 8; + + for (; left >= 4; left -= 4, pos += 4) { + u8 aifsn = pos[0] & 0x0f; + u8 ecwmin = pos[1] & 0x0f; + u8 ecwmax = (pos[1] & 0xf0) >> 4; + int aci = (pos[0] >> 5) & 0x03; + + if (aifsn < 2) { + sdata_info(sdata, + "AP has invalid WMM params (AIFSN=%d for ACI %d), disabling WMM\n", + aifsn, aci); + return false; + } + if (ecwmin > ecwmax) { + sdata_info(sdata, + "AP has invalid WMM params (ECWmin/max=%d/%d for ACI %d), disabling WMM\n", + ecwmin, ecwmax, aci); + return false; + } + } + + return true; +} + int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, struct cfg80211_assoc_request *req) { @@ -4192,9 +4224,45 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, } /* prepare assoc data */ - + ifmgd->beacon_crc_valid = false; + assoc_data->wmm = bss->wmm_used && + (local->hw.queues >= IEEE80211_NUM_ACS); + if (assoc_data->wmm) { + /* try to check validity of WMM params IE */ + const struct cfg80211_bss_ies *ies; + const u8 *wp, *start, *end; + + rcu_read_lock(); + ies = rcu_dereference(req->bss->ies); + start = ies->data; + end = start + ies->len; + + while (true) { + wp = cfg80211_find_vendor_ie( + WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WMM, + start, end - start); + if (!wp) + break; + start = wp + wp[1] + 2; + /* if this IE is too short, try the next */ + if (wp[1] <= 4) + continue; + /* if this IE is WMM params, we found what we wanted */ + if (wp[6] == 1) + break; + } + + if (!wp || !ieee80211_usable_wmm_params(sdata, wp + 2, + wp[1] - 2)) { + assoc_data->wmm = false; + ifmgd->flags |= IEEE80211_STA_DISABLE_WMM; + } + rcu_read_unlock(); + } + /* * IEEE802.11n does not allow TKIP/WEP as pairwise ciphers in HT mode. * We still associate in non-HT mode (11a/b/g) if any one of these @@ -4224,18 +4292,22 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, /* Also disable HT if we don't support it or the AP doesn't use WMM */ sband = local->hw.wiphy->bands[req->bss->channel->band]; if (!sband->ht_cap.ht_supported || - local->hw.queues < IEEE80211_NUM_ACS || !bss->wmm_used) { + local->hw.queues < IEEE80211_NUM_ACS || !bss->wmm_used || + ifmgd->flags & IEEE80211_STA_DISABLE_WMM) { ifmgd->flags |= IEEE80211_STA_DISABLE_HT; - if (!bss->wmm_used) + if (!bss->wmm_used && + !(ifmgd->flags & IEEE80211_STA_DISABLE_WMM)) netdev_info(sdata->dev, "disabling HT as WMM/QoS is not supported by the AP\n"); } /* disable VHT if we don't support it or the AP doesn't use WMM */ if (!sband->vht_cap.vht_supported || - local->hw.queues < IEEE80211_NUM_ACS || !bss->wmm_used) { + local->hw.queues < IEEE80211_NUM_ACS || !bss->wmm_used || + ifmgd->flags & IEEE80211_STA_DISABLE_WMM) { ifmgd->flags |= IEEE80211_STA_DISABLE_VHT; - if (!bss->wmm_used) + if (!bss->wmm_used && + !(ifmgd->flags & IEEE80211_STA_DISABLE_WMM)) netdev_info(sdata->dev, "disabling VHT as WMM/QoS is not supported by the AP\n"); } @@ -4264,8 +4336,6 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, sdata->smps_mode = ifmgd->req_smps; assoc_data->capability = req->bss->capability; - assoc_data->wmm = bss->wmm_used && - (local->hw.queues >= IEEE80211_NUM_ACS); assoc_data->supp_rates = bss->supp_rates; assoc_data->supp_rates_len = bss->supp_rates_len; @@ -4312,6 +4382,8 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, sdata->control_port_protocol = req->crypto.control_port_ethertype; sdata->control_port_no_encrypt = req->crypto.control_port_no_encrypt; + sdata->encrypt_headroom = ieee80211_cs_headroom(local, &req->crypto, + sdata->vif.type); /* kick off associate process */ @@ -4397,37 +4469,41 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata, struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; u8 frame_buf[IEEE80211_DEAUTH_FRAME_LEN]; bool tx = !req->local_state_change; - bool report_frame = false; - sdata_info(sdata, - "deauthenticating from %pM by local choice (reason=%d)\n", - req->bssid, req->reason_code); + if (ifmgd->auth_data && + ether_addr_equal(ifmgd->auth_data->bss->bssid, req->bssid)) { + sdata_info(sdata, + "aborting authentication with %pM by local choice (Reason: %u=%s)\n", + req->bssid, req->reason_code, + ieee80211_get_reason_code_string(req->reason_code)); - if (ifmgd->auth_data) { drv_mgd_prepare_tx(sdata->local, sdata); ieee80211_send_deauth_disassoc(sdata, req->bssid, IEEE80211_STYPE_DEAUTH, req->reason_code, tx, frame_buf); ieee80211_destroy_auth_data(sdata, false); + cfg80211_tx_mlme_mgmt(sdata->dev, frame_buf, + IEEE80211_DEAUTH_FRAME_LEN); - report_frame = true; - goto out; + return 0; } if (ifmgd->associated && ether_addr_equal(ifmgd->associated->bssid, req->bssid)) { + sdata_info(sdata, + "deauthenticating from %pM by local choice (Reason: %u=%s)\n", + req->bssid, req->reason_code, + ieee80211_get_reason_code_string(req->reason_code)); + ieee80211_set_disassoc(sdata, IEEE80211_STYPE_DEAUTH, req->reason_code, tx, frame_buf); - report_frame = true; - } - - out: - if (report_frame) cfg80211_tx_mlme_mgmt(sdata->dev, frame_buf, IEEE80211_DEAUTH_FRAME_LEN); + return 0; + } - return 0; + return -ENOTCONN; } int ieee80211_mgd_disassoc(struct ieee80211_sub_if_data *sdata, @@ -4447,8 +4523,8 @@ int ieee80211_mgd_disassoc(struct ieee80211_sub_if_data *sdata, return -ENOLINK; sdata_info(sdata, - "disassociating from %pM by local choice (reason=%d)\n", - req->bss->bssid, req->reason_code); + "disassociating from %pM by local choice (Reason: %u=%s)\n", + req->bss->bssid, req->reason_code, ieee80211_get_reason_code_string(req->reason_code)); memcpy(bssid, req->bss->bssid, ETH_ALEN); ieee80211_set_disassoc(sdata, IEEE80211_STYPE_DISASSOC, diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index acd1f71adc0..7a17decd27f 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -333,7 +333,7 @@ void ieee80211_sw_roc_work(struct work_struct *work) container_of(work, struct ieee80211_roc_work, work.work); struct ieee80211_sub_if_data *sdata = roc->sdata; struct ieee80211_local *local = sdata->local; - bool started; + bool started, on_channel; mutex_lock(&local->mtx); @@ -354,13 +354,26 @@ void ieee80211_sw_roc_work(struct work_struct *work) if (!roc->started) { struct ieee80211_roc_work *dep; - /* start this ROC */ + WARN_ON(local->use_chanctx); + + /* If actually operating on the desired channel (with at least + * 20 MHz channel width) don't stop all the operations but still + * treat it as though the ROC operation started properly, so + * other ROC operations won't interfere with this one. + */ + roc->on_channel = roc->chan == local->_oper_chandef.chan && + local->_oper_chandef.width != NL80211_CHAN_WIDTH_5 && + local->_oper_chandef.width != NL80211_CHAN_WIDTH_10; - /* switch channel etc */ + /* start this ROC */ ieee80211_recalc_idle(local); - local->tmp_channel = roc->chan; - ieee80211_hw_config(local, 0); + if (!roc->on_channel) { + ieee80211_offchannel_stop_vifs(local); + + local->tmp_channel = roc->chan; + ieee80211_hw_config(local, 0); + } /* tell userspace or send frame */ ieee80211_handle_roc_started(roc); @@ -379,9 +392,10 @@ void ieee80211_sw_roc_work(struct work_struct *work) finish: list_del(&roc->list); started = roc->started; + on_channel = roc->on_channel; ieee80211_roc_notify_destroy(roc, !roc->abort); - if (started) { + if (started && !on_channel) { ieee80211_flush_queues(local, NULL); local->tmp_channel = NULL; @@ -394,6 +408,8 @@ void ieee80211_sw_roc_work(struct work_struct *work) if (started) ieee80211_start_next_roc(local); + else if (list_empty(&local->roc_list)) + ieee80211_run_deferred_scan(local); } out_unlock: diff --git a/net/mac80211/pm.c b/net/mac80211/pm.c index 34012620434..d478b880a0a 100644 --- a/net/mac80211/pm.c +++ b/net/mac80211/pm.c @@ -37,9 +37,8 @@ int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) IEEE80211_MAX_QUEUE_MAP, IEEE80211_QUEUE_STOP_REASON_SUSPEND); - /* flush out all packets and station cleanup call_rcu()s */ + /* flush out all packets */ synchronize_net(); - rcu_barrier(); ieee80211_flush_queues(local, NULL); @@ -101,10 +100,18 @@ int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) /* remove all interfaces that were created in the driver */ list_for_each_entry(sdata, &local->interfaces, list) { - if (!ieee80211_sdata_running(sdata) || - sdata->vif.type == NL80211_IFTYPE_AP_VLAN || - sdata->vif.type == NL80211_IFTYPE_MONITOR) + if (!ieee80211_sdata_running(sdata)) continue; + switch (sdata->vif.type) { + case NL80211_IFTYPE_AP_VLAN: + case NL80211_IFTYPE_MONITOR: + continue; + case NL80211_IFTYPE_STATION: + ieee80211_mgd_quiesce(sdata); + break; + default: + break; + } drv_remove_interface(local, sdata); } diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c index e126605cec6..8fdadfd94ba 100644 --- a/net/mac80211/rate.c +++ b/net/mac80211/rate.c @@ -10,15 +10,15 @@ #include <linux/kernel.h> #include <linux/rtnetlink.h> -#include <linux/slab.h> #include <linux/module.h> +#include <linux/slab.h> #include "rate.h" #include "ieee80211_i.h" #include "debugfs.h" struct rate_control_alg { struct list_head list; - struct rate_control_ops *ops; + const struct rate_control_ops *ops; }; static LIST_HEAD(rate_ctrl_algs); @@ -29,7 +29,7 @@ module_param(ieee80211_default_rc_algo, charp, 0644); MODULE_PARM_DESC(ieee80211_default_rc_algo, "Default rate control algorithm for mac80211 to use"); -int ieee80211_rate_control_register(struct rate_control_ops *ops) +int ieee80211_rate_control_register(const struct rate_control_ops *ops) { struct rate_control_alg *alg; @@ -60,7 +60,7 @@ int ieee80211_rate_control_register(struct rate_control_ops *ops) } EXPORT_SYMBOL(ieee80211_rate_control_register); -void ieee80211_rate_control_unregister(struct rate_control_ops *ops) +void ieee80211_rate_control_unregister(const struct rate_control_ops *ops) { struct rate_control_alg *alg; @@ -76,32 +76,31 @@ void ieee80211_rate_control_unregister(struct rate_control_ops *ops) } EXPORT_SYMBOL(ieee80211_rate_control_unregister); -static struct rate_control_ops * +static const struct rate_control_ops * ieee80211_try_rate_control_ops_get(const char *name) { struct rate_control_alg *alg; - struct rate_control_ops *ops = NULL; + const struct rate_control_ops *ops = NULL; if (!name) return NULL; mutex_lock(&rate_ctrl_mutex); list_for_each_entry(alg, &rate_ctrl_algs, list) { - if (!strcmp(alg->ops->name, name)) - if (try_module_get(alg->ops->module)) { - ops = alg->ops; - break; - } + if (!strcmp(alg->ops->name, name)) { + ops = alg->ops; + break; + } } mutex_unlock(&rate_ctrl_mutex); return ops; } /* Get the rate control algorithm. */ -static struct rate_control_ops * +static const struct rate_control_ops * ieee80211_rate_control_ops_get(const char *name) { - struct rate_control_ops *ops; + const struct rate_control_ops *ops; const char *alg_name; kparam_block_sysfs_write(ieee80211_default_rc_algo); @@ -111,10 +110,6 @@ ieee80211_rate_control_ops_get(const char *name) alg_name = name; ops = ieee80211_try_rate_control_ops_get(alg_name); - if (!ops) { - request_module("rc80211_%s", alg_name); - ops = ieee80211_try_rate_control_ops_get(alg_name); - } if (!ops && name) /* try default if specific alg requested but not found */ ops = ieee80211_try_rate_control_ops_get(ieee80211_default_rc_algo); @@ -127,11 +122,6 @@ ieee80211_rate_control_ops_get(const char *name) return ops; } -static void ieee80211_rate_control_ops_put(struct rate_control_ops *ops) -{ - module_put(ops->module); -} - #ifdef CONFIG_MAC80211_DEBUGFS static ssize_t rcname_read(struct file *file, char __user *userbuf, size_t count, loff_t *ppos) @@ -158,11 +148,11 @@ static struct rate_control_ref *rate_control_alloc(const char *name, ref = kmalloc(sizeof(struct rate_control_ref), GFP_KERNEL); if (!ref) - goto fail_ref; + return NULL; ref->local = local; ref->ops = ieee80211_rate_control_ops_get(name); if (!ref->ops) - goto fail_ops; + goto free; #ifdef CONFIG_MAC80211_DEBUGFS debugfsdir = debugfs_create_dir("rc", local->hw.wiphy->debugfsdir); @@ -172,14 +162,11 @@ static struct rate_control_ref *rate_control_alloc(const char *name, ref->priv = ref->ops->alloc(&local->hw, debugfsdir); if (!ref->priv) - goto fail_priv; + goto free; return ref; -fail_priv: - ieee80211_rate_control_ops_put(ref->ops); -fail_ops: +free: kfree(ref); -fail_ref: return NULL; } @@ -192,7 +179,6 @@ static void rate_control_free(struct rate_control_ref *ctrl_ref) ctrl_ref->local->debugfs.rcdir = NULL; #endif - ieee80211_rate_control_ops_put(ctrl_ref->ops); kfree(ctrl_ref); } @@ -235,7 +221,8 @@ static void rc_send_low_basicrate(s8 *idx, u32 basic_rates, static void __rate_control_send_low(struct ieee80211_hw *hw, struct ieee80211_supported_band *sband, struct ieee80211_sta *sta, - struct ieee80211_tx_info *info) + struct ieee80211_tx_info *info, + u32 rate_mask) { int i; u32 rate_flags = @@ -247,6 +234,12 @@ static void __rate_control_send_low(struct ieee80211_hw *hw, info->control.rates[0].idx = 0; for (i = 0; i < sband->n_bitrates; i++) { + if (!(rate_mask & BIT(i))) + continue; + + if ((rate_flags & sband->bitrates[i].flags) != rate_flags) + continue; + if (!rate_supported(sta, sband->band, i)) continue; @@ -274,7 +267,8 @@ bool rate_control_send_low(struct ieee80211_sta *pubsta, bool use_basicrate = false; if (!pubsta || !priv_sta || rc_no_data_or_no_ack_use_min(txrc)) { - __rate_control_send_low(txrc->hw, sband, pubsta, info); + __rate_control_send_low(txrc->hw, sband, pubsta, info, + txrc->rate_idx_mask); if (!pubsta && txrc->bss) { mcast_rate = txrc->bss_conf->mcast_rate[sband->band]; @@ -656,7 +650,8 @@ void ieee80211_get_tx_rates(struct ieee80211_vif *vif, rate_control_apply_mask(sdata, sta, sband, info, dest, max_rates); if (dest[0].idx < 0) - __rate_control_send_low(&sdata->local->hw, sband, sta, info); + __rate_control_send_low(&sdata->local->hw, sband, sta, info, + sdata->rc_rateidx_mask[info->band]); if (sta) rate_fixup_ratelist(vif, sband, info, dest, max_rates); diff --git a/net/mac80211/rate.h b/net/mac80211/rate.h index 5dedc56c94d..9aa2a1190a8 100644 --- a/net/mac80211/rate.h +++ b/net/mac80211/rate.h @@ -21,7 +21,7 @@ struct rate_control_ref { struct ieee80211_local *local; - struct rate_control_ops *ops; + const struct rate_control_ops *ops; void *priv; }; @@ -54,6 +54,8 @@ static inline void rate_control_rate_init(struct sta_info *sta) struct ieee80211_supported_band *sband; struct ieee80211_chanctx_conf *chanctx_conf; + ieee80211_sta_set_rx_nss(sta); + if (!ref) return; @@ -67,8 +69,6 @@ static inline void rate_control_rate_init(struct sta_info *sta) sband = local->hw.wiphy->bands[chanctx_conf->def.chan->band]; - ieee80211_sta_set_rx_nss(sta); - ref->ops->rate_init(ref->priv, sband, &chanctx_conf->def, ista, priv_sta); rcu_read_unlock(); @@ -144,8 +144,8 @@ void rate_control_deinitialize(struct ieee80211_local *local); /* Rate control algorithms */ #ifdef CONFIG_MAC80211_RC_PID -extern int rc80211_pid_init(void); -extern void rc80211_pid_exit(void); +int rc80211_pid_init(void); +void rc80211_pid_exit(void); #else static inline int rc80211_pid_init(void) { @@ -157,8 +157,8 @@ static inline void rc80211_pid_exit(void) #endif #ifdef CONFIG_MAC80211_RC_MINSTREL -extern int rc80211_minstrel_init(void); -extern void rc80211_minstrel_exit(void); +int rc80211_minstrel_init(void); +void rc80211_minstrel_exit(void); #else static inline int rc80211_minstrel_init(void) { @@ -170,8 +170,8 @@ static inline void rc80211_minstrel_exit(void) #endif #ifdef CONFIG_MAC80211_RC_MINSTREL_HT -extern int rc80211_minstrel_ht_init(void); -extern void rc80211_minstrel_ht_exit(void); +int rc80211_minstrel_ht_init(void); +void rc80211_minstrel_ht_exit(void); #else static inline int rc80211_minstrel_ht_init(void) { diff --git a/net/mac80211/rc80211_minstrel.c b/net/mac80211/rc80211_minstrel.c index 8b5f7ef7c0c..1c1469c36dc 100644 --- a/net/mac80211/rc80211_minstrel.c +++ b/net/mac80211/rc80211_minstrel.c @@ -135,7 +135,7 @@ minstrel_update_stats(struct minstrel_priv *mp, struct minstrel_sta_info *mi) u32 usecs; int i; - for (i=0; i < MAX_THR_RATES; i++) + for (i = 0; i < MAX_THR_RATES; i++) tmp_tp_rate[i] = 0; for (i = 0; i < mi->n_rates; i++) { @@ -190,7 +190,7 @@ minstrel_update_stats(struct minstrel_priv *mp, struct minstrel_sta_info *mi) * choose the maximum throughput rate as max_prob_rate * (2) if all success probabilities < 95%, the rate with * highest success probability is choosen as max_prob_rate */ - if (mr->probability >= MINSTREL_FRAC(95,100)) { + if (mr->probability >= MINSTREL_FRAC(95, 100)) { if (mr->cur_tp >= mi->r[tmp_prob_rate].cur_tp) tmp_prob_rate = i; } else { @@ -203,6 +203,15 @@ minstrel_update_stats(struct minstrel_priv *mp, struct minstrel_sta_info *mi) memcpy(mi->max_tp_rate, tmp_tp_rate, sizeof(mi->max_tp_rate)); mi->max_prob_rate = tmp_prob_rate; +#ifdef CONFIG_MAC80211_DEBUGFS + /* use fixed index if set */ + if (mp->fixed_rate_idx != -1) { + mi->max_tp_rate[0] = mp->fixed_rate_idx; + mi->max_tp_rate[1] = mp->fixed_rate_idx; + mi->max_prob_rate = mp->fixed_rate_idx; + } +#endif + /* Reset update timer */ mi->stats_update = jiffies; @@ -211,7 +220,7 @@ minstrel_update_stats(struct minstrel_priv *mp, struct minstrel_sta_info *mi) static void minstrel_tx_status(void *priv, struct ieee80211_supported_band *sband, - struct ieee80211_sta *sta, void *priv_sta, + struct ieee80211_sta *sta, void *priv_sta, struct sk_buff *skb) { struct minstrel_priv *mp = priv; @@ -251,7 +260,7 @@ minstrel_tx_status(void *priv, struct ieee80211_supported_band *sband, static inline unsigned int minstrel_get_retry_count(struct minstrel_rate *mr, - struct ieee80211_tx_info *info) + struct ieee80211_tx_info *info) { unsigned int retry = mr->adjusted_retry_count; @@ -310,6 +319,11 @@ minstrel_get_rate(void *priv, struct ieee80211_sta *sta, /* increase sum packet counter */ mi->packet_count++; +#ifdef CONFIG_MAC80211_DEBUGFS + if (mp->fixed_rate_idx != -1) + return; +#endif + delta = (mi->packet_count * sampling_ratio / 100) - (mi->sample_count + mi->sample_deferred / 2); @@ -408,10 +422,9 @@ init_sample_table(struct minstrel_sta_info *mi) memset(mi->sample_table, 0xff, SAMPLE_COLUMNS * mi->n_rates); for (col = 0; col < SAMPLE_COLUMNS; col++) { + prandom_bytes(rnd, sizeof(rnd)); for (i = 0; i < mi->n_rates; i++) { - get_random_bytes(rnd, sizeof(rnd)); new_idx = (i + rnd[i & 7]) % mi->n_rates; - while (SAMPLE_TBL(mi, new_idx, col) != 0xff) new_idx = (new_idx + 1) % mi->n_rates; @@ -644,7 +657,18 @@ minstrel_free(void *priv) kfree(priv); } -struct rate_control_ops mac80211_minstrel = { +static u32 minstrel_get_expected_throughput(void *priv_sta) +{ + struct minstrel_sta_info *mi = priv_sta; + int idx = mi->max_tp_rate[0]; + + /* convert pkt per sec in kbps (1200 is the average pkt size used for + * computing cur_tp + */ + return MINSTREL_TRUNC(mi->r[idx].cur_tp) * 1200 * 8 / 1024; +} + +const struct rate_control_ops mac80211_minstrel = { .name = "minstrel", .tx_status = minstrel_tx_status, .get_rate = minstrel_get_rate, @@ -657,6 +681,7 @@ struct rate_control_ops mac80211_minstrel = { .add_sta_debugfs = minstrel_add_sta_debugfs, .remove_sta_debugfs = minstrel_remove_sta_debugfs, #endif + .get_expected_throughput = minstrel_get_expected_throughput, }; int __init diff --git a/net/mac80211/rc80211_minstrel.h b/net/mac80211/rc80211_minstrel.h index f4301f4b2e4..046d1bd598a 100644 --- a/net/mac80211/rc80211_minstrel.h +++ b/net/mac80211/rc80211_minstrel.h @@ -123,7 +123,7 @@ struct minstrel_debugfs_info { char buf[]; }; -extern struct rate_control_ops mac80211_minstrel; +extern const struct rate_control_ops mac80211_minstrel; void minstrel_add_sta_debugfs(void *priv, void *priv_sta, struct dentry *dir); void minstrel_remove_sta_debugfs(void *priv, void *priv_sta); diff --git a/net/mac80211/rc80211_minstrel_ht.c b/net/mac80211/rc80211_minstrel_ht.c index 7c323f27ba2..85c1e74b771 100644 --- a/net/mac80211/rc80211_minstrel_ht.c +++ b/net/mac80211/rc80211_minstrel_ht.c @@ -22,7 +22,7 @@ #define MCS_NBITS (AVG_PKT_SIZE << 3) /* Number of symbols for a packet with (bps) bits per symbol */ -#define MCS_NSYMS(bps) ((MCS_NBITS + (bps) - 1) / (bps)) +#define MCS_NSYMS(bps) DIV_ROUND_UP(MCS_NBITS, (bps)) /* Transmission time (nanoseconds) for a packet containing (syms) symbols */ #define MCS_SYMBOL_TIME(sgi, syms) \ @@ -63,7 +63,7 @@ #define CCK_DURATION(_bitrate, _short, _len) \ (1000 * (10 /* SIFS */ + \ - (_short ? 72 + 24 : 144 + 48 ) + \ + (_short ? 72 + 24 : 144 + 48) + \ (8 * (_len + 4) * 10) / (_bitrate))) #define CCK_ACK_DURATION(_bitrate, _short) \ @@ -124,7 +124,7 @@ const struct mcs_group minstrel_mcs_groups[] = { #define MINSTREL_CCK_GROUP (ARRAY_SIZE(minstrel_mcs_groups) - 1) -static u8 sample_table[SAMPLE_COLUMNS][MCS_GROUP_RATES]; +static u8 sample_table[SAMPLE_COLUMNS][MCS_GROUP_RATES] __read_mostly; static void minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi); @@ -135,7 +135,7 @@ minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi); static int minstrel_ht_get_group_idx(struct ieee80211_tx_rate *rate) { - return GROUP_IDX((rate->idx / MCS_GROUP_RATES) + 1, + return GROUP_IDX((rate->idx / 8) + 1, !!(rate->flags & IEEE80211_TX_RC_SHORT_GI), !!(rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH)); } @@ -148,7 +148,7 @@ minstrel_ht_get_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, if (rate->flags & IEEE80211_TX_RC_MCS) { group = minstrel_ht_get_group_idx(rate); - idx = rate->idx % MCS_GROUP_RATES; + idx = rate->idx % 8; } else { group = MINSTREL_CCK_GROUP; @@ -226,8 +226,9 @@ minstrel_ht_calc_tp(struct minstrel_ht_sta *mi, int group, int rate) nsecs = 1000 * mi->overhead / MINSTREL_TRUNC(mi->avg_ampdu_len); nsecs += minstrel_mcs_groups[group].duration[rate]; - tp = 1000000 * ((mr->probability * 1000) / nsecs); + /* prob is scaled - see MINSTREL_FRAC above */ + tp = 1000000 * ((prob * 1000) / nsecs); mr->cur_tp = MINSTREL_TRUNC(tp); } @@ -277,13 +278,15 @@ minstrel_ht_update_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) if (!(mg->supported & BIT(i))) continue; + index = MCS_GROUP_RATES * group + i; + /* initialize rates selections starting indexes */ if (!mg_rates_valid) { mg->max_tp_rate = mg->max_tp_rate2 = mg->max_prob_rate = i; if (!mi_rates_valid) { mi->max_tp_rate = mi->max_tp_rate2 = - mi->max_prob_rate = i; + mi->max_prob_rate = index; mi_rates_valid = true; } mg_rates_valid = true; @@ -291,7 +294,6 @@ minstrel_ht_update_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) mr = &mg->rates[i]; mr->retry_updated = false; - index = MCS_GROUP_RATES * group + i; minstrel_calc_rate_ewma(mr); minstrel_ht_calc_tp(mi, group, i); @@ -365,6 +367,14 @@ minstrel_ht_update_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) } } +#ifdef CONFIG_MAC80211_DEBUGFS + /* use fixed index if set */ + if (mp->fixed_rate_idx != -1) { + mi->max_tp_rate = mp->fixed_rate_idx; + mi->max_tp_rate2 = mp->fixed_rate_idx; + mi->max_prob_rate = mp->fixed_rate_idx; + } +#endif mi->stats_update = jiffies; } @@ -628,8 +638,7 @@ minstrel_ht_set_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, idx = mp->cck_rates[index % ARRAY_SIZE(mp->cck_rates)]; flags = 0; } else { - idx = index % MCS_GROUP_RATES + - (group->streams - 1) * MCS_GROUP_RATES; + idx = index % MCS_GROUP_RATES + (group->streams - 1) * 8; flags = IEEE80211_TX_RC_MCS | group->flags; } @@ -693,12 +702,16 @@ minstrel_get_sample_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) if (!mi->sample_tries) return -1; - mg = &mi->groups[mi->sample_group]; + sample_group = mi->sample_group; + mg = &mi->groups[sample_group]; sample_idx = sample_table[mg->column][mg->index]; + minstrel_next_sample_idx(mi); + + if (!(mg->supported & BIT(sample_idx))) + return -1; + mr = &mg->rates[sample_idx]; - sample_group = mi->sample_group; sample_idx += sample_group * MCS_GROUP_RATES; - minstrel_next_sample_idx(mi); /* * Sampling might add some overhead (RTS, no aggregation) @@ -774,6 +787,11 @@ minstrel_ht_get_rate(void *priv, struct ieee80211_sta *sta, void *priv_sta, info->flags |= mi->tx_flags; minstrel_ht_check_cck_shortpreamble(mp, mi, txrc->short_preamble); +#ifdef CONFIG_MAC80211_DEBUGFS + if (mp->fixed_rate_idx != -1) + return; +#endif + /* Don't use EAPOL frames for sampling on non-mrr hw */ if (mp->hw->max_rates == 1 && (info->control.flags & IEEE80211_TX_CTRL_PORT_CTRL_PROTO)) @@ -781,16 +799,6 @@ minstrel_ht_get_rate(void *priv, struct ieee80211_sta *sta, void *priv_sta, else sample_idx = minstrel_get_sample_rate(mp, mi); -#ifdef CONFIG_MAC80211_DEBUGFS - /* use fixed index if set */ - if (mp->fixed_rate_idx != -1) { - mi->max_tp_rate = mp->fixed_rate_idx; - mi->max_tp_rate2 = mp->fixed_rate_idx; - mi->max_prob_rate = mp->fixed_rate_idx; - sample_idx = -1; - } -#endif - mi->total_packets++; /* wraparound */ @@ -814,7 +822,7 @@ minstrel_ht_get_rate(void *priv, struct ieee80211_sta *sta, void *priv_sta, } rate->idx = sample_idx % MCS_GROUP_RATES + - (sample_group->streams - 1) * MCS_GROUP_RATES; + (sample_group->streams - 1) * 8; rate->flags = IEEE80211_TX_RC_MCS | sample_group->flags; } @@ -1024,7 +1032,23 @@ minstrel_ht_free(void *priv) mac80211_minstrel.free(priv); } -static struct rate_control_ops mac80211_minstrel_ht = { +static u32 minstrel_ht_get_expected_throughput(void *priv_sta) +{ + struct minstrel_ht_sta_priv *msp = priv_sta; + struct minstrel_ht_sta *mi = &msp->ht; + int i, j; + + if (!msp->is_ht) + return mac80211_minstrel.get_expected_throughput(priv_sta); + + i = mi->max_tp_rate / MCS_GROUP_RATES; + j = mi->max_tp_rate % MCS_GROUP_RATES; + + /* convert cur_tp from pkt per second in kbps */ + return mi->groups[i].rates[j].cur_tp * AVG_PKT_SIZE * 8 / 1024; +} + +static const struct rate_control_ops mac80211_minstrel_ht = { .name = "minstrel_ht", .tx_status = minstrel_ht_tx_status, .get_rate = minstrel_ht_get_rate, @@ -1038,21 +1062,20 @@ static struct rate_control_ops mac80211_minstrel_ht = { .add_sta_debugfs = minstrel_ht_add_sta_debugfs, .remove_sta_debugfs = minstrel_ht_remove_sta_debugfs, #endif + .get_expected_throughput = minstrel_ht_get_expected_throughput, }; -static void -init_sample_table(void) +static void __init init_sample_table(void) { int col, i, new_idx; u8 rnd[MCS_GROUP_RATES]; memset(sample_table, 0xff, sizeof(sample_table)); for (col = 0; col < SAMPLE_COLUMNS; col++) { + prandom_bytes(rnd, sizeof(rnd)); for (i = 0; i < MCS_GROUP_RATES; i++) { - get_random_bytes(rnd, sizeof(rnd)); new_idx = (i + rnd[i]) % MCS_GROUP_RATES; - while (sample_table[col][new_idx] != 0xff) new_idx = (new_idx + 1) % MCS_GROUP_RATES; diff --git a/net/mac80211/rc80211_minstrel_ht_debugfs.c b/net/mac80211/rc80211_minstrel_ht_debugfs.c index df44a5ad827..3e7d793de0c 100644 --- a/net/mac80211/rc80211_minstrel_ht_debugfs.c +++ b/net/mac80211/rc80211_minstrel_ht_debugfs.c @@ -54,8 +54,7 @@ minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p) int r = bitrates[j % 4]; p += sprintf(p, " %2u.%1uM", r / 10, r % 10); } else { - p += sprintf(p, " MCS%-2u", (mg->streams - 1) * - MCS_GROUP_RATES + j); + p += sprintf(p, " MCS%-2u", (mg->streams - 1) * 8 + j); } tp = mr->cur_tp / 10; diff --git a/net/mac80211/rc80211_pid_algo.c b/net/mac80211/rc80211_pid_algo.c index 958fad07b54..d0da2a70fe6 100644 --- a/net/mac80211/rc80211_pid_algo.c +++ b/net/mac80211/rc80211_pid_algo.c @@ -452,7 +452,7 @@ static void rate_control_pid_free_sta(void *priv, struct ieee80211_sta *sta, kfree(priv_sta); } -static struct rate_control_ops mac80211_rcpid = { +static const struct rate_control_ops mac80211_rcpid = { .name = "pid", .tx_status = rate_control_pid_tx_status, .get_rate = rate_control_pid_get_rate, diff --git a/net/mac80211/rc80211_pid_debugfs.c b/net/mac80211/rc80211_pid_debugfs.c index c97a0657c04..6ff134650a8 100644 --- a/net/mac80211/rc80211_pid_debugfs.c +++ b/net/mac80211/rc80211_pid_debugfs.c @@ -167,29 +167,29 @@ static ssize_t rate_control_pid_events_read(struct file *file, char __user *buf, * provide large enough buffers. */ length = length < RC_PID_PRINT_BUF_SIZE ? length : RC_PID_PRINT_BUF_SIZE; - p = snprintf(pb, length, "%u %lu ", ev->id, ev->timestamp); + p = scnprintf(pb, length, "%u %lu ", ev->id, ev->timestamp); switch (ev->type) { case RC_PID_EVENT_TYPE_TX_STATUS: - p += snprintf(pb + p, length - p, "tx_status %u %u", - !(ev->data.flags & IEEE80211_TX_STAT_ACK), - ev->data.tx_status.status.rates[0].idx); + p += scnprintf(pb + p, length - p, "tx_status %u %u", + !(ev->data.flags & IEEE80211_TX_STAT_ACK), + ev->data.tx_status.status.rates[0].idx); break; case RC_PID_EVENT_TYPE_RATE_CHANGE: - p += snprintf(pb + p, length - p, "rate_change %d %d", - ev->data.index, ev->data.rate); + p += scnprintf(pb + p, length - p, "rate_change %d %d", + ev->data.index, ev->data.rate); break; case RC_PID_EVENT_TYPE_TX_RATE: - p += snprintf(pb + p, length - p, "tx_rate %d %d", - ev->data.index, ev->data.rate); + p += scnprintf(pb + p, length - p, "tx_rate %d %d", + ev->data.index, ev->data.rate); break; case RC_PID_EVENT_TYPE_PF_SAMPLE: - p += snprintf(pb + p, length - p, - "pf_sample %d %d %d %d", - ev->data.pf_sample, ev->data.prop_err, - ev->data.int_err, ev->data.der_err); + p += scnprintf(pb + p, length - p, + "pf_sample %d %d %d %d", + ev->data.pf_sample, ev->data.prop_err, + ev->data.int_err, ev->data.der_err); break; } - p += snprintf(pb + p, length - p, "\n"); + p += scnprintf(pb + p, length - p, "\n"); spin_unlock_irqrestore(&events->lock, status); diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 674eac1f996..394e201cde6 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -40,8 +40,6 @@ static struct sk_buff *remove_monitor_info(struct ieee80211_local *local, struct sk_buff *skb) { - struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); - if (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS) { if (likely(skb->len > FCS_LEN)) __pskb_trim(skb, skb->len - FCS_LEN); @@ -53,31 +51,28 @@ static struct sk_buff *remove_monitor_info(struct ieee80211_local *local, } } - if (status->vendor_radiotap_len) - __pskb_pull(skb, status->vendor_radiotap_len); - return skb; } -static inline int should_drop_frame(struct sk_buff *skb, int present_fcs_len) +static inline bool should_drop_frame(struct sk_buff *skb, int present_fcs_len) { struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); - struct ieee80211_hdr *hdr; - - hdr = (void *)(skb->data + status->vendor_radiotap_len); + struct ieee80211_hdr *hdr = (void *)skb->data; if (status->flag & (RX_FLAG_FAILED_FCS_CRC | RX_FLAG_FAILED_PLCP_CRC | RX_FLAG_AMPDU_IS_ZEROLEN)) - return 1; - if (unlikely(skb->len < 16 + present_fcs_len + - status->vendor_radiotap_len)) - return 1; + return true; + + if (unlikely(skb->len < 16 + present_fcs_len)) + return true; + if (ieee80211_is_ctl(hdr->frame_control) && !ieee80211_is_pspoll(hdr->frame_control) && !ieee80211_is_back_req(hdr->frame_control)) - return 1; - return 0; + return true; + + return false; } static int @@ -90,8 +85,6 @@ ieee80211_rx_radiotap_space(struct ieee80211_local *local, len = sizeof(struct ieee80211_radiotap_header) + 8; /* allocate extra bitmaps */ - if (status->vendor_radiotap_len) - len += 4; if (status->chains) len += 4 * hweight8(status->chains); @@ -127,18 +120,6 @@ ieee80211_rx_radiotap_space(struct ieee80211_local *local, len += 2 * hweight8(status->chains); } - if (status->vendor_radiotap_len) { - if (WARN_ON_ONCE(status->vendor_radiotap_align == 0)) - status->vendor_radiotap_align = 1; - /* align standard part of vendor namespace */ - len = ALIGN(len, 2); - /* allocate standard part of vendor namespace */ - len += 6; - /* align vendor-defined part */ - len = ALIGN(len, status->vendor_radiotap_align); - /* vendor-defined part is already in skb */ - } - return len; } @@ -172,7 +153,7 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local, it_present = &rthdr->it_present; /* radiotap header, set always present flags */ - rthdr->it_len = cpu_to_le16(rtap_len + status->vendor_radiotap_len); + rthdr->it_len = cpu_to_le16(rtap_len); it_present_val = BIT(IEEE80211_RADIOTAP_FLAGS) | BIT(IEEE80211_RADIOTAP_CHANNEL) | BIT(IEEE80211_RADIOTAP_RX_FLAGS); @@ -190,14 +171,6 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local, BIT(IEEE80211_RADIOTAP_DBM_ANTSIGNAL); } - if (status->vendor_radiotap_len) { - it_present_val |= BIT(IEEE80211_RADIOTAP_VENDOR_NAMESPACE) | - BIT(IEEE80211_RADIOTAP_EXT); - put_unaligned_le32(it_present_val, it_present); - it_present++; - it_present_val = status->vendor_radiotap_bitmap; - } - put_unaligned_le32(it_present_val, it_present); pos = (void *)(it_present + 1); @@ -307,6 +280,8 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local, *pos |= IEEE80211_RADIOTAP_MCS_BW_40; if (status->flag & RX_FLAG_HT_GF) *pos |= IEEE80211_RADIOTAP_MCS_FMT_GF; + if (status->flag & RX_FLAG_LDPC) + *pos |= IEEE80211_RADIOTAP_MCS_FEC_LDPC; stbc = (status->flag & RX_FLAG_STBC_MASK) >> RX_FLAG_STBC_SHIFT; *pos |= stbc << IEEE80211_RADIOTAP_MCS_STBC_SHIFT; pos++; @@ -349,20 +324,25 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local, rthdr->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_VHT); /* known field - how to handle 80+80? */ - if (status->flag & RX_FLAG_80P80MHZ) + if (status->vht_flag & RX_VHT_FLAG_80P80MHZ) known &= ~IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH; put_unaligned_le16(known, pos); pos += 2; /* flags */ if (status->flag & RX_FLAG_SHORT_GI) *pos |= IEEE80211_RADIOTAP_VHT_FLAG_SGI; + /* in VHT, STBC is binary */ + if (status->flag & RX_FLAG_STBC_MASK) + *pos |= IEEE80211_RADIOTAP_VHT_FLAG_STBC; + if (status->vht_flag & RX_VHT_FLAG_BF) + *pos |= IEEE80211_RADIOTAP_VHT_FLAG_BEAMFORMED; pos++; /* bandwidth */ - if (status->flag & RX_FLAG_80MHZ) + if (status->vht_flag & RX_VHT_FLAG_80MHZ) *pos++ = 4; - else if (status->flag & RX_FLAG_80P80MHZ) + else if (status->vht_flag & RX_VHT_FLAG_80P80MHZ) *pos++ = 0; /* marked not known above */ - else if (status->flag & RX_FLAG_160MHZ) + else if (status->vht_flag & RX_VHT_FLAG_160MHZ) *pos++ = 11; else if (status->flag & RX_FLAG_40MHZ) *pos++ = 1; @@ -372,6 +352,8 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local, *pos = (status->rate_idx << 4) | status->vht_nss; pos += 4; /* coding field */ + if (status->flag & RX_FLAG_LDPC) + *pos |= IEEE80211_RADIOTAP_CODING_LDPC_USER0; pos++; /* group ID */ pos++; @@ -383,21 +365,6 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local, *pos++ = status->chain_signal[chain]; *pos++ = chain; } - - if (status->vendor_radiotap_len) { - /* ensure 2 byte alignment for the vendor field as required */ - if ((pos - (u8 *)rthdr) & 1) - *pos++ = 0; - *pos++ = status->vendor_radiotap_oui[0]; - *pos++ = status->vendor_radiotap_oui[1]; - *pos++ = status->vendor_radiotap_oui[2]; - *pos++ = status->vendor_radiotap_subns; - put_unaligned_le16(status->vendor_radiotap_len, pos); - pos += 2; - /* align the actual payload as requested */ - while ((pos - (u8 *)rthdr) & (status->vendor_radiotap_align - 1)) - *pos++ = 0; - } } /* @@ -428,8 +395,8 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb, if (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS) present_fcs_len = FCS_LEN; - /* ensure hdr->frame_control and vendor radiotap data are in skb head */ - if (!pskb_may_pull(origskb, 2 + status->vendor_radiotap_len)) { + /* ensure hdr->frame_control is in skb head */ + if (!pskb_may_pull(origskb, 2)) { dev_kfree_skb(origskb); return NULL; } @@ -599,10 +566,10 @@ static int ieee80211_is_unicast_robust_mgmt_frame(struct sk_buff *skb) { struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; - if (skb->len < 24 || is_multicast_ether_addr(hdr->addr1)) + if (is_multicast_ether_addr(hdr->addr1)) return 0; - return ieee80211_is_robust_mgmt_frame(hdr); + return ieee80211_is_robust_mgmt_frame(skb); } @@ -610,10 +577,10 @@ static int ieee80211_is_multicast_robust_mgmt_frame(struct sk_buff *skb) { struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; - if (skb->len < 24 || !is_multicast_ether_addr(hdr->addr1)) + if (!is_multicast_ether_addr(hdr->addr1)) return 0; - return ieee80211_is_robust_mgmt_frame(hdr); + return ieee80211_is_robust_mgmt_frame(skb); } @@ -626,7 +593,7 @@ static int ieee80211_get_mmie_keyidx(struct sk_buff *skb) if (skb->len < 24 + sizeof(*mmie) || !is_multicast_ether_addr(hdr->da)) return -1; - if (!ieee80211_is_robust_mgmt_frame((struct ieee80211_hdr *) hdr)) + if (!ieee80211_is_robust_mgmt_frame(skb)) return -1; /* not a robust management frame */ mmie = (struct ieee80211_mmie *) @@ -638,6 +605,27 @@ static int ieee80211_get_mmie_keyidx(struct sk_buff *skb) return le16_to_cpu(mmie->key_id); } +static int iwl80211_get_cs_keyid(const struct ieee80211_cipher_scheme *cs, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + __le16 fc; + int hdrlen; + u8 keyid; + + fc = hdr->frame_control; + hdrlen = ieee80211_hdrlen(fc); + + if (skb->len < hdrlen + cs->hdr_len) + return -EINVAL; + + skb_copy_bits(skb, hdrlen + cs->key_idx_off, &keyid, 1); + keyid &= cs->key_idx_mask; + keyid >>= cs->key_idx_shift; + + return keyid; +} + static ieee80211_rx_result ieee80211_rx_mesh_check(struct ieee80211_rx_data *rx) { struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data; @@ -729,9 +717,7 @@ static void ieee80211_release_reorder_frames(struct ieee80211_sub_if_data *sdata lockdep_assert_held(&tid_agg_rx->reorder_lock); while (ieee80211_sn_less(tid_agg_rx->head_seq_num, head_seq_num)) { - index = ieee80211_sn_sub(tid_agg_rx->head_seq_num, - tid_agg_rx->ssn) % - tid_agg_rx->buf_size; + index = tid_agg_rx->head_seq_num % tid_agg_rx->buf_size; ieee80211_release_reorder_frame(sdata, tid_agg_rx, index, frames); } @@ -757,8 +743,7 @@ static void ieee80211_sta_reorder_release(struct ieee80211_sub_if_data *sdata, lockdep_assert_held(&tid_agg_rx->reorder_lock); /* release the buffer until next missing frame */ - index = ieee80211_sn_sub(tid_agg_rx->head_seq_num, - tid_agg_rx->ssn) % tid_agg_rx->buf_size; + index = tid_agg_rx->head_seq_num % tid_agg_rx->buf_size; if (!tid_agg_rx->reorder_buf[index] && tid_agg_rx->stored_mpdu_num) { /* @@ -793,15 +778,11 @@ static void ieee80211_sta_reorder_release(struct ieee80211_sub_if_data *sdata, } else while (tid_agg_rx->reorder_buf[index]) { ieee80211_release_reorder_frame(sdata, tid_agg_rx, index, frames); - index = ieee80211_sn_sub(tid_agg_rx->head_seq_num, - tid_agg_rx->ssn) % - tid_agg_rx->buf_size; + index = tid_agg_rx->head_seq_num % tid_agg_rx->buf_size; } if (tid_agg_rx->stored_mpdu_num) { - j = index = ieee80211_sn_sub(tid_agg_rx->head_seq_num, - tid_agg_rx->ssn) % - tid_agg_rx->buf_size; + j = index = tid_agg_rx->head_seq_num % tid_agg_rx->buf_size; for (; j != (index - 1) % tid_agg_rx->buf_size; j = (j + 1) % tid_agg_rx->buf_size) { @@ -861,8 +842,7 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_sub_if_data *sdata /* Now the new frame is always in the range of the reordering buffer */ - index = ieee80211_sn_sub(mpdu_seq_num, - tid_agg_rx->ssn) % tid_agg_rx->buf_size; + index = mpdu_seq_num % tid_agg_rx->buf_size; /* check if we already stored this frame */ if (tid_agg_rx->reorder_buf[index]) { @@ -911,7 +891,8 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx, u16 sc; u8 tid, ack_policy; - if (!ieee80211_is_data_qos(hdr->frame_control)) + if (!ieee80211_is_data_qos(hdr->frame_control) || + is_multicast_ether_addr(hdr->addr1)) goto dont_reorder; /* @@ -995,8 +976,9 @@ ieee80211_rx_h_check(struct ieee80211_rx_data *rx) rx->sta->num_duplicates++; } return RX_DROP_UNUSABLE; - } else + } else if (!(status->flag & RX_FLAG_AMSDU_MORE)) { rx->sta->last_seq_ctrl[rx->seqno_idx] = hdr->seq_ctrl; + } } if (unlikely(rx->skb->len < 16)) { @@ -1113,6 +1095,13 @@ static void sta_ps_end(struct sta_info *sta) sta->sta.addr, sta->sta.aid); if (test_sta_flag(sta, WLAN_STA_PS_DRIVER)) { + /* + * Clear the flag only if the other one is still set + * so that the TX path won't start TX'ing new frames + * directly ... In the case that the driver flag isn't + * set ieee80211_sta_ps_deliver_wakeup() will clear it. + */ + clear_sta_flag(sta, WLAN_STA_PS_STA); ps_dbg(sta->sdata, "STA %pM aid %d driver-ps-blocked\n", sta->sta.addr, sta->sta.aid); return; @@ -1243,9 +1232,11 @@ ieee80211_rx_h_sta_process(struct ieee80211_rx_data *rx) if (ether_addr_equal(bssid, rx->sdata->u.ibss.bssid) && test_sta_flag(sta, WLAN_STA_AUTHORIZED)) { sta->last_rx = jiffies; - if (ieee80211_is_data(hdr->frame_control)) { + if (ieee80211_is_data(hdr->frame_control) && + !is_multicast_ether_addr(hdr->addr1)) { sta->last_rx_rate_idx = status->rate_idx; sta->last_rx_rate_flag = status->flag; + sta->last_rx_rate_vht_flag = status->vht_flag; sta->last_rx_rate_vht_nss = status->vht_nss; } } @@ -1258,6 +1249,7 @@ ieee80211_rx_h_sta_process(struct ieee80211_rx_data *rx) if (ieee80211_is_data(hdr->frame_control)) { sta->last_rx_rate_idx = status->rate_idx; sta->last_rx_rate_flag = status->flag; + sta->last_rx_rate_vht_flag = status->vht_flag; sta->last_rx_rate_vht_nss = status->vht_nss; } } @@ -1296,18 +1288,15 @@ ieee80211_rx_h_sta_process(struct ieee80211_rx_data *rx) !ieee80211_has_morefrags(hdr->frame_control) && !(status->rx_flags & IEEE80211_RX_DEFERRED_RELEASE) && (rx->sdata->vif.type == NL80211_IFTYPE_AP || - rx->sdata->vif.type == NL80211_IFTYPE_AP_VLAN)) { + rx->sdata->vif.type == NL80211_IFTYPE_AP_VLAN) && + /* PM bit is only checked in frames where it isn't reserved, + * in AP mode it's reserved in non-bufferable management frames + * (cf. IEEE 802.11-2012 8.2.4.1.7 Power Management field) + */ + (!ieee80211_is_mgmt(hdr->frame_control) || + ieee80211_is_bufferable_mmpdu(hdr->frame_control))) { if (test_sta_flag(sta, WLAN_STA_PS_STA)) { - /* - * Ignore doze->wake transitions that are - * indicated by non-data frames, the standard - * is unclear here, but for example going to - * PS mode and then scanning would cause a - * doze->wake transition for the probe request, - * and that is clearly undesirable. - */ - if (ieee80211_is_data(hdr->frame_control) && - !ieee80211_has_pm(hdr->frame_control)) + if (!ieee80211_has_pm(hdr->frame_control)) sta_ps_end(sta); } else { if (ieee80211_has_pm(hdr->frame_control)) @@ -1367,6 +1356,7 @@ ieee80211_rx_h_decrypt(struct ieee80211_rx_data *rx) struct ieee80211_key *sta_ptk = NULL; int mmie_keyidx = -1; __le16 fc; + const struct ieee80211_cipher_scheme *cs = NULL; /* * Key selection 101 @@ -1404,11 +1394,19 @@ ieee80211_rx_h_decrypt(struct ieee80211_rx_data *rx) /* start without a key */ rx->key = NULL; + fc = hdr->frame_control; - if (rx->sta) - sta_ptk = rcu_dereference(rx->sta->ptk); + if (rx->sta) { + int keyid = rx->sta->ptk_idx; - fc = hdr->frame_control; + if (ieee80211_has_protected(fc) && rx->sta->cipher_scheme) { + cs = rx->sta->cipher_scheme; + keyid = iwl80211_get_cs_keyid(cs, rx->skb); + if (unlikely(keyid < 0)) + return RX_DROP_UNUSABLE; + } + sta_ptk = rcu_dereference(rx->sta->ptk[keyid]); + } if (!ieee80211_has_protected(fc)) mmie_keyidx = ieee80211_get_mmie_keyidx(rx->skb); @@ -1470,6 +1468,7 @@ ieee80211_rx_h_decrypt(struct ieee80211_rx_data *rx) return RX_CONTINUE; } else { u8 keyid; + /* * The device doesn't give us the IV so we won't be * able to look up the key. That's ok though, we @@ -1485,15 +1484,21 @@ ieee80211_rx_h_decrypt(struct ieee80211_rx_data *rx) hdrlen = ieee80211_hdrlen(fc); - if (rx->skb->len < 8 + hdrlen) - return RX_DROP_UNUSABLE; /* TODO: count this? */ + if (cs) { + keyidx = iwl80211_get_cs_keyid(cs, rx->skb); - /* - * no need to call ieee80211_wep_get_keyidx, - * it verifies a bunch of things we've done already - */ - skb_copy_bits(rx->skb, hdrlen + 3, &keyid, 1); - keyidx = keyid >> 6; + if (unlikely(keyidx < 0)) + return RX_DROP_UNUSABLE; + } else { + if (rx->skb->len < 8 + hdrlen) + return RX_DROP_UNUSABLE; /* TODO: count this? */ + /* + * no need to call ieee80211_wep_get_keyidx, + * it verifies a bunch of things we've done already + */ + skb_copy_bits(rx->skb, hdrlen + 3, &keyid, 1); + keyidx = keyid >> 6; + } /* check per-station GTK first, if multicast packet */ if (is_multicast_ether_addr(hdr->addr1) && rx->sta) @@ -1541,11 +1546,7 @@ ieee80211_rx_h_decrypt(struct ieee80211_rx_data *rx) result = ieee80211_crypto_aes_cmac_decrypt(rx); break; default: - /* - * We can reach here only with HW-only algorithms - * but why didn't it decrypt the frame?! - */ - return RX_DROP_UNUSABLE; + result = ieee80211_crypto_hw_decrypt(rx); } /* the hdr variable is invalid after the decrypt handlers */ @@ -1818,8 +1819,7 @@ static int ieee80211_drop_unencrypted_mgmt(struct ieee80211_rx_data *rx) * having configured keys. */ if (unlikely(ieee80211_is_action(fc) && !rx->key && - ieee80211_is_robust_mgmt_frame( - (struct ieee80211_hdr *) rx->skb->data))) + ieee80211_is_robust_mgmt_frame(rx->skb))) return -EACCES; } @@ -1936,20 +1936,17 @@ ieee80211_deliver_skb(struct ieee80211_rx_data *rx) } } - if (skb) { - int align __maybe_unused; - #ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS - /* - * 'align' will only take the values 0 or 2 here - * since all frames are required to be aligned - * to 2-byte boundaries when being passed to - * mac80211; the code here works just as well if - * that isn't true, but mac80211 assumes it can - * access fields as 2-byte aligned (e.g. for - * compare_ether_addr) + if (skb) { + /* 'align' will only take the values 0 or 2 here since all + * frames are required to be aligned to 2-byte boundaries + * when being passed to mac80211; the code here works just + * as well if that isn't true, but mac80211 assumes it can + * access fields as 2-byte aligned (e.g. for ether_addr_equal) */ - align = ((unsigned long)(skb->data + sizeof(struct ethhdr))) & 3; + int align; + + align = (unsigned long)(skb->data + sizeof(struct ethhdr)) & 3; if (align) { if (WARN_ON(skb_headroom(skb) < 3)) { dev_kfree_skb(skb); @@ -1962,14 +1959,17 @@ ieee80211_deliver_skb(struct ieee80211_rx_data *rx) skb_set_tail_pointer(skb, len); } } + } #endif - if (skb) { - /* deliver to local stack */ - skb->protocol = eth_type_trans(skb, dev); - memset(skb->cb, 0, sizeof(skb->cb)); + if (skb) { + /* deliver to local stack */ + skb->protocol = eth_type_trans(skb, dev); + memset(skb->cb, 0, sizeof(skb->cb)); + if (rx->local->napi) + napi_gro_receive(rx->local->napi, skb); + else netif_receive_skb(skb); - } } if (xmit_skb) { @@ -2055,7 +2055,6 @@ ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx) struct ieee80211_sub_if_data *sdata = rx->sdata; struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; - __le16 reason = cpu_to_le16(WLAN_REASON_MESH_PATH_NOFORWARD); u16 q, hdrlen; hdr = (struct ieee80211_hdr *) skb->data; @@ -2163,7 +2162,9 @@ ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx) } else { /* unable to resolve next hop */ mesh_path_error_tx(sdata, ifmsh->mshcfg.element_ttl, - fwd_hdr->addr3, 0, reason, fwd_hdr->addr2); + fwd_hdr->addr3, 0, + WLAN_REASON_MESH_PATH_NOFORWARD, + fwd_hdr->addr2); IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, dropped_frames_no_route); kfree_skb(fwd_skb); return RX_DROP_MONITOR; @@ -2402,7 +2403,8 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx) return RX_DROP_UNUSABLE; if (!rx->sta && mgmt->u.action.category != WLAN_CATEGORY_PUBLIC && - mgmt->u.action.category != WLAN_CATEGORY_SELF_PROTECTED) + mgmt->u.action.category != WLAN_CATEGORY_SELF_PROTECTED && + mgmt->u.action.category != WLAN_CATEGORY_SPECTRUM_MGMT) return RX_DROP_UNUSABLE; if (!(status->rx_flags & IEEE80211_RX_RA_MATCH)) @@ -2566,31 +2568,49 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx) goto queue; case WLAN_CATEGORY_SPECTRUM_MGMT: - if (status->band != IEEE80211_BAND_5GHZ) - break; - - if (sdata->vif.type != NL80211_IFTYPE_STATION) - break; - /* verify action_code is present */ if (len < IEEE80211_MIN_ACTION_SIZE + 1) break; switch (mgmt->u.action.u.measurement.action_code) { case WLAN_ACTION_SPCT_MSR_REQ: + if (status->band != IEEE80211_BAND_5GHZ) + break; + if (len < (IEEE80211_MIN_ACTION_SIZE + sizeof(mgmt->u.action.u.measurement))) break; + + if (sdata->vif.type != NL80211_IFTYPE_STATION) + break; + ieee80211_process_measurement_req(sdata, mgmt, len); goto handled; - case WLAN_ACTION_SPCT_CHL_SWITCH: - if (sdata->vif.type != NL80211_IFTYPE_STATION) + case WLAN_ACTION_SPCT_CHL_SWITCH: { + u8 *bssid; + if (len < (IEEE80211_MIN_ACTION_SIZE + + sizeof(mgmt->u.action.u.chan_switch))) break; - if (!ether_addr_equal(mgmt->bssid, sdata->u.mgd.bssid)) + if (sdata->vif.type != NL80211_IFTYPE_STATION && + sdata->vif.type != NL80211_IFTYPE_ADHOC && + sdata->vif.type != NL80211_IFTYPE_MESH_POINT) + break; + + if (sdata->vif.type == NL80211_IFTYPE_STATION) + bssid = sdata->u.mgd.bssid; + else if (sdata->vif.type == NL80211_IFTYPE_ADHOC) + bssid = sdata->u.ibss.bssid; + else if (sdata->vif.type == NL80211_IFTYPE_MESH_POINT) + bssid = mgmt->sa; + else + break; + + if (!ether_addr_equal(mgmt->bssid, bssid)) break; goto queue; + } } break; case WLAN_CATEGORY_SA_QUERY: @@ -3032,8 +3052,8 @@ void ieee80211_release_reorder_timeout(struct sta_info *sta, int tid) /* main receive path */ -static int prepare_for_handlers(struct ieee80211_rx_data *rx, - struct ieee80211_hdr *hdr) +static bool prepare_for_handlers(struct ieee80211_rx_data *rx, + struct ieee80211_hdr *hdr) { struct ieee80211_sub_if_data *sdata = rx->sdata; struct sk_buff *skb = rx->skb; @@ -3044,29 +3064,29 @@ static int prepare_for_handlers(struct ieee80211_rx_data *rx, switch (sdata->vif.type) { case NL80211_IFTYPE_STATION: if (!bssid && !sdata->u.mgd.use_4addr) - return 0; + return false; if (!multicast && !ether_addr_equal(sdata->vif.addr, hdr->addr1)) { if (!(sdata->dev->flags & IFF_PROMISC) || sdata->u.mgd.use_4addr) - return 0; + return false; status->rx_flags &= ~IEEE80211_RX_RA_MATCH; } break; case NL80211_IFTYPE_ADHOC: if (!bssid) - return 0; + return false; if (ether_addr_equal(sdata->vif.addr, hdr->addr2) || ether_addr_equal(sdata->u.ibss.bssid, hdr->addr2)) - return 0; + return false; if (ieee80211_is_beacon(hdr->frame_control)) { - return 1; + return true; } else if (!ieee80211_bssid_match(bssid, sdata->u.ibss.bssid)) { - return 0; + return false; } else if (!multicast && !ether_addr_equal(sdata->vif.addr, hdr->addr1)) { if (!(sdata->dev->flags & IFF_PROMISC)) - return 0; + return false; status->rx_flags &= ~IEEE80211_RX_RA_MATCH; } else if (!rx->sta) { int rate_idx; @@ -3082,7 +3102,7 @@ static int prepare_for_handlers(struct ieee80211_rx_data *rx, if (!multicast && !ether_addr_equal(sdata->vif.addr, hdr->addr1)) { if (!(sdata->dev->flags & IFF_PROMISC)) - return 0; + return false; status->rx_flags &= ~IEEE80211_RX_RA_MATCH; } @@ -3091,7 +3111,7 @@ static int prepare_for_handlers(struct ieee80211_rx_data *rx, case NL80211_IFTYPE_AP: if (!bssid) { if (!ether_addr_equal(sdata->vif.addr, hdr->addr1)) - return 0; + return false; } else if (!ieee80211_bssid_match(bssid, sdata->vif.addr)) { /* * Accept public action frames even when the @@ -3101,26 +3121,26 @@ static int prepare_for_handlers(struct ieee80211_rx_data *rx, */ if (!multicast && !ether_addr_equal(sdata->vif.addr, hdr->addr1)) - return 0; + return false; if (ieee80211_is_public_action(hdr, skb->len)) - return 1; + return true; if (!ieee80211_is_beacon(hdr->frame_control)) - return 0; + return false; status->rx_flags &= ~IEEE80211_RX_RA_MATCH; } break; case NL80211_IFTYPE_WDS: if (bssid || !ieee80211_is_data(hdr->frame_control)) - return 0; + return false; if (!ether_addr_equal(sdata->u.wds.remote_addr, hdr->addr2)) - return 0; + return false; break; case NL80211_IFTYPE_P2P_DEVICE: if (!ieee80211_is_public_action(hdr, skb->len) && !ieee80211_is_probe_req(hdr->frame_control) && !ieee80211_is_probe_resp(hdr->frame_control) && !ieee80211_is_beacon(hdr->frame_control)) - return 0; + return false; if (!ether_addr_equal(sdata->vif.addr, hdr->addr1) && !multicast) status->rx_flags &= ~IEEE80211_RX_RA_MATCH; @@ -3131,7 +3151,7 @@ static int prepare_for_handlers(struct ieee80211_rx_data *rx, break; } - return 1; + return true; } /* @@ -3147,13 +3167,11 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx, struct ieee80211_sub_if_data *sdata = rx->sdata; struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); struct ieee80211_hdr *hdr = (void *)skb->data; - int prepares; rx->skb = skb; status->rx_flags |= IEEE80211_RX_RA_MATCH; - prepares = prepare_for_handlers(rx, hdr); - if (!prepares) + if (!prepare_for_handlers(rx, hdr)) return false; if (!consume) { @@ -3174,7 +3192,7 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx, } /* - * This is the actual Rx frames handler. as it blongs to Rx path it must + * This is the actual Rx frames handler. as it belongs to Rx path it must * be called with rcu_read_lock protection. */ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index 08afe74b98f..f40661eb75b 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -238,6 +238,9 @@ static bool ieee80211_prep_hw_scan(struct ieee80211_local *local) enum ieee80211_band band; int i, ielen, n_chans; + if (test_bit(SCAN_HW_CANCELLED, &local->scanning)) + return false; + do { if (local->hw_scan_band == IEEE80211_NUM_BANDS) return false; @@ -268,10 +271,11 @@ static bool ieee80211_prep_hw_scan(struct ieee80211_local *local) return true; } -static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted, - bool was_hw_scan) +static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) { struct ieee80211_local *local = hw_to_local(hw); + bool hw_scan = local->ops->hw_scan; + bool was_scanning = local->scanning; lockdep_assert_held(&local->mtx); @@ -287,7 +291,7 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted, if (WARN_ON(!local->scan_req)) return; - if (was_hw_scan && !aborted && ieee80211_prep_hw_scan(local)) { + if (hw_scan && !aborted && ieee80211_prep_hw_scan(local)) { int rc; rc = drv_hw_scan(local, @@ -305,7 +309,7 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted, if (local->scan_req != local->int_scan_req) cfg80211_scan_done(local->scan_req, aborted); local->scan_req = NULL; - rcu_assign_pointer(local->scan_sdata, NULL); + RCU_INIT_POINTER(local->scan_sdata, NULL); local->scanning = 0; local->scan_chandef.chan = NULL; @@ -313,7 +317,7 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted, /* Set power back to normal operating levels. */ ieee80211_hw_config(local, 0); - if (!was_hw_scan) { + if (!hw_scan) { ieee80211_configure_filter(local); drv_sw_scan_complete(local); ieee80211_offchannel_return(local); @@ -324,7 +328,8 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted, ieee80211_mlme_notify_scan_completed(local); ieee80211_ibss_notify_scan_completed(local); ieee80211_mesh_notify_scan_completed(local); - ieee80211_start_next_roc(local); + if (was_scanning) + ieee80211_start_next_roc(local); } void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) @@ -391,8 +396,7 @@ static bool ieee80211_can_scan(struct ieee80211_local *local, return false; if (sdata->vif.type == NL80211_IFTYPE_STATION && - sdata->u.mgd.flags & (IEEE80211_STA_BEACON_POLL | - IEEE80211_STA_CONNECTION_POLL)) + sdata->u.mgd.flags & IEEE80211_STA_CONNECTION_POLL) return false; return true; @@ -468,9 +472,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, if (local->ops->hw_scan) { u8 *ies; - local->hw_scan_ies_bufsize = 2 + IEEE80211_MAX_SSID_LEN + - local->scan_ies_len + - req->ie_len; + local->hw_scan_ies_bufsize = local->scan_ies_len + req->ie_len; local->hw_scan_req = kmalloc( sizeof(*local->hw_scan_req) + req->n_channels * sizeof(req->channels[0]) + @@ -524,7 +526,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, ieee80211_hw_config(local, 0); if ((req->channels[0]->flags & - IEEE80211_CHAN_PASSIVE_SCAN) || + IEEE80211_CHAN_NO_IR) || !local->scan_req->n_ssids) { next_delay = IEEE80211_PASSIVE_CHANNEL_TIME; } else { @@ -557,7 +559,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, ieee80211_recalc_idle(local); local->scan_req = NULL; - rcu_assign_pointer(local->scan_sdata, NULL); + RCU_INIT_POINTER(local->scan_sdata, NULL); } return rc; @@ -570,7 +572,7 @@ ieee80211_scan_get_channel_time(struct ieee80211_channel *chan) * TODO: channel switching also consumes quite some time, * add that delay as well to get a better estimation */ - if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN) + if (chan->flags & IEEE80211_CHAN_NO_IR) return IEEE80211_PASSIVE_CHANNEL_TIME; return IEEE80211_PROBE_DELAY + IEEE80211_CHANNEL_TIME; } @@ -694,7 +696,7 @@ static void ieee80211_scan_state_set_channel(struct ieee80211_local *local, * * In any case, it is not necessary for a passive scan. */ - if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN || + if (chan->flags & IEEE80211_CHAN_NO_IR || !local->scan_req->n_ssids) { *next_delay = IEEE80211_PASSIVE_CHANNEL_TIME; local->next_scan_state = SCAN_DECISION; @@ -745,7 +747,7 @@ void ieee80211_scan_work(struct work_struct *work) container_of(work, struct ieee80211_local, scan_work.work); struct ieee80211_sub_if_data *sdata; unsigned long next_delay = 0; - bool aborted, hw_scan; + bool aborted; mutex_lock(&local->mtx); @@ -771,7 +773,7 @@ void ieee80211_scan_work(struct work_struct *work) int rc; local->scan_req = NULL; - rcu_assign_pointer(local->scan_sdata, NULL); + RCU_INIT_POINTER(local->scan_sdata, NULL); rc = __ieee80211_start_scan(sdata, req); if (rc) { @@ -784,14 +786,6 @@ void ieee80211_scan_work(struct work_struct *work) } /* - * Avoid re-scheduling when the sdata is going away. - */ - if (!ieee80211_sdata_running(sdata)) { - aborted = true; - goto out_complete; - } - - /* * as long as no delay is required advance immediately * without scheduling a new work */ @@ -832,8 +826,7 @@ void ieee80211_scan_work(struct work_struct *work) goto out; out_complete: - hw_scan = test_bit(SCAN_HW_SCANNING, &local->scanning); - __ieee80211_scan_completed(&local->hw, aborted, hw_scan); + __ieee80211_scan_completed(&local->hw, aborted); out: mutex_unlock(&local->mtx); } @@ -879,7 +872,7 @@ int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata, struct ieee80211_channel *tmp_ch = &local->hw.wiphy->bands[band]->channels[i]; - if (tmp_ch->flags & (IEEE80211_CHAN_NO_IBSS | + if (tmp_ch->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_DISABLED)) continue; @@ -893,7 +886,7 @@ int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata, local->int_scan_req->n_channels = n_ch; } else { - if (WARN_ON_ONCE(chan->flags & (IEEE80211_CHAN_NO_IBSS | + if (WARN_ON_ONCE(chan->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_DISABLED))) goto unlock; @@ -940,7 +933,23 @@ void ieee80211_scan_cancel(struct ieee80211_local *local) if (!local->scan_req) goto out; + /* + * We have a scan running and the driver already reported completion, + * but the worker hasn't run yet or is stuck on the mutex - mark it as + * cancelled. + */ + if (test_bit(SCAN_HW_SCANNING, &local->scanning) && + test_bit(SCAN_COMPLETED, &local->scanning)) { + set_bit(SCAN_HW_CANCELLED, &local->scanning); + goto out; + } + if (test_bit(SCAN_HW_SCANNING, &local->scanning)) { + /* + * Make sure that __ieee80211_scan_completed doesn't trigger a + * scan on another band. + */ + set_bit(SCAN_HW_CANCELLED, &local->scanning); if (local->ops->cancel_hw_scan) drv_cancel_hw_scan(local, rcu_dereference_protected(local->scan_sdata, @@ -955,33 +964,25 @@ void ieee80211_scan_cancel(struct ieee80211_local *local) */ cancel_delayed_work(&local->scan_work); /* and clean up */ - __ieee80211_scan_completed(&local->hw, true, false); + __ieee80211_scan_completed(&local->hw, true); out: mutex_unlock(&local->mtx); } -int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, - struct cfg80211_sched_scan_request *req) +int __ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, + struct cfg80211_sched_scan_request *req) { struct ieee80211_local *local = sdata->local; struct ieee80211_sched_scan_ies sched_scan_ies = {}; struct cfg80211_chan_def chandef; int ret, i, iebufsz; - iebufsz = 2 + IEEE80211_MAX_SSID_LEN + - local->scan_ies_len + req->ie_len; - - mutex_lock(&local->mtx); + iebufsz = local->scan_ies_len + req->ie_len; - if (rcu_access_pointer(local->sched_scan_sdata)) { - ret = -EBUSY; - goto out; - } + lockdep_assert_held(&local->mtx); - if (!local->ops->sched_scan_start) { - ret = -ENOTSUPP; - goto out; - } + if (!local->ops->sched_scan_start) + return -ENOTSUPP; for (i = 0; i < IEEE80211_NUM_BANDS; i++) { if (!local->hw.wiphy->bands[i]) @@ -1002,13 +1003,39 @@ int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, } ret = drv_sched_scan_start(local, sdata, req, &sched_scan_ies); - if (ret == 0) + if (ret == 0) { rcu_assign_pointer(local->sched_scan_sdata, sdata); + local->sched_scan_req = req; + } out_free: while (i > 0) kfree(sched_scan_ies.ie[--i]); -out: + + if (ret) { + /* Clean in case of failure after HW restart or upon resume. */ + RCU_INIT_POINTER(local->sched_scan_sdata, NULL); + local->sched_scan_req = NULL; + } + + return ret; +} + +int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, + struct cfg80211_sched_scan_request *req) +{ + struct ieee80211_local *local = sdata->local; + int ret; + + mutex_lock(&local->mtx); + + if (rcu_access_pointer(local->sched_scan_sdata)) { + mutex_unlock(&local->mtx); + return -EBUSY; + } + + ret = __ieee80211_request_sched_scan_start(sdata, req); + mutex_unlock(&local->mtx); return ret; } @@ -1025,9 +1052,14 @@ int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata) goto out; } - if (rcu_access_pointer(local->sched_scan_sdata)) - drv_sched_scan_stop(local, sdata); + /* We don't want to restart sched scan anymore. */ + local->sched_scan_req = NULL; + if (rcu_access_pointer(local->sched_scan_sdata)) { + ret = drv_sched_scan_stop(local, sdata); + if (!ret) + rcu_assign_pointer(local->sched_scan_sdata, NULL); + } out: mutex_unlock(&local->mtx); @@ -1044,12 +1076,8 @@ void ieee80211_sched_scan_results(struct ieee80211_hw *hw) } EXPORT_SYMBOL(ieee80211_sched_scan_results); -void ieee80211_sched_scan_stopped_work(struct work_struct *work) +void ieee80211_sched_scan_end(struct ieee80211_local *local) { - struct ieee80211_local *local = - container_of(work, struct ieee80211_local, - sched_scan_stopped_work); - mutex_lock(&local->mtx); if (!rcu_access_pointer(local->sched_scan_sdata)) { @@ -1057,19 +1085,31 @@ void ieee80211_sched_scan_stopped_work(struct work_struct *work) return; } - rcu_assign_pointer(local->sched_scan_sdata, NULL); + RCU_INIT_POINTER(local->sched_scan_sdata, NULL); + + /* If sched scan was aborted by the driver. */ + local->sched_scan_req = NULL; mutex_unlock(&local->mtx); cfg80211_sched_scan_stopped(local->hw.wiphy); } +void ieee80211_sched_scan_stopped_work(struct work_struct *work) +{ + struct ieee80211_local *local = + container_of(work, struct ieee80211_local, + sched_scan_stopped_work); + + ieee80211_sched_scan_end(local); +} + void ieee80211_sched_scan_stopped(struct ieee80211_hw *hw) { struct ieee80211_local *local = hw_to_local(hw); trace_api_sched_scan_stopped(local); - ieee80211_queue_work(&local->hw, &local->sched_scan_stopped_work); + schedule_work(&local->sched_scan_stopped_work); } EXPORT_SYMBOL(ieee80211_sched_scan_stopped); diff --git a/net/mac80211/spectmgmt.c b/net/mac80211/spectmgmt.c index 578eea3fc04..6ab00907008 100644 --- a/net/mac80211/spectmgmt.c +++ b/net/mac80211/spectmgmt.c @@ -21,6 +21,177 @@ #include "sta_info.h" #include "wme.h" +int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata, + struct ieee802_11_elems *elems, bool beacon, + enum ieee80211_band current_band, + u32 sta_flags, u8 *bssid, + struct ieee80211_csa_ie *csa_ie) +{ + enum ieee80211_band new_band; + int new_freq; + u8 new_chan_no; + struct ieee80211_channel *new_chan; + struct cfg80211_chan_def new_vht_chandef = {}; + const struct ieee80211_sec_chan_offs_ie *sec_chan_offs; + const struct ieee80211_wide_bw_chansw_ie *wide_bw_chansw_ie; + const struct ieee80211_ht_operation *ht_oper; + int secondary_channel_offset = -1; + + sec_chan_offs = elems->sec_chan_offs; + wide_bw_chansw_ie = elems->wide_bw_chansw_ie; + ht_oper = elems->ht_operation; + + if (sta_flags & (IEEE80211_STA_DISABLE_HT | + IEEE80211_STA_DISABLE_40MHZ)) { + sec_chan_offs = NULL; + wide_bw_chansw_ie = NULL; + /* only used for bandwidth here */ + ht_oper = NULL; + } + + if (sta_flags & IEEE80211_STA_DISABLE_VHT) + wide_bw_chansw_ie = NULL; + + if (elems->ext_chansw_ie) { + if (!ieee80211_operating_class_to_band( + elems->ext_chansw_ie->new_operating_class, + &new_band)) { + sdata_info(sdata, + "cannot understand ECSA IE operating class %d, disconnecting\n", + elems->ext_chansw_ie->new_operating_class); + return -EINVAL; + } + new_chan_no = elems->ext_chansw_ie->new_ch_num; + csa_ie->count = elems->ext_chansw_ie->count; + csa_ie->mode = elems->ext_chansw_ie->mode; + } else if (elems->ch_switch_ie) { + new_band = current_band; + new_chan_no = elems->ch_switch_ie->new_ch_num; + csa_ie->count = elems->ch_switch_ie->count; + csa_ie->mode = elems->ch_switch_ie->mode; + } else { + /* nothing here we understand */ + return 1; + } + + /* Mesh Channel Switch Parameters Element */ + if (elems->mesh_chansw_params_ie) { + csa_ie->ttl = elems->mesh_chansw_params_ie->mesh_ttl; + csa_ie->mode = elems->mesh_chansw_params_ie->mesh_flags; + csa_ie->pre_value = le16_to_cpu( + elems->mesh_chansw_params_ie->mesh_pre_value); + } + + new_freq = ieee80211_channel_to_frequency(new_chan_no, new_band); + new_chan = ieee80211_get_channel(sdata->local->hw.wiphy, new_freq); + if (!new_chan || new_chan->flags & IEEE80211_CHAN_DISABLED) { + sdata_info(sdata, + "BSS %pM switches to unsupported channel (%d MHz), disconnecting\n", + bssid, new_freq); + return -EINVAL; + } + + if (!beacon && sec_chan_offs) { + secondary_channel_offset = sec_chan_offs->sec_chan_offs; + } else if (beacon && ht_oper) { + secondary_channel_offset = + ht_oper->ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET; + } else if (!(sta_flags & IEEE80211_STA_DISABLE_HT)) { + /* If it's not a beacon, HT is enabled and the IE not present, + * it's 20 MHz, 802.11-2012 8.5.2.6: + * This element [the Secondary Channel Offset Element] is + * present when switching to a 40 MHz channel. It may be + * present when switching to a 20 MHz channel (in which + * case the secondary channel offset is set to SCN). + */ + secondary_channel_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; + } + + switch (secondary_channel_offset) { + default: + /* secondary_channel_offset was present but is invalid */ + case IEEE80211_HT_PARAM_CHA_SEC_NONE: + cfg80211_chandef_create(&csa_ie->chandef, new_chan, + NL80211_CHAN_HT20); + break; + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + cfg80211_chandef_create(&csa_ie->chandef, new_chan, + NL80211_CHAN_HT40PLUS); + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + cfg80211_chandef_create(&csa_ie->chandef, new_chan, + NL80211_CHAN_HT40MINUS); + break; + case -1: + cfg80211_chandef_create(&csa_ie->chandef, new_chan, + NL80211_CHAN_NO_HT); + /* keep width for 5/10 MHz channels */ + switch (sdata->vif.bss_conf.chandef.width) { + case NL80211_CHAN_WIDTH_5: + case NL80211_CHAN_WIDTH_10: + csa_ie->chandef.width = + sdata->vif.bss_conf.chandef.width; + break; + default: + break; + } + break; + } + + if (wide_bw_chansw_ie) { + new_vht_chandef.chan = new_chan; + new_vht_chandef.center_freq1 = + ieee80211_channel_to_frequency( + wide_bw_chansw_ie->new_center_freq_seg0, + new_band); + + switch (wide_bw_chansw_ie->new_channel_width) { + default: + /* hmmm, ignore VHT and use HT if present */ + case IEEE80211_VHT_CHANWIDTH_USE_HT: + new_vht_chandef.chan = NULL; + break; + case IEEE80211_VHT_CHANWIDTH_80MHZ: + new_vht_chandef.width = NL80211_CHAN_WIDTH_80; + break; + case IEEE80211_VHT_CHANWIDTH_160MHZ: + new_vht_chandef.width = NL80211_CHAN_WIDTH_160; + break; + case IEEE80211_VHT_CHANWIDTH_80P80MHZ: + /* field is otherwise reserved */ + new_vht_chandef.center_freq2 = + ieee80211_channel_to_frequency( + wide_bw_chansw_ie->new_center_freq_seg1, + new_band); + new_vht_chandef.width = NL80211_CHAN_WIDTH_80P80; + break; + } + if (sta_flags & IEEE80211_STA_DISABLE_80P80MHZ && + new_vht_chandef.width == NL80211_CHAN_WIDTH_80P80) + ieee80211_chandef_downgrade(&new_vht_chandef); + if (sta_flags & IEEE80211_STA_DISABLE_160MHZ && + new_vht_chandef.width == NL80211_CHAN_WIDTH_160) + ieee80211_chandef_downgrade(&new_vht_chandef); + if (sta_flags & IEEE80211_STA_DISABLE_40MHZ && + new_vht_chandef.width > NL80211_CHAN_WIDTH_20) + ieee80211_chandef_downgrade(&new_vht_chandef); + } + + /* if VHT data is there validate & use it */ + if (new_vht_chandef.chan) { + if (!cfg80211_chandef_compatible(&new_vht_chandef, + &csa_ie->chandef)) { + sdata_info(sdata, + "BSS %pM: CSA has inconsistent channel data, disconnecting\n", + bssid); + return -EINVAL; + } + csa_ie->chandef = new_vht_chandef; + } + + return 0; +} + static void ieee80211_send_refuse_measurement_request(struct ieee80211_sub_if_data *sdata, struct ieee80211_msrment_ie *request_ie, const u8 *da, const u8 *bssid, diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index aeb967a0aee..a9b46d8ea22 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -91,7 +91,7 @@ static int sta_info_hash_del(struct ieee80211_local *local, return -ENOENT; } -static void cleanup_single_sta(struct sta_info *sta) +static void __cleanup_single_sta(struct sta_info *sta) { int ac, i; struct tid_ampdu_tx *tid_tx; @@ -99,24 +99,8 @@ static void cleanup_single_sta(struct sta_info *sta) struct ieee80211_local *local = sdata->local; struct ps_data *ps; - /* - * At this point, when being called as call_rcu callback, - * neither mac80211 nor the driver can reference this - * sta struct any more except by still existing timers - * associated with this station that we clean up below. - * - * Note though that this still uses the sdata and even - * calls the driver in AP and mesh mode, so interfaces - * of those types mush use call sta_info_flush_cleanup() - * (typically via sta_info_flush()) before deconfiguring - * the driver. - * - * In station mode, nothing happens here so it doesn't - * have to (and doesn't) do that, this is intentional to - * speed up roaming. - */ - - if (test_sta_flag(sta, WLAN_STA_PS_STA)) { + if (test_sta_flag(sta, WLAN_STA_PS_STA) || + test_sta_flag(sta, WLAN_STA_PS_DRIVER)) { if (sta->sdata->vif.type == NL80211_IFTYPE_AP || sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN) ps = &sdata->bss->ps; @@ -126,6 +110,7 @@ static void cleanup_single_sta(struct sta_info *sta) return; clear_sta_flag(sta, WLAN_STA_PS_STA); + clear_sta_flag(sta, WLAN_STA_PS_DRIVER); atomic_dec(&ps->num_sta_ps); sta_info_recalc_tim(sta); @@ -156,39 +141,15 @@ static void cleanup_single_sta(struct sta_info *sta) ieee80211_purge_tx_queue(&local->hw, &tid_tx->pending); kfree(tid_tx); } - - sta_info_free(local, sta); } -void ieee80211_cleanup_sdata_stas(struct ieee80211_sub_if_data *sdata) -{ - struct sta_info *sta; - - spin_lock_bh(&sdata->cleanup_stations_lock); - while (!list_empty(&sdata->cleanup_stations)) { - sta = list_first_entry(&sdata->cleanup_stations, - struct sta_info, list); - list_del(&sta->list); - spin_unlock_bh(&sdata->cleanup_stations_lock); - - cleanup_single_sta(sta); - - spin_lock_bh(&sdata->cleanup_stations_lock); - } - - spin_unlock_bh(&sdata->cleanup_stations_lock); -} - -static void free_sta_rcu(struct rcu_head *h) +static void cleanup_single_sta(struct sta_info *sta) { - struct sta_info *sta = container_of(h, struct sta_info, rcu_head); struct ieee80211_sub_if_data *sdata = sta->sdata; + struct ieee80211_local *local = sdata->local; - spin_lock(&sdata->cleanup_stations_lock); - list_add_tail(&sta->list, &sdata->cleanup_stations); - spin_unlock(&sdata->cleanup_stations_lock); - - ieee80211_queue_work(&sdata->local->hw, &sdata->cleanup_stations_wk); + __cleanup_single_sta(sta); + sta_info_free(local, sta); } /* protected by RCU */ @@ -266,11 +227,20 @@ struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata, */ void sta_info_free(struct ieee80211_local *local, struct sta_info *sta) { + int i; + if (sta->rate_ctrl) rate_control_free_sta(sta); + if (sta->tx_lat) { + for (i = 0; i < IEEE80211_NUM_TIDS; i++) + kfree(sta->tx_lat[i].bins); + kfree(sta->tx_lat); + } + sta_dbg(sta->sdata, "Destroyed STA %pM\n", sta->sta.addr); + kfree(rcu_dereference_raw(sta->sta.rates)); kfree(sta); } @@ -333,13 +303,44 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata, struct ieee80211_local *local = sdata->local; struct sta_info *sta; struct timespec uptime; + struct ieee80211_tx_latency_bin_ranges *tx_latency; int i; sta = kzalloc(sizeof(*sta) + local->hw.sta_data_size, gfp); if (!sta) return NULL; + rcu_read_lock(); + tx_latency = rcu_dereference(local->tx_latency); + /* init stations Tx latency statistics && TID bins */ + if (tx_latency) { + sta->tx_lat = kzalloc(IEEE80211_NUM_TIDS * + sizeof(struct ieee80211_tx_latency_stat), + GFP_ATOMIC); + if (!sta->tx_lat) { + rcu_read_unlock(); + goto free; + } + + if (tx_latency->n_ranges) { + for (i = 0; i < IEEE80211_NUM_TIDS; i++) { + /* size of bins is size of the ranges +1 */ + sta->tx_lat[i].bin_count = + tx_latency->n_ranges + 1; + sta->tx_lat[i].bins = + kcalloc(sta->tx_lat[i].bin_count, + sizeof(u32), GFP_ATOMIC); + if (!sta->tx_lat[i].bins) { + rcu_read_unlock(); + goto free; + } + } + } + } + rcu_read_unlock(); + spin_lock_init(&sta->lock); + spin_lock_init(&sta->ps_lock); INIT_WORK(&sta->drv_unblock_wk, sta_unblock); INIT_WORK(&sta->ampdu_mlme.work, ieee80211_ba_session_work); mutex_init(&sta->ampdu_mlme.mtx); @@ -363,10 +364,8 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata, for (i = 0; i < ARRAY_SIZE(sta->chain_signal_avg); i++) ewma_init(&sta->chain_signal_avg[i], 1024, 8); - if (sta_prepare_rate_control(local, sta, gfp)) { - kfree(sta); - return NULL; - } + if (sta_prepare_rate_control(local, sta, gfp)) + goto free; for (i = 0; i < IEEE80211_NUM_TIDS; i++) { /* @@ -385,10 +384,42 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata, sta->last_seq_ctrl[i] = cpu_to_le16(USHRT_MAX); sta->sta.smps_mode = IEEE80211_SMPS_OFF; + if (sdata->vif.type == NL80211_IFTYPE_AP || + sdata->vif.type == NL80211_IFTYPE_AP_VLAN) { + struct ieee80211_supported_band *sband = + local->hw.wiphy->bands[ieee80211_get_sdata_band(sdata)]; + u8 smps = (sband->ht_cap.cap & IEEE80211_HT_CAP_SM_PS) >> + IEEE80211_HT_CAP_SM_PS_SHIFT; + /* + * Assume that hostapd advertises our caps in the beacon and + * this is the known_smps_mode for a station that just assciated + */ + switch (smps) { + case WLAN_HT_SMPS_CONTROL_DISABLED: + sta->known_smps_mode = IEEE80211_SMPS_OFF; + break; + case WLAN_HT_SMPS_CONTROL_STATIC: + sta->known_smps_mode = IEEE80211_SMPS_STATIC; + break; + case WLAN_HT_SMPS_CONTROL_DYNAMIC: + sta->known_smps_mode = IEEE80211_SMPS_DYNAMIC; + break; + default: + WARN_ON(1); + } + } sta_dbg(sdata, "Allocated STA %pM\n", sta->sta.addr); - return sta; + +free: + if (sta->tx_lat) { + for (i = 0; i < IEEE80211_NUM_TIDS; i++) + kfree(sta->tx_lat[i].bins); + kfree(sta->tx_lat); + } + kfree(sta); + return NULL; } static int sta_info_insert_check(struct sta_info *sta) @@ -467,22 +498,28 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU) goto out_err; } - /* notify driver */ - err = sta_info_insert_drv_state(local, sdata, sta); - if (err) - goto out_err; - local->num_sta++; local->sta_generation++; smp_mb(); + /* simplify things and don't accept BA sessions yet */ + set_sta_flag(sta, WLAN_STA_BLOCK_BA); + /* make the station visible */ sta_info_hash_add(local, sta); list_add_rcu(&sta->list, &local->sta_list); + /* notify driver */ + err = sta_info_insert_drv_state(local, sdata, sta); + if (err) + goto out_remove; + set_sta_flag(sta, WLAN_STA_INSERTED); + /* accept BA sessions now */ + clear_sta_flag(sta, WLAN_STA_BLOCK_BA); + ieee80211_recalc_min_chandef(sdata); ieee80211_sta_debugfs_add(sta); rate_control_add_sta_debugfs(sta); @@ -501,6 +538,12 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU) mesh_accept_plinks_update(sdata); return 0; + out_remove: + sta_info_hash_del(local, sta); + list_del_rcu(&sta->list); + local->num_sta--; + synchronize_net(); + __cleanup_single_sta(sta); out_err: mutex_unlock(&local->sta_mtx); rcu_read_lock(); @@ -510,7 +553,7 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU) int sta_info_insert_rcu(struct sta_info *sta) __acquires(RCU) { struct ieee80211_local *local = sta->local; - int err = 0; + int err; might_sleep(); @@ -528,7 +571,6 @@ int sta_info_insert_rcu(struct sta_info *sta) __acquires(RCU) return 0; out_free: - BUG_ON(!err); sta_info_free(local, sta); return err; } @@ -606,8 +648,8 @@ void sta_info_recalc_tim(struct sta_info *sta) #ifdef CONFIG_MAC80211_MESH } else if (ieee80211_vif_is_mesh(&sta->sdata->vif)) { ps = &sta->sdata->u.mesh.ps; - /* TIM map only for PLID <= IEEE80211_MAX_AID */ - id = le16_to_cpu(sta->plid) % IEEE80211_MAX_AID; + /* TIM map only for 1 <= PLID <= IEEE80211_MAX_AID */ + id = sta->plid % (IEEE80211_MAX_AID + 1); #endif } else { return; @@ -783,7 +825,7 @@ static bool sta_info_cleanup_expire_buffered(struct ieee80211_local *local, return have_buffered; } -int __must_check __sta_info_destroy(struct sta_info *sta) +static int __must_check __sta_info_destroy_part1(struct sta_info *sta) { struct ieee80211_local *local; struct ieee80211_sub_if_data *sdata; @@ -809,12 +851,35 @@ int __must_check __sta_info_destroy(struct sta_info *sta) ieee80211_sta_tear_down_BA_sessions(sta, AGG_STOP_DESTROY_STA); ret = sta_info_hash_del(local, sta); - if (ret) + if (WARN_ON(ret)) return ret; list_del_rcu(&sta->list); - /* this always calls synchronize_net() */ + drv_sta_pre_rcu_remove(local, sta->sdata, sta); + + if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN && + rcu_access_pointer(sdata->u.vlan.sta) == sta) + RCU_INIT_POINTER(sdata->u.vlan.sta, NULL); + + return 0; +} + +static void __sta_info_destroy_part2(struct sta_info *sta) +{ + struct ieee80211_local *local = sta->local; + struct ieee80211_sub_if_data *sdata = sta->sdata; + int ret; + + /* + * NOTE: This assumes at least synchronize_net() was done + * after _part1 and before _part2! + */ + + might_sleep(); + lockdep_assert_held(&local->sta_mtx); + + /* now keys can no longer be reached */ ieee80211_free_sta_keys(local, sta); sta->dead = true; @@ -822,9 +887,6 @@ int __must_check __sta_info_destroy(struct sta_info *sta) local->num_sta--; local->sta_generation++; - if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) - RCU_INIT_POINTER(sdata->u.vlan.sta, NULL); - while (sta->sta_state > IEEE80211_STA_NONE) { ret = sta_info_move_state(sta, sta->sta_state - 1); if (ret) { @@ -845,8 +907,21 @@ int __must_check __sta_info_destroy(struct sta_info *sta) rate_control_remove_sta_debugfs(sta); ieee80211_sta_debugfs_remove(sta); + ieee80211_recalc_min_chandef(sdata); - call_rcu(&sta->rcu_head, free_sta_rcu); + cleanup_single_sta(sta); +} + +int __must_check __sta_info_destroy(struct sta_info *sta) +{ + int err = __sta_info_destroy_part1(sta); + + if (err) + return err; + + synchronize_net(); + + __sta_info_destroy_part2(sta); return 0; } @@ -916,32 +991,38 @@ void sta_info_stop(struct ieee80211_local *local) } -int sta_info_flush_defer(struct ieee80211_sub_if_data *sdata) +int __sta_info_flush(struct ieee80211_sub_if_data *sdata, bool vlans) { struct ieee80211_local *local = sdata->local; struct sta_info *sta, *tmp; + LIST_HEAD(free_list); int ret = 0; might_sleep(); + WARN_ON(vlans && sdata->vif.type != NL80211_IFTYPE_AP); + WARN_ON(vlans && !sdata->bss); + mutex_lock(&local->sta_mtx); list_for_each_entry_safe(sta, tmp, &local->sta_list, list) { - if (sdata == sta->sdata) { - WARN_ON(__sta_info_destroy(sta)); + if (sdata == sta->sdata || + (vlans && sdata->bss == sta->sdata->bss)) { + if (!WARN_ON(__sta_info_destroy_part1(sta))) + list_add(&sta->free_list, &free_list); ret++; } } + + if (!list_empty(&free_list)) { + synchronize_net(); + list_for_each_entry_safe(sta, tmp, &free_list, free_list) + __sta_info_destroy_part2(sta); + } mutex_unlock(&local->sta_mtx); return ret; } -void sta_info_flush_cleanup(struct ieee80211_sub_if_data *sdata) -{ - ieee80211_cleanup_sdata_stas(sdata); - cancel_work_sync(&sdata->cleanup_stations_wk); -} - void ieee80211_sta_expire(struct ieee80211_sub_if_data *sdata, unsigned long exp_time) { @@ -1011,10 +1092,14 @@ struct ieee80211_sta *ieee80211_find_sta(struct ieee80211_vif *vif, } EXPORT_SYMBOL(ieee80211_find_sta); -static void clear_sta_ps_flags(void *_sta) +/* powersave support code */ +void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta) { - struct sta_info *sta = _sta; struct ieee80211_sub_if_data *sdata = sta->sdata; + struct ieee80211_local *local = sdata->local; + struct sk_buff_head pending; + int filtered = 0, buffered = 0, ac; + unsigned long flags; struct ps_data *ps; if (sdata->vif.type == NL80211_IFTYPE_AP || @@ -1025,20 +1110,6 @@ static void clear_sta_ps_flags(void *_sta) else return; - clear_sta_flag(sta, WLAN_STA_PS_DRIVER); - if (test_and_clear_sta_flag(sta, WLAN_STA_PS_STA)) - atomic_dec(&ps->num_sta_ps); -} - -/* powersave support code */ -void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta) -{ - struct ieee80211_sub_if_data *sdata = sta->sdata; - struct ieee80211_local *local = sdata->local; - struct sk_buff_head pending; - int filtered = 0, buffered = 0, ac; - unsigned long flags; - clear_sta_flag(sta, WLAN_STA_SP); BUILD_BUG_ON(BITS_TO_LONGS(IEEE80211_NUM_TIDS) > 1); @@ -1049,6 +1120,8 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta) skb_queue_head_init(&pending); + /* sync with ieee80211_tx_h_unicast_ps_buf */ + spin_lock(&sta->ps_lock); /* Send all buffered frames to the station */ for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { int count = skb_queue_len(&pending), tmp; @@ -1067,7 +1140,26 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta) buffered += tmp - count; } - ieee80211_add_pending_skbs_fn(local, &pending, clear_sta_ps_flags, sta); + ieee80211_add_pending_skbs(local, &pending); + clear_sta_flag(sta, WLAN_STA_PS_DRIVER); + clear_sta_flag(sta, WLAN_STA_PS_STA); + spin_unlock(&sta->ps_lock); + + atomic_dec(&ps->num_sta_ps); + + /* This station just woke up and isn't aware of our SMPS state */ + if (!ieee80211_vif_is_mesh(&sdata->vif) && + !ieee80211_smps_is_restrictive(sta->known_smps_mode, + sdata->smps_mode) && + sta->known_smps_mode != sdata->bss->req_smps && + sta_info_tx_streams(sta) != 1) { + ht_dbg(sdata, + "%pM just woke up and MIMO capable - update SMPS\n", + sta->sta.addr); + ieee80211_send_smps_action(sdata, sdata->bss->req_smps, + sta->sta.addr, + sdata->vif.bss_conf.bssid); + } local->total_ps_buffered -= buffered; @@ -1080,7 +1172,8 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta) static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata, struct sta_info *sta, int tid, - enum ieee80211_frame_release_type reason) + enum ieee80211_frame_release_type reason, + bool call_driver) { struct ieee80211_local *local = sdata->local; struct ieee80211_qos_hdr *nullfunc; @@ -1114,6 +1207,7 @@ static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata, memcpy(nullfunc->addr1, sta->sta.addr, ETH_ALEN); memcpy(nullfunc->addr2, sdata->vif.addr, ETH_ALEN); memcpy(nullfunc->addr3, sdata->vif.addr, ETH_ALEN); + nullfunc->seq_ctrl = 0; skb->priority = tid; skb_set_queue_mapping(skb, ieee802_1d_to_ac[tid]); @@ -1138,7 +1232,9 @@ static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata, IEEE80211_TX_STATUS_EOSP | IEEE80211_TX_CTL_REQ_TX_STATUS; - drv_allow_buffered_frames(local, sta, BIT(tid), 1, reason, false); + if (call_driver) + drv_allow_buffered_frames(local, sta, BIT(tid), 1, + reason, false); skb->dev = sdata->dev; @@ -1154,6 +1250,17 @@ static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata, rcu_read_unlock(); } +static int find_highest_prio_tid(unsigned long tids) +{ + /* lower 3 TIDs aren't ordered perfectly */ + if (tids & 0xF8) + return fls(tids) - 1; + /* TID 0 is BE just like TID 3 */ + if (tids & BIT(0)) + return 0; + return fls(tids) - 1; +} + static void ieee80211_sta_ps_deliver_response(struct sta_info *sta, int n_frames, u8 ignored_acs, @@ -1161,7 +1268,6 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, { struct ieee80211_sub_if_data *sdata = sta->sdata; struct ieee80211_local *local = sdata->local; - bool found = false; bool more_data = false; int ac; unsigned long driver_release_tids = 0; @@ -1172,9 +1278,7 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, __skb_queue_head_init(&frames); - /* - * Get response frame(s) and more data bit for it. - */ + /* Get response frame(s) and more data bit for the last one. */ for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { unsigned long tids; @@ -1183,43 +1287,48 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, tids = ieee80211_tids_for_ac(ac); - if (!found) { - driver_release_tids = sta->driver_buffered_tids & tids; - if (driver_release_tids) { - found = true; - } else { - struct sk_buff *skb; - - while (n_frames > 0) { - skb = skb_dequeue(&sta->tx_filtered[ac]); - if (!skb) { - skb = skb_dequeue( - &sta->ps_tx_buf[ac]); - if (skb) - local->total_ps_buffered--; - } - if (!skb) - break; - n_frames--; - found = true; - __skb_queue_tail(&frames, skb); - } - } + /* if we already have frames from software, then we can't also + * release from hardware queues + */ + if (skb_queue_empty(&frames)) + driver_release_tids |= sta->driver_buffered_tids & tids; - /* - * If the driver has data on more than one TID then + if (driver_release_tids) { + /* If the driver has data on more than one TID then * certainly there's more data if we release just a - * single frame now (from a single TID). + * single frame now (from a single TID). This will + * only happen for PS-Poll. */ if (reason == IEEE80211_FRAME_RELEASE_PSPOLL && hweight16(driver_release_tids) > 1) { more_data = true; driver_release_tids = - BIT(ffs(driver_release_tids) - 1); + BIT(find_highest_prio_tid( + driver_release_tids)); break; } + } else { + struct sk_buff *skb; + + while (n_frames > 0) { + skb = skb_dequeue(&sta->tx_filtered[ac]); + if (!skb) { + skb = skb_dequeue( + &sta->ps_tx_buf[ac]); + if (skb) + local->total_ps_buffered--; + } + if (!skb) + break; + n_frames--; + __skb_queue_tail(&frames, skb); + } } + /* If we have more frames buffered on this AC, then set the + * more-data bit and abort the loop since we can't send more + * data from other ACs before the buffered frames from this. + */ if (!skb_queue_empty(&sta->tx_filtered[ac]) || !skb_queue_empty(&sta->ps_tx_buf[ac])) { more_data = true; @@ -1227,7 +1336,7 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, } } - if (!found) { + if (skb_queue_empty(&frames) && !driver_release_tids) { int tid; /* @@ -1248,15 +1357,13 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, /* This will evaluate to 1, 3, 5 or 7. */ tid = 7 - ((ffs(~ignored_acs) - 1) << 1); - ieee80211_send_null_response(sdata, sta, tid, reason); - return; - } - - if (!driver_release_tids) { + ieee80211_send_null_response(sdata, sta, tid, reason, true); + } else if (!driver_release_tids) { struct sk_buff_head pending; struct sk_buff *skb; int num = 0; u16 tids = 0; + bool need_null = false; skb_queue_head_init(&pending); @@ -1290,22 +1397,57 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, ieee80211_is_qos_nullfunc(hdr->frame_control)) qoshdr = ieee80211_get_qos_ctl(hdr); - /* end service period after last frame */ - if (skb_queue_empty(&frames)) { - if (reason == IEEE80211_FRAME_RELEASE_UAPSD && - qoshdr) - *qoshdr |= IEEE80211_QOS_CTL_EOSP; + tids |= BIT(skb->priority); + + __skb_queue_tail(&pending, skb); + + /* end service period after last frame or add one */ + if (!skb_queue_empty(&frames)) + continue; + if (reason != IEEE80211_FRAME_RELEASE_UAPSD) { + /* for PS-Poll, there's only one frame */ info->flags |= IEEE80211_TX_STATUS_EOSP | IEEE80211_TX_CTL_REQ_TX_STATUS; + break; } - if (qoshdr) - tids |= BIT(*qoshdr & IEEE80211_QOS_CTL_TID_MASK); - else - tids |= BIT(0); + /* For uAPSD, things are a bit more complicated. If the + * last frame has a QoS header (i.e. is a QoS-data or + * QoS-nulldata frame) then just set the EOSP bit there + * and be done. + * If the frame doesn't have a QoS header (which means + * it should be a bufferable MMPDU) then we can't set + * the EOSP bit in the QoS header; add a QoS-nulldata + * frame to the list to send it after the MMPDU. + * + * Note that this code is only in the mac80211-release + * code path, we assume that the driver will not buffer + * anything but QoS-data frames, or if it does, will + * create the QoS-nulldata frame by itself if needed. + * + * Cf. 802.11-2012 10.2.1.10 (c). + */ + if (qoshdr) { + *qoshdr |= IEEE80211_QOS_CTL_EOSP; - __skb_queue_tail(&pending, skb); + info->flags |= IEEE80211_TX_STATUS_EOSP | + IEEE80211_TX_CTL_REQ_TX_STATUS; + } else { + /* The standard isn't completely clear on this + * as it says the more-data bit should be set + * if there are more BUs. The QoS-Null frame + * we're about to send isn't buffered yet, we + * only create it below, but let's pretend it + * was buffered just in case some clients only + * expect more-data=0 when eosp=1. + */ + hdr->frame_control |= + cpu_to_le16(IEEE80211_FCTL_MOREDATA); + need_null = true; + num++; + } + break; } drv_allow_buffered_frames(local, sta, tids, num, @@ -1313,17 +1455,22 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, ieee80211_add_pending_skbs(local, &pending); + if (need_null) + ieee80211_send_null_response( + sdata, sta, find_highest_prio_tid(tids), + reason, false); + sta_info_recalc_tim(sta); } else { /* * We need to release a frame that is buffered somewhere in the * driver ... it'll have to handle that. - * Note that, as per the comment above, it'll also have to see - * if there is more than just one frame on the specific TID that - * we're releasing from, and it needs to set the more-data bit - * accordingly if we tell it that there's no more data. If we do - * tell it there's more data, then of course the more-data bit - * needs to be set anyway. + * Note that the driver also has to check the number of frames + * on the TIDs we're releasing from - if there are more than + * n_frames it has to set the more-data bit (if we didn't ask + * it to set it anyway due to other buffered frames); if there + * are fewer than n_frames it has to make sure to adjust that + * to allow the service period to end properly. */ drv_release_buffered_frames(local, sta, driver_release_tids, n_frames, reason, more_data); @@ -1331,9 +1478,9 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, /* * Note that we don't recalculate the TIM bit here as it would * most likely have no effect at all unless the driver told us - * that the TID became empty before returning here from the + * that the TID(s) became empty before returning here from the * release function. - * Either way, however, when the driver tells us that the TID + * Either way, however, when the driver tells us that the TID(s) * became empty we'll do the TIM recalculation. */ } @@ -1422,6 +1569,8 @@ void ieee80211_sta_set_buffered(struct ieee80211_sta *pubsta, if (WARN_ON(tid >= IEEE80211_NUM_TIDS)) return; + trace_api_sta_set_buffered(sta->local, pubsta, tid, buffered); + if (buffered) set_bit(tid, &sta->driver_buffered_tids); else @@ -1520,3 +1669,38 @@ int sta_info_move_state(struct sta_info *sta, return 0; } + +u8 sta_info_tx_streams(struct sta_info *sta) +{ + struct ieee80211_sta_ht_cap *ht_cap = &sta->sta.ht_cap; + u8 rx_streams; + + if (!sta->sta.ht_cap.ht_supported) + return 1; + + if (sta->sta.vht_cap.vht_supported) { + int i; + u16 tx_mcs_map = + le16_to_cpu(sta->sta.vht_cap.vht_mcs.tx_mcs_map); + + for (i = 7; i >= 0; i--) + if ((tx_mcs_map & (0x3 << (i * 2))) != + IEEE80211_VHT_MCS_NOT_SUPPORTED) + return i + 1; + } + + if (ht_cap->mcs.rx_mask[3]) + rx_streams = 4; + else if (ht_cap->mcs.rx_mask[2]) + rx_streams = 3; + else if (ht_cap->mcs.rx_mask[1]) + rx_streams = 2; + else + rx_streams = 1; + + if (!(ht_cap->mcs.tx_params & IEEE80211_HT_MCS_TX_RX_DIFF)) + return rx_streams; + + return ((ht_cap->mcs.tx_params & IEEE80211_HT_MCS_TX_MAX_STREAMS_MASK) + >> IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT) + 1; +} diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index 4208dbd5861..4acc5fc402f 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -220,6 +220,25 @@ struct sta_ampdu_mlme { u8 dialog_token_allocator; }; +/* + * struct ieee80211_tx_latency_stat - Tx latency statistics + * + * Measures TX latency and jitter for a station per TID. + * + * @max: worst case latency + * @sum: sum of all latencies + * @counter: amount of Tx frames sent from interface + * @bins: each bin counts how many frames transmitted within a certain + * latency range. when disabled it is NULL. + * @bin_count: amount of bins. + */ +struct ieee80211_tx_latency_stat { + u32 max; + u32 sum; + u32 counter; + u32 *bins; + u32 bin_count; +}; /** * struct sta_info - STA information @@ -228,23 +247,28 @@ struct sta_ampdu_mlme { * mac80211 is communicating with. * * @list: global linked list entry + * @free_list: list entry for keeping track of stations to free * @hnext: hash table linked list pointer * @local: pointer to the global information * @sdata: virtual interface this station belongs to - * @ptk: peer key negotiated with this station, if any + * @ptk: peer keys negotiated with this station, if any + * @ptk_idx: last installed peer key index * @gtk: group keys negotiated with this station, if any + * @gtk_idx: last installed group key index * @rate_ctrl: rate control algorithm reference * @rate_ctrl_priv: rate control private per-STA pointer * @last_tx_rate: rate used for last transmit, to report to userspace as * "the" transmit rate * @last_rx_rate_idx: rx status rate index of the last data packet * @last_rx_rate_flag: rx status flag of the last data packet + * @last_rx_rate_vht_flag: rx status vht flag of the last data packet * @last_rx_rate_vht_nss: rx status nss of last data packet * @lock: used for locking all fields that require locking, see comments * in the header file. * @drv_unblock_wk: used for driver PS unblocking * @listen_interval: listen interval of this station, when we're acting as AP * @_flags: STA flags, see &enum ieee80211_sta_info_flags, do not use directly + * @ps_lock: used for powersave (when mac80211 is the AP) related locking * @ps_tx_buf: buffers (per AC) of frames to transmit to this station * when it leaves power saving state or polls * @tx_filtered: buffers (per AC) of frames we already tried to @@ -274,6 +298,7 @@ struct sta_ampdu_mlme { * @tid_seq: per-TID sequence numbers for sending to this STA * @ampdu_mlme: A-MPDU state machine state * @timer_to_tid: identity mapping to ID timers + * @tx_lat: Tx latency statistics * @llid: Local link ID * @plid: Peer link ID * @reason: Cancel reason on PLINK_HOLDING state @@ -301,16 +326,21 @@ struct sta_ampdu_mlme { * @chains: chains ever used for RX from this station * @chain_signal_last: last signal (per chain) * @chain_signal_avg: signal average (per chain) + * @known_smps_mode: the smps_mode the client thinks we are in. Relevant for + * AP only. + * @cipher_scheme: optional cipher scheme for this station */ struct sta_info { /* General information, mostly static */ - struct list_head list; + struct list_head list, free_list; struct rcu_head rcu_head; struct sta_info __rcu *hnext; struct ieee80211_local *local; struct ieee80211_sub_if_data *sdata; struct ieee80211_key __rcu *gtk[NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS]; - struct ieee80211_key __rcu *ptk; + struct ieee80211_key __rcu *ptk[NUM_DEFAULT_KEYS]; + u8 gtk_idx; + u8 ptk_idx; struct rate_control_ref *rate_ctrl; void *rate_ctrl_priv; spinlock_t lock; @@ -328,10 +358,8 @@ struct sta_info { /* use the accessors defined below */ unsigned long _flags; - /* - * STA powersave frame queues, no more than the internal - * locking required. - */ + /* STA powersave lock and frame queues */ + spinlock_t ps_lock; struct sk_buff_head ps_tx_buf[IEEE80211_NUM_ACS]; struct sk_buff_head tx_filtered[IEEE80211_NUM_ACS]; unsigned long driver_buffered_tids; @@ -369,6 +397,7 @@ struct sta_info { struct ieee80211_tx_rate last_tx_rate; int last_rx_rate_idx; u32 last_rx_rate_flag; + u32 last_rx_rate_vht_flag; u8 last_rx_rate_vht_nss; u16 tid_seq[IEEE80211_QOS_CTL_TID_MASK + 1]; @@ -378,14 +407,16 @@ struct sta_info { struct sta_ampdu_mlme ampdu_mlme; u8 timer_to_tid[IEEE80211_NUM_TIDS]; + struct ieee80211_tx_latency_stat *tx_lat; + #ifdef CONFIG_MAC80211_MESH /* * Mesh peer link attributes * TODO: move to a sub-structure that is referenced with pointer? */ - __le16 llid; - __le16 plid; - __le16 reason; + u16 llid; + u16 plid; + u16 reason; u8 plink_retries; bool ignore_plink_timer; enum nl80211_plink_state plink_state; @@ -411,6 +442,9 @@ struct sta_info { unsigned int lost_packets; unsigned int beacon_loss_count; + enum ieee80211_smps_mode known_smps_mode; + const struct ieee80211_cipher_scheme *cipher_scheme; + /* keep last! */ struct ieee80211_sta sta; }; @@ -573,21 +607,6 @@ void sta_info_recalc_tim(struct sta_info *sta); void sta_info_init(struct ieee80211_local *local); void sta_info_stop(struct ieee80211_local *local); -int sta_info_flush_defer(struct ieee80211_sub_if_data *sdata); - -/** - * sta_info_flush_cleanup - flush the sta_info cleanup queue - * @sdata: the interface - * - * Flushes the sta_info cleanup queue for a given interface; - * this is necessary before the interface is removed or, for - * AP/mesh interfaces, before it is deconfigured. - * - * Note an rcu_barrier() must precede the function, after all - * stations have been flushed/removed to ensure the call_rcu() - * calls that add stations to the cleanup queue have completed. - */ -void sta_info_flush_cleanup(struct ieee80211_sub_if_data *sdata); /** * sta_info_flush - flush matching STA entries from the STA table @@ -595,15 +614,13 @@ void sta_info_flush_cleanup(struct ieee80211_sub_if_data *sdata); * Returns the number of removed STA entries. * * @sdata: sdata to remove all stations from + * @vlans: if the given interface is an AP interface, also flush VLANs */ +int __sta_info_flush(struct ieee80211_sub_if_data *sdata, bool vlans); + static inline int sta_info_flush(struct ieee80211_sub_if_data *sdata) { - int ret = sta_info_flush_defer(sdata); - - rcu_barrier(); - sta_info_flush_cleanup(sdata); - - return ret; + return __sta_info_flush(sdata, false); } void sta_set_rate_info_tx(struct sta_info *sta, @@ -613,11 +630,10 @@ void sta_set_rate_info_rx(struct sta_info *sta, struct rate_info *rinfo); void ieee80211_sta_expire(struct ieee80211_sub_if_data *sdata, unsigned long exp_time); +u8 sta_info_tx_streams(struct sta_info *sta); void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta); void ieee80211_sta_ps_deliver_poll_response(struct sta_info *sta); void ieee80211_sta_ps_deliver_uapsd(struct sta_info *sta); -void ieee80211_cleanup_sdata_stas(struct ieee80211_sub_if_data *sdata); - #endif /* STA_INFO_H */ diff --git a/net/mac80211/status.c b/net/mac80211/status.c index 368837fe3b8..ba29ebc8614 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -11,6 +11,7 @@ #include <linux/export.h> #include <linux/etherdevice.h> +#include <linux/time.h> #include <net/mac80211.h> #include <asm/unaligned.h> #include "ieee80211_i.h" @@ -180,6 +181,9 @@ static void ieee80211_frame_acked(struct sta_info *sta, struct sk_buff *skb) struct ieee80211_local *local = sta->local; struct ieee80211_sub_if_data *sdata = sta->sdata; + if (local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS) + sta->last_rx = jiffies; + if (ieee80211_is_data_qos(mgmt->frame_control)) { struct ieee80211_hdr *hdr = (void *) skb->data; u8 *qc = ieee80211_get_qos_ctl(hdr); @@ -191,29 +195,36 @@ static void ieee80211_frame_acked(struct sta_info *sta, struct sk_buff *skb) if (ieee80211_is_action(mgmt->frame_control) && mgmt->u.action.category == WLAN_CATEGORY_HT && mgmt->u.action.u.ht_smps.action == WLAN_HT_ACTION_SMPS && - sdata->vif.type == NL80211_IFTYPE_STATION && ieee80211_sdata_running(sdata)) { - /* - * This update looks racy, but isn't -- if we come - * here we've definitely got a station that we're - * talking to, and on a managed interface that can - * only be the AP. And the only other place updating - * this variable in managed mode is before association. - */ + enum ieee80211_smps_mode smps_mode; + switch (mgmt->u.action.u.ht_smps.smps_control) { case WLAN_HT_SMPS_CONTROL_DYNAMIC: - sdata->smps_mode = IEEE80211_SMPS_DYNAMIC; + smps_mode = IEEE80211_SMPS_DYNAMIC; break; case WLAN_HT_SMPS_CONTROL_STATIC: - sdata->smps_mode = IEEE80211_SMPS_STATIC; + smps_mode = IEEE80211_SMPS_STATIC; break; case WLAN_HT_SMPS_CONTROL_DISABLED: default: /* shouldn't happen since we don't send that */ - sdata->smps_mode = IEEE80211_SMPS_OFF; + smps_mode = IEEE80211_SMPS_OFF; break; } - ieee80211_queue_work(&local->hw, &sdata->recalc_smps); + if (sdata->vif.type == NL80211_IFTYPE_STATION) { + /* + * This update looks racy, but isn't -- if we come + * here we've definitely got a station that we're + * talking to, and on a managed interface that can + * only be the AP. And the only other place updating + * this variable in managed mode is before association. + */ + sdata->smps_mode = smps_mode; + ieee80211_queue_work(&local->hw, &sdata->recalc_smps); + } else if (sdata->vif.type == NL80211_IFTYPE_AP || + sdata->vif.type == NL80211_IFTYPE_AP_VLAN) { + sta->known_smps_mode = smps_mode; + } } } @@ -303,10 +314,9 @@ ieee80211_add_tx_radiotap_header(struct ieee80211_local *local, !is_multicast_ether_addr(hdr->addr1)) txflags |= IEEE80211_RADIOTAP_F_TX_FAIL; - if ((info->status.rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) || - (info->status.rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT)) + if (info->status.rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT) txflags |= IEEE80211_RADIOTAP_F_TX_CTS; - else if (info->status.rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) + if (info->status.rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) txflags |= IEEE80211_RADIOTAP_F_TX_RTS; put_unaligned_le16(txflags, pos); @@ -453,6 +463,76 @@ static void ieee80211_report_used_skb(struct ieee80211_local *local, } /* + * Measure Tx frame completion and removal time for Tx latency statistics + * calculation. A single Tx frame latency should be measured from when it + * is entering the Kernel until we receive Tx complete confirmation indication + * and remove the skb. + */ +static void ieee80211_tx_latency_end_msrmnt(struct ieee80211_local *local, + struct sk_buff *skb, + struct sta_info *sta, + struct ieee80211_hdr *hdr) +{ + ktime_t skb_dprt; + struct timespec dprt_time; + u32 msrmnt; + u16 tid; + u8 *qc; + int i, bin_range_count; + u32 *bin_ranges; + __le16 fc; + struct ieee80211_tx_latency_stat *tx_lat; + struct ieee80211_tx_latency_bin_ranges *tx_latency; + ktime_t skb_arv = skb->tstamp; + + tx_latency = rcu_dereference(local->tx_latency); + + /* assert Tx latency stats are enabled & frame arrived when enabled */ + if (!tx_latency || !ktime_to_ns(skb_arv)) + return; + + fc = hdr->frame_control; + + if (!ieee80211_is_data(fc)) /* make sure it is a data frame */ + return; + + /* get frame tid */ + if (ieee80211_is_data_qos(hdr->frame_control)) { + qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK; + } else { + tid = 0; + } + + tx_lat = &sta->tx_lat[tid]; + + ktime_get_ts(&dprt_time); /* time stamp completion time */ + skb_dprt = ktime_set(dprt_time.tv_sec, dprt_time.tv_nsec); + msrmnt = ktime_to_ms(ktime_sub(skb_dprt, skb_arv)); + + if (tx_lat->max < msrmnt) /* update stats */ + tx_lat->max = msrmnt; + tx_lat->counter++; + tx_lat->sum += msrmnt; + + if (!tx_lat->bins) /* bins not activated */ + return; + + /* count how many Tx frames transmitted with the appropriate latency */ + bin_range_count = tx_latency->n_ranges; + bin_ranges = tx_latency->ranges; + + for (i = 0; i < bin_range_count; i++) { + if (msrmnt <= bin_ranges[i]) { + tx_lat->bins[i]++; + break; + } + } + if (i == bin_range_count) /* msrmnt is bigger than the biggest range */ + tx_lat->bins[i]++; +} + +/* * Use a static threshold for now, best value to be determined * by testing ... * Should it depend on: @@ -461,6 +541,23 @@ static void ieee80211_report_used_skb(struct ieee80211_local *local, */ #define STA_LOST_PKT_THRESHOLD 50 +static void ieee80211_lost_packet(struct sta_info *sta, struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + /* This packet was aggregated but doesn't carry status info */ + if ((info->flags & IEEE80211_TX_CTL_AMPDU) && + !(info->flags & IEEE80211_TX_STAT_AMPDU)) + return; + + if (++sta->lost_packets < STA_LOST_PKT_THRESHOLD) + return; + + cfg80211_cqm_pktloss_notify(sta->sdata->dev, sta->sta.addr, + sta->lost_packets, GFP_ATOMIC); + sta->lost_packets = 0; +} + void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb) { struct sk_buff *skb2; @@ -537,6 +634,7 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb) sta, true, acked); if ((local->hw.flags & IEEE80211_HW_HAS_RATE_CONTROL) && + (ieee80211_is_data(hdr->frame_control)) && (rates_idx != -1)) sta->last_tx_rate = info->status.rates[rates_idx]; @@ -599,17 +697,19 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb) if (info->flags & IEEE80211_TX_STAT_ACK) { if (sta->lost_packets) sta->lost_packets = 0; - } else if (++sta->lost_packets >= STA_LOST_PKT_THRESHOLD) { - cfg80211_cqm_pktloss_notify(sta->sdata->dev, - sta->sta.addr, - sta->lost_packets, - GFP_ATOMIC); - sta->lost_packets = 0; + } else { + ieee80211_lost_packet(sta, skb); } } if (acked) sta->last_ack_signal = info->status.ack_signal; + + /* + * Measure frame removal for tx latency + * statistics calculation + */ + ieee80211_tx_latency_end_msrmnt(local, skb, sta, hdr); } rcu_read_unlock(); diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c new file mode 100644 index 00000000000..652813b2d3d --- /dev/null +++ b/net/mac80211/tdls.c @@ -0,0 +1,325 @@ +/* + * mac80211 TDLS handling code + * + * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2014, Intel Corporation + * + * This file is GPLv2 as found in COPYING. + */ + +#include <linux/ieee80211.h> +#include "ieee80211_i.h" + +static void ieee80211_tdls_add_ext_capab(struct sk_buff *skb) +{ + u8 *pos = (void *)skb_put(skb, 7); + + *pos++ = WLAN_EID_EXT_CAPABILITY; + *pos++ = 5; /* len */ + *pos++ = 0x0; + *pos++ = 0x0; + *pos++ = 0x0; + *pos++ = 0x0; + *pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED; +} + +static u16 ieee80211_get_tdls_sta_capab(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_local *local = sdata->local; + u16 capab; + + capab = 0; + if (ieee80211_get_sdata_band(sdata) != IEEE80211_BAND_2GHZ) + return capab; + + if (!(local->hw.flags & IEEE80211_HW_2GHZ_SHORT_SLOT_INCAPABLE)) + capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME; + if (!(local->hw.flags & IEEE80211_HW_2GHZ_SHORT_PREAMBLE_INCAPABLE)) + capab |= WLAN_CAPABILITY_SHORT_PREAMBLE; + + return capab; +} + +static void ieee80211_tdls_add_link_ie(struct sk_buff *skb, const u8 *src_addr, + const u8 *peer, const u8 *bssid) +{ + struct ieee80211_tdls_lnkie *lnkid; + + lnkid = (void *)skb_put(skb, sizeof(struct ieee80211_tdls_lnkie)); + + lnkid->ie_type = WLAN_EID_LINK_ID; + lnkid->ie_len = sizeof(struct ieee80211_tdls_lnkie) - 2; + + memcpy(lnkid->bssid, bssid, ETH_ALEN); + memcpy(lnkid->init_sta, src_addr, ETH_ALEN); + memcpy(lnkid->resp_sta, peer, ETH_ALEN); +} + +static int +ieee80211_prep_tdls_encap_data(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, struct sk_buff *skb) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + enum ieee80211_band band = ieee80211_get_sdata_band(sdata); + struct ieee80211_tdls_data *tf; + + tf = (void *)skb_put(skb, offsetof(struct ieee80211_tdls_data, u)); + + memcpy(tf->da, peer, ETH_ALEN); + memcpy(tf->sa, sdata->vif.addr, ETH_ALEN); + tf->ether_type = cpu_to_be16(ETH_P_TDLS); + tf->payload_type = WLAN_TDLS_SNAP_RFTYPE; + + switch (action_code) { + case WLAN_TDLS_SETUP_REQUEST: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_SETUP_REQUEST; + + skb_put(skb, sizeof(tf->u.setup_req)); + tf->u.setup_req.dialog_token = dialog_token; + tf->u.setup_req.capability = + cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); + + ieee80211_add_srates_ie(sdata, skb, false, band); + ieee80211_add_ext_srates_ie(sdata, skb, false, band); + ieee80211_tdls_add_ext_capab(skb); + break; + case WLAN_TDLS_SETUP_RESPONSE: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_SETUP_RESPONSE; + + skb_put(skb, sizeof(tf->u.setup_resp)); + tf->u.setup_resp.status_code = cpu_to_le16(status_code); + tf->u.setup_resp.dialog_token = dialog_token; + tf->u.setup_resp.capability = + cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); + + ieee80211_add_srates_ie(sdata, skb, false, band); + ieee80211_add_ext_srates_ie(sdata, skb, false, band); + ieee80211_tdls_add_ext_capab(skb); + break; + case WLAN_TDLS_SETUP_CONFIRM: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_SETUP_CONFIRM; + + skb_put(skb, sizeof(tf->u.setup_cfm)); + tf->u.setup_cfm.status_code = cpu_to_le16(status_code); + tf->u.setup_cfm.dialog_token = dialog_token; + break; + case WLAN_TDLS_TEARDOWN: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_TEARDOWN; + + skb_put(skb, sizeof(tf->u.teardown)); + tf->u.teardown.reason_code = cpu_to_le16(status_code); + break; + case WLAN_TDLS_DISCOVERY_REQUEST: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_DISCOVERY_REQUEST; + + skb_put(skb, sizeof(tf->u.discover_req)); + tf->u.discover_req.dialog_token = dialog_token; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int +ieee80211_prep_tdls_direct(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, struct sk_buff *skb) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + enum ieee80211_band band = ieee80211_get_sdata_band(sdata); + struct ieee80211_mgmt *mgmt; + + mgmt = (void *)skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, peer, ETH_ALEN); + memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN); + memcpy(mgmt->bssid, sdata->u.mgd.bssid, ETH_ALEN); + + mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_ACTION); + + switch (action_code) { + case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: + skb_put(skb, 1 + sizeof(mgmt->u.action.u.tdls_discover_resp)); + mgmt->u.action.category = WLAN_CATEGORY_PUBLIC; + mgmt->u.action.u.tdls_discover_resp.action_code = + WLAN_PUB_ACTION_TDLS_DISCOVER_RES; + mgmt->u.action.u.tdls_discover_resp.dialog_token = + dialog_token; + mgmt->u.action.u.tdls_discover_resp.capability = + cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); + + ieee80211_add_srates_ie(sdata, skb, false, band); + ieee80211_add_ext_srates_ie(sdata, skb, false, band); + ieee80211_tdls_add_ext_capab(skb); + break; + default: + return -EINVAL; + } + + return 0; +} + +int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, u32 peer_capability, + const u8 *extra_ies, size_t extra_ies_len) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; + struct sk_buff *skb = NULL; + bool send_direct; + int ret; + + if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) + return -ENOTSUPP; + + /* make sure we are in managed mode, and associated */ + if (sdata->vif.type != NL80211_IFTYPE_STATION || + !sdata->u.mgd.associated) + return -EINVAL; + + tdls_dbg(sdata, "TDLS mgmt action %d peer %pM\n", + action_code, peer); + + skb = dev_alloc_skb(local->hw.extra_tx_headroom + + max(sizeof(struct ieee80211_mgmt), + sizeof(struct ieee80211_tdls_data)) + + 50 + /* supported rates */ + 7 + /* ext capab */ + extra_ies_len + + sizeof(struct ieee80211_tdls_lnkie)); + if (!skb) + return -ENOMEM; + + skb_reserve(skb, local->hw.extra_tx_headroom); + + switch (action_code) { + case WLAN_TDLS_SETUP_REQUEST: + case WLAN_TDLS_SETUP_RESPONSE: + case WLAN_TDLS_SETUP_CONFIRM: + case WLAN_TDLS_TEARDOWN: + case WLAN_TDLS_DISCOVERY_REQUEST: + ret = ieee80211_prep_tdls_encap_data(wiphy, dev, peer, + action_code, dialog_token, + status_code, skb); + send_direct = false; + break; + case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: + ret = ieee80211_prep_tdls_direct(wiphy, dev, peer, action_code, + dialog_token, status_code, + skb); + send_direct = true; + break; + default: + ret = -ENOTSUPP; + break; + } + + if (ret < 0) + goto fail; + + if (extra_ies_len) + memcpy(skb_put(skb, extra_ies_len), extra_ies, extra_ies_len); + + /* the TDLS link IE is always added last */ + switch (action_code) { + case WLAN_TDLS_SETUP_REQUEST: + case WLAN_TDLS_SETUP_CONFIRM: + case WLAN_TDLS_TEARDOWN: + case WLAN_TDLS_DISCOVERY_REQUEST: + /* we are the initiator */ + ieee80211_tdls_add_link_ie(skb, sdata->vif.addr, peer, + sdata->u.mgd.bssid); + break; + case WLAN_TDLS_SETUP_RESPONSE: + case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: + /* we are the responder */ + ieee80211_tdls_add_link_ie(skb, peer, sdata->vif.addr, + sdata->u.mgd.bssid); + break; + default: + ret = -ENOTSUPP; + goto fail; + } + + if (send_direct) { + ieee80211_tx_skb(sdata, skb); + return 0; + } + + /* + * According to 802.11z: Setup req/resp are sent in AC_BK, otherwise + * we should default to AC_VI. + */ + switch (action_code) { + case WLAN_TDLS_SETUP_REQUEST: + case WLAN_TDLS_SETUP_RESPONSE: + skb_set_queue_mapping(skb, IEEE80211_AC_BK); + skb->priority = 2; + break; + default: + skb_set_queue_mapping(skb, IEEE80211_AC_VI); + skb->priority = 5; + break; + } + + /* disable bottom halves when entering the Tx path */ + local_bh_disable(); + ret = ieee80211_subif_start_xmit(skb, dev); + local_bh_enable(); + + return ret; + +fail: + dev_kfree_skb(skb); + return ret; +} + +int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, enum nl80211_tdls_operation oper) +{ + struct sta_info *sta; + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + + if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) + return -ENOTSUPP; + + if (sdata->vif.type != NL80211_IFTYPE_STATION) + return -EINVAL; + + tdls_dbg(sdata, "TDLS oper %d peer %pM\n", oper, peer); + + switch (oper) { + case NL80211_TDLS_ENABLE_LINK: + rcu_read_lock(); + sta = sta_info_get(sdata, peer); + if (!sta) { + rcu_read_unlock(); + return -ENOLINK; + } + + set_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH); + rcu_read_unlock(); + break; + case NL80211_TDLS_DISABLE_LINK: + return sta_info_destroy_addr(sdata, peer); + case NL80211_TDLS_TEARDOWN: + case NL80211_TDLS_SETUP: + case NL80211_TDLS_DISCOVERY_REQ: + /* We don't support in-driver setup/teardown/discovery */ + return -ENOTSUPP; + default: + return -ENOTSUPP; + } + + return 0; +} diff --git a/net/mac80211/tkip.c b/net/mac80211/tkip.c index 124b1fdc20d..0ae207771a5 100644 --- a/net/mac80211/tkip.c +++ b/net/mac80211/tkip.c @@ -186,7 +186,7 @@ void ieee80211_get_tkip_p1k_iv(struct ieee80211_key_conf *keyconf, EXPORT_SYMBOL(ieee80211_get_tkip_p1k_iv); void ieee80211_get_tkip_rx_p1k(struct ieee80211_key_conf *keyconf, - const u8 *ta, u32 iv32, u16 *p1k) + const u8 *ta, u32 iv32, u16 *p1k) { const u8 *tk = &keyconf->key[NL80211_TKIP_DATA_OFFSET_ENCR_KEY]; struct tkip_ctx ctx; diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h index 1aba645882b..cfe1a0688b5 100644 --- a/net/mac80211/trace.h +++ b/net/mac80211/trace.h @@ -21,10 +21,10 @@ #define VIF_ENTRY __field(enum nl80211_iftype, vif_type) __field(void *, sdata) \ __field(bool, p2p) \ - __string(vif_name, sdata->dev ? sdata->dev->name : "<nodev>") + __string(vif_name, sdata->name) #define VIF_ASSIGN __entry->vif_type = sdata->vif.type; __entry->sdata = sdata; \ __entry->p2p = sdata->vif.p2p; \ - __assign_str(vif_name, sdata->dev ? sdata->dev->name : sdata->name) + __assign_str(vif_name, sdata->name) #define VIF_PR_FMT " vif:%s(%d%s)" #define VIF_PR_ARG __get_str(vif_name), __entry->vif_type, __entry->p2p ? "/p2p" : "" @@ -41,14 +41,31 @@ #define CHANDEF_PR_ARG __entry->control_freq, __entry->chan_width, \ __entry->center_freq1, __entry->center_freq2 +#define MIN_CHANDEF_ENTRY \ + __field(u32, min_control_freq) \ + __field(u32, min_chan_width) \ + __field(u32, min_center_freq1) \ + __field(u32, min_center_freq2) + +#define MIN_CHANDEF_ASSIGN(c) \ + __entry->min_control_freq = (c)->chan ? (c)->chan->center_freq : 0; \ + __entry->min_chan_width = (c)->width; \ + __entry->min_center_freq1 = (c)->center_freq1; \ + __entry->min_center_freq2 = (c)->center_freq2; +#define MIN_CHANDEF_PR_FMT " min_control:%d MHz min_width:%d min_center: %d/%d MHz" +#define MIN_CHANDEF_PR_ARG __entry->min_control_freq, __entry->min_chan_width, \ + __entry->min_center_freq1, __entry->min_center_freq2 + #define CHANCTX_ENTRY CHANDEF_ENTRY \ + MIN_CHANDEF_ENTRY \ __field(u8, rx_chains_static) \ __field(u8, rx_chains_dynamic) #define CHANCTX_ASSIGN CHANDEF_ASSIGN(&ctx->conf.def) \ + MIN_CHANDEF_ASSIGN(&ctx->conf.min_def) \ __entry->rx_chains_static = ctx->conf.rx_chains_static; \ __entry->rx_chains_dynamic = ctx->conf.rx_chains_dynamic -#define CHANCTX_PR_FMT CHANDEF_PR_FMT " chains:%d/%d" -#define CHANCTX_PR_ARG CHANDEF_PR_ARG, \ +#define CHANCTX_PR_FMT CHANDEF_PR_FMT MIN_CHANDEF_PR_FMT " chains:%d/%d" +#define CHANCTX_PR_ARG CHANDEF_PR_ARG, MIN_CHANDEF_PR_ARG, \ __entry->rx_chains_static, __entry->rx_chains_dynamic @@ -77,13 +94,13 @@ DECLARE_EVENT_CLASS(local_sdata_addr_evt, TP_STRUCT__entry( LOCAL_ENTRY VIF_ENTRY - __array(char, addr, 6) + __array(char, addr, ETH_ALEN) ), TP_fast_assign( LOCAL_ASSIGN; VIF_ASSIGN; - memcpy(__entry->addr, sdata->vif.addr, 6); + memcpy(__entry->addr, sdata->vif.addr, ETH_ALEN); ), TP_printk( @@ -167,6 +184,20 @@ TRACE_EVENT(drv_return_bool, "true" : "false") ); +TRACE_EVENT(drv_return_u32, + TP_PROTO(struct ieee80211_local *local, u32 ret), + TP_ARGS(local, ret), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(u32, ret) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->ret = ret; + ), + TP_printk(LOCAL_PR_FMT " - %u", LOCAL_PR_ARG, __entry->ret) +); + TRACE_EVENT(drv_return_u64, TP_PROTO(struct ieee80211_local *local, u64 ret), TP_ARGS(local, ret), @@ -426,30 +457,6 @@ TRACE_EVENT(drv_prepare_multicast, ) ); -TRACE_EVENT(drv_set_multicast_list, - TP_PROTO(struct ieee80211_local *local, - struct ieee80211_sub_if_data *sdata, int mc_count), - - TP_ARGS(local, sdata, mc_count), - - TP_STRUCT__entry( - LOCAL_ENTRY - __field(bool, allmulti) - __field(int, mc_count) - ), - - TP_fast_assign( - LOCAL_ASSIGN; - __entry->allmulti = sdata->flags & IEEE80211_SDATA_ALLMULTI; - __entry->mc_count = mc_count; - ), - - TP_printk( - LOCAL_PR_FMT " configure mc filter, count=%d, allmulti=%d", - LOCAL_PR_ARG, __entry->mc_count, __entry->allmulti - ) -); - TRACE_EVENT(drv_configure_filter, TP_PROTO(struct ieee80211_local *local, unsigned int changed_flags, @@ -560,7 +567,7 @@ TRACE_EVENT(drv_update_tkip_key, TP_printk( LOCAL_PR_FMT VIF_PR_FMT STA_PR_FMT " iv32:%#x", - LOCAL_PR_ARG,VIF_PR_ARG,STA_PR_ARG, __entry->iv32 + LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG, __entry->iv32 ) ); @@ -773,7 +780,7 @@ TRACE_EVENT(drv_sta_rc_update, ) ); -TRACE_EVENT(drv_sta_add, +DECLARE_EVENT_CLASS(sta_event, TP_PROTO(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, struct ieee80211_sta *sta), @@ -798,29 +805,25 @@ TRACE_EVENT(drv_sta_add, ) ); -TRACE_EVENT(drv_sta_remove, +DEFINE_EVENT(sta_event, drv_sta_add, TP_PROTO(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, struct ieee80211_sta *sta), + TP_ARGS(local, sdata, sta) +); - TP_ARGS(local, sdata, sta), - - TP_STRUCT__entry( - LOCAL_ENTRY - VIF_ENTRY - STA_ENTRY - ), - - TP_fast_assign( - LOCAL_ASSIGN; - VIF_ASSIGN; - STA_ASSIGN; - ), +DEFINE_EVENT(sta_event, drv_sta_remove, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_sta *sta), + TP_ARGS(local, sdata, sta) +); - TP_printk( - LOCAL_PR_FMT VIF_PR_FMT STA_PR_FMT, - LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG - ) +DEFINE_EVENT(sta_event, drv_sta_pre_rcu_remove, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_sta *sta), + TP_ARGS(local, sdata, sta) ); TRACE_EVENT(drv_conf_tx, @@ -1386,6 +1389,91 @@ TRACE_EVENT(drv_change_chanctx, ) ); +#if !defined(__TRACE_VIF_ENTRY) +#define __TRACE_VIF_ENTRY +struct trace_vif_entry { + enum nl80211_iftype vif_type; + bool p2p; + char vif_name[IFNAMSIZ]; +} __packed; + +struct trace_chandef_entry { + u32 control_freq; + u32 chan_width; + u32 center_freq1; + u32 center_freq2; +} __packed; + +struct trace_switch_entry { + struct trace_vif_entry vif; + struct trace_chandef_entry old_chandef; + struct trace_chandef_entry new_chandef; +} __packed; + +#define SWITCH_ENTRY_ASSIGN(to, from) local_vifs[i].to = vifs[i].from +#endif + +TRACE_EVENT(drv_switch_vif_chanctx, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_vif_chanctx_switch *vifs, + int n_vifs, enum ieee80211_chanctx_switch_mode mode), + TP_ARGS(local, vifs, n_vifs, mode), + + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, n_vifs) + __field(u32, mode) + __dynamic_array(u8, vifs, + sizeof(struct trace_switch_entry) * n_vifs) + ), + + TP_fast_assign( + LOCAL_ASSIGN; + __entry->n_vifs = n_vifs; + __entry->mode = mode; + { + struct trace_switch_entry *local_vifs = + __get_dynamic_array(vifs); + int i; + + for (i = 0; i < n_vifs; i++) { + struct ieee80211_sub_if_data *sdata; + + sdata = container_of(vifs[i].vif, + struct ieee80211_sub_if_data, + vif); + + SWITCH_ENTRY_ASSIGN(vif.vif_type, vif->type); + SWITCH_ENTRY_ASSIGN(vif.p2p, vif->p2p); + strncpy(local_vifs[i].vif.vif_name, + sdata->name, + sizeof(local_vifs[i].vif.vif_name)); + SWITCH_ENTRY_ASSIGN(old_chandef.control_freq, + old_ctx->def.chan->center_freq); + SWITCH_ENTRY_ASSIGN(old_chandef.chan_width, + old_ctx->def.width); + SWITCH_ENTRY_ASSIGN(old_chandef.center_freq1, + old_ctx->def.center_freq1); + SWITCH_ENTRY_ASSIGN(old_chandef.center_freq2, + old_ctx->def.center_freq2); + SWITCH_ENTRY_ASSIGN(new_chandef.control_freq, + new_ctx->def.chan->center_freq); + SWITCH_ENTRY_ASSIGN(new_chandef.chan_width, + new_ctx->def.width); + SWITCH_ENTRY_ASSIGN(new_chandef.center_freq1, + new_ctx->def.center_freq1); + SWITCH_ENTRY_ASSIGN(new_chandef.center_freq2, + new_ctx->def.center_freq2); + } + } + ), + + TP_printk( + LOCAL_PR_FMT " n_vifs:%d mode:%d", + LOCAL_PR_ARG, __entry->n_vifs, __entry->mode + ) +); + DECLARE_EVENT_CLASS(local_sdata_chanctx, TP_PROTO(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, @@ -1475,6 +1563,59 @@ DEFINE_EVENT(local_sdata_evt, drv_ipv6_addr_change, ); #endif +TRACE_EVENT(drv_join_ibss, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_bss_conf *info), + + TP_ARGS(local, sdata, info), + + TP_STRUCT__entry( + LOCAL_ENTRY + VIF_ENTRY + __field(u8, dtimper) + __field(u16, bcnint) + __dynamic_array(u8, ssid, info->ssid_len); + ), + + TP_fast_assign( + LOCAL_ASSIGN; + VIF_ASSIGN; + __entry->dtimper = info->dtim_period; + __entry->bcnint = info->beacon_int; + memcpy(__get_dynamic_array(ssid), info->ssid, info->ssid_len); + ), + + TP_printk( + LOCAL_PR_FMT VIF_PR_FMT, + LOCAL_PR_ARG, VIF_PR_ARG + ) +); + +DEFINE_EVENT(local_sdata_evt, drv_leave_ibss, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata), + TP_ARGS(local, sdata) +); + +TRACE_EVENT(drv_get_expected_throughput, + TP_PROTO(struct ieee80211_sta *sta), + + TP_ARGS(sta), + + TP_STRUCT__entry( + STA_ENTRY + ), + + TP_fast_assign( + STA_ASSIGN; + ), + + TP_printk( + STA_PR_FMT, STA_PR_ARG + ) +); + /* * Tracing for API calls that drivers call. */ @@ -1811,6 +1952,33 @@ TRACE_EVENT(api_eosp, ) ); +TRACE_EVENT(api_sta_set_buffered, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sta *sta, + u8 tid, bool buffered), + + TP_ARGS(local, sta, tid, buffered), + + TP_STRUCT__entry( + LOCAL_ENTRY + STA_ENTRY + __field(u8, tid) + __field(bool, buffered) + ), + + TP_fast_assign( + LOCAL_ASSIGN; + STA_ASSIGN; + __entry->tid = tid; + __entry->buffered = buffered; + ), + + TP_printk( + LOCAL_PR_FMT STA_PR_FMT " tid:%d buffered:%d", + LOCAL_PR_ARG, STA_PR_ARG, __entry->tid, __entry->buffered + ) +); + /* * Tracing for internal functions * (which may also be called in response to driver calls) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 3456c0486b4..1a252c606ad 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -19,6 +19,7 @@ #include <linux/bitmap.h> #include <linux/rcupdate.h> #include <linux/export.h> +#include <linux/time.h> #include <net/net_namespace.h> #include <net/ieee80211_radiotap.h> #include <net/cfg80211.h> @@ -413,6 +414,9 @@ ieee80211_tx_h_multicast_ps_buf(struct ieee80211_tx_data *tx) if (ieee80211_has_order(hdr->frame_control)) return TX_CONTINUE; + if (ieee80211_is_probe_req(hdr->frame_control)) + return TX_CONTINUE; + if (tx->local->hw.flags & IEEE80211_HW_QUEUE_CONTROL) info->hw_queue = tx->sdata->vif.cab_queue; @@ -451,8 +455,7 @@ static int ieee80211_use_mfp(__le16 fc, struct sta_info *sta, if (sta == NULL || !test_sta_flag(sta, WLAN_STA_MFP)) return 0; - if (!ieee80211_is_robust_mgmt_frame((struct ieee80211_hdr *) - skb->data)) + if (!ieee80211_is_robust_mgmt_frame(skb)) return 0; return 1; @@ -474,11 +477,8 @@ ieee80211_tx_h_unicast_ps_buf(struct ieee80211_tx_data *tx) !(info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER))) { int ac = skb_get_queue_mapping(tx->skb); - /* only deauth, disassoc and action are bufferable MMPDUs */ if (ieee80211_is_mgmt(hdr->frame_control) && - !ieee80211_is_deauth(hdr->frame_control) && - !ieee80211_is_disassoc(hdr->frame_control) && - !ieee80211_is_action(hdr->frame_control)) { + !ieee80211_is_bufferable_mmpdu(hdr->frame_control)) { info->flags |= IEEE80211_TX_CTL_NO_PS_BUFFER; return TX_CONTINUE; } @@ -487,6 +487,20 @@ ieee80211_tx_h_unicast_ps_buf(struct ieee80211_tx_data *tx) sta->sta.addr, sta->sta.aid, ac); if (tx->local->total_ps_buffered >= TOTAL_MAX_TX_BUFFER) purge_old_ps_buffers(tx->local); + + /* sync with ieee80211_sta_ps_deliver_wakeup */ + spin_lock(&sta->ps_lock); + /* + * STA woke up the meantime and all the frames on ps_tx_buf have + * been queued to pending queue. No reordering can happen, go + * ahead and Tx the packet. + */ + if (!test_sta_flag(sta, WLAN_STA_PS_STA) && + !test_sta_flag(sta, WLAN_STA_PS_DRIVER)) { + spin_unlock(&sta->ps_lock); + return TX_CONTINUE; + } + if (skb_queue_len(&sta->ps_tx_buf[ac]) >= STA_MAX_TX_BUFFER) { struct sk_buff *old = skb_dequeue(&sta->ps_tx_buf[ac]); ps_dbg(tx->sdata, @@ -499,7 +513,9 @@ ieee80211_tx_h_unicast_ps_buf(struct ieee80211_tx_data *tx) info->control.jiffies = jiffies; info->control.vif = &tx->sdata->vif; info->flags |= IEEE80211_TX_INTFL_NEED_TXPROCESSING; + info->flags &= ~IEEE80211_TX_TEMPORARY_FLAGS; skb_queue_tail(&sta->ps_tx_buf[ac], tx->skb); + spin_unlock(&sta->ps_lock); if (!timer_pending(&local->sta_cleanup)) mod_timer(&local->sta_cleanup, @@ -557,11 +573,12 @@ ieee80211_tx_h_select_key(struct ieee80211_tx_data *tx) if (unlikely(info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT)) tx->key = NULL; - else if (tx->sta && (key = rcu_dereference(tx->sta->ptk))) + else if (tx->sta && + (key = rcu_dereference(tx->sta->ptk[tx->sta->ptk_idx]))) tx->key = key; else if (ieee80211_is_mgmt(hdr->frame_control) && is_multicast_ether_addr(hdr->addr1) && - ieee80211_is_robust_mgmt_frame(hdr) && + ieee80211_is_robust_mgmt_frame(tx->skb) && (key = rcu_dereference(tx->sdata->default_mgmt_key))) tx->key = key; else if (is_multicast_ether_addr(hdr->addr1) && @@ -576,12 +593,12 @@ ieee80211_tx_h_select_key(struct ieee80211_tx_data *tx) tx->key = NULL; else if (tx->skb->protocol == tx->sdata->control_port_protocol) tx->key = NULL; - else if (ieee80211_is_robust_mgmt_frame(hdr) && + else if (ieee80211_is_robust_mgmt_frame(tx->skb) && !(ieee80211_is_action(hdr->frame_control) && tx->sta && test_sta_flag(tx->sta, WLAN_STA_MFP))) tx->key = NULL; else if (ieee80211_is_mgmt(hdr->frame_control) && - !ieee80211_is_robust_mgmt_frame(hdr)) + !ieee80211_is_robust_mgmt_frame(tx->skb)) tx->key = NULL; else { I802_DEBUG_INC(tx->local->tx_handlers_drop_unencrypted); @@ -840,15 +857,16 @@ static int ieee80211_fragment(struct ieee80211_tx_data *tx, rem -= fraglen; tmp = dev_alloc_skb(local->tx_headroom + frag_threshold + - IEEE80211_ENCRYPT_HEADROOM + + tx->sdata->encrypt_headroom + IEEE80211_ENCRYPT_TAILROOM); if (!tmp) return -ENOMEM; __skb_queue_tail(&tx->skbs, tmp); - skb_reserve(tmp, local->tx_headroom + - IEEE80211_ENCRYPT_HEADROOM); + skb_reserve(tmp, + local->tx_headroom + tx->sdata->encrypt_headroom); + /* copy control information */ memcpy(tmp->cb, skb->cb, sizeof(tmp->cb)); @@ -871,7 +889,7 @@ static int ieee80211_fragment(struct ieee80211_tx_data *tx, } /* adjust first fragment's length */ - skb->len = hdrlen + per_fragm; + skb_trim(skb, hdrlen + per_fragm); return 0; } @@ -1070,6 +1088,7 @@ static bool ieee80211_tx_prep_agg(struct ieee80211_tx_data *tx, queued = true; info->control.vif = &tx->sdata->vif; info->flags |= IEEE80211_TX_INTFL_NEED_TXPROCESSING; + info->flags &= ~IEEE80211_TX_TEMPORARY_FLAGS; __skb_queue_tail(&tid_tx->pending, skb); if (skb_queue_len(&tid_tx->pending) > STA_MAX_TX_BUFFER) purge_skb = __skb_dequeue(&tid_tx->pending); @@ -1120,7 +1139,8 @@ ieee80211_tx_prepare(struct ieee80211_sub_if_data *sdata, tx->sta = rcu_dereference(sdata->u.vlan.sta); if (!tx->sta && sdata->dev->ieee80211_ptr->use_4addr) return TX_DROP; - } else if (info->flags & IEEE80211_TX_CTL_INJECTED || + } else if (info->flags & (IEEE80211_TX_CTL_INJECTED | + IEEE80211_TX_INTFL_NL80211_FRAME_TX) || tx->sdata->control_port_protocol == tx->skb->protocol) { tx->sta = sta_info_get_bss(sdata, hdr->addr1); } @@ -1366,6 +1386,35 @@ static int invoke_tx_handlers(struct ieee80211_tx_data *tx) return 0; } +bool ieee80211_tx_prepare_skb(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, struct sk_buff *skb, + int band, struct ieee80211_sta **sta) +{ + struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_tx_data tx; + + if (ieee80211_tx_prepare(sdata, &tx, skb) == TX_DROP) + return false; + + info->band = band; + info->control.vif = vif; + info->hw_queue = vif->hw_queue[skb_get_queue_mapping(skb)]; + + if (invoke_tx_handlers(&tx)) + return false; + + if (sta) { + if (tx.sta) + *sta = &tx.sta->sta; + else + *sta = NULL; + } + + return true; +} +EXPORT_SYMBOL(ieee80211_tx_prepare_skb); + /* * Returns false if the frame couldn't be transmitted but was queued instead. */ @@ -1455,7 +1504,7 @@ void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, headroom = local->tx_headroom; if (may_encrypt) - headroom += IEEE80211_ENCRYPT_HEADROOM; + headroom += sdata->encrypt_headroom; headroom -= skb_headroom(skb); headroom = max_t(int, 0, headroom); @@ -1694,8 +1743,7 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, * radar detection by itself. We can do that later by adding a * monitor flag interfaces used for AP support. */ - if ((chan->flags & (IEEE80211_CHAN_NO_IBSS | IEEE80211_CHAN_RADAR | - IEEE80211_CHAN_PASSIVE_SCAN))) + if ((chan->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR))) goto fail_rcu; ieee80211_xmit(sdata, skb, chan->band); @@ -1710,6 +1758,26 @@ fail: return NETDEV_TX_OK; /* meaning, we dealt with the skb */ } +/* + * Measure Tx frame arrival time for Tx latency statistics calculation + * A single Tx frame latency should be measured from when it is entering the + * Kernel until we receive Tx complete confirmation indication and the skb is + * freed. + */ +static void ieee80211_tx_latency_start_msrmnt(struct ieee80211_local *local, + struct sk_buff *skb) +{ + struct timespec skb_arv; + struct ieee80211_tx_latency_bin_ranges *tx_latency; + + tx_latency = rcu_dereference(local->tx_latency); + if (!tx_latency) + return; + + ktime_get_ts(&skb_arv); + skb->tstamp = ktime_set(skb_arv.tv_sec, skb_arv.tv_nsec); +} + /** * ieee80211_subif_start_xmit - netif start_xmit function for Ethernet-type * subinterfaces (wlan#, WDS, and VLAN interfaces) @@ -1760,6 +1828,9 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, rcu_read_lock(); + /* Measure frame arrival for Tx latency statistics calculation */ + ieee80211_tx_latency_start_msrmnt(local, skb); + switch (sdata->vif.type) { case NL80211_IFTYPE_AP_VLAN: sta = rcu_dereference(sdata->u.vlan.sta); @@ -1981,7 +2052,7 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, * EAPOL frames from the local station. */ if (unlikely(!ieee80211_vif_is_mesh(&sdata->vif) && - !is_multicast_ether_addr(hdr.addr1) && !authorized && + !multicast && !authorized && (cpu_to_be16(ethertype) != sdata->control_port_protocol || !ether_addr_equal(sdata->vif.addr, skb->data + ETH_ALEN)))) { #ifdef CONFIG_MAC80211_VERBOSE_DEBUG @@ -2079,7 +2150,7 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, */ if (head_need > 0 || skb_cloned(skb)) { - head_need += IEEE80211_ENCRYPT_HEADROOM; + head_need += sdata->encrypt_headroom; head_need += local->tx_headroom; head_need = max_t(int, 0, head_need); if (ieee80211_skb_resize(sdata, skb, head_need, true)) { @@ -2106,7 +2177,7 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, if (ieee80211_is_data_qos(fc)) { __le16 *qos_control; - qos_control = (__le16*) skb_push(skb, 2); + qos_control = (__le16 *) skb_push(skb, 2); memcpy(skb_push(skb, hdrlen - 2), &hdr, hdrlen - 2); /* * Maybe we could actually set some fields here, for now just @@ -2257,7 +2328,8 @@ void ieee80211_tx_pending(unsigned long data) /* functions for drivers to get certain frames */ static void __ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata, - struct ps_data *ps, struct sk_buff *skb) + struct ps_data *ps, struct sk_buff *skb, + bool is_template) { u8 *pos, *tim; int aid0 = 0; @@ -2268,13 +2340,14 @@ static void __ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata, if (atomic_read(&ps->num_sta_ps) > 0) /* in the hope that this is faster than * checking byte-for-byte */ - have_bits = !bitmap_empty((unsigned long*)ps->tim, + have_bits = !bitmap_empty((unsigned long *)ps->tim, IEEE80211_MAX_AID+1); - - if (ps->dtim_count == 0) - ps->dtim_count = sdata->vif.bss_conf.dtim_period - 1; - else - ps->dtim_count--; + if (!is_template) { + if (ps->dtim_count == 0) + ps->dtim_count = sdata->vif.bss_conf.dtim_period - 1; + else + ps->dtim_count--; + } tim = pos = (u8 *) skb_put(skb, 6); *pos++ = WLAN_EID_TIM; @@ -2320,7 +2393,8 @@ static void __ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata, } static int ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata, - struct ps_data *ps, struct sk_buff *skb) + struct ps_data *ps, struct sk_buff *skb, + bool is_template) { struct ieee80211_local *local = sdata->local; @@ -2332,60 +2406,92 @@ static int ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata, * of the tim bitmap in mac80211 and the driver. */ if (local->tim_in_locked_section) { - __ieee80211_beacon_add_tim(sdata, ps, skb); + __ieee80211_beacon_add_tim(sdata, ps, skb, is_template); } else { spin_lock_bh(&local->tim_lock); - __ieee80211_beacon_add_tim(sdata, ps, skb); + __ieee80211_beacon_add_tim(sdata, ps, skb, is_template); spin_unlock_bh(&local->tim_lock); } return 0; } -void ieee80211_csa_finish(struct ieee80211_vif *vif) -{ - struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); - - ieee80211_queue_work(&sdata->local->hw, - &sdata->csa_finalize_work); -} -EXPORT_SYMBOL(ieee80211_csa_finish); - -static void ieee80211_update_csa(struct ieee80211_sub_if_data *sdata, - struct beacon_data *beacon) +static void ieee80211_set_csa(struct ieee80211_sub_if_data *sdata, + struct beacon_data *beacon) { struct probe_resp *resp; - int counter_offset_beacon = sdata->csa_counter_offset_beacon; - int counter_offset_presp = sdata->csa_counter_offset_presp; + u8 *beacon_data; + size_t beacon_data_len; + int i; + u8 count = sdata->csa_current_counter; - /* warn if the driver did not check for/react to csa completeness */ - if (WARN_ON(((u8 *)beacon->tail)[counter_offset_beacon] == 0)) + switch (sdata->vif.type) { + case NL80211_IFTYPE_AP: + beacon_data = beacon->tail; + beacon_data_len = beacon->tail_len; + break; + case NL80211_IFTYPE_ADHOC: + beacon_data = beacon->head; + beacon_data_len = beacon->head_len; + break; + case NL80211_IFTYPE_MESH_POINT: + beacon_data = beacon->head; + beacon_data_len = beacon->head_len; + break; + default: return; + } + + for (i = 0; i < IEEE80211_MAX_CSA_COUNTERS_NUM; ++i) { + u16 counter_offset_beacon = + sdata->csa_counter_offset_beacon[i]; + u16 counter_offset_presp = sdata->csa_counter_offset_presp[i]; - ((u8 *)beacon->tail)[counter_offset_beacon]--; + if (counter_offset_beacon) { + if (WARN_ON(counter_offset_beacon >= beacon_data_len)) + return; - if (sdata->vif.type == NL80211_IFTYPE_AP && - counter_offset_presp) { - rcu_read_lock(); - resp = rcu_dereference(sdata->u.ap.probe_resp); + beacon_data[counter_offset_beacon] = count; + } - /* if nl80211 accepted the offset, this should not happen. */ - if (WARN_ON(!resp)) { + if (sdata->vif.type == NL80211_IFTYPE_AP && + counter_offset_presp) { + rcu_read_lock(); + resp = rcu_dereference(sdata->u.ap.probe_resp); + + /* If nl80211 accepted the offset, this should + * not happen. + */ + if (WARN_ON(!resp)) { + rcu_read_unlock(); + return; + } + resp->data[counter_offset_presp] = count; rcu_read_unlock(); - return; } - resp->data[counter_offset_presp]--; - rcu_read_unlock(); } } +u8 ieee80211_csa_update_counter(struct ieee80211_vif *vif) +{ + struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); + + sdata->csa_current_counter--; + + /* the counter should never reach 0 */ + WARN_ON(!sdata->csa_current_counter); + + return sdata->csa_current_counter; +} +EXPORT_SYMBOL(ieee80211_csa_update_counter); + bool ieee80211_csa_is_complete(struct ieee80211_vif *vif) { struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); struct beacon_data *beacon = NULL; u8 *beacon_data; size_t beacon_data_len; - int counter_beacon = sdata->csa_counter_offset_beacon; + int counter_beacon = sdata->csa_counter_offset_beacon[0]; int ret = false; if (!ieee80211_sdata_running(sdata)) @@ -2400,6 +2506,24 @@ bool ieee80211_csa_is_complete(struct ieee80211_vif *vif) goto out; beacon_data = beacon->tail; beacon_data_len = beacon->tail_len; + } else if (vif->type == NL80211_IFTYPE_ADHOC) { + struct ieee80211_if_ibss *ifibss = &sdata->u.ibss; + + beacon = rcu_dereference(ifibss->presp); + if (!beacon) + goto out; + + beacon_data = beacon->head; + beacon_data_len = beacon->head_len; + } else if (vif->type == NL80211_IFTYPE_MESH_POINT) { + struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + + beacon = rcu_dereference(ifmsh->beacon); + if (!beacon) + goto out; + + beacon_data = beacon->head; + beacon_data_len = beacon->head_len; } else { WARN_ON(1); goto out; @@ -2408,7 +2532,7 @@ bool ieee80211_csa_is_complete(struct ieee80211_vif *vif) if (WARN_ON(counter_beacon > beacon_data_len)) goto out; - if (beacon_data[counter_beacon] == 0) + if (beacon_data[counter_beacon] == 1) ret = true; out: rcu_read_unlock(); @@ -2417,9 +2541,11 @@ bool ieee80211_csa_is_complete(struct ieee80211_vif *vif) } EXPORT_SYMBOL(ieee80211_csa_is_complete); -struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw, - struct ieee80211_vif *vif, - u16 *tim_offset, u16 *tim_length) +static struct sk_buff * +__ieee80211_beacon_get(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_mutable_offsets *offs, + bool is_template) { struct ieee80211_local *local = hw_to_local(hw); struct sk_buff *skb = NULL; @@ -2428,6 +2554,7 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw, enum ieee80211_band band; struct ieee80211_tx_rate_control txrc; struct ieee80211_chanctx_conf *chanctx_conf; + int csa_off_base = 0; rcu_read_lock(); @@ -2437,18 +2564,20 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw, if (!ieee80211_sdata_running(sdata) || !chanctx_conf) goto out; - if (tim_offset) - *tim_offset = 0; - if (tim_length) - *tim_length = 0; + if (offs) + memset(offs, 0, sizeof(*offs)); if (sdata->vif.type == NL80211_IFTYPE_AP) { struct ieee80211_if_ap *ap = &sdata->u.ap; struct beacon_data *beacon = rcu_dereference(ap->beacon); if (beacon) { - if (sdata->vif.csa_active) - ieee80211_update_csa(sdata, beacon); + if (sdata->vif.csa_active) { + if (!is_template) + ieee80211_csa_update_counter(vif); + + ieee80211_set_csa(sdata, beacon); + } /* * headroom, head length, @@ -2456,7 +2585,8 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw, */ skb = dev_alloc_skb(local->tx_headroom + beacon->head_len + - beacon->tail_len + 256); + beacon->tail_len + 256 + + local->hw.extra_beacon_tailroom); if (!skb) goto out; @@ -2464,12 +2594,16 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw, memcpy(skb_put(skb, beacon->head_len), beacon->head, beacon->head_len); - ieee80211_beacon_add_tim(sdata, &ap->ps, skb); + ieee80211_beacon_add_tim(sdata, &ap->ps, skb, + is_template); + + if (offs) { + offs->tim_offset = beacon->head_len; + offs->tim_length = skb->len - beacon->head_len; - if (tim_offset) - *tim_offset = beacon->head_len; - if (tim_length) - *tim_length = skb->len - beacon->head_len; + /* for AP the csa offsets are from tail */ + csa_off_base = skb->len; + } if (beacon->tail) memcpy(skb_put(skb, beacon->tail_len), @@ -2484,7 +2618,15 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw, if (!presp) goto out; - skb = dev_alloc_skb(local->tx_headroom + presp->head_len); + if (sdata->vif.csa_active) { + if (!is_template) + ieee80211_csa_update_counter(vif); + + ieee80211_set_csa(sdata, presp); + } + + skb = dev_alloc_skb(local->tx_headroom + presp->head_len + + local->hw.extra_beacon_tailroom); if (!skb) goto out; skb_reserve(skb, local->tx_headroom); @@ -2501,25 +2643,57 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw, if (!bcn) goto out; + if (sdata->vif.csa_active) { + if (!is_template) + /* TODO: For mesh csa_counter is in TU, so + * decrementing it by one isn't correct, but + * for now we leave it consistent with overall + * mac80211's behavior. + */ + ieee80211_csa_update_counter(vif); + + ieee80211_set_csa(sdata, bcn); + } + if (ifmsh->sync_ops) - ifmsh->sync_ops->adjust_tbtt( - sdata); + ifmsh->sync_ops->adjust_tbtt(sdata, bcn); skb = dev_alloc_skb(local->tx_headroom + bcn->head_len + 256 + /* TIM IE */ - bcn->tail_len); + bcn->tail_len + + local->hw.extra_beacon_tailroom); if (!skb) goto out; skb_reserve(skb, local->tx_headroom); memcpy(skb_put(skb, bcn->head_len), bcn->head, bcn->head_len); - ieee80211_beacon_add_tim(sdata, &ifmsh->ps, skb); + ieee80211_beacon_add_tim(sdata, &ifmsh->ps, skb, is_template); + + if (offs) { + offs->tim_offset = bcn->head_len; + offs->tim_length = skb->len - bcn->head_len; + } + memcpy(skb_put(skb, bcn->tail_len), bcn->tail, bcn->tail_len); } else { WARN_ON(1); goto out; } + /* CSA offsets */ + if (offs) { + int i; + + for (i = 0; i < IEEE80211_MAX_CSA_COUNTERS_NUM; i++) { + u16 csa_off = sdata->csa_counter_offset_beacon[i]; + + if (!csa_off) + continue; + + offs->csa_counter_offs[i] = csa_off_base + csa_off; + } + } + band = chanctx_conf->def.chan->band; info = IEEE80211_SKB_CB(skb); @@ -2550,6 +2724,32 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw, out: rcu_read_unlock(); return skb; + +} + +struct sk_buff * +ieee80211_beacon_get_template(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_mutable_offsets *offs) +{ + return __ieee80211_beacon_get(hw, vif, offs, true); +} +EXPORT_SYMBOL(ieee80211_beacon_get_template); + +struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u16 *tim_offset, u16 *tim_length) +{ + struct ieee80211_mutable_offsets offs = {}; + struct sk_buff *bcn = __ieee80211_beacon_get(hw, vif, &offs, false); + + if (tim_offset) + *tim_offset = offs.tim_offset; + + if (tim_length) + *tim_length = offs.tim_length; + + return bcn; } EXPORT_SYMBOL(ieee80211_beacon_get_tim); @@ -2787,7 +2987,7 @@ ieee80211_get_buffered_bc(struct ieee80211_hw *hw, cpu_to_le16(IEEE80211_FCTL_MOREDATA); } - if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) + if (sdata->vif.type == NL80211_IFTYPE_AP) sdata = IEEE80211_DEV_TO_SUB_IF(skb->dev); if (!ieee80211_tx_prepare(sdata, &tx, skb)) break; diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 9c3200bcfc0..a6cda52ed92 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -34,7 +34,7 @@ #include "wep.h" /* privid for wiphys to determine whether they belong to us or not */ -void *mac80211_wiphy_privid = &mac80211_wiphy_privid; +const void *const mac80211_wiphy_privid = &mac80211_wiphy_privid; struct ieee80211_hw *wiphy_to_ieee80211_hw(struct wiphy *wiphy) { @@ -76,7 +76,7 @@ u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len, } if (ieee80211_is_ctl(fc)) { - if(ieee80211_is_pspoll(fc)) + if (ieee80211_is_pspoll(fc)) return hdr->addr1; if (ieee80211_is_back_req(fc)) { @@ -300,9 +300,6 @@ void ieee80211_propagate_queue_wake(struct ieee80211_local *local, int queue) if (!sdata->dev) continue; - if (test_bit(SDATA_STATE_OFFCHANNEL, &sdata->state)) - continue; - if (sdata->vif.cab_queue != IEEE80211_INVAL_HW_QUEUE && local->queue_stop_reasons[sdata->vif.cab_queue] != 0) continue; @@ -438,9 +435,8 @@ void ieee80211_add_pending_skb(struct ieee80211_local *local, spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags); } -void ieee80211_add_pending_skbs_fn(struct ieee80211_local *local, - struct sk_buff_head *skbs, - void (*fn)(void *data), void *data) +void ieee80211_add_pending_skbs(struct ieee80211_local *local, + struct sk_buff_head *skbs) { struct ieee80211_hw *hw = &local->hw; struct sk_buff *skb; @@ -464,9 +460,6 @@ void ieee80211_add_pending_skbs_fn(struct ieee80211_local *local, __skb_queue_tail(&local->pending[queue], skb); } - if (fn) - fn(data); - for (i = 0; i < hw->queues; i++) __ieee80211_wake_queue(hw, i, IEEE80211_QUEUE_STOP_REASON_SKB_ADD); @@ -561,24 +554,21 @@ void ieee80211_flush_queues(struct ieee80211_local *local, ieee80211_stop_queues_by_reason(&local->hw, IEEE80211_MAX_QUEUE_MAP, IEEE80211_QUEUE_STOP_REASON_FLUSH); - drv_flush(local, queues, false); + drv_flush(local, sdata, queues, false); ieee80211_wake_queues_by_reason(&local->hw, IEEE80211_MAX_QUEUE_MAP, IEEE80211_QUEUE_STOP_REASON_FLUSH); } -void ieee80211_iterate_active_interfaces( - struct ieee80211_hw *hw, u32 iter_flags, - void (*iterator)(void *data, u8 *mac, - struct ieee80211_vif *vif), - void *data) +static void __iterate_active_interfaces(struct ieee80211_local *local, + u32 iter_flags, + void (*iterator)(void *data, u8 *mac, + struct ieee80211_vif *vif), + void *data) { - struct ieee80211_local *local = hw_to_local(hw); struct ieee80211_sub_if_data *sdata; - mutex_lock(&local->iflist_mtx); - - list_for_each_entry(sdata, &local->interfaces, list) { + list_for_each_entry_rcu(sdata, &local->interfaces, list) { switch (sdata->vif.type) { case NL80211_IFTYPE_MONITOR: if (!(sdata->u.mntr_flags & MONITOR_FLAG_ACTIVE)) @@ -597,13 +587,25 @@ void ieee80211_iterate_active_interfaces( &sdata->vif); } - sdata = rcu_dereference_protected(local->monitor_sdata, - lockdep_is_held(&local->iflist_mtx)); + sdata = rcu_dereference_check(local->monitor_sdata, + lockdep_is_held(&local->iflist_mtx) || + lockdep_rtnl_is_held()); if (sdata && (iter_flags & IEEE80211_IFACE_ITER_RESUME_ALL || sdata->flags & IEEE80211_SDATA_IN_DRIVER)) iterator(data, sdata->vif.addr, &sdata->vif); +} +void ieee80211_iterate_active_interfaces( + struct ieee80211_hw *hw, u32 iter_flags, + void (*iterator)(void *data, u8 *mac, + struct ieee80211_vif *vif), + void *data) +{ + struct ieee80211_local *local = hw_to_local(hw); + + mutex_lock(&local->iflist_mtx); + __iterate_active_interfaces(local, iter_flags, iterator, data); mutex_unlock(&local->iflist_mtx); } EXPORT_SYMBOL_GPL(ieee80211_iterate_active_interfaces); @@ -615,38 +617,37 @@ void ieee80211_iterate_active_interfaces_atomic( void *data) { struct ieee80211_local *local = hw_to_local(hw); - struct ieee80211_sub_if_data *sdata; rcu_read_lock(); + __iterate_active_interfaces(local, iter_flags, iterator, data); + rcu_read_unlock(); +} +EXPORT_SYMBOL_GPL(ieee80211_iterate_active_interfaces_atomic); - list_for_each_entry_rcu(sdata, &local->interfaces, list) { - switch (sdata->vif.type) { - case NL80211_IFTYPE_MONITOR: - if (!(sdata->u.mntr_flags & MONITOR_FLAG_ACTIVE)) - continue; - break; - case NL80211_IFTYPE_AP_VLAN: - continue; - default: - break; - } - if (!(iter_flags & IEEE80211_IFACE_ITER_RESUME_ALL) && - !(sdata->flags & IEEE80211_SDATA_IN_DRIVER)) - continue; - if (ieee80211_sdata_running(sdata)) - iterator(data, sdata->vif.addr, - &sdata->vif); - } +void ieee80211_iterate_active_interfaces_rtnl( + struct ieee80211_hw *hw, u32 iter_flags, + void (*iterator)(void *data, u8 *mac, + struct ieee80211_vif *vif), + void *data) +{ + struct ieee80211_local *local = hw_to_local(hw); - sdata = rcu_dereference(local->monitor_sdata); - if (sdata && - (iter_flags & IEEE80211_IFACE_ITER_RESUME_ALL || - sdata->flags & IEEE80211_SDATA_IN_DRIVER)) - iterator(data, sdata->vif.addr, &sdata->vif); + ASSERT_RTNL(); - rcu_read_unlock(); + __iterate_active_interfaces(local, iter_flags, iterator, data); } -EXPORT_SYMBOL_GPL(ieee80211_iterate_active_interfaces_atomic); +EXPORT_SYMBOL_GPL(ieee80211_iterate_active_interfaces_rtnl); + +struct ieee80211_vif *wdev_to_ieee80211_vif(struct wireless_dev *wdev) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); + + if (!ieee80211_sdata_running(sdata) || + !(sdata->flags & IEEE80211_SDATA_IN_DRIVER)) + return NULL; + return &sdata->vif; +} +EXPORT_SYMBOL_GPL(wdev_to_ieee80211_vif); /* * Nothing should have been stuffed into the workqueue during @@ -746,6 +747,7 @@ u32 ieee802_11_parse_elems_crc(const u8 *start, size_t len, bool action, case WLAN_EID_TIMEOUT_INTERVAL: case WLAN_EID_SECONDARY_CHANNEL_OFFSET: case WLAN_EID_WIDE_BW_CHANNEL_SWITCH: + case WLAN_EID_CHAN_SWITCH_PARAM: /* * not listing WLAN_EID_CHANNEL_SWITCH_WRAPPER -- it seems possible * that if the content gets bigger it might be needed more than once @@ -911,6 +913,14 @@ u32 ieee802_11_parse_elems_crc(const u8 *start, size_t len, bool action, } elems->sec_chan_offs = (void *)pos; break; + case WLAN_EID_CHAN_SWITCH_PARAM: + if (elen != + sizeof(*elems->mesh_chansw_params_ie)) { + elem_parse_failed = true; + break; + } + elems->mesh_chansw_params_ie = (void *)pos; + break; case WLAN_EID_WIDE_BW_CHANNEL_SWITCH: if (!action || elen != sizeof(*elems->wide_bw_chansw_ie)) { @@ -1007,14 +1017,21 @@ void ieee80211_set_wmm_default(struct ieee80211_sub_if_data *sdata, */ enable_qos = (sdata->vif.type != NL80211_IFTYPE_STATION); - for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { - /* Set defaults according to 802.11-2007 Table 7-37 */ - aCWmax = 1023; - if (use_11b) - aCWmin = 31; - else - aCWmin = 15; + /* Set defaults according to 802.11-2007 Table 7-37 */ + aCWmax = 1023; + if (use_11b) + aCWmin = 31; + else + aCWmin = 15; + /* Confiure old 802.11b/g medium access rules. */ + qparam.cw_max = aCWmax; + qparam.cw_min = aCWmin; + qparam.txop = 0; + qparam.aifs = 2; + + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { + /* Update if QoS is enabled. */ if (enable_qos) { switch (ac) { case IEEE80211_AC_BK: @@ -1050,12 +1067,6 @@ void ieee80211_set_wmm_default(struct ieee80211_sub_if_data *sdata, qparam.aifs = 2; break; } - } else { - /* Confiure old 802.11b/g medium access rules. */ - qparam.cw_max = aCWmax; - qparam.cw_min = aCWmin; - qparam.txop = 0; - qparam.aifs = 2; } qparam.uapsd = false; @@ -1084,12 +1095,13 @@ void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt; int err; - skb = dev_alloc_skb(local->hw.extra_tx_headroom + - sizeof(*mgmt) + 6 + extra_len); + /* 24 + 6 = header + auth_algo + auth_transaction + status_code */ + skb = dev_alloc_skb(local->hw.extra_tx_headroom + IEEE80211_WEP_IV_LEN + + 24 + 6 + extra_len + IEEE80211_WEP_ICV_LEN); if (!skb) return; - skb_reserve(skb, local->hw.extra_tx_headroom); + skb_reserve(skb, local->hw.extra_tx_headroom + IEEE80211_WEP_IV_LEN); mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24 + 6); memset(mgmt, 0, 24 + 6); @@ -1266,13 +1278,32 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer, * that calculates local->scan_ies_len. */ - /* add any remaining custom IEs */ + /* insert custom IEs that go before VHT */ if (ie && ie_len) { - noffset = ie_len; + static const u8 before_vht[] = { + WLAN_EID_SSID, + WLAN_EID_SUPP_RATES, + WLAN_EID_REQUEST, + WLAN_EID_EXT_SUPP_RATES, + WLAN_EID_DS_PARAMS, + WLAN_EID_SUPPORTED_REGULATORY_CLASSES, + WLAN_EID_HT_CAPABILITY, + WLAN_EID_BSS_COEX_2040, + WLAN_EID_EXT_CAPABILITY, + WLAN_EID_SSID_LIST, + WLAN_EID_CHANNEL_USAGE, + WLAN_EID_INTERWORKING, + /* mesh ID can't happen here */ + /* 60 GHz can't happen here right now */ + }; + noffset = ieee80211_ie_split(ie, ie_len, + before_vht, ARRAY_SIZE(before_vht), + offset); if (end - pos < noffset - offset) goto out_err; memcpy(pos, ie + offset, noffset - offset); pos += noffset - offset; + offset = noffset; } if (sband->vht_cap.vht_supported) { @@ -1282,6 +1313,15 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer, sband->vht_cap.cap); } + /* add any remaining custom IEs */ + if (ie && ie_len) { + noffset = ie_len; + if (end - pos < noffset - offset) + goto out_err; + memcpy(pos, ie + offset, noffset - offset); + pos += noffset - offset; + } + return pos - buffer; out_err: WARN_ONCE(1, "not enough space for preq IEs\n"); @@ -1359,7 +1399,6 @@ u32 ieee80211_sta_get_rates(struct ieee80211_sub_if_data *sdata, enum ieee80211_band band, u32 *basic_rates) { struct ieee80211_supported_band *sband; - struct ieee80211_rate *bitrates; size_t num_rates; u32 supp_rates, rate_flags; int i, j, shift; @@ -1371,7 +1410,6 @@ u32 ieee80211_sta_get_rates(struct ieee80211_sub_if_data *sdata, if (WARN_ON(!sband)) return 1; - bitrates = sband->bitrates; num_rates = sband->n_bitrates; supp_rates = 0; for (i = 0; i < elems->supp_rates_len + @@ -1420,6 +1458,44 @@ void ieee80211_stop_device(struct ieee80211_local *local) drv_stop(local); } +static void ieee80211_handle_reconfig_failure(struct ieee80211_local *local) +{ + struct ieee80211_sub_if_data *sdata; + struct ieee80211_chanctx *ctx; + + /* + * We get here if during resume the device can't be restarted properly. + * We might also get here if this happens during HW reset, which is a + * slightly different situation and we need to drop all connections in + * the latter case. + * + * Ask cfg80211 to turn off all interfaces, this will result in more + * warnings but at least we'll then get into a clean stopped state. + */ + + local->resuming = false; + local->suspended = false; + local->started = false; + + /* scheduled scan clearly can't be running any more, but tell + * cfg80211 and clear local state + */ + ieee80211_sched_scan_end(local); + + list_for_each_entry(sdata, &local->interfaces, list) + sdata->flags &= ~IEEE80211_SDATA_IN_DRIVER; + + /* Mark channel contexts as not being in the driver any more to avoid + * removing them from the driver during the shutdown process... + */ + mutex_lock(&local->chanctx_mtx); + list_for_each_entry(ctx, &local->chanctx_list, list) + ctx->driver_present = false; + mutex_unlock(&local->chanctx_mtx); + + cfg80211_shutdown_all_interfaces(local->hw.wiphy); +} + static void ieee80211_assign_chanctx(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata) { @@ -1447,6 +1523,8 @@ int ieee80211_reconfig(struct ieee80211_local *local) struct sta_info *sta; int res, i; bool reconfig_due_to_wowlan = false; + struct ieee80211_sub_if_data *sched_scan_sdata; + bool sched_scan_stopped = false; #ifdef CONFIG_PM if (local->suspended) @@ -1481,9 +1559,11 @@ int ieee80211_reconfig(struct ieee80211_local *local) */ res = drv_start(local); if (res) { - WARN(local->suspended, "Hardware became unavailable " - "upon resume. This could be a software issue " - "prior to suspend or a hardware issue.\n"); + if (local->suspended) + WARN(1, "Hardware became unavailable upon resume. This could be a software issue prior to suspend or a hardware issue.\n"); + else + WARN(1, "Hardware became unavailable during restart.\n"); + ieee80211_handle_reconfig_failure(local); return res; } @@ -1507,7 +1587,7 @@ int ieee80211_reconfig(struct ieee80211_local *local) WARN_ON(local->resuming); res = drv_add_interface(local, sdata); if (WARN_ON(res)) { - rcu_assign_pointer(local->monitor_sdata, NULL); + RCU_INIT_POINTER(local->monitor_sdata, NULL); synchronize_net(); kfree(sdata); } @@ -1526,17 +1606,17 @@ int ieee80211_reconfig(struct ieee80211_local *local) list_for_each_entry(ctx, &local->chanctx_list, list) WARN_ON(drv_add_chanctx(local, ctx)); mutex_unlock(&local->chanctx_mtx); - } - list_for_each_entry(sdata, &local->interfaces, list) { - if (!ieee80211_sdata_running(sdata)) - continue; - ieee80211_assign_chanctx(local, sdata); - } + list_for_each_entry(sdata, &local->interfaces, list) { + if (!ieee80211_sdata_running(sdata)) + continue; + ieee80211_assign_chanctx(local, sdata); + } - sdata = rtnl_dereference(local->monitor_sdata); - if (sdata && ieee80211_sdata_running(sdata)) - ieee80211_assign_chanctx(local, sdata); + sdata = rtnl_dereference(local->monitor_sdata); + if (sdata && ieee80211_sdata_running(sdata)) + ieee80211_assign_chanctx(local, sdata); + } /* add STAs back */ mutex_lock(&local->sta_mtx); @@ -1632,13 +1712,10 @@ int ieee80211_reconfig(struct ieee80211_local *local) } break; case NL80211_IFTYPE_WDS: - break; case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_MONITOR: - /* ignore virtual */ - break; case NL80211_IFTYPE_P2P_DEVICE: - changed = BSS_CHANGED_IDLE; + /* nothing to do */ break; case NL80211_IFTYPE_UNSPECIFIED: case NUM_NL80211_IFTYPES: @@ -1724,6 +1801,26 @@ int ieee80211_reconfig(struct ieee80211_local *local) IEEE80211_QUEUE_STOP_REASON_SUSPEND); /* + * Reconfigure sched scan if it was interrupted by FW restart or + * suspend. + */ + mutex_lock(&local->mtx); + sched_scan_sdata = rcu_dereference_protected(local->sched_scan_sdata, + lockdep_is_held(&local->mtx)); + if (sched_scan_sdata && local->sched_scan_req) + /* + * Sched scan stopped, but we don't want to report it. Instead, + * we're trying to reschedule. + */ + if (__ieee80211_request_sched_scan_start(sched_scan_sdata, + local->sched_scan_req)) + sched_scan_stopped = true; + mutex_unlock(&local->mtx); + + if (sched_scan_stopped) + cfg80211_sched_scan_stopped_rtnl(local->hw.wiphy); + + /* * If this is for hw restart things are still running. * We may want to change that later, however. */ @@ -1750,6 +1847,7 @@ int ieee80211_reconfig(struct ieee80211_local *local) #else WARN_ON(1); #endif + return 0; } @@ -1800,6 +1898,26 @@ void ieee80211_recalc_smps(struct ieee80211_sub_if_data *sdata) mutex_unlock(&local->chanctx_mtx); } +void ieee80211_recalc_min_chandef(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_chanctx_conf *chanctx_conf; + struct ieee80211_chanctx *chanctx; + + mutex_lock(&local->chanctx_mtx); + + chanctx_conf = rcu_dereference_protected(sdata->vif.chanctx_conf, + lockdep_is_held(&local->chanctx_mtx)); + + if (WARN_ON_ONCE(!chanctx_conf)) + goto unlock; + + chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf); + ieee80211_recalc_chanctx_min_def(local, chanctx); + unlock: + mutex_unlock(&local->chanctx_mtx); +} + static bool ieee80211_id_in_list(const u8 *ids, int n_ids, u8 id) { int i; @@ -2214,11 +2332,11 @@ u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local, ri.nss = status->vht_nss; if (status->flag & RX_FLAG_40MHZ) ri.flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH; - if (status->flag & RX_FLAG_80MHZ) + if (status->vht_flag & RX_VHT_FLAG_80MHZ) ri.flags |= RATE_INFO_FLAGS_80_MHZ_WIDTH; - if (status->flag & RX_FLAG_80P80MHZ) + if (status->vht_flag & RX_VHT_FLAG_80P80MHZ) ri.flags |= RATE_INFO_FLAGS_80P80_MHZ_WIDTH; - if (status->flag & RX_FLAG_160MHZ) + if (status->vht_flag & RX_VHT_FLAG_160MHZ) ri.flags |= RATE_INFO_FLAGS_160_MHZ_WIDTH; if (status->flag & RX_FLAG_SHORT_GI) ri.flags |= RATE_INFO_FLAGS_SHORT_GI; @@ -2238,6 +2356,10 @@ u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local, } rate = cfg80211_calculate_bitrate(&ri); + if (WARN_ONCE(!rate, + "Invalid bitrate: flags=0x%x, idx=%d, vht_nss=%d\n", + status->flag, status->rate_idx, status->vht_nss)) + return 0; /* rewind from end of MPDU */ if (status->flag & RX_FLAG_MACTIME_END) @@ -2251,36 +2373,43 @@ u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local, void ieee80211_dfs_cac_cancel(struct ieee80211_local *local) { struct ieee80211_sub_if_data *sdata; + struct cfg80211_chan_def chandef; + mutex_lock(&local->mtx); mutex_lock(&local->iflist_mtx); list_for_each_entry(sdata, &local->interfaces, list) { - cancel_delayed_work_sync(&sdata->dfs_cac_timer_work); + /* it might be waiting for the local->mtx, but then + * by the time it gets it, sdata->wdev.cac_started + * will no longer be true + */ + cancel_delayed_work(&sdata->dfs_cac_timer_work); if (sdata->wdev.cac_started) { + chandef = sdata->vif.bss_conf.chandef; ieee80211_vif_release_channel(sdata); cfg80211_cac_event(sdata->dev, + &chandef, NL80211_RADAR_CAC_ABORTED, GFP_KERNEL); } } mutex_unlock(&local->iflist_mtx); + mutex_unlock(&local->mtx); } void ieee80211_dfs_radar_detected_work(struct work_struct *work) { struct ieee80211_local *local = container_of(work, struct ieee80211_local, radar_detected_work); - struct cfg80211_chan_def chandef; + struct cfg80211_chan_def chandef = local->hw.conf.chandef; ieee80211_dfs_cac_cancel(local); if (local->use_chanctx) /* currently not handled */ WARN_ON(1); - else { - chandef = local->hw.conf.chandef; + else cfg80211_radar_event(local->hw.wiphy, &chandef, GFP_KERNEL); - } } void ieee80211_radar_detected(struct ieee80211_hw *hw) @@ -2292,3 +2421,535 @@ void ieee80211_radar_detected(struct ieee80211_hw *hw) ieee80211_queue_work(hw, &local->radar_detected_work); } EXPORT_SYMBOL(ieee80211_radar_detected); + +u32 ieee80211_chandef_downgrade(struct cfg80211_chan_def *c) +{ + u32 ret; + int tmp; + + switch (c->width) { + case NL80211_CHAN_WIDTH_20: + c->width = NL80211_CHAN_WIDTH_20_NOHT; + ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT; + break; + case NL80211_CHAN_WIDTH_40: + c->width = NL80211_CHAN_WIDTH_20; + c->center_freq1 = c->chan->center_freq; + ret = IEEE80211_STA_DISABLE_40MHZ | + IEEE80211_STA_DISABLE_VHT; + break; + case NL80211_CHAN_WIDTH_80: + tmp = (30 + c->chan->center_freq - c->center_freq1)/20; + /* n_P40 */ + tmp /= 2; + /* freq_P40 */ + c->center_freq1 = c->center_freq1 - 20 + 40 * tmp; + c->width = NL80211_CHAN_WIDTH_40; + ret = IEEE80211_STA_DISABLE_VHT; + break; + case NL80211_CHAN_WIDTH_80P80: + c->center_freq2 = 0; + c->width = NL80211_CHAN_WIDTH_80; + ret = IEEE80211_STA_DISABLE_80P80MHZ | + IEEE80211_STA_DISABLE_160MHZ; + break; + case NL80211_CHAN_WIDTH_160: + /* n_P20 */ + tmp = (70 + c->chan->center_freq - c->center_freq1)/20; + /* n_P80 */ + tmp /= 4; + c->center_freq1 = c->center_freq1 - 40 + 80 * tmp; + c->width = NL80211_CHAN_WIDTH_80; + ret = IEEE80211_STA_DISABLE_80P80MHZ | + IEEE80211_STA_DISABLE_160MHZ; + break; + default: + case NL80211_CHAN_WIDTH_20_NOHT: + WARN_ON_ONCE(1); + c->width = NL80211_CHAN_WIDTH_20_NOHT; + ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT; + break; + case NL80211_CHAN_WIDTH_5: + case NL80211_CHAN_WIDTH_10: + WARN_ON_ONCE(1); + /* keep c->width */ + ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT; + break; + } + + WARN_ON_ONCE(!cfg80211_chandef_valid(c)); + + return ret; +} + +/* + * Returns true if smps_mode_new is strictly more restrictive than + * smps_mode_old. + */ +bool ieee80211_smps_is_restrictive(enum ieee80211_smps_mode smps_mode_old, + enum ieee80211_smps_mode smps_mode_new) +{ + if (WARN_ON_ONCE(smps_mode_old == IEEE80211_SMPS_AUTOMATIC || + smps_mode_new == IEEE80211_SMPS_AUTOMATIC)) + return false; + + switch (smps_mode_old) { + case IEEE80211_SMPS_STATIC: + return false; + case IEEE80211_SMPS_DYNAMIC: + return smps_mode_new == IEEE80211_SMPS_STATIC; + case IEEE80211_SMPS_OFF: + return smps_mode_new != IEEE80211_SMPS_OFF; + default: + WARN_ON(1); + } + + return false; +} + +int ieee80211_send_action_csa(struct ieee80211_sub_if_data *sdata, + struct cfg80211_csa_settings *csa_settings) +{ + struct sk_buff *skb; + struct ieee80211_mgmt *mgmt; + struct ieee80211_local *local = sdata->local; + int freq; + int hdr_len = offsetof(struct ieee80211_mgmt, u.action.u.chan_switch) + + sizeof(mgmt->u.action.u.chan_switch); + u8 *pos; + + if (sdata->vif.type != NL80211_IFTYPE_ADHOC && + sdata->vif.type != NL80211_IFTYPE_MESH_POINT) + return -EOPNOTSUPP; + + skb = dev_alloc_skb(local->tx_headroom + hdr_len + + 5 + /* channel switch announcement element */ + 3 + /* secondary channel offset element */ + 8); /* mesh channel switch parameters element */ + if (!skb) + return -ENOMEM; + + skb_reserve(skb, local->tx_headroom); + mgmt = (struct ieee80211_mgmt *)skb_put(skb, hdr_len); + memset(mgmt, 0, hdr_len); + mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_ACTION); + + eth_broadcast_addr(mgmt->da); + memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN); + if (ieee80211_vif_is_mesh(&sdata->vif)) { + memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN); + } else { + struct ieee80211_if_ibss *ifibss = &sdata->u.ibss; + memcpy(mgmt->bssid, ifibss->bssid, ETH_ALEN); + } + mgmt->u.action.category = WLAN_CATEGORY_SPECTRUM_MGMT; + mgmt->u.action.u.chan_switch.action_code = WLAN_ACTION_SPCT_CHL_SWITCH; + pos = skb_put(skb, 5); + *pos++ = WLAN_EID_CHANNEL_SWITCH; /* EID */ + *pos++ = 3; /* IE length */ + *pos++ = csa_settings->block_tx ? 1 : 0; /* CSA mode */ + freq = csa_settings->chandef.chan->center_freq; + *pos++ = ieee80211_frequency_to_channel(freq); /* channel */ + *pos++ = csa_settings->count; /* count */ + + if (csa_settings->chandef.width == NL80211_CHAN_WIDTH_40) { + enum nl80211_channel_type ch_type; + + skb_put(skb, 3); + *pos++ = WLAN_EID_SECONDARY_CHANNEL_OFFSET; /* EID */ + *pos++ = 1; /* IE length */ + ch_type = cfg80211_get_chandef_type(&csa_settings->chandef); + if (ch_type == NL80211_CHAN_HT40PLUS) + *pos++ = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + else + *pos++ = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + } + + if (ieee80211_vif_is_mesh(&sdata->vif)) { + struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + + skb_put(skb, 8); + *pos++ = WLAN_EID_CHAN_SWITCH_PARAM; /* EID */ + *pos++ = 6; /* IE length */ + *pos++ = sdata->u.mesh.mshcfg.dot11MeshTTL; /* Mesh TTL */ + *pos = 0x00; /* Mesh Flag: Tx Restrict, Initiator, Reason */ + *pos |= WLAN_EID_CHAN_SWITCH_PARAM_INITIATOR; + *pos++ |= csa_settings->block_tx ? + WLAN_EID_CHAN_SWITCH_PARAM_TX_RESTRICT : 0x00; + put_unaligned_le16(WLAN_REASON_MESH_CHAN, pos); /* Reason Cd */ + pos += 2; + put_unaligned_le16(ifmsh->pre_value, pos);/* Precedence Value */ + pos += 2; + } + + ieee80211_tx_skb(sdata, skb); + return 0; +} + +bool ieee80211_cs_valid(const struct ieee80211_cipher_scheme *cs) +{ + return !(cs == NULL || cs->cipher == 0 || + cs->hdr_len < cs->pn_len + cs->pn_off || + cs->hdr_len <= cs->key_idx_off || + cs->key_idx_shift > 7 || + cs->key_idx_mask == 0); +} + +bool ieee80211_cs_list_valid(const struct ieee80211_cipher_scheme *cs, int n) +{ + int i; + + /* Ensure we have enough iftype bitmap space for all iftype values */ + WARN_ON((NUM_NL80211_IFTYPES / 8 + 1) > sizeof(cs[0].iftype)); + + for (i = 0; i < n; i++) + if (!ieee80211_cs_valid(&cs[i])) + return false; + + return true; +} + +const struct ieee80211_cipher_scheme * +ieee80211_cs_get(struct ieee80211_local *local, u32 cipher, + enum nl80211_iftype iftype) +{ + const struct ieee80211_cipher_scheme *l = local->hw.cipher_schemes; + int n = local->hw.n_cipher_schemes; + int i; + const struct ieee80211_cipher_scheme *cs = NULL; + + for (i = 0; i < n; i++) { + if (l[i].cipher == cipher) { + cs = &l[i]; + break; + } + } + + if (!cs || !(cs->iftype & BIT(iftype))) + return NULL; + + return cs; +} + +int ieee80211_cs_headroom(struct ieee80211_local *local, + struct cfg80211_crypto_settings *crypto, + enum nl80211_iftype iftype) +{ + const struct ieee80211_cipher_scheme *cs; + int headroom = IEEE80211_ENCRYPT_HEADROOM; + int i; + + for (i = 0; i < crypto->n_ciphers_pairwise; i++) { + cs = ieee80211_cs_get(local, crypto->ciphers_pairwise[i], + iftype); + + if (cs && headroom < cs->hdr_len) + headroom = cs->hdr_len; + } + + cs = ieee80211_cs_get(local, crypto->cipher_group, iftype); + if (cs && headroom < cs->hdr_len) + headroom = cs->hdr_len; + + return headroom; +} + +static bool +ieee80211_extend_noa_desc(struct ieee80211_noa_data *data, u32 tsf, int i) +{ + s32 end = data->desc[i].start + data->desc[i].duration - (tsf + 1); + int skip; + + if (end > 0) + return false; + + /* End time is in the past, check for repetitions */ + skip = DIV_ROUND_UP(-end, data->desc[i].interval); + if (data->count[i] < 255) { + if (data->count[i] <= skip) { + data->count[i] = 0; + return false; + } + + data->count[i] -= skip; + } + + data->desc[i].start += skip * data->desc[i].interval; + + return true; +} + +static bool +ieee80211_extend_absent_time(struct ieee80211_noa_data *data, u32 tsf, + s32 *offset) +{ + bool ret = false; + int i; + + for (i = 0; i < IEEE80211_P2P_NOA_DESC_MAX; i++) { + s32 cur; + + if (!data->count[i]) + continue; + + if (ieee80211_extend_noa_desc(data, tsf + *offset, i)) + ret = true; + + cur = data->desc[i].start - tsf; + if (cur > *offset) + continue; + + cur = data->desc[i].start + data->desc[i].duration - tsf; + if (cur > *offset) + *offset = cur; + } + + return ret; +} + +static u32 +ieee80211_get_noa_absent_time(struct ieee80211_noa_data *data, u32 tsf) +{ + s32 offset = 0; + int tries = 0; + /* + * arbitrary limit, used to avoid infinite loops when combined NoA + * descriptors cover the full time period. + */ + int max_tries = 5; + + ieee80211_extend_absent_time(data, tsf, &offset); + do { + if (!ieee80211_extend_absent_time(data, tsf, &offset)) + break; + + tries++; + } while (tries < max_tries); + + return offset; +} + +void ieee80211_update_p2p_noa(struct ieee80211_noa_data *data, u32 tsf) +{ + u32 next_offset = BIT(31) - 1; + int i; + + data->absent = 0; + data->has_next_tsf = false; + for (i = 0; i < IEEE80211_P2P_NOA_DESC_MAX; i++) { + s32 start; + + if (!data->count[i]) + continue; + + ieee80211_extend_noa_desc(data, tsf, i); + start = data->desc[i].start - tsf; + if (start <= 0) + data->absent |= BIT(i); + + if (next_offset > start) + next_offset = start; + + data->has_next_tsf = true; + } + + if (data->absent) + next_offset = ieee80211_get_noa_absent_time(data, tsf); + + data->next_tsf = tsf + next_offset; +} +EXPORT_SYMBOL(ieee80211_update_p2p_noa); + +int ieee80211_parse_p2p_noa(const struct ieee80211_p2p_noa_attr *attr, + struct ieee80211_noa_data *data, u32 tsf) +{ + int ret = 0; + int i; + + memset(data, 0, sizeof(*data)); + + for (i = 0; i < IEEE80211_P2P_NOA_DESC_MAX; i++) { + const struct ieee80211_p2p_noa_desc *desc = &attr->desc[i]; + + if (!desc->count || !desc->duration) + continue; + + data->count[i] = desc->count; + data->desc[i].start = le32_to_cpu(desc->start_time); + data->desc[i].duration = le32_to_cpu(desc->duration); + data->desc[i].interval = le32_to_cpu(desc->interval); + + if (data->count[i] > 1 && + data->desc[i].interval < data->desc[i].duration) + continue; + + ieee80211_extend_noa_desc(data, tsf, i); + ret++; + } + + if (ret) + ieee80211_update_p2p_noa(data, tsf); + + return ret; +} +EXPORT_SYMBOL(ieee80211_parse_p2p_noa); + +void ieee80211_recalc_dtim(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata) +{ + u64 tsf = drv_get_tsf(local, sdata); + u64 dtim_count = 0; + u16 beacon_int = sdata->vif.bss_conf.beacon_int * 1024; + u8 dtim_period = sdata->vif.bss_conf.dtim_period; + struct ps_data *ps; + u8 bcns_from_dtim; + + if (tsf == -1ULL || !beacon_int || !dtim_period) + return; + + if (sdata->vif.type == NL80211_IFTYPE_AP || + sdata->vif.type == NL80211_IFTYPE_AP_VLAN) { + if (!sdata->bss) + return; + + ps = &sdata->bss->ps; + } else if (ieee80211_vif_is_mesh(&sdata->vif)) { + ps = &sdata->u.mesh.ps; + } else { + return; + } + + /* + * actually finds last dtim_count, mac80211 will update in + * __beacon_add_tim(). + * dtim_count = dtim_period - (tsf / bcn_int) % dtim_period + */ + do_div(tsf, beacon_int); + bcns_from_dtim = do_div(tsf, dtim_period); + /* just had a DTIM */ + if (!bcns_from_dtim) + dtim_count = 0; + else + dtim_count = dtim_period - bcns_from_dtim; + + ps->dtim_count = dtim_count; +} + +int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, + const struct cfg80211_chan_def *chandef, + enum ieee80211_chanctx_mode chanmode, + u8 radar_detect) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_sub_if_data *sdata_iter; + enum nl80211_iftype iftype = sdata->wdev.iftype; + int num[NUM_NL80211_IFTYPES]; + struct ieee80211_chanctx *ctx; + int num_different_channels = 0; + int total = 1; + + lockdep_assert_held(&local->chanctx_mtx); + + if (WARN_ON(hweight32(radar_detect) > 1)) + return -EINVAL; + + if (WARN_ON(chandef && chanmode == IEEE80211_CHANCTX_SHARED && + !chandef->chan)) + return -EINVAL; + + if (chandef) + num_different_channels = 1; + + if (WARN_ON(iftype >= NUM_NL80211_IFTYPES)) + return -EINVAL; + + /* Always allow software iftypes */ + if (local->hw.wiphy->software_iftypes & BIT(iftype)) { + if (radar_detect) + return -EINVAL; + return 0; + } + + memset(num, 0, sizeof(num)); + + if (iftype != NL80211_IFTYPE_UNSPECIFIED) + num[iftype] = 1; + + list_for_each_entry(ctx, &local->chanctx_list, list) { + if (ctx->conf.radar_enabled) + radar_detect |= BIT(ctx->conf.def.width); + if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) { + num_different_channels++; + continue; + } + if (chandef && chanmode == IEEE80211_CHANCTX_SHARED && + cfg80211_chandef_compatible(chandef, + &ctx->conf.def)) + continue; + num_different_channels++; + } + + list_for_each_entry_rcu(sdata_iter, &local->interfaces, list) { + struct wireless_dev *wdev_iter; + + wdev_iter = &sdata_iter->wdev; + + if (sdata_iter == sdata || + rcu_access_pointer(sdata_iter->vif.chanctx_conf) == NULL || + local->hw.wiphy->software_iftypes & BIT(wdev_iter->iftype)) + continue; + + num[wdev_iter->iftype]++; + total++; + } + + if (total == 1 && !radar_detect) + return 0; + + return cfg80211_check_combinations(local->hw.wiphy, + num_different_channels, + radar_detect, num); +} + +static void +ieee80211_iter_max_chans(const struct ieee80211_iface_combination *c, + void *data) +{ + u32 *max_num_different_channels = data; + + *max_num_different_channels = max(*max_num_different_channels, + c->num_different_channels); +} + +int ieee80211_max_num_channels(struct ieee80211_local *local) +{ + struct ieee80211_sub_if_data *sdata; + int num[NUM_NL80211_IFTYPES] = {}; + struct ieee80211_chanctx *ctx; + int num_different_channels = 0; + u8 radar_detect = 0; + u32 max_num_different_channels = 1; + int err; + + lockdep_assert_held(&local->chanctx_mtx); + + list_for_each_entry(ctx, &local->chanctx_list, list) { + num_different_channels++; + + if (ctx->conf.radar_enabled) + radar_detect |= BIT(ctx->conf.def.width); + } + + list_for_each_entry_rcu(sdata, &local->interfaces, list) + num[sdata->wdev.iftype]++; + + err = cfg80211_iter_combinations(local->hw.wiphy, + num_different_channels, radar_detect, + num, ieee80211_iter_max_chans, + &max_num_different_channels); + if (err < 0) + return err; + + return max_num_different_channels; +} diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c index 97c289414e3..9265adfdabf 100644 --- a/net/mac80211/vht.c +++ b/net/mac80211/vht.c @@ -129,9 +129,12 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata, if (!vht_cap_ie || !sband->vht_cap.vht_supported) return; - /* A VHT STA must support 40 MHz */ - if (!(sta->sta.ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) - return; + /* + * A VHT STA must support 40 MHz, but if we verify that here + * then we break a few things - some APs (e.g. Netgear R6300v2 + * and others based on the BCM4360 chipset) will unset this + * capability bit when operating in 20 MHz. + */ vht_cap->vht_supported = true; @@ -182,16 +185,15 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata, IEEE80211_VHT_CAP_SHORT_GI_160); /* remaining ones */ - if (own_cap.cap & IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE) { + if (own_cap.cap & IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE) vht_cap->cap |= cap_info & (IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE | - IEEE80211_VHT_CAP_BEAMFORMER_ANTENNAS_MAX | - IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_MAX); - } + IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_MASK); if (own_cap.cap & IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE) vht_cap->cap |= cap_info & - IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; + (IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | + IEEE80211_VHT_CAP_BEAMFORMEE_STS_MASK); if (own_cap.cap & IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE) vht_cap->cap |= cap_info & @@ -350,9 +352,9 @@ void ieee80211_sta_set_rx_nss(struct sta_info *sta) sta->sta.rx_nss = max_t(u8, 1, ht_rx_nss); } -void ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata, - struct sta_info *sta, u8 opmode, - enum ieee80211_band band, bool nss_only) +u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata, + struct sta_info *sta, u8 opmode, + enum ieee80211_band band, bool nss_only) { struct ieee80211_local *local = sdata->local; struct ieee80211_supported_band *sband; @@ -364,7 +366,7 @@ void ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata, /* ignore - no support for BF yet */ if (opmode & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF) - return; + return 0; nss = opmode & IEEE80211_OPMODE_NOTIF_RX_NSS_MASK; nss >>= IEEE80211_OPMODE_NOTIF_RX_NSS_SHIFT; @@ -376,7 +378,7 @@ void ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata, } if (nss_only) - goto change; + return changed; switch (opmode & IEEE80211_OPMODE_NOTIF_CHANWIDTH_MASK) { case IEEE80211_OPMODE_NOTIF_CHANWIDTH_20MHZ: @@ -399,7 +401,19 @@ void ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata, changed |= IEEE80211_RC_BW_CHANGED; } - change: - if (changed) + return changed; +} + +void ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata, + struct sta_info *sta, u8 opmode, + enum ieee80211_band band, bool nss_only) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_supported_band *sband = local->hw.wiphy->bands[band]; + + u32 changed = __ieee80211_vht_handle_opmode(sdata, sta, opmode, + band, nss_only); + + if (changed > 0) rate_control_rate_update(local, sband, sta, changed); } diff --git a/net/mac80211/wme.c b/net/mac80211/wme.c index afba19cb6f8..d51422c778d 100644 --- a/net/mac80211/wme.c +++ b/net/mac80211/wme.c @@ -106,6 +106,7 @@ u16 ieee80211_select_queue(struct ieee80211_sub_if_data *sdata, struct sta_info *sta = NULL; const u8 *ra = NULL; bool qos = false; + struct mac80211_qos_map *qos_map; if (local->hw.queues < IEEE80211_NUM_ACS || skb->len < 6) { skb->priority = 0; /* required for correct WPA/11i MIC */ @@ -153,9 +154,18 @@ u16 ieee80211_select_queue(struct ieee80211_sub_if_data *sdata, return IEEE80211_AC_BE; } + if (skb->protocol == sdata->control_port_protocol) { + skb->priority = 7; + return ieee80211_downgrade_queue(sdata, skb); + } + /* use the data classifier to determine what 802.1d tag the * data frame has */ - skb->priority = cfg80211_classify8021d(skb); + rcu_read_lock(); + qos_map = rcu_dereference(sdata->qos_map); + skb->priority = cfg80211_classify8021d(skb, qos_map ? + &qos_map->qos_map : NULL); + rcu_read_unlock(); return ieee80211_downgrade_queue(sdata, skb); } diff --git a/net/mac80211/wpa.c b/net/mac80211/wpa.c index c9edfcb7a13..9b3dcc20114 100644 --- a/net/mac80211/wpa.c +++ b/net/mac80211/wpa.c @@ -127,7 +127,7 @@ ieee80211_rx_h_michael_mic_verify(struct ieee80211_rx_data *rx) * APs with pairwise keys should never receive Michael MIC * errors for non-zero keyidx because these are reserved for * group keys and only the AP is sending real multicast - * frames in the BSS. ( + * frames in the BSS. */ return RX_DROP_UNUSABLE; } @@ -301,22 +301,15 @@ ieee80211_crypto_tkip_decrypt(struct ieee80211_rx_data *rx) } -static void ccmp_special_blocks(struct sk_buff *skb, u8 *pn, u8 *scratch, - int encrypted) +static void ccmp_special_blocks(struct sk_buff *skb, u8 *pn, u8 *b_0, u8 *aad) { __le16 mask_fc; int a4_included, mgmt; u8 qos_tid; - u8 *b_0, *aad; - u16 data_len, len_a; + u16 len_a; unsigned int hdrlen; struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; - memset(scratch, 0, 6 * AES_BLOCK_SIZE); - - b_0 = scratch + 3 * AES_BLOCK_SIZE; - aad = scratch + 4 * AES_BLOCK_SIZE; - /* * Mask FC: zero subtype b4 b5 b6 (if not mgmt) * Retry, PwrMgt, MoreData; set Protected @@ -338,20 +331,21 @@ static void ccmp_special_blocks(struct sk_buff *skb, u8 *pn, u8 *scratch, else qos_tid = 0; - data_len = skb->len - hdrlen - IEEE80211_CCMP_HDR_LEN; - if (encrypted) - data_len -= IEEE80211_CCMP_MIC_LEN; + /* In CCM, the initial vectors (IV) used for CTR mode encryption and CBC + * mode authentication are not allowed to collide, yet both are derived + * from this vector b_0. We only set L := 1 here to indicate that the + * data size can be represented in (L+1) bytes. The CCM layer will take + * care of storing the data length in the top (L+1) bytes and setting + * and clearing the other bits as is required to derive the two IVs. + */ + b_0[0] = 0x1; - /* First block, b_0 */ - b_0[0] = 0x59; /* flags: Adata: 1, M: 011, L: 001 */ /* Nonce: Nonce Flags | A2 | PN * Nonce Flags: Priority (b0..b3) | Management (b4) | Reserved (b5..b7) */ b_0[1] = qos_tid | (mgmt << 4); memcpy(&b_0[2], hdr->addr2, ETH_ALEN); memcpy(&b_0[8], pn, IEEE80211_CCMP_PN_LEN); - /* l(m) */ - put_unaligned_be16(data_len, &b_0[14]); /* AAD (extra authenticate-only data) / masked 802.11 header * FC | A1 | A2 | A3 | SC | [A4] | [QC] */ @@ -407,11 +401,15 @@ static int ccmp_encrypt_skb(struct ieee80211_tx_data *tx, struct sk_buff *skb) u8 *pos; u8 pn[6]; u64 pn64; - u8 scratch[6 * AES_BLOCK_SIZE]; + u8 aad[2 * AES_BLOCK_SIZE]; + u8 b_0[AES_BLOCK_SIZE]; if (info->control.hw_key && !(info->control.hw_key->flags & IEEE80211_KEY_FLAG_GENERATE_IV) && - !(info->control.hw_key->flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE)) { + !(info->control.hw_key->flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE) && + !((info->control.hw_key->flags & + IEEE80211_KEY_FLAG_GENERATE_IV_MGMT) && + ieee80211_is_mgmt(hdr->frame_control))) { /* * hwaccel has no need for preallocated room for CCMP * header or MIC fields @@ -460,9 +458,9 @@ static int ccmp_encrypt_skb(struct ieee80211_tx_data *tx, struct sk_buff *skb) return 0; pos += IEEE80211_CCMP_HDR_LEN; - ccmp_special_blocks(skb, pn, scratch, 0); - ieee80211_aes_ccm_encrypt(key->u.ccmp.tfm, scratch, pos, len, - pos, skb_put(skb, IEEE80211_CCMP_MIC_LEN)); + ccmp_special_blocks(skb, pn, b_0, aad); + ieee80211_aes_ccm_encrypt(key->u.ccmp.tfm, b_0, aad, pos, len, + skb_put(skb, IEEE80211_CCMP_MIC_LEN)); return 0; } @@ -499,7 +497,7 @@ ieee80211_crypto_ccmp_decrypt(struct ieee80211_rx_data *rx) hdrlen = ieee80211_hdrlen(hdr->frame_control); if (!ieee80211_is_data(hdr->frame_control) && - !ieee80211_is_robust_mgmt_frame(hdr)) + !ieee80211_is_robust_mgmt_frame(skb)) return RX_CONTINUE; data_len = skb->len - hdrlen - IEEE80211_CCMP_HDR_LEN - @@ -525,16 +523,16 @@ ieee80211_crypto_ccmp_decrypt(struct ieee80211_rx_data *rx) } if (!(status->flag & RX_FLAG_DECRYPTED)) { - u8 scratch[6 * AES_BLOCK_SIZE]; + u8 aad[2 * AES_BLOCK_SIZE]; + u8 b_0[AES_BLOCK_SIZE]; /* hardware didn't decrypt/verify MIC */ - ccmp_special_blocks(skb, pn, scratch, 1); + ccmp_special_blocks(skb, pn, b_0, aad); if (ieee80211_aes_ccm_decrypt( - key->u.ccmp.tfm, scratch, + key->u.ccmp.tfm, b_0, aad, skb->data + hdrlen + IEEE80211_CCMP_HDR_LEN, data_len, - skb->data + skb->len - IEEE80211_CCMP_MIC_LEN, - skb->data + hdrlen + IEEE80211_CCMP_HDR_LEN)) + skb->data + skb->len - IEEE80211_CCMP_MIC_LEN)) return RX_DROP_UNUSABLE; } @@ -549,6 +547,106 @@ ieee80211_crypto_ccmp_decrypt(struct ieee80211_rx_data *rx) return RX_CONTINUE; } +static ieee80211_tx_result +ieee80211_crypto_cs_encrypt(struct ieee80211_tx_data *tx, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_key *key = tx->key; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + const struct ieee80211_cipher_scheme *cs = key->sta->cipher_scheme; + int hdrlen; + u8 *pos; + + if (info->control.hw_key && + !(info->control.hw_key->flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE)) { + /* hwaccel has no need for preallocated head room */ + return TX_CONTINUE; + } + + if (unlikely(skb_headroom(skb) < cs->hdr_len && + pskb_expand_head(skb, cs->hdr_len, 0, GFP_ATOMIC))) + return TX_DROP; + + hdrlen = ieee80211_hdrlen(hdr->frame_control); + + pos = skb_push(skb, cs->hdr_len); + memmove(pos, pos + cs->hdr_len, hdrlen); + skb_set_network_header(skb, skb_network_offset(skb) + cs->hdr_len); + + return TX_CONTINUE; +} + +static inline int ieee80211_crypto_cs_pn_compare(u8 *pn1, u8 *pn2, int len) +{ + int i; + + /* pn is little endian */ + for (i = len - 1; i >= 0; i--) { + if (pn1[i] < pn2[i]) + return -1; + else if (pn1[i] > pn2[i]) + return 1; + } + + return 0; +} + +static ieee80211_rx_result +ieee80211_crypto_cs_decrypt(struct ieee80211_rx_data *rx) +{ + struct ieee80211_key *key = rx->key; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data; + const struct ieee80211_cipher_scheme *cs = NULL; + int hdrlen = ieee80211_hdrlen(hdr->frame_control); + struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb); + int data_len; + u8 *rx_pn; + u8 *skb_pn; + u8 qos_tid; + + if (!rx->sta || !rx->sta->cipher_scheme || + !(status->flag & RX_FLAG_DECRYPTED)) + return RX_DROP_UNUSABLE; + + if (!ieee80211_is_data(hdr->frame_control)) + return RX_CONTINUE; + + cs = rx->sta->cipher_scheme; + + data_len = rx->skb->len - hdrlen - cs->hdr_len; + + if (data_len < 0) + return RX_DROP_UNUSABLE; + + if (ieee80211_is_data_qos(hdr->frame_control)) + qos_tid = *ieee80211_get_qos_ctl(hdr) & + IEEE80211_QOS_CTL_TID_MASK; + else + qos_tid = 0; + + if (skb_linearize(rx->skb)) + return RX_DROP_UNUSABLE; + + hdr = (struct ieee80211_hdr *)rx->skb->data; + + rx_pn = key->u.gen.rx_pn[qos_tid]; + skb_pn = rx->skb->data + hdrlen + cs->pn_off; + + if (ieee80211_crypto_cs_pn_compare(skb_pn, rx_pn, cs->pn_len) <= 0) + return RX_DROP_UNUSABLE; + + memcpy(rx_pn, skb_pn, cs->pn_len); + + /* remove security header and MIC */ + if (pskb_trim(rx->skb, rx->skb->len - cs->mic_len)) + return RX_DROP_UNUSABLE; + + memmove(rx->skb->data + cs->hdr_len, rx->skb->data, hdrlen); + skb_pull(rx->skb, cs->hdr_len); + + return RX_CONTINUE; +} static void bip_aad(struct sk_buff *skb, u8 *aad) { @@ -689,6 +787,7 @@ ieee80211_crypto_hw_encrypt(struct ieee80211_tx_data *tx) { struct sk_buff *skb; struct ieee80211_tx_info *info = NULL; + ieee80211_tx_result res; skb_queue_walk(&tx->skbs, skb) { info = IEEE80211_SKB_CB(skb); @@ -696,9 +795,24 @@ ieee80211_crypto_hw_encrypt(struct ieee80211_tx_data *tx) /* handle hw-only algorithm */ if (!info->control.hw_key) return TX_DROP; + + if (tx->key->sta->cipher_scheme) { + res = ieee80211_crypto_cs_encrypt(tx, skb); + if (res != TX_CONTINUE) + return res; + } } ieee80211_tx_set_protected(tx); return TX_CONTINUE; } + +ieee80211_rx_result +ieee80211_crypto_hw_decrypt(struct ieee80211_rx_data *rx) +{ + if (rx->sta->cipher_scheme) + return ieee80211_crypto_cs_decrypt(rx); + + return RX_DROP_UNUSABLE; +} diff --git a/net/mac80211/wpa.h b/net/mac80211/wpa.h index 07e33f899c7..62e5a12dfe0 100644 --- a/net/mac80211/wpa.h +++ b/net/mac80211/wpa.h @@ -34,5 +34,7 @@ ieee80211_rx_result ieee80211_crypto_aes_cmac_decrypt(struct ieee80211_rx_data *rx); ieee80211_tx_result ieee80211_crypto_hw_encrypt(struct ieee80211_tx_data *tx); +ieee80211_rx_result +ieee80211_crypto_hw_decrypt(struct ieee80211_rx_data *rx); #endif /* WPA_H */ |
