diff options
Diffstat (limited to 'net/mac80211/spectmgmt.c')
| -rw-r--r-- | net/mac80211/spectmgmt.c | 177 | 
1 files changed, 172 insertions, 5 deletions
diff --git a/net/mac80211/spectmgmt.c b/net/mac80211/spectmgmt.c index 7733f66ee2c..6ab00907008 100644 --- a/net/mac80211/spectmgmt.c +++ b/net/mac80211/spectmgmt.c @@ -21,6 +21,177 @@  #include "sta_info.h"  #include "wme.h" +int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata, +				 struct ieee802_11_elems *elems, bool beacon, +				 enum ieee80211_band current_band, +				 u32 sta_flags, u8 *bssid, +				 struct ieee80211_csa_ie *csa_ie) +{ +	enum ieee80211_band new_band; +	int new_freq; +	u8 new_chan_no; +	struct ieee80211_channel *new_chan; +	struct cfg80211_chan_def new_vht_chandef = {}; +	const struct ieee80211_sec_chan_offs_ie *sec_chan_offs; +	const struct ieee80211_wide_bw_chansw_ie *wide_bw_chansw_ie; +	const struct ieee80211_ht_operation *ht_oper; +	int secondary_channel_offset = -1; + +	sec_chan_offs = elems->sec_chan_offs; +	wide_bw_chansw_ie = elems->wide_bw_chansw_ie; +	ht_oper = elems->ht_operation; + +	if (sta_flags & (IEEE80211_STA_DISABLE_HT | +			 IEEE80211_STA_DISABLE_40MHZ)) { +		sec_chan_offs = NULL; +		wide_bw_chansw_ie = NULL; +		/* only used for bandwidth here */ +		ht_oper = NULL; +	} + +	if (sta_flags & IEEE80211_STA_DISABLE_VHT) +		wide_bw_chansw_ie = NULL; + +	if (elems->ext_chansw_ie) { +		if (!ieee80211_operating_class_to_band( +				elems->ext_chansw_ie->new_operating_class, +				&new_band)) { +			sdata_info(sdata, +				   "cannot understand ECSA IE operating class %d, disconnecting\n", +				   elems->ext_chansw_ie->new_operating_class); +			return -EINVAL; +		} +		new_chan_no = elems->ext_chansw_ie->new_ch_num; +		csa_ie->count = elems->ext_chansw_ie->count; +		csa_ie->mode = elems->ext_chansw_ie->mode; +	} else if (elems->ch_switch_ie) { +		new_band = current_band; +		new_chan_no = elems->ch_switch_ie->new_ch_num; +		csa_ie->count = elems->ch_switch_ie->count; +		csa_ie->mode = elems->ch_switch_ie->mode; +	} else { +		/* nothing here we understand */ +		return 1; +	} + +	/* Mesh Channel Switch Parameters Element */ +	if (elems->mesh_chansw_params_ie) { +		csa_ie->ttl = elems->mesh_chansw_params_ie->mesh_ttl; +		csa_ie->mode = elems->mesh_chansw_params_ie->mesh_flags; +		csa_ie->pre_value = le16_to_cpu( +				elems->mesh_chansw_params_ie->mesh_pre_value); +	} + +	new_freq = ieee80211_channel_to_frequency(new_chan_no, new_band); +	new_chan = ieee80211_get_channel(sdata->local->hw.wiphy, new_freq); +	if (!new_chan || new_chan->flags & IEEE80211_CHAN_DISABLED) { +		sdata_info(sdata, +			   "BSS %pM switches to unsupported channel (%d MHz), disconnecting\n", +			   bssid, new_freq); +		return -EINVAL; +	} + +	if (!beacon && sec_chan_offs) { +		secondary_channel_offset = sec_chan_offs->sec_chan_offs; +	} else if (beacon && ht_oper) { +		secondary_channel_offset = +			ht_oper->ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET; +	} else if (!(sta_flags & IEEE80211_STA_DISABLE_HT)) { +		/* If it's not a beacon, HT is enabled and the IE not present, +		 * it's 20 MHz, 802.11-2012 8.5.2.6: +		 *	This element [the Secondary Channel Offset Element] is +		 *	present when switching to a 40 MHz channel. It may be +		 *	present when switching to a 20 MHz channel (in which +		 *	case the secondary channel offset is set to SCN). +		 */ +		secondary_channel_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; +	} + +	switch (secondary_channel_offset) { +	default: +		/* secondary_channel_offset was present but is invalid */ +	case IEEE80211_HT_PARAM_CHA_SEC_NONE: +		cfg80211_chandef_create(&csa_ie->chandef, new_chan, +					NL80211_CHAN_HT20); +		break; +	case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: +		cfg80211_chandef_create(&csa_ie->chandef, new_chan, +					NL80211_CHAN_HT40PLUS); +		break; +	case IEEE80211_HT_PARAM_CHA_SEC_BELOW: +		cfg80211_chandef_create(&csa_ie->chandef, new_chan, +					NL80211_CHAN_HT40MINUS); +		break; +	case -1: +		cfg80211_chandef_create(&csa_ie->chandef, new_chan, +					NL80211_CHAN_NO_HT); +		/* keep width for 5/10 MHz channels */ +		switch (sdata->vif.bss_conf.chandef.width) { +		case NL80211_CHAN_WIDTH_5: +		case NL80211_CHAN_WIDTH_10: +			csa_ie->chandef.width = +				sdata->vif.bss_conf.chandef.width; +			break; +		default: +			break; +		} +		break; +	} + +	if (wide_bw_chansw_ie) { +		new_vht_chandef.chan = new_chan; +		new_vht_chandef.center_freq1 = +			ieee80211_channel_to_frequency( +				wide_bw_chansw_ie->new_center_freq_seg0, +				new_band); + +		switch (wide_bw_chansw_ie->new_channel_width) { +		default: +			/* hmmm, ignore VHT and use HT if present */ +		case IEEE80211_VHT_CHANWIDTH_USE_HT: +			new_vht_chandef.chan = NULL; +			break; +		case IEEE80211_VHT_CHANWIDTH_80MHZ: +			new_vht_chandef.width = NL80211_CHAN_WIDTH_80; +			break; +		case IEEE80211_VHT_CHANWIDTH_160MHZ: +			new_vht_chandef.width = NL80211_CHAN_WIDTH_160; +			break; +		case IEEE80211_VHT_CHANWIDTH_80P80MHZ: +			/* field is otherwise reserved */ +			new_vht_chandef.center_freq2 = +				ieee80211_channel_to_frequency( +					wide_bw_chansw_ie->new_center_freq_seg1, +					new_band); +			new_vht_chandef.width = NL80211_CHAN_WIDTH_80P80; +			break; +		} +		if (sta_flags & IEEE80211_STA_DISABLE_80P80MHZ && +		    new_vht_chandef.width == NL80211_CHAN_WIDTH_80P80) +			ieee80211_chandef_downgrade(&new_vht_chandef); +		if (sta_flags & IEEE80211_STA_DISABLE_160MHZ && +		    new_vht_chandef.width == NL80211_CHAN_WIDTH_160) +			ieee80211_chandef_downgrade(&new_vht_chandef); +		if (sta_flags & IEEE80211_STA_DISABLE_40MHZ && +		    new_vht_chandef.width > NL80211_CHAN_WIDTH_20) +			ieee80211_chandef_downgrade(&new_vht_chandef); +	} + +	/* if VHT data is there validate & use it */ +	if (new_vht_chandef.chan) { +		if (!cfg80211_chandef_compatible(&new_vht_chandef, +						 &csa_ie->chandef)) { +			sdata_info(sdata, +				   "BSS %pM: CSA has inconsistent channel data, disconnecting\n", +				   bssid); +			return -EINVAL; +		} +		csa_ie->chandef = new_vht_chandef; +	} + +	return 0; +} +  static void ieee80211_send_refuse_measurement_request(struct ieee80211_sub_if_data *sdata,  					struct ieee80211_msrment_ie *request_ie,  					const u8 *da, const u8 *bssid, @@ -32,12 +203,8 @@ static void ieee80211_send_refuse_measurement_request(struct ieee80211_sub_if_da  	skb = dev_alloc_skb(sizeof(*msr_report) + local->hw.extra_tx_headroom +  				sizeof(struct ieee80211_msrment_ie)); - -	if (!skb) { -		printk(KERN_ERR "%s: failed to allocate buffer for " -				"measurement report frame\n", sdata->name); +	if (!skb)  		return; -	}  	skb_reserve(skb, local->hw.extra_tx_headroom);  	msr_report = (struct ieee80211_mgmt *)skb_put(skb, 24);  | 
