From: Johannes Berg Date: Fri, 15 May 2026 11:12:12 +0000 (+0300) Subject: wifi: cfg80211: add a function to parse UHR DBE X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c62675c217f10a7fec945b4671e21274fe92f097;p=thirdparty%2Fkernel%2Flinux.git wifi: cfg80211: add a function to parse UHR DBE Add a function that takes the DBE information and parses it into an existing chandef that should hold the BSS channel. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260515141209.4eb1490f5cc6.I3ca9421f1fe4c31073846b1b62017f12c75889de@changeid Signed-off-by: Johannes Berg --- diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index ddcf559430dd6..69dc9a978861a 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1190,6 +1190,16 @@ int cfg80211_chandef_add_npca(struct wiphy *wiphy, struct cfg80211_chan_def *chandef, const struct ieee80211_uhr_npca_info *npca); +/** + * cfg80211_chandef_add_dbe - parse and add DBE information to chandef + * @chandef: the chandef to expand + * @dbe: the DBE information, must be size-checked if not %NULL + * + * Returns: 0 for success, a negative error code otherwise + */ +int cfg80211_chandef_add_dbe(struct cfg80211_chan_def *chandef, + const struct ieee80211_uhr_dbe_info *dbe); + /** * nl80211_send_chandef - sends the channel definition. * @msg: the msg to send channel definition diff --git a/net/wireless/chan.c b/net/wireless/chan.c index ed35b55b1b670..f0811efb5d0fb 100644 --- a/net/wireless/chan.c +++ b/net/wireless/chan.c @@ -619,6 +619,136 @@ int cfg80211_chandef_add_npca(struct wiphy *wiphy, } 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, diff --git a/net/wireless/tests/chan.c b/net/wireless/tests/chan.c index 7b97b731993ca..65eb18c498deb 100644 --- a/net/wireless/tests/chan.c +++ b/net/wireless/tests/chan.c @@ -2,13 +2,18 @@ /* * KUnit tests for channel helper functions * - * Copyright (C) 2023-2024 Intel Corporation + * Copyright (C) 2023-2024, 2026 Intel Corporation */ #include #include MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); +static struct ieee80211_channel chan_2ghz_1 = { + .band = NL80211_BAND_2GHZ, + .center_freq = 2412, +}; + static struct ieee80211_channel chan_6ghz_1 = { .band = NL80211_BAND_6GHZ, .center_freq = 5955, @@ -215,14 +220,189 @@ static void test_chandef_compat(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, ret, expect); } -static struct kunit_case chandef_compat_test_cases[] = { +static const struct chandef_dbe_case { + const char *desc; + struct cfg80211_chan_def c; + u8 dbe[3]; + bool fails; + u16 cf1; +} chandef_dbe_cases[] = { + { + .desc = "non-HT failure", + .c = { + .width = NL80211_CHAN_WIDTH_20_NOHT, + .chan = &chan_6ghz_1, + .center_freq1 = 5955, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_40, + .fails = true, + }, + { + .desc = "2.4 GHz fails", + .c = { + .width = NL80211_CHAN_WIDTH_20, + .chan = &chan_2ghz_1, + .center_freq1 = 2412, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_40, + .fails = true, + }, + { + .desc = "DBE narrower", + .c = { + .width = NL80211_CHAN_WIDTH_320, + .chan = &chan_6ghz_1, + .center_freq1 = 5955 + 10 + 20 + 40 + 80, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_160, + .fails = true, + }, + { + .desc = "DBE to 320-1", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_1, + .cf1 = 6425, + }, + { + .desc = "DBE to 320-2", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_2, + .cf1 = 6585, + }, + { + .desc = "bad disabled subchannel bitmap - not enough in BSS (1)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0001, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_1 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0 */ + .fails = true, + }, + { + .desc = "bad disabled subchannel bitmap - too much in BSS (1)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0001, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_1 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0x0300 */ + .dbe[2] = 0x03, + .fails = true, + }, + { + .desc = "bad disabled subchannel bitmap - not enough in BSS (2)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0001, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_2 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0 */ + .fails = true, + }, + { + .desc = "bad disabled subchannel bitmap - too much in BSS (2)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0001, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_2 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0x03 */ + .dbe[1] = 0x03, + .fails = true, + }, + { + .desc = "bad disabled subchannel bitmap - bad bitmap", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0001, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_1 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0x1100 */ + .dbe[2] = 0x11, + .fails = true, + }, + { + .desc = "good disabled subchannel bitmap (1)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0003, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_1 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0x0300 */ + .dbe[2] = 0x03, + .cf1 = 6425, + }, + { + .desc = "good disabled subchannel bitmap (2)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0003, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_2 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0x0003 */ + .dbe[1] = 0x03, + .cf1 = 6585, + }, +}; + +KUNIT_ARRAY_PARAM_DESC(chandef_dbe, chandef_dbe_cases, desc) + +static void test_chandef_dbe(struct kunit *test) +{ + const struct chandef_dbe_case *params = test->param_value; + struct cfg80211_chan_def c = params->c; + int ret; + + KUNIT_EXPECT_EQ(test, cfg80211_chandef_valid(¶ms->c), true); + + ret = cfg80211_chandef_add_dbe(&c, (void *)params->dbe); + KUNIT_EXPECT_EQ(test, ret != 0, params->fails); + + if (params->fails) + return; + + KUNIT_EXPECT_EQ(test, c.center_freq1, params->cf1); +} + +static struct kunit_case chandef_test_cases[] = { KUNIT_CASE_PARAM(test_chandef_compat, chandef_compat_gen_params), + KUNIT_CASE_PARAM(test_chandef_dbe, chandef_dbe_gen_params), {} }; -static struct kunit_suite chandef_compat = { - .name = "cfg80211-chandef-compat", - .test_cases = chandef_compat_test_cases, +static struct kunit_suite chandef = { + .name = "cfg80211-chandef", + .test_cases = chandef_test_cases, }; -kunit_test_suite(chandef_compat); +kunit_test_suite(chandef);