| 1 | |
| 2 | |
| 3 | * HT handling
|
| 4 | *
|
| 5 | * Copyright 2003, Jouni Malinen <[email protected]>
|
| 6 | * Copyright 2002-2005, Instant802 Networks, Inc.
|
| 7 | * Copyright 2005-2006, Devicescape Software, Inc.
|
| 8 | * Copyright 2006-2007 Jiri Benc <[email protected]>
|
| 9 | * Copyright 2007, Michael Wu <[email protected]>
|
| 10 | * Copyright 2007-2010, Intel Corporation
|
| 11 | * Copyright 2017 Intel Deutschland GmbH
|
| 12 | * Copyright(c) 2020-2026 Intel Corporation
|
| 13 |
|
| 14 |
|
| 15 | #include <linux/ieee80211.h>
|
| 16 | #include <linux/export.h>
|
| 17 | #include <net/mac80211.h>
|
| 18 | #include "ieee80211_i.h"
|
| 19 | #include "rate.h"
|
| 20 |
|
| 21 | static void __check_htcap_disable(struct ieee80211_ht_cap *ht_capa,
|
| 22 | struct ieee80211_ht_cap *ht_capa_mask,
|
| 23 | struct ieee80211_sta_ht_cap *ht_cap,
|
| 24 | u16 flag)
|
| 25 | {
|
| 26 | __le16 le_flag = cpu_to_le16(flag);
|
| 27 | if (ht_capa_mask->cap_info & le_flag) {
|
| 28 | if (!(ht_capa->cap_info & le_flag))
|
| 29 | ht_cap->cap &= ~flag;
|
| 30 | }
|
| 31 | }
|
| 32 |
|
| 33 | static void __check_htcap_enable(struct ieee80211_ht_cap *ht_capa,
|
| 34 | struct ieee80211_ht_cap *ht_capa_mask,
|
| 35 | struct ieee80211_sta_ht_cap *ht_cap,
|
| 36 | u16 flag)
|
| 37 | {
|
| 38 | __le16 le_flag = cpu_to_le16(flag);
|
| 39 |
|
| 40 | if ((ht_capa_mask->cap_info & le_flag) &&
|
| 41 | (ht_capa->cap_info & le_flag))
|
| 42 | ht_cap->cap |= flag;
|
| 43 | }
|
| 44 |
|
| 45 | void ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata,
|
| 46 | struct ieee80211_sta_ht_cap *ht_cap)
|
| 47 | {
|
| 48 | struct ieee80211_ht_cap *ht_capa, *ht_capa_mask;
|
| 49 | u8 *scaps, *smask;
|
| 50 | int i;
|
| 51 |
|
| 52 | if (!ht_cap->ht_supported)
|
| 53 | return;
|
| 54 |
|
| 55 | switch (sdata->vif.type) {
|
| 56 | case NL80211_IFTYPE_STATION:
|
| 57 | ht_capa = &sdata->u.mgd.ht_capa;
|
| 58 | ht_capa_mask = &sdata->u.mgd.ht_capa_mask;
|
| 59 | break;
|
| 60 | case NL80211_IFTYPE_ADHOC:
|
| 61 | ht_capa = &sdata->u.ibss.ht_capa;
|
| 62 | ht_capa_mask = &sdata->u.ibss.ht_capa_mask;
|
| 63 | break;
|
| 64 | default:
|
| 65 | WARN_ON_ONCE(1);
|
| 66 | return;
|
| 67 | }
|
| 68 |
|
| 69 | scaps = (u8 *)(&ht_capa->mcs.rx_mask);
|
| 70 | smask = (u8 *)(&ht_capa_mask->mcs.rx_mask);
|
| 71 |
|
| 72 | |
| 73 | * ht_capa_mod_mask logic in main.c as well.
|
| 74 | * And, if this method can ever change ht_cap.ht_supported, fix
|
| 75 | * the check in ieee80211_add_ht_ie.
|
| 76 |
|
| 77 |
|
| 78 |
|
| 79 | for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++) {
|
| 80 | u8 m = smask[i];
|
| 81 | ht_cap->mcs.rx_mask[i] &= ~m;
|
| 82 |
|
| 83 | ht_cap->mcs.rx_mask[i] |= (m & scaps[i]);
|
| 84 | }
|
| 85 |
|
| 86 |
|
| 87 | __check_htcap_disable(ht_capa, ht_capa_mask, ht_cap,
|
| 88 | IEEE80211_HT_CAP_SUP_WIDTH_20_40);
|
| 89 | __check_htcap_disable(ht_capa, ht_capa_mask, ht_cap,
|
| 90 | IEEE80211_HT_CAP_SGI_40);
|
| 91 |
|
| 92 |
|
| 93 | __check_htcap_disable(ht_capa, ht_capa_mask, ht_cap,
|
| 94 | IEEE80211_HT_CAP_SGI_20);
|
| 95 |
|
| 96 |
|
| 97 | __check_htcap_disable(ht_capa, ht_capa_mask, ht_cap,
|
| 98 | IEEE80211_HT_CAP_MAX_AMSDU);
|
| 99 |
|
| 100 |
|
| 101 | __check_htcap_disable(ht_capa, ht_capa_mask, ht_cap,
|
| 102 | IEEE80211_HT_CAP_LDPC_CODING);
|
| 103 |
|
| 104 |
|
| 105 | __check_htcap_enable(ht_capa, ht_capa_mask, ht_cap,
|
| 106 | IEEE80211_HT_CAP_40MHZ_INTOLERANT);
|
| 107 |
|
| 108 |
|
| 109 | __check_htcap_enable(ht_capa, ht_capa_mask, ht_cap,
|
| 110 | IEEE80211_HT_CAP_TX_STBC);
|
| 111 |
|
| 112 |
|
| 113 | if (ht_capa_mask->cap_info & cpu_to_le16(IEEE80211_HT_CAP_RX_STBC))
|
| 114 | ht_cap->cap |= le16_to_cpu(ht_capa->cap_info) &
|
| 115 | IEEE80211_HT_CAP_RX_STBC;
|
| 116 |
|
| 117 |
|
| 118 | if (ht_capa_mask->ampdu_params_info &
|
| 119 | IEEE80211_HT_AMPDU_PARM_FACTOR) {
|
| 120 | u8 n = ht_capa->ampdu_params_info &
|
| 121 | IEEE80211_HT_AMPDU_PARM_FACTOR;
|
| 122 | if (n < ht_cap->ampdu_factor)
|
| 123 | ht_cap->ampdu_factor = n;
|
| 124 | }
|
| 125 |
|
| 126 |
|
| 127 | if (ht_capa_mask->ampdu_params_info &
|
| 128 | IEEE80211_HT_AMPDU_PARM_DENSITY) {
|
| 129 | u8 n = (ht_capa->ampdu_params_info &
|
| 130 | IEEE80211_HT_AMPDU_PARM_DENSITY)
|
| 131 | >> IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT;
|
| 132 | if (n > ht_cap->ampdu_density)
|
| 133 | ht_cap->ampdu_density = n;
|
| 134 | }
|
| 135 | }
|
| 136 |
|
| 137 |
|
| 138 | bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
|
| 139 | const struct ieee80211_sta_ht_cap *own_cap_ptr,
|
| 140 | const struct ieee80211_ht_cap *ht_cap_ie,
|
| 141 | struct link_sta_info *link_sta)
|
| 142 | {
|
| 143 | struct sta_info *sta = link_sta->sta;
|
| 144 | struct ieee80211_sta_ht_cap ht_cap, own_cap;
|
| 145 | u8 ampdu_info, tx_mcs_set_cap;
|
| 146 | int i, max_tx_streams;
|
| 147 | bool changed;
|
| 148 |
|
| 149 | memset(&ht_cap, 0, sizeof(ht_cap));
|
| 150 |
|
| 151 | if (!ht_cap_ie || !own_cap_ptr->ht_supported)
|
| 152 | goto apply;
|
| 153 |
|
| 154 |
|
| 155 | if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
|
| 156 | return 0;
|
| 157 |
|
| 158 | ht_cap.ht_supported = true;
|
| 159 |
|
| 160 | own_cap = *own_cap_ptr;
|
| 161 |
|
| 162 | |
| 163 | * If user has specified capability over-rides, take care
|
| 164 | * of that if the station we're setting up is the AP or TDLS peer that
|
| 165 | * we advertised a restricted capability set to. Override
|
| 166 | * our own capabilities and then use those below.
|
| 167 |
|
| 168 | if (sdata->vif.type == NL80211_IFTYPE_STATION ||
|
| 169 | sdata->vif.type == NL80211_IFTYPE_ADHOC)
|
| 170 | ieee80211_apply_htcap_overrides(sdata, &own_cap);
|
| 171 |
|
| 172 | |
| 173 | * The bits listed in this expression should be
|
| 174 | * the same for the peer and us, if the station
|
| 175 | * advertises more then we can't use those thus
|
| 176 | * we mask them out.
|
| 177 |
|
| 178 | ht_cap.cap = le16_to_cpu(ht_cap_ie->cap_info) &
|
| 179 | (own_cap.cap | ~(IEEE80211_HT_CAP_LDPC_CODING |
|
| 180 | IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
|
| 181 | IEEE80211_HT_CAP_GRN_FLD |
|
| 182 | IEEE80211_HT_CAP_SGI_20 |
|
| 183 | IEEE80211_HT_CAP_SGI_40 |
|
| 184 | IEEE80211_HT_CAP_DSSSCCK40));
|
| 185 |
|
| 186 | |
| 187 | * The STBC bits are asymmetric -- if we don't have
|
| 188 | * TX then mask out the peer's RX and vice versa.
|
| 189 |
|
| 190 | if (!(own_cap.cap & IEEE80211_HT_CAP_TX_STBC))
|
| 191 | ht_cap.cap &= ~IEEE80211_HT_CAP_RX_STBC;
|
| 192 | if (!(own_cap.cap & IEEE80211_HT_CAP_RX_STBC))
|
| 193 | ht_cap.cap &= ~IEEE80211_HT_CAP_TX_STBC;
|
| 194 |
|
| 195 | ampdu_info = ht_cap_ie->ampdu_params_info;
|
| 196 | ht_cap.ampdu_factor =
|
| 197 | ampdu_info & IEEE80211_HT_AMPDU_PARM_FACTOR;
|
| 198 | ht_cap.ampdu_density =
|
| 199 | (ampdu_info & IEEE80211_HT_AMPDU_PARM_DENSITY) >> 2;
|
| 200 |
|
| 201 |
|
| 202 | tx_mcs_set_cap = own_cap.mcs.tx_params;
|
| 203 |
|
| 204 |
|
| 205 | ht_cap.mcs.tx_params = ht_cap_ie->mcs.tx_params;
|
| 206 |
|
| 207 |
|
| 208 | if (!(tx_mcs_set_cap & IEEE80211_HT_MCS_TX_DEFINED))
|
| 209 | goto apply;
|
| 210 |
|
| 211 |
|
| 212 | if (tx_mcs_set_cap & IEEE80211_HT_MCS_TX_RX_DIFF)
|
| 213 | max_tx_streams =
|
| 214 | ((tx_mcs_set_cap & IEEE80211_HT_MCS_TX_MAX_STREAMS_MASK)
|
| 215 | >> IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT) + 1;
|
| 216 | else
|
| 217 | max_tx_streams = IEEE80211_HT_MCS_TX_MAX_STREAMS;
|
| 218 |
|
| 219 | |
| 220 | * 802.11n-2009 20.3.5 / 20.6 says:
|
| 221 | * - indices 0 to 7 and 32 are single spatial stream
|
| 222 | * - 8 to 31 are multiple spatial streams using equal modulation
|
| 223 | * [8..15 for two streams, 16..23 for three and 24..31 for four]
|
| 224 | * - remainder are multiple spatial streams using unequal modulation
|
| 225 |
|
| 226 | for (i = 0; i < max_tx_streams; i++)
|
| 227 | ht_cap.mcs.rx_mask[i] =
|
| 228 | own_cap.mcs.rx_mask[i] & ht_cap_ie->mcs.rx_mask[i];
|
| 229 |
|
| 230 | if (tx_mcs_set_cap & IEEE80211_HT_MCS_TX_UNEQUAL_MODULATION)
|
| 231 | for (i = IEEE80211_HT_MCS_UNEQUAL_MODULATION_START_BYTE;
|
| 232 | i < IEEE80211_HT_MCS_MASK_LEN; i++)
|
| 233 | ht_cap.mcs.rx_mask[i] =
|
| 234 | own_cap.mcs.rx_mask[i] &
|
| 235 | ht_cap_ie->mcs.rx_mask[i];
|
| 236 |
|
| 237 |
|
| 238 | if (own_cap.mcs.rx_mask[32/8] & ht_cap_ie->mcs.rx_mask[32/8] & 1)
|
| 239 | ht_cap.mcs.rx_mask[32/8] |= 1;
|
| 240 |
|
| 241 |
|
| 242 | ht_cap.mcs.rx_highest = ht_cap_ie->mcs.rx_highest;
|
| 243 |
|
| 244 | if (ht_cap.cap & IEEE80211_HT_CAP_MAX_AMSDU)
|
| 245 | link_sta->pub->agg.max_amsdu_len = IEEE80211_MAX_MPDU_LEN_HT_7935;
|
| 246 | else
|
| 247 | link_sta->pub->agg.max_amsdu_len = IEEE80211_MAX_MPDU_LEN_HT_3839;
|
| 248 |
|
| 249 | ieee80211_sta_recalc_aggregates(&sta->sta);
|
| 250 |
|
| 251 | apply:
|
| 252 | changed = memcmp(&link_sta->pub->ht_cap, &ht_cap, sizeof(ht_cap));
|
| 253 |
|
| 254 | memcpy(&link_sta->pub->ht_cap, &ht_cap, sizeof(ht_cap));
|
| 255 |
|
| 256 | if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
|
| 257 | sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
|
| 258 | sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
|
| 259 | sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
|
| 260 | enum ieee80211_smps_mode smps_mode;
|
| 261 |
|
| 262 | switch ((ht_cap.cap & IEEE80211_HT_CAP_SM_PS)
|
| 263 | >> IEEE80211_HT_CAP_SM_PS_SHIFT) {
|
| 264 | case WLAN_HT_CAP_SM_PS_INVALID:
|
| 265 | case WLAN_HT_CAP_SM_PS_STATIC:
|
| 266 | smps_mode = IEEE80211_SMPS_STATIC;
|
| 267 | break;
|
| 268 | case WLAN_HT_CAP_SM_PS_DYNAMIC:
|
| 269 | smps_mode = IEEE80211_SMPS_DYNAMIC;
|
| 270 | break;
|
| 271 | case WLAN_HT_CAP_SM_PS_DISABLED:
|
| 272 | smps_mode = IEEE80211_SMPS_OFF;
|
| 273 | break;
|
| 274 | }
|
| 275 |
|
| 276 | if (smps_mode != link_sta->pub->smps_mode)
|
| 277 | changed = true;
|
| 278 | link_sta->pub->smps_mode = smps_mode;
|
| 279 | } else {
|
| 280 | link_sta->pub->smps_mode = IEEE80211_SMPS_OFF;
|
| 281 | }
|
| 282 |
|
| 283 | return changed;
|
| 284 | }
|
| 285 |
|
| 286 | void ieee80211_sta_tear_down_BA_sessions(struct sta_info *sta,
|
| 287 | enum ieee80211_agg_stop_reason reason)
|
| 288 | {
|
| 289 | int i;
|
| 290 |
|
| 291 | lockdep_assert_wiphy(sta->local->hw.wiphy);
|
| 292 |
|
| 293 | for (i = 0; i < IEEE80211_NUM_TIDS; i++)
|
| 294 | __ieee80211_stop_rx_ba_session(sta, i, WLAN_BACK_RECIPIENT,
|
| 295 | WLAN_REASON_QSTA_LEAVE_QBSS,
|
| 296 | reason != AGG_STOP_DESTROY_STA &&
|
| 297 | reason != AGG_STOP_PEER_REQUEST);
|
| 298 |
|
| 299 | for (i = 0; i < IEEE80211_NUM_TIDS; i++)
|
| 300 | __ieee80211_stop_tx_ba_session(sta, i, reason);
|
| 301 |
|
| 302 | |
| 303 | * In case the tear down is part of a reconfigure due to HW restart
|
| 304 | * request, it is possible that the low level driver requested to stop
|
| 305 | * the BA session, so handle it to properly clean tid_tx data.
|
| 306 |
|
| 307 | if(reason == AGG_STOP_DESTROY_STA) {
|
| 308 | wiphy_work_cancel(sta->local->hw.wiphy, &sta->ampdu_mlme.work);
|
| 309 |
|
| 310 | for (i = 0; i < IEEE80211_NUM_TIDS; i++) {
|
| 311 | struct tid_ampdu_tx *tid_tx =
|
| 312 | rcu_dereference_protected_tid_tx(sta, i);
|
| 313 |
|
| 314 | if (!tid_tx)
|
| 315 | continue;
|
| 316 |
|
| 317 | if (test_and_clear_bit(HT_AGG_STATE_STOP_CB, &tid_tx->state))
|
| 318 | ieee80211_stop_tx_ba_cb(sta, i, tid_tx);
|
| 319 | }
|
| 320 | }
|
| 321 | }
|
| 322 |
|
| 323 | void ieee80211_ba_session_work(struct wiphy *wiphy, struct wiphy_work *work)
|
| 324 | {
|
| 325 | struct sta_info *sta =
|
| 326 | container_of(work, struct sta_info, ampdu_mlme.work);
|
| 327 | struct tid_ampdu_tx *tid_tx;
|
| 328 | bool blocked;
|
| 329 | int tid;
|
| 330 |
|
| 331 | lockdep_assert_wiphy(sta->local->hw.wiphy);
|
| 332 |
|
| 333 |
|
| 334 | blocked = test_sta_flag(sta, WLAN_STA_BLOCK_BA);
|
| 335 |
|
| 336 | for (tid = 0; tid < IEEE80211_NUM_TIDS; tid++) {
|
| 337 | if (test_and_clear_bit(tid, sta->ampdu_mlme.tid_rx_timer_expired))
|
| 338 | __ieee80211_stop_rx_ba_session(
|
| 339 | sta, tid, WLAN_BACK_RECIPIENT,
|
| 340 | WLAN_REASON_QSTA_TIMEOUT, true);
|
| 341 |
|
| 342 | if (test_and_clear_bit(tid,
|
| 343 | sta->ampdu_mlme.tid_rx_stop_requested))
|
| 344 | __ieee80211_stop_rx_ba_session(
|
| 345 | sta, tid, WLAN_BACK_RECIPIENT,
|
| 346 | WLAN_REASON_UNSPECIFIED, true);
|
| 347 |
|
| 348 | if (!blocked &&
|
| 349 | test_and_clear_bit(tid,
|
| 350 | sta->ampdu_mlme.tid_rx_manage_offl))
|
| 351 | __ieee80211_start_rx_ba_session(sta, 0, 0, 0, 1, tid,
|
| 352 | IEEE80211_MAX_AMPDU_BUF_HT,
|
| 353 | false, true, false, 0);
|
| 354 |
|
| 355 | if (test_and_clear_bit(tid + IEEE80211_NUM_TIDS,
|
| 356 | sta->ampdu_mlme.tid_rx_manage_offl))
|
| 357 | __ieee80211_stop_rx_ba_session(
|
| 358 | sta, tid, WLAN_BACK_RECIPIENT,
|
| 359 | 0, false);
|
| 360 |
|
| 361 | spin_lock_bh(&sta->lock);
|
| 362 |
|
| 363 | tid_tx = sta->ampdu_mlme.tid_start_tx[tid];
|
| 364 | if (!blocked && tid_tx) {
|
| 365 | struct txq_info *txqi = to_txq_info(sta->sta.txq[tid]);
|
| 366 | struct ieee80211_sub_if_data *sdata =
|
| 367 | vif_to_sdata(txqi->txq.vif);
|
| 368 | struct fq *fq = &sdata->local->fq;
|
| 369 |
|
| 370 | spin_lock_bh(&fq->lock);
|
| 371 |
|
| 372 |
|
| 373 | set_bit(IEEE80211_TXQ_STOP, &txqi->flags);
|
| 374 |
|
| 375 | if (!skb_queue_empty(&txqi->frags)) {
|
| 376 | |
| 377 | * finish. Reschedule worker to retry later.
|
| 378 |
|
| 379 |
|
| 380 | spin_unlock_bh(&fq->lock);
|
| 381 | spin_unlock_bh(&sta->lock);
|
| 382 |
|
| 383 | |
| 384 | * to send out the queued frags
|
| 385 |
|
| 386 | synchronize_net();
|
| 387 |
|
| 388 | wiphy_work_queue(sdata->local->hw.wiphy, work);
|
| 389 | return;
|
| 390 | }
|
| 391 |
|
| 392 | spin_unlock_bh(&fq->lock);
|
| 393 |
|
| 394 | |
| 395 | * Assign it over to the normal tid_tx array
|
| 396 | * where it "goes live".
|
| 397 |
|
| 398 |
|
| 399 | sta->ampdu_mlme.tid_start_tx[tid] = NULL;
|
| 400 |
|
| 401 | if (sta->ampdu_mlme.tid_tx[tid])
|
| 402 | kfree(tid_tx);
|
| 403 | else
|
| 404 | ieee80211_assign_tid_tx(sta, tid, tid_tx);
|
| 405 | spin_unlock_bh(&sta->lock);
|
| 406 |
|
| 407 | ieee80211_tx_ba_session_handle_start(sta, tid);
|
| 408 | continue;
|
| 409 | }
|
| 410 | spin_unlock_bh(&sta->lock);
|
| 411 |
|
| 412 | tid_tx = rcu_dereference_protected_tid_tx(sta, tid);
|
| 413 | if (!tid_tx)
|
| 414 | continue;
|
| 415 |
|
| 416 | if (!blocked &&
|
| 417 | test_and_clear_bit(HT_AGG_STATE_START_CB, &tid_tx->state))
|
| 418 | ieee80211_start_tx_ba_cb(sta, tid, tid_tx);
|
| 419 | if (test_and_clear_bit(HT_AGG_STATE_WANT_STOP, &tid_tx->state))
|
| 420 | __ieee80211_stop_tx_ba_session(sta, tid,
|
| 421 | AGG_STOP_LOCAL_REQUEST);
|
| 422 | if (test_and_clear_bit(HT_AGG_STATE_STOP_CB, &tid_tx->state))
|
| 423 | ieee80211_stop_tx_ba_cb(sta, tid, tid_tx);
|
| 424 | }
|
| 425 | }
|
| 426 |
|
| 427 | void ieee80211_send_delba(struct ieee80211_sub_if_data *sdata,
|
| 428 | const u8 *da, u16 tid,
|
| 429 | u16 initiator, u16 reason_code,
|
| 430 | bool use_ndp)
|
| 431 | {
|
| 432 | struct ieee80211_local *local = sdata->local;
|
| 433 | struct sk_buff *skb;
|
| 434 | struct ieee80211_mgmt *mgmt;
|
| 435 | u16 params;
|
| 436 |
|
| 437 | skb = dev_alloc_skb(IEEE80211_MIN_ACTION_SIZE(delba) +
|
| 438 | local->hw.extra_tx_headroom);
|
| 439 | if (!skb)
|
| 440 | return;
|
| 441 |
|
| 442 | skb_reserve(skb, local->hw.extra_tx_headroom);
|
| 443 | mgmt = ieee80211_mgmt_ba(skb, da, sdata);
|
| 444 |
|
| 445 | skb_put(skb, 2 + sizeof(mgmt->u.action.delba));
|
| 446 |
|
| 447 | mgmt->u.action.category = WLAN_CATEGORY_BACK;
|
| 448 | mgmt->u.action.action_code = use_ndp ?
|
| 449 | WLAN_ACTION_NDP_DELBA : WLAN_ACTION_DELBA;
|
| 450 | params = (u16)(initiator << 11);
|
| 451 | params |= (u16)(tid << 12);
|
| 452 |
|
| 453 | mgmt->u.action.delba.params = cpu_to_le16(params);
|
| 454 | mgmt->u.action.delba.reason_code = cpu_to_le16(reason_code);
|
| 455 |
|
| 456 | ieee80211_tx_skb(sdata, skb);
|
| 457 | }
|
| 458 |
|
| 459 | void ieee80211_process_delba(struct ieee80211_sub_if_data *sdata,
|
| 460 | struct sta_info *sta,
|
| 461 | struct ieee80211_mgmt *mgmt, size_t len)
|
| 462 | {
|
| 463 | u16 tid, params;
|
| 464 | u16 initiator;
|
| 465 |
|
| 466 | params = le16_to_cpu(mgmt->u.action.delba.params);
|
| 467 | tid = (params & IEEE80211_DELBA_PARAM_TID_MASK) >> 12;
|
| 468 | initiator = (params & IEEE80211_DELBA_PARAM_INITIATOR_MASK) >> 11;
|
| 469 |
|
| 470 | ht_dbg_ratelimited(sdata, "delba from %pM (%s) tid %d reason code %d\n",
|
| 471 | mgmt->sa, initiator ? "initiator" : "recipient",
|
| 472 | tid,
|
| 473 | le16_to_cpu(mgmt->u.action.delba.reason_code));
|
| 474 |
|
| 475 | if (initiator == WLAN_BACK_INITIATOR)
|
| 476 | __ieee80211_stop_rx_ba_session(sta, tid, WLAN_BACK_INITIATOR, 0,
|
| 477 | true);
|
| 478 | else
|
| 479 | __ieee80211_stop_tx_ba_session(sta, tid, AGG_STOP_PEER_REQUEST);
|
| 480 | }
|
| 481 |
|
| 482 | enum nl80211_smps_mode
|
| 483 | ieee80211_smps_mode_to_smps_mode(enum ieee80211_smps_mode smps)
|
| 484 | {
|
| 485 | switch (smps) {
|
| 486 | case IEEE80211_SMPS_OFF:
|
| 487 | return NL80211_SMPS_OFF;
|
| 488 | case IEEE80211_SMPS_STATIC:
|
| 489 | return NL80211_SMPS_STATIC;
|
| 490 | case IEEE80211_SMPS_DYNAMIC:
|
| 491 | return NL80211_SMPS_DYNAMIC;
|
| 492 | default:
|
| 493 | return NL80211_SMPS_OFF;
|
| 494 | }
|
| 495 | }
|
| 496 |
|
| 497 | int ieee80211_send_smps_action(struct ieee80211_sub_if_data *sdata,
|
| 498 | enum ieee80211_smps_mode smps, const u8 *da,
|
| 499 | const u8 *bssid, int link_id)
|
| 500 | {
|
| 501 | struct ieee80211_local *local = sdata->local;
|
| 502 | struct sk_buff *skb;
|
| 503 | struct ieee80211_mgmt *action_frame;
|
| 504 | struct ieee80211_tx_info *info;
|
| 505 | u8 status_link_id = link_id < 0 ? 0 : link_id;
|
| 506 |
|
| 507 | skb = dev_alloc_skb(IEEE80211_MIN_ACTION_SIZE(ht_smps) +
|
| 508 | local->hw.extra_tx_headroom);
|
| 509 | if (!skb)
|
| 510 | return -ENOMEM;
|
| 511 |
|
| 512 | skb_reserve(skb, local->hw.extra_tx_headroom);
|
| 513 | action_frame = skb_put_zero(skb, IEEE80211_MIN_ACTION_SIZE(ht_smps));
|
| 514 | memcpy(action_frame->da, da, ETH_ALEN);
|
| 515 | memcpy(action_frame->sa, sdata->dev->dev_addr, ETH_ALEN);
|
| 516 | memcpy(action_frame->bssid, bssid, ETH_ALEN);
|
| 517 | action_frame->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
|
| 518 | IEEE80211_STYPE_ACTION);
|
| 519 | action_frame->u.action.category = WLAN_CATEGORY_HT;
|
| 520 | action_frame->u.action.action_code = WLAN_HT_ACTION_SMPS;
|
| 521 | switch (smps) {
|
| 522 | case IEEE80211_SMPS_AUTOMATIC:
|
| 523 | case IEEE80211_SMPS_NUM_MODES:
|
| 524 | WARN_ON(1);
|
| 525 | smps = IEEE80211_SMPS_OFF;
|
| 526 | fallthrough;
|
| 527 | case IEEE80211_SMPS_OFF:
|
| 528 | action_frame->u.action.ht_smps.smps_control =
|
| 529 | WLAN_HT_SMPS_CONTROL_DISABLED;
|
| 530 | break;
|
| 531 | case IEEE80211_SMPS_STATIC:
|
| 532 | action_frame->u.action.ht_smps.smps_control =
|
| 533 | WLAN_HT_SMPS_CONTROL_STATIC;
|
| 534 | break;
|
| 535 | case IEEE80211_SMPS_DYNAMIC:
|
| 536 | action_frame->u.action.ht_smps.smps_control =
|
| 537 | WLAN_HT_SMPS_CONTROL_DYNAMIC;
|
| 538 | break;
|
| 539 | }
|
| 540 |
|
| 541 |
|
| 542 | info = IEEE80211_SKB_CB(skb);
|
| 543 | info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
|
| 544 |
|
| 545 | info->status_data = IEEE80211_STATUS_TYPE_SMPS |
|
| 546 | u16_encode_bits(status_link_id << 2 | smps,
|
| 547 | IEEE80211_STATUS_SUBDATA_MASK);
|
| 548 | ieee80211_tx_skb_tid(sdata, skb, 7, link_id);
|
| 549 |
|
| 550 | return 0;
|
| 551 | }
|
| 552 |
|
| 553 | void ieee80211_request_smps(struct ieee80211_vif *vif, unsigned int link_id,
|
| 554 | enum ieee80211_smps_mode smps_mode)
|
| 555 | {
|
| 556 | struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
| 557 | struct ieee80211_link_data *link;
|
| 558 |
|
| 559 | if (WARN_ON_ONCE(vif->type != NL80211_IFTYPE_STATION))
|
| 560 | return;
|
| 561 |
|
| 562 | rcu_read_lock();
|
| 563 | link = rcu_dereference(sdata->link[link_id]);
|
| 564 | if (WARN_ON(!link))
|
| 565 | goto out;
|
| 566 |
|
| 567 | trace_api_request_smps(sdata->local, sdata, link, smps_mode);
|
| 568 |
|
| 569 | if (link->u.mgd.driver_smps_mode == smps_mode)
|
| 570 | goto out;
|
| 571 |
|
| 572 | link->u.mgd.driver_smps_mode = smps_mode;
|
| 573 | wiphy_work_queue(sdata->local->hw.wiphy,
|
| 574 | &link->u.mgd.request_smps_work);
|
| 575 | out:
|
| 576 | rcu_read_unlock();
|
| 577 | }
|
| 578 |
|
| 579 | EXPORT_SYMBOL_GPL(ieee80211_request_smps);
|
| 580 |
|
| 581 | void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
|
| 582 | struct ieee80211_sub_if_data *sdata,
|
| 583 | struct sta_info *sta,
|
| 584 | struct link_sta_info *link_sta,
|
| 585 | u8 chanwidth, enum nl80211_band band)
|
| 586 | {
|
| 587 | enum ieee80211_sta_rx_bandwidth max_bw;
|
| 588 | struct sta_opmode_info sta_opmode = {
|
| 589 | .changed = STA_OPMODE_MAX_BW_CHANGED,
|
| 590 | };
|
| 591 | struct ieee80211_link_data *link;
|
| 592 |
|
| 593 | lockdep_assert_wiphy(local->hw.wiphy);
|
| 594 |
|
| 595 | link = sdata_dereference(sdata->link[link_sta->link_id], sdata);
|
| 596 | if (WARN_ON(!link))
|
| 597 | return;
|
| 598 |
|
| 599 | if (chanwidth == IEEE80211_HT_CHANWIDTH_20MHZ)
|
| 600 | max_bw = IEEE80211_STA_RX_BW_20;
|
| 601 | else
|
| 602 | max_bw = IEEE80211_STA_RX_BW_MAX;
|
| 603 |
|
| 604 |
|
| 605 | link_sta->op_mode_bw = max_bw;
|
| 606 |
|
| 607 | if (!ieee80211_link_sta_update_rc_bw(link, link_sta))
|
| 608 | return;
|
| 609 |
|
| 610 | sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(link_sta->pub->bandwidth);
|
| 611 |
|
| 612 | cfg80211_sta_opmode_change_notify(sdata->dev, sta->addr,
|
| 613 | &sta_opmode, GFP_KERNEL);
|
| 614 | }
|