162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * mac80211 TDLS handling code
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2006-2010	Johannes Berg <johannes@sipsolutions.net>
662306a36Sopenharmony_ci * Copyright 2014, Intel Corporation
762306a36Sopenharmony_ci * Copyright 2014  Intel Mobile Communications GmbH
862306a36Sopenharmony_ci * Copyright 2015 - 2016 Intel Deutschland GmbH
962306a36Sopenharmony_ci * Copyright (C) 2019, 2021-2023 Intel Corporation
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/ieee80211.h>
1362306a36Sopenharmony_ci#include <linux/log2.h>
1462306a36Sopenharmony_ci#include <net/cfg80211.h>
1562306a36Sopenharmony_ci#include <linux/rtnetlink.h>
1662306a36Sopenharmony_ci#include "ieee80211_i.h"
1762306a36Sopenharmony_ci#include "driver-ops.h"
1862306a36Sopenharmony_ci#include "rate.h"
1962306a36Sopenharmony_ci#include "wme.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/* give usermode some time for retries in setting up the TDLS session */
2262306a36Sopenharmony_ci#define TDLS_PEER_SETUP_TIMEOUT	(15 * HZ)
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_civoid ieee80211_tdls_peer_del_work(struct work_struct *wk)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata;
2762306a36Sopenharmony_ci	struct ieee80211_local *local;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	sdata = container_of(wk, struct ieee80211_sub_if_data,
3062306a36Sopenharmony_ci			     u.mgd.tdls_peer_del_work.work);
3162306a36Sopenharmony_ci	local = sdata->local;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	mutex_lock(&local->mtx);
3462306a36Sopenharmony_ci	if (!is_zero_ether_addr(sdata->u.mgd.tdls_peer)) {
3562306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS del peer %pM\n", sdata->u.mgd.tdls_peer);
3662306a36Sopenharmony_ci		sta_info_destroy_addr(sdata, sdata->u.mgd.tdls_peer);
3762306a36Sopenharmony_ci		eth_zero_addr(sdata->u.mgd.tdls_peer);
3862306a36Sopenharmony_ci	}
3962306a36Sopenharmony_ci	mutex_unlock(&local->mtx);
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic void ieee80211_tdls_add_ext_capab(struct ieee80211_link_data *link,
4362306a36Sopenharmony_ci					 struct sk_buff *skb)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = link->sdata;
4662306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
4762306a36Sopenharmony_ci	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
4862306a36Sopenharmony_ci	bool chan_switch = local->hw.wiphy->features &
4962306a36Sopenharmony_ci			   NL80211_FEATURE_TDLS_CHANNEL_SWITCH;
5062306a36Sopenharmony_ci	bool wider_band = ieee80211_hw_check(&local->hw, TDLS_WIDER_BW) &&
5162306a36Sopenharmony_ci			  !ifmgd->tdls_wider_bw_prohibited;
5262306a36Sopenharmony_ci	bool buffer_sta = ieee80211_hw_check(&local->hw,
5362306a36Sopenharmony_ci					     SUPPORTS_TDLS_BUFFER_STA);
5462306a36Sopenharmony_ci	struct ieee80211_supported_band *sband = ieee80211_get_link_sband(link);
5562306a36Sopenharmony_ci	bool vht = sband && sband->vht_cap.vht_supported;
5662306a36Sopenharmony_ci	u8 *pos = skb_put(skb, 10);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	*pos++ = WLAN_EID_EXT_CAPABILITY;
5962306a36Sopenharmony_ci	*pos++ = 8; /* len */
6062306a36Sopenharmony_ci	*pos++ = 0x0;
6162306a36Sopenharmony_ci	*pos++ = 0x0;
6262306a36Sopenharmony_ci	*pos++ = 0x0;
6362306a36Sopenharmony_ci	*pos++ = (chan_switch ? WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH : 0) |
6462306a36Sopenharmony_ci		 (buffer_sta ? WLAN_EXT_CAPA4_TDLS_BUFFER_STA : 0);
6562306a36Sopenharmony_ci	*pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED;
6662306a36Sopenharmony_ci	*pos++ = 0;
6762306a36Sopenharmony_ci	*pos++ = 0;
6862306a36Sopenharmony_ci	*pos++ = (vht && wider_band) ? WLAN_EXT_CAPA8_TDLS_WIDE_BW_ENABLED : 0;
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic u8
7262306a36Sopenharmony_ciieee80211_tdls_add_subband(struct ieee80211_sub_if_data *sdata,
7362306a36Sopenharmony_ci			   struct sk_buff *skb, u16 start, u16 end,
7462306a36Sopenharmony_ci			   u16 spacing)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	u8 subband_cnt = 0, ch_cnt = 0;
7762306a36Sopenharmony_ci	struct ieee80211_channel *ch;
7862306a36Sopenharmony_ci	struct cfg80211_chan_def chandef;
7962306a36Sopenharmony_ci	int i, subband_start;
8062306a36Sopenharmony_ci	struct wiphy *wiphy = sdata->local->hw.wiphy;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	for (i = start; i <= end; i += spacing) {
8362306a36Sopenharmony_ci		if (!ch_cnt)
8462306a36Sopenharmony_ci			subband_start = i;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci		ch = ieee80211_get_channel(sdata->local->hw.wiphy, i);
8762306a36Sopenharmony_ci		if (ch) {
8862306a36Sopenharmony_ci			/* we will be active on the channel */
8962306a36Sopenharmony_ci			cfg80211_chandef_create(&chandef, ch,
9062306a36Sopenharmony_ci						NL80211_CHAN_NO_HT);
9162306a36Sopenharmony_ci			if (cfg80211_reg_can_beacon_relax(wiphy, &chandef,
9262306a36Sopenharmony_ci							  sdata->wdev.iftype)) {
9362306a36Sopenharmony_ci				ch_cnt++;
9462306a36Sopenharmony_ci				/*
9562306a36Sopenharmony_ci				 * check if the next channel is also part of
9662306a36Sopenharmony_ci				 * this allowed range
9762306a36Sopenharmony_ci				 */
9862306a36Sopenharmony_ci				continue;
9962306a36Sopenharmony_ci			}
10062306a36Sopenharmony_ci		}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci		/*
10362306a36Sopenharmony_ci		 * we've reached the end of a range, with allowed channels
10462306a36Sopenharmony_ci		 * found
10562306a36Sopenharmony_ci		 */
10662306a36Sopenharmony_ci		if (ch_cnt) {
10762306a36Sopenharmony_ci			u8 *pos = skb_put(skb, 2);
10862306a36Sopenharmony_ci			*pos++ = ieee80211_frequency_to_channel(subband_start);
10962306a36Sopenharmony_ci			*pos++ = ch_cnt;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci			subband_cnt++;
11262306a36Sopenharmony_ci			ch_cnt = 0;
11362306a36Sopenharmony_ci		}
11462306a36Sopenharmony_ci	}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	/* all channels in the requested range are allowed - add them here */
11762306a36Sopenharmony_ci	if (ch_cnt) {
11862306a36Sopenharmony_ci		u8 *pos = skb_put(skb, 2);
11962306a36Sopenharmony_ci		*pos++ = ieee80211_frequency_to_channel(subband_start);
12062306a36Sopenharmony_ci		*pos++ = ch_cnt;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci		subband_cnt++;
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	return subband_cnt;
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic void
12962306a36Sopenharmony_ciieee80211_tdls_add_supp_channels(struct ieee80211_sub_if_data *sdata,
13062306a36Sopenharmony_ci				 struct sk_buff *skb)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	/*
13362306a36Sopenharmony_ci	 * Add possible channels for TDLS. These are channels that are allowed
13462306a36Sopenharmony_ci	 * to be active.
13562306a36Sopenharmony_ci	 */
13662306a36Sopenharmony_ci	u8 subband_cnt;
13762306a36Sopenharmony_ci	u8 *pos = skb_put(skb, 2);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	*pos++ = WLAN_EID_SUPPORTED_CHANNELS;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	/*
14262306a36Sopenharmony_ci	 * 5GHz and 2GHz channels numbers can overlap. Ignore this for now, as
14362306a36Sopenharmony_ci	 * this doesn't happen in real world scenarios.
14462306a36Sopenharmony_ci	 */
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	/* 2GHz, with 5MHz spacing */
14762306a36Sopenharmony_ci	subband_cnt = ieee80211_tdls_add_subband(sdata, skb, 2412, 2472, 5);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	/* 5GHz, with 20MHz spacing */
15062306a36Sopenharmony_ci	subband_cnt += ieee80211_tdls_add_subband(sdata, skb, 5000, 5825, 20);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	/* length */
15362306a36Sopenharmony_ci	*pos = 2 * subband_cnt;
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cistatic void ieee80211_tdls_add_oper_classes(struct ieee80211_link_data *link,
15762306a36Sopenharmony_ci					    struct sk_buff *skb)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	u8 *pos;
16062306a36Sopenharmony_ci	u8 op_class;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	if (!ieee80211_chandef_to_operating_class(&link->conf->chandef,
16362306a36Sopenharmony_ci						  &op_class))
16462306a36Sopenharmony_ci		return;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	pos = skb_put(skb, 4);
16762306a36Sopenharmony_ci	*pos++ = WLAN_EID_SUPPORTED_REGULATORY_CLASSES;
16862306a36Sopenharmony_ci	*pos++ = 2; /* len */
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	*pos++ = op_class;
17162306a36Sopenharmony_ci	*pos++ = op_class; /* give current operating class as alternate too */
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic void ieee80211_tdls_add_bss_coex_ie(struct sk_buff *skb)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	u8 *pos = skb_put(skb, 3);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	*pos++ = WLAN_EID_BSS_COEX_2040;
17962306a36Sopenharmony_ci	*pos++ = 1; /* len */
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	*pos++ = WLAN_BSS_COEX_INFORMATION_REQUEST;
18262306a36Sopenharmony_ci}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cistatic u16 ieee80211_get_tdls_sta_capab(struct ieee80211_link_data *link,
18562306a36Sopenharmony_ci					u16 status_code)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	struct ieee80211_supported_band *sband;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	/* The capability will be 0 when sending a failure code */
19062306a36Sopenharmony_ci	if (status_code != 0)
19162306a36Sopenharmony_ci		return 0;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	sband = ieee80211_get_link_sband(link);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (sband && sband->band == NL80211_BAND_2GHZ) {
19662306a36Sopenharmony_ci		return WLAN_CAPABILITY_SHORT_SLOT_TIME |
19762306a36Sopenharmony_ci		       WLAN_CAPABILITY_SHORT_PREAMBLE;
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	return 0;
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_cistatic void ieee80211_tdls_add_link_ie(struct ieee80211_link_data *link,
20462306a36Sopenharmony_ci				       struct sk_buff *skb, const u8 *peer,
20562306a36Sopenharmony_ci				       bool initiator)
20662306a36Sopenharmony_ci{
20762306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = link->sdata;
20862306a36Sopenharmony_ci	struct ieee80211_tdls_lnkie *lnkid;
20962306a36Sopenharmony_ci	const u8 *init_addr, *rsp_addr;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	if (initiator) {
21262306a36Sopenharmony_ci		init_addr = sdata->vif.addr;
21362306a36Sopenharmony_ci		rsp_addr = peer;
21462306a36Sopenharmony_ci	} else {
21562306a36Sopenharmony_ci		init_addr = peer;
21662306a36Sopenharmony_ci		rsp_addr = sdata->vif.addr;
21762306a36Sopenharmony_ci	}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	lnkid = skb_put(skb, sizeof(struct ieee80211_tdls_lnkie));
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	lnkid->ie_type = WLAN_EID_LINK_ID;
22262306a36Sopenharmony_ci	lnkid->ie_len = sizeof(struct ieee80211_tdls_lnkie) - 2;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	memcpy(lnkid->bssid, link->u.mgd.bssid, ETH_ALEN);
22562306a36Sopenharmony_ci	memcpy(lnkid->init_sta, init_addr, ETH_ALEN);
22662306a36Sopenharmony_ci	memcpy(lnkid->resp_sta, rsp_addr, ETH_ALEN);
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic void
23062306a36Sopenharmony_ciieee80211_tdls_add_aid(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci	u8 *pos = skb_put(skb, 4);
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	*pos++ = WLAN_EID_AID;
23562306a36Sopenharmony_ci	*pos++ = 2; /* len */
23662306a36Sopenharmony_ci	put_unaligned_le16(sdata->vif.cfg.aid, pos);
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci/* translate numbering in the WMM parameter IE to the mac80211 notation */
24062306a36Sopenharmony_cistatic enum ieee80211_ac_numbers ieee80211_ac_from_wmm(int ac)
24162306a36Sopenharmony_ci{
24262306a36Sopenharmony_ci	switch (ac) {
24362306a36Sopenharmony_ci	default:
24462306a36Sopenharmony_ci		WARN_ON_ONCE(1);
24562306a36Sopenharmony_ci		fallthrough;
24662306a36Sopenharmony_ci	case 0:
24762306a36Sopenharmony_ci		return IEEE80211_AC_BE;
24862306a36Sopenharmony_ci	case 1:
24962306a36Sopenharmony_ci		return IEEE80211_AC_BK;
25062306a36Sopenharmony_ci	case 2:
25162306a36Sopenharmony_ci		return IEEE80211_AC_VI;
25262306a36Sopenharmony_ci	case 3:
25362306a36Sopenharmony_ci		return IEEE80211_AC_VO;
25462306a36Sopenharmony_ci	}
25562306a36Sopenharmony_ci}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cistatic u8 ieee80211_wmm_aci_aifsn(int aifsn, bool acm, int aci)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	u8 ret;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	ret = aifsn & 0x0f;
26262306a36Sopenharmony_ci	if (acm)
26362306a36Sopenharmony_ci		ret |= 0x10;
26462306a36Sopenharmony_ci	ret |= (aci << 5) & 0x60;
26562306a36Sopenharmony_ci	return ret;
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic u8 ieee80211_wmm_ecw(u16 cw_min, u16 cw_max)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	return ((ilog2(cw_min + 1) << 0x0) & 0x0f) |
27162306a36Sopenharmony_ci	       ((ilog2(cw_max + 1) << 0x4) & 0xf0);
27262306a36Sopenharmony_ci}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cistatic void ieee80211_tdls_add_wmm_param_ie(struct ieee80211_sub_if_data *sdata,
27562306a36Sopenharmony_ci					    struct sk_buff *skb)
27662306a36Sopenharmony_ci{
27762306a36Sopenharmony_ci	struct ieee80211_wmm_param_ie *wmm;
27862306a36Sopenharmony_ci	struct ieee80211_tx_queue_params *txq;
27962306a36Sopenharmony_ci	int i;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	wmm = skb_put_zero(skb, sizeof(*wmm));
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	wmm->element_id = WLAN_EID_VENDOR_SPECIFIC;
28462306a36Sopenharmony_ci	wmm->len = sizeof(*wmm) - 2;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	wmm->oui[0] = 0x00; /* Microsoft OUI 00:50:F2 */
28762306a36Sopenharmony_ci	wmm->oui[1] = 0x50;
28862306a36Sopenharmony_ci	wmm->oui[2] = 0xf2;
28962306a36Sopenharmony_ci	wmm->oui_type = 2; /* WME */
29062306a36Sopenharmony_ci	wmm->oui_subtype = 1; /* WME param */
29162306a36Sopenharmony_ci	wmm->version = 1; /* WME ver */
29262306a36Sopenharmony_ci	wmm->qos_info = 0; /* U-APSD not in use */
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	/*
29562306a36Sopenharmony_ci	 * Use the EDCA parameters defined for the BSS, or default if the AP
29662306a36Sopenharmony_ci	 * doesn't support it, as mandated by 802.11-2012 section 10.22.4
29762306a36Sopenharmony_ci	 */
29862306a36Sopenharmony_ci	for (i = 0; i < IEEE80211_NUM_ACS; i++) {
29962306a36Sopenharmony_ci		txq = &sdata->deflink.tx_conf[ieee80211_ac_from_wmm(i)];
30062306a36Sopenharmony_ci		wmm->ac[i].aci_aifsn = ieee80211_wmm_aci_aifsn(txq->aifs,
30162306a36Sopenharmony_ci							       txq->acm, i);
30262306a36Sopenharmony_ci		wmm->ac[i].cw = ieee80211_wmm_ecw(txq->cw_min, txq->cw_max);
30362306a36Sopenharmony_ci		wmm->ac[i].txop_limit = cpu_to_le16(txq->txop);
30462306a36Sopenharmony_ci	}
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic void
30862306a36Sopenharmony_ciieee80211_tdls_chandef_vht_upgrade(struct ieee80211_sub_if_data *sdata,
30962306a36Sopenharmony_ci				   struct sta_info *sta)
31062306a36Sopenharmony_ci{
31162306a36Sopenharmony_ci	/* IEEE802.11ac-2013 Table E-4 */
31262306a36Sopenharmony_ci	u16 centers_80mhz[] = { 5210, 5290, 5530, 5610, 5690, 5775 };
31362306a36Sopenharmony_ci	struct cfg80211_chan_def uc = sta->tdls_chandef;
31462306a36Sopenharmony_ci	enum nl80211_chan_width max_width =
31562306a36Sopenharmony_ci		ieee80211_sta_cap_chan_bw(&sta->deflink);
31662306a36Sopenharmony_ci	int i;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	/* only support upgrading non-narrow channels up to 80Mhz */
31962306a36Sopenharmony_ci	if (max_width == NL80211_CHAN_WIDTH_5 ||
32062306a36Sopenharmony_ci	    max_width == NL80211_CHAN_WIDTH_10)
32162306a36Sopenharmony_ci		return;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	if (max_width > NL80211_CHAN_WIDTH_80)
32462306a36Sopenharmony_ci		max_width = NL80211_CHAN_WIDTH_80;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	if (uc.width >= max_width)
32762306a36Sopenharmony_ci		return;
32862306a36Sopenharmony_ci	/*
32962306a36Sopenharmony_ci	 * Channel usage constrains in the IEEE802.11ac-2013 specification only
33062306a36Sopenharmony_ci	 * allow expanding a 20MHz channel to 80MHz in a single way. In
33162306a36Sopenharmony_ci	 * addition, there are no 40MHz allowed channels that are not part of
33262306a36Sopenharmony_ci	 * the allowed 80MHz range in the 5GHz spectrum (the relevant one here).
33362306a36Sopenharmony_ci	 */
33462306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(centers_80mhz); i++)
33562306a36Sopenharmony_ci		if (abs(uc.chan->center_freq - centers_80mhz[i]) <= 30) {
33662306a36Sopenharmony_ci			uc.center_freq1 = centers_80mhz[i];
33762306a36Sopenharmony_ci			uc.center_freq2 = 0;
33862306a36Sopenharmony_ci			uc.width = NL80211_CHAN_WIDTH_80;
33962306a36Sopenharmony_ci			break;
34062306a36Sopenharmony_ci		}
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	if (!uc.center_freq1)
34362306a36Sopenharmony_ci		return;
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	/* proceed to downgrade the chandef until usable or the same as AP BW */
34662306a36Sopenharmony_ci	while (uc.width > max_width ||
34762306a36Sopenharmony_ci	       (uc.width > sta->tdls_chandef.width &&
34862306a36Sopenharmony_ci		!cfg80211_reg_can_beacon_relax(sdata->local->hw.wiphy, &uc,
34962306a36Sopenharmony_ci					       sdata->wdev.iftype)))
35062306a36Sopenharmony_ci		ieee80211_chandef_downgrade(&uc);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	if (!cfg80211_chandef_identical(&uc, &sta->tdls_chandef)) {
35362306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS ch width upgraded %d -> %d\n",
35462306a36Sopenharmony_ci			 sta->tdls_chandef.width, uc.width);
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci		/*
35762306a36Sopenharmony_ci		 * the station is not yet authorized when BW upgrade is done,
35862306a36Sopenharmony_ci		 * locking is not required
35962306a36Sopenharmony_ci		 */
36062306a36Sopenharmony_ci		sta->tdls_chandef = uc;
36162306a36Sopenharmony_ci	}
36262306a36Sopenharmony_ci}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_cistatic void
36562306a36Sopenharmony_ciieee80211_tdls_add_setup_start_ies(struct ieee80211_link_data *link,
36662306a36Sopenharmony_ci				   struct sk_buff *skb, const u8 *peer,
36762306a36Sopenharmony_ci				   u8 action_code, bool initiator,
36862306a36Sopenharmony_ci				   const u8 *extra_ies, size_t extra_ies_len)
36962306a36Sopenharmony_ci{
37062306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = link->sdata;
37162306a36Sopenharmony_ci	struct ieee80211_supported_band *sband;
37262306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
37362306a36Sopenharmony_ci	struct ieee80211_sta_ht_cap ht_cap;
37462306a36Sopenharmony_ci	struct ieee80211_sta_vht_cap vht_cap;
37562306a36Sopenharmony_ci	const struct ieee80211_sta_he_cap *he_cap;
37662306a36Sopenharmony_ci	const struct ieee80211_sta_eht_cap *eht_cap;
37762306a36Sopenharmony_ci	struct sta_info *sta = NULL;
37862306a36Sopenharmony_ci	size_t offset = 0, noffset;
37962306a36Sopenharmony_ci	u8 *pos;
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	sband = ieee80211_get_link_sband(link);
38262306a36Sopenharmony_ci	if (WARN_ON_ONCE(!sband))
38362306a36Sopenharmony_ci		return;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	ieee80211_add_srates_ie(sdata, skb, false, sband->band);
38662306a36Sopenharmony_ci	ieee80211_add_ext_srates_ie(sdata, skb, false, sband->band);
38762306a36Sopenharmony_ci	ieee80211_tdls_add_supp_channels(sdata, skb);
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	/* add any custom IEs that go before Extended Capabilities */
39062306a36Sopenharmony_ci	if (extra_ies_len) {
39162306a36Sopenharmony_ci		static const u8 before_ext_cap[] = {
39262306a36Sopenharmony_ci			WLAN_EID_SUPP_RATES,
39362306a36Sopenharmony_ci			WLAN_EID_COUNTRY,
39462306a36Sopenharmony_ci			WLAN_EID_EXT_SUPP_RATES,
39562306a36Sopenharmony_ci			WLAN_EID_SUPPORTED_CHANNELS,
39662306a36Sopenharmony_ci			WLAN_EID_RSN,
39762306a36Sopenharmony_ci		};
39862306a36Sopenharmony_ci		noffset = ieee80211_ie_split(extra_ies, extra_ies_len,
39962306a36Sopenharmony_ci					     before_ext_cap,
40062306a36Sopenharmony_ci					     ARRAY_SIZE(before_ext_cap),
40162306a36Sopenharmony_ci					     offset);
40262306a36Sopenharmony_ci		skb_put_data(skb, extra_ies + offset, noffset - offset);
40362306a36Sopenharmony_ci		offset = noffset;
40462306a36Sopenharmony_ci	}
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	ieee80211_tdls_add_ext_capab(link, skb);
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	/* add the QoS element if we support it */
40962306a36Sopenharmony_ci	if (local->hw.queues >= IEEE80211_NUM_ACS &&
41062306a36Sopenharmony_ci	    action_code != WLAN_PUB_ACTION_TDLS_DISCOVER_RES)
41162306a36Sopenharmony_ci		ieee80211_add_wmm_info_ie(skb_put(skb, 9), 0); /* no U-APSD */
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	/* add any custom IEs that go before HT capabilities */
41462306a36Sopenharmony_ci	if (extra_ies_len) {
41562306a36Sopenharmony_ci		static const u8 before_ht_cap[] = {
41662306a36Sopenharmony_ci			WLAN_EID_SUPP_RATES,
41762306a36Sopenharmony_ci			WLAN_EID_COUNTRY,
41862306a36Sopenharmony_ci			WLAN_EID_EXT_SUPP_RATES,
41962306a36Sopenharmony_ci			WLAN_EID_SUPPORTED_CHANNELS,
42062306a36Sopenharmony_ci			WLAN_EID_RSN,
42162306a36Sopenharmony_ci			WLAN_EID_EXT_CAPABILITY,
42262306a36Sopenharmony_ci			WLAN_EID_QOS_CAPA,
42362306a36Sopenharmony_ci			WLAN_EID_FAST_BSS_TRANSITION,
42462306a36Sopenharmony_ci			WLAN_EID_TIMEOUT_INTERVAL,
42562306a36Sopenharmony_ci			WLAN_EID_SUPPORTED_REGULATORY_CLASSES,
42662306a36Sopenharmony_ci		};
42762306a36Sopenharmony_ci		noffset = ieee80211_ie_split(extra_ies, extra_ies_len,
42862306a36Sopenharmony_ci					     before_ht_cap,
42962306a36Sopenharmony_ci					     ARRAY_SIZE(before_ht_cap),
43062306a36Sopenharmony_ci					     offset);
43162306a36Sopenharmony_ci		skb_put_data(skb, extra_ies + offset, noffset - offset);
43262306a36Sopenharmony_ci		offset = noffset;
43362306a36Sopenharmony_ci	}
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	/* we should have the peer STA if we're already responding */
43662306a36Sopenharmony_ci	if (action_code == WLAN_TDLS_SETUP_RESPONSE) {
43762306a36Sopenharmony_ci		sta = sta_info_get(sdata, peer);
43862306a36Sopenharmony_ci		if (WARN_ON_ONCE(!sta))
43962306a36Sopenharmony_ci			return;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci		sta->tdls_chandef = link->conf->chandef;
44262306a36Sopenharmony_ci	}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	ieee80211_tdls_add_oper_classes(link, skb);
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	/*
44762306a36Sopenharmony_ci	 * with TDLS we can switch channels, and HT-caps are not necessarily
44862306a36Sopenharmony_ci	 * the same on all bands. The specification limits the setup to a
44962306a36Sopenharmony_ci	 * single HT-cap, so use the current band for now.
45062306a36Sopenharmony_ci	 */
45162306a36Sopenharmony_ci	memcpy(&ht_cap, &sband->ht_cap, sizeof(ht_cap));
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	if ((action_code == WLAN_TDLS_SETUP_REQUEST ||
45462306a36Sopenharmony_ci	     action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) &&
45562306a36Sopenharmony_ci	    ht_cap.ht_supported) {
45662306a36Sopenharmony_ci		ieee80211_apply_htcap_overrides(sdata, &ht_cap);
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci		/* disable SMPS in TDLS initiator */
45962306a36Sopenharmony_ci		ht_cap.cap |= WLAN_HT_CAP_SM_PS_DISABLED
46062306a36Sopenharmony_ci				<< IEEE80211_HT_CAP_SM_PS_SHIFT;
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci		pos = skb_put(skb, sizeof(struct ieee80211_ht_cap) + 2);
46362306a36Sopenharmony_ci		ieee80211_ie_build_ht_cap(pos, &ht_cap, ht_cap.cap);
46462306a36Sopenharmony_ci	} else if (action_code == WLAN_TDLS_SETUP_RESPONSE &&
46562306a36Sopenharmony_ci		   ht_cap.ht_supported && sta->sta.deflink.ht_cap.ht_supported) {
46662306a36Sopenharmony_ci		/* the peer caps are already intersected with our own */
46762306a36Sopenharmony_ci		memcpy(&ht_cap, &sta->sta.deflink.ht_cap, sizeof(ht_cap));
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci		pos = skb_put(skb, sizeof(struct ieee80211_ht_cap) + 2);
47062306a36Sopenharmony_ci		ieee80211_ie_build_ht_cap(pos, &ht_cap, ht_cap.cap);
47162306a36Sopenharmony_ci	}
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	if (ht_cap.ht_supported &&
47462306a36Sopenharmony_ci	    (ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40))
47562306a36Sopenharmony_ci		ieee80211_tdls_add_bss_coex_ie(skb);
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	ieee80211_tdls_add_link_ie(link, skb, peer, initiator);
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	/* add any custom IEs that go before VHT capabilities */
48062306a36Sopenharmony_ci	if (extra_ies_len) {
48162306a36Sopenharmony_ci		static const u8 before_vht_cap[] = {
48262306a36Sopenharmony_ci			WLAN_EID_SUPP_RATES,
48362306a36Sopenharmony_ci			WLAN_EID_COUNTRY,
48462306a36Sopenharmony_ci			WLAN_EID_EXT_SUPP_RATES,
48562306a36Sopenharmony_ci			WLAN_EID_SUPPORTED_CHANNELS,
48662306a36Sopenharmony_ci			WLAN_EID_RSN,
48762306a36Sopenharmony_ci			WLAN_EID_EXT_CAPABILITY,
48862306a36Sopenharmony_ci			WLAN_EID_QOS_CAPA,
48962306a36Sopenharmony_ci			WLAN_EID_FAST_BSS_TRANSITION,
49062306a36Sopenharmony_ci			WLAN_EID_TIMEOUT_INTERVAL,
49162306a36Sopenharmony_ci			WLAN_EID_SUPPORTED_REGULATORY_CLASSES,
49262306a36Sopenharmony_ci			WLAN_EID_MULTI_BAND,
49362306a36Sopenharmony_ci		};
49462306a36Sopenharmony_ci		noffset = ieee80211_ie_split(extra_ies, extra_ies_len,
49562306a36Sopenharmony_ci					     before_vht_cap,
49662306a36Sopenharmony_ci					     ARRAY_SIZE(before_vht_cap),
49762306a36Sopenharmony_ci					     offset);
49862306a36Sopenharmony_ci		skb_put_data(skb, extra_ies + offset, noffset - offset);
49962306a36Sopenharmony_ci		offset = noffset;
50062306a36Sopenharmony_ci	}
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	/* add AID if VHT, HE or EHT capabilities supported */
50362306a36Sopenharmony_ci	memcpy(&vht_cap, &sband->vht_cap, sizeof(vht_cap));
50462306a36Sopenharmony_ci	he_cap = ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif);
50562306a36Sopenharmony_ci	eht_cap = ieee80211_get_eht_iftype_cap_vif(sband, &sdata->vif);
50662306a36Sopenharmony_ci	if ((vht_cap.vht_supported || he_cap || eht_cap) &&
50762306a36Sopenharmony_ci	    (action_code == WLAN_TDLS_SETUP_REQUEST ||
50862306a36Sopenharmony_ci	     action_code == WLAN_TDLS_SETUP_RESPONSE))
50962306a36Sopenharmony_ci		ieee80211_tdls_add_aid(sdata, skb);
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	/* build the VHT-cap similarly to the HT-cap */
51262306a36Sopenharmony_ci	if ((action_code == WLAN_TDLS_SETUP_REQUEST ||
51362306a36Sopenharmony_ci	     action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) &&
51462306a36Sopenharmony_ci	    vht_cap.vht_supported) {
51562306a36Sopenharmony_ci		ieee80211_apply_vhtcap_overrides(sdata, &vht_cap);
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci		pos = skb_put(skb, sizeof(struct ieee80211_vht_cap) + 2);
51862306a36Sopenharmony_ci		ieee80211_ie_build_vht_cap(pos, &vht_cap, vht_cap.cap);
51962306a36Sopenharmony_ci	} else if (action_code == WLAN_TDLS_SETUP_RESPONSE &&
52062306a36Sopenharmony_ci		   vht_cap.vht_supported && sta->sta.deflink.vht_cap.vht_supported) {
52162306a36Sopenharmony_ci		/* the peer caps are already intersected with our own */
52262306a36Sopenharmony_ci		memcpy(&vht_cap, &sta->sta.deflink.vht_cap, sizeof(vht_cap));
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci		pos = skb_put(skb, sizeof(struct ieee80211_vht_cap) + 2);
52562306a36Sopenharmony_ci		ieee80211_ie_build_vht_cap(pos, &vht_cap, vht_cap.cap);
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci		/*
52862306a36Sopenharmony_ci		 * if both peers support WIDER_BW, we can expand the chandef to
52962306a36Sopenharmony_ci		 * a wider compatible one, up to 80MHz
53062306a36Sopenharmony_ci		 */
53162306a36Sopenharmony_ci		if (test_sta_flag(sta, WLAN_STA_TDLS_WIDER_BW))
53262306a36Sopenharmony_ci			ieee80211_tdls_chandef_vht_upgrade(sdata, sta);
53362306a36Sopenharmony_ci	}
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	/* add any custom IEs that go before HE capabilities */
53662306a36Sopenharmony_ci	if (extra_ies_len) {
53762306a36Sopenharmony_ci		static const u8 before_he_cap[] = {
53862306a36Sopenharmony_ci			WLAN_EID_EXTENSION,
53962306a36Sopenharmony_ci			WLAN_EID_EXT_FILS_REQ_PARAMS,
54062306a36Sopenharmony_ci			WLAN_EID_AP_CSN,
54162306a36Sopenharmony_ci		};
54262306a36Sopenharmony_ci		noffset = ieee80211_ie_split(extra_ies, extra_ies_len,
54362306a36Sopenharmony_ci					     before_he_cap,
54462306a36Sopenharmony_ci					     ARRAY_SIZE(before_he_cap),
54562306a36Sopenharmony_ci					     offset);
54662306a36Sopenharmony_ci		skb_put_data(skb, extra_ies + offset, noffset - offset);
54762306a36Sopenharmony_ci		offset = noffset;
54862306a36Sopenharmony_ci	}
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	/* build the HE-cap from sband */
55162306a36Sopenharmony_ci	if (he_cap &&
55262306a36Sopenharmony_ci	    (action_code == WLAN_TDLS_SETUP_REQUEST ||
55362306a36Sopenharmony_ci	     action_code == WLAN_TDLS_SETUP_RESPONSE ||
55462306a36Sopenharmony_ci	     action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES)) {
55562306a36Sopenharmony_ci		__le16 he_6ghz_capa;
55662306a36Sopenharmony_ci		u8 cap_size;
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci		cap_size =
55962306a36Sopenharmony_ci			2 + 1 + sizeof(he_cap->he_cap_elem) +
56062306a36Sopenharmony_ci			ieee80211_he_mcs_nss_size(&he_cap->he_cap_elem) +
56162306a36Sopenharmony_ci			ieee80211_he_ppe_size(he_cap->ppe_thres[0],
56262306a36Sopenharmony_ci					      he_cap->he_cap_elem.phy_cap_info);
56362306a36Sopenharmony_ci		pos = skb_put(skb, cap_size);
56462306a36Sopenharmony_ci		pos = ieee80211_ie_build_he_cap(0, pos, he_cap, pos + cap_size);
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci		/* Build HE 6Ghz capa IE from sband */
56762306a36Sopenharmony_ci		if (sband->band == NL80211_BAND_6GHZ) {
56862306a36Sopenharmony_ci			cap_size = 2 + 1 + sizeof(struct ieee80211_he_6ghz_capa);
56962306a36Sopenharmony_ci			pos = skb_put(skb, cap_size);
57062306a36Sopenharmony_ci			he_6ghz_capa =
57162306a36Sopenharmony_ci				ieee80211_get_he_6ghz_capa_vif(sband, &sdata->vif);
57262306a36Sopenharmony_ci			pos = ieee80211_write_he_6ghz_cap(pos, he_6ghz_capa,
57362306a36Sopenharmony_ci							  pos + cap_size);
57462306a36Sopenharmony_ci		}
57562306a36Sopenharmony_ci	}
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	/* add any custom IEs that go before EHT capabilities */
57862306a36Sopenharmony_ci	if (extra_ies_len) {
57962306a36Sopenharmony_ci		static const u8 before_he_cap[] = {
58062306a36Sopenharmony_ci			WLAN_EID_EXTENSION,
58162306a36Sopenharmony_ci			WLAN_EID_EXT_FILS_REQ_PARAMS,
58262306a36Sopenharmony_ci			WLAN_EID_AP_CSN,
58362306a36Sopenharmony_ci		};
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci		noffset = ieee80211_ie_split(extra_ies, extra_ies_len,
58662306a36Sopenharmony_ci					     before_he_cap,
58762306a36Sopenharmony_ci					     ARRAY_SIZE(before_he_cap),
58862306a36Sopenharmony_ci					     offset);
58962306a36Sopenharmony_ci		skb_put_data(skb, extra_ies + offset, noffset - offset);
59062306a36Sopenharmony_ci		offset = noffset;
59162306a36Sopenharmony_ci	}
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	/* build the EHT-cap from sband */
59462306a36Sopenharmony_ci	if (he_cap && eht_cap &&
59562306a36Sopenharmony_ci	    (action_code == WLAN_TDLS_SETUP_REQUEST ||
59662306a36Sopenharmony_ci	     action_code == WLAN_TDLS_SETUP_RESPONSE ||
59762306a36Sopenharmony_ci	     action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES)) {
59862306a36Sopenharmony_ci		u8 cap_size;
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci		cap_size =
60162306a36Sopenharmony_ci			2 + 1 + sizeof(eht_cap->eht_cap_elem) +
60262306a36Sopenharmony_ci			ieee80211_eht_mcs_nss_size(&he_cap->he_cap_elem,
60362306a36Sopenharmony_ci						   &eht_cap->eht_cap_elem, false) +
60462306a36Sopenharmony_ci			ieee80211_eht_ppe_size(eht_cap->eht_ppe_thres[0],
60562306a36Sopenharmony_ci					       eht_cap->eht_cap_elem.phy_cap_info);
60662306a36Sopenharmony_ci		pos = skb_put(skb, cap_size);
60762306a36Sopenharmony_ci		ieee80211_ie_build_eht_cap(pos, he_cap, eht_cap, pos + cap_size, false);
60862306a36Sopenharmony_ci	}
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	/* add any remaining IEs */
61162306a36Sopenharmony_ci	if (extra_ies_len) {
61262306a36Sopenharmony_ci		noffset = extra_ies_len;
61362306a36Sopenharmony_ci		skb_put_data(skb, extra_ies + offset, noffset - offset);
61462306a36Sopenharmony_ci	}
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci}
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_cistatic void
61962306a36Sopenharmony_ciieee80211_tdls_add_setup_cfm_ies(struct ieee80211_link_data *link,
62062306a36Sopenharmony_ci				 struct sk_buff *skb, const u8 *peer,
62162306a36Sopenharmony_ci				 bool initiator, const u8 *extra_ies,
62262306a36Sopenharmony_ci				 size_t extra_ies_len)
62362306a36Sopenharmony_ci{
62462306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = link->sdata;
62562306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
62662306a36Sopenharmony_ci	size_t offset = 0, noffset;
62762306a36Sopenharmony_ci	struct sta_info *sta, *ap_sta;
62862306a36Sopenharmony_ci	struct ieee80211_supported_band *sband;
62962306a36Sopenharmony_ci	u8 *pos;
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	sband = ieee80211_get_link_sband(link);
63262306a36Sopenharmony_ci	if (WARN_ON_ONCE(!sband))
63362306a36Sopenharmony_ci		return;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	sta = sta_info_get(sdata, peer);
63662306a36Sopenharmony_ci	ap_sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ci	if (WARN_ON_ONCE(!sta || !ap_sta))
63962306a36Sopenharmony_ci		return;
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	sta->tdls_chandef = link->conf->chandef;
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_ci	/* add any custom IEs that go before the QoS IE */
64462306a36Sopenharmony_ci	if (extra_ies_len) {
64562306a36Sopenharmony_ci		static const u8 before_qos[] = {
64662306a36Sopenharmony_ci			WLAN_EID_RSN,
64762306a36Sopenharmony_ci		};
64862306a36Sopenharmony_ci		noffset = ieee80211_ie_split(extra_ies, extra_ies_len,
64962306a36Sopenharmony_ci					     before_qos,
65062306a36Sopenharmony_ci					     ARRAY_SIZE(before_qos),
65162306a36Sopenharmony_ci					     offset);
65262306a36Sopenharmony_ci		skb_put_data(skb, extra_ies + offset, noffset - offset);
65362306a36Sopenharmony_ci		offset = noffset;
65462306a36Sopenharmony_ci	}
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	/* add the QoS param IE if both the peer and we support it */
65762306a36Sopenharmony_ci	if (local->hw.queues >= IEEE80211_NUM_ACS && sta->sta.wme)
65862306a36Sopenharmony_ci		ieee80211_tdls_add_wmm_param_ie(sdata, skb);
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	/* add any custom IEs that go before HT operation */
66162306a36Sopenharmony_ci	if (extra_ies_len) {
66262306a36Sopenharmony_ci		static const u8 before_ht_op[] = {
66362306a36Sopenharmony_ci			WLAN_EID_RSN,
66462306a36Sopenharmony_ci			WLAN_EID_QOS_CAPA,
66562306a36Sopenharmony_ci			WLAN_EID_FAST_BSS_TRANSITION,
66662306a36Sopenharmony_ci			WLAN_EID_TIMEOUT_INTERVAL,
66762306a36Sopenharmony_ci		};
66862306a36Sopenharmony_ci		noffset = ieee80211_ie_split(extra_ies, extra_ies_len,
66962306a36Sopenharmony_ci					     before_ht_op,
67062306a36Sopenharmony_ci					     ARRAY_SIZE(before_ht_op),
67162306a36Sopenharmony_ci					     offset);
67262306a36Sopenharmony_ci		skb_put_data(skb, extra_ies + offset, noffset - offset);
67362306a36Sopenharmony_ci		offset = noffset;
67462306a36Sopenharmony_ci	}
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	/*
67762306a36Sopenharmony_ci	 * if HT support is only added in TDLS, we need an HT-operation IE.
67862306a36Sopenharmony_ci	 * add the IE as required by IEEE802.11-2012 9.23.3.2.
67962306a36Sopenharmony_ci	 */
68062306a36Sopenharmony_ci	if (!ap_sta->sta.deflink.ht_cap.ht_supported && sta->sta.deflink.ht_cap.ht_supported) {
68162306a36Sopenharmony_ci		u16 prot = IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED |
68262306a36Sopenharmony_ci			   IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT |
68362306a36Sopenharmony_ci			   IEEE80211_HT_OP_MODE_NON_HT_STA_PRSNT;
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci		pos = skb_put(skb, 2 + sizeof(struct ieee80211_ht_operation));
68662306a36Sopenharmony_ci		ieee80211_ie_build_ht_oper(pos, &sta->sta.deflink.ht_cap,
68762306a36Sopenharmony_ci					   &link->conf->chandef, prot,
68862306a36Sopenharmony_ci					   true);
68962306a36Sopenharmony_ci	}
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	ieee80211_tdls_add_link_ie(link, skb, peer, initiator);
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	/* only include VHT-operation if not on the 2.4GHz band */
69462306a36Sopenharmony_ci	if (sband->band != NL80211_BAND_2GHZ &&
69562306a36Sopenharmony_ci	    sta->sta.deflink.vht_cap.vht_supported) {
69662306a36Sopenharmony_ci		/*
69762306a36Sopenharmony_ci		 * if both peers support WIDER_BW, we can expand the chandef to
69862306a36Sopenharmony_ci		 * a wider compatible one, up to 80MHz
69962306a36Sopenharmony_ci		 */
70062306a36Sopenharmony_ci		if (test_sta_flag(sta, WLAN_STA_TDLS_WIDER_BW))
70162306a36Sopenharmony_ci			ieee80211_tdls_chandef_vht_upgrade(sdata, sta);
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_ci		pos = skb_put(skb, 2 + sizeof(struct ieee80211_vht_operation));
70462306a36Sopenharmony_ci		ieee80211_ie_build_vht_oper(pos, &sta->sta.deflink.vht_cap,
70562306a36Sopenharmony_ci					    &sta->tdls_chandef);
70662306a36Sopenharmony_ci	}
70762306a36Sopenharmony_ci
70862306a36Sopenharmony_ci	/* add any remaining IEs */
70962306a36Sopenharmony_ci	if (extra_ies_len) {
71062306a36Sopenharmony_ci		noffset = extra_ies_len;
71162306a36Sopenharmony_ci		skb_put_data(skb, extra_ies + offset, noffset - offset);
71262306a36Sopenharmony_ci	}
71362306a36Sopenharmony_ci}
71462306a36Sopenharmony_ci
71562306a36Sopenharmony_cistatic void
71662306a36Sopenharmony_ciieee80211_tdls_add_chan_switch_req_ies(struct ieee80211_link_data *link,
71762306a36Sopenharmony_ci				       struct sk_buff *skb, const u8 *peer,
71862306a36Sopenharmony_ci				       bool initiator, const u8 *extra_ies,
71962306a36Sopenharmony_ci				       size_t extra_ies_len, u8 oper_class,
72062306a36Sopenharmony_ci				       struct cfg80211_chan_def *chandef)
72162306a36Sopenharmony_ci{
72262306a36Sopenharmony_ci	struct ieee80211_tdls_data *tf;
72362306a36Sopenharmony_ci	size_t offset = 0, noffset;
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci	if (WARN_ON_ONCE(!chandef))
72662306a36Sopenharmony_ci		return;
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci	tf = (void *)skb->data;
72962306a36Sopenharmony_ci	tf->u.chan_switch_req.target_channel =
73062306a36Sopenharmony_ci		ieee80211_frequency_to_channel(chandef->chan->center_freq);
73162306a36Sopenharmony_ci	tf->u.chan_switch_req.oper_class = oper_class;
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_ci	if (extra_ies_len) {
73462306a36Sopenharmony_ci		static const u8 before_lnkie[] = {
73562306a36Sopenharmony_ci			WLAN_EID_SECONDARY_CHANNEL_OFFSET,
73662306a36Sopenharmony_ci		};
73762306a36Sopenharmony_ci		noffset = ieee80211_ie_split(extra_ies, extra_ies_len,
73862306a36Sopenharmony_ci					     before_lnkie,
73962306a36Sopenharmony_ci					     ARRAY_SIZE(before_lnkie),
74062306a36Sopenharmony_ci					     offset);
74162306a36Sopenharmony_ci		skb_put_data(skb, extra_ies + offset, noffset - offset);
74262306a36Sopenharmony_ci		offset = noffset;
74362306a36Sopenharmony_ci	}
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci	ieee80211_tdls_add_link_ie(link, skb, peer, initiator);
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci	/* add any remaining IEs */
74862306a36Sopenharmony_ci	if (extra_ies_len) {
74962306a36Sopenharmony_ci		noffset = extra_ies_len;
75062306a36Sopenharmony_ci		skb_put_data(skb, extra_ies + offset, noffset - offset);
75162306a36Sopenharmony_ci	}
75262306a36Sopenharmony_ci}
75362306a36Sopenharmony_ci
75462306a36Sopenharmony_cistatic void
75562306a36Sopenharmony_ciieee80211_tdls_add_chan_switch_resp_ies(struct ieee80211_link_data *link,
75662306a36Sopenharmony_ci					struct sk_buff *skb, const u8 *peer,
75762306a36Sopenharmony_ci					u16 status_code, bool initiator,
75862306a36Sopenharmony_ci					const u8 *extra_ies,
75962306a36Sopenharmony_ci					size_t extra_ies_len)
76062306a36Sopenharmony_ci{
76162306a36Sopenharmony_ci	if (status_code == 0)
76262306a36Sopenharmony_ci		ieee80211_tdls_add_link_ie(link, skb, peer, initiator);
76362306a36Sopenharmony_ci
76462306a36Sopenharmony_ci	if (extra_ies_len)
76562306a36Sopenharmony_ci		skb_put_data(skb, extra_ies, extra_ies_len);
76662306a36Sopenharmony_ci}
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_cistatic void ieee80211_tdls_add_ies(struct ieee80211_link_data *link,
76962306a36Sopenharmony_ci				   struct sk_buff *skb, const u8 *peer,
77062306a36Sopenharmony_ci				   u8 action_code, u16 status_code,
77162306a36Sopenharmony_ci				   bool initiator, const u8 *extra_ies,
77262306a36Sopenharmony_ci				   size_t extra_ies_len, u8 oper_class,
77362306a36Sopenharmony_ci				   struct cfg80211_chan_def *chandef)
77462306a36Sopenharmony_ci{
77562306a36Sopenharmony_ci	switch (action_code) {
77662306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_REQUEST:
77762306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_RESPONSE:
77862306a36Sopenharmony_ci	case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
77962306a36Sopenharmony_ci		if (status_code == 0)
78062306a36Sopenharmony_ci			ieee80211_tdls_add_setup_start_ies(link,
78162306a36Sopenharmony_ci							   skb, peer,
78262306a36Sopenharmony_ci							   action_code,
78362306a36Sopenharmony_ci							   initiator,
78462306a36Sopenharmony_ci							   extra_ies,
78562306a36Sopenharmony_ci							   extra_ies_len);
78662306a36Sopenharmony_ci		break;
78762306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_CONFIRM:
78862306a36Sopenharmony_ci		if (status_code == 0)
78962306a36Sopenharmony_ci			ieee80211_tdls_add_setup_cfm_ies(link, skb, peer,
79062306a36Sopenharmony_ci							 initiator, extra_ies,
79162306a36Sopenharmony_ci							 extra_ies_len);
79262306a36Sopenharmony_ci		break;
79362306a36Sopenharmony_ci	case WLAN_TDLS_TEARDOWN:
79462306a36Sopenharmony_ci	case WLAN_TDLS_DISCOVERY_REQUEST:
79562306a36Sopenharmony_ci		if (extra_ies_len)
79662306a36Sopenharmony_ci			skb_put_data(skb, extra_ies, extra_ies_len);
79762306a36Sopenharmony_ci		if (status_code == 0 || action_code == WLAN_TDLS_TEARDOWN)
79862306a36Sopenharmony_ci			ieee80211_tdls_add_link_ie(link, skb,
79962306a36Sopenharmony_ci						   peer, initiator);
80062306a36Sopenharmony_ci		break;
80162306a36Sopenharmony_ci	case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
80262306a36Sopenharmony_ci		ieee80211_tdls_add_chan_switch_req_ies(link, skb, peer,
80362306a36Sopenharmony_ci						       initiator, extra_ies,
80462306a36Sopenharmony_ci						       extra_ies_len,
80562306a36Sopenharmony_ci						       oper_class, chandef);
80662306a36Sopenharmony_ci		break;
80762306a36Sopenharmony_ci	case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
80862306a36Sopenharmony_ci		ieee80211_tdls_add_chan_switch_resp_ies(link, skb, peer,
80962306a36Sopenharmony_ci							status_code,
81062306a36Sopenharmony_ci							initiator, extra_ies,
81162306a36Sopenharmony_ci							extra_ies_len);
81262306a36Sopenharmony_ci		break;
81362306a36Sopenharmony_ci	}
81462306a36Sopenharmony_ci
81562306a36Sopenharmony_ci}
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_cistatic int
81862306a36Sopenharmony_ciieee80211_prep_tdls_encap_data(struct wiphy *wiphy, struct net_device *dev,
81962306a36Sopenharmony_ci			       struct ieee80211_link_data *link,
82062306a36Sopenharmony_ci			       const u8 *peer, u8 action_code, u8 dialog_token,
82162306a36Sopenharmony_ci			       u16 status_code, struct sk_buff *skb)
82262306a36Sopenharmony_ci{
82362306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
82462306a36Sopenharmony_ci	struct ieee80211_tdls_data *tf;
82562306a36Sopenharmony_ci
82662306a36Sopenharmony_ci	tf = skb_put(skb, offsetof(struct ieee80211_tdls_data, u));
82762306a36Sopenharmony_ci
82862306a36Sopenharmony_ci	memcpy(tf->da, peer, ETH_ALEN);
82962306a36Sopenharmony_ci	memcpy(tf->sa, sdata->vif.addr, ETH_ALEN);
83062306a36Sopenharmony_ci	tf->ether_type = cpu_to_be16(ETH_P_TDLS);
83162306a36Sopenharmony_ci	tf->payload_type = WLAN_TDLS_SNAP_RFTYPE;
83262306a36Sopenharmony_ci
83362306a36Sopenharmony_ci	/* network header is after the ethernet header */
83462306a36Sopenharmony_ci	skb_set_network_header(skb, ETH_HLEN);
83562306a36Sopenharmony_ci
83662306a36Sopenharmony_ci	switch (action_code) {
83762306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_REQUEST:
83862306a36Sopenharmony_ci		tf->category = WLAN_CATEGORY_TDLS;
83962306a36Sopenharmony_ci		tf->action_code = WLAN_TDLS_SETUP_REQUEST;
84062306a36Sopenharmony_ci
84162306a36Sopenharmony_ci		skb_put(skb, sizeof(tf->u.setup_req));
84262306a36Sopenharmony_ci		tf->u.setup_req.dialog_token = dialog_token;
84362306a36Sopenharmony_ci		tf->u.setup_req.capability =
84462306a36Sopenharmony_ci			cpu_to_le16(ieee80211_get_tdls_sta_capab(link,
84562306a36Sopenharmony_ci								 status_code));
84662306a36Sopenharmony_ci		break;
84762306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_RESPONSE:
84862306a36Sopenharmony_ci		tf->category = WLAN_CATEGORY_TDLS;
84962306a36Sopenharmony_ci		tf->action_code = WLAN_TDLS_SETUP_RESPONSE;
85062306a36Sopenharmony_ci
85162306a36Sopenharmony_ci		skb_put(skb, sizeof(tf->u.setup_resp));
85262306a36Sopenharmony_ci		tf->u.setup_resp.status_code = cpu_to_le16(status_code);
85362306a36Sopenharmony_ci		tf->u.setup_resp.dialog_token = dialog_token;
85462306a36Sopenharmony_ci		tf->u.setup_resp.capability =
85562306a36Sopenharmony_ci			cpu_to_le16(ieee80211_get_tdls_sta_capab(link,
85662306a36Sopenharmony_ci								 status_code));
85762306a36Sopenharmony_ci		break;
85862306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_CONFIRM:
85962306a36Sopenharmony_ci		tf->category = WLAN_CATEGORY_TDLS;
86062306a36Sopenharmony_ci		tf->action_code = WLAN_TDLS_SETUP_CONFIRM;
86162306a36Sopenharmony_ci
86262306a36Sopenharmony_ci		skb_put(skb, sizeof(tf->u.setup_cfm));
86362306a36Sopenharmony_ci		tf->u.setup_cfm.status_code = cpu_to_le16(status_code);
86462306a36Sopenharmony_ci		tf->u.setup_cfm.dialog_token = dialog_token;
86562306a36Sopenharmony_ci		break;
86662306a36Sopenharmony_ci	case WLAN_TDLS_TEARDOWN:
86762306a36Sopenharmony_ci		tf->category = WLAN_CATEGORY_TDLS;
86862306a36Sopenharmony_ci		tf->action_code = WLAN_TDLS_TEARDOWN;
86962306a36Sopenharmony_ci
87062306a36Sopenharmony_ci		skb_put(skb, sizeof(tf->u.teardown));
87162306a36Sopenharmony_ci		tf->u.teardown.reason_code = cpu_to_le16(status_code);
87262306a36Sopenharmony_ci		break;
87362306a36Sopenharmony_ci	case WLAN_TDLS_DISCOVERY_REQUEST:
87462306a36Sopenharmony_ci		tf->category = WLAN_CATEGORY_TDLS;
87562306a36Sopenharmony_ci		tf->action_code = WLAN_TDLS_DISCOVERY_REQUEST;
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_ci		skb_put(skb, sizeof(tf->u.discover_req));
87862306a36Sopenharmony_ci		tf->u.discover_req.dialog_token = dialog_token;
87962306a36Sopenharmony_ci		break;
88062306a36Sopenharmony_ci	case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
88162306a36Sopenharmony_ci		tf->category = WLAN_CATEGORY_TDLS;
88262306a36Sopenharmony_ci		tf->action_code = WLAN_TDLS_CHANNEL_SWITCH_REQUEST;
88362306a36Sopenharmony_ci
88462306a36Sopenharmony_ci		skb_put(skb, sizeof(tf->u.chan_switch_req));
88562306a36Sopenharmony_ci		break;
88662306a36Sopenharmony_ci	case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
88762306a36Sopenharmony_ci		tf->category = WLAN_CATEGORY_TDLS;
88862306a36Sopenharmony_ci		tf->action_code = WLAN_TDLS_CHANNEL_SWITCH_RESPONSE;
88962306a36Sopenharmony_ci
89062306a36Sopenharmony_ci		skb_put(skb, sizeof(tf->u.chan_switch_resp));
89162306a36Sopenharmony_ci		tf->u.chan_switch_resp.status_code = cpu_to_le16(status_code);
89262306a36Sopenharmony_ci		break;
89362306a36Sopenharmony_ci	default:
89462306a36Sopenharmony_ci		return -EINVAL;
89562306a36Sopenharmony_ci	}
89662306a36Sopenharmony_ci
89762306a36Sopenharmony_ci	return 0;
89862306a36Sopenharmony_ci}
89962306a36Sopenharmony_ci
90062306a36Sopenharmony_cistatic int
90162306a36Sopenharmony_ciieee80211_prep_tdls_direct(struct wiphy *wiphy, struct net_device *dev,
90262306a36Sopenharmony_ci			   const u8 *peer, struct ieee80211_link_data *link,
90362306a36Sopenharmony_ci			   u8 action_code, u8 dialog_token,
90462306a36Sopenharmony_ci			   u16 status_code, struct sk_buff *skb)
90562306a36Sopenharmony_ci{
90662306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
90762306a36Sopenharmony_ci	struct ieee80211_mgmt *mgmt;
90862306a36Sopenharmony_ci
90962306a36Sopenharmony_ci	mgmt = skb_put_zero(skb, 24);
91062306a36Sopenharmony_ci	memcpy(mgmt->da, peer, ETH_ALEN);
91162306a36Sopenharmony_ci	memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
91262306a36Sopenharmony_ci	memcpy(mgmt->bssid, link->u.mgd.bssid, ETH_ALEN);
91362306a36Sopenharmony_ci	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
91462306a36Sopenharmony_ci					  IEEE80211_STYPE_ACTION);
91562306a36Sopenharmony_ci
91662306a36Sopenharmony_ci	switch (action_code) {
91762306a36Sopenharmony_ci	case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
91862306a36Sopenharmony_ci		skb_put(skb, 1 + sizeof(mgmt->u.action.u.tdls_discover_resp));
91962306a36Sopenharmony_ci		mgmt->u.action.category = WLAN_CATEGORY_PUBLIC;
92062306a36Sopenharmony_ci		mgmt->u.action.u.tdls_discover_resp.action_code =
92162306a36Sopenharmony_ci			WLAN_PUB_ACTION_TDLS_DISCOVER_RES;
92262306a36Sopenharmony_ci		mgmt->u.action.u.tdls_discover_resp.dialog_token =
92362306a36Sopenharmony_ci			dialog_token;
92462306a36Sopenharmony_ci		mgmt->u.action.u.tdls_discover_resp.capability =
92562306a36Sopenharmony_ci			cpu_to_le16(ieee80211_get_tdls_sta_capab(link,
92662306a36Sopenharmony_ci								 status_code));
92762306a36Sopenharmony_ci		break;
92862306a36Sopenharmony_ci	default:
92962306a36Sopenharmony_ci		return -EINVAL;
93062306a36Sopenharmony_ci	}
93162306a36Sopenharmony_ci
93262306a36Sopenharmony_ci	return 0;
93362306a36Sopenharmony_ci}
93462306a36Sopenharmony_ci
93562306a36Sopenharmony_cistatic struct sk_buff *
93662306a36Sopenharmony_ciieee80211_tdls_build_mgmt_packet_data(struct ieee80211_sub_if_data *sdata,
93762306a36Sopenharmony_ci				      const u8 *peer, int link_id,
93862306a36Sopenharmony_ci				      u8 action_code, u8 dialog_token,
93962306a36Sopenharmony_ci				      u16 status_code, bool initiator,
94062306a36Sopenharmony_ci				      const u8 *extra_ies, size_t extra_ies_len,
94162306a36Sopenharmony_ci				      u8 oper_class,
94262306a36Sopenharmony_ci				      struct cfg80211_chan_def *chandef)
94362306a36Sopenharmony_ci{
94462306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
94562306a36Sopenharmony_ci	struct sk_buff *skb;
94662306a36Sopenharmony_ci	int ret;
94762306a36Sopenharmony_ci	struct ieee80211_link_data *link;
94862306a36Sopenharmony_ci
94962306a36Sopenharmony_ci	link_id = link_id >= 0 ? link_id : 0;
95062306a36Sopenharmony_ci	rcu_read_lock();
95162306a36Sopenharmony_ci	link = rcu_dereference(sdata->link[link_id]);
95262306a36Sopenharmony_ci	if (WARN_ON(!link))
95362306a36Sopenharmony_ci		goto unlock;
95462306a36Sopenharmony_ci
95562306a36Sopenharmony_ci	skb = netdev_alloc_skb(sdata->dev,
95662306a36Sopenharmony_ci			       local->hw.extra_tx_headroom +
95762306a36Sopenharmony_ci			       max(sizeof(struct ieee80211_mgmt),
95862306a36Sopenharmony_ci				   sizeof(struct ieee80211_tdls_data)) +
95962306a36Sopenharmony_ci			       50 + /* supported rates */
96062306a36Sopenharmony_ci			       10 + /* ext capab */
96162306a36Sopenharmony_ci			       26 + /* max(WMM-info, WMM-param) */
96262306a36Sopenharmony_ci			       2 + max(sizeof(struct ieee80211_ht_cap),
96362306a36Sopenharmony_ci				       sizeof(struct ieee80211_ht_operation)) +
96462306a36Sopenharmony_ci			       2 + max(sizeof(struct ieee80211_vht_cap),
96562306a36Sopenharmony_ci				       sizeof(struct ieee80211_vht_operation)) +
96662306a36Sopenharmony_ci			       2 + 1 + sizeof(struct ieee80211_he_cap_elem) +
96762306a36Sopenharmony_ci				       sizeof(struct ieee80211_he_mcs_nss_supp) +
96862306a36Sopenharmony_ci				       IEEE80211_HE_PPE_THRES_MAX_LEN +
96962306a36Sopenharmony_ci			       2 + 1 + sizeof(struct ieee80211_he_6ghz_capa) +
97062306a36Sopenharmony_ci			       2 + 1 + sizeof(struct ieee80211_eht_cap_elem) +
97162306a36Sopenharmony_ci				       sizeof(struct ieee80211_eht_mcs_nss_supp) +
97262306a36Sopenharmony_ci				       IEEE80211_EHT_PPE_THRES_MAX_LEN +
97362306a36Sopenharmony_ci			       50 + /* supported channels */
97462306a36Sopenharmony_ci			       3 + /* 40/20 BSS coex */
97562306a36Sopenharmony_ci			       4 + /* AID */
97662306a36Sopenharmony_ci			       4 + /* oper classes */
97762306a36Sopenharmony_ci			       extra_ies_len +
97862306a36Sopenharmony_ci			       sizeof(struct ieee80211_tdls_lnkie));
97962306a36Sopenharmony_ci	if (!skb)
98062306a36Sopenharmony_ci		goto unlock;
98162306a36Sopenharmony_ci
98262306a36Sopenharmony_ci	skb_reserve(skb, local->hw.extra_tx_headroom);
98362306a36Sopenharmony_ci
98462306a36Sopenharmony_ci	switch (action_code) {
98562306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_REQUEST:
98662306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_RESPONSE:
98762306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_CONFIRM:
98862306a36Sopenharmony_ci	case WLAN_TDLS_TEARDOWN:
98962306a36Sopenharmony_ci	case WLAN_TDLS_DISCOVERY_REQUEST:
99062306a36Sopenharmony_ci	case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
99162306a36Sopenharmony_ci	case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
99262306a36Sopenharmony_ci		ret = ieee80211_prep_tdls_encap_data(local->hw.wiphy,
99362306a36Sopenharmony_ci						     sdata->dev, link, peer,
99462306a36Sopenharmony_ci						     action_code, dialog_token,
99562306a36Sopenharmony_ci						     status_code, skb);
99662306a36Sopenharmony_ci		break;
99762306a36Sopenharmony_ci	case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
99862306a36Sopenharmony_ci		ret = ieee80211_prep_tdls_direct(local->hw.wiphy, sdata->dev,
99962306a36Sopenharmony_ci						 peer, link, action_code,
100062306a36Sopenharmony_ci						 dialog_token, status_code,
100162306a36Sopenharmony_ci						 skb);
100262306a36Sopenharmony_ci		break;
100362306a36Sopenharmony_ci	default:
100462306a36Sopenharmony_ci		ret = -ENOTSUPP;
100562306a36Sopenharmony_ci		break;
100662306a36Sopenharmony_ci	}
100762306a36Sopenharmony_ci
100862306a36Sopenharmony_ci	if (ret < 0)
100962306a36Sopenharmony_ci		goto fail;
101062306a36Sopenharmony_ci
101162306a36Sopenharmony_ci	ieee80211_tdls_add_ies(link, skb, peer, action_code, status_code,
101262306a36Sopenharmony_ci			       initiator, extra_ies, extra_ies_len, oper_class,
101362306a36Sopenharmony_ci			       chandef);
101462306a36Sopenharmony_ci	rcu_read_unlock();
101562306a36Sopenharmony_ci	return skb;
101662306a36Sopenharmony_ci
101762306a36Sopenharmony_cifail:
101862306a36Sopenharmony_ci	dev_kfree_skb(skb);
101962306a36Sopenharmony_ciunlock:
102062306a36Sopenharmony_ci	rcu_read_unlock();
102162306a36Sopenharmony_ci	return NULL;
102262306a36Sopenharmony_ci}
102362306a36Sopenharmony_ci
102462306a36Sopenharmony_cistatic int
102562306a36Sopenharmony_ciieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
102662306a36Sopenharmony_ci				const u8 *peer, int link_id,
102762306a36Sopenharmony_ci				u8 action_code, u8 dialog_token,
102862306a36Sopenharmony_ci				u16 status_code, u32 peer_capability,
102962306a36Sopenharmony_ci				bool initiator, const u8 *extra_ies,
103062306a36Sopenharmony_ci				size_t extra_ies_len, u8 oper_class,
103162306a36Sopenharmony_ci				struct cfg80211_chan_def *chandef)
103262306a36Sopenharmony_ci{
103362306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
103462306a36Sopenharmony_ci	struct sk_buff *skb = NULL;
103562306a36Sopenharmony_ci	struct sta_info *sta;
103662306a36Sopenharmony_ci	u32 flags = 0;
103762306a36Sopenharmony_ci	int ret = 0;
103862306a36Sopenharmony_ci
103962306a36Sopenharmony_ci	rcu_read_lock();
104062306a36Sopenharmony_ci	sta = sta_info_get(sdata, peer);
104162306a36Sopenharmony_ci
104262306a36Sopenharmony_ci	/* infer the initiator if we can, to support old userspace */
104362306a36Sopenharmony_ci	switch (action_code) {
104462306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_REQUEST:
104562306a36Sopenharmony_ci		if (sta) {
104662306a36Sopenharmony_ci			set_sta_flag(sta, WLAN_STA_TDLS_INITIATOR);
104762306a36Sopenharmony_ci			sta->sta.tdls_initiator = false;
104862306a36Sopenharmony_ci		}
104962306a36Sopenharmony_ci		fallthrough;
105062306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_CONFIRM:
105162306a36Sopenharmony_ci	case WLAN_TDLS_DISCOVERY_REQUEST:
105262306a36Sopenharmony_ci		initiator = true;
105362306a36Sopenharmony_ci		break;
105462306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_RESPONSE:
105562306a36Sopenharmony_ci		/*
105662306a36Sopenharmony_ci		 * In some testing scenarios, we send a request and response.
105762306a36Sopenharmony_ci		 * Make the last packet sent take effect for the initiator
105862306a36Sopenharmony_ci		 * value.
105962306a36Sopenharmony_ci		 */
106062306a36Sopenharmony_ci		if (sta) {
106162306a36Sopenharmony_ci			clear_sta_flag(sta, WLAN_STA_TDLS_INITIATOR);
106262306a36Sopenharmony_ci			sta->sta.tdls_initiator = true;
106362306a36Sopenharmony_ci		}
106462306a36Sopenharmony_ci		fallthrough;
106562306a36Sopenharmony_ci	case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
106662306a36Sopenharmony_ci		initiator = false;
106762306a36Sopenharmony_ci		break;
106862306a36Sopenharmony_ci	case WLAN_TDLS_TEARDOWN:
106962306a36Sopenharmony_ci	case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
107062306a36Sopenharmony_ci	case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
107162306a36Sopenharmony_ci		/* any value is ok */
107262306a36Sopenharmony_ci		break;
107362306a36Sopenharmony_ci	default:
107462306a36Sopenharmony_ci		ret = -ENOTSUPP;
107562306a36Sopenharmony_ci		break;
107662306a36Sopenharmony_ci	}
107762306a36Sopenharmony_ci
107862306a36Sopenharmony_ci	if (sta && test_sta_flag(sta, WLAN_STA_TDLS_INITIATOR))
107962306a36Sopenharmony_ci		initiator = true;
108062306a36Sopenharmony_ci
108162306a36Sopenharmony_ci	rcu_read_unlock();
108262306a36Sopenharmony_ci	if (ret < 0)
108362306a36Sopenharmony_ci		goto fail;
108462306a36Sopenharmony_ci
108562306a36Sopenharmony_ci	skb = ieee80211_tdls_build_mgmt_packet_data(sdata, peer,
108662306a36Sopenharmony_ci						    link_id, action_code,
108762306a36Sopenharmony_ci						    dialog_token, status_code,
108862306a36Sopenharmony_ci						    initiator, extra_ies,
108962306a36Sopenharmony_ci						    extra_ies_len, oper_class,
109062306a36Sopenharmony_ci						    chandef);
109162306a36Sopenharmony_ci	if (!skb) {
109262306a36Sopenharmony_ci		ret = -EINVAL;
109362306a36Sopenharmony_ci		goto fail;
109462306a36Sopenharmony_ci	}
109562306a36Sopenharmony_ci
109662306a36Sopenharmony_ci	if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) {
109762306a36Sopenharmony_ci		ieee80211_tx_skb_tid(sdata, skb, 7, link_id);
109862306a36Sopenharmony_ci		return 0;
109962306a36Sopenharmony_ci	}
110062306a36Sopenharmony_ci
110162306a36Sopenharmony_ci	/*
110262306a36Sopenharmony_ci	 * According to 802.11z: Setup req/resp are sent in AC_BK, otherwise
110362306a36Sopenharmony_ci	 * we should default to AC_VI.
110462306a36Sopenharmony_ci	 */
110562306a36Sopenharmony_ci	switch (action_code) {
110662306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_REQUEST:
110762306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_RESPONSE:
110862306a36Sopenharmony_ci		skb->priority = 256 + 2;
110962306a36Sopenharmony_ci		break;
111062306a36Sopenharmony_ci	default:
111162306a36Sopenharmony_ci		skb->priority = 256 + 5;
111262306a36Sopenharmony_ci		break;
111362306a36Sopenharmony_ci	}
111462306a36Sopenharmony_ci
111562306a36Sopenharmony_ci	/*
111662306a36Sopenharmony_ci	 * Set the WLAN_TDLS_TEARDOWN flag to indicate a teardown in progress.
111762306a36Sopenharmony_ci	 * Later, if no ACK is returned from peer, we will re-send the teardown
111862306a36Sopenharmony_ci	 * packet through the AP.
111962306a36Sopenharmony_ci	 */
112062306a36Sopenharmony_ci	if ((action_code == WLAN_TDLS_TEARDOWN) &&
112162306a36Sopenharmony_ci	    ieee80211_hw_check(&sdata->local->hw, REPORTS_TX_ACK_STATUS)) {
112262306a36Sopenharmony_ci		bool try_resend; /* Should we keep skb for possible resend */
112362306a36Sopenharmony_ci
112462306a36Sopenharmony_ci		/* If not sending directly to peer - no point in keeping skb */
112562306a36Sopenharmony_ci		rcu_read_lock();
112662306a36Sopenharmony_ci		sta = sta_info_get(sdata, peer);
112762306a36Sopenharmony_ci		try_resend = sta && test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH);
112862306a36Sopenharmony_ci		rcu_read_unlock();
112962306a36Sopenharmony_ci
113062306a36Sopenharmony_ci		spin_lock_bh(&sdata->u.mgd.teardown_lock);
113162306a36Sopenharmony_ci		if (try_resend && !sdata->u.mgd.teardown_skb) {
113262306a36Sopenharmony_ci			/* Mark it as requiring TX status callback  */
113362306a36Sopenharmony_ci			flags |= IEEE80211_TX_CTL_REQ_TX_STATUS |
113462306a36Sopenharmony_ci				 IEEE80211_TX_INTFL_MLME_CONN_TX;
113562306a36Sopenharmony_ci
113662306a36Sopenharmony_ci			/*
113762306a36Sopenharmony_ci			 * skb is copied since mac80211 will later set
113862306a36Sopenharmony_ci			 * properties that might not be the same as the AP,
113962306a36Sopenharmony_ci			 * such as encryption, QoS, addresses, etc.
114062306a36Sopenharmony_ci			 *
114162306a36Sopenharmony_ci			 * No problem if skb_copy() fails, so no need to check.
114262306a36Sopenharmony_ci			 */
114362306a36Sopenharmony_ci			sdata->u.mgd.teardown_skb = skb_copy(skb, GFP_ATOMIC);
114462306a36Sopenharmony_ci			sdata->u.mgd.orig_teardown_skb = skb;
114562306a36Sopenharmony_ci		}
114662306a36Sopenharmony_ci		spin_unlock_bh(&sdata->u.mgd.teardown_lock);
114762306a36Sopenharmony_ci	}
114862306a36Sopenharmony_ci
114962306a36Sopenharmony_ci	/* disable bottom halves when entering the Tx path */
115062306a36Sopenharmony_ci	local_bh_disable();
115162306a36Sopenharmony_ci	__ieee80211_subif_start_xmit(skb, dev, flags,
115262306a36Sopenharmony_ci				     IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, NULL);
115362306a36Sopenharmony_ci	local_bh_enable();
115462306a36Sopenharmony_ci
115562306a36Sopenharmony_ci	return ret;
115662306a36Sopenharmony_ci
115762306a36Sopenharmony_cifail:
115862306a36Sopenharmony_ci	dev_kfree_skb(skb);
115962306a36Sopenharmony_ci	return ret;
116062306a36Sopenharmony_ci}
116162306a36Sopenharmony_ci
116262306a36Sopenharmony_cistatic int
116362306a36Sopenharmony_ciieee80211_tdls_mgmt_setup(struct wiphy *wiphy, struct net_device *dev,
116462306a36Sopenharmony_ci			  const u8 *peer, int link_id,
116562306a36Sopenharmony_ci			  u8 action_code, u8 dialog_token,
116662306a36Sopenharmony_ci			  u16 status_code, u32 peer_capability, bool initiator,
116762306a36Sopenharmony_ci			  const u8 *extra_ies, size_t extra_ies_len)
116862306a36Sopenharmony_ci{
116962306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
117062306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
117162306a36Sopenharmony_ci	enum ieee80211_smps_mode smps_mode =
117262306a36Sopenharmony_ci		sdata->deflink.u.mgd.driver_smps_mode;
117362306a36Sopenharmony_ci	int ret;
117462306a36Sopenharmony_ci
117562306a36Sopenharmony_ci	/* don't support setup with forced SMPS mode that's not off */
117662306a36Sopenharmony_ci	if (smps_mode != IEEE80211_SMPS_AUTOMATIC &&
117762306a36Sopenharmony_ci	    smps_mode != IEEE80211_SMPS_OFF) {
117862306a36Sopenharmony_ci		tdls_dbg(sdata, "Aborting TDLS setup due to SMPS mode %d\n",
117962306a36Sopenharmony_ci			 smps_mode);
118062306a36Sopenharmony_ci		return -ENOTSUPP;
118162306a36Sopenharmony_ci	}
118262306a36Sopenharmony_ci
118362306a36Sopenharmony_ci	mutex_lock(&local->mtx);
118462306a36Sopenharmony_ci
118562306a36Sopenharmony_ci	/* we don't support concurrent TDLS peer setups */
118662306a36Sopenharmony_ci	if (!is_zero_ether_addr(sdata->u.mgd.tdls_peer) &&
118762306a36Sopenharmony_ci	    !ether_addr_equal(sdata->u.mgd.tdls_peer, peer)) {
118862306a36Sopenharmony_ci		ret = -EBUSY;
118962306a36Sopenharmony_ci		goto out_unlock;
119062306a36Sopenharmony_ci	}
119162306a36Sopenharmony_ci
119262306a36Sopenharmony_ci	/*
119362306a36Sopenharmony_ci	 * make sure we have a STA representing the peer so we drop or buffer
119462306a36Sopenharmony_ci	 * non-TDLS-setup frames to the peer. We can't send other packets
119562306a36Sopenharmony_ci	 * during setup through the AP path.
119662306a36Sopenharmony_ci	 * Allow error packets to be sent - sometimes we don't even add a STA
119762306a36Sopenharmony_ci	 * before failing the setup.
119862306a36Sopenharmony_ci	 */
119962306a36Sopenharmony_ci	if (status_code == 0) {
120062306a36Sopenharmony_ci		rcu_read_lock();
120162306a36Sopenharmony_ci		if (!sta_info_get(sdata, peer)) {
120262306a36Sopenharmony_ci			rcu_read_unlock();
120362306a36Sopenharmony_ci			ret = -ENOLINK;
120462306a36Sopenharmony_ci			goto out_unlock;
120562306a36Sopenharmony_ci		}
120662306a36Sopenharmony_ci		rcu_read_unlock();
120762306a36Sopenharmony_ci	}
120862306a36Sopenharmony_ci
120962306a36Sopenharmony_ci	ieee80211_flush_queues(local, sdata, false);
121062306a36Sopenharmony_ci	memcpy(sdata->u.mgd.tdls_peer, peer, ETH_ALEN);
121162306a36Sopenharmony_ci	mutex_unlock(&local->mtx);
121262306a36Sopenharmony_ci
121362306a36Sopenharmony_ci	/* we cannot take the mutex while preparing the setup packet */
121462306a36Sopenharmony_ci	ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer,
121562306a36Sopenharmony_ci					      link_id, action_code,
121662306a36Sopenharmony_ci					      dialog_token, status_code,
121762306a36Sopenharmony_ci					      peer_capability, initiator,
121862306a36Sopenharmony_ci					      extra_ies, extra_ies_len, 0,
121962306a36Sopenharmony_ci					      NULL);
122062306a36Sopenharmony_ci	if (ret < 0) {
122162306a36Sopenharmony_ci		mutex_lock(&local->mtx);
122262306a36Sopenharmony_ci		eth_zero_addr(sdata->u.mgd.tdls_peer);
122362306a36Sopenharmony_ci		mutex_unlock(&local->mtx);
122462306a36Sopenharmony_ci		return ret;
122562306a36Sopenharmony_ci	}
122662306a36Sopenharmony_ci
122762306a36Sopenharmony_ci	ieee80211_queue_delayed_work(&sdata->local->hw,
122862306a36Sopenharmony_ci				     &sdata->u.mgd.tdls_peer_del_work,
122962306a36Sopenharmony_ci				     TDLS_PEER_SETUP_TIMEOUT);
123062306a36Sopenharmony_ci	return 0;
123162306a36Sopenharmony_ci
123262306a36Sopenharmony_ciout_unlock:
123362306a36Sopenharmony_ci	mutex_unlock(&local->mtx);
123462306a36Sopenharmony_ci	return ret;
123562306a36Sopenharmony_ci}
123662306a36Sopenharmony_ci
123762306a36Sopenharmony_cistatic int
123862306a36Sopenharmony_ciieee80211_tdls_mgmt_teardown(struct wiphy *wiphy, struct net_device *dev,
123962306a36Sopenharmony_ci			     const u8 *peer, int link_id,
124062306a36Sopenharmony_ci			     u8 action_code, u8 dialog_token,
124162306a36Sopenharmony_ci			     u16 status_code, u32 peer_capability,
124262306a36Sopenharmony_ci			     bool initiator, const u8 *extra_ies,
124362306a36Sopenharmony_ci			     size_t extra_ies_len)
124462306a36Sopenharmony_ci{
124562306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
124662306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
124762306a36Sopenharmony_ci	struct sta_info *sta;
124862306a36Sopenharmony_ci	int ret;
124962306a36Sopenharmony_ci
125062306a36Sopenharmony_ci	/*
125162306a36Sopenharmony_ci	 * No packets can be transmitted to the peer via the AP during setup -
125262306a36Sopenharmony_ci	 * the STA is set as a TDLS peer, but is not authorized.
125362306a36Sopenharmony_ci	 * During teardown, we prevent direct transmissions by stopping the
125462306a36Sopenharmony_ci	 * queues and flushing all direct packets.
125562306a36Sopenharmony_ci	 */
125662306a36Sopenharmony_ci	ieee80211_stop_vif_queues(local, sdata,
125762306a36Sopenharmony_ci				  IEEE80211_QUEUE_STOP_REASON_TDLS_TEARDOWN);
125862306a36Sopenharmony_ci	ieee80211_flush_queues(local, sdata, false);
125962306a36Sopenharmony_ci
126062306a36Sopenharmony_ci	ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer,
126162306a36Sopenharmony_ci					      link_id, action_code,
126262306a36Sopenharmony_ci					      dialog_token, status_code,
126362306a36Sopenharmony_ci					      peer_capability, initiator,
126462306a36Sopenharmony_ci					      extra_ies, extra_ies_len, 0,
126562306a36Sopenharmony_ci					      NULL);
126662306a36Sopenharmony_ci	if (ret < 0)
126762306a36Sopenharmony_ci		sdata_err(sdata, "Failed sending TDLS teardown packet %d\n",
126862306a36Sopenharmony_ci			  ret);
126962306a36Sopenharmony_ci
127062306a36Sopenharmony_ci	/*
127162306a36Sopenharmony_ci	 * Remove the STA AUTH flag to force further traffic through the AP. If
127262306a36Sopenharmony_ci	 * the STA was unreachable, it was already removed.
127362306a36Sopenharmony_ci	 */
127462306a36Sopenharmony_ci	rcu_read_lock();
127562306a36Sopenharmony_ci	sta = sta_info_get(sdata, peer);
127662306a36Sopenharmony_ci	if (sta)
127762306a36Sopenharmony_ci		clear_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH);
127862306a36Sopenharmony_ci	rcu_read_unlock();
127962306a36Sopenharmony_ci
128062306a36Sopenharmony_ci	ieee80211_wake_vif_queues(local, sdata,
128162306a36Sopenharmony_ci				  IEEE80211_QUEUE_STOP_REASON_TDLS_TEARDOWN);
128262306a36Sopenharmony_ci
128362306a36Sopenharmony_ci	return 0;
128462306a36Sopenharmony_ci}
128562306a36Sopenharmony_ci
128662306a36Sopenharmony_ciint ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
128762306a36Sopenharmony_ci			const u8 *peer, int link_id,
128862306a36Sopenharmony_ci			u8 action_code, u8 dialog_token, u16 status_code,
128962306a36Sopenharmony_ci			u32 peer_capability, bool initiator,
129062306a36Sopenharmony_ci			const u8 *extra_ies, size_t extra_ies_len)
129162306a36Sopenharmony_ci{
129262306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
129362306a36Sopenharmony_ci	int ret;
129462306a36Sopenharmony_ci
129562306a36Sopenharmony_ci	if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
129662306a36Sopenharmony_ci		return -ENOTSUPP;
129762306a36Sopenharmony_ci
129862306a36Sopenharmony_ci	/* make sure we are in managed mode, and associated */
129962306a36Sopenharmony_ci	if (sdata->vif.type != NL80211_IFTYPE_STATION ||
130062306a36Sopenharmony_ci	    !sdata->u.mgd.associated)
130162306a36Sopenharmony_ci		return -EINVAL;
130262306a36Sopenharmony_ci
130362306a36Sopenharmony_ci	switch (action_code) {
130462306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_REQUEST:
130562306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_RESPONSE:
130662306a36Sopenharmony_ci		ret = ieee80211_tdls_mgmt_setup(wiphy, dev, peer,
130762306a36Sopenharmony_ci						link_id, action_code,
130862306a36Sopenharmony_ci						dialog_token, status_code,
130962306a36Sopenharmony_ci						peer_capability, initiator,
131062306a36Sopenharmony_ci						extra_ies, extra_ies_len);
131162306a36Sopenharmony_ci		break;
131262306a36Sopenharmony_ci	case WLAN_TDLS_TEARDOWN:
131362306a36Sopenharmony_ci		ret = ieee80211_tdls_mgmt_teardown(wiphy, dev, peer, link_id,
131462306a36Sopenharmony_ci						   action_code, dialog_token,
131562306a36Sopenharmony_ci						   status_code,
131662306a36Sopenharmony_ci						   peer_capability, initiator,
131762306a36Sopenharmony_ci						   extra_ies, extra_ies_len);
131862306a36Sopenharmony_ci		break;
131962306a36Sopenharmony_ci	case WLAN_TDLS_DISCOVERY_REQUEST:
132062306a36Sopenharmony_ci		/*
132162306a36Sopenharmony_ci		 * Protect the discovery so we can hear the TDLS discovery
132262306a36Sopenharmony_ci		 * response frame. It is transmitted directly and not buffered
132362306a36Sopenharmony_ci		 * by the AP.
132462306a36Sopenharmony_ci		 */
132562306a36Sopenharmony_ci		drv_mgd_protect_tdls_discover(sdata->local, sdata);
132662306a36Sopenharmony_ci		fallthrough;
132762306a36Sopenharmony_ci	case WLAN_TDLS_SETUP_CONFIRM:
132862306a36Sopenharmony_ci	case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
132962306a36Sopenharmony_ci		/* no special handling */
133062306a36Sopenharmony_ci		ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer,
133162306a36Sopenharmony_ci						      link_id, action_code,
133262306a36Sopenharmony_ci						      dialog_token,
133362306a36Sopenharmony_ci						      status_code,
133462306a36Sopenharmony_ci						      peer_capability,
133562306a36Sopenharmony_ci						      initiator, extra_ies,
133662306a36Sopenharmony_ci						      extra_ies_len, 0, NULL);
133762306a36Sopenharmony_ci		break;
133862306a36Sopenharmony_ci	default:
133962306a36Sopenharmony_ci		ret = -EOPNOTSUPP;
134062306a36Sopenharmony_ci		break;
134162306a36Sopenharmony_ci	}
134262306a36Sopenharmony_ci
134362306a36Sopenharmony_ci	tdls_dbg(sdata, "TDLS mgmt action %d peer %pM link_id %d status %d\n",
134462306a36Sopenharmony_ci		 action_code, peer, link_id, ret);
134562306a36Sopenharmony_ci	return ret;
134662306a36Sopenharmony_ci}
134762306a36Sopenharmony_ci
134862306a36Sopenharmony_cistatic void iee80211_tdls_recalc_chanctx(struct ieee80211_sub_if_data *sdata,
134962306a36Sopenharmony_ci					 struct sta_info *sta)
135062306a36Sopenharmony_ci{
135162306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
135262306a36Sopenharmony_ci	struct ieee80211_chanctx_conf *conf;
135362306a36Sopenharmony_ci	struct ieee80211_chanctx *ctx;
135462306a36Sopenharmony_ci	enum nl80211_chan_width width;
135562306a36Sopenharmony_ci	struct ieee80211_supported_band *sband;
135662306a36Sopenharmony_ci
135762306a36Sopenharmony_ci	mutex_lock(&local->chanctx_mtx);
135862306a36Sopenharmony_ci	conf = rcu_dereference_protected(sdata->vif.bss_conf.chanctx_conf,
135962306a36Sopenharmony_ci					 lockdep_is_held(&local->chanctx_mtx));
136062306a36Sopenharmony_ci	if (conf) {
136162306a36Sopenharmony_ci		width = conf->def.width;
136262306a36Sopenharmony_ci		sband = local->hw.wiphy->bands[conf->def.chan->band];
136362306a36Sopenharmony_ci		ctx = container_of(conf, struct ieee80211_chanctx, conf);
136462306a36Sopenharmony_ci		ieee80211_recalc_chanctx_chantype(local, ctx);
136562306a36Sopenharmony_ci
136662306a36Sopenharmony_ci		/* if width changed and a peer is given, update its BW */
136762306a36Sopenharmony_ci		if (width != conf->def.width && sta &&
136862306a36Sopenharmony_ci		    test_sta_flag(sta, WLAN_STA_TDLS_WIDER_BW)) {
136962306a36Sopenharmony_ci			enum ieee80211_sta_rx_bandwidth bw;
137062306a36Sopenharmony_ci
137162306a36Sopenharmony_ci			bw = ieee80211_chan_width_to_rx_bw(conf->def.width);
137262306a36Sopenharmony_ci			bw = min(bw, ieee80211_sta_cap_rx_bw(&sta->deflink));
137362306a36Sopenharmony_ci			if (bw != sta->sta.deflink.bandwidth) {
137462306a36Sopenharmony_ci				sta->sta.deflink.bandwidth = bw;
137562306a36Sopenharmony_ci				rate_control_rate_update(local, sband, sta, 0,
137662306a36Sopenharmony_ci							 IEEE80211_RC_BW_CHANGED);
137762306a36Sopenharmony_ci				/*
137862306a36Sopenharmony_ci				 * if a TDLS peer BW was updated, we need to
137962306a36Sopenharmony_ci				 * recalc the chandef width again, to get the
138062306a36Sopenharmony_ci				 * correct chanctx min_def
138162306a36Sopenharmony_ci				 */
138262306a36Sopenharmony_ci				ieee80211_recalc_chanctx_chantype(local, ctx);
138362306a36Sopenharmony_ci			}
138462306a36Sopenharmony_ci		}
138562306a36Sopenharmony_ci
138662306a36Sopenharmony_ci	}
138762306a36Sopenharmony_ci	mutex_unlock(&local->chanctx_mtx);
138862306a36Sopenharmony_ci}
138962306a36Sopenharmony_ci
139062306a36Sopenharmony_cistatic int iee80211_tdls_have_ht_peers(struct ieee80211_sub_if_data *sdata)
139162306a36Sopenharmony_ci{
139262306a36Sopenharmony_ci	struct sta_info *sta;
139362306a36Sopenharmony_ci	bool result = false;
139462306a36Sopenharmony_ci
139562306a36Sopenharmony_ci	rcu_read_lock();
139662306a36Sopenharmony_ci	list_for_each_entry_rcu(sta, &sdata->local->sta_list, list) {
139762306a36Sopenharmony_ci		if (!sta->sta.tdls || sta->sdata != sdata || !sta->uploaded ||
139862306a36Sopenharmony_ci		    !test_sta_flag(sta, WLAN_STA_AUTHORIZED) ||
139962306a36Sopenharmony_ci		    !test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH) ||
140062306a36Sopenharmony_ci		    !sta->sta.deflink.ht_cap.ht_supported)
140162306a36Sopenharmony_ci			continue;
140262306a36Sopenharmony_ci		result = true;
140362306a36Sopenharmony_ci		break;
140462306a36Sopenharmony_ci	}
140562306a36Sopenharmony_ci	rcu_read_unlock();
140662306a36Sopenharmony_ci
140762306a36Sopenharmony_ci	return result;
140862306a36Sopenharmony_ci}
140962306a36Sopenharmony_ci
141062306a36Sopenharmony_cistatic void
141162306a36Sopenharmony_ciiee80211_tdls_recalc_ht_protection(struct ieee80211_sub_if_data *sdata,
141262306a36Sopenharmony_ci				   struct sta_info *sta)
141362306a36Sopenharmony_ci{
141462306a36Sopenharmony_ci	bool tdls_ht;
141562306a36Sopenharmony_ci	u16 protection = IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED |
141662306a36Sopenharmony_ci			 IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT |
141762306a36Sopenharmony_ci			 IEEE80211_HT_OP_MODE_NON_HT_STA_PRSNT;
141862306a36Sopenharmony_ci	u16 opmode;
141962306a36Sopenharmony_ci
142062306a36Sopenharmony_ci	/* Nothing to do if the BSS connection uses HT */
142162306a36Sopenharmony_ci	if (!(sdata->deflink.u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT))
142262306a36Sopenharmony_ci		return;
142362306a36Sopenharmony_ci
142462306a36Sopenharmony_ci	tdls_ht = (sta && sta->sta.deflink.ht_cap.ht_supported) ||
142562306a36Sopenharmony_ci		  iee80211_tdls_have_ht_peers(sdata);
142662306a36Sopenharmony_ci
142762306a36Sopenharmony_ci	opmode = sdata->vif.bss_conf.ht_operation_mode;
142862306a36Sopenharmony_ci
142962306a36Sopenharmony_ci	if (tdls_ht)
143062306a36Sopenharmony_ci		opmode |= protection;
143162306a36Sopenharmony_ci	else
143262306a36Sopenharmony_ci		opmode &= ~protection;
143362306a36Sopenharmony_ci
143462306a36Sopenharmony_ci	if (opmode == sdata->vif.bss_conf.ht_operation_mode)
143562306a36Sopenharmony_ci		return;
143662306a36Sopenharmony_ci
143762306a36Sopenharmony_ci	sdata->vif.bss_conf.ht_operation_mode = opmode;
143862306a36Sopenharmony_ci	ieee80211_link_info_change_notify(sdata, &sdata->deflink,
143962306a36Sopenharmony_ci					  BSS_CHANGED_HT);
144062306a36Sopenharmony_ci}
144162306a36Sopenharmony_ci
144262306a36Sopenharmony_ciint ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev,
144362306a36Sopenharmony_ci			const u8 *peer, enum nl80211_tdls_operation oper)
144462306a36Sopenharmony_ci{
144562306a36Sopenharmony_ci	struct sta_info *sta;
144662306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
144762306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
144862306a36Sopenharmony_ci	int ret;
144962306a36Sopenharmony_ci
145062306a36Sopenharmony_ci	if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
145162306a36Sopenharmony_ci		return -ENOTSUPP;
145262306a36Sopenharmony_ci
145362306a36Sopenharmony_ci	if (sdata->vif.type != NL80211_IFTYPE_STATION)
145462306a36Sopenharmony_ci		return -EINVAL;
145562306a36Sopenharmony_ci
145662306a36Sopenharmony_ci	switch (oper) {
145762306a36Sopenharmony_ci	case NL80211_TDLS_ENABLE_LINK:
145862306a36Sopenharmony_ci	case NL80211_TDLS_DISABLE_LINK:
145962306a36Sopenharmony_ci		break;
146062306a36Sopenharmony_ci	case NL80211_TDLS_TEARDOWN:
146162306a36Sopenharmony_ci	case NL80211_TDLS_SETUP:
146262306a36Sopenharmony_ci	case NL80211_TDLS_DISCOVERY_REQ:
146362306a36Sopenharmony_ci		/* We don't support in-driver setup/teardown/discovery */
146462306a36Sopenharmony_ci		return -ENOTSUPP;
146562306a36Sopenharmony_ci	}
146662306a36Sopenharmony_ci
146762306a36Sopenharmony_ci	/* protect possible bss_conf changes and avoid concurrency in
146862306a36Sopenharmony_ci	 * ieee80211_bss_info_change_notify()
146962306a36Sopenharmony_ci	 */
147062306a36Sopenharmony_ci	sdata_lock(sdata);
147162306a36Sopenharmony_ci	mutex_lock(&local->mtx);
147262306a36Sopenharmony_ci	tdls_dbg(sdata, "TDLS oper %d peer %pM\n", oper, peer);
147362306a36Sopenharmony_ci
147462306a36Sopenharmony_ci	switch (oper) {
147562306a36Sopenharmony_ci	case NL80211_TDLS_ENABLE_LINK:
147662306a36Sopenharmony_ci		if (sdata->vif.bss_conf.csa_active) {
147762306a36Sopenharmony_ci			tdls_dbg(sdata, "TDLS: disallow link during CSA\n");
147862306a36Sopenharmony_ci			ret = -EBUSY;
147962306a36Sopenharmony_ci			break;
148062306a36Sopenharmony_ci		}
148162306a36Sopenharmony_ci
148262306a36Sopenharmony_ci		mutex_lock(&local->sta_mtx);
148362306a36Sopenharmony_ci		sta = sta_info_get(sdata, peer);
148462306a36Sopenharmony_ci		if (!sta) {
148562306a36Sopenharmony_ci			mutex_unlock(&local->sta_mtx);
148662306a36Sopenharmony_ci			ret = -ENOLINK;
148762306a36Sopenharmony_ci			break;
148862306a36Sopenharmony_ci		}
148962306a36Sopenharmony_ci
149062306a36Sopenharmony_ci		iee80211_tdls_recalc_chanctx(sdata, sta);
149162306a36Sopenharmony_ci		iee80211_tdls_recalc_ht_protection(sdata, sta);
149262306a36Sopenharmony_ci
149362306a36Sopenharmony_ci		set_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH);
149462306a36Sopenharmony_ci		mutex_unlock(&local->sta_mtx);
149562306a36Sopenharmony_ci
149662306a36Sopenharmony_ci		WARN_ON_ONCE(is_zero_ether_addr(sdata->u.mgd.tdls_peer) ||
149762306a36Sopenharmony_ci			     !ether_addr_equal(sdata->u.mgd.tdls_peer, peer));
149862306a36Sopenharmony_ci		ret = 0;
149962306a36Sopenharmony_ci		break;
150062306a36Sopenharmony_ci	case NL80211_TDLS_DISABLE_LINK:
150162306a36Sopenharmony_ci		/*
150262306a36Sopenharmony_ci		 * The teardown message in ieee80211_tdls_mgmt_teardown() was
150362306a36Sopenharmony_ci		 * created while the queues were stopped, so it might still be
150462306a36Sopenharmony_ci		 * pending. Before flushing the queues we need to be sure the
150562306a36Sopenharmony_ci		 * message is handled by the tasklet handling pending messages,
150662306a36Sopenharmony_ci		 * otherwise we might start destroying the station before
150762306a36Sopenharmony_ci		 * sending the teardown packet.
150862306a36Sopenharmony_ci		 * Note that this only forces the tasklet to flush pendings -
150962306a36Sopenharmony_ci		 * not to stop the tasklet from rescheduling itself.
151062306a36Sopenharmony_ci		 */
151162306a36Sopenharmony_ci		tasklet_kill(&local->tx_pending_tasklet);
151262306a36Sopenharmony_ci		/* flush a potentially queued teardown packet */
151362306a36Sopenharmony_ci		ieee80211_flush_queues(local, sdata, false);
151462306a36Sopenharmony_ci
151562306a36Sopenharmony_ci		ret = sta_info_destroy_addr(sdata, peer);
151662306a36Sopenharmony_ci
151762306a36Sopenharmony_ci		mutex_lock(&local->sta_mtx);
151862306a36Sopenharmony_ci		iee80211_tdls_recalc_ht_protection(sdata, NULL);
151962306a36Sopenharmony_ci		mutex_unlock(&local->sta_mtx);
152062306a36Sopenharmony_ci
152162306a36Sopenharmony_ci		iee80211_tdls_recalc_chanctx(sdata, NULL);
152262306a36Sopenharmony_ci		break;
152362306a36Sopenharmony_ci	default:
152462306a36Sopenharmony_ci		ret = -ENOTSUPP;
152562306a36Sopenharmony_ci		break;
152662306a36Sopenharmony_ci	}
152762306a36Sopenharmony_ci
152862306a36Sopenharmony_ci	if (ret == 0 && ether_addr_equal(sdata->u.mgd.tdls_peer, peer)) {
152962306a36Sopenharmony_ci		cancel_delayed_work(&sdata->u.mgd.tdls_peer_del_work);
153062306a36Sopenharmony_ci		eth_zero_addr(sdata->u.mgd.tdls_peer);
153162306a36Sopenharmony_ci	}
153262306a36Sopenharmony_ci
153362306a36Sopenharmony_ci	if (ret == 0)
153462306a36Sopenharmony_ci		wiphy_work_queue(sdata->local->hw.wiphy,
153562306a36Sopenharmony_ci				 &sdata->deflink.u.mgd.request_smps_work);
153662306a36Sopenharmony_ci
153762306a36Sopenharmony_ci	mutex_unlock(&local->mtx);
153862306a36Sopenharmony_ci	sdata_unlock(sdata);
153962306a36Sopenharmony_ci	return ret;
154062306a36Sopenharmony_ci}
154162306a36Sopenharmony_ci
154262306a36Sopenharmony_civoid ieee80211_tdls_oper_request(struct ieee80211_vif *vif, const u8 *peer,
154362306a36Sopenharmony_ci				 enum nl80211_tdls_operation oper,
154462306a36Sopenharmony_ci				 u16 reason_code, gfp_t gfp)
154562306a36Sopenharmony_ci{
154662306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
154762306a36Sopenharmony_ci
154862306a36Sopenharmony_ci	if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc) {
154962306a36Sopenharmony_ci		sdata_err(sdata, "Discarding TDLS oper %d - not STA or disconnected\n",
155062306a36Sopenharmony_ci			  oper);
155162306a36Sopenharmony_ci		return;
155262306a36Sopenharmony_ci	}
155362306a36Sopenharmony_ci
155462306a36Sopenharmony_ci	cfg80211_tdls_oper_request(sdata->dev, peer, oper, reason_code, gfp);
155562306a36Sopenharmony_ci}
155662306a36Sopenharmony_ciEXPORT_SYMBOL(ieee80211_tdls_oper_request);
155762306a36Sopenharmony_ci
155862306a36Sopenharmony_cistatic void
155962306a36Sopenharmony_ciiee80211_tdls_add_ch_switch_timing(u8 *buf, u16 switch_time, u16 switch_timeout)
156062306a36Sopenharmony_ci{
156162306a36Sopenharmony_ci	struct ieee80211_ch_switch_timing *ch_sw;
156262306a36Sopenharmony_ci
156362306a36Sopenharmony_ci	*buf++ = WLAN_EID_CHAN_SWITCH_TIMING;
156462306a36Sopenharmony_ci	*buf++ = sizeof(struct ieee80211_ch_switch_timing);
156562306a36Sopenharmony_ci
156662306a36Sopenharmony_ci	ch_sw = (void *)buf;
156762306a36Sopenharmony_ci	ch_sw->switch_time = cpu_to_le16(switch_time);
156862306a36Sopenharmony_ci	ch_sw->switch_timeout = cpu_to_le16(switch_timeout);
156962306a36Sopenharmony_ci}
157062306a36Sopenharmony_ci
157162306a36Sopenharmony_ci/* find switch timing IE in SKB ready for Tx */
157262306a36Sopenharmony_cistatic const u8 *ieee80211_tdls_find_sw_timing_ie(struct sk_buff *skb)
157362306a36Sopenharmony_ci{
157462306a36Sopenharmony_ci	struct ieee80211_tdls_data *tf;
157562306a36Sopenharmony_ci	const u8 *ie_start;
157662306a36Sopenharmony_ci
157762306a36Sopenharmony_ci	/*
157862306a36Sopenharmony_ci	 * Get the offset for the new location of the switch timing IE.
157962306a36Sopenharmony_ci	 * The SKB network header will now point to the "payload_type"
158062306a36Sopenharmony_ci	 * element of the TDLS data frame struct.
158162306a36Sopenharmony_ci	 */
158262306a36Sopenharmony_ci	tf = container_of(skb->data + skb_network_offset(skb),
158362306a36Sopenharmony_ci			  struct ieee80211_tdls_data, payload_type);
158462306a36Sopenharmony_ci	ie_start = tf->u.chan_switch_req.variable;
158562306a36Sopenharmony_ci	return cfg80211_find_ie(WLAN_EID_CHAN_SWITCH_TIMING, ie_start,
158662306a36Sopenharmony_ci				skb->len - (ie_start - skb->data));
158762306a36Sopenharmony_ci}
158862306a36Sopenharmony_ci
158962306a36Sopenharmony_cistatic struct sk_buff *
159062306a36Sopenharmony_ciieee80211_tdls_ch_sw_tmpl_get(struct sta_info *sta, u8 oper_class,
159162306a36Sopenharmony_ci			      struct cfg80211_chan_def *chandef,
159262306a36Sopenharmony_ci			      u32 *ch_sw_tm_ie_offset)
159362306a36Sopenharmony_ci{
159462306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = sta->sdata;
159562306a36Sopenharmony_ci	u8 extra_ies[2 + sizeof(struct ieee80211_sec_chan_offs_ie) +
159662306a36Sopenharmony_ci		     2 + sizeof(struct ieee80211_ch_switch_timing)];
159762306a36Sopenharmony_ci	int extra_ies_len = 2 + sizeof(struct ieee80211_ch_switch_timing);
159862306a36Sopenharmony_ci	u8 *pos = extra_ies;
159962306a36Sopenharmony_ci	struct sk_buff *skb;
160062306a36Sopenharmony_ci	int link_id = sta->sta.valid_links ? ffs(sta->sta.valid_links) - 1 : 0;
160162306a36Sopenharmony_ci
160262306a36Sopenharmony_ci	/*
160362306a36Sopenharmony_ci	 * if chandef points to a wide channel add a Secondary-Channel
160462306a36Sopenharmony_ci	 * Offset information element
160562306a36Sopenharmony_ci	 */
160662306a36Sopenharmony_ci	if (chandef->width == NL80211_CHAN_WIDTH_40) {
160762306a36Sopenharmony_ci		struct ieee80211_sec_chan_offs_ie *sec_chan_ie;
160862306a36Sopenharmony_ci		bool ht40plus;
160962306a36Sopenharmony_ci
161062306a36Sopenharmony_ci		*pos++ = WLAN_EID_SECONDARY_CHANNEL_OFFSET;
161162306a36Sopenharmony_ci		*pos++ = sizeof(*sec_chan_ie);
161262306a36Sopenharmony_ci		sec_chan_ie = (void *)pos;
161362306a36Sopenharmony_ci
161462306a36Sopenharmony_ci		ht40plus = cfg80211_get_chandef_type(chandef) ==
161562306a36Sopenharmony_ci							NL80211_CHAN_HT40PLUS;
161662306a36Sopenharmony_ci		sec_chan_ie->sec_chan_offs = ht40plus ?
161762306a36Sopenharmony_ci					     IEEE80211_HT_PARAM_CHA_SEC_ABOVE :
161862306a36Sopenharmony_ci					     IEEE80211_HT_PARAM_CHA_SEC_BELOW;
161962306a36Sopenharmony_ci		pos += sizeof(*sec_chan_ie);
162062306a36Sopenharmony_ci
162162306a36Sopenharmony_ci		extra_ies_len += 2 + sizeof(struct ieee80211_sec_chan_offs_ie);
162262306a36Sopenharmony_ci	}
162362306a36Sopenharmony_ci
162462306a36Sopenharmony_ci	/* just set the values to 0, this is a template */
162562306a36Sopenharmony_ci	iee80211_tdls_add_ch_switch_timing(pos, 0, 0);
162662306a36Sopenharmony_ci
162762306a36Sopenharmony_ci	skb = ieee80211_tdls_build_mgmt_packet_data(sdata, sta->sta.addr,
162862306a36Sopenharmony_ci					      link_id,
162962306a36Sopenharmony_ci					      WLAN_TDLS_CHANNEL_SWITCH_REQUEST,
163062306a36Sopenharmony_ci					      0, 0, !sta->sta.tdls_initiator,
163162306a36Sopenharmony_ci					      extra_ies, extra_ies_len,
163262306a36Sopenharmony_ci					      oper_class, chandef);
163362306a36Sopenharmony_ci	if (!skb)
163462306a36Sopenharmony_ci		return NULL;
163562306a36Sopenharmony_ci
163662306a36Sopenharmony_ci	skb = ieee80211_build_data_template(sdata, skb, 0);
163762306a36Sopenharmony_ci	if (IS_ERR(skb)) {
163862306a36Sopenharmony_ci		tdls_dbg(sdata, "Failed building TDLS channel switch frame\n");
163962306a36Sopenharmony_ci		return NULL;
164062306a36Sopenharmony_ci	}
164162306a36Sopenharmony_ci
164262306a36Sopenharmony_ci	if (ch_sw_tm_ie_offset) {
164362306a36Sopenharmony_ci		const u8 *tm_ie = ieee80211_tdls_find_sw_timing_ie(skb);
164462306a36Sopenharmony_ci
164562306a36Sopenharmony_ci		if (!tm_ie) {
164662306a36Sopenharmony_ci			tdls_dbg(sdata, "No switch timing IE in TDLS switch\n");
164762306a36Sopenharmony_ci			dev_kfree_skb_any(skb);
164862306a36Sopenharmony_ci			return NULL;
164962306a36Sopenharmony_ci		}
165062306a36Sopenharmony_ci
165162306a36Sopenharmony_ci		*ch_sw_tm_ie_offset = tm_ie - skb->data;
165262306a36Sopenharmony_ci	}
165362306a36Sopenharmony_ci
165462306a36Sopenharmony_ci	tdls_dbg(sdata,
165562306a36Sopenharmony_ci		 "TDLS channel switch request template for %pM ch %d width %d\n",
165662306a36Sopenharmony_ci		 sta->sta.addr, chandef->chan->center_freq, chandef->width);
165762306a36Sopenharmony_ci	return skb;
165862306a36Sopenharmony_ci}
165962306a36Sopenharmony_ci
166062306a36Sopenharmony_ciint
166162306a36Sopenharmony_ciieee80211_tdls_channel_switch(struct wiphy *wiphy, struct net_device *dev,
166262306a36Sopenharmony_ci			      const u8 *addr, u8 oper_class,
166362306a36Sopenharmony_ci			      struct cfg80211_chan_def *chandef)
166462306a36Sopenharmony_ci{
166562306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
166662306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
166762306a36Sopenharmony_ci	struct sta_info *sta;
166862306a36Sopenharmony_ci	struct sk_buff *skb = NULL;
166962306a36Sopenharmony_ci	u32 ch_sw_tm_ie;
167062306a36Sopenharmony_ci	int ret;
167162306a36Sopenharmony_ci
167262306a36Sopenharmony_ci	if (chandef->chan->freq_offset)
167362306a36Sopenharmony_ci		/* this may work, but is untested */
167462306a36Sopenharmony_ci		return -EOPNOTSUPP;
167562306a36Sopenharmony_ci
167662306a36Sopenharmony_ci	mutex_lock(&local->sta_mtx);
167762306a36Sopenharmony_ci	sta = sta_info_get(sdata, addr);
167862306a36Sopenharmony_ci	if (!sta) {
167962306a36Sopenharmony_ci		tdls_dbg(sdata,
168062306a36Sopenharmony_ci			 "Invalid TDLS peer %pM for channel switch request\n",
168162306a36Sopenharmony_ci			 addr);
168262306a36Sopenharmony_ci		ret = -ENOENT;
168362306a36Sopenharmony_ci		goto out;
168462306a36Sopenharmony_ci	}
168562306a36Sopenharmony_ci
168662306a36Sopenharmony_ci	if (!test_sta_flag(sta, WLAN_STA_TDLS_CHAN_SWITCH)) {
168762306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS channel switch unsupported by %pM\n",
168862306a36Sopenharmony_ci			 addr);
168962306a36Sopenharmony_ci		ret = -ENOTSUPP;
169062306a36Sopenharmony_ci		goto out;
169162306a36Sopenharmony_ci	}
169262306a36Sopenharmony_ci
169362306a36Sopenharmony_ci	skb = ieee80211_tdls_ch_sw_tmpl_get(sta, oper_class, chandef,
169462306a36Sopenharmony_ci					    &ch_sw_tm_ie);
169562306a36Sopenharmony_ci	if (!skb) {
169662306a36Sopenharmony_ci		ret = -ENOENT;
169762306a36Sopenharmony_ci		goto out;
169862306a36Sopenharmony_ci	}
169962306a36Sopenharmony_ci
170062306a36Sopenharmony_ci	ret = drv_tdls_channel_switch(local, sdata, &sta->sta, oper_class,
170162306a36Sopenharmony_ci				      chandef, skb, ch_sw_tm_ie);
170262306a36Sopenharmony_ci	if (!ret)
170362306a36Sopenharmony_ci		set_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL);
170462306a36Sopenharmony_ci
170562306a36Sopenharmony_ciout:
170662306a36Sopenharmony_ci	mutex_unlock(&local->sta_mtx);
170762306a36Sopenharmony_ci	dev_kfree_skb_any(skb);
170862306a36Sopenharmony_ci	return ret;
170962306a36Sopenharmony_ci}
171062306a36Sopenharmony_ci
171162306a36Sopenharmony_civoid
171262306a36Sopenharmony_ciieee80211_tdls_cancel_channel_switch(struct wiphy *wiphy,
171362306a36Sopenharmony_ci				     struct net_device *dev,
171462306a36Sopenharmony_ci				     const u8 *addr)
171562306a36Sopenharmony_ci{
171662306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
171762306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
171862306a36Sopenharmony_ci	struct sta_info *sta;
171962306a36Sopenharmony_ci
172062306a36Sopenharmony_ci	mutex_lock(&local->sta_mtx);
172162306a36Sopenharmony_ci	sta = sta_info_get(sdata, addr);
172262306a36Sopenharmony_ci	if (!sta) {
172362306a36Sopenharmony_ci		tdls_dbg(sdata,
172462306a36Sopenharmony_ci			 "Invalid TDLS peer %pM for channel switch cancel\n",
172562306a36Sopenharmony_ci			 addr);
172662306a36Sopenharmony_ci		goto out;
172762306a36Sopenharmony_ci	}
172862306a36Sopenharmony_ci
172962306a36Sopenharmony_ci	if (!test_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL)) {
173062306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS channel switch not initiated by %pM\n",
173162306a36Sopenharmony_ci			 addr);
173262306a36Sopenharmony_ci		goto out;
173362306a36Sopenharmony_ci	}
173462306a36Sopenharmony_ci
173562306a36Sopenharmony_ci	drv_tdls_cancel_channel_switch(local, sdata, &sta->sta);
173662306a36Sopenharmony_ci	clear_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL);
173762306a36Sopenharmony_ci
173862306a36Sopenharmony_ciout:
173962306a36Sopenharmony_ci	mutex_unlock(&local->sta_mtx);
174062306a36Sopenharmony_ci}
174162306a36Sopenharmony_ci
174262306a36Sopenharmony_cistatic struct sk_buff *
174362306a36Sopenharmony_ciieee80211_tdls_ch_sw_resp_tmpl_get(struct sta_info *sta,
174462306a36Sopenharmony_ci				   u32 *ch_sw_tm_ie_offset)
174562306a36Sopenharmony_ci{
174662306a36Sopenharmony_ci	struct ieee80211_sub_if_data *sdata = sta->sdata;
174762306a36Sopenharmony_ci	struct sk_buff *skb;
174862306a36Sopenharmony_ci	u8 extra_ies[2 + sizeof(struct ieee80211_ch_switch_timing)];
174962306a36Sopenharmony_ci	int link_id = sta->sta.valid_links ? ffs(sta->sta.valid_links) - 1 : 0;
175062306a36Sopenharmony_ci
175162306a36Sopenharmony_ci	/* initial timing are always zero in the template */
175262306a36Sopenharmony_ci	iee80211_tdls_add_ch_switch_timing(extra_ies, 0, 0);
175362306a36Sopenharmony_ci
175462306a36Sopenharmony_ci	skb = ieee80211_tdls_build_mgmt_packet_data(sdata, sta->sta.addr,
175562306a36Sopenharmony_ci					link_id,
175662306a36Sopenharmony_ci					WLAN_TDLS_CHANNEL_SWITCH_RESPONSE,
175762306a36Sopenharmony_ci					0, 0, !sta->sta.tdls_initiator,
175862306a36Sopenharmony_ci					extra_ies, sizeof(extra_ies), 0, NULL);
175962306a36Sopenharmony_ci	if (!skb)
176062306a36Sopenharmony_ci		return NULL;
176162306a36Sopenharmony_ci
176262306a36Sopenharmony_ci	skb = ieee80211_build_data_template(sdata, skb, 0);
176362306a36Sopenharmony_ci	if (IS_ERR(skb)) {
176462306a36Sopenharmony_ci		tdls_dbg(sdata,
176562306a36Sopenharmony_ci			 "Failed building TDLS channel switch resp frame\n");
176662306a36Sopenharmony_ci		return NULL;
176762306a36Sopenharmony_ci	}
176862306a36Sopenharmony_ci
176962306a36Sopenharmony_ci	if (ch_sw_tm_ie_offset) {
177062306a36Sopenharmony_ci		const u8 *tm_ie = ieee80211_tdls_find_sw_timing_ie(skb);
177162306a36Sopenharmony_ci
177262306a36Sopenharmony_ci		if (!tm_ie) {
177362306a36Sopenharmony_ci			tdls_dbg(sdata,
177462306a36Sopenharmony_ci				 "No switch timing IE in TDLS switch resp\n");
177562306a36Sopenharmony_ci			dev_kfree_skb_any(skb);
177662306a36Sopenharmony_ci			return NULL;
177762306a36Sopenharmony_ci		}
177862306a36Sopenharmony_ci
177962306a36Sopenharmony_ci		*ch_sw_tm_ie_offset = tm_ie - skb->data;
178062306a36Sopenharmony_ci	}
178162306a36Sopenharmony_ci
178262306a36Sopenharmony_ci	tdls_dbg(sdata, "TDLS get channel switch response template for %pM\n",
178362306a36Sopenharmony_ci		 sta->sta.addr);
178462306a36Sopenharmony_ci	return skb;
178562306a36Sopenharmony_ci}
178662306a36Sopenharmony_ci
178762306a36Sopenharmony_cistatic int
178862306a36Sopenharmony_ciieee80211_process_tdls_channel_switch_resp(struct ieee80211_sub_if_data *sdata,
178962306a36Sopenharmony_ci					   struct sk_buff *skb)
179062306a36Sopenharmony_ci{
179162306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
179262306a36Sopenharmony_ci	struct ieee802_11_elems *elems = NULL;
179362306a36Sopenharmony_ci	struct sta_info *sta;
179462306a36Sopenharmony_ci	struct ieee80211_tdls_data *tf = (void *)skb->data;
179562306a36Sopenharmony_ci	bool local_initiator;
179662306a36Sopenharmony_ci	struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);
179762306a36Sopenharmony_ci	int baselen = offsetof(typeof(*tf), u.chan_switch_resp.variable);
179862306a36Sopenharmony_ci	struct ieee80211_tdls_ch_sw_params params = {};
179962306a36Sopenharmony_ci	int ret;
180062306a36Sopenharmony_ci
180162306a36Sopenharmony_ci	params.action_code = WLAN_TDLS_CHANNEL_SWITCH_RESPONSE;
180262306a36Sopenharmony_ci	params.timestamp = rx_status->device_timestamp;
180362306a36Sopenharmony_ci
180462306a36Sopenharmony_ci	if (skb->len < baselen) {
180562306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS channel switch resp too short: %d\n",
180662306a36Sopenharmony_ci			 skb->len);
180762306a36Sopenharmony_ci		return -EINVAL;
180862306a36Sopenharmony_ci	}
180962306a36Sopenharmony_ci
181062306a36Sopenharmony_ci	mutex_lock(&local->sta_mtx);
181162306a36Sopenharmony_ci	sta = sta_info_get(sdata, tf->sa);
181262306a36Sopenharmony_ci	if (!sta || !test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH)) {
181362306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS chan switch from non-peer sta %pM\n",
181462306a36Sopenharmony_ci			 tf->sa);
181562306a36Sopenharmony_ci		ret = -EINVAL;
181662306a36Sopenharmony_ci		goto out;
181762306a36Sopenharmony_ci	}
181862306a36Sopenharmony_ci
181962306a36Sopenharmony_ci	params.sta = &sta->sta;
182062306a36Sopenharmony_ci	params.status = le16_to_cpu(tf->u.chan_switch_resp.status_code);
182162306a36Sopenharmony_ci	if (params.status != 0) {
182262306a36Sopenharmony_ci		ret = 0;
182362306a36Sopenharmony_ci		goto call_drv;
182462306a36Sopenharmony_ci	}
182562306a36Sopenharmony_ci
182662306a36Sopenharmony_ci	elems = ieee802_11_parse_elems(tf->u.chan_switch_resp.variable,
182762306a36Sopenharmony_ci				       skb->len - baselen, false, NULL);
182862306a36Sopenharmony_ci	if (!elems) {
182962306a36Sopenharmony_ci		ret = -ENOMEM;
183062306a36Sopenharmony_ci		goto out;
183162306a36Sopenharmony_ci	}
183262306a36Sopenharmony_ci
183362306a36Sopenharmony_ci	if (elems->parse_error) {
183462306a36Sopenharmony_ci		tdls_dbg(sdata, "Invalid IEs in TDLS channel switch resp\n");
183562306a36Sopenharmony_ci		ret = -EINVAL;
183662306a36Sopenharmony_ci		goto out;
183762306a36Sopenharmony_ci	}
183862306a36Sopenharmony_ci
183962306a36Sopenharmony_ci	if (!elems->ch_sw_timing || !elems->lnk_id) {
184062306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS channel switch resp - missing IEs\n");
184162306a36Sopenharmony_ci		ret = -EINVAL;
184262306a36Sopenharmony_ci		goto out;
184362306a36Sopenharmony_ci	}
184462306a36Sopenharmony_ci
184562306a36Sopenharmony_ci	/* validate the initiator is set correctly */
184662306a36Sopenharmony_ci	local_initiator =
184762306a36Sopenharmony_ci		!memcmp(elems->lnk_id->init_sta, sdata->vif.addr, ETH_ALEN);
184862306a36Sopenharmony_ci	if (local_initiator == sta->sta.tdls_initiator) {
184962306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS chan switch invalid lnk-id initiator\n");
185062306a36Sopenharmony_ci		ret = -EINVAL;
185162306a36Sopenharmony_ci		goto out;
185262306a36Sopenharmony_ci	}
185362306a36Sopenharmony_ci
185462306a36Sopenharmony_ci	params.switch_time = le16_to_cpu(elems->ch_sw_timing->switch_time);
185562306a36Sopenharmony_ci	params.switch_timeout = le16_to_cpu(elems->ch_sw_timing->switch_timeout);
185662306a36Sopenharmony_ci
185762306a36Sopenharmony_ci	params.tmpl_skb =
185862306a36Sopenharmony_ci		ieee80211_tdls_ch_sw_resp_tmpl_get(sta, &params.ch_sw_tm_ie);
185962306a36Sopenharmony_ci	if (!params.tmpl_skb) {
186062306a36Sopenharmony_ci		ret = -ENOENT;
186162306a36Sopenharmony_ci		goto out;
186262306a36Sopenharmony_ci	}
186362306a36Sopenharmony_ci
186462306a36Sopenharmony_ci	ret = 0;
186562306a36Sopenharmony_cicall_drv:
186662306a36Sopenharmony_ci	drv_tdls_recv_channel_switch(sdata->local, sdata, &params);
186762306a36Sopenharmony_ci
186862306a36Sopenharmony_ci	tdls_dbg(sdata,
186962306a36Sopenharmony_ci		 "TDLS channel switch response received from %pM status %d\n",
187062306a36Sopenharmony_ci		 tf->sa, params.status);
187162306a36Sopenharmony_ci
187262306a36Sopenharmony_ciout:
187362306a36Sopenharmony_ci	mutex_unlock(&local->sta_mtx);
187462306a36Sopenharmony_ci	dev_kfree_skb_any(params.tmpl_skb);
187562306a36Sopenharmony_ci	kfree(elems);
187662306a36Sopenharmony_ci	return ret;
187762306a36Sopenharmony_ci}
187862306a36Sopenharmony_ci
187962306a36Sopenharmony_cistatic int
188062306a36Sopenharmony_ciieee80211_process_tdls_channel_switch_req(struct ieee80211_sub_if_data *sdata,
188162306a36Sopenharmony_ci					  struct sk_buff *skb)
188262306a36Sopenharmony_ci{
188362306a36Sopenharmony_ci	struct ieee80211_local *local = sdata->local;
188462306a36Sopenharmony_ci	struct ieee802_11_elems *elems;
188562306a36Sopenharmony_ci	struct cfg80211_chan_def chandef;
188662306a36Sopenharmony_ci	struct ieee80211_channel *chan;
188762306a36Sopenharmony_ci	enum nl80211_channel_type chan_type;
188862306a36Sopenharmony_ci	int freq;
188962306a36Sopenharmony_ci	u8 target_channel, oper_class;
189062306a36Sopenharmony_ci	bool local_initiator;
189162306a36Sopenharmony_ci	struct sta_info *sta;
189262306a36Sopenharmony_ci	enum nl80211_band band;
189362306a36Sopenharmony_ci	struct ieee80211_tdls_data *tf = (void *)skb->data;
189462306a36Sopenharmony_ci	struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);
189562306a36Sopenharmony_ci	int baselen = offsetof(typeof(*tf), u.chan_switch_req.variable);
189662306a36Sopenharmony_ci	struct ieee80211_tdls_ch_sw_params params = {};
189762306a36Sopenharmony_ci	int ret = 0;
189862306a36Sopenharmony_ci
189962306a36Sopenharmony_ci	params.action_code = WLAN_TDLS_CHANNEL_SWITCH_REQUEST;
190062306a36Sopenharmony_ci	params.timestamp = rx_status->device_timestamp;
190162306a36Sopenharmony_ci
190262306a36Sopenharmony_ci	if (skb->len < baselen) {
190362306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS channel switch req too short: %d\n",
190462306a36Sopenharmony_ci			 skb->len);
190562306a36Sopenharmony_ci		return -EINVAL;
190662306a36Sopenharmony_ci	}
190762306a36Sopenharmony_ci
190862306a36Sopenharmony_ci	target_channel = tf->u.chan_switch_req.target_channel;
190962306a36Sopenharmony_ci	oper_class = tf->u.chan_switch_req.oper_class;
191062306a36Sopenharmony_ci
191162306a36Sopenharmony_ci	/*
191262306a36Sopenharmony_ci	 * We can't easily infer the channel band. The operating class is
191362306a36Sopenharmony_ci	 * ambiguous - there are multiple tables (US/Europe/JP/Global). The
191462306a36Sopenharmony_ci	 * solution here is to treat channels with number >14 as 5GHz ones,
191562306a36Sopenharmony_ci	 * and specifically check for the (oper_class, channel) combinations
191662306a36Sopenharmony_ci	 * where this doesn't hold. These are thankfully unique according to
191762306a36Sopenharmony_ci	 * IEEE802.11-2012.
191862306a36Sopenharmony_ci	 * We consider only the 2GHz and 5GHz bands and 20MHz+ channels as
191962306a36Sopenharmony_ci	 * valid here.
192062306a36Sopenharmony_ci	 */
192162306a36Sopenharmony_ci	if ((oper_class == 112 || oper_class == 2 || oper_class == 3 ||
192262306a36Sopenharmony_ci	     oper_class == 4 || oper_class == 5 || oper_class == 6) &&
192362306a36Sopenharmony_ci	     target_channel < 14)
192462306a36Sopenharmony_ci		band = NL80211_BAND_5GHZ;
192562306a36Sopenharmony_ci	else
192662306a36Sopenharmony_ci		band = target_channel < 14 ? NL80211_BAND_2GHZ :
192762306a36Sopenharmony_ci					     NL80211_BAND_5GHZ;
192862306a36Sopenharmony_ci
192962306a36Sopenharmony_ci	freq = ieee80211_channel_to_frequency(target_channel, band);
193062306a36Sopenharmony_ci	if (freq == 0) {
193162306a36Sopenharmony_ci		tdls_dbg(sdata, "Invalid channel in TDLS chan switch: %d\n",
193262306a36Sopenharmony_ci			 target_channel);
193362306a36Sopenharmony_ci		return -EINVAL;
193462306a36Sopenharmony_ci	}
193562306a36Sopenharmony_ci
193662306a36Sopenharmony_ci	chan = ieee80211_get_channel(sdata->local->hw.wiphy, freq);
193762306a36Sopenharmony_ci	if (!chan) {
193862306a36Sopenharmony_ci		tdls_dbg(sdata,
193962306a36Sopenharmony_ci			 "Unsupported channel for TDLS chan switch: %d\n",
194062306a36Sopenharmony_ci			 target_channel);
194162306a36Sopenharmony_ci		return -EINVAL;
194262306a36Sopenharmony_ci	}
194362306a36Sopenharmony_ci
194462306a36Sopenharmony_ci	elems = ieee802_11_parse_elems(tf->u.chan_switch_req.variable,
194562306a36Sopenharmony_ci				       skb->len - baselen, false, NULL);
194662306a36Sopenharmony_ci	if (!elems)
194762306a36Sopenharmony_ci		return -ENOMEM;
194862306a36Sopenharmony_ci
194962306a36Sopenharmony_ci	if (elems->parse_error) {
195062306a36Sopenharmony_ci		tdls_dbg(sdata, "Invalid IEs in TDLS channel switch req\n");
195162306a36Sopenharmony_ci		ret = -EINVAL;
195262306a36Sopenharmony_ci		goto free;
195362306a36Sopenharmony_ci	}
195462306a36Sopenharmony_ci
195562306a36Sopenharmony_ci	if (!elems->ch_sw_timing || !elems->lnk_id) {
195662306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS channel switch req - missing IEs\n");
195762306a36Sopenharmony_ci		ret = -EINVAL;
195862306a36Sopenharmony_ci		goto free;
195962306a36Sopenharmony_ci	}
196062306a36Sopenharmony_ci
196162306a36Sopenharmony_ci	if (!elems->sec_chan_offs) {
196262306a36Sopenharmony_ci		chan_type = NL80211_CHAN_HT20;
196362306a36Sopenharmony_ci	} else {
196462306a36Sopenharmony_ci		switch (elems->sec_chan_offs->sec_chan_offs) {
196562306a36Sopenharmony_ci		case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
196662306a36Sopenharmony_ci			chan_type = NL80211_CHAN_HT40PLUS;
196762306a36Sopenharmony_ci			break;
196862306a36Sopenharmony_ci		case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
196962306a36Sopenharmony_ci			chan_type = NL80211_CHAN_HT40MINUS;
197062306a36Sopenharmony_ci			break;
197162306a36Sopenharmony_ci		default:
197262306a36Sopenharmony_ci			chan_type = NL80211_CHAN_HT20;
197362306a36Sopenharmony_ci			break;
197462306a36Sopenharmony_ci		}
197562306a36Sopenharmony_ci	}
197662306a36Sopenharmony_ci
197762306a36Sopenharmony_ci	cfg80211_chandef_create(&chandef, chan, chan_type);
197862306a36Sopenharmony_ci
197962306a36Sopenharmony_ci	/* we will be active on the TDLS link */
198062306a36Sopenharmony_ci	if (!cfg80211_reg_can_beacon_relax(sdata->local->hw.wiphy, &chandef,
198162306a36Sopenharmony_ci					   sdata->wdev.iftype)) {
198262306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS chan switch to forbidden channel\n");
198362306a36Sopenharmony_ci		ret = -EINVAL;
198462306a36Sopenharmony_ci		goto free;
198562306a36Sopenharmony_ci	}
198662306a36Sopenharmony_ci
198762306a36Sopenharmony_ci	mutex_lock(&local->sta_mtx);
198862306a36Sopenharmony_ci	sta = sta_info_get(sdata, tf->sa);
198962306a36Sopenharmony_ci	if (!sta || !test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH)) {
199062306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS chan switch from non-peer sta %pM\n",
199162306a36Sopenharmony_ci			 tf->sa);
199262306a36Sopenharmony_ci		ret = -EINVAL;
199362306a36Sopenharmony_ci		goto out;
199462306a36Sopenharmony_ci	}
199562306a36Sopenharmony_ci
199662306a36Sopenharmony_ci	params.sta = &sta->sta;
199762306a36Sopenharmony_ci
199862306a36Sopenharmony_ci	/* validate the initiator is set correctly */
199962306a36Sopenharmony_ci	local_initiator =
200062306a36Sopenharmony_ci		!memcmp(elems->lnk_id->init_sta, sdata->vif.addr, ETH_ALEN);
200162306a36Sopenharmony_ci	if (local_initiator == sta->sta.tdls_initiator) {
200262306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS chan switch invalid lnk-id initiator\n");
200362306a36Sopenharmony_ci		ret = -EINVAL;
200462306a36Sopenharmony_ci		goto out;
200562306a36Sopenharmony_ci	}
200662306a36Sopenharmony_ci
200762306a36Sopenharmony_ci	/* peer should have known better */
200862306a36Sopenharmony_ci	if (!sta->sta.deflink.ht_cap.ht_supported && elems->sec_chan_offs &&
200962306a36Sopenharmony_ci	    elems->sec_chan_offs->sec_chan_offs) {
201062306a36Sopenharmony_ci		tdls_dbg(sdata, "TDLS chan switch - wide chan unsupported\n");
201162306a36Sopenharmony_ci		ret = -ENOTSUPP;
201262306a36Sopenharmony_ci		goto out;
201362306a36Sopenharmony_ci	}
201462306a36Sopenharmony_ci
201562306a36Sopenharmony_ci	params.chandef = &chandef;
201662306a36Sopenharmony_ci	params.switch_time = le16_to_cpu(elems->ch_sw_timing->switch_time);
201762306a36Sopenharmony_ci	params.switch_timeout = le16_to_cpu(elems->ch_sw_timing->switch_timeout);
201862306a36Sopenharmony_ci
201962306a36Sopenharmony_ci	params.tmpl_skb =
202062306a36Sopenharmony_ci		ieee80211_tdls_ch_sw_resp_tmpl_get(sta,
202162306a36Sopenharmony_ci						   &params.ch_sw_tm_ie);
202262306a36Sopenharmony_ci	if (!params.tmpl_skb) {
202362306a36Sopenharmony_ci		ret = -ENOENT;
202462306a36Sopenharmony_ci		goto out;
202562306a36Sopenharmony_ci	}
202662306a36Sopenharmony_ci
202762306a36Sopenharmony_ci	drv_tdls_recv_channel_switch(sdata->local, sdata, &params);
202862306a36Sopenharmony_ci
202962306a36Sopenharmony_ci	tdls_dbg(sdata,
203062306a36Sopenharmony_ci		 "TDLS ch switch request received from %pM ch %d width %d\n",
203162306a36Sopenharmony_ci		 tf->sa, params.chandef->chan->center_freq,
203262306a36Sopenharmony_ci		 params.chandef->width);
203362306a36Sopenharmony_ciout:
203462306a36Sopenharmony_ci	mutex_unlock(&local->sta_mtx);
203562306a36Sopenharmony_ci	dev_kfree_skb_any(params.tmpl_skb);
203662306a36Sopenharmony_cifree:
203762306a36Sopenharmony_ci	kfree(elems);
203862306a36Sopenharmony_ci	return ret;
203962306a36Sopenharmony_ci}
204062306a36Sopenharmony_ci
204162306a36Sopenharmony_civoid
204262306a36Sopenharmony_ciieee80211_process_tdls_channel_switch(struct ieee80211_sub_if_data *sdata,
204362306a36Sopenharmony_ci				      struct sk_buff *skb)
204462306a36Sopenharmony_ci{
204562306a36Sopenharmony_ci	struct ieee80211_tdls_data *tf = (void *)skb->data;
204662306a36Sopenharmony_ci	struct wiphy *wiphy = sdata->local->hw.wiphy;
204762306a36Sopenharmony_ci
204862306a36Sopenharmony_ci	lockdep_assert_wiphy(wiphy);
204962306a36Sopenharmony_ci
205062306a36Sopenharmony_ci	/* make sure the driver supports it */
205162306a36Sopenharmony_ci	if (!(wiphy->features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH))
205262306a36Sopenharmony_ci		return;
205362306a36Sopenharmony_ci
205462306a36Sopenharmony_ci	/* we want to access the entire packet */
205562306a36Sopenharmony_ci	if (skb_linearize(skb))
205662306a36Sopenharmony_ci		return;
205762306a36Sopenharmony_ci	/*
205862306a36Sopenharmony_ci	 * The packet/size was already validated by mac80211 Rx path, only look
205962306a36Sopenharmony_ci	 * at the action type.
206062306a36Sopenharmony_ci	 */
206162306a36Sopenharmony_ci	switch (tf->action_code) {
206262306a36Sopenharmony_ci	case WLAN_TDLS_CHANNEL_SWITCH_REQUEST:
206362306a36Sopenharmony_ci		ieee80211_process_tdls_channel_switch_req(sdata, skb);
206462306a36Sopenharmony_ci		break;
206562306a36Sopenharmony_ci	case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE:
206662306a36Sopenharmony_ci		ieee80211_process_tdls_channel_switch_resp(sdata, skb);
206762306a36Sopenharmony_ci		break;
206862306a36Sopenharmony_ci	default:
206962306a36Sopenharmony_ci		WARN_ON_ONCE(1);
207062306a36Sopenharmony_ci		return;
207162306a36Sopenharmony_ci	}
207262306a36Sopenharmony_ci}
207362306a36Sopenharmony_ci
207462306a36Sopenharmony_civoid ieee80211_teardown_tdls_peers(struct ieee80211_sub_if_data *sdata)
207562306a36Sopenharmony_ci{
207662306a36Sopenharmony_ci	struct sta_info *sta;
207762306a36Sopenharmony_ci	u16 reason = WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED;
207862306a36Sopenharmony_ci
207962306a36Sopenharmony_ci	rcu_read_lock();
208062306a36Sopenharmony_ci	list_for_each_entry_rcu(sta, &sdata->local->sta_list, list) {
208162306a36Sopenharmony_ci		if (!sta->sta.tdls || sta->sdata != sdata || !sta->uploaded ||
208262306a36Sopenharmony_ci		    !test_sta_flag(sta, WLAN_STA_AUTHORIZED))
208362306a36Sopenharmony_ci			continue;
208462306a36Sopenharmony_ci
208562306a36Sopenharmony_ci		ieee80211_tdls_oper_request(&sdata->vif, sta->sta.addr,
208662306a36Sopenharmony_ci					    NL80211_TDLS_TEARDOWN, reason,
208762306a36Sopenharmony_ci					    GFP_ATOMIC);
208862306a36Sopenharmony_ci	}
208962306a36Sopenharmony_ci	rcu_read_unlock();
209062306a36Sopenharmony_ci}
209162306a36Sopenharmony_ci
209262306a36Sopenharmony_civoid ieee80211_tdls_handle_disconnect(struct ieee80211_sub_if_data *sdata,
209362306a36Sopenharmony_ci				      const u8 *peer, u16 reason)
209462306a36Sopenharmony_ci{
209562306a36Sopenharmony_ci	struct ieee80211_sta *sta;
209662306a36Sopenharmony_ci
209762306a36Sopenharmony_ci	rcu_read_lock();
209862306a36Sopenharmony_ci	sta = ieee80211_find_sta(&sdata->vif, peer);
209962306a36Sopenharmony_ci	if (!sta || !sta->tdls) {
210062306a36Sopenharmony_ci		rcu_read_unlock();
210162306a36Sopenharmony_ci		return;
210262306a36Sopenharmony_ci	}
210362306a36Sopenharmony_ci	rcu_read_unlock();
210462306a36Sopenharmony_ci
210562306a36Sopenharmony_ci	tdls_dbg(sdata, "disconnected from TDLS peer %pM (Reason: %u=%s)\n",
210662306a36Sopenharmony_ci		 peer, reason,
210762306a36Sopenharmony_ci		 ieee80211_get_reason_code_string(reason));
210862306a36Sopenharmony_ci
210962306a36Sopenharmony_ci	ieee80211_tdls_oper_request(&sdata->vif, peer,
211062306a36Sopenharmony_ci				    NL80211_TDLS_TEARDOWN,
211162306a36Sopenharmony_ci				    WLAN_REASON_TDLS_TEARDOWN_UNREACHABLE,
211262306a36Sopenharmony_ci				    GFP_ATOMIC);
211362306a36Sopenharmony_ci}
2114