]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
mac80211: improve patch to allow grace period for DFS
authorFelix Fietkau <nbd@nbd.name>
Sun, 1 Feb 2026 15:38:53 +0000 (15:38 +0000)
committerFelix Fietkau <nbd@nbd.name>
Sun, 1 Feb 2026 19:21:38 +0000 (20:21 +0100)
Fix corner cases in updates.
Improve channel puncturing handling.
Fix dealing with CSA.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/kernel/mac80211/patches/ath/404-regd_no_assoc_hints.patch
package/kernel/mac80211/patches/subsys/310-cfg80211-allow-grace-period-for-DFS-available-after-.patch

index 57c9c7b0d9291c0cc92a058c7d7e9fc475c197c2..a82021af31557d95d3222a2bf83e0b55a9810b11 100644 (file)
@@ -1,6 +1,6 @@
 --- a/net/wireless/reg.c
 +++ b/net/wireless/reg.c
-@@ -3335,6 +3335,8 @@ void regulatory_hint_country_ie(struct w
+@@ -3336,6 +3336,8 @@ void regulatory_hint_country_ie(struct w
        enum environment_cap env = ENVIRON_ANY;
        struct regulatory_request *request = NULL, *lr;
  
@@ -9,7 +9,7 @@
        /* IE len must be evenly divisible by 2 */
        if (country_ie_len & 0x01)
                return;
-@@ -3584,6 +3586,7 @@ static bool is_wiphy_all_set_reg_flag(en
+@@ -3585,6 +3587,7 @@ static bool is_wiphy_all_set_reg_flag(en
  
  void regulatory_hint_disconnect(void)
  {
index 01fe5dd7df800937de01472fb3f5372f94c4f528..5ada929cc7d3efef68168d8cd778c65582cadfe0 100644 (file)
@@ -3,7 +3,27 @@ Date: Thu, 14 Sep 2023 13:17:16 +0200
 Subject: [PATCH] cfg80211: allow grace period for DFS available after beacon
  shutdown
 
-Fixes reconfiguring an AP on a DFS channel in non-ETSI regdomain
+In non-ETSI regulatory domains, DFS channel availability from completed CAC
+expires after REG_PRE_CAC_EXPIRY_GRACE_MS (2 seconds) when no longer in use.
+This creates a problem when reconfiguring an AP on a DFS channel: stopping
+the AP to apply new settings causes the channel to immediately expire to
+USABLE state, requiring a full CAC again.
+
+The root cause is that the grace period timeout was calculated from
+dfs_state_entered, which records when CAC completed. For an AP that has
+been running for hours, this timestamp is far in the past, causing
+immediate expiration.
+
+Fix this by introducing a new field dfs_state_last_available that tracks
+when a DFS channel was last actively used. This timestamp is updated:
+- When DFS state transitions to AVAILABLE (CAC completion)
+- When an AP stops beaconing on the channel
+- When a channel switch moves away from the channel
+- When DFS state is copied between wiphys
+
+The grace period for AVAILABLE->USABLE transitions now uses this new
+timestamp, giving a 2-second window to reconfigure the AP without losing
+DFS availability.
 
 Fixes: b35a51c7dd25 ("cfg80211: Make pre-CAC results valid only for ETSI domain")
 Signed-off-by: Felix Fietkau <nbd@nbd.name>
@@ -61,55 +81,30 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
        }
  }
  
-@@ -990,6 +992,53 @@ bool cfg80211_any_wiphy_oper_chan(struct
+@@ -990,6 +992,28 @@ bool cfg80211_any_wiphy_oper_chan(struct
        return false;
  }
  
-+static void
-+__cfg80211_update_last_available(struct wiphy *wiphy,
-+                                       u32 center_freq,
-+                                       u32 bandwidth)
-+{
-+      struct ieee80211_channel *c;
-+      u32 freq, start_freq, end_freq;
-+
-+      if (bandwidth <= MHZ_TO_KHZ(20))
-+              start_freq = end_freq = center_freq;
-+      else {
-+              start_freq = center_freq - bandwidth / 2 + MHZ_TO_KHZ(10);
-+              end_freq = center_freq + bandwidth / 2 - MHZ_TO_KHZ(10);
-+      }
-+
-+      /*
-+       * 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 (freq = start_freq; freq <= end_freq; freq += MHZ_TO_KHZ(20)) {
-+              c = ieee80211_get_channel_khz(wiphy, freq);
-+              if (!c)
-+                      return;
-+
-+              c->dfs_state_last_available = jiffies;
-+      }
-+}
-+
 +void cfg80211_update_last_available(struct wiphy *wiphy,
 +                                  const struct cfg80211_chan_def *chandef)
 +{
++      struct ieee80211_channel *c;
 +      int width;
 +
++      if (WARN_ON(!cfg80211_chandef_valid(chandef)))
++              return;
++
 +      width = cfg80211_chandef_get_width(chandef);
 +      if (width < 0)
 +              return;
 +
-+      __cfg80211_update_last_available(wiphy, MHZ_TO_KHZ(chandef->center_freq1),
-+                                               width);
-+      if (chandef->width != NL80211_CHAN_WIDTH_80P80)
-+          return;
++      for_each_subchan(chandef, freq, cf) {
++              c = ieee80211_get_channel_khz(wiphy, freq);
++              if (!c)
++                      return;
 +
-+      __cfg80211_update_last_available(wiphy, MHZ_TO_KHZ(chandef->center_freq2),
-+                                               width);
++              c->dfs_state_last_available = jiffies;
++      }
 +}
 +
  static bool cfg80211_chandef_dfs_available(struct wiphy *wiphy,
@@ -151,3 +146,25 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
                        if (time_after_eq(jiffies, timeout)) {
                                c->dfs_state = NL80211_DFS_USABLE;
                                c->dfs_state_entered = jiffies;
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -20967,6 +20967,9 @@ void cfg80211_ch_switch_notify(struct ne
+               break;
+       case NL80211_IFTYPE_AP:
+       case NL80211_IFTYPE_P2P_GO:
++              if (wdev->links[link_id].ap.chandef.chan)
++                      cfg80211_update_last_available(wdev->wiphy,
++                                                     &wdev->links[link_id].ap.chandef);
+               wdev->links[link_id].ap.chandef = *chandef;
+               break;
+       case NL80211_IFTYPE_ADHOC:
+--- a/net/wireless/reg.c
++++ b/net/wireless/reg.c
+@@ -2954,6 +2954,7 @@ static void reg_copy_dfs_chan_state(stru
+           dst_chan->dfs_state == NL80211_DFS_USABLE) {
+               dst_chan->dfs_state = src_chan->dfs_state;
+               dst_chan->dfs_state_entered = src_chan->dfs_state_entered;
++              dst_chan->dfs_state_last_available = src_chan->dfs_state_last_available;
+       }
+ }