| // 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 bool | |
| cfg80211_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; | |
| /* not intended to be called this way, can't determine */ | |
| if (WARN_ON_ONCE(pri_width > width)) | |
| return -1; | |
| if (!punctured) | |
| punctured = &_punct; | |
| *punctured = c->punctured; | |
| while (width > pri_width) { | |
| unsigned int bits_to_drop = width / 20 / 2; | |
| if (control > center) { | |
| center += width / 4; | |
| *punctured >>= bits_to_drop; | |
| } else { | |
| center -= width / 4; | |
| *punctured &= (1 << bits_to_drop) - 1; | |
| } | |
| width /= 2; | |
| } | |
| return center; | |
| } | |
| EXPORT_SYMBOL(cfg80211_chandef_primary); | |
| bool cfg80211_chandef_npca_valid(struct wiphy *wiphy, | |
| const struct cfg80211_chan_def *chandef, | |
| const struct ieee80211_uhr_npca_info *npca) | |
| { | |
| struct cfg80211_chan_def tmp = *chandef; | |
| bool pri_upper, npca_upper; | |
| u32 cf1; | |
| if (chandef->npca_chan || chandef->npca_punctured) | |
| return false; | |
| if (!npca) | |
| return true; | |
| if (cfg80211_chandef_add_npca(wiphy, &tmp, npca)) | |
| return false; | |
| if (!cfg80211_chandef_valid_control_freq(&tmp, | |
| tmp.npca_chan->center_freq)) | |
| return false; | |
| cf1 = tmp.center_freq1; | |
| pri_upper = tmp.chan->center_freq > cf1; | |
| npca_upper = tmp.npca_chan->center_freq > cf1; | |
| if (pri_upper == npca_upper) | |
| return false; | |
| if (!valid_puncturing_bitmap(&tmp, | |
| tmp.npca_chan->center_freq, | |
| tmp.npca_punctured) || | |
| (tmp.punctured & tmp.npca_punctured) != tmp.punctured) | |
| return false; | |
| return true; | |
| } | |
| EXPORT_SYMBOL(cfg80211_chandef_npca_valid); | |
| int cfg80211_chandef_add_npca(struct wiphy *wiphy, | |
| struct cfg80211_chan_def *chandef, | |
| const struct ieee80211_uhr_npca_info *npca) | |
| { | |
| struct cfg80211_chan_def new_chandef = *chandef; | |
| u32 width, npca_freq; | |
| u8 offs; | |
| if (chandef->npca_chan || chandef->npca_punctured) | |
| return -EINVAL; | |
| if (WARN_ON(!cfg80211_chandef_valid(chandef))) | |
| return -EINVAL; | |
| if (!npca) | |
| return 0; | |
| switch (chandef->width) { | |
| case NL80211_CHAN_WIDTH_80: | |
| case NL80211_CHAN_WIDTH_160: | |
| case NL80211_CHAN_WIDTH_320: | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| offs = le32_get_bits(npca->params, | |
| IEEE80211_UHR_NPCA_PARAMS_PRIMARY_CHAN_OFFS); | |
| width = cfg80211_chandef_get_width(chandef); | |
| npca_freq = chandef->center_freq1 - width / 2 + 10 + 20 * offs; | |
| new_chandef.npca_chan = ieee80211_get_channel(wiphy, npca_freq); | |
| if (!new_chandef.npca_chan) | |
| return -EINVAL; | |
| if (npca->params & cpu_to_le32(IEEE80211_UHR_NPCA_PARAMS_DIS_SUBCH_BMAP_PRES)) | |
| new_chandef.npca_punctured = le16_to_cpu(npca->dis_subch_bmap[0]); | |
| if (!cfg80211_chandef_valid(&new_chandef)) | |
| return -EINVAL; | |
| *chandef = new_chandef; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(cfg80211_chandef_add_npca); | |
| int cfg80211_chandef_add_dbe(struct cfg80211_chan_def *chandef, | |
| const struct ieee80211_uhr_dbe_info *dbe) | |
| { | |
| struct cfg80211_chan_def new_chandef = *chandef; | |
| u16 starting_freq, bw_mhz, start_old, start_new; | |
| u8 bw, punct_shift; | |
| int offset, index; | |
| if (!dbe) | |
| return 0; | |
| if (!cfg80211_chandef_valid(chandef)) | |
| return -EINVAL; | |
| if (chandef->width == NL80211_CHAN_WIDTH_20_NOHT) | |
| return -EINVAL; | |
| bw = u8_get_bits(dbe->params, IEEE80211_UHR_DBE_OPER_BANDWIDTH); | |
| switch (chandef->chan->band) { | |
| case NL80211_BAND_5GHZ: | |
| if (bw > IEEE80211_UHR_DBE_OPER_BW_160) | |
| return -EINVAL; | |
| if (chandef->chan->center_freq < 5745) | |
| starting_freq = 5180; /* channel 36 */ | |
| else | |
| starting_freq = 5745; /* channel 149 */ | |
| break; | |
| case NL80211_BAND_6GHZ: | |
| starting_freq = 5955; /* channel 1 center */ | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| switch (bw) { | |
| case IEEE80211_UHR_DBE_OPER_BW_320_2: | |
| case IEEE80211_UHR_DBE_OPER_BW_320_1: | |
| if (chandef->width == NL80211_CHAN_WIDTH_160) | |
| break; | |
| fallthrough; | |
| case IEEE80211_UHR_DBE_OPER_BW_160: | |
| if (chandef->width == NL80211_CHAN_WIDTH_80) | |
| break; | |
| fallthrough; | |
| case IEEE80211_UHR_DBE_OPER_BW_80: | |
| if (chandef->width == NL80211_CHAN_WIDTH_40) | |
| break; | |
| fallthrough; | |
| case IEEE80211_UHR_DBE_OPER_BW_40: | |
| if (chandef->width == NL80211_CHAN_WIDTH_20) | |
| break; | |
| fallthrough; | |
| default: | |
| return -EINVAL; | |
| } | |
| switch (bw) { | |
| case IEEE80211_UHR_DBE_OPER_BW_320_2: | |
| /* 320-2 starts shifted by 160 */ | |
| starting_freq += 160; | |
| fallthrough; | |
| case IEEE80211_UHR_DBE_OPER_BW_320_1: | |
| new_chandef.width = NL80211_CHAN_WIDTH_320; | |
| bw_mhz = 320; | |
| break; | |
| case IEEE80211_UHR_DBE_OPER_BW_160: | |
| new_chandef.width = NL80211_CHAN_WIDTH_160; | |
| bw_mhz = 160; | |
| break; | |
| case IEEE80211_UHR_DBE_OPER_BW_80: | |
| new_chandef.width = NL80211_CHAN_WIDTH_80; | |
| bw_mhz = 80; | |
| break; | |
| case IEEE80211_UHR_DBE_OPER_BW_40: | |
| new_chandef.width = NL80211_CHAN_WIDTH_40; | |
| bw_mhz = 40; | |
| break; | |
| } | |
| /* this should only happen for 320-2 and misconfigured AP */ | |
| if (chandef->chan->center_freq < starting_freq) | |
| return -EINVAL; | |
| offset = chandef->chan->center_freq - starting_freq; | |
| index = offset / bw_mhz; | |
| start_new = starting_freq - 10 + index * bw_mhz; | |
| new_chandef.center_freq1 = start_new + bw_mhz / 2; | |
| start_old = chandef->center_freq1 - | |
| cfg80211_chandef_get_width(chandef) / 2; | |
| /* | |
| * If the DBE channel extends downward below the lower | |
| * edge of the BSS channel, we need to shift puncturing | |
| * bitmaps up to adjust for that. | |
| */ | |
| if (start_new < start_old) | |
| punct_shift = (start_old - start_new) / 20; | |
| else | |
| punct_shift = 0; | |
| new_chandef.punctured <<= punct_shift; | |
| new_chandef.npca_punctured <<= punct_shift; | |
| if (dbe->params & IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES) { | |
| u16 punct_mask = ((1 << (bw_mhz / 40)) - 1) << punct_shift; | |
| u16 punctured = le16_to_cpu(dbe->dis_subch_bmap[0]); | |
| if ((punctured & punct_mask) != (new_chandef.punctured & punct_mask)) | |
| return -EINVAL; | |
| new_chandef.punctured = punctured; | |
| } | |
| if (!cfg80211_chandef_valid(&new_chandef)) | |
| return -EINVAL; | |
| /* | |
| * If e.g. a 40 MHz BSS channel (erroneously) occupies the center of the | |
| * DBE 80 MHz channel, they would be incompatible; check and reject. | |
| */ | |
| if (!cfg80211_chandef_compatible(&new_chandef, chandef)) | |
| return -EINVAL; | |
| *chandef = new_chandef; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(cfg80211_chandef_add_dbe); | |
| static const struct cfg80211_chan_def * | |
| check_chandef_primary_compat(const struct cfg80211_chan_def *c1, | |
| const struct cfg80211_chan_def *c2, | |
| enum nl80211_chan_width primary_chan_width) | |
| { | |
| u16 punct_c1 = 0, punct_c2 = 0; | |
| /* check primary is compatible -> error if not */ | |
| if (cfg80211_chandef_primary(c1, primary_chan_width, &punct_c1) != | |
| cfg80211_chandef_primary(c2, primary_chan_width, &punct_c2)) | |
| return ERR_PTR(-EINVAL); | |
| if (punct_c1 != punct_c2) | |
| return ERR_PTR(-EINVAL); | |
| /* assumes c1 is smaller width, if that was just checked -> done */ | |
| if (c1->width == primary_chan_width) | |
| return c2; | |
| /* otherwise continue checking the next width */ | |
| return NULL; | |
| } | |
| static const struct cfg80211_chan_def * | |
| _cfg80211_chandef_compatible(const struct cfg80211_chan_def *c1, | |
| const struct cfg80211_chan_def *c2) | |
| { | |
| const struct cfg80211_chan_def *ret; | |
| /* If they are identical, return */ | |
| if (cfg80211_chandef_identical(c1, c2)) | |
| return c2; | |
| /* otherwise, must have same control channel */ | |
| if (c1->chan != c2->chan) | |
| return NULL; | |
| /* | |
| * If they have the same width, but aren't identical, | |
| * then they can't be compatible. | |
| */ | |
| if (c1->width == c2->width) | |
| return NULL; | |
| /* | |
| * We need NPCA to be compatible for some scenarios such as | |
| * multiple APs, but in this case userspace should configure | |
| * identical chandefs including NPCA, even if perhaps one of | |
| * the AP interfaces doesn't even advertise it. | |
| */ | |
| if (c1->npca_chan || c2->npca_chan) | |
| return NULL; | |
| /* | |
| * can't be compatible if one of them is S1G | |
| * but they don't have the same width. | |
| */ | |
| #define IS_S1G(width) ((width) == NL80211_CHAN_WIDTH_1 || \ | |
| (width) == NL80211_CHAN_WIDTH_2 || \ | |
| (width) == NL80211_CHAN_WIDTH_4 || \ | |
| (width) == NL80211_CHAN_WIDTH_8 || \ | |
| (width) == NL80211_CHAN_WIDTH_16) | |
| if (IS_S1G(c1->width) || IS_S1G(c2->width)) | |
| return NULL; | |
| /* | |
| * Make sure that c1 is always the narrower one, so that later | |
| * we either return NULL or c2 and don't have to check both | |
| * directions. | |
| */ | |
| if (c1->width > c2->width) | |
| swap(c1, c2); | |
| /* | |
| * No further checks needed if the "narrower" one is only 20 MHz. | |
| * Here "narrower" includes being a 20 MHz non-HT channel vs. a | |
| * 20 MHz HT (or later) one. | |
| */ | |
| if (c1->width <= NL80211_CHAN_WIDTH_20) | |
| return c2; | |
| ret = check_chandef_primary_compat(c1, c2, NL80211_CHAN_WIDTH_40); | |
| if (ret) | |
| return ret; | |
| ret = check_chandef_primary_compat(c1, c2, NL80211_CHAN_WIDTH_80); | |
| if (ret) | |
| return ret; | |
| /* | |
| * If c1 is 80+80, then c2 is 160 or higher, but that cannot | |
| * match. If c2 was also 80+80 it was already either accepted | |
| * or rejected above (identical or not, respectively.) | |
| */ | |
| if (c1->width == NL80211_CHAN_WIDTH_80P80) | |
| return NULL; | |
| ret = check_chandef_primary_compat(c1, c2, NL80211_CHAN_WIDTH_160); | |
| if (ret) | |
| return ret; | |
| /* | |
| * Getting here would mean they're both wider than 160, have the | |
| * same primary 160, but are not identical - this cannot happen | |
| * since they must be 320 (no wider chandefs exist, at least yet.) | |
| */ | |
| WARN_ON_ONCE(1); | |
| return NULL; | |
| } | |
| const struct cfg80211_chan_def * | |
| cfg80211_chandef_compatible(const struct cfg80211_chan_def *c1, | |
| const struct cfg80211_chan_def *c2) | |
| { | |
| const struct cfg80211_chan_def *ret; | |
| ret = _cfg80211_chandef_compatible(c1, c2); | |
| if (IS_ERR(ret)) | |
| return NULL; | |
| return ret; | |
| } | |
| EXPORT_SYMBOL(cfg80211_chandef_compatible); | |
| void cfg80211_set_dfs_state(struct wiphy *wiphy, | |
| const struct cfg80211_chan_def *chandef, | |
| enum nl80211_dfs_state dfs_state) | |
| { | |
| struct ieee80211_channel *c; | |
| int width; | |
| if (WARN_ON(!cfg80211_chandef_valid(chandef))) | |
| return; | |
| width = cfg80211_chandef_get_width(chandef); | |
| if (width < 0) | |
| return; | |
| for_each_subchan(chandef, freq, cf) { | |
| c = ieee80211_get_channel_khz(wiphy, freq); | |
| if (!c || !(c->flags & IEEE80211_CHAN_RADAR)) | |
| continue; | |
| c->dfs_state = dfs_state; | |
| c->dfs_state_entered = jiffies; | |
| } | |
| } | |
| void cfg80211_set_cac_state(struct wiphy *wiphy, | |
| const struct cfg80211_chan_def *chandef, | |
| bool cac_ongoing) | |
| { | |
| struct ieee80211_channel *c; | |
| int width; | |
| u64 cac_time; | |
| if (WARN_ON(!cfg80211_chandef_valid(chandef))) | |
| return; | |
| width = cfg80211_chandef_get_width(chandef); | |
| if (width < 0) | |
| return; | |
| /* Get the same timestamp for all subchannels */ | |
| cac_time = cac_ongoing ? ktime_get_boottime_ns() : 0; | |
| for_each_subchan(chandef, freq, cf) { | |
| c = ieee80211_get_channel_khz(wiphy, freq); | |
| if (!c) | |
| continue; | |
| c->cac_start_time = cac_time; | |
| } | |
| } | |
| static bool | |
| cfg80211_dfs_permissive_check_wdev(struct cfg80211_registered_device *rdev, | |
| enum nl80211_iftype iftype, | |
| struct wireless_dev *wdev, | |
| struct ieee80211_channel *chan) | |
| { | |
| unsigned int link_id; | |
| for_each_valid_link(wdev, link_id) { | |
| struct ieee80211_channel *other_chan = NULL; | |
| struct cfg80211_chan_def chandef = {}; | |
| int ret; | |
| /* In order to avoid daisy chaining only allow BSS STA */ | |
| if (wdev->iftype != NL80211_IFTYPE_STATION || | |
| !wdev->links[link_id].client.current_bss) | |
| continue; | |
| other_chan = | |
| wdev->links[link_id].client.current_bss->pub.channel; | |
| if (!other_chan) | |
| continue; | |
| if (chan == other_chan) | |
| return true; | |
| /* continue if we can't get the channel */ | |
| ret = rdev_get_channel(rdev, wdev, link_id, &chandef); | |
| if (ret) | |
| continue; | |
| if (cfg80211_is_sub_chan(&chandef, chan, false)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| /* | |
| * Check if P2P GO is allowed to operate on a DFS channel | |
| */ | |
| static bool cfg80211_dfs_permissive_chan(struct wiphy *wiphy, | |
| enum nl80211_iftype iftype, | |
| struct ieee80211_channel *chan) | |
| { | |
| struct wireless_dev *wdev; | |
| struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); | |
| lockdep_assert_held(&rdev->wiphy.mtx); | |
| if (!wiphy_ext_feature_isset(&rdev->wiphy, | |
| NL80211_EXT_FEATURE_DFS_CONCURRENT) || | |
| !(chan->flags & IEEE80211_CHAN_DFS_CONCURRENT)) | |
| return false; | |
| /* only valid for P2P GO */ | |
| if (iftype != NL80211_IFTYPE_P2P_GO) | |
| return false; | |
| /* | |
| * Allow only if there's a concurrent BSS | |
| */ | |
| list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { | |
| bool ret = cfg80211_dfs_permissive_check_wdev(rdev, iftype, | |
| wdev, chan); | |
| if (ret) | |
| return ret; | |
| } | |
| return false; | |
| } | |
| static int cfg80211_get_chans_dfs_required(struct wiphy *wiphy, | |
| const struct cfg80211_chan_def *chandef, | |
| enum nl80211_iftype iftype) | |
| { | |
| struct ieee80211_channel *c; | |
| /* DFS is not required for S1G */ | |
| if (cfg80211_chandef_is_s1g(chandef)) | |
| return 0; | |
| for_each_subchan(chandef, freq, cf) { | |
| c = ieee80211_get_channel_khz(wiphy, freq); | |
| if (!c) | |
| return -EINVAL; | |
| if (c->flags & IEEE80211_CHAN_RADAR && | |
| !cfg80211_dfs_permissive_chan(wiphy, iftype, c)) | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| int cfg80211_chandef_dfs_required(struct wiphy *wiphy, | |
| const struct cfg80211_chan_def *chandef, | |
| enum nl80211_iftype iftype) | |
| { | |
| int width; | |
| int ret; | |
| if (WARN_ON(!cfg80211_chandef_valid(chandef))) | |
| return -EINVAL; | |
| switch (iftype) { | |
| case NL80211_IFTYPE_ADHOC: | |
| case NL80211_IFTYPE_AP: | |
| case NL80211_IFTYPE_P2P_GO: | |
| case NL80211_IFTYPE_MESH_POINT: | |
| case NL80211_IFTYPE_NAN: | |
| width = cfg80211_chandef_get_width(chandef); | |
| if (width < 0) | |
| return -EINVAL; | |
| ret = cfg80211_get_chans_dfs_required(wiphy, chandef, iftype); | |
| return (ret > 0) ? BIT(chandef->width) : ret; | |
| break; | |
| case NL80211_IFTYPE_STATION: | |
| case NL80211_IFTYPE_OCB: | |
| case NL80211_IFTYPE_P2P_CLIENT: | |
| case NL80211_IFTYPE_MONITOR: | |
| case NL80211_IFTYPE_AP_VLAN: | |
| case NL80211_IFTYPE_P2P_DEVICE: | |
| case NL80211_IFTYPE_NAN_DATA: | |
| case NL80211_IFTYPE_PD: | |
| break; | |
| case NL80211_IFTYPE_WDS: | |
| case NL80211_IFTYPE_UNSPECIFIED: | |
| case NUM_NL80211_IFTYPES: | |
| WARN_ON(1); | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(cfg80211_chandef_dfs_required); | |
| bool cfg80211_chandef_dfs_usable(struct wiphy *wiphy, | |
| const struct cfg80211_chan_def *chandef) | |
| { | |
| struct ieee80211_channel *c; | |
| int width, count = 0; | |
| if (WARN_ON(!cfg80211_chandef_valid(chandef))) | |
| return false; | |
| width = cfg80211_chandef_get_width(chandef); | |
| if (width < 0) | |
| return false; | |
| /* | |
| * Check entire range of channels for the bandwidth. | |
| * Check all channels are DFS channels (DFS_USABLE or | |
| * DFS_AVAILABLE). Return number of usable channels | |
| * (require CAC). Allow DFS and non-DFS channel mix. | |
| */ | |
| for_each_subchan(chandef, freq, cf) { | |
| c = ieee80211_get_channel_khz(wiphy, freq); | |
| if (!c) | |
| return false; | |
| if (c->flags & IEEE80211_CHAN_DISABLED) | |
| return false; | |
| if (c->flags & IEEE80211_CHAN_RADAR) { | |
| if (c->dfs_state == NL80211_DFS_UNAVAILABLE) | |
| return false; | |
| if (c->dfs_state == NL80211_DFS_USABLE) | |
| count++; | |
| } | |
| } | |
| return count > 0; | |
| } | |
| EXPORT_SYMBOL(cfg80211_chandef_dfs_usable); | |
| /* | |
| * Checks if center frequency of chan falls with in the bandwidth | |
| * range of chandef. | |
| */ | |
| bool cfg80211_is_sub_chan(struct cfg80211_chan_def *chandef, | |
| struct ieee80211_channel *chan, | |
| bool primary_only) | |
| { | |
| int width; | |
| u32 freq; | |
| if (!chandef->chan) | |
| return false; | |
| if (chandef->chan->center_freq == chan->center_freq) | |
| return true; | |
| if (primary_only) | |
| return false; | |
| width = cfg80211_chandef_get_width(chandef); | |
| if (width <= 20) | |
| return false; | |
| for (freq = chandef->center_freq1 - width / 2 + 10; | |
| freq <= chandef->center_freq1 + width / 2 - 10; freq += 20) { | |
| if (chan->center_freq == freq) | |
| return true; | |
| } | |
| if (!chandef->center_freq2) | |
| return false; | |
| for (freq = chandef->center_freq2 - width / 2 + 10; | |
| freq <= chandef->center_freq2 + width / 2 - 10; freq += 20) { | |
| if (chan->center_freq == freq) | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool cfg80211_beaconing_iface_active(struct wireless_dev *wdev) | |
| { | |
| unsigned int link; | |
| lockdep_assert_wiphy(wdev->wiphy); | |
| switch (wdev->iftype) { | |
| case NL80211_IFTYPE_AP: | |
| case NL80211_IFTYPE_P2P_GO: | |
| for_each_valid_link(wdev, link) { | |
| if (wdev->links[link].ap.beacon_interval) | |
| return true; | |
| } | |
| break; | |
| case NL80211_IFTYPE_ADHOC: | |
| if (wdev->u.ibss.ssid_len) | |
| return true; | |
| break; | |
| case NL80211_IFTYPE_MESH_POINT: | |
| if (wdev->u.mesh.id_len) | |
| return true; | |
| break; | |
| case NL80211_IFTYPE_STATION: | |
| case NL80211_IFTYPE_OCB: | |
| case NL80211_IFTYPE_P2P_CLIENT: | |
| case NL80211_IFTYPE_MONITOR: | |
| case NL80211_IFTYPE_AP_VLAN: | |
| case NL80211_IFTYPE_P2P_DEVICE: | |
| /* Can NAN type be considered as beaconing interface? */ | |
| case NL80211_IFTYPE_NAN: | |
| case NL80211_IFTYPE_NAN_DATA: | |
| case NL80211_IFTYPE_PD: | |
| break; | |
| case NL80211_IFTYPE_UNSPECIFIED: | |
| case NL80211_IFTYPE_WDS: | |
| case NUM_NL80211_IFTYPES: | |
| WARN_ON(1); | |
| } | |
| return false; | |
| } | |
| bool cfg80211_wdev_on_sub_chan(struct wireless_dev *wdev, | |
| struct ieee80211_channel *chan, | |
| bool primary_only) | |
| { | |
| unsigned int link; | |
| switch (wdev->iftype) { | |
| case NL80211_IFTYPE_AP: | |
| case NL80211_IFTYPE_P2P_GO: | |
| for_each_valid_link(wdev, link) { | |
| if (cfg80211_is_sub_chan(&wdev->links[link].ap.chandef, | |
| chan, primary_only)) | |
| return true; | |
| } | |
| break; | |
| case NL80211_IFTYPE_ADHOC: | |
| return cfg80211_is_sub_chan(&wdev->u.ibss.chandef, chan, | |
| primary_only); | |
| case NL80211_IFTYPE_MESH_POINT: | |
| return cfg80211_is_sub_chan(&wdev->u.mesh.chandef, chan, | |
| primary_only); | |
| default: | |
| break; | |
| } | |
| return false; | |
| } | |
| static bool cfg80211_is_wiphy_oper_chan(struct wiphy *wiphy, | |
| struct ieee80211_channel *chan) | |
| { | |
| struct wireless_dev *wdev; | |
| lockdep_assert_wiphy(wiphy); | |
| list_for_each_entry(wdev, &wiphy->wdev_list, list) { | |
| if (!cfg80211_beaconing_iface_active(wdev)) | |
| continue; | |
| if (cfg80211_wdev_on_sub_chan(wdev, chan, false)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool | |
| cfg80211_offchan_chain_is_active(struct cfg80211_registered_device *rdev, | |
| struct ieee80211_channel *channel) | |
| { | |
| if (!rdev->background_radar_wdev) | |
| return false; | |
| if (!cfg80211_chandef_valid(&rdev->background_radar_chandef)) | |
| return false; | |
| return cfg80211_is_sub_chan(&rdev->background_radar_chandef, channel, | |
| false); | |
| } | |
| bool cfg80211_any_wiphy_oper_chan(struct wiphy *wiphy, | |
| struct ieee80211_channel *chan) | |
| { | |
| struct cfg80211_registered_device *rdev; | |
| ASSERT_RTNL(); | |
| if (!(chan->flags & IEEE80211_CHAN_RADAR)) | |
| return false; | |
| for_each_rdev(rdev) { | |
| bool found; | |
| if (!reg_dfs_domain_same(wiphy, &rdev->wiphy)) | |
| continue; | |
| guard(wiphy)(&rdev->wiphy); | |
| found = cfg80211_is_wiphy_oper_chan(&rdev->wiphy, chan) || | |
| cfg80211_offchan_chain_is_active(rdev, chan); | |
| if (found) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool cfg80211_chandef_dfs_available(struct wiphy *wiphy, | |
| const struct cfg80211_chan_def *chandef) | |
| { | |
| struct ieee80211_channel *c; | |
| int width; | |
| bool dfs_offload; | |
| if (WARN_ON(!cfg80211_chandef_valid(chandef))) | |
| return false; | |
| width = cfg80211_chandef_get_width(chandef); | |
| if (width < 0) | |
| return false; | |
| dfs_offload = wiphy_ext_feature_isset(wiphy, | |
| NL80211_EXT_FEATURE_DFS_OFFLOAD); | |
| /* | |
| * Check entire range of channels for the bandwidth. | |
| * If any channel in between is disabled or has not | |
| * had gone through CAC return false | |
| */ | |
| for_each_subchan(chandef, freq, cf) { | |
| c = ieee80211_get_channel_khz(wiphy, freq); | |
| if (!c) | |
| return false; | |
| if (c->flags & IEEE80211_CHAN_DISABLED) | |
| return false; | |
| if ((c->flags & IEEE80211_CHAN_RADAR) && | |
| (c->dfs_state != NL80211_DFS_AVAILABLE) && | |
| !(c->dfs_state == NL80211_DFS_USABLE && dfs_offload)) | |
| return false; | |
| } | |
| return true; | |
| } | |
| unsigned int | |
| cfg80211_chandef_dfs_cac_time(struct wiphy *wiphy, | |
| const struct cfg80211_chan_def *chandef) | |
| { | |
| struct ieee80211_channel *c; | |
| int width; | |
| unsigned int t1 = 0, t2 = 0; | |
| if (WARN_ON(!cfg80211_chandef_valid(chandef))) | |
| return 0; | |
| width = cfg80211_chandef_get_width(chandef); | |
| if (width < 0) | |
| return 0; | |
| for_each_subchan(chandef, freq, cf) { | |
| c = ieee80211_get_channel_khz(wiphy, freq); | |
| if (!c || (c->flags & IEEE80211_CHAN_DISABLED)) { | |
| if (cf == 1) | |
| t1 = INT_MAX; | |
| else | |
| t2 = INT_MAX; | |
| continue; | |
| } | |
| if (!(c->flags & IEEE80211_CHAN_RADAR)) | |
| continue; | |
| if (cf == 1 && c->dfs_cac_ms > t1) | |
| t1 = c->dfs_cac_ms; | |
| if (cf == 2 && c->dfs_cac_ms > t2) | |
| t2 = c->dfs_cac_ms; | |
| } | |
| if (t1 == INT_MAX && t2 == INT_MAX) | |
| return 0; | |
| if (t1 == INT_MAX) | |
| return t2; | |
| if (t2 == INT_MAX) | |
| return t1; | |
| return max(t1, t2); | |
| } | |
| EXPORT_SYMBOL(cfg80211_chandef_dfs_cac_time); | |
| /* check if the operating channels are valid and supported */ | |
| static bool cfg80211_edmg_usable(struct wiphy *wiphy, u8 edmg_channels, | |
| enum ieee80211_edmg_bw_config edmg_bw_config, | |
| int primary_channel, | |
| struct ieee80211_edmg *edmg_cap) | |
| { | |
| struct ieee80211_channel *chan; | |
| int i, freq; | |
| int channels_counter = 0; | |
| if (!edmg_channels && !edmg_bw_config) | |
| return true; | |
| if ((!edmg_channels && edmg_bw_config) || | |
| (edmg_channels && !edmg_bw_config)) | |
| return false; | |
| if (!(edmg_channels & BIT(primary_channel - 1))) | |
| return false; | |
| /* 60GHz channels 1..6 */ | |
| for (i = 0; i < 6; i++) { | |
| if (!(edmg_channels & BIT(i))) | |
| continue; | |
| if (!(edmg_cap->channels & BIT(i))) | |
| return false; | |
| channels_counter++; | |
| freq = ieee80211_channel_to_frequency(i + 1, | |
| NL80211_BAND_60GHZ); | |
| chan = ieee80211_get_channel(wiphy, freq); | |
| if (!chan || chan->flags & IEEE80211_CHAN_DISABLED) | |
| return false; | |
| } | |
| /* IEEE802.11 allows max 4 channels */ | |
| if (channels_counter > 4) | |
| return false; | |
| /* check bw_config is a subset of what driver supports | |
| * (see IEEE P802.11ay/D4.0 section 9.4.2.251, Table 13) | |
| */ | |
| if ((edmg_bw_config % 4) > (edmg_cap->bw_config % 4)) | |
| return false; | |
| if (edmg_bw_config > edmg_cap->bw_config) | |
| return false; | |
| return true; | |
| } | |
| static bool cfg80211_s1g_usable(struct wiphy *wiphy, | |
| const struct cfg80211_chan_def *chandef) | |
| { | |
| u32 freq_khz; | |
| const struct ieee80211_channel *chan; | |
| u32 pri_khz = ieee80211_channel_to_khz(chandef->chan); | |
| u32 end_khz = cfg80211_s1g_get_end_freq_khz(chandef); | |
| u32 start_khz = cfg80211_s1g_get_start_freq_khz(chandef); | |
| int width_mhz = cfg80211_chandef_get_width(chandef); | |
| u32 prohibited_flags = IEEE80211_CHAN_DISABLED; | |
| if (width_mhz >= 16) | |
| prohibited_flags |= IEEE80211_CHAN_NO_16MHZ; | |
| if (width_mhz >= 8) | |
| prohibited_flags |= IEEE80211_CHAN_NO_8MHZ; | |
| if (width_mhz >= 4) | |
| prohibited_flags |= IEEE80211_CHAN_NO_4MHZ; | |
| if (chandef->chan->flags & IEEE80211_CHAN_S1G_NO_PRIMARY) | |
| return false; | |
| if (pri_khz < start_khz || pri_khz > end_khz) | |
| return false; | |
| for_each_s1g_subchan(chandef, freq_khz) { | |
| chan = ieee80211_get_channel_khz(wiphy, freq_khz); | |
| if (!chan || (chan->flags & prohibited_flags)) | |
| return false; | |
| } | |
| if (chandef->s1g_primary_2mhz) { | |
| u32 sib_khz; | |
| const struct ieee80211_channel *sibling; | |
| sibling = cfg80211_s1g_get_primary_sibling(wiphy, chandef); | |
| if (!sibling) | |
| return false; | |
| if (sibling->flags & IEEE80211_CHAN_S1G_NO_PRIMARY) | |
| return false; | |
| sib_khz = ieee80211_channel_to_khz(sibling); | |
| if (sib_khz < start_khz || sib_khz > end_khz) | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool _cfg80211_chandef_usable(struct wiphy *wiphy, | |
| const struct cfg80211_chan_def *chandef, | |
| u32 prohibited_flags, | |
| u32 permitting_flags) | |
| { | |
| struct ieee80211_sta_ht_cap *ht_cap; | |
| struct ieee80211_sta_vht_cap *vht_cap; | |
| struct ieee80211_edmg *edmg_cap; | |
| u32 width, control_freq, cap; | |
| bool ext_nss_cap, support_80_80 = false, support_320 = false; | |
| const struct ieee80211_sband_iftype_data *iftd; | |
| struct ieee80211_supported_band *sband; | |
| struct ieee80211_channel *c; | |
| int i; | |
| if (WARN_ON(!cfg80211_chandef_valid(chandef))) | |
| return false; | |
| ht_cap = &wiphy->bands[chandef->chan->band]->ht_cap; | |
| vht_cap = &wiphy->bands[chandef->chan->band]->vht_cap; | |
| edmg_cap = &wiphy->bands[chandef->chan->band]->edmg_cap; | |
| ext_nss_cap = __le16_to_cpu(vht_cap->vht_mcs.tx_highest) & | |
| IEEE80211_VHT_EXT_NSS_BW_CAPABLE; | |
| if (cfg80211_chandef_is_s1g(chandef)) | |
| return cfg80211_s1g_usable(wiphy, chandef); | |
| if (edmg_cap->channels && | |
| !cfg80211_edmg_usable(wiphy, | |
| chandef->edmg.channels, | |
| chandef->edmg.bw_config, | |
| chandef->chan->hw_value, | |
| edmg_cap)) | |
| return false; | |
| control_freq = chandef->chan->center_freq; | |
| switch (chandef->width) { | |
| case NL80211_CHAN_WIDTH_20: | |
| if (!ht_cap->ht_supported && | |
| chandef->chan->band != NL80211_BAND_6GHZ) | |
| return false; | |
| fallthrough; | |
| case NL80211_CHAN_WIDTH_20_NOHT: | |
| prohibited_flags |= IEEE80211_CHAN_NO_20MHZ; | |
| width = 20; | |
| break; | |
| case NL80211_CHAN_WIDTH_40: | |
| width = 40; | |
| if (chandef->chan->band == NL80211_BAND_6GHZ) | |
| break; | |
| if (!ht_cap->ht_supported) | |
| return false; | |
| if (!(ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) || | |
| ht_cap->cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT) | |
| return false; | |
| if (chandef->center_freq1 < control_freq && | |
| chandef->chan->flags & IEEE80211_CHAN_NO_HT40MINUS) | |
| return false; | |
| if (chandef->center_freq1 > control_freq && | |
| chandef->chan->flags & IEEE80211_CHAN_NO_HT40PLUS) | |
| return false; | |
| break; | |
| case NL80211_CHAN_WIDTH_80P80: | |
| cap = vht_cap->cap; | |
| support_80_80 = | |
| (cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ) || | |
| (cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ && | |
| cap & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK) || | |
| (ext_nss_cap && | |
| u32_get_bits(cap, IEEE80211_VHT_CAP_EXT_NSS_BW_MASK) > 1); | |
| if (chandef->chan->band != NL80211_BAND_6GHZ && !support_80_80) | |
| return false; | |
| fallthrough; | |
| case NL80211_CHAN_WIDTH_80: | |
| prohibited_flags |= IEEE80211_CHAN_NO_80MHZ; | |
| width = 80; | |
| if (chandef->chan->band == NL80211_BAND_6GHZ) | |
| break; | |
| if (!vht_cap->vht_supported) | |
| return false; | |
| break; | |
| case NL80211_CHAN_WIDTH_160: | |
| prohibited_flags |= IEEE80211_CHAN_NO_160MHZ; | |
| width = 160; | |
| if (chandef->chan->band == NL80211_BAND_6GHZ) | |
| break; | |
| if (!vht_cap->vht_supported) | |
| return false; | |
| cap = vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK; | |
| if (cap != IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ && | |
| cap != IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ && | |
| !(ext_nss_cap && | |
| (vht_cap->cap & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK))) | |
| return false; | |
| break; | |
| case NL80211_CHAN_WIDTH_320: | |
| prohibited_flags |= IEEE80211_CHAN_NO_320MHZ; | |
| width = 320; | |
| if (chandef->chan->band != NL80211_BAND_6GHZ) | |
| return false; | |
| sband = wiphy->bands[NL80211_BAND_6GHZ]; | |
| if (!sband) | |
| return false; | |
| for_each_sband_iftype_data(sband, i, iftd) { | |
| if (!iftd->eht_cap.has_eht) | |
| continue; | |
| if (iftd->eht_cap.eht_cap_elem.phy_cap_info[0] & | |
| IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ) { | |
| support_320 = true; | |
| break; | |
| } | |
| } | |
| if (!support_320) | |
| return false; | |
| break; | |
| default: | |
| WARN_ON_ONCE(1); | |
| return false; | |
| } | |
| /* | |
| * TODO: What if there are only certain 80/160/80+80 MHz channels | |
| * allowed by the driver, or only certain combinations? | |
| * For 40 MHz the driver can set the NO_HT40 flags, but for | |
| * 80/160 MHz and in particular 80+80 MHz this isn't really | |
| * feasible and we only have NO_80MHZ/NO_160MHZ so far but | |
| * no way to cover 80+80 MHz or more complex restrictions. | |
| * Note that such restrictions also need to be advertised to | |
| * userspace, for example for P2P channel selection. | |
| */ | |
| if (width > 20) | |
| prohibited_flags |= IEEE80211_CHAN_NO_OFDM; | |
| /* 5 and 10 MHz are only defined for the OFDM PHY */ | |
| if (width < 20) | |
| prohibited_flags |= IEEE80211_CHAN_NO_OFDM; | |
| for_each_subchan(chandef, freq, cf) { | |
| c = ieee80211_get_channel_khz(wiphy, freq); | |
| if (!c) | |
| return false; | |
| if (c->flags & permitting_flags) | |
| continue; | |
| if (c->flags & prohibited_flags) | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool cfg80211_chandef_usable(struct wiphy *wiphy, | |
| const struct cfg80211_chan_def *chandef, | |
| u32 prohibited_flags) | |
| { | |
| return _cfg80211_chandef_usable(wiphy, chandef, prohibited_flags, 0); | |
| } | |
| EXPORT_SYMBOL(cfg80211_chandef_usable); | |
| static bool cfg80211_ir_permissive_check_wdev(enum nl80211_iftype iftype, | |
| struct wireless_dev *wdev, | |
| struct ieee80211_channel *chan) | |
| { | |
| struct ieee80211_channel *other_chan = NULL; | |
| unsigned int link_id; | |
| int r1, r2; | |
| for_each_valid_link(wdev, link_id) { | |
| if (wdev->iftype == NL80211_IFTYPE_STATION && | |
| wdev->links[link_id].client.current_bss) | |
| other_chan = wdev->links[link_id].client.current_bss->pub.channel; | |
| /* | |
| * If a GO already operates on the same GO_CONCURRENT channel, | |
| * this one (maybe the same one) can beacon as well. We allow | |
| * the operation even if the station we relied on with | |
| * GO_CONCURRENT is disconnected now. But then we must make sure | |
| * we're not outdoor on an indoor-only channel. | |
| */ | |
| if (iftype == NL80211_IFTYPE_P2P_GO && | |
| wdev->iftype == NL80211_IFTYPE_P2P_GO && | |
| wdev->links[link_id].ap.beacon_interval && | |
| !(chan->flags & IEEE80211_CHAN_INDOOR_ONLY)) | |
| other_chan = wdev->links[link_id].ap.chandef.chan; | |
| if (!other_chan) | |
| continue; | |
| if (chan == other_chan) | |
| return true; | |
| if (chan->band != NL80211_BAND_5GHZ && | |
| chan->band != NL80211_BAND_6GHZ) | |
| continue; | |
| r1 = cfg80211_get_unii(chan->center_freq); | |
| r2 = cfg80211_get_unii(other_chan->center_freq); | |
| if (r1 != -EINVAL && r1 == r2) { | |
| /* | |
| * At some locations channels 149-165 are considered a | |
| * bundle, but at other locations, e.g., Indonesia, | |
| * channels 149-161 are considered a bundle while | |
| * channel 165 is left out and considered to be in a | |
| * different bundle. Thus, in case that there is a | |
| * station interface connected to an AP on channel 165, | |
| * it is assumed that channels 149-161 are allowed for | |
| * GO operations. However, having a station interface | |
| * connected to an AP on channels 149-161, does not | |
| * allow GO operation on channel 165. | |
| */ | |
| if (chan->center_freq == 5825 && | |
| other_chan->center_freq != 5825) | |
| continue; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /* | |
| * Check if the channel can be used under permissive conditions mandated by | |
| * some regulatory bodies, i.e., the channel is marked with | |
| * IEEE80211_CHAN_IR_CONCURRENT and there is an additional station interface | |
| * associated to an AP on the same channel or on the same UNII band | |
| * (assuming that the AP is an authorized master). | |
| * In addition allow operation on a channel on which indoor operation is | |
| * allowed, iff we are currently operating in an indoor environment. | |
| */ | |
| static bool cfg80211_ir_permissive_chan(struct wiphy *wiphy, | |
| enum nl80211_iftype iftype, | |
| struct ieee80211_channel *chan) | |
| { | |
| struct wireless_dev *wdev; | |
| struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); | |
| lockdep_assert_held(&rdev->wiphy.mtx); | |
| if (!IS_ENABLED(CONFIG_CFG80211_REG_RELAX_NO_IR) || | |
| !(wiphy->regulatory_flags & REGULATORY_ENABLE_RELAX_NO_IR)) | |
| return false; | |
| /* only valid for GO and TDLS off-channel (station/p2p-CL) */ | |
| if (iftype != NL80211_IFTYPE_P2P_GO && | |
| iftype != NL80211_IFTYPE_STATION && | |
| iftype != NL80211_IFTYPE_P2P_CLIENT) | |
| return false; | |
| if (regulatory_indoor_allowed() && | |
| (chan->flags & IEEE80211_CHAN_INDOOR_ONLY)) | |
| return true; | |
| if (!(chan->flags & IEEE80211_CHAN_IR_CONCURRENT)) | |
| return false; | |
| /* | |
| * Generally, it is possible to rely on another device/driver to allow | |
| * the IR concurrent relaxation, however, since the device can further | |
| * enforce the relaxation (by doing a similar verifications as this), | |
| * and thus fail the GO instantiation, consider only the interfaces of | |
| * the current registered device. | |
| */ | |
| list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { | |
| bool ret; | |
| ret = cfg80211_ir_permissive_check_wdev(iftype, wdev, chan); | |
| if (ret) | |
| return ret; | |
| } | |
| return false; | |
| } | |
| static bool _cfg80211_reg_can_beacon(struct wiphy *wiphy, | |
| struct cfg80211_chan_def *chandef, | |
| enum nl80211_iftype iftype, | |
| u32 prohibited_flags, | |
| u32 permitting_flags) | |
| { | |
| bool res, check_radar; | |
| int dfs_required; | |
| trace_cfg80211_reg_can_beacon(wiphy, chandef, iftype, | |
| prohibited_flags, | |
| permitting_flags); | |
| if (!_cfg80211_chandef_usable(wiphy, chandef, | |
| IEEE80211_CHAN_DISABLED, 0)) | |
| return false; | |
| dfs_required = cfg80211_chandef_dfs_required(wiphy, chandef, iftype); | |
| check_radar = dfs_required != 0; | |
| if (dfs_required > 0 && | |
| cfg80211_chandef_dfs_available(wiphy, chandef)) { | |
| /* We can skip IEEE80211_CHAN_NO_IR if chandef dfs available */ | |
| prohibited_flags &= ~IEEE80211_CHAN_NO_IR; | |
| check_radar = false; | |
| } | |
| if (check_radar && | |
| !_cfg80211_chandef_usable(wiphy, chandef, | |
| IEEE80211_CHAN_RADAR, 0)) | |
| return false; | |
| res = _cfg80211_chandef_usable(wiphy, chandef, | |
| prohibited_flags, | |
| permitting_flags); | |
| trace_cfg80211_return_bool(res); | |
| return res; | |
| } | |
| bool cfg80211_reg_check_beaconing(struct wiphy *wiphy, | |
| struct cfg80211_chan_def *chandef, | |
| struct cfg80211_beaconing_check_config *cfg) | |
| { | |
| struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); | |
| u32 permitting_flags = 0; | |
| bool check_no_ir = true; | |
| /* | |
| * Under certain conditions suggested by some regulatory bodies a | |
| * GO/STA can IR on channels marked with IEEE80211_NO_IR. Set this flag | |
| * only if such relaxations are not enabled and the conditions are not | |
| * met. | |
| */ | |
| if (cfg->relax) { | |
| lockdep_assert_held(&rdev->wiphy.mtx); | |
| check_no_ir = !cfg80211_ir_permissive_chan(wiphy, cfg->iftype, | |
| chandef->chan); | |
| } | |
| if (cfg->reg_power == IEEE80211_REG_VLP_AP) | |
| permitting_flags |= IEEE80211_CHAN_ALLOW_6GHZ_VLP_AP; | |
| if ((cfg->iftype == NL80211_IFTYPE_P2P_GO || | |
| cfg->iftype == NL80211_IFTYPE_AP) && | |
| (chandef->width == NL80211_CHAN_WIDTH_20_NOHT || | |
| chandef->width == NL80211_CHAN_WIDTH_20)) | |
| permitting_flags |= IEEE80211_CHAN_ALLOW_20MHZ_ACTIVITY; | |
| return _cfg80211_reg_can_beacon(wiphy, chandef, cfg->iftype, | |
| check_no_ir ? IEEE80211_CHAN_NO_IR : 0, | |
| permitting_flags); | |
| } | |
| EXPORT_SYMBOL(cfg80211_reg_check_beaconing); | |
| int cfg80211_set_monitor_channel(struct cfg80211_registered_device *rdev, | |
| struct net_device *dev, | |
| struct cfg80211_chan_def *chandef) | |
| { | |
| if (!rdev->ops->set_monitor_channel) | |
| return -EOPNOTSUPP; | |
| if (!cfg80211_has_monitors_only(rdev)) | |
| return -EBUSY; | |
| return rdev_set_monitor_channel(rdev, dev, chandef); | |
| } | |
| bool cfg80211_any_usable_channels(struct wiphy *wiphy, | |
| unsigned long sband_mask, | |
| u32 prohibited_flags) | |
| { | |
| int idx; | |
| prohibited_flags |= IEEE80211_CHAN_DISABLED; | |
| for_each_set_bit(idx, &sband_mask, NUM_NL80211_BANDS) { | |
| struct ieee80211_supported_band *sband = wiphy->bands[idx]; | |
| int chanidx; | |
| if (!sband) | |
| continue; | |
| for (chanidx = 0; chanidx < sband->n_channels; chanidx++) { | |
| struct ieee80211_channel *chan; | |
| chan = &sband->channels[chanidx]; | |
| if (chan->flags & prohibited_flags) | |
| continue; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| EXPORT_SYMBOL(cfg80211_any_usable_channels); | |
| struct cfg80211_chan_def *wdev_chandef(struct wireless_dev *wdev, | |
| unsigned int link_id) | |
| { | |
| lockdep_assert_wiphy(wdev->wiphy); | |
| WARN_ON(wdev->valid_links && !(wdev->valid_links & BIT(link_id))); | |
| WARN_ON(!wdev->valid_links && link_id > 0); | |
| switch (wdev->iftype) { | |
| case NL80211_IFTYPE_MESH_POINT: | |
| return &wdev->u.mesh.chandef; | |
| case NL80211_IFTYPE_ADHOC: | |
| return &wdev->u.ibss.chandef; | |
| case NL80211_IFTYPE_OCB: | |
| return &wdev->u.ocb.chandef; | |
| case NL80211_IFTYPE_AP: | |
| case NL80211_IFTYPE_P2P_GO: | |
| return &wdev->links[link_id].ap.chandef; | |
| default: | |
| return NULL; | |
| } | |
| } | |
| EXPORT_SYMBOL(wdev_chandef); |