| 1 | |
| 2 | |
| 3 | * HE handling
|
| 4 | *
|
| 5 | * Copyright(c) 2017 Intel Deutschland GmbH
|
| 6 | * Copyright(c) 2019-2026 Intel Corporation
|
| 7 |
|
| 8 |
|
| 9 | #include "ieee80211_i.h"
|
| 10 | #include "rate.h"
|
| 11 |
|
| 12 | static void
|
| 13 | ieee80211_update_from_he_6ghz_capa(const struct ieee80211_he_6ghz_capa *he_6ghz_capa,
|
| 14 | struct link_sta_info *link_sta)
|
| 15 | {
|
| 16 | struct sta_info *sta = link_sta->sta;
|
| 17 | enum ieee80211_smps_mode smps_mode;
|
| 18 |
|
| 19 | if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
|
| 20 | sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
|
| 21 | switch (le16_get_bits(he_6ghz_capa->capa,
|
| 22 | IEEE80211_HE_6GHZ_CAP_SM_PS)) {
|
| 23 | case WLAN_HT_CAP_SM_PS_INVALID:
|
| 24 | case WLAN_HT_CAP_SM_PS_STATIC:
|
| 25 | smps_mode = IEEE80211_SMPS_STATIC;
|
| 26 | break;
|
| 27 | case WLAN_HT_CAP_SM_PS_DYNAMIC:
|
| 28 | smps_mode = IEEE80211_SMPS_DYNAMIC;
|
| 29 | break;
|
| 30 | case WLAN_HT_CAP_SM_PS_DISABLED:
|
| 31 | smps_mode = IEEE80211_SMPS_OFF;
|
| 32 | break;
|
| 33 | }
|
| 34 |
|
| 35 | link_sta->pub->smps_mode = smps_mode;
|
| 36 | } else {
|
| 37 | link_sta->pub->smps_mode = IEEE80211_SMPS_OFF;
|
| 38 | }
|
| 39 |
|
| 40 | switch (le16_get_bits(he_6ghz_capa->capa,
|
| 41 | IEEE80211_HE_6GHZ_CAP_MAX_MPDU_LEN)) {
|
| 42 | case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454:
|
| 43 | link_sta->pub->agg.max_amsdu_len = IEEE80211_MAX_MPDU_LEN_VHT_11454;
|
| 44 | break;
|
| 45 | case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991:
|
| 46 | link_sta->pub->agg.max_amsdu_len = IEEE80211_MAX_MPDU_LEN_VHT_7991;
|
| 47 | break;
|
| 48 | case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895:
|
| 49 | default:
|
| 50 | link_sta->pub->agg.max_amsdu_len = IEEE80211_MAX_MPDU_LEN_VHT_3895;
|
| 51 | break;
|
| 52 | }
|
| 53 |
|
| 54 | ieee80211_sta_recalc_aggregates(&sta->sta);
|
| 55 |
|
| 56 | link_sta->pub->he_6ghz_capa = *he_6ghz_capa;
|
| 57 | }
|
| 58 |
|
| 59 | static void ieee80211_he_mcs_disable(__le16 *he_mcs)
|
| 60 | {
|
| 61 | u32 i;
|
| 62 |
|
| 63 | for (i = 0; i < 8; i++)
|
| 64 | *he_mcs |= cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << i * 2);
|
| 65 | }
|
| 66 |
|
| 67 | static void ieee80211_he_mcs_intersection(__le16 *he_own_rx, __le16 *he_peer_rx,
|
| 68 | __le16 *he_own_tx, __le16 *he_peer_tx)
|
| 69 | {
|
| 70 | u32 i;
|
| 71 | u16 own_rx, own_tx, peer_rx, peer_tx;
|
| 72 |
|
| 73 | for (i = 0; i < 8; i++) {
|
| 74 | own_rx = le16_to_cpu(*he_own_rx);
|
| 75 | own_rx = (own_rx >> i * 2) & IEEE80211_HE_MCS_NOT_SUPPORTED;
|
| 76 |
|
| 77 | own_tx = le16_to_cpu(*he_own_tx);
|
| 78 | own_tx = (own_tx >> i * 2) & IEEE80211_HE_MCS_NOT_SUPPORTED;
|
| 79 |
|
| 80 | peer_rx = le16_to_cpu(*he_peer_rx);
|
| 81 | peer_rx = (peer_rx >> i * 2) & IEEE80211_HE_MCS_NOT_SUPPORTED;
|
| 82 |
|
| 83 | peer_tx = le16_to_cpu(*he_peer_tx);
|
| 84 | peer_tx = (peer_tx >> i * 2) & IEEE80211_HE_MCS_NOT_SUPPORTED;
|
| 85 |
|
| 86 | if (peer_tx != IEEE80211_HE_MCS_NOT_SUPPORTED) {
|
| 87 | if (own_rx == IEEE80211_HE_MCS_NOT_SUPPORTED)
|
| 88 | peer_tx = IEEE80211_HE_MCS_NOT_SUPPORTED;
|
| 89 | else if (own_rx < peer_tx)
|
| 90 | peer_tx = own_rx;
|
| 91 | }
|
| 92 |
|
| 93 | if (peer_rx != IEEE80211_HE_MCS_NOT_SUPPORTED) {
|
| 94 | if (own_tx == IEEE80211_HE_MCS_NOT_SUPPORTED)
|
| 95 | peer_rx = IEEE80211_HE_MCS_NOT_SUPPORTED;
|
| 96 | else if (own_tx < peer_rx)
|
| 97 | peer_rx = own_tx;
|
| 98 | }
|
| 99 |
|
| 100 | *he_peer_rx &=
|
| 101 | ~cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << i * 2);
|
| 102 | *he_peer_rx |= cpu_to_le16(peer_rx << i * 2);
|
| 103 |
|
| 104 | *he_peer_tx &=
|
| 105 | ~cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << i * 2);
|
| 106 | *he_peer_tx |= cpu_to_le16(peer_tx << i * 2);
|
| 107 | }
|
| 108 | }
|
| 109 |
|
| 110 | void
|
| 111 | _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
|
| 112 | const struct ieee80211_sta_he_cap *own_he_cap_ptr,
|
| 113 | const u8 *he_cap_ie, u8 he_cap_len,
|
| 114 | const struct ieee80211_he_6ghz_capa *he_6ghz_capa,
|
| 115 | struct link_sta_info *link_sta)
|
| 116 | {
|
| 117 | struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
|
| 118 | struct ieee80211_sta_he_cap own_he_cap;
|
| 119 | struct ieee80211_he_cap_elem *he_cap_ie_elem = (void *)he_cap_ie;
|
| 120 | u8 he_ppe_size;
|
| 121 | u8 mcs_nss_size;
|
| 122 | u8 he_total_size;
|
| 123 | bool own_160, peer_160, own_80p80, peer_80p80;
|
| 124 |
|
| 125 | memset(he_cap, 0, sizeof(*he_cap));
|
| 126 |
|
| 127 | if (!he_cap_ie || !own_he_cap_ptr || !own_he_cap_ptr->has_he)
|
| 128 | return;
|
| 129 |
|
| 130 |
|
| 131 | if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
|
| 132 | return;
|
| 133 |
|
| 134 | own_he_cap = *own_he_cap_ptr;
|
| 135 |
|
| 136 |
|
| 137 | mcs_nss_size = ieee80211_he_mcs_nss_size(he_cap_ie_elem);
|
| 138 | he_ppe_size =
|
| 139 | ieee80211_he_ppe_size(he_cap_ie[sizeof(he_cap->he_cap_elem) +
|
| 140 | mcs_nss_size],
|
| 141 | he_cap_ie_elem->phy_cap_info);
|
| 142 | he_total_size = sizeof(he_cap->he_cap_elem) + mcs_nss_size +
|
| 143 | he_ppe_size;
|
| 144 | if (he_cap_len < he_total_size)
|
| 145 | return;
|
| 146 |
|
| 147 | memcpy(&he_cap->he_cap_elem, he_cap_ie, sizeof(he_cap->he_cap_elem));
|
| 148 |
|
| 149 |
|
| 150 | memcpy(&he_cap->he_mcs_nss_supp,
|
| 151 | &he_cap_ie[sizeof(he_cap->he_cap_elem)], mcs_nss_size);
|
| 152 |
|
| 153 |
|
| 154 | if (he_cap->he_cap_elem.phy_cap_info[6] &
|
| 155 | IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT)
|
| 156 | memcpy(he_cap->ppe_thres,
|
| 157 | &he_cap_ie[sizeof(he_cap->he_cap_elem) + mcs_nss_size],
|
| 158 | he_ppe_size);
|
| 159 |
|
| 160 | he_cap->has_he = true;
|
| 161 |
|
| 162 | if (he_6ghz_capa)
|
| 163 | ieee80211_update_from_he_6ghz_capa(he_6ghz_capa, link_sta);
|
| 164 |
|
| 165 | ieee80211_he_mcs_intersection(&own_he_cap.he_mcs_nss_supp.rx_mcs_80,
|
| 166 | &he_cap->he_mcs_nss_supp.rx_mcs_80,
|
| 167 | &own_he_cap.he_mcs_nss_supp.tx_mcs_80,
|
| 168 | &he_cap->he_mcs_nss_supp.tx_mcs_80);
|
| 169 |
|
| 170 | own_160 = own_he_cap.he_cap_elem.phy_cap_info[0] &
|
| 171 | IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
|
| 172 | peer_160 = he_cap->he_cap_elem.phy_cap_info[0] &
|
| 173 | IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
|
| 174 |
|
| 175 | if (peer_160 && own_160) {
|
| 176 | ieee80211_he_mcs_intersection(&own_he_cap.he_mcs_nss_supp.rx_mcs_160,
|
| 177 | &he_cap->he_mcs_nss_supp.rx_mcs_160,
|
| 178 | &own_he_cap.he_mcs_nss_supp.tx_mcs_160,
|
| 179 | &he_cap->he_mcs_nss_supp.tx_mcs_160);
|
| 180 | } else if (peer_160 && !own_160) {
|
| 181 | ieee80211_he_mcs_disable(&he_cap->he_mcs_nss_supp.rx_mcs_160);
|
| 182 | ieee80211_he_mcs_disable(&he_cap->he_mcs_nss_supp.tx_mcs_160);
|
| 183 | he_cap->he_cap_elem.phy_cap_info[0] &=
|
| 184 | ~IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
|
| 185 | }
|
| 186 |
|
| 187 | own_80p80 = own_he_cap.he_cap_elem.phy_cap_info[0] &
|
| 188 | IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G;
|
| 189 | peer_80p80 = he_cap->he_cap_elem.phy_cap_info[0] &
|
| 190 | IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G;
|
| 191 |
|
| 192 | if (peer_80p80 && own_80p80) {
|
| 193 | ieee80211_he_mcs_intersection(&own_he_cap.he_mcs_nss_supp.rx_mcs_80p80,
|
| 194 | &he_cap->he_mcs_nss_supp.rx_mcs_80p80,
|
| 195 | &own_he_cap.he_mcs_nss_supp.tx_mcs_80p80,
|
| 196 | &he_cap->he_mcs_nss_supp.tx_mcs_80p80);
|
| 197 | } else if (peer_80p80 && !own_80p80) {
|
| 198 | ieee80211_he_mcs_disable(&he_cap->he_mcs_nss_supp.rx_mcs_80p80);
|
| 199 | ieee80211_he_mcs_disable(&he_cap->he_mcs_nss_supp.tx_mcs_80p80);
|
| 200 | he_cap->he_cap_elem.phy_cap_info[0] &=
|
| 201 | ~IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G;
|
| 202 | }
|
| 203 | }
|
| 204 |
|
| 205 | void
|
| 206 | ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
|
| 207 | struct ieee80211_supported_band *sband,
|
| 208 | const u8 *he_cap_ie, u8 he_cap_len,
|
| 209 | const struct ieee80211_he_6ghz_capa *he_6ghz_capa,
|
| 210 | struct link_sta_info *link_sta)
|
| 211 | {
|
| 212 | const struct ieee80211_sta_he_cap *own_he_cap =
|
| 213 | ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif);
|
| 214 |
|
| 215 | _ieee80211_he_cap_ie_to_sta_he_cap(sdata, own_he_cap, he_cap_ie,
|
| 216 | he_cap_len,
|
| 217 | (sband->band == NL80211_BAND_6GHZ) ?
|
| 218 | he_6ghz_capa : NULL,
|
| 219 | link_sta);
|
| 220 | }
|
| 221 |
|
| 222 | void
|
| 223 | ieee80211_he_op_ie_to_bss_conf(struct ieee80211_vif *vif,
|
| 224 | const struct ieee80211_he_operation *he_op_ie)
|
| 225 | {
|
| 226 | memset(&vif->bss_conf.he_oper, 0, sizeof(vif->bss_conf.he_oper));
|
| 227 | if (!he_op_ie)
|
| 228 | return;
|
| 229 |
|
| 230 | vif->bss_conf.he_oper.params = __le32_to_cpu(he_op_ie->he_oper_params);
|
| 231 | vif->bss_conf.he_oper.nss_set = __le16_to_cpu(he_op_ie->he_mcs_nss_set);
|
| 232 | }
|
| 233 |
|
| 234 | void
|
| 235 | ieee80211_he_spr_ie_to_bss_conf(struct ieee80211_vif *vif,
|
| 236 | const struct ieee80211_he_spr *he_spr_ie_elem)
|
| 237 | {
|
| 238 | struct ieee80211_he_obss_pd *he_obss_pd =
|
| 239 | &vif->bss_conf.he_obss_pd;
|
| 240 | const u8 *data;
|
| 241 |
|
| 242 | memset(he_obss_pd, 0, sizeof(*he_obss_pd));
|
| 243 |
|
| 244 | if (!he_spr_ie_elem)
|
| 245 | return;
|
| 246 |
|
| 247 | he_obss_pd->sr_ctrl = he_spr_ie_elem->he_sr_control;
|
| 248 | data = he_spr_ie_elem->optional;
|
| 249 |
|
| 250 | if (he_spr_ie_elem->he_sr_control &
|
| 251 | IEEE80211_HE_SPR_NON_SRG_OFFSET_PRESENT)
|
| 252 | he_obss_pd->non_srg_max_offset = *data++;
|
| 253 |
|
| 254 | if (he_spr_ie_elem->he_sr_control &
|
| 255 | IEEE80211_HE_SPR_SRG_INFORMATION_PRESENT) {
|
| 256 | he_obss_pd->min_offset = *data++;
|
| 257 | he_obss_pd->max_offset = *data++;
|
| 258 | memcpy(he_obss_pd->bss_color_bitmap, data, 8);
|
| 259 | data += 8;
|
| 260 | memcpy(he_obss_pd->partial_bssid_bitmap, data, 8);
|
| 261 | he_obss_pd->enable = true;
|
| 262 | }
|
| 263 | }
|
| 264 |
|
| 265 | bool ieee80211_prepare_rx_omi_bw(struct ieee80211_link_sta *pub_link_sta,
|
| 266 | enum ieee80211_sta_rx_bandwidth bw)
|
| 267 | {
|
| 268 | struct sta_info *sta = container_of(pub_link_sta->sta,
|
| 269 | struct sta_info, sta);
|
| 270 | struct ieee80211_local *local = sta->sdata->local;
|
| 271 | struct link_sta_info *link_sta =
|
| 272 | sdata_dereference(sta->link[pub_link_sta->link_id], sta->sdata);
|
| 273 | struct ieee80211_link_data *link =
|
| 274 | sdata_dereference(sta->sdata->link[pub_link_sta->link_id],
|
| 275 | sta->sdata);
|
| 276 | struct ieee80211_chanctx_conf *conf;
|
| 277 | struct ieee80211_chanctx *chanctx;
|
| 278 | bool ret;
|
| 279 |
|
| 280 | if (WARN_ON(!link || !link_sta || link_sta->pub != pub_link_sta))
|
| 281 | return false;
|
| 282 |
|
| 283 | conf = sdata_dereference(link->conf->chanctx_conf, sta->sdata);
|
| 284 | if (WARN_ON(!conf))
|
| 285 | return false;
|
| 286 |
|
| 287 | trace_api_prepare_rx_omi_bw(local, sta->sdata, link_sta, bw);
|
| 288 |
|
| 289 | chanctx = container_of(conf, typeof(*chanctx), conf);
|
| 290 |
|
| 291 | if (link_sta->rx_omi_bw_staging == bw) {
|
| 292 | ret = false;
|
| 293 | goto trace;
|
| 294 | }
|
| 295 |
|
| 296 |
|
| 297 | if (WARN_ON(link_sta->rx_omi_bw_tx != link_sta->rx_omi_bw_staging ||
|
| 298 | link_sta->rx_omi_bw_rx != link_sta->rx_omi_bw_staging)) {
|
| 299 | ret = false;
|
| 300 | goto trace;
|
| 301 | }
|
| 302 |
|
| 303 | if (bw < link_sta->rx_omi_bw_staging) {
|
| 304 | link_sta->rx_omi_bw_tx = bw;
|
| 305 | ieee80211_link_sta_update_rc_bw(link, link_sta);
|
| 306 | } else {
|
| 307 | link_sta->rx_omi_bw_rx = bw;
|
| 308 | ieee80211_recalc_chanctx_min_def(local, chanctx);
|
| 309 | }
|
| 310 |
|
| 311 | link_sta->rx_omi_bw_staging = bw;
|
| 312 | ret = true;
|
| 313 | trace:
|
| 314 | trace_api_return_bool(local, ret);
|
| 315 | return ret;
|
| 316 | }
|
| 317 | EXPORT_SYMBOL_GPL(ieee80211_prepare_rx_omi_bw);
|
| 318 |
|
| 319 | void ieee80211_finalize_rx_omi_bw(struct ieee80211_link_sta *pub_link_sta)
|
| 320 | {
|
| 321 | struct sta_info *sta = container_of(pub_link_sta->sta,
|
| 322 | struct sta_info, sta);
|
| 323 | struct ieee80211_local *local = sta->sdata->local;
|
| 324 | struct link_sta_info *link_sta =
|
| 325 | sdata_dereference(sta->link[pub_link_sta->link_id], sta->sdata);
|
| 326 | struct ieee80211_link_data *link =
|
| 327 | sdata_dereference(sta->sdata->link[pub_link_sta->link_id],
|
| 328 | sta->sdata);
|
| 329 | struct ieee80211_chanctx_conf *conf;
|
| 330 | struct ieee80211_chanctx *chanctx;
|
| 331 |
|
| 332 | if (WARN_ON(!link || !link_sta || link_sta->pub != pub_link_sta))
|
| 333 | return;
|
| 334 |
|
| 335 | conf = sdata_dereference(link->conf->chanctx_conf, sta->sdata);
|
| 336 | if (WARN_ON(!conf))
|
| 337 | return;
|
| 338 |
|
| 339 | trace_api_finalize_rx_omi_bw(local, sta->sdata, link_sta);
|
| 340 |
|
| 341 | chanctx = container_of(conf, typeof(*chanctx), conf);
|
| 342 |
|
| 343 | if (link_sta->rx_omi_bw_tx != link_sta->rx_omi_bw_staging) {
|
| 344 |
|
| 345 | WARN_ON(link_sta->rx_omi_bw_tx > link_sta->rx_omi_bw_staging);
|
| 346 | link_sta->rx_omi_bw_tx = link_sta->rx_omi_bw_staging;
|
| 347 | ieee80211_link_sta_update_rc_bw(link, link_sta);
|
| 348 | }
|
| 349 |
|
| 350 | if (link_sta->rx_omi_bw_rx != link_sta->rx_omi_bw_staging) {
|
| 351 |
|
| 352 | WARN_ON(link_sta->rx_omi_bw_rx < link_sta->rx_omi_bw_staging);
|
| 353 | link_sta->rx_omi_bw_rx = link_sta->rx_omi_bw_staging;
|
| 354 | ieee80211_recalc_chanctx_min_def(local, chanctx);
|
| 355 | }
|
| 356 |
|
| 357 | trace_api_return_void(local);
|
| 358 | }
|
| 359 | EXPORT_SYMBOL_GPL(ieee80211_finalize_rx_omi_bw);
|