1879 lines · 46.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * This file contains helper code to handle channel * settings and keeping track of what is possible at * any point in time. * * Copyright 2009	Johannes Berg <johannes@sipsolutions.net> * Copyright 2013-2014  Intel Mobile Communications GmbH * Copyright 2018-2026	Intel Corporation */ #include <linux/export.h>#include <linux/bitfield.h>#include <net/cfg80211.h>#include "core.h"#include "rdev-ops.h" static bool cfg80211_valid_60g_freq(u32 freq){	return freq >= 58320 && freq <= 70200;} void cfg80211_chandef_create(struct cfg80211_chan_def *chandef,			     struct ieee80211_channel *chan,			     enum nl80211_channel_type chan_type){	if (WARN_ON(!chan))		return; 	*chandef = (struct cfg80211_chan_def) {		.chan = chan,	}; 	WARN_ON(chan->band == NL80211_BAND_60GHZ ||		chan->band == NL80211_BAND_S1GHZ); 	switch (chan_type) {	case NL80211_CHAN_NO_HT:		chandef->width = NL80211_CHAN_WIDTH_20_NOHT;		chandef->center_freq1 = chan->center_freq;		break;	case NL80211_CHAN_HT20:		chandef->width = NL80211_CHAN_WIDTH_20;		chandef->center_freq1 = chan->center_freq;		break;	case NL80211_CHAN_HT40PLUS:		chandef->width = NL80211_CHAN_WIDTH_40;		chandef->center_freq1 = chan->center_freq + 10;		break;	case NL80211_CHAN_HT40MINUS:		chandef->width = NL80211_CHAN_WIDTH_40;		chandef->center_freq1 = chan->center_freq - 10;		break;	default:		WARN_ON(1);	}}EXPORT_SYMBOL(cfg80211_chandef_create); static u32 cfg80211_get_start_freq(const struct cfg80211_chan_def *chandef,				   u32 cf){	u32 start_freq, center_freq, bandwidth; 	center_freq = MHZ_TO_KHZ((cf == 1) ?			chandef->center_freq1 : chandef->center_freq2);	bandwidth = MHZ_TO_KHZ(cfg80211_chandef_get_width(chandef)); 	if (bandwidth <= MHZ_TO_KHZ(20))		start_freq = center_freq;	else		start_freq = center_freq - bandwidth / 2 + MHZ_TO_KHZ(10); 	return start_freq;} static u32 cfg80211_get_end_freq(const struct cfg80211_chan_def *chandef,				 u32 cf){	u32 end_freq, center_freq, bandwidth; 	center_freq = MHZ_TO_KHZ((cf == 1) ?			chandef->center_freq1 : chandef->center_freq2);	bandwidth = MHZ_TO_KHZ(cfg80211_chandef_get_width(chandef)); 	if (bandwidth <= MHZ_TO_KHZ(20))		end_freq = center_freq;	else		end_freq = center_freq + bandwidth / 2 - MHZ_TO_KHZ(10); 	return end_freq;} #define for_each_subchan(chandef, freq, cf)				\	for (u32 punctured = chandef->punctured,			\	     cf = 1, freq = cfg80211_get_start_freq(chandef, cf);	\	     freq <= cfg80211_get_end_freq(chandef, cf);		\	     freq += MHZ_TO_KHZ(20),					\	     ((cf == 1 && chandef->center_freq2 != 0 &&			\	       freq > cfg80211_get_end_freq(chandef, cf)) ?		\	      (cf++, freq = cfg80211_get_start_freq(chandef, cf),	\	       punctured = 0) : (punctured >>= 1)))			\		if (!(punctured & 1)) #define for_each_s1g_subchan(chandef, freq_khz)                   \	for (freq_khz = cfg80211_s1g_get_start_freq_khz(chandef); \	     freq_khz <= cfg80211_s1g_get_end_freq_khz(chandef);  \	     freq_khz += MHZ_TO_KHZ(1)) struct cfg80211_per_bw_puncturing_values {	u8 len;	const u16 *valid_values;}; static const u16 puncturing_values_80mhz[] = {	0x8, 0x4, 0x2, 0x1}; static const u16 puncturing_values_160mhz[] = {	 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1, 0xc0, 0x30, 0xc, 0x3}; static const u16 puncturing_values_320mhz[] = {	0xc000, 0x3000, 0xc00, 0x300, 0xc0, 0x30, 0xc, 0x3, 0xf000, 0xf00,	0xf0, 0xf, 0xfc00, 0xf300, 0xf0c0, 0xf030, 0xf00c, 0xf003, 0xc00f,	0x300f, 0xc0f, 0x30f, 0xcf, 0x3f}; #define CFG80211_PER_BW_VALID_PUNCTURING_VALUES(_bw) \	{ \		.len = ARRAY_SIZE(puncturing_values_ ## _bw ## mhz), \		.valid_values = puncturing_values_ ## _bw ## mhz \	} static const struct cfg80211_per_bw_puncturing_values per_bw_puncturing[] = {	CFG80211_PER_BW_VALID_PUNCTURING_VALUES(80),	CFG80211_PER_BW_VALID_PUNCTURING_VALUES(160),	CFG80211_PER_BW_VALID_PUNCTURING_VALUES(320)}; static bool valid_puncturing_bitmap(const struct cfg80211_chan_def *chandef,				    u32 primary_center, u32 punctured){	u32 idx, i, start_freq; 	switch (chandef->width) {	case NL80211_CHAN_WIDTH_80:		idx = 0;		start_freq = chandef->center_freq1 - 40;		break;	case NL80211_CHAN_WIDTH_160:		idx = 1;		start_freq = chandef->center_freq1 - 80;		break;	case NL80211_CHAN_WIDTH_320:		idx = 2;		start_freq = chandef->center_freq1 - 160;		break;	default:		return punctured == 0;	} 	if (!punctured)		return true; 	/* check if primary channel is punctured */	if (punctured & (u16)BIT((primary_center - start_freq) / 20))		return false; 	for (i = 0; i < per_bw_puncturing[idx].len; i++) {		if (per_bw_puncturing[idx].valid_values[i] == punctured)			return true;	} 	return false;} static bool cfg80211_edmg_chandef_valid(const struct cfg80211_chan_def *chandef){	int max_contiguous = 0;	int num_of_enabled = 0;	int contiguous = 0;	int i; 	if (!chandef->edmg.channels || !chandef->edmg.bw_config)		return false; 	if (!cfg80211_valid_60g_freq(chandef->chan->center_freq))		return false; 	for (i = 0; i < 6; i++) {		if (chandef->edmg.channels & BIT(i)) {			contiguous++;			num_of_enabled++;		} else {			contiguous = 0;		} 		max_contiguous = max(contiguous, max_contiguous);	}	/* basic verification of edmg configuration according to	 * IEEE P802.11ay/D4.0 section 9.4.2.251	 */	/* check bw_config against contiguous edmg channels */	switch (chandef->edmg.bw_config) {	case IEEE80211_EDMG_BW_CONFIG_4:	case IEEE80211_EDMG_BW_CONFIG_8:	case IEEE80211_EDMG_BW_CONFIG_12:		if (max_contiguous < 1)			return false;		break;	case IEEE80211_EDMG_BW_CONFIG_5:	case IEEE80211_EDMG_BW_CONFIG_9:	case IEEE80211_EDMG_BW_CONFIG_13:		if (max_contiguous < 2)			return false;		break;	case IEEE80211_EDMG_BW_CONFIG_6:	case IEEE80211_EDMG_BW_CONFIG_10:	case IEEE80211_EDMG_BW_CONFIG_14:		if (max_contiguous < 3)			return false;		break;	case IEEE80211_EDMG_BW_CONFIG_7:	case IEEE80211_EDMG_BW_CONFIG_11:	case IEEE80211_EDMG_BW_CONFIG_15:		if (max_contiguous < 4)			return false;		break; 	default:		return false;	} 	/* check bw_config against aggregated (non contiguous) edmg channels */	switch (chandef->edmg.bw_config) {	case IEEE80211_EDMG_BW_CONFIG_4:	case IEEE80211_EDMG_BW_CONFIG_5:	case IEEE80211_EDMG_BW_CONFIG_6:	case IEEE80211_EDMG_BW_CONFIG_7:		break;	case IEEE80211_EDMG_BW_CONFIG_8:	case IEEE80211_EDMG_BW_CONFIG_9:	case IEEE80211_EDMG_BW_CONFIG_10:	case IEEE80211_EDMG_BW_CONFIG_11:		if (num_of_enabled < 2)			return false;		break;	case IEEE80211_EDMG_BW_CONFIG_12:	case IEEE80211_EDMG_BW_CONFIG_13:	case IEEE80211_EDMG_BW_CONFIG_14:	case IEEE80211_EDMG_BW_CONFIG_15:		if (num_of_enabled < 4 || max_contiguous < 2)			return false;		break;	default:		return false;	} 	return true;} int nl80211_chan_width_to_mhz(enum nl80211_chan_width chan_width){	int mhz; 	switch (chan_width) {	case NL80211_CHAN_WIDTH_1:		mhz = 1;		break;	case NL80211_CHAN_WIDTH_2:		mhz = 2;		break;	case NL80211_CHAN_WIDTH_4:		mhz = 4;		break;	case NL80211_CHAN_WIDTH_8:		mhz = 8;		break;	case NL80211_CHAN_WIDTH_16:		mhz = 16;		break;	case NL80211_CHAN_WIDTH_20:	case NL80211_CHAN_WIDTH_20_NOHT:		mhz = 20;		break;	case NL80211_CHAN_WIDTH_40:		mhz = 40;		break;	case NL80211_CHAN_WIDTH_80P80:	case NL80211_CHAN_WIDTH_80:		mhz = 80;		break;	case NL80211_CHAN_WIDTH_160:		mhz = 160;		break;	case NL80211_CHAN_WIDTH_320:		mhz = 320;		break;	default:		WARN_ON_ONCE(1);		return -1;	}	return mhz;}EXPORT_SYMBOL(nl80211_chan_width_to_mhz); static bool cfg80211_valid_center_freq(u32 center,				       enum nl80211_chan_width width){	int bw;	int step; 	/* We only do strict verification on 6 GHz */	if (center < 5955 || center > 7215)		return true; 	bw = nl80211_chan_width_to_mhz(width);	if (bw < 0)		return false; 	/* Validate that the channels bw is entirely within the 6 GHz band */	if (center - bw / 2 < 5945 || center + bw / 2 > 7225)		return false; 	/* With 320 MHz the permitted channels overlap */	if (bw == 320)		step = 160;	else		step = bw; 	/*	 * Valid channels are packed from lowest frequency towards higher ones.	 * So test that the lower frequency aligns with one of these steps.	 */	return (center - bw / 2 - 5945) % step == 0;} static boolcfg80211_chandef_valid_control_freq(const struct cfg80211_chan_def *chandef,				    u32 control_freq){	switch (chandef->width) {	case NL80211_CHAN_WIDTH_20:	case NL80211_CHAN_WIDTH_20_NOHT:	case NL80211_CHAN_WIDTH_1:	case NL80211_CHAN_WIDTH_2:	case NL80211_CHAN_WIDTH_4:	case NL80211_CHAN_WIDTH_8:	case NL80211_CHAN_WIDTH_16:		/* checked separately */		break;	case NL80211_CHAN_WIDTH_320:		if (chandef->center_freq1 == control_freq + 150 ||		    chandef->center_freq1 == control_freq + 130 ||		    chandef->center_freq1 == control_freq + 110 ||		    chandef->center_freq1 == control_freq + 90 ||		    chandef->center_freq1 == control_freq - 90 ||		    chandef->center_freq1 == control_freq - 110 ||		    chandef->center_freq1 == control_freq - 130 ||		    chandef->center_freq1 == control_freq - 150)			break;		fallthrough;	case NL80211_CHAN_WIDTH_160:		if (chandef->center_freq1 == control_freq + 70 ||		    chandef->center_freq1 == control_freq + 50 ||		    chandef->center_freq1 == control_freq - 50 ||		    chandef->center_freq1 == control_freq - 70)			break;		fallthrough;	case NL80211_CHAN_WIDTH_80P80:	case NL80211_CHAN_WIDTH_80:		if (chandef->center_freq1 == control_freq + 30 ||		    chandef->center_freq1 == control_freq - 30)			break;		fallthrough;	case NL80211_CHAN_WIDTH_40:		if (chandef->center_freq1 == control_freq + 10 ||		    chandef->center_freq1 == control_freq - 10)			break;		fallthrough;	default:		return false;	} 	return true;} bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef){	u32 control_freq, control_freq_khz, start_khz, end_khz; 	if (!chandef->chan)		return false; 	if (chandef->freq1_offset >= 1000)		return false; 	control_freq = chandef->chan->center_freq; 	if (cfg80211_chandef_is_s1g(chandef) &&	    chandef->width != NL80211_CHAN_WIDTH_1 &&	    chandef->width != NL80211_CHAN_WIDTH_2 &&	    chandef->width != NL80211_CHAN_WIDTH_4 &&	    chandef->width != NL80211_CHAN_WIDTH_8 &&	    chandef->width != NL80211_CHAN_WIDTH_16)		return false; 	switch (chandef->width) {	case NL80211_CHAN_WIDTH_20:	case NL80211_CHAN_WIDTH_20_NOHT:		if (ieee80211_chandef_to_khz(chandef) !=		    ieee80211_channel_to_khz(chandef->chan))			return false;		if (chandef->center_freq2)			return false;		break;	case NL80211_CHAN_WIDTH_1:	case NL80211_CHAN_WIDTH_2:	case NL80211_CHAN_WIDTH_4:	case NL80211_CHAN_WIDTH_8:	case NL80211_CHAN_WIDTH_16:		if (!cfg80211_chandef_is_s1g(chandef))			return false;		if (chandef->center_freq2)			return false; 		control_freq_khz = ieee80211_channel_to_khz(chandef->chan);		start_khz = cfg80211_s1g_get_start_freq_khz(chandef);		end_khz = cfg80211_s1g_get_end_freq_khz(chandef); 		if (control_freq_khz < start_khz || control_freq_khz > end_khz)			return false;		break;	case NL80211_CHAN_WIDTH_80P80:		if (!chandef->center_freq2)			return false;		/* adjacent is not allowed -- that's a 160 MHz channel */		if (chandef->center_freq1 - chandef->center_freq2 == 80 ||		    chandef->center_freq2 - chandef->center_freq1 == 80)			return false;		break;	default:		if (chandef->center_freq2)			return false;		break;	} 	if (!cfg80211_chandef_valid_control_freq(chandef, control_freq))		return false; 	if (chandef->npca_chan) {		switch (chandef->width) {		case NL80211_CHAN_WIDTH_80:		case NL80211_CHAN_WIDTH_160:		case NL80211_CHAN_WIDTH_320:			break;		default:			return false;		}	} else if (chandef->npca_punctured) {		return false;	} 	if (!cfg80211_valid_center_freq(chandef->center_freq1, chandef->width))		return false; 	if (chandef->width == NL80211_CHAN_WIDTH_80P80 &&	    !cfg80211_valid_center_freq(chandef->center_freq2, chandef->width))		return false; 	/* channel 14 is only for IEEE 802.11b */	if (chandef->center_freq1 == 2484 &&	    chandef->width != NL80211_CHAN_WIDTH_20_NOHT)		return false; 	if (cfg80211_chandef_is_edmg(chandef) &&	    !cfg80211_edmg_chandef_valid(chandef))		return false; 	if (!cfg80211_chandef_is_s1g(chandef) && chandef->s1g_primary_2mhz)		return false; 	return valid_puncturing_bitmap(chandef, control_freq,				       chandef->punctured);}EXPORT_SYMBOL(cfg80211_chandef_valid); int cfg80211_chandef_primary(const struct cfg80211_chan_def *c,			     enum nl80211_chan_width primary_chan_width,			     u16 *punctured){	int pri_width = nl80211_chan_width_to_mhz(primary_chan_width);	int width = cfg80211_chandef_get_width(c);	u32 control = c->chan->center_freq;	u32 center = c->center_freq1;	u16 _punct = 0; 	if (WARN_ON_ONCE(pri_width < 0 || width < 0))		return -1;

Lines 1–500 of 1879.