ieee80211_process_tdls_channel_switch_req(struct ieee80211_sub_if_data *sdata,
					  struct sk_buff *skb)
{
	struct ieee80211_local *local = sdata->local;
	struct ieee802_11_elems elems;
	struct cfg80211_chan_def chandef;
	struct ieee80211_channel *chan;
	enum nl80211_channel_type chan_type;
	int freq;
	u8 target_channel, oper_class;
	bool local_initiator;
	struct sta_info *sta;
	enum nl80211_band band;
	struct ieee80211_tdls_data *tf = (void *)skb->data;
	struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);
	int baselen = offsetof(typeof(*tf), u.chan_switch_req.variable);
	struct ieee80211_tdls_ch_sw_params params = {};
	int ret = 0;

	params.action_code = WLAN_TDLS_CHANNEL_SWITCH_REQUEST;
	params.timestamp = rx_status->device_timestamp;

	if (skb->len < baselen) {
		tdls_dbg(sdata, "TDLS channel switch req too short: %d\n",
			 skb->len);
		return -EINVAL;
	}

	target_channel = tf->u.chan_switch_req.target_channel;
	oper_class = tf->u.chan_switch_req.oper_class;

	/*
	 * We can't easily infer the channel band. The operating class is
	 * ambiguous - there are multiple tables (US/Europe/JP/Global). The
	 * solution here is to treat channels with number >14 as 5GHz ones,
	 * and specifically check for the (oper_class, channel) combinations
	 * where this doesn't hold. These are thankfully unique according to
	 * IEEE802.11-2012.
	 * We consider only the 2GHz and 5GHz bands and 20MHz+ channels as
	 * valid here.
	 */
	if ((oper_class == 112 || oper_class == 2 || oper_class == 3 ||
	     oper_class == 4 || oper_class == 5 || oper_class == 6) &&
	     target_channel < 14)
		band = NL80211_BAND_5GHZ;
	else
		band = target_channel < 14 ? NL80211_BAND_2GHZ :
					     NL80211_BAND_5GHZ;

	freq = ieee80211_channel_to_frequency(target_channel, band);
	if (freq == 0) {
		tdls_dbg(sdata, "Invalid channel in TDLS chan switch: %d\n",
			 target_channel);
		return -EINVAL;
	}

	chan = ieee80211_get_channel(sdata->local->hw.wiphy, freq);
	if (!chan) {
		tdls_dbg(sdata,
			 "Unsupported channel for TDLS chan switch: %d\n",
			 target_channel);
		return -EINVAL;
	}

	ieee802_11_parse_elems(tf->u.chan_switch_req.variable,
			       skb->len - baselen, false, &elems, NULL, NULL);
	if (elems.parse_error) {
		tdls_dbg(sdata, "Invalid IEs in TDLS channel switch req\n");
		return -EINVAL;
	}

	if (!elems.ch_sw_timing || !elems.lnk_id) {
		tdls_dbg(sdata, "TDLS channel switch req - missing IEs\n");
		return -EINVAL;
	}

	if (!elems.sec_chan_offs) {
		chan_type = NL80211_CHAN_HT20;
	} else {
		switch (elems.sec_chan_offs->sec_chan_offs) {
		case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
			chan_type = NL80211_CHAN_HT40PLUS;
			break;
		case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
			chan_type = NL80211_CHAN_HT40MINUS;
			break;
		default:
			chan_type = NL80211_CHAN_HT20;
			break;
		}
	}

	cfg80211_chandef_create(&chandef, chan, chan_type);

	/* we will be active on the TDLS link */
	if (!cfg80211_reg_can_beacon_relax(sdata->local->hw.wiphy, &chandef,
					   sdata->wdev.iftype)) {
		tdls_dbg(sdata, "TDLS chan switch to forbidden channel\n");
		return -EINVAL;
	}

	mutex_lock(&local->sta_mtx);
	sta = sta_info_get(sdata, tf->sa);
	if (!sta || !test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH)) {
		tdls_dbg(sdata, "TDLS chan switch from non-peer sta %pM\n",
			 tf->sa);
		ret = -EINVAL;
		goto out;
	}

	params.sta = &sta->sta;

	/* validate the initiator is set correctly */
	local_initiator =
		!memcmp(elems.lnk_id->init_sta, sdata->vif.addr, ETH_ALEN);
	if (local_initiator == sta->sta.tdls_initiator) {
		tdls_dbg(sdata, "TDLS chan switch invalid lnk-id initiator\n");
		ret = -EINVAL;
		goto out;
	}

	/* peer should have known better */
	if (!sta->sta.ht_cap.ht_supported && elems.sec_chan_offs &&
	    elems.sec_chan_offs->sec_chan_offs) {
		tdls_dbg(sdata, "TDLS chan switch - wide chan unsupported\n");
		ret = -ENOTSUPP;
		goto out;
	}

	params.chandef = &chandef;
	params.switch_time = le16_to_cpu(elems.ch_sw_timing->switch_time);
	params.switch_timeout = le16_to_cpu(elems.ch_sw_timing->switch_timeout);

	params.tmpl_skb =
		ieee80211_tdls_ch_sw_resp_tmpl_get(sta,
						   &params.ch_sw_tm_ie);
	if (!params.tmpl_skb) {
		ret = -ENOENT;
		goto out;
	}

	drv_tdls_recv_channel_switch(sdata->local, sdata, &params);

	tdls_dbg(sdata,
		 "TDLS ch switch request received from %pM ch %d width %d\n",
		 tf->sa, params.chandef->chan->center_freq,
		 params.chandef->width);
out:
	mutex_unlock(&local->sta_mtx);
	dev_kfree_skb_any(params.tmpl_skb);
	return ret;
}
ieee80211_process_tdls_channel_switch_req(struct ieee80211_sub_if_data *sdata,
					  struct sk_buff *skb)
{
	struct ieee80211_local *local = sdata->local;
	struct ieee802_11_elems elems;
	struct cfg80211_chan_def chandef;
	struct ieee80211_channel *chan;
	enum nl80211_channel_type chan_type;
	int freq;
	u8 target_channel, oper_class;
	bool local_initiator;
	struct sta_info *sta;
	enum nl80211_band band;
	struct ieee80211_tdls_data *tf = (void *)skb->data;
	struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);
	int baselen = offsetof(typeof(*tf), u.chan_switch_req.variable);
	struct ieee80211_tdls_ch_sw_params params = {};
	int ret = 0;

	params.action_code = WLAN_TDLS_CHANNEL_SWITCH_REQUEST;
	params.timestamp = rx_status->device_timestamp;

	if (skb->len < baselen) {
		tdls_dbg(sdata, "TDLS channel switch req too short: %d\n",
			 skb->len);
		return -EINVAL;
	}

	target_channel = tf->u.chan_switch_req.target_channel;
	oper_class = tf->u.chan_switch_req.oper_class;

	 
	if ((oper_class == 112 || oper_class == 2 || oper_class == 3 ||
	     oper_class == 4 || oper_class == 5 || oper_class == 6) &&
	     target_channel < 14)
		band = NL80211_BAND_5GHZ;
	else
		band = target_channel < 14 ? NL80211_BAND_2GHZ :
					     NL80211_BAND_5GHZ;

	freq = ieee80211_channel_to_frequency(target_channel, band);
	if (freq == 0) {
		tdls_dbg(sdata, "Invalid channel in TDLS chan switch: %d\n",
			 target_channel);
		return -EINVAL;
	}

	chan = ieee80211_get_channel(sdata->local->hw.wiphy, freq);
	if (!chan) {
		tdls_dbg(sdata,
			 "Unsupported channel for TDLS chan switch: %d\n",
			 target_channel);
		return -EINVAL;
	}

	ieee802_11_parse_elems(tf->u.chan_switch_req.variable,
			       skb->len - baselen, false, &elems, NULL, NULL);
	if (elems.parse_error) {
		tdls_dbg(sdata, "Invalid IEs in TDLS channel switch req\n");
		return -EINVAL;
	}

	if (!elems.ch_sw_timing || !elems.lnk_id) {
		tdls_dbg(sdata, "TDLS channel switch req - missing IEs\n");
		return -EINVAL;
	}

	if (!elems.sec_chan_offs) {
		chan_type = NL80211_CHAN_HT20;
	} else {
		switch (elems.sec_chan_offs->sec_chan_offs) {
		case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
			chan_type = NL80211_CHAN_HT40PLUS;
			break;
		case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
			chan_type = NL80211_CHAN_HT40MINUS;
			break;
		default:
			chan_type = NL80211_CHAN_HT20;
			break;
		}
	}

	cfg80211_chandef_create(&chandef, chan, chan_type);

	 
	if (!cfg80211_reg_can_beacon_relax(sdata->local->hw.wiphy, &chandef,
					   sdata->wdev.iftype)) {
		tdls_dbg(sdata, "TDLS chan switch to forbidden channel\n");
		return -EINVAL;
	}

	mutex_lock(&local->sta_mtx);
	sta = sta_info_get(sdata, tf->sa);
	if (!sta || !test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH)) {
		tdls_dbg(sdata, "TDLS chan switch from non-peer sta %pM\n",
			 tf->sa);
		ret = -EINVAL;
		goto out;
	}

	params.sta = &sta->sta;

	 
	local_initiator =
		!memcmp(elems.lnk_id->init_sta, sdata->vif.addr, ETH_ALEN);
	if (local_initiator == sta->sta.tdls_initiator) {
		tdls_dbg(sdata, "TDLS chan switch invalid lnk-id initiator\n");
		ret = -EINVAL;
		goto out;
	}

	 
	if (!sta->sta.ht_cap.ht_supported && elems.sec_chan_offs &&
	    elems.sec_chan_offs->sec_chan_offs) {
		tdls_dbg(sdata, "TDLS chan switch - wide chan unsupported\n");
		ret = -ENOTSUPP;
		goto out;
	}

	params.chandef = &chandef;
	params.switch_time = le16_to_cpu(elems.ch_sw_timing->switch_time);
	params.switch_timeout = le16_to_cpu(elems.ch_sw_timing->switch_timeout);

	params.tmpl_skb =
		ieee80211_tdls_ch_sw_resp_tmpl_get(sta,
						   &params.ch_sw_tm_ie);
	if (!params.tmpl_skb) {
		ret = -ENOENT;
		goto out;
	}

	drv_tdls_recv_channel_switch(sdata->local, sdata, &params);

	tdls_dbg(sdata,
		 "TDLS ch switch request received from %pM ch %d width %d\n",
		 tf->sa, params.chandef->chan->center_freq,
		 params.chandef->width);
out:
	mutex_unlock(&local->sta_mtx);
	dev_kfree_skb_any(params.tmpl_skb);
	return ret;
}