]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: cfg80211: add a function to parse UHR DBE
authorJohannes Berg <johannes.berg@intel.com>
Fri, 15 May 2026 11:12:12 +0000 (14:12 +0300)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 20 May 2026 10:03:19 +0000 (12:03 +0200)
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 <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260515141209.4eb1490f5cc6.I3ca9421f1fe4c31073846b1b62017f12c75889de@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/cfg80211.h
net/wireless/chan.c
net/wireless/tests/chan.c

index ddcf559430dd6087c400c52efbc77ef99c7d81ba..69dc9a978861a8ac8a9b472760d69a08707de38c 100644 (file)
@@ -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
index ed35b55b1b6704a8b0b51e2583df549a3e80cf33..f0811efb5d0fba2a46743a3dbffaa10ebab70df7 100644 (file)
@@ -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,
index 7b97b731993ca72bcd4efed7cc6eeb407fcec6dd..65eb18c498deb43cca976ae9ef3f459d1784f1fe 100644 (file)
@@ -2,13 +2,18 @@
 /*
  * KUnit tests for channel helper functions
  *
- * Copyright (C) 2023-2024 Intel Corporation
+ * Copyright (C) 2023-2024, 2026 Intel Corporation
  */
 #include <net/cfg80211.h>
 #include <kunit/test.h>
 
 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(&params->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);