Commit 6911458d authored by Johannes Berg's avatar Johannes Berg

wifi: mac80211: mlme: refactor assoc success handling

Refactor the per-link setup out of ieee80211_assoc_success()
into a new function ieee80211_assoc_config_link().

It looks useless for now to parse the elements again inside
ieee80211_assoc_config_link(), but that will be done with
the link ID in the future.
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 7781f0d8
......@@ -3541,894 +3541,928 @@ static bool ieee80211_twt_bcast_support(struct ieee80211_sub_if_data *sdata,
IEEE80211_HE_MAC_CAP2_BCAST_TWT);
}
static int ieee80211_mgd_setup_link_sta(struct ieee80211_link_data *link,
struct sta_info *sta,
struct ieee80211_link_sta *link_sta,
struct cfg80211_bss *cbss)
static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
struct link_sta_info *link_sta,
struct cfg80211_bss *cbss,
struct ieee80211_mgmt *mgmt,
const u8 *elem_start,
unsigned int elem_len,
u64 *changed)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_mgd_assoc_data *assoc_data = sdata->u.mgd.assoc_data;
struct ieee80211_bss_conf *bss_conf = link->conf;
struct ieee80211_local *local = sdata->local;
struct ieee80211_bss *bss = (void *)cbss->priv;
u32 rates = 0, basic_rates = 0;
bool have_higher_than_11mbit = false;
int min_rate = INT_MAX, min_rate_index = -1;
/* this is clearly wrong for MLO but we'll just remove it later */
int shift = ieee80211_vif_get_shift(&sdata->vif);
struct ieee80211_elems_parse_params parse_params = {
.start = elem_start,
.len = elem_len,
.bss = cbss,
};
bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ;
bool is_s1g = cbss->channel->band == NL80211_BAND_S1GHZ;
const struct cfg80211_bss_ies *bss_ies = NULL;
struct ieee80211_supported_band *sband;
struct ieee802_11_elems *elems;
u16 capab_info;
bool ret;
memcpy(link_sta->addr, cbss->bssid, ETH_ALEN);
elems = ieee802_11_parse_elems_full(&parse_params);
if (!elems)
return false;
/* TODO: S1G Basic Rate Set is expressed elsewhere */
if (cbss->channel->band == NL80211_BAND_S1GHZ) {
ieee80211_s1g_sta_rate_init(sta);
return 0;
}
capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info);
sband = local->hw.wiphy->bands[cbss->channel->band];
if (!is_s1g && !elems->supp_rates) {
sdata_info(sdata, "no SuppRates element in AssocResp\n");
ret = false;
goto out;
}
ieee80211_get_rates(sband, bss->supp_rates, bss->supp_rates_len,
&rates, &basic_rates, &have_higher_than_11mbit,
&min_rate, &min_rate_index, shift);
link->u.mgd.tdls_chan_switch_prohibited =
elems->ext_capab && elems->ext_capab_len >= 5 &&
(elems->ext_capab[4] & WLAN_EXT_CAPA5_TDLS_CH_SW_PROHIBITED);
/*
* This used to be a workaround for basic rates missing
* in the association response frame. Now that we no
* longer use the basic rates from there, it probably
* doesn't happen any more, but keep the workaround so
* in case some *other* APs are buggy in different ways
* we can connect -- with a warning.
* Allow this workaround only in case the AP provided at least
* one rate.
* Some APs are erroneously not including some information in their
* (re)association response frames. Try to recover by using the data
* from the beacon or probe response. This seems to afflict mobile
* 2G/3G/4G wifi routers, reported models include the "Onda PN51T",
* "Vodafone PocketWiFi 2", "ZTE MF60" and a similar T-Mobile device.
*/
if (min_rate_index < 0) {
link_info(link, "No legacy rates in association response\n");
return -EINVAL;
} else if (!basic_rates) {
link_info(link, "No basic rates, using min rate instead\n");
basic_rates = BIT(min_rate_index);
}
if (rates)
link_sta->supp_rates[cbss->channel->band] = rates;
else
link_info(link, "No rates found, keeping mandatory only\n");
link->conf->basic_rates = basic_rates;
/* cf. IEEE 802.11 9.2.12 */
link->operating_11g_mode = sband->band == NL80211_BAND_2GHZ &&
have_higher_than_11mbit;
return 0;
}
static u8 ieee80211_max_rx_chains(struct ieee80211_link_data *link,
struct cfg80211_bss *cbss)
{
struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp;
const struct element *ht_cap_elem, *vht_cap_elem;
if (!is_6ghz &&
((assoc_data->wmm && !elems->wmm_param) ||
(!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) &&
(!elems->ht_cap_elem || !elems->ht_operation)) ||
(!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) &&
(!elems->vht_cap_elem || !elems->vht_operation)))) {
const struct cfg80211_bss_ies *ies;
const struct ieee80211_ht_cap *ht_cap;
const struct ieee80211_vht_cap *vht_cap;
const struct ieee80211_he_cap_elem *he_cap;
const struct element *he_cap_elem;
u16 mcs_80_map, mcs_160_map;
int i, mcs_nss_size;
bool support_160;
u8 chains = 1;
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)
return chains;
struct ieee802_11_elems *bss_elems;
ht_cap_elem = ieee80211_bss_get_elem(cbss, WLAN_EID_HT_CAPABILITY);
if (ht_cap_elem && ht_cap_elem->datalen >= sizeof(*ht_cap)) {
ht_cap = (void *)ht_cap_elem->data;
chains = ieee80211_mcs_to_chains(&ht_cap->mcs);
/*
* TODO: use "Tx Maximum Number Spatial Streams Supported" and
* "Tx Unequal Modulation Supported" fields.
*/
rcu_read_lock();
ies = rcu_dereference(cbss->ies);
if (ies)
bss_ies = kmemdup(ies, sizeof(*ies) + ies->len,
GFP_ATOMIC);
rcu_read_unlock();
if (!bss_ies) {
ret = false;
goto out;
}
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT)
return chains;
vht_cap_elem = ieee80211_bss_get_elem(cbss, WLAN_EID_VHT_CAPABILITY);
if (vht_cap_elem && vht_cap_elem->datalen >= sizeof(*vht_cap)) {
u8 nss;
u16 tx_mcs_map;
vht_cap = (void *)vht_cap_elem->data;
tx_mcs_map = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map);
for (nss = 8; nss > 0; nss--) {
if (((tx_mcs_map >> (2 * (nss - 1))) & 3) !=
IEEE80211_VHT_MCS_NOT_SUPPORTED)
break;
}
/* TODO: use "Tx Highest Supported Long GI Data Rate" field? */
chains = max(chains, nss);
parse_params.start = bss_ies->data;
parse_params.len = bss_ies->len;
bss_elems = ieee802_11_parse_elems_full(&parse_params);
if (!bss_elems) {
ret = false;
goto out;
}
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE)
return chains;
ies = rcu_dereference(cbss->ies);
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))
return chains;
/* skip one byte ext_tag_id */
he_cap = (void *)(he_cap_elem->data + 1);
mcs_nss_size = ieee80211_he_mcs_nss_size(he_cap);
/* invalid HE IE */
if (he_cap_elem->datalen < 1 + mcs_nss_size + sizeof(*he_cap))
return chains;
/* mcs_nss is right after he_cap info */
he_mcs_nss_supp = (void *)(he_cap + 1);
mcs_80_map = le16_to_cpu(he_mcs_nss_supp->tx_mcs_80);
for (i = 7; i >= 0; i--) {
u8 mcs_80 = mcs_80_map >> (2 * i) & 3;
if (assoc_data->wmm &&
!elems->wmm_param && bss_elems->wmm_param) {
elems->wmm_param = bss_elems->wmm_param;
sdata_info(sdata,
"AP bug: WMM param missing from AssocResp\n");
}
if (mcs_80 != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
chains = max_t(u8, chains, i + 1);
break;
/*
* Also check if we requested HT/VHT, otherwise the AP doesn't
* have to include the IEs in the (re)association response.
*/
if (!elems->ht_cap_elem && bss_elems->ht_cap_elem &&
!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)) {
elems->ht_cap_elem = bss_elems->ht_cap_elem;
sdata_info(sdata,
"AP bug: HT capability missing from AssocResp\n");
}
if (!elems->ht_operation && bss_elems->ht_operation &&
!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)) {
elems->ht_operation = bss_elems->ht_operation;
sdata_info(sdata,
"AP bug: HT operation missing from AssocResp\n");
}
support_160 = he_cap->phy_cap_info[0] &
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
if (!support_160)
return chains;
mcs_160_map = le16_to_cpu(he_mcs_nss_supp->tx_mcs_160);
for (i = 7; i >= 0; i--) {
u8 mcs_160 = mcs_160_map >> (2 * i) & 3;
if (mcs_160 != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
chains = max_t(u8, chains, i + 1);
break;
if (!elems->vht_cap_elem && bss_elems->vht_cap_elem &&
!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_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_flags & IEEE80211_CONN_DISABLE_VHT)) {
elems->vht_operation = bss_elems->vht_operation;
sdata_info(sdata,
"AP bug: VHT operation missing from AssocResp\n");
}
return chains;
}
static bool
ieee80211_verify_peer_he_mcs_support(struct ieee80211_sub_if_data *sdata,
const struct cfg80211_bss_ies *ies,
const struct ieee80211_he_operation *he_op)
{
const struct element *he_cap_elem;
const struct ieee80211_he_cap_elem *he_cap;
struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp;
u16 mcs_80_map_tx, mcs_80_map_rx;
u16 ap_min_req_set;
int mcs_nss_size;
int nss;
he_cap_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY,
ies->data, ies->len);
kfree(bss_elems);
}
/* invalid HE IE */
if (!he_cap_elem || he_cap_elem->datalen < 1 + sizeof(*he_cap)) {
/*
* We previously checked these in the beacon/probe response, so
* they should be present here. This is just a safety net.
*/
if (!is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) &&
(!elems->wmm_param || !elems->ht_cap_elem || !elems->ht_operation)) {
sdata_info(sdata,
"Invalid HE elem, Disable HE\n");
return false;
"HT AP is missing WMM params or HT capability/operation\n");
ret = false;
goto out;
}
/* skip one byte ext_tag_id */
he_cap = (void *)(he_cap_elem->data + 1);
mcs_nss_size = ieee80211_he_mcs_nss_size(he_cap);
/* invalid HE IE */
if (he_cap_elem->datalen < 1 + sizeof(*he_cap) + mcs_nss_size) {
if (!is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) &&
(!elems->vht_cap_elem || !elems->vht_operation)) {
sdata_info(sdata,
"Invalid HE elem with nss size, Disable HE\n");
return false;
"VHT AP is missing VHT capability/operation\n");
ret = false;
goto out;
}
/* mcs_nss is right after he_cap info */
he_mcs_nss_supp = (void *)(he_cap + 1);
if (is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) &&
!elems->he_6ghz_capa) {
sdata_info(sdata,
"HE 6 GHz AP is missing HE 6 GHz band capability\n");
ret = false;
goto out;
}
mcs_80_map_tx = le16_to_cpu(he_mcs_nss_supp->tx_mcs_80);
mcs_80_map_rx = le16_to_cpu(he_mcs_nss_supp->rx_mcs_80);
sband = ieee80211_get_link_sband(link);
if (!sband) {
ret = false;
goto out;
}
/* P802.11-REVme/D0.3
* 27.1.1 Introduction to the HE PHY
* ...
* An HE STA shall support the following features:
* ...
* Single spatial stream HE-MCSs 0 to 7 (transmit and receive) in all
* supported channel widths for HE SU PPDUs
*/
if ((mcs_80_map_tx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED ||
(mcs_80_map_rx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED) {
if (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) &&
(!elems->he_cap || !elems->he_operation)) {
mutex_unlock(&sdata->local->sta_mtx);
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);
return false;
"HE AP is missing HE capability/operation\n");
ret = false;
goto out;
}
if (!he_op)
return true;
/* Set up internal HT/VHT capabilities */
if (elems->ht_cap_elem && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT))
ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
elems->ht_cap_elem,
link_sta);
ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set);
if (elems->vht_cap_elem && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT))
ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
elems->vht_cap_elem,
link_sta);
/*
* Apparently iPhone 13 (at least iOS version 15.3.1) sets this to all
* 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)
return true;
if (elems->he_operation && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) &&
elems->he_cap) {
ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband,
elems->he_cap,
elems->he_cap_len,
elems->he_6ghz_capa,
link_sta);
/* make sure the AP is consistent with itself
*
* P802.11-REVme/D0.3
* 26.17.1 Basic HE BSS operation
*
* A STA that is operating in an HE BSS shall be able to receive and
* transmit at each of the <HE-MCS, NSS> tuple values indicated by the
* Basic HE-MCS And NSS Set field of the HE Operation parameter of the
* MLME-START.request primitive and shall be able to receive at each of
* the <HE-MCS, NSS> tuple values indicated by the Supported HE-MCS and
* NSS Set field in the HE Capabilities parameter of the MLMESTART.request
* primitive
*/
for (nss = 8; nss > 0; nss--) {
u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3;
u8 ap_rx_val;
u8 ap_tx_val;
bss_conf->he_support = link_sta->pub->he_cap.has_he;
if (elems->rsnx && elems->rsnx_len &&
(elems->rsnx[0] & WLAN_RSNX_CAPA_PROTECTED_TWT) &&
wiphy_ext_feature_isset(local->hw.wiphy,
NL80211_EXT_FEATURE_PROTECTED_TWT))
bss_conf->twt_protected = true;
else
bss_conf->twt_protected = false;
if (ap_op_val == IEEE80211_HE_MCS_NOT_SUPPORTED)
continue;
*changed |= ieee80211_recalc_twt_req(link, link_sta, elems);
ap_rx_val = (mcs_80_map_rx >> (2 * (nss - 1))) & 3;
ap_tx_val = (mcs_80_map_tx >> (2 * (nss - 1))) & 3;
if (elems->eht_operation && elems->eht_cap &&
!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_EHT)) {
ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband,
elems->he_cap,
elems->he_cap_len,
elems->eht_cap,
elems->eht_cap_len,
link_sta);
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);
return false;
bss_conf->eht_support = link_sta->pub->eht_cap.has_eht;
} else {
bss_conf->eht_support = false;
}
} else {
bss_conf->he_support = false;
bss_conf->twt_requester = false;
bss_conf->twt_protected = false;
bss_conf->eht_support = false;
}
return true;
}
bss_conf->twt_broadcast =
ieee80211_twt_bcast_support(sdata, bss_conf, sband, link_sta);
static bool
ieee80211_verify_sta_he_mcs_support(struct ieee80211_sub_if_data *sdata,
struct ieee80211_supported_band *sband,
const struct ieee80211_he_operation *he_op)
{
const struct ieee80211_sta_he_cap *sta_he_cap =
ieee80211_get_he_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif));
u16 ap_min_req_set;
int i;
if (bss_conf->he_support) {
bss_conf->he_bss_color.color =
le32_get_bits(elems->he_operation->he_oper_params,
IEEE80211_HE_OPERATION_BSS_COLOR_MASK);
bss_conf->he_bss_color.partial =
le32_get_bits(elems->he_operation->he_oper_params,
IEEE80211_HE_OPERATION_PARTIAL_BSS_COLOR);
bss_conf->he_bss_color.enabled =
!le32_get_bits(elems->he_operation->he_oper_params,
IEEE80211_HE_OPERATION_BSS_COLOR_DISABLED);
if (!sta_he_cap || !he_op)
return false;
if (bss_conf->he_bss_color.enabled)
*changed |= BSS_CHANGED_HE_BSS_COLOR;
ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set);
bss_conf->htc_trig_based_pkt_ext =
le32_get_bits(elems->he_operation->he_oper_params,
IEEE80211_HE_OPERATION_DFLT_PE_DURATION_MASK);
bss_conf->frame_time_rts_th =
le32_get_bits(elems->he_operation->he_oper_params,
IEEE80211_HE_OPERATION_RTS_THRESHOLD_MASK);
bss_conf->uora_exists = !!elems->uora_element;
if (elems->uora_element)
bss_conf->uora_ocw_range = elems->uora_element[0];
ieee80211_he_op_ie_to_bss_conf(&sdata->vif, elems->he_operation);
ieee80211_he_spr_ie_to_bss_conf(&sdata->vif, elems->he_spr);
/* TODO: OPEN: what happens if BSS color disable is set? */
}
if (cbss->transmitted_bss) {
bss_conf->nontransmitted = true;
ether_addr_copy(bss_conf->transmitter_bssid,
cbss->transmitted_bss->bssid);
bss_conf->bssid_indicator = cbss->max_bssid_indicator;
bss_conf->bssid_index = cbss->bssid_index;
}
/*
* Apparently iPhone 13 (at least iOS version 15.3.1) sets this to all
* zeroes, which is nonsense, and completely inconsistent with itself
* (it doesn't have 8 streams). Accept the settings in this case anyway.
* Some APs, e.g. Netgear WNDR3700, report invalid HT operation data
* in their association response, so ignore that data for our own
* configuration. If it changed since the last beacon, we'll get the
* next beacon and update then.
*/
if (!ap_min_req_set)
return true;
/* Need to go over for 80MHz, 160MHz and for 80+80 */
for (i = 0; i < 3; i++) {
const struct ieee80211_he_mcs_nss_supp *sta_mcs_nss_supp =
&sta_he_cap->he_mcs_nss_supp;
u16 sta_mcs_map_rx =
le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i]);
u16 sta_mcs_map_tx =
le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i + 1]);
u8 nss;
bool verified = true;
/*
* For each band there is a maximum of 8 spatial streams
* possible. Each of the sta_mcs_map_* is a 16-bit struct built
* of 2 bits per NSS (1-8), with the values defined in enum
* ieee80211_he_mcs_support. Need to make sure STA TX and RX
* capabilities aren't less than the AP's minimum requirements
* for this HE BSS per SS.
* It is enough to find one such band that meets the reqs.
* If an operating mode notification IE is present, override the
* NSS calculation (that would be done in rate_control_rate_init())
* and use the # of streams from that element.
*/
for (nss = 8; nss > 0; nss--) {
u8 sta_rx_val = (sta_mcs_map_rx >> (2 * (nss - 1))) & 3;
u8 sta_tx_val = (sta_mcs_map_tx >> (2 * (nss - 1))) & 3;
u8 ap_val = (ap_min_req_set >> (2 * (nss - 1))) & 3;
if (elems->opmode_notif &&
!(*elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)) {
u8 nss;
if (ap_val == IEEE80211_HE_MCS_NOT_SUPPORTED)
continue;
nss = *elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_MASK;
nss >>= IEEE80211_OPMODE_NOTIF_RX_NSS_SHIFT;
nss += 1;
link_sta->pub->rx_nss = nss;
}
/*
* Make sure the HE AP doesn't require MCSs that aren't
* supported by the client as required by spec
*
* P802.11-REVme/D0.3
* 26.17.1 Basic HE BSS operation
*
* An HE STA shall not attempt to join * (MLME-JOIN.request primitive)
* a BSS, unless it supports (i.e., is able to both transmit and
* receive using) all of the <HE-MCS, NSS> tuples in the basic
* HE-MCS and NSS set.
* Always handle WMM once after association regardless
* of the first value the AP uses. Setting -1 here has
* that effect because the AP values is an unsigned
* 4-bit value.
*/
link->u.mgd.wmm_last_param_set = -1;
link->u.mgd.mu_edca_last_param_set = -1;
if (link->u.mgd.disable_wmm_tracking) {
ieee80211_set_wmm_default(link, false, false);
} else if (!ieee80211_sta_wmm_params(local, link, elems->wmm_param,
elems->wmm_param_len,
elems->mu_edca_param_set)) {
/* still enable QoS since we might have HT/VHT */
ieee80211_set_wmm_default(link, false, true);
/* disable WMM tracking in this case to disable
* tracking WMM parameter changes in the beacon if
* the parameters weren't actually valid. Doing so
* avoids changing parameters very strangely when
* the AP is going back and forth between valid and
* invalid parameters.
*/
if (sta_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
sta_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
(ap_val > sta_rx_val) || (ap_val > sta_tx_val)) {
verified = false;
break;
}
link->u.mgd.disable_wmm_tracking = true;
}
if (verified)
return true;
if (elems->max_idle_period_ie) {
bss_conf->max_idle_period =
le16_to_cpu(elems->max_idle_period_ie->max_idle_period);
bss_conf->protected_keep_alive =
!!(elems->max_idle_period_ie->idle_options &
WLAN_IDLE_OPTIONS_PROTECTED_KEEP_ALIVE);
*changed |= BSS_CHANGED_KEEP_ALIVE;
} else {
bss_conf->max_idle_period = 0;
bss_conf->protected_keep_alive = false;
}
/* If here, STA doesn't meet AP's HE min requirements */
return false;
/* set assoc capability (AID was already set earlier),
* ieee80211_set_associated() will tell the driver */
bss_conf->assoc_capability = capab_info;
ret = true;
out:
kfree(elems);
kfree(bss_ies);
return ret;
}
static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
struct cfg80211_bss *cbss,
ieee80211_conn_flags_t *conn_flags)
static int ieee80211_mgd_setup_link_sta(struct ieee80211_link_data *link,
struct sta_info *sta,
struct ieee80211_link_sta *link_sta,
struct cfg80211_bss *cbss)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_local *local = sdata->local;
const struct ieee80211_ht_cap *ht_cap = NULL;
const struct ieee80211_ht_operation *ht_oper = NULL;
const struct ieee80211_vht_operation *vht_oper = NULL;
const struct ieee80211_he_operation *he_oper = NULL;
const struct ieee80211_eht_operation *eht_oper = NULL;
const struct ieee80211_s1g_oper_ie *s1g_oper = NULL;
struct ieee80211_supported_band *sband;
struct cfg80211_chan_def chandef;
bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ;
bool is_5ghz = cbss->channel->band == NL80211_BAND_5GHZ;
struct ieee80211_bss *bss = (void *)cbss->priv;
struct ieee802_11_elems *elems;
const struct cfg80211_bss_ies *ies;
int ret;
u32 i;
bool have_80mhz;
u32 rates = 0, basic_rates = 0;
bool have_higher_than_11mbit = false;
int min_rate = INT_MAX, min_rate_index = -1;
/* this is clearly wrong for MLO but we'll just remove it later */
int shift = ieee80211_vif_get_shift(&sdata->vif);
struct ieee80211_supported_band *sband;
rcu_read_lock();
memcpy(link_sta->addr, cbss->bssid, ETH_ALEN);
ies = rcu_dereference(cbss->ies);
elems = ieee802_11_parse_elems(ies->data, ies->len, false, cbss);
if (!elems) {
rcu_read_unlock();
return -ENOMEM;
/* TODO: S1G Basic Rate Set is expressed elsewhere */
if (cbss->channel->band == NL80211_BAND_S1GHZ) {
ieee80211_s1g_sta_rate_init(sta);
return 0;
}
sband = local->hw.wiphy->bands[cbss->channel->band];
*conn_flags &= ~(IEEE80211_CONN_DISABLE_40MHZ |
IEEE80211_CONN_DISABLE_80P80MHZ |
IEEE80211_CONN_DISABLE_160MHZ);
ieee80211_get_rates(sband, bss->supp_rates, bss->supp_rates_len,
&rates, &basic_rates, &have_higher_than_11mbit,
&min_rate, &min_rate_index, shift);
/* disable HT/VHT/HE if we don't support them */
if (!sband->ht_cap.ht_supported && !is_6ghz) {
mlme_dbg(sdata, "HT not supported, disabling HT/VHT/HE/EHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_HT;
*conn_flags |= IEEE80211_CONN_DISABLE_VHT;
*conn_flags |= IEEE80211_CONN_DISABLE_HE;
*conn_flags |= IEEE80211_CONN_DISABLE_EHT;
/*
* This used to be a workaround for basic rates missing
* in the association response frame. Now that we no
* longer use the basic rates from there, it probably
* doesn't happen any more, but keep the workaround so
* in case some *other* APs are buggy in different ways
* we can connect -- with a warning.
* Allow this workaround only in case the AP provided at least
* one rate.
*/
if (min_rate_index < 0) {
link_info(link, "No legacy rates in association response\n");
return -EINVAL;
} else if (!basic_rates) {
link_info(link, "No basic rates, using min rate instead\n");
basic_rates = BIT(min_rate_index);
}
if (!sband->vht_cap.vht_supported && is_5ghz) {
mlme_dbg(sdata, "VHT not supported, disabling VHT/HE/EHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_VHT;
*conn_flags |= IEEE80211_CONN_DISABLE_HE;
*conn_flags |= IEEE80211_CONN_DISABLE_EHT;
}
if (rates)
link_sta->supp_rates[cbss->channel->band] = rates;
else
link_info(link, "No rates found, keeping mandatory only\n");
if (!ieee80211_get_he_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif))) {
mlme_dbg(sdata, "HE not supported, disabling HE and EHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_HE;
*conn_flags |= IEEE80211_CONN_DISABLE_EHT;
}
link->conf->basic_rates = basic_rates;
if (!ieee80211_get_eht_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif))) {
mlme_dbg(sdata, "EHT not supported, disabling EHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_EHT;
}
/* cf. IEEE 802.11 9.2.12 */
link->operating_11g_mode = sband->band == NL80211_BAND_2GHZ &&
have_higher_than_11mbit;
if (!(*conn_flags & IEEE80211_CONN_DISABLE_HT) && !is_6ghz) {
ht_oper = elems->ht_operation;
ht_cap = elems->ht_cap_elem;
return 0;
}
if (!ht_cap) {
*conn_flags |= IEEE80211_CONN_DISABLE_HT;
ht_oper = NULL;
}
}
static u8 ieee80211_max_rx_chains(struct ieee80211_link_data *link,
struct cfg80211_bss *cbss)
{
struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp;
const struct element *ht_cap_elem, *vht_cap_elem;
const struct cfg80211_bss_ies *ies;
const struct ieee80211_ht_cap *ht_cap;
const struct ieee80211_vht_cap *vht_cap;
const struct ieee80211_he_cap_elem *he_cap;
const struct element *he_cap_elem;
u16 mcs_80_map, mcs_160_map;
int i, mcs_nss_size;
bool support_160;
u8 chains = 1;
if (!(*conn_flags & IEEE80211_CONN_DISABLE_VHT) && !is_6ghz) {
vht_oper = elems->vht_operation;
if (vht_oper && !ht_oper) {
vht_oper = NULL;
sdata_info(sdata,
"AP advertised VHT without HT, disabling HT/VHT/HE\n");
*conn_flags |= IEEE80211_CONN_DISABLE_HT;
*conn_flags |= IEEE80211_CONN_DISABLE_VHT;
*conn_flags |= IEEE80211_CONN_DISABLE_HE;
*conn_flags |= IEEE80211_CONN_DISABLE_EHT;
}
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)
return chains;
if (!elems->vht_cap_elem) {
sdata_info(sdata,
"bad VHT capabilities, disabling VHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_VHT;
vht_oper = NULL;
}
ht_cap_elem = ieee80211_bss_get_elem(cbss, WLAN_EID_HT_CAPABILITY);
if (ht_cap_elem && ht_cap_elem->datalen >= sizeof(*ht_cap)) {
ht_cap = (void *)ht_cap_elem->data;
chains = ieee80211_mcs_to_chains(&ht_cap->mcs);
/*
* TODO: use "Tx Maximum Number Spatial Streams Supported" and
* "Tx Unequal Modulation Supported" fields.
*/
}
if (!(*conn_flags & IEEE80211_CONN_DISABLE_HE)) {
he_oper = elems->he_operation;
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT)
return chains;
if (link && is_6ghz) {
struct ieee80211_bss_conf *bss_conf;
u8 j = 0;
vht_cap_elem = ieee80211_bss_get_elem(cbss, WLAN_EID_VHT_CAPABILITY);
if (vht_cap_elem && vht_cap_elem->datalen >= sizeof(*vht_cap)) {
u8 nss;
u16 tx_mcs_map;
bss_conf = link->conf;
vht_cap = (void *)vht_cap_elem->data;
tx_mcs_map = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map);
for (nss = 8; nss > 0; nss--) {
if (((tx_mcs_map >> (2 * (nss - 1))) & 3) !=
IEEE80211_VHT_MCS_NOT_SUPPORTED)
break;
}
/* TODO: use "Tx Highest Supported Long GI Data Rate" field? */
chains = max(chains, nss);
}
if (elems->pwr_constr_elem)
bss_conf->pwr_reduction = *elems->pwr_constr_elem;
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE)
return chains;
BUILD_BUG_ON(ARRAY_SIZE(bss_conf->tx_pwr_env) !=
ARRAY_SIZE(elems->tx_pwr_env));
ies = rcu_dereference(cbss->ies);
he_cap_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY,
ies->data, ies->len);
for (i = 0; i < elems->tx_pwr_env_num; i++) {
if (elems->tx_pwr_env_len[i] >
sizeof(bss_conf->tx_pwr_env[j]))
continue;
if (!he_cap_elem || he_cap_elem->datalen < sizeof(*he_cap))
return chains;
bss_conf->tx_pwr_env_num++;
memcpy(&bss_conf->tx_pwr_env[j], elems->tx_pwr_env[i],
elems->tx_pwr_env_len[i]);
j++;
}
}
/* skip one byte ext_tag_id */
he_cap = (void *)(he_cap_elem->data + 1);
mcs_nss_size = ieee80211_he_mcs_nss_size(he_cap);
if (!ieee80211_verify_peer_he_mcs_support(sdata, ies, he_oper) ||
!ieee80211_verify_sta_he_mcs_support(sdata, sband, he_oper))
*conn_flags |= IEEE80211_CONN_DISABLE_HE |
IEEE80211_CONN_DISABLE_EHT;
}
/* invalid HE IE */
if (he_cap_elem->datalen < 1 + mcs_nss_size + sizeof(*he_cap))
return chains;
/*
* EHT requires HE to be supported as well. Specifically for 6 GHz
* channels, the operation channel information can only be deduced from
* both the 6 GHz operation information (from the HE operation IE) and
* EHT operation.
*/
if (!(*conn_flags &
(IEEE80211_CONN_DISABLE_HE |
IEEE80211_CONN_DISABLE_EHT)) &&
he_oper) {
const struct cfg80211_bss_ies *cbss_ies;
const u8 *eht_oper_ie;
/* mcs_nss is right after he_cap info */
he_mcs_nss_supp = (void *)(he_cap + 1);
mcs_80_map = le16_to_cpu(he_mcs_nss_supp->tx_mcs_80);
cbss_ies = rcu_dereference(cbss->ies);
eht_oper_ie = cfg80211_find_ext_ie(WLAN_EID_EXT_EHT_OPERATION,
cbss_ies->data, cbss_ies->len);
if (eht_oper_ie && eht_oper_ie[1] >=
1 + sizeof(struct ieee80211_eht_operation))
eht_oper = (void *)(eht_oper_ie + 3);
else
eht_oper = NULL;
for (i = 7; i >= 0; i--) {
u8 mcs_80 = mcs_80_map >> (2 * i) & 3;
if (mcs_80 != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
chains = max_t(u8, chains, i + 1);
break;
}
}
/* Allow VHT if at least one channel on the sband supports 80 MHz */
have_80mhz = false;
for (i = 0; i < sband->n_channels; i++) {
if (sband->channels[i].flags & (IEEE80211_CHAN_DISABLED |
IEEE80211_CHAN_NO_80MHZ))
continue;
support_160 = he_cap->phy_cap_info[0] &
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
have_80mhz = true;
if (!support_160)
return chains;
mcs_160_map = le16_to_cpu(he_mcs_nss_supp->tx_mcs_160);
for (i = 7; i >= 0; i--) {
u8 mcs_160 = mcs_160_map >> (2 * i) & 3;
if (mcs_160 != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
chains = max_t(u8, chains, i + 1);
break;
}
if (!have_80mhz) {
sdata_info(sdata, "80 MHz not supported, disabling VHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_VHT;
}
if (sband->band == NL80211_BAND_S1GHZ) {
s1g_oper = elems->s1g_oper;
if (!s1g_oper)
return chains;
}
static bool
ieee80211_verify_peer_he_mcs_support(struct ieee80211_sub_if_data *sdata,
const struct cfg80211_bss_ies *ies,
const struct ieee80211_he_operation *he_op)
{
const struct element *he_cap_elem;
const struct ieee80211_he_cap_elem *he_cap;
struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp;
u16 mcs_80_map_tx, mcs_80_map_rx;
u16 ap_min_req_set;
int mcs_nss_size;
int nss;
he_cap_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY,
ies->data, ies->len);
/* invalid HE IE */
if (!he_cap_elem || he_cap_elem->datalen < 1 + sizeof(*he_cap)) {
sdata_info(sdata,
"AP missing S1G operation element?\n");
"Invalid HE elem, Disable HE\n");
return false;
}
*conn_flags |=
ieee80211_determine_chantype(sdata, link, *conn_flags,
sband,
cbss->channel,
bss->vht_cap_info,
ht_oper, vht_oper,
he_oper, eht_oper,
s1g_oper,
&chandef, false);
/* skip one byte ext_tag_id */
he_cap = (void *)(he_cap_elem->data + 1);
mcs_nss_size = ieee80211_he_mcs_nss_size(he_cap);
if (link)
link->needed_rx_chains =
min(ieee80211_max_rx_chains(link, cbss),
local->rx_chains);
/* invalid HE IE */
if (he_cap_elem->datalen < 1 + sizeof(*he_cap) + mcs_nss_size) {
sdata_info(sdata,
"Invalid HE elem with nss size, Disable HE\n");
return false;
}
rcu_read_unlock();
/* the element data was RCU protected so no longer valid anyway */
kfree(elems);
elems = NULL;
/* mcs_nss is right after he_cap info */
he_mcs_nss_supp = (void *)(he_cap + 1);
if (*conn_flags & IEEE80211_CONN_DISABLE_HE && is_6ghz) {
sdata_info(sdata, "Rejecting non-HE 6/7 GHz connection");
return -EINVAL;
mcs_80_map_tx = le16_to_cpu(he_mcs_nss_supp->tx_mcs_80);
mcs_80_map_rx = le16_to_cpu(he_mcs_nss_supp->rx_mcs_80);
/* P802.11-REVme/D0.3
* 27.1.1 Introduction to the HE PHY
* ...
* An HE STA shall support the following features:
* ...
* Single spatial stream HE-MCSs 0 to 7 (transmit and receive) in all
* supported channel widths for HE SU PPDUs
*/
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);
return false;
}
if (!link)
return 0;
if (!he_op)
return true;
/* will change later if needed */
link->smps_mode = IEEE80211_SMPS_OFF;
ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set);
mutex_lock(&local->mtx);
/*
* If this fails (possibly due to channel context sharing
* on incompatible channels, e.g. 80+80 and 160 sharing the
* same control channel) try to use a smaller bandwidth.
* Apparently iPhone 13 (at least iOS version 15.3.1) sets this to all
* zeroes, which is nonsense, and completely inconsistent with itself
* (it doesn't have 8 streams). Accept the settings in this case anyway.
*/
ret = ieee80211_link_use_channel(link, &chandef,
IEEE80211_CHANCTX_SHARED);
if (!ap_min_req_set)
return true;
/* don't downgrade for 5 and 10 MHz channels, though. */
if (chandef.width == NL80211_CHAN_WIDTH_5 ||
chandef.width == NL80211_CHAN_WIDTH_10)
goto out;
/* make sure the AP is consistent with itself
*
* P802.11-REVme/D0.3
* 26.17.1 Basic HE BSS operation
*
* A STA that is operating in an HE BSS shall be able to receive and
* transmit at each of the <HE-MCS, NSS> tuple values indicated by the
* Basic HE-MCS And NSS Set field of the HE Operation parameter of the
* MLME-START.request primitive and shall be able to receive at each of
* the <HE-MCS, NSS> tuple values indicated by the Supported HE-MCS and
* NSS Set field in the HE Capabilities parameter of the MLMESTART.request
* primitive
*/
for (nss = 8; nss > 0; nss--) {
u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3;
u8 ap_rx_val;
u8 ap_tx_val;
while (ret && chandef.width != NL80211_CHAN_WIDTH_20_NOHT) {
*conn_flags |=
ieee80211_chandef_downgrade(&chandef);
ret = ieee80211_link_use_channel(link, &chandef,
IEEE80211_CHANCTX_SHARED);
if (ap_op_val == IEEE80211_HE_MCS_NOT_SUPPORTED)
continue;
ap_rx_val = (mcs_80_map_rx >> (2 * (nss - 1))) & 3;
ap_tx_val = (mcs_80_map_tx >> (2 * (nss - 1))) & 3;
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);
return false;
}
out:
mutex_unlock(&local->mtx);
return ret;
}
return true;
}
static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
struct cfg80211_bss *cbss,
struct ieee80211_mgmt *mgmt, size_t len,
struct ieee802_11_elems *elems)
static bool
ieee80211_verify_sta_he_mcs_support(struct ieee80211_sub_if_data *sdata,
struct ieee80211_supported_band *sband,
const struct ieee80211_he_operation *he_op)
{
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_local *local = sdata->local;
struct ieee80211_supported_band *sband;
struct link_sta_info *link_sta;
struct sta_info *sta;
u16 capab_info, aid;
struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf;
const struct cfg80211_bss_ies *bss_ies = NULL;
struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data;
bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ;
bool is_s1g = cbss->channel->band == NL80211_BAND_S1GHZ;
struct ieee80211_link_data *link = &sdata->deflink;
u32 changed = 0;
int err;
bool ret;
const struct ieee80211_sta_he_cap *sta_he_cap =
ieee80211_get_he_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif));
u16 ap_min_req_set;
int i;
capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info);
if (!sta_he_cap || !he_op)
return false;
if (elems->aid_resp)
aid = le16_to_cpu(elems->aid_resp->aid);
else if (is_s1g)
aid = 0; /* TODO */
else
aid = le16_to_cpu(mgmt->u.assoc_resp.aid);
ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set);
/*
* The 5 MSB of the AID field are reserved
* (802.11-2016 9.4.1.8 AID field)
* Apparently iPhone 13 (at least iOS version 15.3.1) sets this to all
* zeroes, which is nonsense, and completely inconsistent with itself
* (it doesn't have 8 streams). Accept the settings in this case anyway.
*/
aid &= 0x7ff;
if (!ap_min_req_set)
return true;
ifmgd->broken_ap = false;
/* Need to go over for 80MHz, 160MHz and for 80+80 */
for (i = 0; i < 3; i++) {
const struct ieee80211_he_mcs_nss_supp *sta_mcs_nss_supp =
&sta_he_cap->he_mcs_nss_supp;
u16 sta_mcs_map_rx =
le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i]);
u16 sta_mcs_map_tx =
le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i + 1]);
u8 nss;
bool verified = true;
if (aid == 0 || aid > IEEE80211_MAX_AID) {
sdata_info(sdata, "invalid AID value %d (out of range), turn off PS\n",
aid);
aid = 0;
ifmgd->broken_ap = true;
/*
* For each band there is a maximum of 8 spatial streams
* possible. Each of the sta_mcs_map_* is a 16-bit struct built
* of 2 bits per NSS (1-8), with the values defined in enum
* ieee80211_he_mcs_support. Need to make sure STA TX and RX
* capabilities aren't less than the AP's minimum requirements
* for this HE BSS per SS.
* It is enough to find one such band that meets the reqs.
*/
for (nss = 8; nss > 0; nss--) {
u8 sta_rx_val = (sta_mcs_map_rx >> (2 * (nss - 1))) & 3;
u8 sta_tx_val = (sta_mcs_map_tx >> (2 * (nss - 1))) & 3;
u8 ap_val = (ap_min_req_set >> (2 * (nss - 1))) & 3;
if (ap_val == IEEE80211_HE_MCS_NOT_SUPPORTED)
continue;
/*
* Make sure the HE AP doesn't require MCSs that aren't
* supported by the client as required by spec
*
* P802.11-REVme/D0.3
* 26.17.1 Basic HE BSS operation
*
* An HE STA shall not attempt to join * (MLME-JOIN.request primitive)
* a BSS, unless it supports (i.e., is able to both transmit and
* receive using) all of the <HE-MCS, NSS> tuples in the basic
* HE-MCS and NSS set.
*/
if (sta_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
sta_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
(ap_val > sta_rx_val) || (ap_val > sta_tx_val)) {
verified = false;
break;
}
}
if (!is_s1g && !elems->supp_rates) {
sdata_info(sdata, "no SuppRates element in AssocResp\n");
ret = false;
goto out;
if (verified)
return true;
}
sdata->vif.cfg.aid = aid;
sdata->deflink.u.mgd.tdls_chan_switch_prohibited =
elems->ext_capab && elems->ext_capab_len >= 5 &&
(elems->ext_capab[4] & WLAN_EXT_CAPA5_TDLS_CH_SW_PROHIBITED);
/* If here, STA doesn't meet AP's HE min requirements */
return false;
}
/*
* Some APs are erroneously not including some information in their
* (re)association response frames. Try to recover by using the data
* from the beacon or probe response. This seems to afflict mobile
* 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 &&
((assoc_data->wmm && !elems->wmm_param) ||
(!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) &&
(!elems->ht_cap_elem || !elems->ht_operation)) ||
(!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) &&
(!elems->vht_cap_elem || !elems->vht_operation)))) {
static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
struct cfg80211_bss *cbss,
ieee80211_conn_flags_t *conn_flags)
{
struct ieee80211_local *local = sdata->local;
const struct ieee80211_ht_cap *ht_cap = NULL;
const struct ieee80211_ht_operation *ht_oper = NULL;
const struct ieee80211_vht_operation *vht_oper = NULL;
const struct ieee80211_he_operation *he_oper = NULL;
const struct ieee80211_eht_operation *eht_oper = NULL;
const struct ieee80211_s1g_oper_ie *s1g_oper = NULL;
struct ieee80211_supported_band *sband;
struct cfg80211_chan_def chandef;
bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ;
bool is_5ghz = cbss->channel->band == NL80211_BAND_5GHZ;
struct ieee80211_bss *bss = (void *)cbss->priv;
struct ieee802_11_elems *elems;
const struct cfg80211_bss_ies *ies;
struct ieee802_11_elems *bss_elems;
int ret;
u32 i;
bool have_80mhz;
rcu_read_lock();
ies = rcu_dereference(cbss->ies);
if (ies)
bss_ies = kmemdup(ies, sizeof(*ies) + ies->len,
GFP_ATOMIC);
elems = ieee802_11_parse_elems(ies->data, ies->len, false, cbss);
if (!elems) {
rcu_read_unlock();
if (!bss_ies) {
ret = false;
goto out;
return -ENOMEM;
}
bss_elems = ieee802_11_parse_elems(bss_ies->data, bss_ies->len,
false, assoc_data->bss);
if (!bss_elems) {
ret = false;
goto out;
}
sband = local->hw.wiphy->bands[cbss->channel->band];
if (assoc_data->wmm &&
!elems->wmm_param && bss_elems->wmm_param) {
elems->wmm_param = bss_elems->wmm_param;
sdata_info(sdata,
"AP bug: WMM param missing from AssocResp\n");
}
*conn_flags &= ~(IEEE80211_CONN_DISABLE_40MHZ |
IEEE80211_CONN_DISABLE_80P80MHZ |
IEEE80211_CONN_DISABLE_160MHZ);
/*
* Also check if we requested HT/VHT, otherwise the AP doesn't
* have to include the IEs in the (re)association response.
*/
if (!elems->ht_cap_elem && bss_elems->ht_cap_elem &&
!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)) {
elems->ht_cap_elem = bss_elems->ht_cap_elem;
sdata_info(sdata,
"AP bug: HT capability missing from AssocResp\n");
}
if (!elems->ht_operation && bss_elems->ht_operation &&
!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)) {
elems->ht_operation = bss_elems->ht_operation;
sdata_info(sdata,
"AP bug: HT operation missing from AssocResp\n");
/* disable HT/VHT/HE if we don't support them */
if (!sband->ht_cap.ht_supported && !is_6ghz) {
mlme_dbg(sdata, "HT not supported, disabling HT/VHT/HE/EHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_HT;
*conn_flags |= IEEE80211_CONN_DISABLE_VHT;
*conn_flags |= IEEE80211_CONN_DISABLE_HE;
*conn_flags |= IEEE80211_CONN_DISABLE_EHT;
}
if (!elems->vht_cap_elem && bss_elems->vht_cap_elem &&
!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT)) {
elems->vht_cap_elem = bss_elems->vht_cap_elem;
sdata_info(sdata,
"AP bug: VHT capa missing from AssocResp\n");
if (!sband->vht_cap.vht_supported && is_5ghz) {
mlme_dbg(sdata, "VHT not supported, disabling VHT/HE/EHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_VHT;
*conn_flags |= IEEE80211_CONN_DISABLE_HE;
*conn_flags |= IEEE80211_CONN_DISABLE_EHT;
}
if (!elems->vht_operation && bss_elems->vht_operation &&
!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT)) {
elems->vht_operation = bss_elems->vht_operation;
sdata_info(sdata,
"AP bug: VHT operation missing from AssocResp\n");
if (!ieee80211_get_he_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif))) {
mlme_dbg(sdata, "HE not supported, disabling HE and EHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_HE;
*conn_flags |= IEEE80211_CONN_DISABLE_EHT;
}
kfree(bss_elems);
if (!ieee80211_get_eht_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif))) {
mlme_dbg(sdata, "EHT not supported, disabling EHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_EHT;
}
/*
* We previously checked these in the beacon/probe response, so
* they should be present here. This is just a safety net.
*/
if (!is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) &&
(!elems->wmm_param || !elems->ht_cap_elem || !elems->ht_operation)) {
sdata_info(sdata,
"HT AP is missing WMM params or HT capability/operation\n");
ret = false;
goto out;
if (!(*conn_flags & IEEE80211_CONN_DISABLE_HT) && !is_6ghz) {
ht_oper = elems->ht_operation;
ht_cap = elems->ht_cap_elem;
if (!ht_cap) {
*conn_flags |= IEEE80211_CONN_DISABLE_HT;
ht_oper = NULL;
}
}
if (!is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) &&
(!elems->vht_cap_elem || !elems->vht_operation)) {
if (!(*conn_flags & IEEE80211_CONN_DISABLE_VHT) && !is_6ghz) {
vht_oper = elems->vht_operation;
if (vht_oper && !ht_oper) {
vht_oper = NULL;
sdata_info(sdata,
"VHT AP is missing VHT capability/operation\n");
ret = false;
goto out;
"AP advertised VHT without HT, disabling HT/VHT/HE\n");
*conn_flags |= IEEE80211_CONN_DISABLE_HT;
*conn_flags |= IEEE80211_CONN_DISABLE_VHT;
*conn_flags |= IEEE80211_CONN_DISABLE_HE;
*conn_flags |= IEEE80211_CONN_DISABLE_EHT;
}
if (is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) &&
!elems->he_6ghz_capa) {
if (!elems->vht_cap_elem) {
sdata_info(sdata,
"HE 6 GHz AP is missing HE 6 GHz band capability\n");
ret = false;
goto out;
"bad VHT capabilities, disabling VHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_VHT;
vht_oper = NULL;
}
mutex_lock(&sdata->local->sta_mtx);
/*
* station info was already allocated and inserted before
* the association and should be available to us
*/
sta = sta_info_get(sdata, cbss->bssid);
if (WARN_ON(!sta)) {
mutex_unlock(&sdata->local->sta_mtx);
ret = false;
goto out;
}
link_sta = rcu_dereference_protected(sta->link[link->link_id],
lockdep_is_held(&local->sta_mtx));
if (WARN_ON(!link_sta)) {
mutex_unlock(&sdata->local->sta_mtx);
ret = false;
goto out;
}
if (!(*conn_flags & IEEE80211_CONN_DISABLE_HE)) {
he_oper = elems->he_operation;
sband = ieee80211_get_link_sband(link);
if (!sband) {
mutex_unlock(&sdata->local->sta_mtx);
ret = false;
goto out;
}
if (link && is_6ghz) {
struct ieee80211_bss_conf *bss_conf;
u8 j = 0;
if (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) &&
(!elems->he_cap || !elems->he_operation)) {
mutex_unlock(&sdata->local->sta_mtx);
sdata_info(sdata,
"HE AP is missing HE capability/operation\n");
ret = false;
goto out;
}
bss_conf = link->conf;
/* Set up internal HT/VHT capabilities */
if (elems->ht_cap_elem && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT))
ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
elems->ht_cap_elem,
link_sta);
if (elems->pwr_constr_elem)
bss_conf->pwr_reduction = *elems->pwr_constr_elem;
if (elems->vht_cap_elem && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT))
ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
elems->vht_cap_elem,
link_sta);
BUILD_BUG_ON(ARRAY_SIZE(bss_conf->tx_pwr_env) !=
ARRAY_SIZE(elems->tx_pwr_env));
if (elems->he_operation && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) &&
elems->he_cap) {
ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband,
elems->he_cap,
elems->he_cap_len,
elems->he_6ghz_capa,
link_sta);
for (i = 0; i < elems->tx_pwr_env_num; i++) {
if (elems->tx_pwr_env_len[i] >
sizeof(bss_conf->tx_pwr_env[j]))
continue;
bss_conf->tx_pwr_env_num++;
memcpy(&bss_conf->tx_pwr_env[j], elems->tx_pwr_env[i],
elems->tx_pwr_env_len[i]);
j++;
}
}
if (!ieee80211_verify_peer_he_mcs_support(sdata, ies, he_oper) ||
!ieee80211_verify_sta_he_mcs_support(sdata, sband, he_oper))
*conn_flags |= IEEE80211_CONN_DISABLE_HE |
IEEE80211_CONN_DISABLE_EHT;
}
/*
* EHT requires HE to be supported as well. Specifically for 6 GHz
* channels, the operation channel information can only be deduced from
* both the 6 GHz operation information (from the HE operation IE) and
* EHT operation.
*/
if (!(*conn_flags &
(IEEE80211_CONN_DISABLE_HE |
IEEE80211_CONN_DISABLE_EHT)) &&
he_oper) {
const struct cfg80211_bss_ies *cbss_ies;
const u8 *eht_oper_ie;
bss_conf->he_support = link_sta->pub->he_cap.has_he;
if (elems->rsnx && elems->rsnx_len &&
(elems->rsnx[0] & WLAN_RSNX_CAPA_PROTECTED_TWT) &&
wiphy_ext_feature_isset(local->hw.wiphy,
NL80211_EXT_FEATURE_PROTECTED_TWT))
bss_conf->twt_protected = true;
cbss_ies = rcu_dereference(cbss->ies);
eht_oper_ie = cfg80211_find_ext_ie(WLAN_EID_EXT_EHT_OPERATION,
cbss_ies->data, cbss_ies->len);
if (eht_oper_ie && eht_oper_ie[1] >=
1 + sizeof(struct ieee80211_eht_operation))
eht_oper = (void *)(eht_oper_ie + 3);
else
bss_conf->twt_protected = false;
changed |= ieee80211_recalc_twt_req(link, link_sta, elems);
eht_oper = NULL;
}
if (elems->eht_operation && elems->eht_cap &&
!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_EHT)) {
ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband,
elems->he_cap,
elems->he_cap_len,
elems->eht_cap,
elems->eht_cap_len,
link_sta);
/* Allow VHT if at least one channel on the sband supports 80 MHz */
have_80mhz = false;
for (i = 0; i < sband->n_channels; i++) {
if (sband->channels[i].flags & (IEEE80211_CHAN_DISABLED |
IEEE80211_CHAN_NO_80MHZ))
continue;
bss_conf->eht_support = link_sta->pub->eht_cap.has_eht;
} else {
bss_conf->eht_support = false;
}
} else {
bss_conf->he_support = false;
bss_conf->twt_requester = false;
bss_conf->twt_protected = false;
bss_conf->eht_support = false;
have_80mhz = true;
break;
}
bss_conf->twt_broadcast =
ieee80211_twt_bcast_support(sdata, bss_conf, sband, link_sta);
if (!have_80mhz) {
sdata_info(sdata, "80 MHz not supported, disabling VHT\n");
*conn_flags |= IEEE80211_CONN_DISABLE_VHT;
}
if (bss_conf->he_support) {
bss_conf->he_bss_color.color =
le32_get_bits(elems->he_operation->he_oper_params,
IEEE80211_HE_OPERATION_BSS_COLOR_MASK);
bss_conf->he_bss_color.partial =
le32_get_bits(elems->he_operation->he_oper_params,
IEEE80211_HE_OPERATION_PARTIAL_BSS_COLOR);
bss_conf->he_bss_color.enabled =
!le32_get_bits(elems->he_operation->he_oper_params,
IEEE80211_HE_OPERATION_BSS_COLOR_DISABLED);
if (sband->band == NL80211_BAND_S1GHZ) {
s1g_oper = elems->s1g_oper;
if (!s1g_oper)
sdata_info(sdata,
"AP missing S1G operation element?\n");
}
if (bss_conf->he_bss_color.enabled)
changed |= BSS_CHANGED_HE_BSS_COLOR;
*conn_flags |=
ieee80211_determine_chantype(sdata, link, *conn_flags,
sband,
cbss->channel,
bss->vht_cap_info,
ht_oper, vht_oper,
he_oper, eht_oper,
s1g_oper,
&chandef, false);
bss_conf->htc_trig_based_pkt_ext =
le32_get_bits(elems->he_operation->he_oper_params,
IEEE80211_HE_OPERATION_DFLT_PE_DURATION_MASK);
bss_conf->frame_time_rts_th =
le32_get_bits(elems->he_operation->he_oper_params,
IEEE80211_HE_OPERATION_RTS_THRESHOLD_MASK);
if (link)
link->needed_rx_chains =
min(ieee80211_max_rx_chains(link, cbss),
local->rx_chains);
bss_conf->uora_exists = !!elems->uora_element;
if (elems->uora_element)
bss_conf->uora_ocw_range = elems->uora_element[0];
rcu_read_unlock();
/* the element data was RCU protected so no longer valid anyway */
kfree(elems);
elems = NULL;
ieee80211_he_op_ie_to_bss_conf(&sdata->vif, elems->he_operation);
ieee80211_he_spr_ie_to_bss_conf(&sdata->vif, elems->he_spr);
/* TODO: OPEN: what happens if BSS color disable is set? */
if (*conn_flags & IEEE80211_CONN_DISABLE_HE && is_6ghz) {
sdata_info(sdata, "Rejecting non-HE 6/7 GHz connection");
return -EINVAL;
}
if (cbss->transmitted_bss) {
bss_conf->nontransmitted = true;
ether_addr_copy(bss_conf->transmitter_bssid,
cbss->transmitted_bss->bssid);
bss_conf->bssid_indicator = cbss->max_bssid_indicator;
bss_conf->bssid_index = cbss->bssid_index;
} else {
bss_conf->nontransmitted = false;
memset(bss_conf->transmitter_bssid, 0,
sizeof(bss_conf->transmitter_bssid));
bss_conf->bssid_indicator = 0;
bss_conf->bssid_index = 0;
}
if (!link)
return 0;
/* will change later if needed */
link->smps_mode = IEEE80211_SMPS_OFF;
mutex_lock(&local->mtx);
/*
* Some APs, e.g. Netgear WNDR3700, report invalid HT operation data
* in their association response, so ignore that data for our own
* configuration. If it changed since the last beacon, we'll get the
* next beacon and update then.
* If this fails (possibly due to channel context sharing
* on incompatible channels, e.g. 80+80 and 160 sharing the
* same control channel) try to use a smaller bandwidth.
*/
ret = ieee80211_link_use_channel(link, &chandef,
IEEE80211_CHANCTX_SHARED);
/* don't downgrade for 5 and 10 MHz channels, though. */
if (chandef.width == NL80211_CHAN_WIDTH_5 ||
chandef.width == NL80211_CHAN_WIDTH_10)
goto out;
while (ret && chandef.width != NL80211_CHAN_WIDTH_20_NOHT) {
*conn_flags |=
ieee80211_chandef_downgrade(&chandef);
ret = ieee80211_link_use_channel(link, &chandef,
IEEE80211_CHANCTX_SHARED);
}
out:
mutex_unlock(&local->mtx);
return ret;
}
static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
struct cfg80211_bss *cbss,
struct ieee80211_mgmt *mgmt,
struct ieee802_11_elems *elems,
const u8 *elem_start, unsigned int elem_len)
{
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_local *local = sdata->local;
struct sta_info *sta;
u64 changed = 0;
int err;
mutex_lock(&sdata->local->sta_mtx);
/*
* If an operating mode notification IE is present, override the
* NSS calculation (that would be done in rate_control_rate_init())
* and use the # of streams from that element.
* station info was already allocated and inserted before
* the association and should be available to us
*/
if (elems->opmode_notif &&
!(*elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)) {
u8 nss;
sta = sta_info_get(sdata, cbss->bssid);
if (WARN_ON(!sta))
goto out_err;
nss = *elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_MASK;
nss >>= IEEE80211_OPMODE_NOTIF_RX_NSS_SHIFT;
nss += 1;
link_sta->pub->rx_nss = nss;
}
if (!ieee80211_assoc_config_link(&sdata->deflink, &sta->deflink,
cbss, mgmt, elem_start, elem_len,
&changed))
goto out_err;
rate_control_rate_init(sta);
......@@ -4450,9 +4484,7 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
"failed to move station %pM to desired state\n",
sta->sta.addr);
WARN_ON(__sta_info_destroy(sta));
mutex_unlock(&sdata->local->sta_mtx);
ret = false;
goto out;
goto out_err;
}
if (sdata->wdev.use_4addr)
......@@ -4460,48 +4492,6 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
mutex_unlock(&sdata->local->sta_mtx);
/*
* Always handle WMM once after association regardless
* of the first value the AP uses. Setting -1 here has
* that effect because the AP values is an unsigned
* 4-bit value.
*/
link->u.mgd.wmm_last_param_set = -1;
link->u.mgd.mu_edca_last_param_set = -1;
if (link->u.mgd.disable_wmm_tracking) {
ieee80211_set_wmm_default(link, false, false);
} else if (!ieee80211_sta_wmm_params(local, link, elems->wmm_param,
elems->wmm_param_len,
elems->mu_edca_param_set)) {
/* still enable QoS since we might have HT/VHT */
ieee80211_set_wmm_default(link, false, true);
/* disable WMM tracking in this case to disable
* tracking WMM parameter changes in the beacon if
* the parameters weren't actually valid. Doing so
* avoids changing parameters very strangely when
* the AP is going back and forth between valid and
* invalid parameters.
*/
link->u.mgd.disable_wmm_tracking = true;
}
changed |= BSS_CHANGED_QOS;
if (elems->max_idle_period_ie) {
bss_conf->max_idle_period =
le16_to_cpu(elems->max_idle_period_ie->max_idle_period);
bss_conf->protected_keep_alive =
!!(elems->max_idle_period_ie->idle_options &
WLAN_IDLE_OPTIONS_PROTECTED_KEEP_ALIVE);
changed |= BSS_CHANGED_KEEP_ALIVE;
} else {
bss_conf->max_idle_period = 0;
bss_conf->protected_keep_alive = false;
}
/* set assoc capability (AID was already set earlier),
* ieee80211_set_associated() will tell the driver */
bss_conf->assoc_capability = capab_info;
ieee80211_set_associated(sdata, cbss, changed);
/*
......@@ -4518,10 +4508,10 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
ieee80211_sta_reset_beacon_monitor(sdata);
ieee80211_sta_reset_conn_monitor(sdata);
ret = true;
out:
kfree(bss_ies);
return ret;
return true;
out_err:
mutex_unlock(&sdata->local->sta_mtx);
return false;
}
static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
......@@ -4533,7 +4523,8 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
u16 capab_info, status_code, aid;
struct ieee802_11_elems *elems;
int ac;
u8 *pos;
const u8 *elem_start;
unsigned int elem_len;
bool reassoc;
struct cfg80211_bss *cbss;
struct ieee80211_event event = {
......@@ -4566,12 +4557,10 @@ 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);
pos = mgmt->u.assoc_resp.variable;
aid = le16_to_cpu(mgmt->u.assoc_resp.aid);
if (cbss->channel->band == NL80211_BAND_S1GHZ) {
pos = (u8 *) mgmt->u.s1g_assoc_resp.variable;
aid = 0; /* TODO */
}
if (cbss->channel->band == NL80211_BAND_S1GHZ)
elem_start = mgmt->u.s1g_assoc_resp.variable;
else
elem_start = mgmt->u.assoc_resp.variable;
/*
* Note: this may not be perfect, AP might misbehave - if
......@@ -4582,20 +4571,35 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
info.subtype = reassoc ? IEEE80211_STYPE_REASSOC_REQ :
IEEE80211_STYPE_ASSOC_REQ;
sdata_info(sdata,
"RX %sssocResp from %pM (capab=0x%x status=%d aid=%d)\n",
reassoc ? "Rea" : "A", mgmt->sa,
capab_info, status_code, (u16)(aid & ~(BIT(15) | BIT(14))));
if (assoc_data->fils_kek_len &&
fils_decrypt_assoc_resp(sdata, (u8 *)mgmt, &len, assoc_data) < 0)
return;
elems = ieee802_11_parse_elems(pos, len - (pos - (u8 *)mgmt), false,
assoc_data->bss);
elem_len = len - (elem_start - (u8 *)mgmt);
elems = ieee802_11_parse_elems(elem_start, elem_len, false, NULL);
if (!elems)
goto notify_driver;
if (elems->aid_resp)
aid = le16_to_cpu(elems->aid_resp->aid);
else if (cbss->channel->band == NL80211_BAND_S1GHZ)
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)
*/
aid &= 0x7ff;
sdata_info(sdata,
"RX %sssocResp from %pM (capab=0x%x status=%d aid=%d)\n",
reassoc ? "Rea" : "A", mgmt->sa,
capab_info, status_code, (u16)(aid & ~(BIT(15) | BIT(14))));
ifmgd->broken_ap = false;
if (status_code == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY &&
elems->timeout_int &&
elems->timeout_int->type == WLAN_TIMEOUT_ASSOC_COMEBACK) {
......@@ -4624,7 +4628,18 @@ 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 (!ieee80211_assoc_success(sdata, cbss, mgmt, len, elems)) {
if (aid == 0 || aid > IEEE80211_MAX_AID) {
sdata_info(sdata,
"invalid AID value %d (out of range), turn off PS\n",
aid);
aid = 0;
ifmgd->broken_ap = true;
}
sdata->vif.cfg.aid = aid;
if (!ieee80211_assoc_success(sdata, cbss, mgmt, elems,
elem_start, elem_len)) {
/* oops -- internal error -- send timeout for now */
ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT);
goto notify_driver;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment