xref: /kernel/linux/linux-6.6/net/mac80211/ht.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * HT handling
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2003, Jouni Malinen <jkmaline@cc.hut.fi>
662306a36Sopenharmony_ci * Copyright 2002-2005, Instant802 Networks, Inc.
762306a36Sopenharmony_ci * Copyright 2005-2006, Devicescape Software, Inc.
862306a36Sopenharmony_ci * Copyright 2006-2007	Jiri Benc <jbenc@suse.cz>
962306a36Sopenharmony_ci * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
1062306a36Sopenharmony_ci * Copyright 2007-2010, Intel Corporation
1162306a36Sopenharmony_ci * Copyright 2017	Intel Deutschland GmbH
1262306a36Sopenharmony_ci * Copyright(c) 2020-2023 Intel Corporation
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/ieee80211.h>
1662306a36Sopenharmony_ci#include <linux/export.h>
1762306a36Sopenharmony_ci#include <net/mac80211.h>
1862306a36Sopenharmony_ci#include "ieee80211_i.h"
1962306a36Sopenharmony_ci#include "rate.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic void __check_htcap_disable(struct ieee80211_ht_cap *ht_capa,
2262306a36Sopenharmony_ci				  struct ieee80211_ht_cap *ht_capa_mask,
2362306a36Sopenharmony_ci				  struct ieee80211_sta_ht_cap *ht_cap,
2462306a36Sopenharmony_ci				  u16 flag)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	__le16 le_flag = cpu_to_le16(flag);
2762306a36Sopenharmony_ci	if (ht_capa_mask->cap_info & le_flag) {
2862306a36Sopenharmony_ci		if (!(ht_capa->cap_info & le_flag))
2962306a36Sopenharmony_ci			ht_cap->cap &= ~flag;
3062306a36Sopenharmony_ci	}
3162306a36Sopenharmony_ci}
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic void __check_htcap_enable(struct ieee80211_ht_cap *ht_capa,
3462306a36Sopenharmony_ci				  struct ieee80211_ht_cap *ht_capa_mask,
3562306a36Sopenharmony_ci				  struct ieee80211_sta_ht_cap *ht_cap,
3662306a36Sopenharmony_ci				  u16 flag)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	__le16 le_flag = cpu_to_le16(flag);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	if ((ht_capa_mask->cap_info & le_flag) &&
4162306a36Sopenharmony_ci	    (ht_capa->cap_info & le_flag))
4262306a36Sopenharmony_ci		ht_cap->cap |= flag;
4362306a36Sopenharmony_ci}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_civoid ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata,
4662306a36Sopenharmony_ci				     struct ieee80211_sta_ht_cap *ht_cap)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	struct ieee80211_ht_cap *ht_capa, *ht_capa_mask;
4962306a36Sopenharmony_ci	u8 *scaps, *smask;
5062306a36Sopenharmony_ci	int i;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	if (!ht_cap->ht_supported)
5362306a36Sopenharmony_ci		return;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	switch (sdata->vif.type) {
5662306a36Sopenharmony_ci	case NL80211_IFTYPE_STATION:
5762306a36Sopenharmony_ci		ht_capa = &sdata->u.mgd.ht_capa;
5862306a36Sopenharmony_ci		ht_capa_mask = &sdata->u.mgd.ht_capa_mask;
5962306a36Sopenharmony_ci		break;
6062306a36Sopenharmony_ci	case NL80211_IFTYPE_ADHOC:
6162306a36Sopenharmony_ci		ht_capa = &sdata->u.ibss.ht_capa;
6262306a36Sopenharmony_ci		ht_capa_mask = &sdata->u.ibss.ht_capa_mask;
6362306a36Sopenharmony_ci		break;
6462306a36Sopenharmony_ci	default:
6562306a36Sopenharmony_ci		WARN_ON_ONCE(1);
6662306a36Sopenharmony_ci		return;
6762306a36Sopenharmony_ci	}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	scaps = (u8 *)(&ht_capa->mcs.rx_mask);
7062306a36Sopenharmony_ci	smask = (u8 *)(&ht_capa_mask->mcs.rx_mask);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/* NOTE:  If you add more over-rides here, update register_hw
7362306a36Sopenharmony_ci	 * ht_capa_mod_mask logic in main.c as well.
7462306a36Sopenharmony_ci	 * And, if this method can ever change ht_cap.ht_supported, fix
7562306a36Sopenharmony_ci	 * the check in ieee80211_add_ht_ie.
7662306a36Sopenharmony_ci	 */
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	/* check for HT over-rides, MCS rates first. */
7962306a36Sopenharmony_ci	for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++) {
8062306a36Sopenharmony_ci		u8 m = smask[i];
8162306a36Sopenharmony_ci		ht_cap->mcs.rx_mask[i] &= ~m; /* turn off all masked bits */
8262306a36Sopenharmony_ci		/* Add back rates that are supported */
8362306a36Sopenharmony_ci		ht_cap->mcs.rx_mask[i] |= (m & scaps[i]);
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	/* Force removal of HT-40 capabilities? */
8762306a36Sopenharmony_ci	__check_htcap_disable(ht_capa, ht_capa_mask, ht_cap,
8862306a36Sopenharmony_ci			      IEEE80211_HT_CAP_SUP_WIDTH_20_40);
8962306a36Sopenharmony_ci	__check_htcap_disable(ht_capa, ht_capa_mask, ht_cap,
9062306a36Sopenharmony_ci			      IEEE80211_HT_CAP_SGI_40);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	/* Allow user to disable SGI-20 (SGI-40 is handled above) */
9362306a36Sopenharmony_ci	__check_htcap_disable(ht_capa, ht_capa_mask, ht_cap,
9462306a36Sopenharmony_ci			      IEEE80211_HT_CAP_SGI_20);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	/* Allow user to disable the max-AMSDU bit. */
9762306a36Sopenharmony_ci	__check_htcap_disable(ht_capa, ht_capa_mask, ht_cap,
9862306a36Sopenharmony_ci			      IEEE80211_HT_CAP_MAX_AMSDU);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	/* Allow user to disable LDPC */
10162306a36Sopenharmony_ci	__check_htcap_disable(ht_capa, ht_capa_mask, ht_cap,
10262306a36Sopenharmony_ci			      IEEE80211_HT_CAP_LDPC_CODING);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	/* Allow user to enable 40 MHz intolerant bit. */
10562306a36Sopenharmony_ci	__check_htcap_enable(ht_capa, ht_capa_mask, ht_cap,
10662306a36Sopenharmony_ci			     IEEE80211_HT_CAP_40MHZ_INTOLERANT);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	/* Allow user to enable TX STBC bit  */
10962306a36Sopenharmony_ci	__check_htcap_enable(ht_capa, ht_capa_mask, ht_cap,
11062306a36Sopenharmony_ci			     IEEE80211_HT_CAP_TX_STBC);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	/* Allow user to configure RX STBC bits */
11362306a36Sopenharmony_ci	if (ht_capa_mask->cap_info & cpu_to_le16(IEEE80211_HT_CAP_RX_STBC))
11462306a36Sopenharmony_ci		ht_cap->cap |= le16_to_cpu(ht_capa->cap_info) &
11562306a36Sopenharmony_ci					IEEE80211_HT_CAP_RX_STBC;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	/* Allow user to decrease AMPDU factor */
11862306a36Sopenharmony_ci	if (ht_capa_mask->ampdu_params_info &
11962306a36Sopenharmony_ci	    IEEE80211_HT_AMPDU_PARM_FACTOR) {
12062306a36Sopenharmony_ci		u8 n = ht_capa->ampdu_params_info &
12162306a36Sopenharmony_ci		       IEEE80211_HT_AMPDU_PARM_FACTOR;
12262306a36Sopenharmony_ci		if (n < ht_cap->ampdu_factor)
12362306a36Sopenharmony_ci			ht_cap->ampdu_factor = n;
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	/* Allow the user to increase AMPDU density. */
12762306a36Sopenharmony_ci	if (ht_capa_mask->ampdu_params_info &
12862306a36Sopenharmony_ci	    IEEE80211_HT_AMPDU_PARM_DENSITY) {
12962306a36Sopenharmony_ci		u8 n = (ht_capa->ampdu_params_info &
13062306a36Sopenharmony_ci			IEEE80211_HT_AMPDU_PARM_DENSITY)
13162306a36Sopenharmony_ci			>> IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT;
13262306a36Sopenharmony_ci		if (n > ht_cap->ampdu_density)
13362306a36Sopenharmony_ci			ht_cap->ampdu_density = n;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cibool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
13962306a36Sopenharmony_ci				       struct ieee80211_supported_band *sband,
14062306a36Sopenharmony_ci				       const struct ieee80211_ht_cap *ht_cap_ie,
14162306a36Sopenharmony_ci				       struct link_sta_info *link_sta)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	struct ieee80211_bss_conf *link_conf;
14462306a36Sopenharmony_ci	struct sta_info *sta = link_sta->sta;
14562306a36Sopenharmony_ci	struct ieee80211_sta_ht_cap ht_cap, own_cap;
14662306a36Sopenharmony_ci	u8 ampdu_info, tx_mcs_set_cap;
14762306a36Sopenharmony_ci	int i, max_tx_streams;
14862306a36Sopenharmony_ci	bool changed;
14962306a36Sopenharmony_ci	enum ieee80211_sta_rx_bandwidth bw;
15062306a36Sopenharmony_ci	enum nl80211_chan_width width;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	memset(&ht_cap, 0, sizeof(ht_cap));
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (!ht_cap_ie || !sband->ht_cap.ht_supported)
15562306a36Sopenharmony_ci		goto apply;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	ht_cap.ht_supported = true;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	own_cap = sband->ht_cap;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	/*
16262306a36Sopenharmony_ci	 * If user has specified capability over-rides, take care
16362306a36Sopenharmony_ci	 * of that if the station we're setting up is the AP or TDLS peer that
16462306a36Sopenharmony_ci	 * we advertised a restricted capability set to. Override
16562306a36Sopenharmony_ci	 * our own capabilities and then use those below.
16662306a36Sopenharmony_ci	 */
16762306a36Sopenharmony_ci	if (sdata->vif.type == NL80211_IFTYPE_STATION ||
16862306a36Sopenharmony_ci	    sdata->vif.type == NL80211_IFTYPE_ADHOC)
16962306a36Sopenharmony_ci		ieee80211_apply_htcap_overrides(sdata, &own_cap);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	/*
17262306a36Sopenharmony_ci	 * The bits listed in this expression should be
17362306a36Sopenharmony_ci	 * the same for the peer and us, if the station
17462306a36Sopenharmony_ci	 * advertises more then we can't use those thus
17562306a36Sopenharmony_ci	 * we mask them out.
17662306a36Sopenharmony_ci	 */
17762306a36Sopenharmony_ci	ht_cap.cap = le16_to_cpu(ht_cap_ie->cap_info) &
17862306a36Sopenharmony_ci		(own_cap.cap | ~(IEEE80211_HT_CAP_LDPC_CODING |
17962306a36Sopenharmony_ci				 IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
18062306a36Sopenharmony_ci				 IEEE80211_HT_CAP_GRN_FLD |
18162306a36Sopenharmony_ci				 IEEE80211_HT_CAP_SGI_20 |
18262306a36Sopenharmony_ci				 IEEE80211_HT_CAP_SGI_40 |
18362306a36Sopenharmony_ci				 IEEE80211_HT_CAP_DSSSCCK40));
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	/*
18662306a36Sopenharmony_ci	 * The STBC bits are asymmetric -- if we don't have
18762306a36Sopenharmony_ci	 * TX then mask out the peer's RX and vice versa.
18862306a36Sopenharmony_ci	 */
18962306a36Sopenharmony_ci	if (!(own_cap.cap & IEEE80211_HT_CAP_TX_STBC))
19062306a36Sopenharmony_ci		ht_cap.cap &= ~IEEE80211_HT_CAP_RX_STBC;
19162306a36Sopenharmony_ci	if (!(own_cap.cap & IEEE80211_HT_CAP_RX_STBC))
19262306a36Sopenharmony_ci		ht_cap.cap &= ~IEEE80211_HT_CAP_TX_STBC;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	ampdu_info = ht_cap_ie->ampdu_params_info;
19562306a36Sopenharmony_ci	ht_cap.ampdu_factor =
19662306a36Sopenharmony_ci		ampdu_info & IEEE80211_HT_AMPDU_PARM_FACTOR;
19762306a36Sopenharmony_ci	ht_cap.ampdu_density =
19862306a36Sopenharmony_ci		(ampdu_info & IEEE80211_HT_AMPDU_PARM_DENSITY) >> 2;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	/* own MCS TX capabilities */
20162306a36Sopenharmony_ci	tx_mcs_set_cap = own_cap.mcs.tx_params;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	/* Copy peer MCS TX capabilities, the driver might need them. */
20462306a36Sopenharmony_ci	ht_cap.mcs.tx_params = ht_cap_ie->mcs.tx_params;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	/* can we TX with MCS rates? */
20762306a36Sopenharmony_ci	if (!(tx_mcs_set_cap & IEEE80211_HT_MCS_TX_DEFINED))
20862306a36Sopenharmony_ci		goto apply;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	/* Counting from 0, therefore +1 */
21162306a36Sopenharmony_ci	if (tx_mcs_set_cap & IEEE80211_HT_MCS_TX_RX_DIFF)
21262306a36Sopenharmony_ci		max_tx_streams =
21362306a36Sopenharmony_ci			((tx_mcs_set_cap & IEEE80211_HT_MCS_TX_MAX_STREAMS_MASK)
21462306a36Sopenharmony_ci				>> IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT) + 1;
21562306a36Sopenharmony_ci	else
21662306a36Sopenharmony_ci		max_tx_streams = IEEE80211_HT_MCS_TX_MAX_STREAMS;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	/*
21962306a36Sopenharmony_ci	 * 802.11n-2009 20.3.5 / 20.6 says:
22062306a36Sopenharmony_ci	 * - indices 0 to 7 and 32 are single spatial stream
22162306a36Sopenharmony_ci	 * - 8 to 31 are multiple spatial streams using equal modulation
22262306a36Sopenharmony_ci	 *   [8..15 for two streams, 16..23 for three and 24..31 for four]
22362306a36Sopenharmony_ci	 * - remainder are multiple spatial streams using unequal modulation
22462306a36Sopenharmony_ci	 */
22562306a36Sopenharmony_ci	for (i = 0; i < max_tx_streams; i++)
22662306a36Sopenharmony_ci		ht_cap.mcs.rx_mask[i] =
22762306a36Sopenharmony_ci			own_cap.mcs.rx_mask[i] & ht_cap_ie->mcs.rx_mask[i];
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	if (tx_mcs_set_cap & IEEE80211_HT_MCS_TX_UNEQUAL_MODULATION)
23062306a36Sopenharmony_ci		for (i = IEEE80211_HT_MCS_UNEQUAL_MODULATION_START_BYTE;
23162306a36Sopenharmony_ci		     i < IEEE80211_HT_MCS_MASK_LEN; i++)
23262306a36Sopenharmony_ci			ht_cap.mcs.rx_mask[i] =
23362306a36Sopenharmony_ci				own_cap.mcs.rx_mask[i] &
23462306a36Sopenharmony_ci					ht_cap_ie->mcs.rx_mask[i];
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	/* handle MCS rate 32 too */
23762306a36Sopenharmony_ci	if (own_cap.mcs.rx_mask[32/8] & ht_cap_ie->mcs.rx_mask[32/8] & 1)
23862306a36Sopenharmony_ci		ht_cap.mcs.rx_mask[32/8] |= 1;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	/* set Rx highest rate */
24162306a36Sopenharmony_ci	ht_cap.mcs.rx_highest = ht_cap_ie->mcs.rx_highest;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	if (ht_cap.cap & IEEE80211_HT_CAP_MAX_AMSDU)
24462306a36Sopenharmony_ci		link_sta->pub->agg.max_amsdu_len = IEEE80211_MAX_MPDU_LEN_HT_7935;
24562306a36Sopenharmony_ci	else
24662306a36Sopenharmony_ci		link_sta->pub->agg.max_amsdu_len = IEEE80211_MAX_MPDU_LEN_HT_3839;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	ieee80211_sta_recalc_aggregates(&sta->sta);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci apply:
25162306a36Sopenharmony_ci	changed = memcmp(&link_sta->pub->ht_cap, &ht_cap, sizeof(ht_cap));
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	memcpy(&link_sta->pub->ht_cap, &ht_cap, sizeof(ht_cap));
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	rcu_read_lock();
25662306a36Sopenharmony_ci	link_conf = rcu_dereference(sdata->vif.link_conf[link_sta->link_id]);
25762306a36Sopenharmony_ci	if (WARN_ON(!link_conf))
25862306a36Sopenharmony_ci		width = NL80211_CHAN_WIDTH_20_NOHT;
25962306a36Sopenharmony_ci	else
26062306a36Sopenharmony_ci		width = link_conf->chandef.width;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	switch (width) {
26362306a36Sopenharmony_ci	default:
26462306a36Sopenharmony_ci		WARN_ON_ONCE(1);
26562306a36Sopenharmony_ci		fallthrough;
26662306a36Sopenharmony_ci	case NL80211_CHAN_WIDTH_20_NOHT:
26762306a36Sopenharmony_ci	case NL80211_CHAN_WIDTH_20:
26862306a36Sopenharmony_ci		bw = IEEE80211_STA_RX_BW_20;
26962306a36Sopenharmony_ci		break;
27062306a36Sopenharmony_ci	case NL80211_CHAN_WIDTH_40:
27162306a36Sopenharmony_ci	case NL80211_CHAN_WIDTH_80:
27262306a36Sopenharmony_ci	case NL80211_CHAN_WIDTH_80P80:
27362306a36Sopenharmony_ci	case NL80211_CHAN_WIDTH_160:
27462306a36Sopenharmony_ci	case NL80211_CHAN_WIDTH_320:
27562306a36Sopenharmony_ci		bw = ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
27662306a36Sopenharmony_ci				IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
27762306a36Sopenharmony_ci		break;
27862306a36Sopenharmony_ci	}
27962306a36Sopenharmony_ci	rcu_read_unlock();
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	link_sta->pub->bandwidth = bw;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	link_sta->cur_max_bandwidth =
28462306a36Sopenharmony_ci		ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
28562306a36Sopenharmony_ci				IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
28862306a36Sopenharmony_ci	    sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
28962306a36Sopenharmony_ci		enum ieee80211_smps_mode smps_mode;
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci		switch ((ht_cap.cap & IEEE80211_HT_CAP_SM_PS)
29262306a36Sopenharmony_ci				>> IEEE80211_HT_CAP_SM_PS_SHIFT) {
29362306a36Sopenharmony_ci		case WLAN_HT_CAP_SM_PS_INVALID:
29462306a36Sopenharmony_ci		case WLAN_HT_CAP_SM_PS_STATIC:
29562306a36Sopenharmony_ci			smps_mode = IEEE80211_SMPS_STATIC;
29662306a36Sopenharmony_ci			break;
29762306a36Sopenharmony_ci		case WLAN_HT_CAP_SM_PS_DYNAMIC:
29862306a36Sopenharmony_ci			smps_mode = IEEE80211_SMPS_DYNAMIC;
29962306a36Sopenharmony_ci			break;
30062306a36Sopenharmony_ci		case WLAN_HT_CAP_SM_PS_DISABLED:
30162306a36Sopenharmony_ci			smps_mode = IEEE80211_SMPS_OFF;
30262306a36Sopenharmony_ci			break;
30362306a36Sopenharmony_ci		}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci		if (smps_mode != link_sta->pub->smps_mode)
30662306a36Sopenharmony_ci			changed = true;
30762306a36Sopenharmony_ci		link_sta->pub->smps_mode = smps_mode;
30862306a36Sopenharmony_ci	} else {
30962306a36Sopenharmony_ci		link_sta->pub->smps_mode = IEEE80211_SMPS_OFF;
31062306a36Sopenharmony_ci	}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	return changed;
31362306a36Sopenharmony_ci}
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_civoid ieee80211_sta_tear_down_BA_sessions(struct sta_info *sta,
31662306a36Sopenharmony_ci					 enum ieee80211_agg_stop_reason reason)
31762306a36Sopenharmony_ci{
31862306a36Sopenharmony_ci	int i;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	mutex_lock(&sta->ampdu_mlme.mtx);
32162306a36Sopenharmony_ci	for (i = 0; i <  IEEE80211_NUM_TIDS; i++)
32262306a36Sopenharmony_ci		___ieee80211_stop_rx_ba_session(sta, i, WLAN_BACK_RECIPIENT,
32362306a36Sopenharmony_ci						WLAN_REASON_QSTA_LEAVE_QBSS,
32462306a36Sopenharmony_ci						reason != AGG_STOP_DESTROY_STA &&
32562306a36Sopenharmony_ci						reason != AGG_STOP_PEER_REQUEST);
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	for (i = 0; i <  IEEE80211_NUM_TIDS; i++)
32862306a36Sopenharmony_ci		___ieee80211_stop_tx_ba_session(sta, i, reason);
32962306a36Sopenharmony_ci	mutex_unlock(&sta->ampdu_mlme.mtx);
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	/*
33262306a36Sopenharmony_ci	 * In case the tear down is part of a reconfigure due to HW restart
33362306a36Sopenharmony_ci	 * request, it is possible that the low level driver requested to stop
33462306a36Sopenharmony_ci	 * the BA session, so handle it to properly clean tid_tx data.
33562306a36Sopenharmony_ci	 */
33662306a36Sopenharmony_ci	if(reason == AGG_STOP_DESTROY_STA) {
33762306a36Sopenharmony_ci		cancel_work_sync(&sta->ampdu_mlme.work);
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci		mutex_lock(&sta->ampdu_mlme.mtx);
34062306a36Sopenharmony_ci		for (i = 0; i < IEEE80211_NUM_TIDS; i++) {
34162306a36Sopenharmony_ci			struct tid_ampdu_tx *tid_tx =
34262306a36Sopenharmony_ci				rcu_dereference_protected_tid_tx(sta, i);
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci			if (!tid_tx)
34562306a36Sopenharmony_ci				continue;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci			if (test_and_clear_bit(HT_AGG_STATE_STOP_CB, &tid_tx->state))
34862306a36Sopenharmony_ci				ieee80211_stop_tx_ba_cb(sta, i, tid_tx);
34962306a36Sopenharmony_ci		}
35062306a36Sopenharmony_ci		mutex_unlock(&sta->ampdu_mlme.mtx);
35162306a36Sopenharmony_ci	}
35262306a36Sopenharmony_ci}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_civoid ieee80211_ba_session_work(struct work_struct *work)
35562306a36Sopenharmony_ci{
35662306a36Sopenharmony_ci	struct sta_info *sta =
35762306a36Sopenharmony_ci		container_of(work, struct sta_info, ampdu_mlme.work);
35862306a36Sopenharmony_ci	struct tid_ampdu_tx *tid_tx;
35962306a36Sopenharmony_ci	bool blocked;
36062306a36Sopenharmony_ci	int tid;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	/* When this flag is set, new sessions should be blocked. */
36362306a36Sopenharmony_ci	blocked = test_sta_flag(sta, WLAN_STA_BLOCK_BA);
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	mutex_lock(&sta->ampdu_mlme.mtx);
36662306a36Sopenharmony_ci	for (tid = 0; tid < IEEE80211_NUM_TIDS; tid++) {
36762306a36Sopenharmony_ci		if (test_and_clear_bit(tid, sta->ampdu_mlme.tid_rx_timer_expired))
36862306a36Sopenharmony_ci			___ieee80211_stop_rx_ba_session(
36962306a36Sopenharmony_ci				sta, tid, WLAN_BACK_RECIPIENT,
37062306a36Sopenharmony_ci				WLAN_REASON_QSTA_TIMEOUT, true);
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci		if (test_and_clear_bit(tid,
37362306a36Sopenharmony_ci				       sta->ampdu_mlme.tid_rx_stop_requested))
37462306a36Sopenharmony_ci			___ieee80211_stop_rx_ba_session(
37562306a36Sopenharmony_ci				sta, tid, WLAN_BACK_RECIPIENT,
37662306a36Sopenharmony_ci				WLAN_REASON_UNSPECIFIED, true);
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci		if (!blocked &&
37962306a36Sopenharmony_ci		    test_and_clear_bit(tid,
38062306a36Sopenharmony_ci				       sta->ampdu_mlme.tid_rx_manage_offl))
38162306a36Sopenharmony_ci			___ieee80211_start_rx_ba_session(sta, 0, 0, 0, 1, tid,
38262306a36Sopenharmony_ci							 IEEE80211_MAX_AMPDU_BUF_HT,
38362306a36Sopenharmony_ci							 false, true, NULL);
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci		if (test_and_clear_bit(tid + IEEE80211_NUM_TIDS,
38662306a36Sopenharmony_ci				       sta->ampdu_mlme.tid_rx_manage_offl))
38762306a36Sopenharmony_ci			___ieee80211_stop_rx_ba_session(
38862306a36Sopenharmony_ci				sta, tid, WLAN_BACK_RECIPIENT,
38962306a36Sopenharmony_ci				0, false);
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci		spin_lock_bh(&sta->lock);
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci		tid_tx = sta->ampdu_mlme.tid_start_tx[tid];
39462306a36Sopenharmony_ci		if (!blocked && tid_tx) {
39562306a36Sopenharmony_ci			struct txq_info *txqi = to_txq_info(sta->sta.txq[tid]);
39662306a36Sopenharmony_ci			struct ieee80211_sub_if_data *sdata =
39762306a36Sopenharmony_ci				vif_to_sdata(txqi->txq.vif);
39862306a36Sopenharmony_ci			struct fq *fq = &sdata->local->fq;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci			spin_lock_bh(&fq->lock);
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci			/* Allow only frags to be dequeued */
40362306a36Sopenharmony_ci			set_bit(IEEE80211_TXQ_STOP, &txqi->flags);
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci			if (!skb_queue_empty(&txqi->frags)) {
40662306a36Sopenharmony_ci				/* Fragmented Tx is ongoing, wait for it to
40762306a36Sopenharmony_ci				 * finish. Reschedule worker to retry later.
40862306a36Sopenharmony_ci				 */
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci				spin_unlock_bh(&fq->lock);
41162306a36Sopenharmony_ci				spin_unlock_bh(&sta->lock);
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci				/* Give the task working on the txq a chance
41462306a36Sopenharmony_ci				 * to send out the queued frags
41562306a36Sopenharmony_ci				 */
41662306a36Sopenharmony_ci				synchronize_net();
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci				mutex_unlock(&sta->ampdu_mlme.mtx);
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci				ieee80211_queue_work(&sdata->local->hw, work);
42162306a36Sopenharmony_ci				return;
42262306a36Sopenharmony_ci			}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci			spin_unlock_bh(&fq->lock);
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci			/*
42762306a36Sopenharmony_ci			 * Assign it over to the normal tid_tx array
42862306a36Sopenharmony_ci			 * where it "goes live".
42962306a36Sopenharmony_ci			 */
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci			sta->ampdu_mlme.tid_start_tx[tid] = NULL;
43262306a36Sopenharmony_ci			/* could there be a race? */
43362306a36Sopenharmony_ci			if (sta->ampdu_mlme.tid_tx[tid])
43462306a36Sopenharmony_ci				kfree(tid_tx);
43562306a36Sopenharmony_ci			else
43662306a36Sopenharmony_ci				ieee80211_assign_tid_tx(sta, tid, tid_tx);
43762306a36Sopenharmony_ci			spin_unlock_bh(&sta->lock);
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci			ieee80211_tx_ba_session_handle_start(sta, tid);
44062306a36Sopenharmony_ci			continue;
44162306a36Sopenharmony_ci		}
44262306a36Sopenharmony_ci		spin_unlock_bh(&sta->lock);
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci		tid_tx = rcu_dereference_protected_tid_tx(sta, tid);
44562306a36Sopenharmony_ci		if (!tid_tx)
44662306a36Sopenharmony_ci			continue;
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci		if (!blocked &&
44962306a36Sopenharmony_ci		    test_and_clear_bit(HT_AGG_STATE_START_CB, &tid_tx->state))
45062306a36Sopenharmony_ci			ieee80211_start_tx_ba_cb(sta, tid, tid_tx);
45162306a36Sopenharmony_ci		if (test_and_clear_bit(HT_AGG_STATE_WANT_STOP, &tid_tx->state))
45262306a36Sopenharmony_ci			___ieee80211_stop_tx_ba_session(sta, tid,
45362306a36Sopenharmony_ci							AGG_STOP_LOCAL_REQUEST);
45462306a36Sopenharmony_ci		if (test_and_clear_bit(HT_AGG_STATE_STOP_CB, &tid_tx->state))
45562306a36Sopenharmony_ci			ieee80211_stop_tx_ba_cb(sta, tid, tid_tx);
45662306a36Sopenharmony_ci	}
45762306a36Sopenharmony_ci	mutex_unlock(&sta->ampdu_mlme.mtx);
45862306a36Sopenharmony_ci}
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_civoid ieee80211_send_delba(struct ieee80211_sub_if_data *sdata,
46162306a36Sopenharmony_ci			  const u8 *da, u16 tid,
46262306a36Sopenharmony_ci			  u16 initiator, u16 reason_code)
46362306a36Sopenharmony_ci{
46462306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
46562306a36Sopenharmony_ci	struct sk_buff *skb;
46662306a36Sopenharmony_ci	struct ieee80211_mgmt *mgmt;
46762306a36Sopenharmony_ci	u16 params;
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	skb = dev_alloc_skb(sizeof(*mgmt) + local->hw.extra_tx_headroom);
47062306a36Sopenharmony_ci	if (!skb)
47162306a36Sopenharmony_ci		return;
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	skb_reserve(skb, local->hw.extra_tx_headroom);
47462306a36Sopenharmony_ci	mgmt = skb_put_zero(skb, 24);
47562306a36Sopenharmony_ci	memcpy(mgmt->da, da, ETH_ALEN);
47662306a36Sopenharmony_ci	memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
47762306a36Sopenharmony_ci	if (sdata->vif.type == NL80211_IFTYPE_AP ||
47862306a36Sopenharmony_ci	    sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
47962306a36Sopenharmony_ci	    sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
48062306a36Sopenharmony_ci		memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
48162306a36Sopenharmony_ci	else if (sdata->vif.type == NL80211_IFTYPE_STATION)
48262306a36Sopenharmony_ci		memcpy(mgmt->bssid, sdata->deflink.u.mgd.bssid, ETH_ALEN);
48362306a36Sopenharmony_ci	else if (sdata->vif.type == NL80211_IFTYPE_ADHOC)
48462306a36Sopenharmony_ci		memcpy(mgmt->bssid, sdata->u.ibss.bssid, ETH_ALEN);
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
48762306a36Sopenharmony_ci					  IEEE80211_STYPE_ACTION);
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	skb_put(skb, 1 + sizeof(mgmt->u.action.u.delba));
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	mgmt->u.action.category = WLAN_CATEGORY_BACK;
49262306a36Sopenharmony_ci	mgmt->u.action.u.delba.action_code = WLAN_ACTION_DELBA;
49362306a36Sopenharmony_ci	params = (u16)(initiator << 11); 	/* bit 11 initiator */
49462306a36Sopenharmony_ci	params |= (u16)(tid << 12); 		/* bit 15:12 TID number */
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	mgmt->u.action.u.delba.params = cpu_to_le16(params);
49762306a36Sopenharmony_ci	mgmt->u.action.u.delba.reason_code = cpu_to_le16(reason_code);
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	ieee80211_tx_skb(sdata, skb);
50062306a36Sopenharmony_ci}
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_civoid ieee80211_process_delba(struct ieee80211_sub_if_data *sdata,
50362306a36Sopenharmony_ci			     struct sta_info *sta,
50462306a36Sopenharmony_ci			     struct ieee80211_mgmt *mgmt, size_t len)
50562306a36Sopenharmony_ci{
50662306a36Sopenharmony_ci	u16 tid, params;
50762306a36Sopenharmony_ci	u16 initiator;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	params = le16_to_cpu(mgmt->u.action.u.delba.params);
51062306a36Sopenharmony_ci	tid = (params & IEEE80211_DELBA_PARAM_TID_MASK) >> 12;
51162306a36Sopenharmony_ci	initiator = (params & IEEE80211_DELBA_PARAM_INITIATOR_MASK) >> 11;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	ht_dbg_ratelimited(sdata, "delba from %pM (%s) tid %d reason code %d\n",
51462306a36Sopenharmony_ci			   mgmt->sa, initiator ? "initiator" : "recipient",
51562306a36Sopenharmony_ci			   tid,
51662306a36Sopenharmony_ci			   le16_to_cpu(mgmt->u.action.u.delba.reason_code));
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	if (initiator == WLAN_BACK_INITIATOR)
51962306a36Sopenharmony_ci		__ieee80211_stop_rx_ba_session(sta, tid, WLAN_BACK_INITIATOR, 0,
52062306a36Sopenharmony_ci					       true);
52162306a36Sopenharmony_ci	else
52262306a36Sopenharmony_ci		__ieee80211_stop_tx_ba_session(sta, tid, AGG_STOP_PEER_REQUEST);
52362306a36Sopenharmony_ci}
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_cienum nl80211_smps_mode
52662306a36Sopenharmony_ciieee80211_smps_mode_to_smps_mode(enum ieee80211_smps_mode smps)
52762306a36Sopenharmony_ci{
52862306a36Sopenharmony_ci	switch (smps) {
52962306a36Sopenharmony_ci	case IEEE80211_SMPS_OFF:
53062306a36Sopenharmony_ci		return NL80211_SMPS_OFF;
53162306a36Sopenharmony_ci	case IEEE80211_SMPS_STATIC:
53262306a36Sopenharmony_ci		return NL80211_SMPS_STATIC;
53362306a36Sopenharmony_ci	case IEEE80211_SMPS_DYNAMIC:
53462306a36Sopenharmony_ci		return NL80211_SMPS_DYNAMIC;
53562306a36Sopenharmony_ci	default:
53662306a36Sopenharmony_ci		return NL80211_SMPS_OFF;
53762306a36Sopenharmony_ci	}
53862306a36Sopenharmony_ci}
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ciint ieee80211_send_smps_action(struct ieee80211_sub_if_data *sdata,
54162306a36Sopenharmony_ci			       enum ieee80211_smps_mode smps, const u8 *da,
54262306a36Sopenharmony_ci			       const u8 *bssid)
54362306a36Sopenharmony_ci{
54462306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
54562306a36Sopenharmony_ci	struct sk_buff *skb;
54662306a36Sopenharmony_ci	struct ieee80211_mgmt *action_frame;
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	/* 27 = header + category + action + smps mode */
54962306a36Sopenharmony_ci	skb = dev_alloc_skb(27 + local->hw.extra_tx_headroom);
55062306a36Sopenharmony_ci	if (!skb)
55162306a36Sopenharmony_ci		return -ENOMEM;
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	skb_reserve(skb, local->hw.extra_tx_headroom);
55462306a36Sopenharmony_ci	action_frame = skb_put(skb, 27);
55562306a36Sopenharmony_ci	memcpy(action_frame->da, da, ETH_ALEN);
55662306a36Sopenharmony_ci	memcpy(action_frame->sa, sdata->dev->dev_addr, ETH_ALEN);
55762306a36Sopenharmony_ci	memcpy(action_frame->bssid, bssid, ETH_ALEN);
55862306a36Sopenharmony_ci	action_frame->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
55962306a36Sopenharmony_ci						  IEEE80211_STYPE_ACTION);
56062306a36Sopenharmony_ci	action_frame->u.action.category = WLAN_CATEGORY_HT;
56162306a36Sopenharmony_ci	action_frame->u.action.u.ht_smps.action = WLAN_HT_ACTION_SMPS;
56262306a36Sopenharmony_ci	switch (smps) {
56362306a36Sopenharmony_ci	case IEEE80211_SMPS_AUTOMATIC:
56462306a36Sopenharmony_ci	case IEEE80211_SMPS_NUM_MODES:
56562306a36Sopenharmony_ci		WARN_ON(1);
56662306a36Sopenharmony_ci		fallthrough;
56762306a36Sopenharmony_ci	case IEEE80211_SMPS_OFF:
56862306a36Sopenharmony_ci		action_frame->u.action.u.ht_smps.smps_control =
56962306a36Sopenharmony_ci				WLAN_HT_SMPS_CONTROL_DISABLED;
57062306a36Sopenharmony_ci		break;
57162306a36Sopenharmony_ci	case IEEE80211_SMPS_STATIC:
57262306a36Sopenharmony_ci		action_frame->u.action.u.ht_smps.smps_control =
57362306a36Sopenharmony_ci				WLAN_HT_SMPS_CONTROL_STATIC;
57462306a36Sopenharmony_ci		break;
57562306a36Sopenharmony_ci	case IEEE80211_SMPS_DYNAMIC:
57662306a36Sopenharmony_ci		action_frame->u.action.u.ht_smps.smps_control =
57762306a36Sopenharmony_ci				WLAN_HT_SMPS_CONTROL_DYNAMIC;
57862306a36Sopenharmony_ci		break;
57962306a36Sopenharmony_ci	}
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ci	/* we'll do more on status of this frame */
58262306a36Sopenharmony_ci	IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
58362306a36Sopenharmony_ci	ieee80211_tx_skb(sdata, skb);
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci	return 0;
58662306a36Sopenharmony_ci}
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_civoid ieee80211_request_smps(struct ieee80211_vif *vif, unsigned int link_id,
58962306a36Sopenharmony_ci			    enum ieee80211_smps_mode smps_mode)
59062306a36Sopenharmony_ci{
59162306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
59262306a36Sopenharmony_ci	struct ieee80211_link_data *link;
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	if (WARN_ON_ONCE(vif->type != NL80211_IFTYPE_STATION))
59562306a36Sopenharmony_ci		return;
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci	rcu_read_lock();
59862306a36Sopenharmony_ci	link = rcu_dereference(sdata->link[link_id]);
59962306a36Sopenharmony_ci	if (WARN_ON(!link))
60062306a36Sopenharmony_ci		goto out;
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	if (link->u.mgd.driver_smps_mode == smps_mode)
60362306a36Sopenharmony_ci		goto out;
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	link->u.mgd.driver_smps_mode = smps_mode;
60662306a36Sopenharmony_ci	wiphy_work_queue(sdata->local->hw.wiphy,
60762306a36Sopenharmony_ci			 &link->u.mgd.request_smps_work);
60862306a36Sopenharmony_ciout:
60962306a36Sopenharmony_ci	rcu_read_unlock();
61062306a36Sopenharmony_ci}
61162306a36Sopenharmony_ci/* this might change ... don't want non-open drivers using it */
61262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ieee80211_request_smps);
613