|
@@ -149,6 +149,7 @@ ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata,
|
|
|
struct ieee80211_channel *channel,
|
|
|
const struct ieee80211_ht_operation *ht_oper,
|
|
|
const struct ieee80211_vht_operation *vht_oper,
|
|
|
+ const struct ieee80211_he_operation *he_oper,
|
|
|
struct cfg80211_chan_def *chandef, bool tracking)
|
|
|
{
|
|
|
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
|
|
@@ -207,7 +208,27 @@ ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata,
|
|
|
}
|
|
|
|
|
|
vht_chandef = *chandef;
|
|
|
- if (!ieee80211_chandef_vht_oper(vht_oper, &vht_chandef)) {
|
|
|
+ if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HE) && he_oper &&
|
|
|
+ (le32_to_cpu(he_oper->he_oper_params) &
|
|
|
+ IEEE80211_HE_OPERATION_VHT_OPER_INFO)) {
|
|
|
+ struct ieee80211_vht_operation he_oper_vht_cap;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Set only first 3 bytes (other 2 aren't used in
|
|
|
+ * ieee80211_chandef_vht_oper() anyway)
|
|
|
+ */
|
|
|
+ memcpy(&he_oper_vht_cap, he_oper->optional, 3);
|
|
|
+ he_oper_vht_cap.basic_mcs_set = cpu_to_le16(0);
|
|
|
+
|
|
|
+ if (!ieee80211_chandef_vht_oper(&he_oper_vht_cap,
|
|
|
+ &vht_chandef)) {
|
|
|
+ if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HE))
|
|
|
+ sdata_info(sdata,
|
|
|
+ "HE AP VHT information is invalid, disable HE\n");
|
|
|
+ ret = IEEE80211_STA_DISABLE_HE;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ } else if (!ieee80211_chandef_vht_oper(vht_oper, &vht_chandef)) {
|
|
|
if (!(ifmgd->flags & IEEE80211_STA_DISABLE_VHT))
|
|
|
sdata_info(sdata,
|
|
|
"AP VHT information is invalid, disable VHT\n");
|
|
@@ -300,12 +321,14 @@ static int ieee80211_config_bw(struct ieee80211_sub_if_data *sdata,
|
|
|
const struct ieee80211_ht_cap *ht_cap,
|
|
|
const struct ieee80211_ht_operation *ht_oper,
|
|
|
const struct ieee80211_vht_operation *vht_oper,
|
|
|
+ const struct ieee80211_he_operation *he_oper,
|
|
|
const u8 *bssid, u32 *changed)
|
|
|
{
|
|
|
struct ieee80211_local *local = sdata->local;
|
|
|
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
|
|
|
- struct ieee80211_supported_band *sband;
|
|
|
- struct ieee80211_channel *chan;
|
|
|
+ struct ieee80211_channel *chan = sdata->vif.bss_conf.chandef.chan;
|
|
|
+ struct ieee80211_supported_band *sband =
|
|
|
+ local->hw.wiphy->bands[chan->band];
|
|
|
struct cfg80211_chan_def chandef;
|
|
|
u16 ht_opmode;
|
|
|
u32 flags;
|
|
@@ -320,6 +343,11 @@ static int ieee80211_config_bw(struct ieee80211_sub_if_data *sdata,
|
|
|
if (ifmgd->flags & IEEE80211_STA_DISABLE_VHT)
|
|
|
vht_oper = NULL;
|
|
|
|
|
|
+ /* don't check HE if we associated as non-HE station */
|
|
|
+ if (ifmgd->flags & IEEE80211_STA_DISABLE_HE ||
|
|
|
+ !ieee80211_get_he_sta_cap(sband))
|
|
|
+ he_oper = NULL;
|
|
|
+
|
|
|
if (WARN_ON_ONCE(!sta))
|
|
|
return -EINVAL;
|
|
|
|
|
@@ -333,12 +361,9 @@ static int ieee80211_config_bw(struct ieee80211_sub_if_data *sdata,
|
|
|
sdata->vif.bss_conf.ht_operation_mode = ht_opmode;
|
|
|
}
|
|
|
|
|
|
- chan = sdata->vif.bss_conf.chandef.chan;
|
|
|
- sband = local->hw.wiphy->bands[chan->band];
|
|
|
-
|
|
|
- /* calculate new channel (type) based on HT/VHT operation IEs */
|
|
|
+ /* calculate new channel (type) based on HT/VHT/HE operation IEs */
|
|
|
flags = ieee80211_determine_chantype(sdata, sband, chan,
|
|
|
- ht_oper, vht_oper,
|
|
|
+ ht_oper, vht_oper, he_oper,
|
|
|
&chandef, true);
|
|
|
|
|
|
/*
|
|
@@ -582,6 +607,34 @@ static void ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata,
|
|
|
ieee80211_ie_build_vht_cap(pos, &vht_cap, cap);
|
|
|
}
|
|
|
|
|
|
+/* This function determines HE capability flags for the association
|
|
|
+ * and builds the IE.
|
|
|
+ */
|
|
|
+static void ieee80211_add_he_ie(struct ieee80211_sub_if_data *sdata,
|
|
|
+ struct sk_buff *skb,
|
|
|
+ struct ieee80211_supported_band *sband)
|
|
|
+{
|
|
|
+ u8 *pos;
|
|
|
+ const struct ieee80211_sta_he_cap *he_cap = NULL;
|
|
|
+ u8 he_cap_size;
|
|
|
+
|
|
|
+ he_cap = ieee80211_get_he_sta_cap(sband);
|
|
|
+ if (!he_cap)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * TODO: the 1 added is because this temporarily is under the EXTENSION
|
|
|
+ * IE. Get rid of it when it moves.
|
|
|
+ */
|
|
|
+ he_cap_size =
|
|
|
+ 2 + 1 + sizeof(he_cap->he_cap_elem) +
|
|
|
+ ieee80211_he_mcs_nss_size(&he_cap->he_cap_elem) +
|
|
|
+ ieee80211_he_ppe_size(he_cap->ppe_thres[0],
|
|
|
+ he_cap->he_cap_elem.phy_cap_info);
|
|
|
+ pos = skb_put(skb, he_cap_size);
|
|
|
+ ieee80211_ie_build_he_cap(pos, he_cap, pos + he_cap_size);
|
|
|
+}
|
|
|
+
|
|
|
static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
|
|
|
{
|
|
|
struct ieee80211_local *local = sdata->local;
|
|
@@ -643,6 +696,9 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
|
|
|
2 + 2 * sband->n_channels + /* supported channels */
|
|
|
2 + sizeof(struct ieee80211_ht_cap) + /* HT */
|
|
|
2 + sizeof(struct ieee80211_vht_cap) + /* VHT */
|
|
|
+ 2 + 1 + sizeof(struct ieee80211_he_cap_elem) + /* HE */
|
|
|
+ sizeof(struct ieee80211_he_mcs_nss_supp) +
|
|
|
+ IEEE80211_HE_PPE_THRES_MAX_LEN +
|
|
|
assoc_data->ie_len + /* extra IEs */
|
|
|
(assoc_data->fils_kek_len ? 16 /* AES-SIV */ : 0) +
|
|
|
9, /* WMM */
|
|
@@ -827,11 +883,41 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
|
|
|
offset = noffset;
|
|
|
}
|
|
|
|
|
|
+ /* if present, add any custom IEs that go before HE */
|
|
|
+ if (assoc_data->ie_len) {
|
|
|
+ static const u8 before_he[] = {
|
|
|
+ /*
|
|
|
+ * no need to list the ones split off before VHT
|
|
|
+ * or generated here
|
|
|
+ */
|
|
|
+ WLAN_EID_OPMODE_NOTIF,
|
|
|
+ WLAN_EID_EXTENSION, WLAN_EID_EXT_FUTURE_CHAN_GUIDANCE,
|
|
|
+ /* 11ai elements */
|
|
|
+ WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_SESSION,
|
|
|
+ WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_PUBLIC_KEY,
|
|
|
+ WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_KEY_CONFIRM,
|
|
|
+ WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_HLP_CONTAINER,
|
|
|
+ WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_IP_ADDR_ASSIGN,
|
|
|
+ /* TODO: add 11ah/11aj/11ak elements */
|
|
|
+ };
|
|
|
+
|
|
|
+ /* RIC already taken above, so no need to handle here anymore */
|
|
|
+ noffset = ieee80211_ie_split(assoc_data->ie, assoc_data->ie_len,
|
|
|
+ before_he, ARRAY_SIZE(before_he),
|
|
|
+ offset);
|
|
|
+ pos = skb_put(skb, noffset - offset);
|
|
|
+ memcpy(pos, assoc_data->ie + offset, noffset - offset);
|
|
|
+ offset = noffset;
|
|
|
+ }
|
|
|
+
|
|
|
if (!(ifmgd->flags & IEEE80211_STA_DISABLE_VHT))
|
|
|
ieee80211_add_vht_ie(sdata, skb, sband,
|
|
|
&assoc_data->ap_vht_cap);
|
|
|
|
|
|
- /* if present, add any custom non-vendor IEs that go after HT */
|
|
|
+ if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HE))
|
|
|
+ ieee80211_add_he_ie(sdata, skb, sband);
|
|
|
+
|
|
|
+ /* if present, add any custom non-vendor IEs that go after HE */
|
|
|
if (assoc_data->ie_len) {
|
|
|
noffset = ieee80211_ie_split_vendor(assoc_data->ie,
|
|
|
assoc_data->ie_len,
|
|
@@ -898,6 +984,11 @@ void ieee80211_send_nullfunc(struct ieee80211_local *local,
|
|
|
struct ieee80211_hdr_3addr *nullfunc;
|
|
|
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
|
|
|
|
|
|
+ /* Don't send NDPs when STA is connected HE */
|
|
|
+ if (sdata->vif.type == NL80211_IFTYPE_STATION &&
|
|
|
+ !(ifmgd->flags & IEEE80211_STA_DISABLE_HE))
|
|
|
+ return;
|
|
|
+
|
|
|
skb = ieee80211_nullfunc_get(&local->hw, &sdata->vif,
|
|
|
!ieee80211_hw_check(&local->hw, DOESNT_SUPPORT_QOS_NDP));
|
|
|
if (!skb)
|
|
@@ -929,6 +1020,10 @@ static void ieee80211_send_4addr_nullfunc(struct ieee80211_local *local,
|
|
|
if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_STATION))
|
|
|
return;
|
|
|
|
|
|
+ /* Don't send NDPs when connected HE */
|
|
|
+ if (!(sdata->u.mgd.flags & IEEE80211_STA_DISABLE_HE))
|
|
|
+ return;
|
|
|
+
|
|
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom + 30);
|
|
|
if (!skb)
|
|
|
return;
|
|
@@ -1700,9 +1795,11 @@ static void ieee80211_sta_handle_tspec_ac_params_wk(struct work_struct *work)
|
|
|
}
|
|
|
|
|
|
/* MLME */
|
|
|
-static bool ieee80211_sta_wmm_params(struct ieee80211_local *local,
|
|
|
- struct ieee80211_sub_if_data *sdata,
|
|
|
- const u8 *wmm_param, size_t wmm_param_len)
|
|
|
+static bool
|
|
|
+ieee80211_sta_wmm_params(struct ieee80211_local *local,
|
|
|
+ struct ieee80211_sub_if_data *sdata,
|
|
|
+ const u8 *wmm_param, size_t wmm_param_len,
|
|
|
+ const struct ieee80211_mu_edca_param_set *mu_edca)
|
|
|
{
|
|
|
struct ieee80211_tx_queue_params params[IEEE80211_NUM_ACS];
|
|
|
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
|
|
@@ -1749,6 +1846,9 @@ static bool ieee80211_sta_wmm_params(struct ieee80211_local *local,
|
|
|
sdata->wmm_acm |= BIT(1) | BIT(2); /* BK/- */
|
|
|
if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK)
|
|
|
uapsd = true;
|
|
|
+ params[ac].mu_edca = !!mu_edca;
|
|
|
+ if (mu_edca)
|
|
|
+ params[ac].mu_edca_param_rec = mu_edca->ac_bk;
|
|
|
break;
|
|
|
case 2: /* AC_VI */
|
|
|
ac = IEEE80211_AC_VI;
|
|
@@ -1756,6 +1856,9 @@ static bool ieee80211_sta_wmm_params(struct ieee80211_local *local,
|
|
|
sdata->wmm_acm |= BIT(4) | BIT(5); /* CL/VI */
|
|
|
if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI)
|
|
|
uapsd = true;
|
|
|
+ params[ac].mu_edca = !!mu_edca;
|
|
|
+ if (mu_edca)
|
|
|
+ params[ac].mu_edca_param_rec = mu_edca->ac_vi;
|
|
|
break;
|
|
|
case 3: /* AC_VO */
|
|
|
ac = IEEE80211_AC_VO;
|
|
@@ -1763,6 +1866,9 @@ static bool ieee80211_sta_wmm_params(struct ieee80211_local *local,
|
|
|
sdata->wmm_acm |= BIT(6) | BIT(7); /* VO/NC */
|
|
|
if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO)
|
|
|
uapsd = true;
|
|
|
+ params[ac].mu_edca = !!mu_edca;
|
|
|
+ if (mu_edca)
|
|
|
+ params[ac].mu_edca_param_rec = mu_edca->ac_vo;
|
|
|
break;
|
|
|
case 0: /* AC_BE */
|
|
|
default:
|
|
@@ -1771,6 +1877,9 @@ static bool ieee80211_sta_wmm_params(struct ieee80211_local *local,
|
|
|
sdata->wmm_acm |= BIT(0) | BIT(3); /* BE/EE */
|
|
|
if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE)
|
|
|
uapsd = true;
|
|
|
+ params[ac].mu_edca = !!mu_edca;
|
|
|
+ if (mu_edca)
|
|
|
+ params[ac].mu_edca_param_rec = mu_edca->ac_be;
|
|
|
break;
|
|
|
}
|
|
|
|
|
@@ -2219,6 +2328,20 @@ void ieee80211_sta_tx_notify(struct ieee80211_sub_if_data *sdata,
|
|
|
ieee80211_sta_reset_conn_monitor(sdata);
|
|
|
}
|
|
|
|
|
|
+static void ieee80211_mlme_send_probe_req(struct ieee80211_sub_if_data *sdata,
|
|
|
+ const u8 *src, const u8 *dst,
|
|
|
+ const u8 *ssid, size_t ssid_len,
|
|
|
+ struct ieee80211_channel *channel)
|
|
|
+{
|
|
|
+ struct sk_buff *skb;
|
|
|
+
|
|
|
+ skb = ieee80211_build_probe_req(sdata, src, dst, (u32)-1, channel,
|
|
|
+ ssid, ssid_len, NULL, 0,
|
|
|
+ IEEE80211_PROBE_FLAG_DIRECTED);
|
|
|
+ if (skb)
|
|
|
+ ieee80211_tx_skb(sdata, skb);
|
|
|
+}
|
|
|
+
|
|
|
static void ieee80211_mgd_probe_ap_send(struct ieee80211_sub_if_data *sdata)
|
|
|
{
|
|
|
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
|
|
@@ -2265,10 +2388,9 @@ static void ieee80211_mgd_probe_ap_send(struct ieee80211_sub_if_data *sdata)
|
|
|
else
|
|
|
ssid_len = ssid[1];
|
|
|
|
|
|
- ieee80211_send_probe_req(sdata, sdata->vif.addr, dst,
|
|
|
- ssid + 2, ssid_len, NULL,
|
|
|
- 0, (u32) -1, true, 0,
|
|
|
- ifmgd->associated->channel, false);
|
|
|
+ ieee80211_mlme_send_probe_req(sdata, sdata->vif.addr, dst,
|
|
|
+ ssid + 2, ssid_len,
|
|
|
+ ifmgd->associated->channel);
|
|
|
rcu_read_unlock();
|
|
|
}
|
|
|
|
|
@@ -2370,7 +2492,7 @@ struct sk_buff *ieee80211_ap_probereq_get(struct ieee80211_hw *hw,
|
|
|
skb = ieee80211_build_probe_req(sdata, sdata->vif.addr, cbss->bssid,
|
|
|
(u32) -1, cbss->channel,
|
|
|
ssid + 2, ssid_len,
|
|
|
- NULL, 0, true);
|
|
|
+ NULL, 0, IEEE80211_PROBE_FLAG_DIRECTED);
|
|
|
rcu_read_unlock();
|
|
|
|
|
|
return skb;
|
|
@@ -3008,6 +3130,25 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
+ /*
|
|
|
+ * If AP doesn't support HT, or it doesn't have HE mandatory IEs, mark
|
|
|
+ * HE as disabled. If on the 5GHz band, make sure it supports VHT.
|
|
|
+ */
|
|
|
+ if (ifmgd->flags & IEEE80211_STA_DISABLE_HT ||
|
|
|
+ (sband->band == NL80211_BAND_5GHZ &&
|
|
|
+ ifmgd->flags & IEEE80211_STA_DISABLE_VHT) ||
|
|
|
+ (!elems.he_cap && !elems.he_operation))
|
|
|
+ ifmgd->flags |= IEEE80211_STA_DISABLE_HE;
|
|
|
+
|
|
|
+ if (!(ifmgd->flags & IEEE80211_STA_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;
|
|
|
+ }
|
|
|
+
|
|
|
/* Set up internal HT/VHT capabilities */
|
|
|
if (elems.ht_cap_elem && !(ifmgd->flags & IEEE80211_STA_DISABLE_HT))
|
|
|
ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
|
|
@@ -3017,6 +3158,48 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
|
|
|
ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
|
|
|
elems.vht_cap_elem, sta);
|
|
|
|
|
|
+ if (elems.he_operation && !(ifmgd->flags & IEEE80211_STA_DISABLE_HE) &&
|
|
|
+ elems.he_cap) {
|
|
|
+ ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband,
|
|
|
+ elems.he_cap,
|
|
|
+ elems.he_cap_len,
|
|
|
+ sta);
|
|
|
+
|
|
|
+ bss_conf->he_support = sta->sta.he_cap.has_he;
|
|
|
+ } else {
|
|
|
+ bss_conf->he_support = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bss_conf->he_support) {
|
|
|
+ u32 he_oper_params =
|
|
|
+ le32_to_cpu(elems.he_operation->he_oper_params);
|
|
|
+
|
|
|
+ bss_conf->bss_color = he_oper_params &
|
|
|
+ IEEE80211_HE_OPERATION_BSS_COLOR_MASK;
|
|
|
+ bss_conf->htc_trig_based_pkt_ext =
|
|
|
+ (he_oper_params &
|
|
|
+ IEEE80211_HE_OPERATION_DFLT_PE_DURATION_MASK) <<
|
|
|
+ IEEE80211_HE_OPERATION_DFLT_PE_DURATION_OFFSET;
|
|
|
+ bss_conf->frame_time_rts_th =
|
|
|
+ (he_oper_params &
|
|
|
+ IEEE80211_HE_OPERATION_RTS_THRESHOLD_MASK) <<
|
|
|
+ IEEE80211_HE_OPERATION_RTS_THRESHOLD_OFFSET;
|
|
|
+
|
|
|
+ bss_conf->multi_sta_back_32bit =
|
|
|
+ sta->sta.he_cap.he_cap_elem.mac_cap_info[2] &
|
|
|
+ IEEE80211_HE_MAC_CAP2_32BIT_BA_BITMAP;
|
|
|
+
|
|
|
+ bss_conf->ack_enabled =
|
|
|
+ sta->sta.he_cap.he_cap_elem.mac_cap_info[2] &
|
|
|
+ IEEE80211_HE_MAC_CAP2_ACK_EN;
|
|
|
+
|
|
|
+ bss_conf->uora_exists = !!elems.uora_element;
|
|
|
+ if (elems.uora_element)
|
|
|
+ bss_conf->uora_ocw_range = elems.uora_element[0];
|
|
|
+
|
|
|
+ /* TODO: OPEN: what happens if BSS color disable is set? */
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
* Some APs, e.g. Netgear WNDR3700, report invalid HT operation data
|
|
|
* in their association response, so ignore that data for our own
|
|
@@ -3076,7 +3259,8 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
|
|
|
if (ifmgd->flags & IEEE80211_STA_DISABLE_WMM) {
|
|
|
ieee80211_set_wmm_default(sdata, false, false);
|
|
|
} else if (!ieee80211_sta_wmm_params(local, sdata, elems.wmm_param,
|
|
|
- elems.wmm_param_len)) {
|
|
|
+ elems.wmm_param_len,
|
|
|
+ elems.mu_edca_param_set)) {
|
|
|
/* still enable QoS since we might have HT/VHT */
|
|
|
ieee80211_set_wmm_default(sdata, false, true);
|
|
|
/* set the disable-WMM flag in this case to disable
|
|
@@ -3590,7 +3774,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
|
|
|
|
|
|
if (!(ifmgd->flags & IEEE80211_STA_DISABLE_WMM) &&
|
|
|
ieee80211_sta_wmm_params(local, sdata, elems.wmm_param,
|
|
|
- elems.wmm_param_len))
|
|
|
+ elems.wmm_param_len,
|
|
|
+ elems.mu_edca_param_set))
|
|
|
changed |= BSS_CHANGED_QOS;
|
|
|
|
|
|
/*
|
|
@@ -3629,7 +3814,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
|
|
|
|
|
|
if (ieee80211_config_bw(sdata, sta,
|
|
|
elems.ht_cap_elem, elems.ht_operation,
|
|
|
- elems.vht_operation, bssid, &changed)) {
|
|
|
+ elems.vht_operation, elems.he_operation,
|
|
|
+ bssid, &changed)) {
|
|
|
mutex_unlock(&local->sta_mtx);
|
|
|
sdata_info(sdata,
|
|
|
"failed to follow AP %pM bandwidth change, disconnect\n",
|
|
@@ -4266,6 +4452,68 @@ static u8 ieee80211_ht_vht_rx_chains(struct ieee80211_sub_if_data *sdata,
|
|
|
return chains;
|
|
|
}
|
|
|
|
|
|
+static bool
|
|
|
+ieee80211_verify_sta_he_mcs_support(struct ieee80211_supported_band *sband,
|
|
|
+ const struct ieee80211_he_operation *he_op)
|
|
|
+{
|
|
|
+ const struct ieee80211_sta_he_cap *sta_he_cap =
|
|
|
+ ieee80211_get_he_sta_cap(sband);
|
|
|
+ u16 ap_min_req_set;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (!sta_he_cap || !he_op)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set);
|
|
|
+
|
|
|
+ /* 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.
|
|
|
+ */
|
|
|
+ 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
|
|
|
+ */
|
|
|
+ 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 (verified)
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If here, STA doesn't meet AP's HE min requirements */
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
|
|
|
struct cfg80211_bss *cbss)
|
|
|
{
|
|
@@ -4274,6 +4522,7 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
|
|
|
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;
|
|
|
struct ieee80211_supported_band *sband;
|
|
|
struct cfg80211_chan_def chandef;
|
|
|
int ret;
|
|
@@ -4329,6 +4578,24 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HE) &&
|
|
|
+ ieee80211_get_he_sta_cap(sband)) {
|
|
|
+ const struct cfg80211_bss_ies *ies;
|
|
|
+ const u8 *he_oper_ie;
|
|
|
+
|
|
|
+ ies = rcu_dereference(cbss->ies);
|
|
|
+ he_oper_ie = cfg80211_find_ext_ie(WLAN_EID_EXT_HE_OPERATION,
|
|
|
+ ies->data, ies->len);
|
|
|
+ if (he_oper_ie &&
|
|
|
+ he_oper_ie[1] == ieee80211_he_oper_size(&he_oper_ie[3]))
|
|
|
+ he_oper = (void *)(he_oper_ie + 3);
|
|
|
+ else
|
|
|
+ he_oper = NULL;
|
|
|
+
|
|
|
+ if (!ieee80211_verify_sta_he_mcs_support(sband, he_oper))
|
|
|
+ ifmgd->flags |= IEEE80211_STA_DISABLE_HE;
|
|
|
+ }
|
|
|
+
|
|
|
/* Allow VHT if at least one channel on the sband supports 80 MHz */
|
|
|
have_80mhz = false;
|
|
|
for (i = 0; i < sband->n_channels; i++) {
|
|
@@ -4345,7 +4612,7 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
|
|
|
|
|
|
ifmgd->flags |= ieee80211_determine_chantype(sdata, sband,
|
|
|
cbss->channel,
|
|
|
- ht_oper, vht_oper,
|
|
|
+ ht_oper, vht_oper, he_oper,
|
|
|
&chandef, false);
|
|
|
|
|
|
sdata->needed_rx_chains = min(ieee80211_ht_vht_rx_chains(sdata, cbss),
|
|
@@ -4751,8 +5018,9 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
|
|
|
req->crypto.ciphers_pairwise[i] == WLAN_CIPHER_SUITE_WEP104) {
|
|
|
ifmgd->flags |= IEEE80211_STA_DISABLE_HT;
|
|
|
ifmgd->flags |= IEEE80211_STA_DISABLE_VHT;
|
|
|
+ ifmgd->flags |= IEEE80211_STA_DISABLE_HE;
|
|
|
netdev_info(sdata->dev,
|
|
|
- "disabling HT/VHT due to WEP/TKIP use\n");
|
|
|
+ "disabling HE/HT/VHT due to WEP/TKIP use\n");
|
|
|
}
|
|
|
}
|
|
|
|