summaryrefslogtreecommitdiff
path: root/net/mac80211/mlme.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/mac80211/mlme.c')
-rw-r--r--net/mac80211/mlme.c2547
1 files changed, 2136 insertions, 411 deletions
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 4779a18ab75d..e56ad4b9330f 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -8,7 +8,7 @@
* Copyright 2007, Michael Wu <flamingice@sourmilk.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015 - 2017 Intel Deutschland GmbH
- * Copyright (C) 2018 - 2024 Intel Corporation
+ * Copyright (C) 2018 - 2025 Intel Corporation
*/
#include <linux/delay.h>
@@ -23,7 +23,7 @@
#include <linux/slab.h>
#include <linux/export.h>
#include <net/mac80211.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include "ieee80211_i.h"
#include "driver-ops.h"
@@ -31,6 +31,8 @@
#include "led.h"
#include "fils_aead.h"
+#include <kunit/static_stub.h>
+
#define IEEE80211_AUTH_TIMEOUT (HZ / 5)
#define IEEE80211_AUTH_TIMEOUT_LONG (HZ / 2)
#define IEEE80211_AUTH_TIMEOUT_SHORT (HZ / 10)
@@ -43,7 +45,7 @@
#define IEEE80211_ASSOC_TIMEOUT_SHORT (HZ / 10)
#define IEEE80211_ASSOC_MAX_TRIES 3
-#define IEEE80211_ADV_TTLM_SAFETY_BUFFER_MS msecs_to_jiffies(100)
+#define IEEE80211_ADV_TTLM_SAFETY_BUFFER_MS (100 * USEC_PER_MSEC)
#define IEEE80211_ADV_TTLM_ST_UNDERFLOW 0xff00
#define IEEE80211_NEG_TTLM_REQ_TIMEOUT (HZ / 5)
@@ -166,6 +168,9 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
bool no_vht = false;
u32 ht_cfreq;
+ if (ieee80211_hw_check(&sdata->local->hw, STRICT))
+ ignore_ht_channel_mismatch = false;
+
*chandef = (struct cfg80211_chan_def) {
.chan = channel,
.width = NL80211_CHAN_WIDTH_20_NOHT,
@@ -175,10 +180,11 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
/* get special S1G case out of the way */
if (sband->band == NL80211_BAND_S1GHZ) {
- if (!ieee80211_chandef_s1g_oper(elems->s1g_oper, chandef)) {
- sdata_info(sdata,
- "Missing S1G Operation Element? Trying operating == primary\n");
- chandef->width = ieee80211_s1g_channel_width(channel);
+ if (!ieee80211_chandef_s1g_oper(sdata->local, elems->s1g_oper,
+ chandef)) {
+ /* Fallback to default 1MHz */
+ chandef->width = NL80211_CHAN_WIDTH_1;
+ chandef->s1g_primary_2mhz = false;
}
return IEEE80211_CONN_MODE_S1G;
@@ -270,11 +276,8 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
return IEEE80211_CONN_MODE_VHT;
}
} else if (!vht_oper || !elems->vht_cap_elem) {
- if (sband->band == NL80211_BAND_5GHZ) {
- sdata_info(sdata,
- "VHT information is missing, disabling VHT\n");
+ if (sband->band == NL80211_BAND_5GHZ)
return IEEE80211_CONN_MODE_HT;
- }
no_vht = true;
} else if (sband->band == NL80211_BAND_2GHZ) {
no_vht = true;
@@ -341,7 +344,117 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
}
static bool
+ieee80211_verify_sta_ht_mcs_support(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_supported_band *sband,
+ const struct ieee80211_ht_operation *ht_op)
+{
+ struct ieee80211_sta_ht_cap sta_ht_cap;
+ int i;
+
+ if (sband->band == NL80211_BAND_6GHZ)
+ return true;
+
+ if (!ht_op)
+ return false;
+
+ memcpy(&sta_ht_cap, &sband->ht_cap, sizeof(sta_ht_cap));
+ ieee80211_apply_htcap_overrides(sdata, &sta_ht_cap);
+
+ /*
+ * P802.11REVme/D7.0 - 6.5.4.2.4
+ * ...
+ * If the MLME of an HT STA receives an MLME-JOIN.request primitive
+ * with the SelectedBSS parameter containing a Basic HT-MCS Set field
+ * in the HT Operation parameter that contains any unsupported MCSs,
+ * the MLME response in the resulting MLME-JOIN.confirm primitive shall
+ * contain a ResultCode parameter that is not set to the value SUCCESS.
+ * ...
+ */
+
+ /* Simply check that all basic rates are in the STA RX mask */
+ for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++) {
+ if ((ht_op->basic_set[i] & sta_ht_cap.mcs.rx_mask[i]) !=
+ ht_op->basic_set[i])
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+ieee80211_verify_sta_vht_mcs_support(struct ieee80211_sub_if_data *sdata,
+ int link_id,
+ struct ieee80211_supported_band *sband,
+ const struct ieee80211_vht_operation *vht_op)
+{
+ struct ieee80211_sta_vht_cap sta_vht_cap;
+ u16 ap_min_req_set, sta_rx_mcs_map, sta_tx_mcs_map;
+ int nss;
+
+ if (sband->band != NL80211_BAND_5GHZ)
+ return true;
+
+ if (!vht_op)
+ return false;
+
+ memcpy(&sta_vht_cap, &sband->vht_cap, sizeof(sta_vht_cap));
+ ieee80211_apply_vhtcap_overrides(sdata, &sta_vht_cap);
+
+ ap_min_req_set = le16_to_cpu(vht_op->basic_mcs_set);
+ sta_rx_mcs_map = le16_to_cpu(sta_vht_cap.vht_mcs.rx_mcs_map);
+ sta_tx_mcs_map = le16_to_cpu(sta_vht_cap.vht_mcs.tx_mcs_map);
+
+ /*
+ * Many APs are incorrectly advertising an all-zero value here,
+ * which really means MCS 0-7 are required for 1-8 streams, but
+ * they don't really mean it that way.
+ * Some other APs are incorrectly advertising 3 spatial streams
+ * with MCS 0-7 are required, but don't really mean it that way
+ * and we'll connect only with HT, rather than even HE.
+ * As a result, unfortunately the VHT basic MCS/NSS set cannot
+ * be used at all, so check it only in strict mode.
+ */
+ if (!ieee80211_hw_check(&sdata->local->hw, STRICT))
+ return true;
+
+ /*
+ * P802.11REVme/D7.0 - 6.5.4.2.4
+ * ...
+ * If the MLME of a VHT STA receives an MLME-JOIN.request primitive
+ * with a SelectedBSS parameter containing a Basic VHT-MCS And NSS Set
+ * field in the VHT Operation parameter that contains any unsupported
+ * <VHT-MCS, NSS> tuple, the MLME response in the resulting
+ * MLME-JOIN.confirm primitive shall contain a ResultCode parameter
+ * that is not set to the value SUCCESS.
+ * ...
+ */
+ for (nss = 8; nss > 0; nss--) {
+ u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3;
+ u8 sta_rx_val;
+ u8 sta_tx_val;
+
+ if (ap_op_val == IEEE80211_HE_MCS_NOT_SUPPORTED)
+ continue;
+
+ sta_rx_val = (sta_rx_mcs_map >> (2 * (nss - 1))) & 3;
+ sta_tx_val = (sta_tx_mcs_map >> (2 * (nss - 1))) & 3;
+
+ if (sta_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
+ sta_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
+ sta_rx_val < ap_op_val || sta_tx_val < ap_op_val) {
+ link_id_info(sdata, link_id,
+ "Missing mandatory rates for %d Nss, rx %d, tx %d oper %d, disable VHT\n",
+ nss, sta_rx_val, sta_tx_val, ap_op_val);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
ieee80211_verify_peer_he_mcs_support(struct ieee80211_sub_if_data *sdata,
+ int link_id,
const struct ieee80211_he_cap_elem *he_cap,
const struct ieee80211_he_operation *he_op)
{
@@ -369,9 +482,9 @@ ieee80211_verify_peer_he_mcs_support(struct ieee80211_sub_if_data *sdata,
*/
if ((mcs_80_map_tx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED ||
(mcs_80_map_rx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED) {
- sdata_info(sdata,
- "Missing mandatory rates for 1 Nss, rx 0x%x, tx 0x%x, disable HE\n",
- mcs_80_map_tx, mcs_80_map_rx);
+ link_id_info(sdata, link_id,
+ "Missing mandatory rates for 1 Nss, rx 0x%x, tx 0x%x, disable HE\n",
+ mcs_80_map_tx, mcs_80_map_rx);
return false;
}
@@ -385,7 +498,7 @@ ieee80211_verify_peer_he_mcs_support(struct ieee80211_sub_if_data *sdata,
* zeroes, which is nonsense, and completely inconsistent with itself
* (it doesn't have 8 streams). Accept the settings in this case anyway.
*/
- if (!ap_min_req_set)
+ if (!ieee80211_hw_check(&sdata->local->hw, STRICT) && !ap_min_req_set)
return true;
/* make sure the AP is consistent with itself
@@ -415,9 +528,9 @@ ieee80211_verify_peer_he_mcs_support(struct ieee80211_sub_if_data *sdata,
if (ap_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
ap_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
ap_rx_val < ap_op_val || ap_tx_val < ap_op_val) {
- sdata_info(sdata,
- "Invalid rates for %d Nss, rx %d, tx %d oper %d, disable HE\n",
- nss, ap_rx_val, ap_rx_val, ap_op_val);
+ link_id_info(sdata, link_id,
+ "Invalid rates for %d Nss, rx %d, tx %d oper %d, disable HE\n",
+ nss, ap_rx_val, ap_tx_val, ap_op_val);
return false;
}
}
@@ -445,7 +558,7 @@ ieee80211_verify_sta_he_mcs_support(struct ieee80211_sub_if_data *sdata,
* zeroes, which is nonsense, and completely inconsistent with itself
* (it doesn't have 8 streams). Accept the settings in this case anyway.
*/
- if (!ap_min_req_set)
+ if (!ieee80211_hw_check(&sdata->local->hw, STRICT) && !ap_min_req_set)
return true;
/* Need to go over for 80MHz, 160MHz and for 80+80 */
@@ -587,6 +700,68 @@ ieee80211_verify_sta_eht_mcs_support(struct ieee80211_sub_if_data *sdata,
return true;
}
+static void ieee80211_get_rates(struct ieee80211_supported_band *sband,
+ const u8 *supp_rates,
+ unsigned int supp_rates_len,
+ const u8 *ext_supp_rates,
+ unsigned int ext_supp_rates_len,
+ u32 *rates, u32 *basic_rates,
+ unsigned long *unknown_rates_selectors,
+ bool *have_higher_than_11mbit,
+ int *min_rate, int *min_rate_index)
+{
+ int i, j;
+
+ for (i = 0; i < supp_rates_len + ext_supp_rates_len; i++) {
+ u8 supp_rate = i < supp_rates_len ?
+ supp_rates[i] :
+ ext_supp_rates[i - supp_rates_len];
+ int rate = supp_rate & 0x7f;
+ bool is_basic = !!(supp_rate & 0x80);
+
+ if ((rate * 5) > 110 && have_higher_than_11mbit)
+ *have_higher_than_11mbit = true;
+
+ /*
+ * Skip membership selectors since they're not rates.
+ *
+ * Note: Even though the membership selector and the basic
+ * rate flag share the same bit, they are not exactly
+ * the same.
+ */
+ if (is_basic && rate >= BSS_MEMBERSHIP_SELECTOR_MIN) {
+ if (unknown_rates_selectors)
+ set_bit(rate, unknown_rates_selectors);
+ continue;
+ }
+
+ for (j = 0; j < sband->n_bitrates; j++) {
+ struct ieee80211_rate *br;
+ int brate;
+
+ br = &sband->bitrates[j];
+
+ brate = DIV_ROUND_UP(br->bitrate, 5);
+ if (brate == rate) {
+ if (rates)
+ *rates |= BIT(j);
+ if (is_basic && basic_rates)
+ *basic_rates |= BIT(j);
+ if (min_rate && (rate * 5) < *min_rate) {
+ *min_rate = rate * 5;
+ if (min_rate_index)
+ *min_rate_index = j;
+ }
+ break;
+ }
+ }
+
+ /* Handle an unknown entry as if it is an unknown selector */
+ if (is_basic && unknown_rates_selectors && j == sband->n_bitrates)
+ set_bit(rate, unknown_rates_selectors);
+ }
+}
+
static bool ieee80211_chandef_usable(struct ieee80211_sub_if_data *sdata,
const struct cfg80211_chan_def *chandef,
u32 prohibited_flags)
@@ -599,10 +774,6 @@ static bool ieee80211_chandef_usable(struct ieee80211_sub_if_data *sdata,
ieee80211_hw_check(&sdata->local->hw, DISALLOW_PUNCTURING))
return false;
- if (chandef->punctured && chandef->chan->band == NL80211_BAND_5GHZ &&
- ieee80211_hw_check(&sdata->local->hw, DISALLOW_PUNCTURING_5GHZ))
- return false;
-
return true;
}
@@ -611,7 +782,7 @@ static int ieee80211_chandef_num_subchans(const struct cfg80211_chan_def *c)
if (c->width == NL80211_CHAN_WIDTH_80P80)
return 4 + 4;
- return nl80211_chan_width_to_mhz(c->width) / 20;
+ return cfg80211_chandef_get_width(c) / 20;
}
static int ieee80211_chandef_num_widths(const struct cfg80211_chan_def *c)
@@ -812,12 +983,13 @@ static void ieee80211_set_chanreq_ap(struct ieee80211_sub_if_data *sdata,
chanreq->ap = *ap_chandef;
}
-static struct ieee802_11_elems *
+VISIBLE_IF_MAC80211_KUNIT struct ieee802_11_elems *
ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
struct ieee80211_conn_settings *conn,
struct cfg80211_bss *cbss, int link_id,
struct ieee80211_chan_req *chanreq,
- struct cfg80211_chan_def *ap_chandef)
+ struct cfg80211_chan_def *ap_chandef,
+ unsigned long *userspace_selectors)
{
const struct cfg80211_bss_ies *ies = rcu_dereference(cbss->ies);
struct ieee80211_bss *bss = (void *)cbss->priv;
@@ -827,10 +999,15 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
.from_ap = true,
.start = ies->data,
.len = ies->len,
+ .type = ies->from_beacon ?
+ IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_BEACON :
+ IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_PROBE_RESP,
};
struct ieee802_11_elems *elems;
struct ieee80211_supported_band *sband;
enum ieee80211_conn_mode ap_mode;
+ unsigned long unknown_rates_selectors[BITS_TO_LONGS(128)] = {};
+ unsigned long sta_selectors[BITS_TO_LONGS(128)] = {};
int ret;
again:
@@ -859,17 +1036,30 @@ again:
sband = sdata->local->hw.wiphy->bands[channel->band];
+ ieee80211_get_rates(sband, elems->supp_rates, elems->supp_rates_len,
+ elems->ext_supp_rates, elems->ext_supp_rates_len,
+ NULL, NULL, unknown_rates_selectors, NULL, NULL,
+ NULL);
+
switch (channel->band) {
case NL80211_BAND_S1GHZ:
if (WARN_ON(ap_mode != IEEE80211_CONN_MODE_S1G)) {
ret = -EINVAL;
goto free;
}
+
+ chanreq->oper = *ap_chandef;
+ if (!cfg80211_chandef_usable(sdata->wdev.wiphy, &chanreq->oper,
+ IEEE80211_CHAN_DISABLED)) {
+ ret = -EINVAL;
+ goto free;
+ }
+
return elems;
case NL80211_BAND_6GHZ:
if (ap_mode < IEEE80211_CONN_MODE_HE) {
- sdata_info(sdata,
- "Rejecting non-HE 6/7 GHz connection");
+ link_id_info(sdata, link_id,
+ "Rejecting non-HE 6/7 GHz connection");
ret = -EINVAL;
goto free;
}
@@ -909,6 +1099,29 @@ again:
chanreq->oper = *ap_chandef;
+ bitmap_copy(sta_selectors, userspace_selectors, 128);
+ if (conn->mode >= IEEE80211_CONN_MODE_HT)
+ set_bit(BSS_MEMBERSHIP_SELECTOR_HT_PHY, sta_selectors);
+ if (conn->mode >= IEEE80211_CONN_MODE_VHT)
+ set_bit(BSS_MEMBERSHIP_SELECTOR_VHT_PHY, sta_selectors);
+ if (conn->mode >= IEEE80211_CONN_MODE_HE)
+ set_bit(BSS_MEMBERSHIP_SELECTOR_HE_PHY, sta_selectors);
+ if (conn->mode >= IEEE80211_CONN_MODE_EHT)
+ set_bit(BSS_MEMBERSHIP_SELECTOR_EHT_PHY, sta_selectors);
+
+ /*
+ * We do not support EPD or GLK so never add them.
+ * SAE_H2E is handled through userspace_selectors.
+ */
+
+ /* Check if we support all required features */
+ if (!bitmap_subset(unknown_rates_selectors, sta_selectors, 128)) {
+ link_id_info(sdata, link_id,
+ "required basic rate or BSS membership selectors not supported or disabled, rejecting connection\n");
+ ret = -EINVAL;
+ goto free;
+ }
+
ieee80211_set_chanreq_ap(sdata, chanreq, conn, ap_chandef);
while (!ieee80211_chandef_usable(sdata, &chanreq->oper,
@@ -940,16 +1153,38 @@ again:
}
if (chanreq->oper.width != ap_chandef->width || ap_mode != conn->mode)
- sdata_info(sdata,
- "regulatory prevented using AP config, downgraded\n");
+ link_id_info(sdata, link_id,
+ "regulatory prevented using AP config, downgraded\n");
+
+ if (conn->mode >= IEEE80211_CONN_MODE_HT &&
+ !ieee80211_verify_sta_ht_mcs_support(sdata, sband,
+ elems->ht_operation)) {
+ conn->mode = IEEE80211_CONN_MODE_LEGACY;
+ conn->bw_limit = IEEE80211_CONN_BW_LIMIT_20;
+ link_id_info(sdata, link_id,
+ "required MCSes not supported, disabling HT\n");
+ }
+
+ if (conn->mode >= IEEE80211_CONN_MODE_VHT &&
+ !ieee80211_verify_sta_vht_mcs_support(sdata, link_id, sband,
+ elems->vht_operation)) {
+ conn->mode = IEEE80211_CONN_MODE_HT;
+ conn->bw_limit = min_t(enum ieee80211_conn_bw_limit,
+ conn->bw_limit,
+ IEEE80211_CONN_BW_LIMIT_40);
+ link_id_info(sdata, link_id,
+ "required MCSes not supported, disabling VHT\n");
+ }
if (conn->mode >= IEEE80211_CONN_MODE_HE &&
- (!ieee80211_verify_peer_he_mcs_support(sdata, (void *)elems->he_cap,
+ (!ieee80211_verify_peer_he_mcs_support(sdata, link_id,
+ (void *)elems->he_cap,
elems->he_operation) ||
!ieee80211_verify_sta_he_mcs_support(sdata, sband,
elems->he_operation))) {
conn->mode = IEEE80211_CONN_MODE_VHT;
- sdata_info(sdata, "required MCSes not supported, disabling HE\n");
+ link_id_info(sdata, link_id,
+ "required MCSes not supported, disabling HE\n");
}
if (conn->mode >= IEEE80211_CONN_MODE_EHT &&
@@ -959,7 +1194,16 @@ again:
conn->bw_limit = min_t(enum ieee80211_conn_bw_limit,
conn->bw_limit,
IEEE80211_CONN_BW_LIMIT_160);
- sdata_info(sdata, "required MCSes not supported, disabling EHT\n");
+ link_id_info(sdata, link_id,
+ "required MCSes not supported, disabling EHT\n");
+ }
+
+ if (conn->mode >= IEEE80211_CONN_MODE_EHT &&
+ channel->band != NL80211_BAND_2GHZ &&
+ conn->bw_limit == IEEE80211_CONN_BW_LIMIT_40) {
+ conn->mode = IEEE80211_CONN_MODE_HE;
+ link_id_info(sdata, link_id,
+ "required bandwidth not supported, disabling EHT\n");
}
/* the mode can only decrease, so this must terminate */
@@ -983,20 +1227,40 @@ free:
kfree(elems);
return ERR_PTR(ret);
}
+EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_determine_chan_mode);
static int ieee80211_config_bw(struct ieee80211_link_data *link,
struct ieee802_11_elems *elems,
- bool update, u64 *changed)
+ bool update, u64 *changed, u16 stype)
{
struct ieee80211_channel *channel = link->conf->chanreq.oper.chan;
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_chan_req chanreq = {};
struct cfg80211_chan_def ap_chandef;
enum ieee80211_conn_mode ap_mode;
+ const char *frame;
u32 vht_cap_info = 0;
u16 ht_opmode;
int ret;
+ switch (stype) {
+ case IEEE80211_STYPE_BEACON:
+ frame = "beacon";
+ break;
+ case IEEE80211_STYPE_ASSOC_RESP:
+ frame = "assoc response";
+ break;
+ case IEEE80211_STYPE_REASSOC_RESP:
+ frame = "reassoc response";
+ break;
+ case IEEE80211_STYPE_ACTION:
+ /* the only action frame that gets here */
+ frame = "ML reconf response";
+ break;
+ default:
+ return -EINVAL;
+ }
+
/* don't track any bandwidth changes in legacy/S1G modes */
if (link->u.mgd.conn.mode == IEEE80211_CONN_MODE_LEGACY ||
link->u.mgd.conn.mode == IEEE80211_CONN_MODE_S1G)
@@ -1011,9 +1275,10 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link,
if (ap_mode != link->u.mgd.conn.mode) {
link_info(link,
- "AP appears to change mode (expected %s, found %s), disconnect\n",
+ "AP %pM appears to change mode (expected %s, found %s) in %s, disconnect\n",
+ link->u.mgd.bssid,
ieee80211_conn_mode_str(link->u.mgd.conn.mode),
- ieee80211_conn_mode_str(ap_mode));
+ ieee80211_conn_mode_str(ap_mode), frame);
return -EINVAL;
}
@@ -1044,7 +1309,9 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link,
ieee80211_min_bw_limit_from_chandef(&chanreq.oper))
ieee80211_chandef_downgrade(&chanreq.oper, NULL);
- if (ap_chandef.chan->band == NL80211_BAND_6GHZ &&
+ /* TPE element is not present in (re)assoc/ML reconfig response */
+ if (stype == IEEE80211_STYPE_BEACON &&
+ ap_chandef.chan->band == NL80211_BAND_6GHZ &&
link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_HE) {
ieee80211_rearrange_tpe(&elems->tpe, &ap_chandef,
&chanreq.oper);
@@ -1058,16 +1325,16 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link,
return 0;
link_info(link,
- "AP %pM changed bandwidth, new used config is %d.%03d MHz, width %d (%d.%03d/%d MHz)\n",
- link->u.mgd.bssid, chanreq.oper.chan->center_freq,
+ "AP %pM changed bandwidth in %s, new used config is %d.%03d MHz, width %d (%d.%03d/%d MHz)\n",
+ link->u.mgd.bssid, frame, chanreq.oper.chan->center_freq,
chanreq.oper.chan->freq_offset, chanreq.oper.width,
chanreq.oper.center_freq1, chanreq.oper.freq1_offset,
chanreq.oper.center_freq2);
if (!cfg80211_chandef_valid(&chanreq.oper)) {
sdata_info(sdata,
- "AP %pM changed caps/bw in a way we can't support - disconnect\n",
- link->u.mgd.bssid);
+ "AP %pM changed caps/bw in %s in a way we can't support - disconnect\n",
+ link->u.mgd.bssid, frame);
return -EINVAL;
}
@@ -1096,8 +1363,8 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link,
ret = ieee80211_link_change_chanreq(link, &chanreq, changed);
if (ret) {
sdata_info(sdata,
- "AP %pM changed bandwidth to incompatible one - disconnect\n",
- link->u.mgd.bssid);
+ "AP %pM changed bandwidth in %s to incompatible one - disconnect\n",
+ link->u.mgd.bssid, frame);
return ret;
}
@@ -1212,13 +1479,15 @@ static bool ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata,
* Some APs apparently get confused if our capabilities are better
* than theirs, so restrict what we advertise in the assoc request.
*/
- if (!(ap_vht_cap->vht_cap_info &
- cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE)))
- cap &= ~(IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE |
- IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE);
- else if (!(ap_vht_cap->vht_cap_info &
- cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE)))
- cap &= ~IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
+ if (!ieee80211_hw_check(&local->hw, STRICT)) {
+ if (!(ap_vht_cap->vht_cap_info &
+ cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE)))
+ cap &= ~(IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE |
+ IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE);
+ else if (!(ap_vht_cap->vht_cap_info &
+ cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE)))
+ cap &= ~IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
+ }
/*
* If some other vif is using the MU-MIMO capability we cannot associate
@@ -1231,7 +1500,7 @@ static bool ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata,
bool disable_mu_mimo = false;
struct ieee80211_sub_if_data *other;
- list_for_each_entry_rcu(other, &local->interfaces, list) {
+ list_for_each_entry(other, &local->interfaces, list) {
if (other->vif.bss_conf.mu_mimo_owner) {
disable_mu_mimo = true;
break;
@@ -1260,14 +1529,16 @@ static bool ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata,
return mu_mimo_owner;
}
-static void ieee80211_assoc_add_rates(struct sk_buff *skb,
+static void ieee80211_assoc_add_rates(struct ieee80211_local *local,
+ struct sk_buff *skb,
enum nl80211_chan_width width,
struct ieee80211_supported_band *sband,
struct ieee80211_mgd_assoc_data *assoc_data)
{
u32 rates;
- if (assoc_data->supp_rates_len) {
+ if (assoc_data->supp_rates_len &&
+ !ieee80211_hw_check(&local->hw, STRICT)) {
/*
* Get all rates supported by the device and the AP as
* some APs don't like getting a superset of their rates
@@ -1287,9 +1558,9 @@ static void ieee80211_assoc_add_rates(struct sk_buff *skb,
rates = ~0;
}
- ieee80211_put_srates_elem(skb, sband, 0, 0, ~rates,
+ ieee80211_put_srates_elem(skb, sband, 0, ~rates,
WLAN_EID_SUPP_RATES);
- ieee80211_put_srates_elem(skb, sband, 0, 0, ~rates,
+ ieee80211_put_srates_elem(skb, sband, 0, ~rates,
WLAN_EID_EXT_SUPP_RATES);
}
@@ -1407,26 +1678,52 @@ static size_t ieee80211_add_before_he_elems(struct sk_buff *skb,
return noffset;
}
+static size_t ieee80211_add_before_reg_conn(struct sk_buff *skb,
+ const u8 *elems, size_t elems_len,
+ size_t offset)
+{
+ static const u8 before_reg_conn[] = {
+ /*
+ * no need to list the ones split off before HE
+ * or generated here
+ */
+ WLAN_EID_EXTENSION, WLAN_EID_EXT_DH_PARAMETER,
+ WLAN_EID_EXTENSION, WLAN_EID_EXT_KNOWN_STA_IDENTIFCATION,
+ };
+ size_t noffset;
+
+ if (!elems_len)
+ return offset;
+
+ noffset = ieee80211_ie_split(elems, elems_len, before_reg_conn,
+ ARRAY_SIZE(before_reg_conn), offset);
+ skb_put_data(skb, elems + offset, noffset - offset);
+
+ return noffset;
+}
+
#define PRESENT_ELEMS_MAX 8
#define PRESENT_ELEM_EXT_OFFS 0x100
-static void ieee80211_assoc_add_ml_elem(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, u16 capab,
- const struct element *ext_capa,
- const u16 *present_elems);
-
-static size_t ieee80211_assoc_link_elems(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, u16 *capab,
- const struct element *ext_capa,
- const u8 *extra_elems,
- size_t extra_elems_len,
- unsigned int link_id,
- struct ieee80211_link_data *link,
- u16 *present_elems)
+static void
+ieee80211_assoc_add_ml_elem(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, u16 capab,
+ const struct element *ext_capa,
+ const u16 *present_elems,
+ struct ieee80211_mgd_assoc_data *assoc_data);
+
+static size_t
+ieee80211_add_link_elems(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, u16 *capab,
+ const struct element *ext_capa,
+ const u8 *extra_elems,
+ size_t extra_elems_len,
+ unsigned int link_id,
+ struct ieee80211_link_data *link,
+ u16 *present_elems,
+ struct ieee80211_mgd_assoc_data *assoc_data)
{
enum nl80211_iftype iftype = ieee80211_vif_type_p2p(&sdata->vif);
- struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
- struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data;
struct cfg80211_bss *cbss = assoc_data->link[link_id].bss;
struct ieee80211_channel *chan = cbss->channel;
const struct ieee80211_sband_iftype_data *iftd;
@@ -1481,7 +1778,7 @@ static size_t ieee80211_assoc_link_elems(struct ieee80211_sub_if_data *sdata,
*capab |= WLAN_CAPABILITY_SPECTRUM_MGMT;
if (sband->band != NL80211_BAND_S1GHZ)
- ieee80211_assoc_add_rates(skb, width, sband, assoc_data);
+ ieee80211_assoc_add_rates(local, skb, width, sband, assoc_data);
if (*capab & WLAN_CAPABILITY_SPECTRUM_MGMT ||
*capab & WLAN_CAPABILITY_RADIO_MEASURE) {
@@ -1562,7 +1859,24 @@ static size_t ieee80211_assoc_link_elems(struct ieee80211_sub_if_data *sdata,
ieee80211_put_he_cap(skb, sdata, sband,
&assoc_data->link[link_id].conn);
ADD_PRESENT_EXT_ELEM(WLAN_EID_EXT_HE_CAPABILITY);
- ieee80211_put_he_6ghz_cap(skb, sdata, smps_mode);
+ if (sband->band == NL80211_BAND_6GHZ)
+ ieee80211_put_he_6ghz_cap(skb, sdata, smps_mode);
+ }
+
+ /*
+ * if present, add any custom IEs that go before regulatory
+ * connectivity element
+ */
+ offset = ieee80211_add_before_reg_conn(skb, extra_elems,
+ extra_elems_len, offset);
+
+ if (sband->band == NL80211_BAND_6GHZ) {
+ /*
+ * as per Section E.2.7 of IEEE 802.11 REVme D7.0, non-AP STA
+ * capable of operating on the 6 GHz band shall transmit
+ * regulatory connectivity element.
+ */
+ ieee80211_put_reg_conn(skb, chan->flags);
}
/*
@@ -1575,7 +1889,7 @@ static size_t ieee80211_assoc_link_elems(struct ieee80211_sub_if_data *sdata,
if (link_id == assoc_data->assoc_link_id)
ieee80211_assoc_add_ml_elem(sdata, skb, orig_capab, ext_capa,
- present_elems);
+ present_elems, assoc_data);
/* crash if somebody gets it wrong */
present_elems = NULL;
@@ -1654,14 +1968,14 @@ static void ieee80211_add_non_inheritance_elem(struct sk_buff *skb,
*len = skb->len - skb_len - 2;
}
-static void ieee80211_assoc_add_ml_elem(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, u16 capab,
- const struct element *ext_capa,
- const u16 *outer_present_elems)
+static void
+ieee80211_assoc_add_ml_elem(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb, u16 capab,
+ const struct element *ext_capa,
+ const u16 *outer_present_elems,
+ struct ieee80211_mgd_assoc_data *assoc_data)
{
struct ieee80211_local *local = sdata->local;
- struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
- struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data;
struct ieee80211_multi_link_elem *ml_elem;
struct ieee80211_mle_basic_common_info *common;
const struct wiphy_iftype_ext_capab *ift_ext_capa;
@@ -1703,6 +2017,14 @@ static void ieee80211_assoc_add_ml_elem(struct ieee80211_sub_if_data *sdata,
}
skb_put_data(skb, &mld_capa_ops, sizeof(mld_capa_ops));
+ if (assoc_data->ext_mld_capa_ops) {
+ ml_elem->control |=
+ cpu_to_le16(IEEE80211_MLC_BASIC_PRES_EXT_MLD_CAPA_OP);
+ common->len += 2;
+ skb_put_data(skb, &assoc_data->ext_mld_capa_ops,
+ sizeof(assoc_data->ext_mld_capa_ops));
+ }
+
for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
u16 link_present_elems[PRESENT_ELEMS_MAX] = {};
const u8 *extra_elems;
@@ -1734,16 +2056,17 @@ static void ieee80211_assoc_add_ml_elem(struct ieee80211_sub_if_data *sdata,
* (if applicable) are skipped. So we only have
* the capability field (remember the position and fill
* later), followed by the elements added below by
- * calling ieee80211_assoc_link_elems().
+ * calling ieee80211_add_link_elems().
*/
capab_pos = skb_put(skb, 2);
- extra_used = ieee80211_assoc_link_elems(sdata, skb, &capab,
- ext_capa,
- extra_elems,
- extra_elems_len,
- link_id, NULL,
- link_present_elems);
+ extra_used = ieee80211_add_link_elems(sdata, skb, &capab,
+ ext_capa,
+ extra_elems,
+ extra_elems_len,
+ link_id, NULL,
+ link_present_elems,
+ assoc_data);
if (extra_elems)
skb_put_data(skb, extra_elems + extra_used,
extra_elems_len - extra_used);
@@ -1760,6 +2083,58 @@ static void ieee80211_assoc_add_ml_elem(struct ieee80211_sub_if_data *sdata,
ieee80211_fragment_element(skb, ml_elem_len, WLAN_EID_FRAGMENT);
}
+static int
+ieee80211_link_common_elems_size(struct ieee80211_sub_if_data *sdata,
+ enum nl80211_iftype iftype,
+ struct cfg80211_bss *cbss,
+ size_t elems_len)
+{
+ struct ieee80211_local *local = sdata->local;
+ const struct ieee80211_sband_iftype_data *iftd;
+ struct ieee80211_supported_band *sband;
+ size_t size = 0;
+
+ if (!cbss)
+ return size;
+
+ sband = local->hw.wiphy->bands[cbss->channel->band];
+
+ /* add STA profile elements length */
+ size += elems_len;
+
+ /* and supported rates length */
+ size += 4 + sband->n_bitrates;
+
+ /* supported channels */
+ size += 2 + 2 * sband->n_channels;
+
+ iftd = ieee80211_get_sband_iftype_data(sband, iftype);
+ if (iftd)
+ size += iftd->vendor_elems.len;
+
+ /* power capability */
+ size += 4;
+
+ /* HT, VHT, HE, EHT */
+ size += 2 + sizeof(struct ieee80211_ht_cap);
+ size += 2 + sizeof(struct ieee80211_vht_cap);
+ size += 2 + 1 + sizeof(struct ieee80211_he_cap_elem) +
+ sizeof(struct ieee80211_he_mcs_nss_supp) +
+ IEEE80211_HE_PPE_THRES_MAX_LEN;
+
+ if (sband->band == NL80211_BAND_6GHZ) {
+ size += 2 + 1 + sizeof(struct ieee80211_he_6ghz_capa);
+ /* reg connection */
+ size += 4;
+ }
+
+ size += 2 + 1 + sizeof(struct ieee80211_eht_cap_elem) +
+ sizeof(struct ieee80211_eht_mcs_nss_supp) +
+ IEEE80211_EHT_PPE_THRES_MAX_LEN;
+
+ return size;
+}
+
static int ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_local *local = sdata->local;
@@ -1798,42 +2173,15 @@ static int ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
struct cfg80211_bss *cbss = assoc_data->link[link_id].bss;
- const struct ieee80211_sband_iftype_data *iftd;
- struct ieee80211_supported_band *sband;
+ size_t elems_len = assoc_data->link[link_id].elems_len;
if (!cbss)
continue;
- sband = local->hw.wiphy->bands[cbss->channel->band];
-
n_links++;
- /* add STA profile elements length */
- size += assoc_data->link[link_id].elems_len;
- /* and supported rates length */
- size += 4 + sband->n_bitrates;
- /* supported channels */
- size += 2 + 2 * sband->n_channels;
-
- iftd = ieee80211_get_sband_iftype_data(sband, iftype);
- if (iftd)
- size += iftd->vendor_elems.len;
-
- /* power capability */
- size += 4;
- /* HT, VHT, HE, EHT */
- size += 2 + sizeof(struct ieee80211_ht_cap);
- size += 2 + sizeof(struct ieee80211_vht_cap);
- size += 2 + 1 + sizeof(struct ieee80211_he_cap_elem) +
- sizeof(struct ieee80211_he_mcs_nss_supp) +
- IEEE80211_HE_PPE_THRES_MAX_LEN;
-
- if (sband->band == NL80211_BAND_6GHZ)
- size += 2 + 1 + sizeof(struct ieee80211_he_6ghz_capa);
-
- size += 2 + 1 + sizeof(struct ieee80211_eht_cap_elem) +
- sizeof(struct ieee80211_eht_mcs_nss_supp) +
- IEEE80211_EHT_PPE_THRES_MAX_LEN;
+ size += ieee80211_link_common_elems_size(sdata, iftype, cbss,
+ elems_len);
/* non-inheritance element */
size += 2 + 2 + PRESENT_ELEMS_MAX;
@@ -1849,13 +2197,10 @@ static int ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
/* max common info field in basic multi-link element */
size += sizeof(struct ieee80211_mle_basic_common_info) +
2 + /* capa & op */
+ 2 + /* ext capa & op */
2; /* EML capa */
- /*
- * The capability elements were already considered above;
- * note this over-estimates a bit because there's no
- * STA profile for the assoc link.
- */
+ /* The capability elements were already considered above */
size += (n_links - 1) *
(1 + 1 + /* subelement ID/length */
2 + /* STA control */
@@ -1925,17 +2270,18 @@ static int ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
* for some reason check it and want it to be set, set the bit for all
* pre-EHT connections as we used to do.
*/
- if (link->u.mgd.conn.mode < IEEE80211_CONN_MODE_EHT)
+ if (link->u.mgd.conn.mode < IEEE80211_CONN_MODE_EHT &&
+ !ieee80211_hw_check(&local->hw, STRICT))
capab |= WLAN_CAPABILITY_ESS;
/* add the elements for the assoc (main) link */
link_capab = capab;
- offset = ieee80211_assoc_link_elems(sdata, skb, &link_capab,
- ext_capa,
- assoc_data->ie,
- assoc_data->ie_len,
- assoc_data->assoc_link_id, link,
- present_elems);
+ offset = ieee80211_add_link_elems(sdata, skb, &link_capab,
+ ext_capa,
+ assoc_data->ie,
+ assoc_data->ie_len,
+ assoc_data->assoc_link_id, link,
+ present_elems, assoc_data);
put_unaligned_le16(link_capab, capab_pos);
/* if present, add any custom non-vendor IEs */
@@ -2101,9 +2447,26 @@ static void ieee80211_csa_switch_work(struct wiphy *wiphy,
* update cfg80211 directly.
*/
if (!ieee80211_vif_link_active(&sdata->vif, link->link_id)) {
+ struct link_sta_info *link_sta;
+ struct sta_info *ap_sta;
+
link->conf->chanreq = link->csa.chanreq;
cfg80211_ch_switch_notify(sdata->dev, &link->csa.chanreq.oper,
link->link_id);
+ link->conf->csa_active = false;
+
+ ap_sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
+ if (WARN_ON(!ap_sta))
+ return;
+
+ link_sta = wiphy_dereference(wiphy,
+ ap_sta->link[link->link_id]);
+ if (WARN_ON(!link_sta))
+ return;
+
+ link_sta->pub->bandwidth =
+ _ieee80211_sta_cur_vht_bw(link_sta,
+ &link->csa.chanreq.oper);
return;
}
@@ -2145,6 +2508,16 @@ static void ieee80211_csa_switch_work(struct wiphy *wiphy,
link->u.mgd.csa.waiting_bcn = true;
+ /*
+ * The next beacon really should always be different, so this should
+ * have no effect whatsoever. However, some APs (we observed this in
+ * an Asus AXE11000), the beacon after the CSA might be identical to
+ * the last beacon on the old channel - in this case we'd ignore it.
+ * Resetting the CRC will lead us to handle it better (albeit with a
+ * disconnect, but clearly the AP is broken.)
+ */
+ link->u.mgd.beacon_crc_valid = false;
+
/* apply new TPE restrictions immediately on the new channel */
if (link->u.mgd.csa.ap_chandef.chan->band == NL80211_BAND_6GHZ &&
link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_HE) {
@@ -2159,6 +2532,21 @@ static void ieee80211_csa_switch_work(struct wiphy *wiphy,
}
}
+ /*
+ * It is not necessary to reset these timers if any link does not
+ * have an active CSA and that link still receives the beacons
+ * when other links have active CSA.
+ */
+ for_each_link_data(sdata, link) {
+ if (!link->conf->csa_active)
+ return;
+ }
+
+ /*
+ * Reset the beacon monitor and connection monitor timers when CSA
+ * is active for all links in MLO when channel switch occurs in all
+ * the links.
+ */
ieee80211_sta_reset_beacon_monitor(sdata);
ieee80211_sta_reset_conn_monitor(sdata);
}
@@ -2216,7 +2604,7 @@ void ieee80211_chswitch_done(struct ieee80211_vif *vif, bool success,
return;
}
- wiphy_delayed_work_queue(sdata->local->hw.wiphy,
+ wiphy_hrtimer_work_queue(sdata->local->hw.wiphy,
&link->u.mgd.csa.switch_work, 0);
}
@@ -2235,7 +2623,8 @@ ieee80211_sta_abort_chanswitch(struct ieee80211_link_data *link)
if (!local->ops->abort_channel_switch)
return;
- ieee80211_link_unreserve_chanctx(link);
+ if (rcu_access_pointer(link->conf->chanctx_conf))
+ ieee80211_link_unreserve_chanctx(link);
ieee80211_vif_unblock_queues_csa(sdata);
@@ -2374,7 +2763,8 @@ ieee80211_sta_process_chanswitch(struct ieee80211_link_data *link,
.timestamp = timestamp,
.device_timestamp = device_timestamp,
};
- unsigned long now;
+ u32 csa_time_tu;
+ ktime_t now;
int res;
lockdep_assert_wiphy(local->hw.wiphy);
@@ -2604,10 +2994,9 @@ ieee80211_sta_process_chanswitch(struct ieee80211_link_data *link,
csa_ie.mode);
/* we may have to handle timeout for deactivated link in software */
- now = jiffies;
- link->u.mgd.csa.time = now +
- TU_TO_JIFFIES((max_t(int, csa_ie.count, 1) - 1) *
- link->conf->beacon_int);
+ now = ktime_get_boottime();
+ csa_time_tu = (max_t(int, csa_ie.count, 1) - 1) * link->conf->beacon_int;
+ link->u.mgd.csa.time = now + us_to_ktime(ieee80211_tu_to_usec(csa_time_tu));
if (ieee80211_vif_link_active(&sdata->vif, link->link_id) &&
local->ops->channel_switch) {
@@ -2622,7 +3011,7 @@ ieee80211_sta_process_chanswitch(struct ieee80211_link_data *link,
}
/* channel switch handled in software */
- wiphy_delayed_work_queue(local->hw.wiphy,
+ wiphy_hrtimer_work_queue(local->hw.wiphy,
&link->u.mgd.csa.switch_work,
link->u.mgd.csa.time - now);
return;
@@ -2636,16 +3025,96 @@ ieee80211_sta_process_chanswitch(struct ieee80211_link_data *link,
*/
link->conf->csa_active = true;
link->u.mgd.csa.blocked_tx = csa_ie.mode;
- sdata->csa_blocked_queues =
- csa_ie.mode && !ieee80211_hw_check(&local->hw, HANDLES_QUIET_CSA);
wiphy_work_queue(sdata->local->hw.wiphy,
&ifmgd->csa_connection_drop_work);
}
+struct sta_bss_param_ch_cnt_data {
+ struct ieee80211_sub_if_data *sdata;
+ u8 reporting_link_id;
+ u8 mld_id;
+};
+
+static enum cfg80211_rnr_iter_ret
+ieee80211_sta_bss_param_ch_cnt_iter(void *_data, u8 type,
+ const struct ieee80211_neighbor_ap_info *info,
+ const u8 *tbtt_info, u8 tbtt_info_len)
+{
+ struct sta_bss_param_ch_cnt_data *data = _data;
+ struct ieee80211_sub_if_data *sdata = data->sdata;
+ const struct ieee80211_tbtt_info_ge_11 *ti;
+ u8 bss_param_ch_cnt;
+ int link_id;
+
+ if (type != IEEE80211_TBTT_INFO_TYPE_TBTT)
+ return RNR_ITER_CONTINUE;
+
+ if (tbtt_info_len < sizeof(*ti))
+ return RNR_ITER_CONTINUE;
+
+ ti = (const void *)tbtt_info;
+
+ if (ti->mld_params.mld_id != data->mld_id)
+ return RNR_ITER_CONTINUE;
+
+ link_id = le16_get_bits(ti->mld_params.params,
+ IEEE80211_RNR_MLD_PARAMS_LINK_ID);
+ bss_param_ch_cnt =
+ le16_get_bits(ti->mld_params.params,
+ IEEE80211_RNR_MLD_PARAMS_BSS_CHANGE_COUNT);
+
+ if (bss_param_ch_cnt != 255 &&
+ link_id < ARRAY_SIZE(sdata->link)) {
+ struct ieee80211_link_data *link =
+ sdata_dereference(sdata->link[link_id], sdata);
+
+ if (link && link->conf->bss_param_ch_cnt != bss_param_ch_cnt) {
+ link->conf->bss_param_ch_cnt = bss_param_ch_cnt;
+ link->conf->bss_param_ch_cnt_link_id =
+ data->reporting_link_id;
+ }
+ }
+
+ return RNR_ITER_CONTINUE;
+}
+
+static void
+ieee80211_mgd_update_bss_param_ch_cnt(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_bss_conf *bss_conf,
+ struct ieee802_11_elems *elems)
+{
+ struct sta_bss_param_ch_cnt_data data = {
+ .reporting_link_id = bss_conf->link_id,
+ .sdata = sdata,
+ };
+ int bss_param_ch_cnt;
+
+ if (!elems->ml_basic)
+ return;
+
+ data.mld_id = ieee80211_mle_get_mld_id((const void *)elems->ml_basic);
+
+ cfg80211_iter_rnr(elems->ie_start, elems->total_len,
+ ieee80211_sta_bss_param_ch_cnt_iter, &data);
+
+ bss_param_ch_cnt =
+ ieee80211_mle_get_bss_param_ch_cnt((const void *)elems->ml_basic);
+
+ /*
+ * Update bss_param_ch_cnt_link_id even if bss_param_ch_cnt
+ * didn't change to indicate that we got a beacon on our own
+ * link.
+ */
+ if (bss_param_ch_cnt >= 0 && bss_param_ch_cnt != 255) {
+ bss_conf->bss_param_ch_cnt = bss_param_ch_cnt;
+ bss_conf->bss_param_ch_cnt_link_id =
+ bss_conf->link_id;
+ }
+}
+
static bool
-ieee80211_find_80211h_pwr_constr(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_channel *channel,
+ieee80211_find_80211h_pwr_constr(struct ieee80211_channel *channel,
const u8 *country_ie, u8 country_ie_len,
const u8 *pwr_constr_elem,
int *chan_pwr, int *pwr_reduction)
@@ -2715,8 +3184,7 @@ ieee80211_find_80211h_pwr_constr(struct ieee80211_sub_if_data *sdata,
return have_chan_pwr;
}
-static void ieee80211_find_cisco_dtpc(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_channel *channel,
+static void ieee80211_find_cisco_dtpc(struct ieee80211_channel *channel,
const u8 *cisco_dtpc_ie,
int *pwr_level)
{
@@ -2750,7 +3218,7 @@ static u64 ieee80211_handle_pwr_constr(struct ieee80211_link_data *link,
(capab & cpu_to_le16(WLAN_CAPABILITY_SPECTRUM_MGMT) ||
capab & cpu_to_le16(WLAN_CAPABILITY_RADIO_MEASURE))) {
has_80211h_pwr = ieee80211_find_80211h_pwr_constr(
- sdata, channel, country_ie, country_ie_len,
+ channel, country_ie, country_ie_len,
pwr_constr_ie, &chan_pwr, &pwr_reduction_80211h);
pwr_level_80211h =
max_t(int, 0, chan_pwr - pwr_reduction_80211h);
@@ -2758,7 +3226,7 @@ static u64 ieee80211_handle_pwr_constr(struct ieee80211_link_data *link,
if (cisco_dtpc_ie) {
ieee80211_find_cisco_dtpc(
- sdata, channel, cisco_dtpc_ie, &pwr_level_cisco);
+ channel, cisco_dtpc_ie, &pwr_level_cisco);
has_cisco_pwr = true;
}
@@ -2791,7 +3259,7 @@ static u64 ieee80211_handle_pwr_constr(struct ieee80211_link_data *link,
}
link->ap_power_level = new_ap_level;
- if (__ieee80211_recalc_txpower(sdata))
+ if (__ieee80211_recalc_txpower(link))
return BSS_CHANGED_TXPOWER;
return 0;
}
@@ -2822,7 +3290,7 @@ static void ieee80211_enable_ps(struct ieee80211_local *local,
return;
conf->flags |= IEEE80211_CONF_PS;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+ ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS);
}
}
@@ -2834,8 +3302,8 @@ static void ieee80211_change_ps(struct ieee80211_local *local)
ieee80211_enable_ps(local, local->ps_sdata);
} else if (conf->flags & IEEE80211_CONF_PS) {
conf->flags &= ~IEEE80211_CONF_PS;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
- del_timer_sync(&local->dynamic_ps_timer);
+ ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS);
+ timer_delete_sync(&local->dynamic_ps_timer);
wiphy_work_cancel(local->hw.wiphy,
&local->dynamic_ps_enable_work);
}
@@ -2943,7 +3411,7 @@ void ieee80211_dynamic_ps_disable_work(struct wiphy *wiphy,
if (local->hw.conf.flags & IEEE80211_CONF_PS) {
local->hw.conf.flags &= ~IEEE80211_CONF_PS;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+ ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS);
}
ieee80211_wake_queues_by_reason(&local->hw,
@@ -3018,31 +3486,33 @@ void ieee80211_dynamic_ps_enable_work(struct wiphy *wiphy,
(ifmgd->flags & IEEE80211_STA_NULLFUNC_ACKED)) {
ifmgd->flags &= ~IEEE80211_STA_NULLFUNC_ACKED;
local->hw.conf.flags |= IEEE80211_CONF_PS;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+ ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS);
}
}
void ieee80211_dynamic_ps_timer(struct timer_list *t)
{
- struct ieee80211_local *local = from_timer(local, t, dynamic_ps_timer);
+ struct ieee80211_local *local = timer_container_of(local, t,
+ dynamic_ps_timer);
wiphy_work_queue(local->hw.wiphy, &local->dynamic_ps_enable_work);
}
void ieee80211_dfs_cac_timer_work(struct wiphy *wiphy, struct wiphy_work *work)
{
- struct ieee80211_sub_if_data *sdata =
- container_of(work, struct ieee80211_sub_if_data,
+ struct ieee80211_link_data *link =
+ container_of(work, struct ieee80211_link_data,
dfs_cac_timer_work.work);
- struct cfg80211_chan_def chandef = sdata->vif.bss_conf.chanreq.oper;
+ struct cfg80211_chan_def chandef = link->conf->chanreq.oper;
+ struct ieee80211_sub_if_data *sdata = link->sdata;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
- if (sdata->wdev.cac_started) {
- ieee80211_link_release_channel(&sdata->deflink);
+ if (sdata->wdev.links[link->link_id].cac_started) {
+ ieee80211_link_release_channel(link);
cfg80211_cac_event(sdata->dev, &chandef,
NL80211_RADAR_CAC_FINISHED,
- GFP_KERNEL);
+ GFP_KERNEL, link->link_id);
}
}
@@ -3169,10 +3639,10 @@ void ieee80211_mgd_set_link_qos_params(struct ieee80211_link_data *link)
/* MLME */
static bool
-ieee80211_sta_wmm_params(struct ieee80211_local *local,
- struct ieee80211_link_data *link,
- const u8 *wmm_param, size_t wmm_param_len,
- const struct ieee80211_mu_edca_param_set *mu_edca)
+_ieee80211_sta_wmm_params(struct ieee80211_local *local,
+ struct ieee80211_link_data *link,
+ const u8 *wmm_param, size_t wmm_param_len,
+ const struct ieee80211_mu_edca_param_set *mu_edca)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_tx_queue_params params[IEEE80211_NUM_ACS];
@@ -3301,6 +3771,19 @@ ieee80211_sta_wmm_params(struct ieee80211_local *local,
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
link->tx_conf[ac] = params[ac];
+ return true;
+}
+
+static bool
+ieee80211_sta_wmm_params(struct ieee80211_local *local,
+ struct ieee80211_link_data *link,
+ const u8 *wmm_param, size_t wmm_param_len,
+ const struct ieee80211_mu_edca_param_set *mu_edca)
+{
+ if (!_ieee80211_sta_wmm_params(local, link, wmm_param, wmm_param_len,
+ mu_edca))
+ return false;
+
ieee80211_mgd_set_link_qos_params(link);
/* enable WMM or activate new settings */
@@ -3511,12 +3994,45 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
netif_carrier_on(sdata->dev);
}
+static void ieee80211_ml_reconf_reset(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_mgd_assoc_data *add_links_data =
+ sdata->u.mgd.reconf.add_links_data;
+
+ if (!ieee80211_vif_is_mld(&sdata->vif) ||
+ !(sdata->u.mgd.reconf.added_links |
+ sdata->u.mgd.reconf.removed_links))
+ return;
+
+ wiphy_delayed_work_cancel(sdata->local->hw.wiphy,
+ &sdata->u.mgd.reconf.wk);
+ sdata->u.mgd.reconf.added_links = 0;
+ sdata->u.mgd.reconf.removed_links = 0;
+ sdata->u.mgd.reconf.dialog_token = 0;
+
+ if (add_links_data) {
+ struct cfg80211_mlo_reconf_done_data done_data = {};
+ u8 link_id;
+
+ for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS;
+ link_id++)
+ done_data.links[link_id].bss =
+ add_links_data->link[link_id].bss;
+
+ cfg80211_mlo_reconf_add_done(sdata->dev, &done_data);
+
+ kfree(sdata->u.mgd.reconf.add_links_data);
+ sdata->u.mgd.reconf.add_links_data = NULL;
+ }
+}
+
static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
u16 stype, u16 reason, bool tx,
u8 *frame_buf)
{
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_local *local = sdata->local;
+ struct sta_info *ap_sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
unsigned int link_id;
u64 changed = 0;
struct ieee80211_prep_tx_info info = {
@@ -3527,6 +4043,12 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
lockdep_assert_wiphy(local->hw.wiphy);
+ if (frame_buf)
+ memset(frame_buf, 0, IEEE80211_DEAUTH_FRAME_LEN);
+
+ if (WARN_ON(!ap_sta))
+ return;
+
if (WARN_ON_ONCE(tx && !frame_buf))
return;
@@ -3537,8 +4059,34 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
ifmgd->associated = false;
+ if (tx) {
+ bool tx_link_found = false;
+
+ for (link_id = 0;
+ link_id < ARRAY_SIZE(sdata->link);
+ link_id++) {
+ struct ieee80211_link_data *link;
+
+ if (!ieee80211_vif_link_active(&sdata->vif, link_id))
+ continue;
+
+ link = sdata_dereference(sdata->link[link_id], sdata);
+ if (WARN_ON_ONCE(!link))
+ continue;
+
+ if (link->u.mgd.csa.blocked_tx)
+ continue;
+
+ tx_link_found = true;
+ break;
+ }
+
+ tx = tx_link_found;
+ }
+
/* other links will be destroyed */
sdata->deflink.conf->bss = NULL;
+ sdata->deflink.conf->epcs_support = false;
sdata->deflink.smps_mode = IEEE80211_SMPS_OFF;
netif_carrier_off(sdata->dev);
@@ -3550,7 +4098,7 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
*/
if (local->hw.conf.flags & IEEE80211_CONF_PS) {
local->hw.conf.flags &= ~IEEE80211_CONF_PS;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+ ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS);
}
local->ps_sdata = NULL;
@@ -3566,23 +4114,24 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
* insist sending these frames which can take time and delay
* the disconnection and possible the roaming.
*/
- if (tx)
- ieee80211_flush_queues(local, sdata, true);
+ ieee80211_flush_queues(local, sdata, true);
- /* deauthenticate/disassociate now */
- if (tx || frame_buf) {
+ if (tx) {
drv_mgd_prepare_tx(sdata->local, sdata, &info);
ieee80211_send_deauth_disassoc(sdata, sdata->vif.cfg.ap_addr,
sdata->vif.cfg.ap_addr, stype,
- reason, tx, frame_buf);
- }
+ reason, true, frame_buf);
- /* flush out frame - make sure the deauth was actually sent */
- if (tx)
+ /* flush out frame - make sure the deauth was actually sent */
ieee80211_flush_queues(local, sdata, false);
- drv_mgd_complete_tx(sdata->local, sdata, &info);
+ drv_mgd_complete_tx(sdata->local, sdata, &info);
+ } else if (frame_buf) {
+ ieee80211_send_deauth_disassoc(sdata, sdata->vif.cfg.ap_addr,
+ sdata->vif.cfg.ap_addr, stype,
+ reason, false, frame_buf);
+ }
/* clear AP addr only after building the needed mgmt frames */
eth_zero_addr(sdata->deflink.u.mgd.bssid);
@@ -3590,8 +4139,16 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
sdata->vif.cfg.ssid_len = 0;
- /* remove AP and TDLS peers */
- sta_info_flush(sdata, -1);
+ /* Remove TDLS peers */
+ __sta_info_flush(sdata, false, -1, ap_sta);
+
+ if (sdata->vif.driver_flags & IEEE80211_VIF_REMOVE_AP_AFTER_DISASSOC) {
+ /* Only move the AP state */
+ sta_info_move_state(ap_sta, IEEE80211_STA_NONE);
+ } else {
+ /* Remove AP peer */
+ sta_info_flush(sdata, -1);
+ }
/* finally reset all BSS / config parameters */
if (!ieee80211_vif_is_mld(&sdata->vif))
@@ -3625,7 +4182,7 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
sdata->deflink.ap_power_level = IEEE80211_UNSET_POWER_LEVEL;
- del_timer_sync(&local->dynamic_ps_timer);
+ timer_delete_sync(&local->dynamic_ps_timer);
wiphy_work_cancel(local->hw.wiphy, &local->dynamic_ps_enable_work);
/* Disable ARP filtering */
@@ -3642,12 +4199,20 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
ieee80211_vif_cfg_change_notify(sdata, changed);
}
+ if (sdata->vif.driver_flags & IEEE80211_VIF_REMOVE_AP_AFTER_DISASSOC) {
+ /*
+ * After notifying the driver about the disassoc,
+ * remove the ap sta.
+ */
+ sta_info_flush(sdata, -1);
+ }
+
/* disassociated - set to defaults now */
ieee80211_set_wmm_default(&sdata->deflink, false, false);
- del_timer_sync(&sdata->u.mgd.conn_mon_timer);
- del_timer_sync(&sdata->u.mgd.bcn_mon_timer);
- del_timer_sync(&sdata->u.mgd.timer);
+ timer_delete_sync(&sdata->u.mgd.conn_mon_timer);
+ timer_delete_sync(&sdata->u.mgd.bcn_mon_timer);
+ timer_delete_sync(&sdata->u.mgd.timer);
sdata->vif.bss_conf.dtim_period = 0;
sdata->vif.bss_conf.beacon_rate = NULL;
@@ -3687,22 +4252,34 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
memset(&sdata->u.mgd.ttlm_info, 0,
sizeof(sdata->u.mgd.ttlm_info));
- wiphy_delayed_work_cancel(sdata->local->hw.wiphy, &ifmgd->ttlm_work);
+ wiphy_hrtimer_work_cancel(sdata->local->hw.wiphy, &ifmgd->ttlm_work);
memset(&sdata->vif.neg_ttlm, 0, sizeof(sdata->vif.neg_ttlm));
wiphy_delayed_work_cancel(sdata->local->hw.wiphy,
&ifmgd->neg_ttlm_timeout_work);
sdata->u.mgd.removed_links = 0;
- wiphy_delayed_work_cancel(sdata->local->hw.wiphy,
+ wiphy_hrtimer_work_cancel(sdata->local->hw.wiphy,
&sdata->u.mgd.ml_reconf_work);
wiphy_work_cancel(sdata->local->hw.wiphy,
&ifmgd->teardown_ttlm_work);
+ /* if disconnection happens in the middle of the ML reconfiguration
+ * flow, cfg80211 must called to release the BSS references obtained
+ * when the flow started.
+ */
+ ieee80211_ml_reconf_reset(sdata);
+
ieee80211_vif_set_links(sdata, 0, 0);
ifmgd->mcast_seq_last = IEEE80211_SN_MODULO;
+
+ ifmgd->epcs.enabled = false;
+ ifmgd->epcs.dialog_token = 0;
+
+ memset(ifmgd->userspace_selectors, 0,
+ sizeof(ifmgd->userspace_selectors));
}
static void ieee80211_reset_ap_probe(struct ieee80211_sub_if_data *sdata)
@@ -3817,9 +4394,6 @@ static void ieee80211_mgd_probe_ap_send(struct ieee80211_sub_if_data *sdata)
lockdep_assert_wiphy(sdata->local->hw.wiphy);
- if (WARN_ON(ieee80211_vif_is_mld(&sdata->vif)))
- return;
-
/*
* Try sending broadcast probe requests for the last three
* probe requests after the first ones failed since some
@@ -3865,9 +4439,6 @@ static void ieee80211_mgd_probe_ap(struct ieee80211_sub_if_data *sdata,
lockdep_assert_wiphy(sdata->local->hw.wiphy);
- if (WARN_ON_ONCE(ieee80211_vif_is_mld(&sdata->vif)))
- return;
-
if (!ieee80211_sdata_running(sdata))
return;
@@ -3983,33 +4554,12 @@ 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];
- bool tx = false;
lockdep_assert_wiphy(local->hw.wiphy);
if (!ifmgd->associated)
return;
- /* only transmit if we have a link that makes that worthwhile */
- for (unsigned int link_id = 0;
- link_id < ARRAY_SIZE(sdata->link);
- link_id++) {
- struct ieee80211_link_data *link;
-
- if (!ieee80211_vif_link_active(&sdata->vif, link_id))
- continue;
-
- link = sdata_dereference(sdata->link[link_id], sdata);
- if (WARN_ON_ONCE(!link))
- continue;
-
- if (link->u.mgd.csa.blocked_tx)
- continue;
-
- tx = true;
- break;
- }
-
if (!ifmgd->driver_disconnect) {
unsigned int link_id;
@@ -4026,7 +4576,7 @@ static void __ieee80211_disconnect(struct ieee80211_sub_if_data *sdata)
struct ieee80211_link_data *link;
link = sdata_dereference(sdata->link[link_id], sdata);
- if (!link)
+ if (!link || !link->conf->bss)
continue;
cfg80211_unlink_bss(local->hw.wiphy, link->conf->bss);
link->conf->bss = NULL;
@@ -4037,14 +4587,14 @@ static void __ieee80211_disconnect(struct ieee80211_sub_if_data *sdata)
ifmgd->driver_disconnect ?
WLAN_REASON_DEAUTH_LEAVING :
WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY,
- tx, frame_buf);
+ true, frame_buf);
/* the other links will be destroyed */
sdata->vif.bss_conf.csa_active = false;
sdata->deflink.u.mgd.csa.waiting_bcn = false;
sdata->deflink.u.mgd.csa.blocked_tx = false;
ieee80211_vif_unblock_queues_csa(sdata);
- ieee80211_report_disconnect(sdata, frame_buf, sizeof(frame_buf), tx,
+ ieee80211_report_disconnect(sdata, frame_buf, sizeof(frame_buf), true,
WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY,
ifmgd->reconnect);
ifmgd->reconnect = false;
@@ -4100,8 +4650,13 @@ EXPORT_SYMBOL(ieee80211_beacon_loss);
void ieee80211_connection_loss(struct ieee80211_vif *vif)
{
- struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
- struct ieee80211_hw *hw = &sdata->local->hw;
+ struct ieee80211_sub_if_data *sdata;
+ struct ieee80211_hw *hw;
+
+ KUNIT_STATIC_STUB_REDIRECT(ieee80211_connection_loss, vif);
+
+ sdata = vif_to_sdata(vif);
+ hw = &sdata->local->hw;
trace_api_connection_loss(sdata);
@@ -4133,13 +4688,15 @@ static void ieee80211_destroy_auth_data(struct ieee80211_sub_if_data *sdata,
lockdep_assert_wiphy(sdata->local->hw.wiphy);
+ sdata->u.mgd.auth_data = NULL;
+
if (!assoc) {
/*
* we are not authenticated yet, the only timer that could be
* running is the timeout for the authentication response which
* which is not relevant anymore.
*/
- del_timer_sync(&sdata->u.mgd.timer);
+ timer_delete_sync(&sdata->u.mgd.timer);
sta_info_destroy_addr(sdata, auth_data->ap_addr);
/* other links are destroyed */
@@ -4154,7 +4711,6 @@ static void ieee80211_destroy_auth_data(struct ieee80211_sub_if_data *sdata,
cfg80211_put_bss(sdata->local->hw.wiphy, auth_data->bss);
kfree(auth_data);
- sdata->u.mgd.auth_data = NULL;
}
enum assoc_status {
@@ -4171,13 +4727,15 @@ static void ieee80211_destroy_assoc_data(struct ieee80211_sub_if_data *sdata,
lockdep_assert_wiphy(sdata->local->hw.wiphy);
+ sdata->u.mgd.assoc_data = NULL;
+
if (status != ASSOC_SUCCESS) {
/*
* we are not associated yet, the only timer that could be
* running is the timeout for the association response which
* which is not relevant anymore.
*/
- del_timer_sync(&sdata->u.mgd.timer);
+ timer_delete_sync(&sdata->u.mgd.timer);
sta_info_destroy_addr(sdata, assoc_data->ap_addr);
eth_zero_addr(sdata->deflink.u.mgd.bssid);
@@ -4209,7 +4767,6 @@ static void ieee80211_destroy_assoc_data(struct ieee80211_sub_if_data *sdata,
}
kfree(assoc_data);
- sdata->u.mgd.assoc_data = NULL;
}
static void ieee80211_auth_challenge(struct ieee80211_sub_if_data *sdata,
@@ -4283,6 +4840,7 @@ static void ieee80211_rx_mgmt_auth(struct ieee80211_sub_if_data *sdata,
struct ieee80211_prep_tx_info info = {
.subtype = IEEE80211_STYPE_AUTH,
};
+ bool sae_need_confirm = false;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
@@ -4299,6 +4857,8 @@ static void ieee80211_rx_mgmt_auth(struct ieee80211_sub_if_data *sdata,
auth_transaction = le16_to_cpu(mgmt->u.auth.auth_transaction);
status_code = le16_to_cpu(mgmt->u.auth.status_code);
+ info.link_id = ifmgd->auth_data->link_id;
+
if (auth_alg != ifmgd->auth_data->algorithm ||
(auth_alg != WLAN_AUTH_SAE &&
auth_transaction != ifmgd->auth_data->expected_transaction) ||
@@ -4326,6 +4886,8 @@ static void ieee80211_rx_mgmt_auth(struct ieee80211_sub_if_data *sdata,
jiffies + IEEE80211_AUTH_WAIT_SAE_RETRY;
ifmgd->auth_data->timeout_started = true;
run_again(sdata, ifmgd->auth_data->timeout);
+ if (auth_transaction == 1)
+ sae_need_confirm = true;
goto notify_driver;
}
@@ -4369,6 +4931,9 @@ static void ieee80211_rx_mgmt_auth(struct ieee80211_sub_if_data *sdata,
if (!ieee80211_mark_sta_auth(sdata))
return; /* ignore frame -- wait for timeout */
} else if (ifmgd->auth_data->algorithm == WLAN_AUTH_SAE &&
+ auth_transaction == 1) {
+ sae_need_confirm = true;
+ } else if (ifmgd->auth_data->algorithm == WLAN_AUTH_SAE &&
auth_transaction == 2) {
sdata_info(sdata, "SAE peer confirmed\n");
ifmgd->auth_data->peer_confirmed = true;
@@ -4376,7 +4941,8 @@ static void ieee80211_rx_mgmt_auth(struct ieee80211_sub_if_data *sdata,
cfg80211_rx_mlme_mgmt(sdata->dev, (u8 *)mgmt, len);
notify_driver:
- drv_mgd_complete_tx(sdata->local, sdata, &info);
+ if (!sae_need_confirm)
+ drv_mgd_complete_tx(sdata->local, sdata, &info);
}
#define case_WLAN(type) \
@@ -4512,57 +5078,6 @@ static void ieee80211_rx_mgmt_disassoc(struct ieee80211_sub_if_data *sdata,
false);
}
-static void ieee80211_get_rates(struct ieee80211_supported_band *sband,
- u8 *supp_rates, unsigned int supp_rates_len,
- u32 *rates, u32 *basic_rates,
- bool *have_higher_than_11mbit,
- int *min_rate, int *min_rate_index)
-{
- int i, j;
-
- for (i = 0; i < supp_rates_len; i++) {
- int rate = supp_rates[i] & 0x7f;
- bool is_basic = !!(supp_rates[i] & 0x80);
-
- if ((rate * 5) > 110)
- *have_higher_than_11mbit = true;
-
- /*
- * Skip HT, VHT, HE, EHT and SAE H2E only BSS membership
- * selectors since they're not rates.
- *
- * Note: Even though the membership selector and the basic
- * rate flag share the same bit, they are not exactly
- * the same.
- */
- if (supp_rates[i] == (0x80 | BSS_MEMBERSHIP_SELECTOR_HT_PHY) ||
- supp_rates[i] == (0x80 | BSS_MEMBERSHIP_SELECTOR_VHT_PHY) ||
- supp_rates[i] == (0x80 | BSS_MEMBERSHIP_SELECTOR_HE_PHY) ||
- supp_rates[i] == (0x80 | BSS_MEMBERSHIP_SELECTOR_EHT_PHY) ||
- supp_rates[i] == (0x80 | BSS_MEMBERSHIP_SELECTOR_SAE_H2E))
- continue;
-
- for (j = 0; j < sband->n_bitrates; j++) {
- struct ieee80211_rate *br;
- int brate;
-
- br = &sband->bitrates[j];
-
- brate = DIV_ROUND_UP(br->bitrate, 5);
- if (brate == rate) {
- *rates |= BIT(j);
- if (is_basic)
- *basic_rates |= BIT(j);
- if ((rate * 5) < *min_rate) {
- *min_rate = rate * 5;
- *min_rate_index = j;
- }
- break;
- }
- }
- }
-}
-
static bool ieee80211_twt_req_supported(struct ieee80211_sub_if_data *sdata,
struct ieee80211_supported_band *sband,
const struct link_sta_info *link_sta,
@@ -4615,6 +5130,84 @@ static bool ieee80211_twt_bcast_support(struct ieee80211_sub_if_data *sdata,
IEEE80211_HE_MAC_CAP2_BCAST_TWT);
}
+static void ieee80211_epcs_changed(struct ieee80211_sub_if_data *sdata,
+ bool enabled)
+{
+ /* in any case this is called, dialog token should be reset */
+ sdata->u.mgd.epcs.dialog_token = 0;
+
+ if (sdata->u.mgd.epcs.enabled == enabled)
+ return;
+
+ sdata->u.mgd.epcs.enabled = enabled;
+ cfg80211_epcs_changed(sdata->dev, enabled);
+}
+
+static void ieee80211_epcs_teardown(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_local *local = sdata->local;
+ u8 link_id;
+
+ if (!sdata->u.mgd.epcs.enabled)
+ return;
+
+ lockdep_assert_wiphy(local->hw.wiphy);
+
+ for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
+ struct ieee802_11_elems *elems;
+ struct ieee80211_link_data *link;
+ const struct cfg80211_bss_ies *ies;
+ bool ret;
+
+ rcu_read_lock();
+
+ link = sdata_dereference(sdata->link[link_id], sdata);
+ if (!link || !link->conf || !link->conf->bss) {
+ rcu_read_unlock();
+ continue;
+ }
+
+ if (link->u.mgd.disable_wmm_tracking) {
+ rcu_read_unlock();
+ ieee80211_set_wmm_default(link, false, false);
+ continue;
+ }
+
+ ies = rcu_dereference(link->conf->bss->beacon_ies);
+ if (!ies) {
+ rcu_read_unlock();
+ ieee80211_set_wmm_default(link, false, false);
+ continue;
+ }
+
+ elems = ieee802_11_parse_elems(ies->data, ies->len,
+ IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_BEACON,
+ NULL);
+ if (!elems) {
+ rcu_read_unlock();
+ ieee80211_set_wmm_default(link, false, false);
+ continue;
+ }
+
+ ret = _ieee80211_sta_wmm_params(local, link,
+ elems->wmm_param,
+ elems->wmm_param_len,
+ elems->mu_edca_param_set);
+
+ kfree(elems);
+ rcu_read_unlock();
+
+ if (!ret) {
+ ieee80211_set_wmm_default(link, false, false);
+ continue;
+ }
+
+ ieee80211_mgd_set_link_qos_params(link);
+ ieee80211_link_info_change_notify(sdata, link, BSS_CHANGED_QOS);
+ }
+}
+
static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
struct link_sta_info *link_sta,
struct cfg80211_bss *cbss,
@@ -4624,7 +5217,8 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
u64 *changed)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
- struct ieee80211_mgd_assoc_data *assoc_data = sdata->u.mgd.assoc_data;
+ struct ieee80211_mgd_assoc_data *assoc_data =
+ sdata->u.mgd.assoc_data ?: sdata->u.mgd.reconf.add_links_data;
struct ieee80211_bss_conf *bss_conf = link->conf;
struct ieee80211_local *local = sdata->local;
unsigned int link_id = link->link_id;
@@ -4634,6 +5228,7 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
.len = elem_len,
.link_id = link_id == assoc_data->assoc_link_id ? -1 : link_id,
.from_ap = true,
+ .type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE,
};
bool is_5ghz = cbss->channel->band == NL80211_BAND_5GHZ;
bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ;
@@ -4666,7 +5261,8 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
ret = false;
goto out;
}
- link->u.mgd.bss_param_ch_cnt = bss_param_ch_cnt;
+ bss_conf->bss_param_ch_cnt = bss_param_ch_cnt;
+ bss_conf->bss_param_ch_cnt_link_id = link_id;
}
} else if (elems->parse_error & IEEE80211_PARSE_ERR_DUP_NEST_ML_BASIC ||
!elems->prof ||
@@ -4676,6 +5272,7 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
} else {
const u8 *ptr = elems->prof->variable +
elems->prof->sta_info_len - 1;
+ int bss_param_ch_cnt;
/*
* During parsing, we validated that these fields exist,
@@ -4683,8 +5280,10 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
*/
capab_info = get_unaligned_le16(ptr);
assoc_data->link[link_id].status = get_unaligned_le16(ptr + 2);
- link->u.mgd.bss_param_ch_cnt =
+ bss_param_ch_cnt =
ieee80211_mle_basic_sta_prof_bss_param_ch_cnt(elems->prof);
+ bss_conf->bss_param_ch_cnt = bss_param_ch_cnt;
+ bss_conf->bss_param_ch_cnt_link_id = link_id;
if (assoc_data->link[link_id].status != WLAN_STATUS_SUCCESS) {
link_info(link, "association response status code=%u\n",
@@ -4711,11 +5310,11 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
* 2G/3G/4G wifi routers, reported models include the "Onda PN51T",
* "Vodafone PocketWiFi 2", "ZTE MF60" and a similar T-Mobile device.
*/
- if (!is_6ghz &&
+ if (!ieee80211_hw_check(&local->hw, STRICT) && !is_6ghz &&
((assoc_data->wmm && !elems->wmm_param) ||
(link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_HT &&
(!elems->ht_cap_elem || !elems->ht_operation)) ||
- (link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_VHT &&
+ (is_5ghz && link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_VHT &&
(!elems->vht_cap_elem || !elems->vht_operation)))) {
const struct cfg80211_bss_ies *ies;
struct ieee802_11_elems *bss_elems;
@@ -4734,6 +5333,7 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
parse_params.start = bss_ies->data;
parse_params.len = bss_ies->len;
parse_params.bss = cbss;
+ parse_params.link_id = -1;
bss_elems = ieee802_11_parse_elems_full(&parse_params);
if (!bss_elems) {
ret = false;
@@ -4763,19 +5363,22 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
sdata_info(sdata,
"AP bug: HT operation missing from AssocResp\n");
}
- if (!elems->vht_cap_elem && bss_elems->vht_cap_elem &&
- link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_VHT) {
- elems->vht_cap_elem = bss_elems->vht_cap_elem;
- sdata_info(sdata,
- "AP bug: VHT capa missing from AssocResp\n");
- }
- if (!elems->vht_operation && bss_elems->vht_operation &&
- link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_VHT) {
- elems->vht_operation = bss_elems->vht_operation;
- sdata_info(sdata,
- "AP bug: VHT operation missing from AssocResp\n");
- }
+ if (is_5ghz) {
+ if (!elems->vht_cap_elem && bss_elems->vht_cap_elem &&
+ link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_VHT) {
+ elems->vht_cap_elem = bss_elems->vht_cap_elem;
+ sdata_info(sdata,
+ "AP bug: VHT capa missing from AssocResp\n");
+ }
+
+ if (!elems->vht_operation && bss_elems->vht_operation &&
+ link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_VHT) {
+ elems->vht_operation = bss_elems->vht_operation;
+ sdata_info(sdata,
+ "AP bug: VHT operation missing from AssocResp\n");
+ }
+ }
kfree(bss_elems);
}
@@ -4804,7 +5407,9 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
/* check/update if AP changed anything in assoc response vs. scan */
if (ieee80211_config_bw(link, elems,
link_id == assoc_data->assoc_link_id,
- changed)) {
+ changed,
+ le16_to_cpu(mgmt->frame_control) &
+ IEEE80211_FCTL_STYPE)) {
ret = false;
goto out;
}
@@ -4843,6 +5448,15 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
bss_vht_cap = (const void *)elem->data;
}
+ if (ieee80211_hw_check(&local->hw, STRICT) &&
+ (!bss_vht_cap || memcmp(bss_vht_cap, elems->vht_cap_elem,
+ sizeof(*bss_vht_cap)))) {
+ rcu_read_unlock();
+ ret = false;
+ link_info(link, "VHT capabilities mismatch\n");
+ goto out;
+ }
+
ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
elems->vht_cap_elem,
bss_vht_cap, link_sta);
@@ -4880,16 +5494,35 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
link_sta);
bss_conf->eht_support = link_sta->pub->eht_cap.has_eht;
+ bss_conf->epcs_support = bss_conf->eht_support &&
+ !!(elems->eht_cap->fixed.mac_cap_info[0] &
+ IEEE80211_EHT_MAC_CAP0_EPCS_PRIO_ACCESS);
+
+ /* EPCS might be already enabled but a new added link
+ * does not support EPCS. This should not really happen
+ * in practice.
+ */
+ if (sdata->u.mgd.epcs.enabled &&
+ !bss_conf->epcs_support)
+ ieee80211_epcs_teardown(sdata);
} else {
bss_conf->eht_support = false;
+ bss_conf->epcs_support = false;
}
} else {
bss_conf->he_support = false;
bss_conf->twt_requester = false;
bss_conf->twt_protected = false;
bss_conf->eht_support = false;
+ bss_conf->epcs_support = false;
}
+ if (elems->s1g_oper &&
+ link->u.mgd.conn.mode == IEEE80211_CONN_MODE_S1G &&
+ elems->s1g_capab)
+ ieee80211_s1g_cap_to_sta_s1g_cap(sdata, elems->s1g_capab,
+ link_sta);
+
bss_conf->twt_broadcast =
ieee80211_twt_bcast_support(sdata, bss_conf, sband, link_sta);
@@ -5027,7 +5660,9 @@ static int ieee80211_mgd_setup_link_sta(struct ieee80211_link_data *link,
sband = local->hw.wiphy->bands[cbss->channel->band];
ieee80211_get_rates(sband, bss->supp_rates, bss->supp_rates_len,
- &rates, &basic_rates, &have_higher_than_11mbit,
+ NULL, 0,
+ &rates, &basic_rates, NULL,
+ &have_higher_than_11mbit,
&min_rate, &min_rate_index);
/*
@@ -5116,7 +5751,7 @@ static u8 ieee80211_max_rx_chains(struct ieee80211_link_data *link,
he_cap_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY,
ies->data, ies->len);
- if (!he_cap_elem || he_cap_elem->datalen < sizeof(*he_cap))
+ if (!he_cap_elem || he_cap_elem->datalen < sizeof(*he_cap) + 1)
return chains;
/* skip one byte ext_tag_id */
@@ -5399,28 +6034,12 @@ ieee80211_determine_our_sta_mode_assoc(struct ieee80211_sub_if_data *sdata,
conn->bw_limit, tmp.bw_limit);
}
-static enum ieee80211_ap_reg_power
-ieee80211_ap_power_type(u8 control)
-{
- switch (u8_get_bits(control, IEEE80211_HE_6GHZ_OPER_CTRL_REG_INFO)) {
- case IEEE80211_6GHZ_CTRL_REG_LPI_AP:
- case IEEE80211_6GHZ_CTRL_REG_INDOOR_LPI_AP:
- return IEEE80211_REG_LPI_AP;
- case IEEE80211_6GHZ_CTRL_REG_SP_AP:
- case IEEE80211_6GHZ_CTRL_REG_INDOOR_SP_AP:
- return IEEE80211_REG_SP_AP;
- case IEEE80211_6GHZ_CTRL_REG_VLP_AP:
- return IEEE80211_REG_VLP_AP;
- default:
- return IEEE80211_REG_UNSET_AP;
- }
-}
-
static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
int link_id,
struct cfg80211_bss *cbss, bool mlo,
- struct ieee80211_conn_settings *conn)
+ struct ieee80211_conn_settings *conn,
+ unsigned long *userspace_selectors)
{
struct ieee80211_local *local = sdata->local;
bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ;
@@ -5433,7 +6052,8 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
rcu_read_lock();
elems = ieee80211_determine_chan_mode(sdata, conn, cbss, link_id,
- &chanreq, &ap_chandef);
+ &chanreq, &ap_chandef,
+ userspace_selectors);
if (IS_ERR(elems)) {
rcu_read_unlock();
@@ -5456,7 +6076,8 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
he_6ghz_oper = ieee80211_he_6ghz_oper(elems->he_operation);
if (he_6ghz_oper)
link->conf->power_type =
- ieee80211_ap_power_type(he_6ghz_oper->control);
+ cfg80211_6ghz_power_type(he_6ghz_oper->control,
+ cbss->channel->flags);
else
link_info(link,
"HE 6 GHz operation missing (on %d MHz), expect issues\n",
@@ -5487,9 +6108,10 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
ret = ieee80211_link_use_channel(link, &chanreq,
IEEE80211_CHANCTX_SHARED);
- /* don't downgrade for 5 and 10 MHz channels, though. */
+ /* don't downgrade for 5/10/S1G MHz channels, though. */
if (chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- chanreq.oper.width == NL80211_CHAN_WIDTH_10)
+ chanreq.oper.width == NL80211_CHAN_WIDTH_10 ||
+ cfg80211_chandef_is_s1g(&chanreq.oper))
return ret;
while (ret && chanreq.oper.width != NL80211_CHAN_WIDTH_20_NOHT) {
@@ -5627,7 +6249,8 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
link->u.mgd.conn = assoc_data->link[link_id].conn;
err = ieee80211_prep_channel(sdata, link, link_id, cbss,
- true, &link->u.mgd.conn);
+ true, &link->u.mgd.conn,
+ sdata->u.mgd.userspace_selectors);
if (err) {
link_info(link, "prep_channel failed\n");
goto out_err;
@@ -5661,7 +6284,7 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
/* links might have changed due to rejected ones, set them again */
ieee80211_vif_set_links(sdata, valid_links, dormant_links);
- rate_control_rate_init(sta);
+ rate_control_rate_init_all_links(sta);
if (ifmgd->flags & IEEE80211_STA_MFP_ENABLED) {
set_sta_flag(sta, WLAN_STA_MFP);
@@ -5723,6 +6346,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
.bss = NULL,
.link_id = -1,
.from_ap = true,
+ .type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE,
};
struct ieee802_11_elems *elems;
int ac;
@@ -5739,12 +6363,15 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
};
u8 ap_mld_addr[ETH_ALEN] __aligned(2);
unsigned int link_id;
+ u16 max_aid = IEEE80211_MAX_AID;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
if (!assoc_data)
return;
+ info.link_id = assoc_data->assoc_link_id;
+
parse_params.mode =
assoc_data->link[assoc_data->assoc_link_id].conn.mode;
@@ -5763,10 +6390,12 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
reassoc = ieee80211_is_reassoc_resp(mgmt->frame_control);
capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info);
status_code = le16_to_cpu(mgmt->u.assoc_resp.status_code);
- if (assoc_data->s1g)
+ if (assoc_data->s1g) {
elem_start = mgmt->u.s1g_assoc_resp.variable;
- else
+ max_aid = IEEE80211_MAX_SUPPORTED_S1G_AID;
+ } else {
elem_start = mgmt->u.assoc_resp.variable;
+ }
/*
* Note: this may not be perfect, AP might misbehave - if
@@ -5790,16 +6419,15 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
if (elems->aid_resp)
aid = le16_to_cpu(elems->aid_resp->aid);
- else if (assoc_data->s1g)
- aid = 0; /* TODO */
else
aid = le16_to_cpu(mgmt->u.assoc_resp.aid);
/*
- * The 5 MSB of the AID field are reserved
- * (802.11-2016 9.4.1.8 AID field)
+ * The 5 MSB of the AID field are reserved for a non-S1G STA. For
+ * an S1G STA the 3 MSBs are reserved.
+ * (802.11-2016 9.4.1.8 AID field).
*/
- aid &= 0x7ff;
+ aid &= assoc_data->s1g ? 0x1fff : 0x7ff;
sdata_info(sdata,
"RX %sssocResp from %pM (capab=0x%x status=%d aid=%d)\n",
@@ -5836,7 +6464,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
event.u.mlme.reason = status_code;
drv_event_callback(sdata->local, sdata, &event);
} else {
- if (aid == 0 || aid > IEEE80211_MAX_AID) {
+ if (aid == 0 || aid > max_aid) {
sdata_info(sdata,
"invalid AID value %d (out of range), turn off PS\n",
aid);
@@ -5874,6 +6502,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
}
sdata->vif.cfg.aid = aid;
+ sdata->vif.cfg.s1g = assoc_data->s1g;
if (!ieee80211_assoc_success(sdata, mgmt, elems,
elem_start, elem_len)) {
@@ -5979,8 +6608,8 @@ static void ieee80211_rx_mgmt_probe_resp(struct ieee80211_link_data *link,
* Response frame shall be set to the broadcast address [..]"
* So, on 6GHz band we should also accept broadcast responses.
*/
- channel = ieee80211_get_channel(sdata->local->hw.wiphy,
- rx_status->freq);
+ channel = ieee80211_get_channel_khz(sdata->local->hw.wiphy,
+ ieee80211_rx_status_to_khz(rx_status));
if (!channel)
return;
@@ -6245,7 +6874,7 @@ static void ieee80211_ml_reconfiguration(struct ieee80211_sub_if_data *sdata,
/* In case the removal was cancelled, abort it */
if (sdata->u.mgd.removed_links) {
sdata->u.mgd.removed_links = 0;
- wiphy_delayed_work_cancel(sdata->local->hw.wiphy,
+ wiphy_hrtimer_work_cancel(sdata->local->hw.wiphy,
&sdata->u.mgd.ml_reconf_work);
}
return;
@@ -6275,9 +6904,9 @@ static void ieee80211_ml_reconfiguration(struct ieee80211_sub_if_data *sdata,
}
sdata->u.mgd.removed_links = removed_links;
- wiphy_delayed_work_queue(sdata->local->hw.wiphy,
+ wiphy_hrtimer_work_queue(sdata->local->hw.wiphy,
&sdata->u.mgd.ml_reconf_work,
- TU_TO_JIFFIES(delay));
+ us_to_ktime(ieee80211_tu_to_usec(delay)));
}
static int ieee80211_ttlm_set_links(struct ieee80211_sub_if_data *sdata,
@@ -6464,7 +7093,7 @@ static void ieee80211_process_adv_ttlm(struct ieee80211_sub_if_data *sdata,
/* if a planned TID-to-link mapping was cancelled -
* abort it
*/
- wiphy_delayed_work_cancel(sdata->local->hw.wiphy,
+ wiphy_hrtimer_work_cancel(sdata->local->hw.wiphy,
&sdata->u.mgd.ttlm_work);
} else if (sdata->u.mgd.ttlm_info.active) {
/* if no TID-to-link element, set to default mapping in
@@ -6499,7 +7128,7 @@ static void ieee80211_process_adv_ttlm(struct ieee80211_sub_if_data *sdata,
if (ttlm_info.switch_time) {
u16 beacon_ts_tu, st_tu, delay;
- u32 delay_jiffies;
+ u64 delay_usec;
u64 mask;
/* The t2l map switch time is indicated with a partial
@@ -6521,23 +7150,23 @@ static void ieee80211_process_adv_ttlm(struct ieee80211_sub_if_data *sdata,
if (delay > IEEE80211_ADV_TTLM_ST_UNDERFLOW)
return;
- delay_jiffies = TU_TO_JIFFIES(delay);
+ delay_usec = ieee80211_tu_to_usec(delay);
/* Link switching can take time, so schedule it
* 100ms before to be ready on time
*/
- if (delay_jiffies > IEEE80211_ADV_TTLM_SAFETY_BUFFER_MS)
- delay_jiffies -=
+ if (delay_usec > IEEE80211_ADV_TTLM_SAFETY_BUFFER_MS)
+ delay_usec -=
IEEE80211_ADV_TTLM_SAFETY_BUFFER_MS;
else
- delay_jiffies = 0;
+ delay_usec = 0;
sdata->u.mgd.ttlm_info = ttlm_info;
- wiphy_delayed_work_cancel(sdata->local->hw.wiphy,
+ wiphy_hrtimer_work_cancel(sdata->local->hw.wiphy,
&sdata->u.mgd.ttlm_work);
- wiphy_delayed_work_queue(sdata->local->hw.wiphy,
+ wiphy_hrtimer_work_queue(sdata->local->hw.wiphy,
&sdata->u.mgd.ttlm_work,
- delay_jiffies);
+ us_to_ktime(delay_usec));
return;
}
}
@@ -6626,7 +7255,9 @@ ieee80211_mgd_check_cross_link_csa(struct ieee80211_sub_if_data *sdata,
(prof->sta_info_len - 1),
len -
(prof->sta_info_len - 1),
- false, NULL);
+ IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_BEACON,
+ NULL);
/* memory allocation failed - let's hope that's transient */
if (!prof_elems)
@@ -6664,12 +7295,44 @@ static bool ieee80211_mgd_ssid_mismatch(struct ieee80211_sub_if_data *sdata,
return true;
/* hidden SSID: zeroed out */
- if (memcmp(elems->ssid, zero_ssid, elems->ssid_len))
+ if (!memcmp(elems->ssid, zero_ssid, elems->ssid_len))
return false;
return memcmp(elems->ssid, cfg->ssid, cfg->ssid_len);
}
+static bool
+ieee80211_rx_beacon_freq_valid(struct ieee80211_local *local,
+ struct ieee80211_mgmt *mgmt,
+ struct ieee80211_rx_status *rx_status,
+ struct ieee80211_chanctx_conf *chanctx)
+{
+ u32 pri_2mhz_khz;
+ struct ieee80211_channel *s1g_sibling_1mhz;
+ u32 pri_khz = ieee80211_channel_to_khz(chanctx->def.chan);
+ u32 rx_khz = ieee80211_rx_status_to_khz(rx_status);
+
+ if (rx_khz == pri_khz)
+ return true;
+
+ if (!chanctx->def.s1g_primary_2mhz)
+ return false;
+
+ /*
+ * If we have an S1G interface with a 2MHz primary, beacons are
+ * sent on the center frequency of the 2MHz primary. Find the sibling
+ * 1MHz channel and calculate the 2MHz primary center frequency.
+ */
+ s1g_sibling_1mhz = cfg80211_s1g_get_primary_sibling(local->hw.wiphy,
+ &chanctx->def);
+ if (!s1g_sibling_1mhz)
+ return false;
+
+ pri_2mhz_khz =
+ (pri_khz + ieee80211_channel_to_khz(s1g_sibling_1mhz)) / 2;
+ return rx_khz == pri_2mhz_khz;
+}
+
static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
struct ieee80211_hdr *hdr, size_t len,
struct ieee80211_rx_status *rx_status)
@@ -6679,6 +7342,7 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
struct ieee80211_bss_conf *bss_conf = link->conf;
struct ieee80211_vif_cfg *vif_cfg = &sdata->vif.cfg;
struct ieee80211_mgmt *mgmt = (void *) hdr;
+ struct ieee80211_ext *ext = NULL;
size_t baselen;
struct ieee802_11_elems *elems;
struct ieee80211_local *local = sdata->local;
@@ -6697,6 +7361,7 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
.mode = link->u.mgd.conn.mode,
.link_id = -1,
.from_ap = true,
+ .type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE,
};
lockdep_assert_wiphy(local->hw.wiphy);
@@ -6704,12 +7369,9 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
/* Process beacon from the current BSS */
bssid = ieee80211_get_bssid(hdr, len, sdata->vif.type);
if (ieee80211_is_s1g_beacon(mgmt->frame_control)) {
- struct ieee80211_ext *ext = (void *) mgmt;
-
- if (ieee80211_is_s1g_short_beacon(ext->frame_control))
- variable = ext->u.s1g_short_beacon.variable;
- else
- variable = ext->u.s1g_beacon.variable;
+ ext = (void *)mgmt;
+ variable = ext->u.s1g_beacon.variable +
+ ieee80211_s1g_optional_len(ext->frame_control);
}
baselen = (u8 *) variable - (u8 *) mgmt;
@@ -6726,8 +7388,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
return;
}
- if (ieee80211_rx_status_to_khz(rx_status) !=
- ieee80211_channel_to_khz(chanctx_conf->def.chan)) {
+ if (!ieee80211_rx_beacon_freq_valid(local, mgmt, rx_status,
+ chanctx_conf)) {
rcu_read_unlock();
return;
}
@@ -6823,11 +7485,12 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
ncrc = elems->crc;
if (ieee80211_hw_check(&local->hw, PS_NULLFUNC_STACK) &&
- ieee80211_check_tim(elems->tim, elems->tim_len, vif_cfg->aid)) {
+ ieee80211_check_tim(elems->tim, elems->tim_len, vif_cfg->aid,
+ vif_cfg->s1g)) {
if (local->hw.conf.dynamic_ps_timeout > 0) {
if (local->hw.conf.flags & IEEE80211_CONF_PS) {
local->hw.conf.flags &= ~IEEE80211_CONF_PS;
- ieee80211_hw_config(local,
+ ieee80211_hw_config(local, -1,
IEEE80211_CONF_CHANGE_PS);
}
ieee80211_send_nullfunc(local, sdata, false);
@@ -6894,7 +7557,9 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
}
if ((ncrc == link->u.mgd.beacon_crc && link->u.mgd.beacon_crc_valid) ||
- ieee80211_is_s1g_short_beacon(mgmt->frame_control))
+ (ext && ieee80211_is_s1g_short_beacon(ext->frame_control,
+ parse_params.start,
+ parse_params.len)))
goto free;
link->u.mgd.beacon_crc = ncrc;
link->u.mgd.beacon_crc_valid = true;
@@ -6909,7 +7574,10 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
/* note that after this elems->ml_basic can no longer be used fully */
ieee80211_mgd_check_cross_link_csa(sdata, rx_status->link_id, elems);
- if (!link->u.mgd.disable_wmm_tracking &&
+ ieee80211_mgd_update_bss_param_ch_cnt(sdata, bss_conf, elems);
+
+ if (!sdata->u.mgd.epcs.enabled &&
+ !link->u.mgd.disable_wmm_tracking &&
ieee80211_sta_wmm_params(local, link, elems->wmm_param,
elems->wmm_param_len,
elems->mu_edca_param_set))
@@ -6960,7 +7628,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
changed |= ieee80211_recalc_twt_req(sdata, sband, link, link_sta, elems);
- if (ieee80211_config_bw(link, elems, true, &changed)) {
+ if (ieee80211_config_bw(link, elems, true, &changed,
+ IEEE80211_STYPE_BEACON)) {
ieee80211_set_disassoc(sdata, IEEE80211_STYPE_DEAUTH,
WLAN_REASON_DEAUTH_LEAVING,
true, deauth_buf);
@@ -7157,6 +7826,7 @@ ieee80211_send_neg_ttlm_res(struct ieee80211_sub_if_data *sdata,
int hdr_len = offsetofend(struct ieee80211_mgmt, u.action.u.ttlm_res);
int ttlm_max_len = 2 + 1 + sizeof(struct ieee80211_ttlm_elem) + 1 +
2 * 2 * IEEE80211_TTLM_NUM_TIDS;
+ u16 status_code;
skb = dev_alloc_skb(local->tx_headroom + hdr_len + ttlm_max_len);
if (!skb)
@@ -7179,19 +7849,18 @@ ieee80211_send_neg_ttlm_res(struct ieee80211_sub_if_data *sdata,
WARN_ON(1);
fallthrough;
case NEG_TTLM_RES_REJECT:
- mgmt->u.action.u.ttlm_res.status_code =
- WLAN_STATUS_DENIED_TID_TO_LINK_MAPPING;
+ status_code = WLAN_STATUS_DENIED_TID_TO_LINK_MAPPING;
break;
case NEG_TTLM_RES_ACCEPT:
- mgmt->u.action.u.ttlm_res.status_code = WLAN_STATUS_SUCCESS;
+ status_code = WLAN_STATUS_SUCCESS;
break;
case NEG_TTLM_RES_SUGGEST_PREFERRED:
- mgmt->u.action.u.ttlm_res.status_code =
- WLAN_STATUS_PREF_TID_TO_LINK_MAPPING_SUGGESTED;
+ status_code = WLAN_STATUS_PREF_TID_TO_LINK_MAPPING_SUGGESTED;
ieee80211_neg_ttlm_add_suggested_map(skb, neg_ttlm);
break;
}
+ mgmt->u.action.u.ttlm_res.status_code = cpu_to_le16(status_code);
ieee80211_tx_skb(sdata, skb);
}
@@ -7295,7 +7964,10 @@ void ieee80211_process_neg_ttlm_req(struct ieee80211_sub_if_data *sdata,
ies_len = len - offsetof(struct ieee80211_mgmt,
u.action.u.ttlm_req.variable);
elems = ieee802_11_parse_elems(mgmt->u.action.u.ttlm_req.variable,
- ies_len, true, NULL);
+ ies_len,
+ IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION,
+ NULL);
if (!elems) {
ttlm_res = NEG_TTLM_RES_REJECT;
goto out;
@@ -7357,17 +8029,13 @@ void ieee80211_process_neg_ttlm_res(struct ieee80211_sub_if_data *sdata,
* This can be better implemented in the future, to handle request
* rejections.
*/
- if (mgmt->u.action.u.ttlm_res.status_code != WLAN_STATUS_SUCCESS)
+ if (le16_to_cpu(mgmt->u.action.u.ttlm_res.status_code) != WLAN_STATUS_SUCCESS)
__ieee80211_disconnect(sdata);
}
-static void ieee80211_teardown_ttlm_work(struct wiphy *wiphy,
- struct wiphy_work *work)
+void ieee80211_process_ttlm_teardown(struct ieee80211_sub_if_data *sdata)
{
u16 new_dormant_links;
- struct ieee80211_sub_if_data *sdata =
- container_of(work, struct ieee80211_sub_if_data,
- u.mgd.teardown_ttlm_work);
if (!sdata->vif.neg_ttlm.valid)
return;
@@ -7382,6 +8050,16 @@ static void ieee80211_teardown_ttlm_work(struct wiphy *wiphy,
BSS_CHANGED_MLD_VALID_LINKS);
}
+static void ieee80211_teardown_ttlm_work(struct wiphy *wiphy,
+ struct wiphy_work *work)
+{
+ struct ieee80211_sub_if_data *sdata =
+ container_of(work, struct ieee80211_sub_if_data,
+ u.mgd.teardown_ttlm_work);
+
+ ieee80211_process_ttlm_teardown(sdata);
+}
+
void ieee80211_send_teardown_neg_ttlm(struct ieee80211_vif *vif)
{
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
@@ -7495,9 +8173,11 @@ void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
break;
/* CSA IE cannot be overridden, no need for BSSID */
- elems = ieee802_11_parse_elems(
- mgmt->u.action.u.chan_switch.variable,
- ies_len, true, NULL);
+ elems = ieee802_11_parse_elems(mgmt->u.action.u.chan_switch.variable,
+ ies_len,
+ IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION,
+ NULL);
if (elems && !elems->parse_error) {
enum ieee80211_csa_source src =
@@ -7524,9 +8204,11 @@ void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
* extended CSA IE can't be overridden, no need for
* BSSID
*/
- elems = ieee802_11_parse_elems(
- mgmt->u.action.u.ext_chan_switch.variable,
- ies_len, true, NULL);
+ elems = ieee802_11_parse_elems(mgmt->u.action.u.ext_chan_switch.variable,
+ ies_len,
+ IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION,
+ NULL);
if (elems && !elems->parse_error) {
enum ieee80211_csa_source src;
@@ -7558,7 +8240,7 @@ void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
static void ieee80211_sta_timer(struct timer_list *t)
{
struct ieee80211_sub_if_data *sdata =
- from_timer(sdata, t, u.mgd.timer);
+ timer_container_of(sdata, t, u.mgd.timer);
wiphy_work_queue(sdata->local->hw.wiphy, &sdata->work);
}
@@ -7660,6 +8342,7 @@ static int ieee80211_do_assoc(struct ieee80211_sub_if_data *sdata)
lockdep_assert_wiphy(sdata->local->hw.wiphy);
assoc_data->tries++;
+ assoc_data->comeback = false;
if (assoc_data->tries > IEEE80211_ASSOC_MAX_TRIES) {
sdata_info(sdata, "association with %pM timed out\n",
assoc_data->ap_addr);
@@ -7860,16 +8543,32 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata)
}
}
+static bool
+ieee80211_is_csa_in_progress(struct ieee80211_sub_if_data *sdata)
+{
+ /*
+ * In MLO, check the CSA flags 'active' and 'waiting_bcn' for all
+ * the links.
+ */
+ struct ieee80211_link_data *link;
+
+ guard(rcu)();
+
+ for_each_link_data_rcu(sdata, link) {
+ if (!(link->conf->csa_active &&
+ !link->u.mgd.csa.waiting_bcn))
+ return false;
+ }
+
+ return true;
+}
+
static void ieee80211_sta_bcn_mon_timer(struct timer_list *t)
{
struct ieee80211_sub_if_data *sdata =
- from_timer(sdata, t, u.mgd.bcn_mon_timer);
+ timer_container_of(sdata, t, u.mgd.bcn_mon_timer);
- if (WARN_ON(ieee80211_vif_is_mld(&sdata->vif)))
- return;
-
- if (sdata->vif.bss_conf.csa_active &&
- !sdata->deflink.u.mgd.csa.waiting_bcn)
+ if (ieee80211_is_csa_in_progress(sdata))
return;
if (sdata->vif.driver_flags & IEEE80211_VIF_BEACON_FILTER)
@@ -7880,36 +8579,69 @@ static void ieee80211_sta_bcn_mon_timer(struct timer_list *t)
&sdata->u.mgd.beacon_connection_loss_work);
}
-static void ieee80211_sta_conn_mon_timer(struct timer_list *t)
+static unsigned long
+ieee80211_latest_active_link_conn_timeout(struct ieee80211_sub_if_data *sdata)
{
- struct ieee80211_sub_if_data *sdata =
- from_timer(sdata, t, u.mgd.conn_mon_timer);
- struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
- struct ieee80211_local *local = sdata->local;
+ unsigned long latest_timeout = jiffies;
+ unsigned int link_id;
struct sta_info *sta;
- unsigned long timeout;
-
- if (WARN_ON(ieee80211_vif_is_mld(&sdata->vif)))
- return;
- if (sdata->vif.bss_conf.csa_active &&
- !sdata->deflink.u.mgd.csa.waiting_bcn)
- return;
+ guard(rcu)();
sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
if (!sta)
+ return 0;
+
+ for (link_id = 0; link_id < ARRAY_SIZE(sta->link);
+ link_id++) {
+ struct link_sta_info *link_sta;
+ unsigned long timeout;
+
+ link_sta = rcu_dereference(sta->link[link_id]);
+ if (!link_sta)
+ continue;
+
+ timeout = link_sta->status_stats.last_ack;
+ if (time_before(timeout, link_sta->rx_stats.last_rx))
+ timeout = link_sta->rx_stats.last_rx;
+
+ timeout += IEEE80211_CONNECTION_IDLE_TIME;
+
+ /*
+ * latest_timeout holds the timeout of the link
+ * that will expire last among all links in an
+ * non-AP MLD STA. This ensures that the connection
+ * monitor timer is only reset if at least one link
+ * is still active, and it is scheduled to fire at
+ * the latest possible timeout.
+ */
+ if (time_after(timeout, latest_timeout))
+ latest_timeout = timeout;
+ }
+
+ return latest_timeout;
+}
+
+static void ieee80211_sta_conn_mon_timer(struct timer_list *t)
+{
+ struct ieee80211_sub_if_data *sdata =
+ timer_container_of(sdata, t, u.mgd.conn_mon_timer);
+ struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+ struct ieee80211_local *local = sdata->local;
+ unsigned long latest_timeout;
+
+ if (ieee80211_is_csa_in_progress(sdata))
return;
- timeout = sta->deflink.status_stats.last_ack;
- if (time_before(sta->deflink.status_stats.last_ack, sta->deflink.rx_stats.last_rx))
- timeout = sta->deflink.rx_stats.last_rx;
- timeout += IEEE80211_CONNECTION_IDLE_TIME;
+ latest_timeout = ieee80211_latest_active_link_conn_timeout(sdata);
- /* If timeout is after now, then update timer to fire at
+ /*
+ * If latest timeout is after now, then update timer to fire at
* the later date, but do not actually probe at this time.
*/
- if (time_is_after_jiffies(timeout)) {
- mod_timer(&ifmgd->conn_mon_timer, round_jiffies_up(timeout));
+ if (time_is_after_jiffies(latest_timeout)) {
+ mod_timer(&ifmgd->conn_mon_timer,
+ round_jiffies_up(latest_timeout));
return;
}
@@ -8038,6 +8770,25 @@ static void ieee80211_request_smps_mgd_work(struct wiphy *wiphy,
link->u.mgd.driver_smps_mode);
}
+static void ieee80211_ml_sta_reconf_timeout(struct wiphy *wiphy,
+ struct wiphy_work *work)
+{
+ struct ieee80211_sub_if_data *sdata =
+ container_of(work, struct ieee80211_sub_if_data,
+ u.mgd.reconf.wk.work);
+
+ if (!sdata->u.mgd.reconf.added_links &&
+ !sdata->u.mgd.reconf.removed_links)
+ return;
+
+ sdata_info(sdata,
+ "mlo: reconf: timeout: added=0x%x, removed=0x%x\n",
+ sdata->u.mgd.reconf.added_links,
+ sdata->u.mgd.reconf.removed_links);
+
+ __ieee80211_disconnect(sdata);
+}
+
/* interface setup */
void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata)
{
@@ -8050,14 +8801,16 @@ void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata)
ieee80211_csa_connection_drop_work);
wiphy_delayed_work_init(&ifmgd->tdls_peer_del_work,
ieee80211_tdls_peer_del_work);
- wiphy_delayed_work_init(&ifmgd->ml_reconf_work,
+ wiphy_hrtimer_work_init(&ifmgd->ml_reconf_work,
ieee80211_ml_reconf_work);
+ wiphy_delayed_work_init(&ifmgd->reconf.wk,
+ ieee80211_ml_sta_reconf_timeout);
timer_setup(&ifmgd->timer, ieee80211_sta_timer, 0);
timer_setup(&ifmgd->bcn_mon_timer, ieee80211_sta_bcn_mon_timer, 0);
timer_setup(&ifmgd->conn_mon_timer, ieee80211_sta_conn_mon_timer, 0);
wiphy_delayed_work_init(&ifmgd->tx_tspec_wk,
ieee80211_sta_handle_tspec_ac_params_wk);
- wiphy_delayed_work_init(&ifmgd->ttlm_work,
+ wiphy_hrtimer_work_init(&ifmgd->ttlm_work,
ieee80211_tid_to_link_map_work);
wiphy_delayed_work_init(&ifmgd->neg_ttlm_timeout_work,
ieee80211_neg_ttlm_timeout_work);
@@ -8104,7 +8857,7 @@ void ieee80211_mgd_setup_link(struct ieee80211_link_data *link)
else
link->u.mgd.req_smps = IEEE80211_SMPS_OFF;
- wiphy_delayed_work_init(&link->u.mgd.csa.switch_work,
+ wiphy_hrtimer_work_init(&link->u.mgd.csa.switch_work,
ieee80211_csa_switch_work);
ieee80211_clear_tpe(&link->conf->tpe);
@@ -8112,6 +8865,9 @@ void ieee80211_mgd_setup_link(struct ieee80211_link_data *link)
if (sdata->u.mgd.assoc_data)
ether_addr_copy(link->conf->addr,
sdata->u.mgd.assoc_data->link[link_id].addr);
+ else if (sdata->u.mgd.reconf.add_links_data)
+ ether_addr_copy(link->conf->addr,
+ sdata->u.mgd.reconf.add_links_data->link[link_id].addr);
else if (!is_valid_ether_addr(link->conf->addr))
eth_random_addr(link->conf->addr);
}
@@ -8134,7 +8890,8 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata,
struct cfg80211_bss *cbss, s8 link_id,
const u8 *ap_mld_addr, bool assoc,
struct ieee80211_conn_settings *conn,
- bool override)
+ bool override,
+ unsigned long *userspace_selectors)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
@@ -8144,21 +8901,33 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata,
bool have_sta = false;
bool mlo;
int err;
+ u16 new_links;
if (link_id >= 0) {
mlo = true;
if (WARN_ON(!ap_mld_addr))
return -EINVAL;
- err = ieee80211_vif_set_links(sdata, BIT(link_id), 0);
+ new_links = BIT(link_id);
} else {
if (WARN_ON(ap_mld_addr))
return -EINVAL;
ap_mld_addr = cbss->bssid;
- err = ieee80211_vif_set_links(sdata, 0, 0);
+ new_links = 0;
link_id = 0;
mlo = false;
}
+ if (assoc) {
+ rcu_read_lock();
+ have_sta = sta_info_get(sdata, ap_mld_addr);
+ rcu_read_unlock();
+ }
+
+ if (mlo && !have_sta &&
+ WARN_ON(sdata->vif.valid_links || sdata->vif.active_links))
+ return -EINVAL;
+
+ err = ieee80211_vif_set_links(sdata, new_links, 0);
if (err)
return err;
@@ -8179,12 +8948,6 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata,
goto out_err;
}
- if (assoc) {
- rcu_read_lock();
- have_sta = sta_info_get(sdata, ap_mld_addr);
- rcu_read_unlock();
- }
-
if (!have_sta) {
if (mlo)
new_sta = sta_info_alloc_with_link(sdata, ap_mld_addr,
@@ -8273,7 +9036,8 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata,
*/
link->u.mgd.conn = *conn;
err = ieee80211_prep_channel(sdata, link, link->link_id, cbss,
- mlo, &link->u.mgd.conn);
+ mlo, &link->u.mgd.conn,
+ userspace_selectors);
if (err) {
if (new_sta)
sta_info_free(local, new_sta);
@@ -8389,6 +9153,22 @@ out:
return ret;
}
+static void ieee80211_parse_cfg_selectors(unsigned long *userspace_selectors,
+ const u8 *supported_selectors,
+ u8 supported_selectors_len)
+{
+ if (supported_selectors) {
+ for (int i = 0; i < supported_selectors_len; i++) {
+ set_bit(supported_selectors[i],
+ userspace_selectors);
+ }
+ } else {
+ /* Assume SAE_H2E support for backward compatibility. */
+ set_bit(BSS_MEMBERSHIP_SELECTOR_SAE_H2E,
+ userspace_selectors);
+ }
+}
+
/* config hooks */
int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata,
struct cfg80211_auth_request *req)
@@ -8490,6 +9270,10 @@ int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata,
memcpy(auth_data->key, req->key, req->key_len);
}
+ ieee80211_parse_cfg_selectors(auth_data->userspace_selectors,
+ req->supported_selectors,
+ req->supported_selectors_len);
+
auth_data->algorithm = auth_alg;
/* try to authenticate/probe */
@@ -8543,7 +9327,8 @@ int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata,
err = ieee80211_prep_connection(sdata, req->bss, req->link_id,
req->ap_mld_addr, cont_auth,
- &conn, false);
+ &conn, false,
+ auth_data->userspace_selectors);
if (err)
goto err_clear;
@@ -8762,6 +9547,39 @@ out_rcu:
return err;
}
+static bool
+ieee80211_mgd_assoc_bss_has_mld_ext_capa_ops(struct cfg80211_assoc_request *req)
+{
+ const struct cfg80211_bss_ies *ies;
+ struct cfg80211_bss *bss;
+ const struct element *ml;
+
+ /* not an MLO connection if link_id < 0, so irrelevant */
+ if (req->link_id < 0)
+ return false;
+
+ bss = req->links[req->link_id].bss;
+
+ guard(rcu)();
+ ies = rcu_dereference(bss->ies);
+ for_each_element_extid(ml, WLAN_EID_EXT_EHT_MULTI_LINK,
+ ies->data, ies->len) {
+ const struct ieee80211_multi_link_elem *mle;
+
+ if (!ieee80211_mle_type_ok(ml->data + 1,
+ IEEE80211_ML_CONTROL_TYPE_BASIC,
+ ml->datalen - 1))
+ continue;
+
+ mle = (void *)(ml->data + 1);
+ if (mle->control & cpu_to_le16(IEEE80211_MLC_BASIC_PRES_EXT_MLD_CAPA_OP))
+ return true;
+ }
+
+ return false;
+
+}
+
int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
struct cfg80211_assoc_request *req)
{
@@ -8814,6 +9632,18 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
else
memcpy(assoc_data->ap_addr, cbss->bssid, ETH_ALEN);
+ /*
+ * Many APs have broken parsing of the extended MLD capa/ops field,
+ * dropping (re-)association request frames or replying with association
+ * response with a failure status if it's present.
+ * Set our value from the userspace request only in strict mode or if
+ * the AP also had that field present.
+ */
+ if (ieee80211_hw_check(&local->hw, STRICT) ||
+ ieee80211_mgd_assoc_bss_has_mld_ext_capa_ops(req))
+ assoc_data->ext_mld_capa_ops =
+ cpu_to_le16(req->ext_mld_capa_ops);
+
if (ifmgd->associated) {
u8 frame_buf[IEEE80211_DEAUTH_FRAME_LEN];
@@ -8830,6 +9660,12 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
false);
}
+ memset(sdata->u.mgd.userspace_selectors, 0,
+ sizeof(sdata->u.mgd.userspace_selectors));
+ ieee80211_parse_cfg_selectors(sdata->u.mgd.userspace_selectors,
+ req->supported_selectors,
+ req->supported_selectors_len);
+
memcpy(&ifmgd->ht_capa, &req->ht_capa, sizeof(ifmgd->ht_capa));
memcpy(&ifmgd->ht_capa_mask, &req->ht_capa_mask,
sizeof(ifmgd->ht_capa_mask));
@@ -9076,7 +9912,8 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
/* only calculate the mode, hence link == NULL */
err = ieee80211_prep_channel(sdata, NULL, i,
assoc_data->link[i].bss, true,
- &assoc_data->link[i].conn);
+ &assoc_data->link[i].conn,
+ sdata->u.mgd.userspace_selectors);
if (err) {
req->links[i].error = err;
goto err_clear;
@@ -9092,7 +9929,8 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
err = ieee80211_prep_connection(sdata, cbss, req->link_id,
req->ap_mld_addr, true,
&assoc_data->link[assoc_link_id].conn,
- override);
+ override,
+ sdata->u.mgd.userspace_selectors);
if (err)
goto err_clear;
@@ -9198,7 +10036,6 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata,
ieee80211_report_disconnect(sdata, frame_buf,
sizeof(frame_buf), true,
req->reason_code, false);
- drv_mgd_complete_tx(sdata->local, sdata, &info);
return 0;
}
@@ -9235,7 +10072,7 @@ void ieee80211_mgd_stop_link(struct ieee80211_link_data *link)
&link->u.mgd.request_smps_work);
wiphy_work_cancel(link->sdata->local->hw.wiphy,
&link->u.mgd.recalc_smps);
- wiphy_delayed_work_cancel(link->sdata->local->hw.wiphy,
+ wiphy_hrtimer_work_cancel(link->sdata->local->hw.wiphy,
&link->u.mgd.csa.switch_work);
}
@@ -9271,7 +10108,7 @@ void ieee80211_mgd_stop(struct ieee80211_sub_if_data *sdata)
ifmgd->assoc_req_ies = NULL;
ifmgd->assoc_req_ies_len = 0;
spin_unlock_bh(&ifmgd->teardown_lock);
- del_timer_sync(&ifmgd->timer);
+ timer_delete_sync(&ifmgd->timer);
}
void ieee80211_cqm_rssi_notify(struct ieee80211_vif *vif,
@@ -9336,3 +10173,891 @@ void ieee80211_disable_rssi_reports(struct ieee80211_vif *vif)
_ieee80211_enable_rssi_reports(sdata, 0, 0);
}
EXPORT_SYMBOL(ieee80211_disable_rssi_reports);
+
+void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt, size_t len)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+ struct ieee80211_mgd_assoc_data *add_links_data =
+ ifmgd->reconf.add_links_data;
+ struct sta_info *sta;
+ struct cfg80211_mlo_reconf_done_data done_data = {};
+ u16 sta_changed_links = sdata->u.mgd.reconf.added_links |
+ sdata->u.mgd.reconf.removed_links;
+ u16 link_mask, valid_links;
+ unsigned int link_id;
+ size_t orig_len = len;
+ u8 i, group_key_data_len;
+ u8 *pos;
+
+ if (!ieee80211_vif_is_mld(&sdata->vif) ||
+ len < offsetofend(typeof(*mgmt), u.action.u.ml_reconf_resp) ||
+ mgmt->u.action.u.ml_reconf_resp.dialog_token !=
+ sdata->u.mgd.reconf.dialog_token ||
+ !sta_changed_links)
+ return;
+
+ pos = mgmt->u.action.u.ml_reconf_resp.variable;
+ len -= offsetofend(typeof(*mgmt), u.action.u.ml_reconf_resp);
+
+ /* each status duple is 3 octets */
+ if (len < mgmt->u.action.u.ml_reconf_resp.count * 3) {
+ sdata_info(sdata,
+ "mlo: reconf: unexpected len=%zu, count=%u\n",
+ len, mgmt->u.action.u.ml_reconf_resp.count);
+ goto disconnect;
+ }
+
+ link_mask = sta_changed_links;
+ for (i = 0; i < mgmt->u.action.u.ml_reconf_resp.count; i++) {
+ u16 status = get_unaligned_le16(pos + 1);
+
+ link_id = *pos;
+
+ if (!(link_mask & BIT(link_id))) {
+ sdata_info(sdata,
+ "mlo: reconf: unexpected link: %u, changed=0x%x\n",
+ link_id, sta_changed_links);
+ goto disconnect;
+ }
+
+ /* clear the corresponding link, to detect the case that
+ * the same link was included more than one time
+ */
+ link_mask &= ~BIT(link_id);
+
+ /* Handle failure to remove links here. Failure to remove added
+ * links will be done later in the flow.
+ */
+ if (status != WLAN_STATUS_SUCCESS) {
+ sdata_info(sdata,
+ "mlo: reconf: failed on link=%u, status=%u\n",
+ link_id, status);
+
+ /* The AP MLD failed to remove a link that was already
+ * removed locally. As this is not expected behavior,
+ * disconnect
+ */
+ if (sdata->u.mgd.reconf.removed_links & BIT(link_id))
+ goto disconnect;
+
+ /* The AP MLD failed to add a link. Remove it from the
+ * added links.
+ */
+ sdata->u.mgd.reconf.added_links &= ~BIT(link_id);
+ }
+
+ pos += 3;
+ len -= 3;
+ }
+
+ if (link_mask) {
+ sdata_info(sdata,
+ "mlo: reconf: no response for links=0x%x\n",
+ link_mask);
+ goto disconnect;
+ }
+
+ if (!sdata->u.mgd.reconf.added_links)
+ goto out;
+
+ if (len < 1 || len < 1 + *pos) {
+ sdata_info(sdata,
+ "mlo: reconf: invalid group key data length");
+ goto disconnect;
+ }
+
+ /* The Group Key Data field must be present when links are added. This
+ * field should be processed by userland.
+ */
+ group_key_data_len = *pos++;
+
+ pos += group_key_data_len;
+ len -= group_key_data_len + 1;
+
+ /* Process the information for the added links */
+ sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
+ if (WARN_ON(!sta))
+ goto disconnect;
+
+ valid_links = sdata->vif.valid_links;
+ for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
+ if (!add_links_data->link[link_id].bss ||
+ !(sdata->u.mgd.reconf.added_links & BIT(link_id)))
+ continue;
+
+ valid_links |= BIT(link_id);
+ if (ieee80211_sta_allocate_link(sta, link_id))
+ goto disconnect;
+ }
+
+ ieee80211_vif_set_links(sdata, valid_links, sdata->vif.dormant_links);
+ link_mask = 0;
+ for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
+ struct cfg80211_bss *cbss = add_links_data->link[link_id].bss;
+ struct ieee80211_link_data *link;
+ struct link_sta_info *link_sta;
+ u64 changed = 0;
+
+ if (!cbss)
+ continue;
+
+ link = sdata_dereference(sdata->link[link_id], sdata);
+ if (WARN_ON(!link))
+ goto disconnect;
+
+ link_info(link,
+ "mlo: reconf: local address %pM, AP link address %pM\n",
+ add_links_data->link[link_id].addr,
+ add_links_data->link[link_id].bss->bssid);
+
+ link_sta = rcu_dereference_protected(sta->link[link_id],
+ lockdep_is_held(&local->hw.wiphy->mtx));
+ if (WARN_ON(!link_sta))
+ goto disconnect;
+
+ if (!link->u.mgd.have_beacon) {
+ const struct cfg80211_bss_ies *ies;
+
+ rcu_read_lock();
+ ies = rcu_dereference(cbss->beacon_ies);
+ if (ies)
+ link->u.mgd.have_beacon = true;
+ else
+ ies = rcu_dereference(cbss->ies);
+ ieee80211_get_dtim(ies,
+ &link->conf->sync_dtim_count,
+ &link->u.mgd.dtim_period);
+ link->conf->beacon_int = cbss->beacon_interval;
+ rcu_read_unlock();
+ }
+
+ link->conf->dtim_period = link->u.mgd.dtim_period ?: 1;
+
+ link->u.mgd.conn = add_links_data->link[link_id].conn;
+ if (ieee80211_prep_channel(sdata, link, link_id, cbss,
+ true, &link->u.mgd.conn,
+ sdata->u.mgd.userspace_selectors)) {
+ link_info(link, "mlo: reconf: prep_channel failed\n");
+ goto disconnect;
+ }
+
+ if (ieee80211_mgd_setup_link_sta(link, sta, link_sta,
+ add_links_data->link[link_id].bss))
+ goto disconnect;
+
+ if (!ieee80211_assoc_config_link(link, link_sta,
+ add_links_data->link[link_id].bss,
+ mgmt, pos, len,
+ &changed))
+ goto disconnect;
+
+ /* The AP MLD indicated success for this link, but the station
+ * profile status indicated otherwise. Since there is an
+ * inconsistency in the ML reconfiguration response, disconnect
+ */
+ if (add_links_data->link[link_id].status != WLAN_STATUS_SUCCESS)
+ goto disconnect;
+
+ ieee80211_sta_init_nss(link_sta);
+ if (ieee80211_sta_activate_link(sta, link_id))
+ goto disconnect;
+
+ changed |= ieee80211_link_set_associated(link, cbss);
+ ieee80211_link_info_change_notify(sdata, link, changed);
+
+ ieee80211_recalc_smps(sdata, link);
+ link_mask |= BIT(link_id);
+ }
+
+ sdata_info(sdata,
+ "mlo: reconf: current valid_links=0x%x, added=0x%x\n",
+ valid_links, link_mask);
+
+ /* links might have changed due to rejected ones, set them again */
+ ieee80211_vif_set_links(sdata, valid_links, sdata->vif.dormant_links);
+ ieee80211_vif_cfg_change_notify(sdata, BSS_CHANGED_MLD_VALID_LINKS);
+
+ ieee80211_recalc_ps(local);
+ ieee80211_recalc_ps_vif(sdata);
+
+ done_data.buf = (const u8 *)mgmt;
+ done_data.len = orig_len;
+ done_data.added_links = link_mask;
+
+ for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
+ done_data.links[link_id].bss = add_links_data->link[link_id].bss;
+ done_data.links[link_id].addr =
+ add_links_data->link[link_id].addr;
+ }
+
+ cfg80211_mlo_reconf_add_done(sdata->dev, &done_data);
+ kfree(sdata->u.mgd.reconf.add_links_data);
+ sdata->u.mgd.reconf.add_links_data = NULL;
+out:
+ ieee80211_ml_reconf_reset(sdata);
+ return;
+
+disconnect:
+ __ieee80211_disconnect(sdata);
+}
+
+static struct sk_buff *
+ieee80211_build_ml_reconf_req(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgd_assoc_data *add_links_data,
+ u16 removed_links, __le16 ext_mld_capa_ops)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_mgmt *mgmt;
+ struct ieee80211_multi_link_elem *ml_elem;
+ struct ieee80211_mle_basic_common_info *common;
+ enum nl80211_iftype iftype = ieee80211_vif_type_p2p(&sdata->vif);
+ struct sk_buff *skb;
+ size_t size;
+ unsigned int link_id;
+ __le16 eml_capa = 0, mld_capa_ops = 0;
+ struct ieee80211_tx_info *info;
+ u8 common_size, var_common_size;
+ u8 *ml_elem_len;
+ u16 capab = 0;
+
+ size = local->hw.extra_tx_headroom + sizeof(*mgmt);
+
+ /* Consider the maximal length of the reconfiguration ML element */
+ size += sizeof(struct ieee80211_multi_link_elem);
+
+ /* The Basic ML element and the Reconfiguration ML element have the same
+ * fixed common information fields in the context of ML reconfiguration
+ * action frame. The AP MLD MAC address must always be present
+ */
+ common_size = sizeof(*common);
+
+ /* when adding links, the MLD capabilities must be present */
+ var_common_size = 0;
+ if (add_links_data) {
+ const struct wiphy_iftype_ext_capab *ift_ext_capa =
+ cfg80211_get_iftype_ext_capa(local->hw.wiphy,
+ ieee80211_vif_type_p2p(&sdata->vif));
+
+ if (ift_ext_capa) {
+ eml_capa = cpu_to_le16(ift_ext_capa->eml_capabilities);
+ mld_capa_ops =
+ cpu_to_le16(ift_ext_capa->mld_capa_and_ops);
+ }
+
+ /* MLD capabilities and operation */
+ var_common_size += 2;
+
+ /* EML capabilities */
+ if (eml_capa & cpu_to_le16((IEEE80211_EML_CAP_EMLSR_SUPP |
+ IEEE80211_EML_CAP_EMLMR_SUPPORT)))
+ var_common_size += 2;
+ }
+
+ if (ext_mld_capa_ops)
+ var_common_size += 2;
+
+ /* Add the common information length */
+ size += common_size + var_common_size;
+
+ for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
+ struct cfg80211_bss *cbss;
+ size_t elems_len;
+
+ if (removed_links & BIT(link_id)) {
+ size += sizeof(struct ieee80211_mle_per_sta_profile) +
+ ETH_ALEN;
+ continue;
+ }
+
+ if (!add_links_data || !add_links_data->link[link_id].bss)
+ continue;
+
+ elems_len = add_links_data->link[link_id].elems_len;
+ cbss = add_links_data->link[link_id].bss;
+
+ /* should be the same across all BSSes */
+ if (cbss->capability & WLAN_CAPABILITY_PRIVACY)
+ capab |= WLAN_CAPABILITY_PRIVACY;
+
+ size += 2 + sizeof(struct ieee80211_mle_per_sta_profile) +
+ ETH_ALEN;
+
+ /* WMM */
+ size += 9;
+ size += ieee80211_link_common_elems_size(sdata, iftype, cbss,
+ elems_len);
+ }
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (!skb)
+ return NULL;
+
+ skb_reserve(skb, local->hw.extra_tx_headroom);
+ mgmt = skb_put_zero(skb, offsetofend(struct ieee80211_mgmt,
+ u.action.u.ml_reconf_req));
+
+ /* Add the MAC header */
+ mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION);
+ memcpy(mgmt->da, sdata->vif.cfg.ap_addr, ETH_ALEN);
+ memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+ memcpy(mgmt->bssid, sdata->vif.cfg.ap_addr, ETH_ALEN);
+
+ /* Add the action frame fixed fields */
+ mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_EHT;
+ mgmt->u.action.u.ml_reconf_req.action_code =
+ WLAN_PROTECTED_EHT_ACTION_LINK_RECONFIG_REQ;
+
+ /* allocate a dialog token and store it */
+ sdata->u.mgd.reconf.dialog_token = ++sdata->u.mgd.dialog_token_alloc;
+ mgmt->u.action.u.ml_reconf_req.dialog_token =
+ sdata->u.mgd.reconf.dialog_token;
+
+ /* Add the ML reconfiguration element and the common information */
+ skb_put_u8(skb, WLAN_EID_EXTENSION);
+ ml_elem_len = skb_put(skb, 1);
+ skb_put_u8(skb, WLAN_EID_EXT_EHT_MULTI_LINK);
+ ml_elem = skb_put(skb, sizeof(*ml_elem));
+ ml_elem->control =
+ cpu_to_le16(IEEE80211_ML_CONTROL_TYPE_RECONF |
+ IEEE80211_MLC_RECONF_PRES_MLD_MAC_ADDR);
+ common = skb_put(skb, common_size);
+ common->len = common_size + var_common_size;
+ memcpy(common->mld_mac_addr, sdata->vif.addr, ETH_ALEN);
+
+ if (add_links_data) {
+ if (eml_capa &
+ cpu_to_le16((IEEE80211_EML_CAP_EMLSR_SUPP |
+ IEEE80211_EML_CAP_EMLMR_SUPPORT))) {
+ ml_elem->control |=
+ cpu_to_le16(IEEE80211_MLC_RECONF_PRES_EML_CAPA);
+ skb_put_data(skb, &eml_capa, sizeof(eml_capa));
+ }
+
+ ml_elem->control |=
+ cpu_to_le16(IEEE80211_MLC_RECONF_PRES_MLD_CAPA_OP);
+
+ skb_put_data(skb, &mld_capa_ops, sizeof(mld_capa_ops));
+ }
+
+ if (ext_mld_capa_ops) {
+ ml_elem->control |=
+ cpu_to_le16(IEEE80211_MLC_RECONF_PRES_EXT_MLD_CAPA_OP);
+ skb_put_data(skb, &ext_mld_capa_ops, sizeof(ext_mld_capa_ops));
+ }
+
+ if (sdata->u.mgd.flags & IEEE80211_STA_ENABLE_RRM)
+ capab |= WLAN_CAPABILITY_RADIO_MEASURE;
+
+ /* Add the per station profile */
+ for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
+ u8 *subelem_len = NULL;
+ u16 ctrl;
+ const u8 *addr;
+
+ /* Skip links that are not changing */
+ if (!(removed_links & BIT(link_id)) &&
+ (!add_links_data || !add_links_data->link[link_id].bss))
+ continue;
+
+ ctrl = link_id |
+ IEEE80211_MLE_STA_RECONF_CONTROL_STA_MAC_ADDR_PRESENT;
+
+ if (removed_links & BIT(link_id)) {
+ struct ieee80211_bss_conf *conf =
+ sdata_dereference(sdata->vif.link_conf[link_id],
+ sdata);
+ if (!conf)
+ continue;
+
+ addr = conf->addr;
+ ctrl |= u16_encode_bits(IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_DEL_LINK,
+ IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE);
+ } else {
+ addr = add_links_data->link[link_id].addr;
+ ctrl |= IEEE80211_MLE_STA_RECONF_CONTROL_COMPLETE_PROFILE |
+ u16_encode_bits(IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_ADD_LINK,
+ IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE);
+ }
+
+ skb_put_u8(skb, IEEE80211_MLE_SUBELEM_PER_STA_PROFILE);
+ subelem_len = skb_put(skb, 1);
+
+ put_unaligned_le16(ctrl, skb_put(skb, sizeof(ctrl)));
+ skb_put_u8(skb, 1 + ETH_ALEN);
+ skb_put_data(skb, addr, ETH_ALEN);
+
+ if (!(removed_links & BIT(link_id))) {
+ u16 link_present_elems[PRESENT_ELEMS_MAX] = {};
+ size_t extra_used;
+ void *capab_pos;
+ u8 qos_info;
+
+ capab_pos = skb_put(skb, 2);
+
+ extra_used =
+ ieee80211_add_link_elems(sdata, skb, &capab, NULL,
+ add_links_data->link[link_id].elems,
+ add_links_data->link[link_id].elems_len,
+ link_id, NULL,
+ link_present_elems,
+ add_links_data);
+
+ if (add_links_data->link[link_id].elems)
+ skb_put_data(skb,
+ add_links_data->link[link_id].elems +
+ extra_used,
+ add_links_data->link[link_id].elems_len -
+ extra_used);
+ if (sdata->u.mgd.flags & IEEE80211_STA_UAPSD_ENABLED) {
+ qos_info = sdata->u.mgd.uapsd_queues;
+ qos_info |= (sdata->u.mgd.uapsd_max_sp_len <<
+ IEEE80211_WMM_IE_STA_QOSINFO_SP_SHIFT);
+ } else {
+ qos_info = 0;
+ }
+
+ ieee80211_add_wmm_info_ie(skb_put(skb, 9), qos_info);
+ put_unaligned_le16(capab, capab_pos);
+ }
+
+ ieee80211_fragment_element(skb, subelem_len,
+ IEEE80211_MLE_SUBELEM_FRAGMENT);
+ }
+
+ ieee80211_fragment_element(skb, ml_elem_len, WLAN_EID_FRAGMENT);
+
+ info = IEEE80211_SKB_CB(skb);
+ info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
+
+ return skb;
+}
+
+int ieee80211_mgd_assoc_ml_reconf(struct ieee80211_sub_if_data *sdata,
+ struct cfg80211_ml_reconf_req *req)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_mgd_assoc_data *data = NULL;
+ struct sta_info *sta;
+ struct sk_buff *skb;
+ u16 added_links, new_valid_links;
+ int link_id, err;
+
+ if (!ieee80211_vif_is_mld(&sdata->vif) ||
+ !(sdata->vif.cfg.mld_capa_op &
+ IEEE80211_MLD_CAP_OP_LINK_RECONF_SUPPORT))
+ return -EINVAL;
+
+ /* No support for concurrent ML reconfiguration operation */
+ if (sdata->u.mgd.reconf.added_links ||
+ sdata->u.mgd.reconf.removed_links)
+ return -EBUSY;
+
+ added_links = 0;
+ for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
+ if (!req->add_links[link_id].bss)
+ continue;
+
+ added_links |= BIT(link_id);
+ }
+
+ sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
+ if (WARN_ON(!sta))
+ return -ENOLINK;
+
+ /* Adding links to the set of valid link is done only after a successful
+ * ML reconfiguration frame exchange. Here prepare the data for the ML
+ * reconfiguration frame construction and allocate the required
+ * resources
+ */
+ if (added_links) {
+ bool uapsd_supported;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->assoc_link_id = -1;
+ data->wmm = true;
+
+ uapsd_supported = true;
+ for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS;
+ link_id++) {
+ struct ieee80211_supported_band *sband;
+ struct cfg80211_bss *link_cbss =
+ req->add_links[link_id].bss;
+ struct ieee80211_bss *bss;
+
+ if (!link_cbss)
+ continue;
+
+ bss = (void *)link_cbss->priv;
+
+ if (!bss->wmm_used) {
+ err = -EINVAL;
+ goto err_free;
+ }
+
+ if (link_cbss->channel->band == NL80211_BAND_S1GHZ) {
+ err = -EINVAL;
+ goto err_free;
+ }
+
+ eth_random_addr(data->link[link_id].addr);
+ data->link[link_id].conn =
+ ieee80211_conn_settings_unlimited;
+ sband =
+ local->hw.wiphy->bands[link_cbss->channel->band];
+
+ ieee80211_determine_our_sta_mode(sdata, sband,
+ NULL, true, link_id,
+ &data->link[link_id].conn);
+
+ data->link[link_id].bss = link_cbss;
+ data->link[link_id].disabled =
+ req->add_links[link_id].disabled;
+ data->link[link_id].elems =
+ (u8 *)req->add_links[link_id].elems;
+ data->link[link_id].elems_len =
+ req->add_links[link_id].elems_len;
+
+ if (!bss->uapsd_supported)
+ uapsd_supported = false;
+
+ if (data->link[link_id].conn.mode <
+ IEEE80211_CONN_MODE_EHT) {
+ err = -EINVAL;
+ goto err_free;
+ }
+
+ err = ieee80211_mgd_get_ap_ht_vht_capa(sdata, data,
+ link_id);
+ if (err) {
+ err = -EINVAL;
+ goto err_free;
+ }
+ }
+
+ /* Require U-APSD support if we enabled it */
+ if (sdata->u.mgd.flags & IEEE80211_STA_UAPSD_ENABLED &&
+ !uapsd_supported) {
+ err = -EINVAL;
+ sdata_info(sdata, "U-APSD on but not available on (all) new links\n");
+ goto err_free;
+ }
+
+ for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS;
+ link_id++) {
+ if (!data->link[link_id].bss)
+ continue;
+
+ /* only used to verify the mode, nothing is allocated */
+ err = ieee80211_prep_channel(sdata, NULL, link_id,
+ data->link[link_id].bss,
+ true,
+ &data->link[link_id].conn,
+ sdata->u.mgd.userspace_selectors);
+ if (err)
+ goto err_free;
+ }
+ }
+
+ /* link removal is done before the ML reconfiguration frame exchange so
+ * that these links will not be used between their removal by the AP MLD
+ * and before the station got the ML reconfiguration response. Based on
+ * Section 35.3.6.4 in Draft P802.11be_D7.0 the AP MLD should accept the
+ * link removal request.
+ */
+ if (req->rem_links) {
+ u16 new_active_links =
+ sdata->vif.active_links & ~req->rem_links;
+
+ new_valid_links = sdata->vif.valid_links & ~req->rem_links;
+
+ /* Should not be left with no valid links to perform the
+ * ML reconfiguration
+ */
+ if (!new_valid_links ||
+ !(new_valid_links & ~sdata->vif.dormant_links)) {
+ sdata_info(sdata, "mlo: reconf: no valid links\n");
+ err = -EINVAL;
+ goto err_free;
+ }
+
+ if (new_active_links != sdata->vif.active_links) {
+ if (!new_active_links)
+ new_active_links =
+ BIT(__ffs(new_valid_links &
+ ~sdata->vif.dormant_links));
+
+ err = ieee80211_set_active_links(&sdata->vif,
+ new_active_links);
+ if (err) {
+ sdata_info(sdata,
+ "mlo: reconf: failed set active links\n");
+ goto err_free;
+ }
+ }
+ }
+
+ /* Build the SKB before the link removal as the construction of the
+ * station info for removed links requires the local address.
+ * Invalidate the removed links, so that the transmission of the ML
+ * reconfiguration request frame would not be done using them, as the AP
+ * is expected to send the ML reconfiguration response frame on the link
+ * on which the request was received.
+ */
+ skb = ieee80211_build_ml_reconf_req(sdata, data, req->rem_links,
+ cpu_to_le16(req->ext_mld_capa_ops));
+ if (!skb) {
+ err = -ENOMEM;
+ goto err_free;
+ }
+
+ if (req->rem_links) {
+ u16 new_dormant_links =
+ sdata->vif.dormant_links & ~req->rem_links;
+
+ err = ieee80211_vif_set_links(sdata, new_valid_links,
+ new_dormant_links);
+ if (err) {
+ sdata_info(sdata,
+ "mlo: reconf: failed set valid links\n");
+ kfree_skb(skb);
+ goto err_free;
+ }
+
+ for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS;
+ link_id++) {
+ if (!(req->rem_links & BIT(link_id)))
+ continue;
+
+ ieee80211_sta_remove_link(sta, link_id);
+ }
+
+ /* notify the driver and upper layers */
+ ieee80211_vif_cfg_change_notify(sdata,
+ BSS_CHANGED_MLD_VALID_LINKS);
+ cfg80211_links_removed(sdata->dev, req->rem_links);
+ }
+
+ sdata_info(sdata, "mlo: reconf: adding=0x%x, removed=0x%x\n",
+ added_links, req->rem_links);
+
+ ieee80211_tx_skb(sdata, skb);
+
+ sdata->u.mgd.reconf.added_links = added_links;
+ sdata->u.mgd.reconf.add_links_data = data;
+ sdata->u.mgd.reconf.removed_links = req->rem_links;
+ wiphy_delayed_work_queue(sdata->local->hw.wiphy,
+ &sdata->u.mgd.reconf.wk,
+ IEEE80211_ASSOC_TIMEOUT_SHORT);
+ return 0;
+
+ err_free:
+ kfree(data);
+ return err;
+}
+
+static bool ieee80211_mgd_epcs_supp(struct ieee80211_sub_if_data *sdata)
+{
+ unsigned long valid_links = sdata->vif.valid_links;
+ u8 link_id;
+
+ lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+ if (!ieee80211_vif_is_mld(&sdata->vif))
+ return false;
+
+ for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) {
+ struct ieee80211_bss_conf *bss_conf =
+ sdata_dereference(sdata->vif.link_conf[link_id], sdata);
+
+ if (WARN_ON(!bss_conf) || !bss_conf->epcs_support)
+ return false;
+ }
+
+ return true;
+}
+
+int ieee80211_mgd_set_epcs(struct ieee80211_sub_if_data *sdata, bool enable)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_mgmt *mgmt;
+ struct sk_buff *skb;
+ int frame_len = offsetofend(struct ieee80211_mgmt,
+ u.action.u.epcs) + (enable ? 1 : 0);
+
+ if (!ieee80211_mgd_epcs_supp(sdata))
+ return -EINVAL;
+
+ if (sdata->u.mgd.epcs.enabled == enable &&
+ !sdata->u.mgd.epcs.dialog_token)
+ return 0;
+
+ /* Do not allow enabling EPCS if the AP didn't respond yet.
+ * However, allow disabling EPCS in such a case.
+ */
+ if (sdata->u.mgd.epcs.dialog_token && enable)
+ return -EALREADY;
+
+ skb = dev_alloc_skb(local->hw.extra_tx_headroom + frame_len);
+ if (!skb)
+ return -ENOBUFS;
+
+ skb_reserve(skb, local->hw.extra_tx_headroom);
+ mgmt = skb_put_zero(skb, frame_len);
+ mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION);
+ memcpy(mgmt->da, sdata->vif.cfg.ap_addr, ETH_ALEN);
+ memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+ memcpy(mgmt->bssid, sdata->vif.cfg.ap_addr, ETH_ALEN);
+
+ mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_EHT;
+ if (enable) {
+ u8 *pos = mgmt->u.action.u.epcs.variable;
+
+ mgmt->u.action.u.epcs.action_code =
+ WLAN_PROTECTED_EHT_ACTION_EPCS_ENABLE_REQ;
+
+ *pos = ++sdata->u.mgd.dialog_token_alloc;
+ sdata->u.mgd.epcs.dialog_token = *pos;
+ } else {
+ mgmt->u.action.u.epcs.action_code =
+ WLAN_PROTECTED_EHT_ACTION_EPCS_ENABLE_TEARDOWN;
+
+ ieee80211_epcs_teardown(sdata);
+ ieee80211_epcs_changed(sdata, false);
+ }
+
+ ieee80211_tx_skb(sdata, skb);
+ return 0;
+}
+
+static void ieee80211_ml_epcs(struct ieee80211_sub_if_data *sdata,
+ struct ieee802_11_elems *elems)
+{
+ const struct element *sub;
+ size_t scratch_len = elems->ml_epcs_len;
+ u8 *scratch __free(kfree) = kzalloc(scratch_len, GFP_KERNEL);
+
+ lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+ if (!ieee80211_vif_is_mld(&sdata->vif) || !elems->ml_epcs)
+ return;
+
+ if (WARN_ON(!scratch))
+ return;
+
+ /* Directly parse the sub elements as the common information doesn't
+ * hold any useful information.
+ */
+ for_each_mle_subelement(sub, (const u8 *)elems->ml_epcs,
+ elems->ml_epcs_len) {
+ struct ieee802_11_elems *link_elems __free(kfree) = NULL;
+ struct ieee80211_link_data *link;
+ u8 *pos = (void *)sub->data;
+ u16 control;
+ ssize_t len;
+ u8 link_id;
+
+ if (sub->id != IEEE80211_MLE_SUBELEM_PER_STA_PROFILE)
+ continue;
+
+ if (sub->datalen < sizeof(control))
+ break;
+
+ control = get_unaligned_le16(pos);
+ link_id = control & IEEE80211_MLE_STA_EPCS_CONTROL_LINK_ID;
+
+ link = sdata_dereference(sdata->link[link_id], sdata);
+ if (!link)
+ continue;
+
+ len = cfg80211_defragment_element(sub, (u8 *)elems->ml_epcs,
+ elems->ml_epcs_len,
+ scratch, scratch_len,
+ IEEE80211_MLE_SUBELEM_FRAGMENT);
+ if (len < (ssize_t)sizeof(control))
+ continue;
+
+ pos = scratch + sizeof(control);
+ len -= sizeof(control);
+
+ link_elems = ieee802_11_parse_elems(pos, len,
+ IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION,
+ NULL);
+ if (!link_elems)
+ continue;
+
+ if (ieee80211_sta_wmm_params(sdata->local, link,
+ link_elems->wmm_param,
+ link_elems->wmm_param_len,
+ link_elems->mu_edca_param_set))
+ ieee80211_link_info_change_notify(sdata, link,
+ BSS_CHANGED_QOS);
+ }
+}
+
+void ieee80211_process_epcs_ena_resp(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt, size_t len)
+{
+ struct ieee802_11_elems *elems __free(kfree) = NULL;
+ size_t ies_len;
+ u16 status_code;
+ u8 *pos, dialog_token;
+
+ if (!ieee80211_mgd_epcs_supp(sdata))
+ return;
+
+ /* Handle dialog token and status code */
+ pos = mgmt->u.action.u.epcs.variable;
+ dialog_token = *pos;
+ status_code = get_unaligned_le16(pos + 1);
+
+ /* An EPCS enable response with dialog token == 0 is an unsolicited
+ * notification from the AP MLD. In such a case, EPCS should already be
+ * enabled and status must be success
+ */
+ if (!dialog_token &&
+ (!sdata->u.mgd.epcs.enabled ||
+ status_code != WLAN_STATUS_SUCCESS))
+ return;
+
+ if (sdata->u.mgd.epcs.dialog_token != dialog_token)
+ return;
+
+ sdata->u.mgd.epcs.dialog_token = 0;
+
+ if (status_code != WLAN_STATUS_SUCCESS)
+ return;
+
+ pos += IEEE80211_EPCS_ENA_RESP_BODY_LEN;
+ ies_len = len - offsetof(struct ieee80211_mgmt,
+ u.action.u.epcs.variable) -
+ IEEE80211_EPCS_ENA_RESP_BODY_LEN;
+
+ elems = ieee802_11_parse_elems(pos, ies_len,
+ IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION,
+ NULL);
+ if (!elems)
+ return;
+
+ ieee80211_ml_epcs(sdata, elems);
+ ieee80211_epcs_changed(sdata, true);
+}
+
+void ieee80211_process_epcs_teardown(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt, size_t len)
+{
+ if (!ieee80211_vif_is_mld(&sdata->vif) ||
+ !sdata->u.mgd.epcs.enabled)
+ return;
+
+ ieee80211_epcs_teardown(sdata);
+ ieee80211_epcs_changed(sdata, false);
+}