diff mbox series

[v2] wifi: cfg80211: skip regulatory checks when the channel is punctured

Message ID 20241125051624.29085-1-quic_kkavita@quicinc.com
State New
Headers show
Series [v2] wifi: cfg80211: skip regulatory checks when the channel is punctured | expand

Commit Message

Kavita Kavita Nov. 25, 2024, 5:16 a.m. UTC
The kernel performs several regulatory checks for AP mode in
nl80211/cfg80211. These checks include radar detection,
verification of whether the sub-channel is disabled, and
an examination to determine if the channel is a DFS channel
(both DFS usable and DFS available). These checks are
performed across a frequency range, examining each sub-channel.

However, these checks are also performed on frequencies that
have been punctured, which should not be examined as they are
not in use.

This leads to the issue where the AP stops because one of
the 20 MHz sub-channels is disabled or radar detected on
the channel, even when the sub-channel is punctured.

To address this issue, add a condition check wherever
regulatory checks exist for AP mode in nl80211/cfg80211.
This check identifies punctured channels and, upon finding
them, skips the regulatory checks for those channels.

Co-developed-by: Manaswini Paluri <quic_mpaluri@quicinc.com>
Signed-off-by: Manaswini Paluri <quic_mpaluri@quicinc.com>
Signed-off-by: Kavita Kavita <quic_kkavita@quicinc.com>
---
Changes in v2:
- Added macro "for_each_subchan" for traversing non punctured channels.
- Skip setting dfs state for punctured channels.
- for center_freq2, pass 0 as punctured bitmap.
---

Comments

Johannes Berg Dec. 4, 2024, 3:35 p.m. UTC | #1
The subject is a bit misleading IMHO - you don't skip all checks when
there's puncturing ...

maybe just say "wifi: cfg80211: skip regulatory for punctured
subchannels"

> However, these checks are also performed on frequencies that
> have been punctured, which should not be examined as they are
> not in use.

I'd argue subchannels are punctured (or really disabled, the whole
channel is punctured ... but we mix that up already), not frequencies

>  static void cfg80211_set_chans_dfs_state(struct wiphy *wiphy, u32 center_freq,
> -					 u32 bandwidth,
> +					 u32 bandwidth, u16 punctured,
>  					 enum nl80211_dfs_state dfs_state)
>  {
>  	struct ieee80211_channel *c;
>  	u32 freq;
> +	int subchan = 0;
>  
>  	for (freq = center_freq - bandwidth/2 + 10;
>  	     freq <= center_freq + bandwidth/2 - 10;
>  	     freq += 20) {
> +		if (punctured & BIT(subchan))
> +			continue;
> +		subchan++;
> 

You never tested this code properly, it's clearly broken.

but anyway - for_each_subchan()?

> +#define for_each_subchan(wiphy, center_freq, bandwidth, punctured,	\
> +			       subchan)						\

I feel like we really should make this work on a chandef, not all these
arguments ... and cover both center_freq1 and center_freq2, because we
have all these duplicate calls like cfg80211_chandef_dfs_cac_time().

> +	for (subchan = ieee80211_next_subchan(wiphy, center_freq, bandwidth,	\
> +					      punctured, NULL);			\

It should be especially easy if you're pulling it out into iterator
functions? Worst case, keep some extra state in the loop, like

for (u32 punctured = chandef->punctured,
     freq = cfg80211_get_start_freq(chandef, 1),
     _cf = 1;
     freq = _cf == 1 ? 20, punctured >>= 1;
     ...)
  if (!(punctured & 1))


I don't really mind a bit more complexity here, if it means we can get
rid of all the functions like cfg80211_get_chans_dfs_required() that get
called twice ...

johannes
Johannes Berg Dec. 19, 2024, 9:01 a.m. UTC | #2
On Thu, 2024-12-19 at 11:35 +0530, Kavita Kavita wrote:
> 
> The for_each_subchan() macro will not work for this. When sub channel is 
> null, it will terminate the loop, but in this case, we should continue 
> checking other sub channels.

Wait, I'm confused by what you're saying here. The for_each_subchan() 
macro should iterate over all enabled (not punctured) subchannels, so
why would it not be applicable here?

johannes
Kavita Kavita Dec. 19, 2024, 10:57 a.m. UTC | #3
On 12/19/2024 2:31 PM, Johannes Berg wrote:
> On Thu, 2024-12-19 at 11:35 +0530, Kavita Kavita wrote:
>>
>> The for_each_subchan() macro will not work for this. When sub channel is
>> null, it will terminate the loop, but in this case, we should continue
>> checking other sub channels.
> 
> Wait, I'm confused by what you're saying here. The for_each_subchan()
> macro should iterate over all enabled (not punctured) subchannels, so
> why would it not be applicable here?
> 

So, In the following regulatory checks: cfg80211_get_chans_dfs_required, 
cfg80211_get_chans_dfs_usable, cfg80211_get_chans_dfs_available, and so on.

When iterating over primary or secondary bandwidth, if we encounter any 
null subchannel, the loop will terminate, and we will not check the 
remaining subchannels. I handled this null subchannel case within the 
macro itself, so when this situation occurs, the loop terminates and 
does not check further subchannels.

However, for cfg80211_set_chans_dfs_state, we should iterate over all 
channels, even if any subchannel is null.

The for_each_subchan() macro is designed to iterate over all enabled 
(not punctured) subchannels.

However, I implemented this macro specifically for the above regulatory 
checks.

I will fix this in the updated patch to make it applicable for all.

Thanks

> johannes
Kavita Kavita Dec. 19, 2024, 11:59 a.m. UTC | #4
On 12/19/2024 4:29 PM, Johannes Berg wrote:
> On Thu, 2024-12-19 at 16:27 +0530, Kavita Kavita wrote:
>>
>> On 12/19/2024 2:31 PM, Johannes Berg wrote:
>>> On Thu, 2024-12-19 at 11:35 +0530, Kavita Kavita wrote:
>>>>
>>>> The for_each_subchan() macro will not work for this. When sub channel is
>>>> null, it will terminate the loop, but in this case, we should continue
>>>> checking other sub channels.
>>>
>>> Wait, I'm confused by what you're saying here. The for_each_subchan()
>>> macro should iterate over all enabled (not punctured) subchannels, so
>>> why would it not be applicable here?
>>>
>>
>> So, In the following regulatory checks: cfg80211_get_chans_dfs_required,
>> cfg80211_get_chans_dfs_usable, cfg80211_get_chans_dfs_available, and so on.
>>
>> When iterating over primary or secondary bandwidth, if we encounter any
>> null subchannel,
> 
> What do you mean by "null subchannel"?

For example,

Please refer below Implementation of cfg80211_get_chans_dfs_required:

for (freq = start_freq; freq <= end_freq; freq += MHZ_TO_KHZ(20)) {
	c = ieee80211_get_channel_khz(wiphy, freq);
	if (!c)
		return -EINVAL;

I handled this above "if" case within the macro itself, so when this 
case occurs, the loop terminates and does not check further subchannels.

Now, please refer below Implementation of cfg80211_set_chans_dfs_state:

for (freq = center_freq - bandwidth/2 + 10;
	freq <= center_freq + bandwidth/2 - 10;
	freq += 20) {
	c = ieee80211_get_channel(wiphy, freq);
	if (!c || !(c->flags & IEEE80211_CHAN_RADAR))
		continue;

However, for cfg80211_set_chans_dfs_state, we should iterate over all 
subchannels, even if any subchannel is null or radar detected.

That is why I haven't used the for_each_subchan macro for 
cfg80211_set_chans_dfs_state.

I will fix this in the updated patch to make the for_each_subchan macro 
applicable for all.

> 
> In all of these cases, if you have e.g. the following 320 MHz channel,
> where "x" indicates removed by puncturing:
> 
> | | | | | | | | | | | | |x|x| | |
> 
> then of course it should iterate over all the non-removed 14
> subchannels, not just the first 12?
> 

Yes, You are correct. The for_each_subchan macro will check all the 14 
non-punctured subchannels.

>> the loop will terminate, and we will not check the
>> remaining subchannels.
> 
> As you write it here, that seems wrong for all cases?
> 
> johannes
diff mbox series

Patch

diff --git a/net/wireless/chan.c b/net/wireless/chan.c
index 40b6375a5de4..bd6b0056ce42 100644
--- a/net/wireless/chan.c
+++ b/net/wireless/chan.c
@@ -583,15 +583,20 @@  cfg80211_chandef_compatible(const struct cfg80211_chan_def *c1,
 EXPORT_SYMBOL(cfg80211_chandef_compatible);
 
 static void cfg80211_set_chans_dfs_state(struct wiphy *wiphy, u32 center_freq,
-					 u32 bandwidth,
+					 u32 bandwidth, u16 punctured,
 					 enum nl80211_dfs_state dfs_state)
 {
 	struct ieee80211_channel *c;
 	u32 freq;
+	int subchan = 0;
 
 	for (freq = center_freq - bandwidth/2 + 10;
 	     freq <= center_freq + bandwidth/2 - 10;
 	     freq += 20) {
+		if (punctured & BIT(subchan))
+			continue;
+		subchan++;
+
 		c = ieee80211_get_channel(wiphy, freq);
 		if (!c || !(c->flags & IEEE80211_CHAN_RADAR))
 			continue;
@@ -615,40 +620,12 @@  void cfg80211_set_dfs_state(struct wiphy *wiphy,
 		return;
 
 	cfg80211_set_chans_dfs_state(wiphy, chandef->center_freq1,
-				     width, dfs_state);
+				     width, chandef->punctured, dfs_state);
 
 	if (!chandef->center_freq2)
 		return;
 	cfg80211_set_chans_dfs_state(wiphy, chandef->center_freq2,
-				     width, dfs_state);
-}
-
-static u32 cfg80211_get_start_freq(u32 center_freq,
-				   u32 bandwidth)
-{
-	u32 start_freq;
-
-	bandwidth = MHZ_TO_KHZ(bandwidth);
-	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(u32 center_freq,
-				 u32 bandwidth)
-{
-	u32 end_freq;
-
-	bandwidth = MHZ_TO_KHZ(bandwidth);
-	if (bandwidth <= MHZ_TO_KHZ(20))
-		end_freq = center_freq;
-	else
-		end_freq = center_freq + bandwidth / 2 - MHZ_TO_KHZ(10);
-
-	return end_freq;
+				     width, 0, dfs_state);
 }
 
 static bool
@@ -727,24 +704,20 @@  static bool cfg80211_dfs_permissive_chan(struct wiphy *wiphy,
 static int cfg80211_get_chans_dfs_required(struct wiphy *wiphy,
 					    u32 center_freq,
 					    u32 bandwidth,
+					    u16 punctured,
 					    enum nl80211_iftype iftype)
 {
-	struct ieee80211_channel *c;
-	u32 freq, start_freq, end_freq;
-
-	start_freq = cfg80211_get_start_freq(center_freq, bandwidth);
-	end_freq = cfg80211_get_end_freq(center_freq, bandwidth);
+	struct ieee80211_channel *subchan;
 
-	for (freq = start_freq; freq <= end_freq; freq += MHZ_TO_KHZ(20)) {
-		c = ieee80211_get_channel_khz(wiphy, freq);
-		if (!c)
-			return -EINVAL;
-
-		if (c->flags & IEEE80211_CHAN_RADAR &&
-		    !cfg80211_dfs_permissive_chan(wiphy, iftype, c))
+	for_each_subchan(wiphy, center_freq, bandwidth, punctured, subchan) {
+		if ((subchan->flags & IEEE80211_CHAN_RADAR) &&
+		    !cfg80211_dfs_permissive_chan(wiphy, iftype, subchan))
 			return 1;
 	}
 
+	if (subchan && IS_ERR(subchan))
+		return PTR_ERR(subchan);
+
 	return 0;
 }
 
@@ -770,7 +743,8 @@  int cfg80211_chandef_dfs_required(struct wiphy *wiphy,
 
 		ret = cfg80211_get_chans_dfs_required(wiphy,
 					ieee80211_chandef_to_khz(chandef),
-					width, iftype);
+					width, chandef->punctured, iftype);
+
 		if (ret < 0)
 			return ret;
 		else if (ret > 0)
@@ -781,7 +755,8 @@  int cfg80211_chandef_dfs_required(struct wiphy *wiphy,
 
 		ret = cfg80211_get_chans_dfs_required(wiphy,
 					MHZ_TO_KHZ(chandef->center_freq2),
-					width, iftype);
+					width, 0, iftype);
+
 		if (ret < 0)
 			return ret;
 		else if (ret > 0)
@@ -808,38 +783,33 @@  EXPORT_SYMBOL(cfg80211_chandef_dfs_required);
 
 static int cfg80211_get_chans_dfs_usable(struct wiphy *wiphy,
 					 u32 center_freq,
-					 u32 bandwidth)
+					 u32 bandwidth, u16 punctured)
 {
-	struct ieee80211_channel *c;
-	u32 freq, start_freq, end_freq;
+	struct ieee80211_channel *subchan;
 	int count = 0;
 
-	start_freq = cfg80211_get_start_freq(center_freq, bandwidth);
-	end_freq = cfg80211_get_end_freq(center_freq, bandwidth);
-
 	/*
 	 * 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 (freq = start_freq; freq <= end_freq; freq += MHZ_TO_KHZ(20)) {
-		c = ieee80211_get_channel_khz(wiphy, freq);
-		if (!c)
-			return -EINVAL;
-
-		if (c->flags & IEEE80211_CHAN_DISABLED)
+	for_each_subchan(wiphy, center_freq, bandwidth, punctured, subchan) {
+		if (subchan->flags & IEEE80211_CHAN_DISABLED)
 			return -EINVAL;
 
-		if (c->flags & IEEE80211_CHAN_RADAR) {
-			if (c->dfs_state == NL80211_DFS_UNAVAILABLE)
+		if (subchan->flags & IEEE80211_CHAN_RADAR) {
+			if (subchan->dfs_state == NL80211_DFS_UNAVAILABLE)
 				return -EINVAL;
 
-			if (c->dfs_state == NL80211_DFS_USABLE)
+			if (subchan->dfs_state == NL80211_DFS_USABLE)
 				count++;
 		}
 	}
 
+	if (subchan && IS_ERR(subchan))
+		return PTR_ERR(subchan);
+
 	return count;
 }
 
@@ -858,7 +828,7 @@  bool cfg80211_chandef_dfs_usable(struct wiphy *wiphy,
 
 	r1 = cfg80211_get_chans_dfs_usable(wiphy,
 					   MHZ_TO_KHZ(chandef->center_freq1),
-					   width);
+					   width, chandef->punctured);
 
 	if (r1 < 0)
 		return false;
@@ -868,7 +838,7 @@  bool cfg80211_chandef_dfs_usable(struct wiphy *wiphy,
 		WARN_ON(!chandef->center_freq2);
 		r2 = cfg80211_get_chans_dfs_usable(wiphy,
 					MHZ_TO_KHZ(chandef->center_freq2),
-					width);
+					width, 0);
 		if (r2 < 0)
 			return false;
 		break;
@@ -1053,37 +1023,32 @@  bool cfg80211_any_wiphy_oper_chan(struct wiphy *wiphy,
 
 static bool cfg80211_get_chans_dfs_available(struct wiphy *wiphy,
 					     u32 center_freq,
-					     u32 bandwidth)
+					     u32 bandwidth, u16 punctured)
 {
-	struct ieee80211_channel *c;
-	u32 freq, start_freq, end_freq;
+	struct ieee80211_channel *subchan;
 	bool dfs_offload;
 
 	dfs_offload = wiphy_ext_feature_isset(wiphy,
 					      NL80211_EXT_FEATURE_DFS_OFFLOAD);
 
-	start_freq = cfg80211_get_start_freq(center_freq, bandwidth);
-	end_freq = cfg80211_get_end_freq(center_freq, bandwidth);
-
 	/*
 	 * 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)
+	for_each_subchan(wiphy, center_freq, bandwidth, punctured, subchan) {
+		if (subchan->flags & IEEE80211_CHAN_DISABLED)
 			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))
+		if ((subchan->flags & IEEE80211_CHAN_RADAR) &&
+		    subchan->dfs_state != NL80211_DFS_AVAILABLE &&
+		    !(subchan->dfs_state == NL80211_DFS_USABLE && dfs_offload))
 			return false;
 	}
 
+	if (subchan && IS_ERR(subchan))
+		return false;
+
 	return true;
 }
 
@@ -1102,7 +1067,7 @@  static bool cfg80211_chandef_dfs_available(struct wiphy *wiphy,
 
 	r = cfg80211_get_chans_dfs_available(wiphy,
 					     MHZ_TO_KHZ(chandef->center_freq1),
-					     width);
+					     width, chandef->punctured);
 
 	/* If any of channels unavailable for cf1 just return */
 	if (!r)
@@ -1113,7 +1078,7 @@  static bool cfg80211_chandef_dfs_available(struct wiphy *wiphy,
 		WARN_ON(!chandef->center_freq2);
 		r = cfg80211_get_chans_dfs_available(wiphy,
 					MHZ_TO_KHZ(chandef->center_freq2),
-					width);
+					width, 0);
 		break;
 	default:
 		WARN_ON(chandef->center_freq2);
@@ -1125,30 +1090,25 @@  static bool cfg80211_chandef_dfs_available(struct wiphy *wiphy,
 
 static unsigned int cfg80211_get_chans_dfs_cac_time(struct wiphy *wiphy,
 						    u32 center_freq,
-						    u32 bandwidth)
+						    u32 bandwidth, u16 punctured)
 {
-	struct ieee80211_channel *c;
-	u32 start_freq, end_freq, freq;
+	struct ieee80211_channel *subchan;
 	unsigned int dfs_cac_ms = 0;
 
-	start_freq = cfg80211_get_start_freq(center_freq, bandwidth);
-	end_freq = cfg80211_get_end_freq(center_freq, bandwidth);
-
-	for (freq = start_freq; freq <= end_freq; freq += MHZ_TO_KHZ(20)) {
-		c = ieee80211_get_channel_khz(wiphy, freq);
-		if (!c)
-			return 0;
-
-		if (c->flags & IEEE80211_CHAN_DISABLED)
+	for_each_subchan(wiphy, center_freq, bandwidth, punctured, subchan) {
+		if (subchan->flags & IEEE80211_CHAN_DISABLED)
 			return 0;
 
-		if (!(c->flags & IEEE80211_CHAN_RADAR))
+		if (!(subchan->flags & IEEE80211_CHAN_RADAR))
 			continue;
 
-		if (c->dfs_cac_ms > dfs_cac_ms)
-			dfs_cac_ms = c->dfs_cac_ms;
+		if (subchan->dfs_cac_ms > dfs_cac_ms)
+			dfs_cac_ms = subchan->dfs_cac_ms;
 	}
 
+	if (subchan && IS_ERR(subchan))
+		return 0;
+
 	return dfs_cac_ms;
 }
 
@@ -1168,14 +1128,14 @@  cfg80211_chandef_dfs_cac_time(struct wiphy *wiphy,
 
 	t1 = cfg80211_get_chans_dfs_cac_time(wiphy,
 					     MHZ_TO_KHZ(chandef->center_freq1),
-					     width);
+					     width, chandef->punctured);
 
 	if (!chandef->center_freq2)
 		return t1;
 
 	t2 = cfg80211_get_chans_dfs_cac_time(wiphy,
 					     MHZ_TO_KHZ(chandef->center_freq2),
-					     width);
+					     width, 0);
 
 	return max(t1, t2);
 }
@@ -1183,25 +1143,21 @@  EXPORT_SYMBOL(cfg80211_chandef_dfs_cac_time);
 
 static bool cfg80211_secondary_chans_ok(struct wiphy *wiphy,
 					u32 center_freq, u32 bandwidth,
-					u32 prohibited_flags,
+					u16 punctured, u32 prohibited_flags,
 					u32 permitting_flags)
 {
-	struct ieee80211_channel *c;
-	u32 freq, start_freq, end_freq;
-
-	start_freq = cfg80211_get_start_freq(center_freq, bandwidth);
-	end_freq = cfg80211_get_end_freq(center_freq, bandwidth);
+	struct ieee80211_channel *subchan;
 
-	for (freq = start_freq; freq <= end_freq; freq += MHZ_TO_KHZ(20)) {
-		c = ieee80211_get_channel_khz(wiphy, freq);
-		if (!c)
-			return false;
-		if (c->flags & permitting_flags)
+	for_each_subchan(wiphy, center_freq, bandwidth, punctured, subchan) {
+		if (subchan->flags & permitting_flags)
 			continue;
-		if (c->flags & prohibited_flags)
+		if (subchan->flags & prohibited_flags)
 			return false;
 	}
 
+	if (subchan && IS_ERR(subchan))
+		return false;
+
 	return true;
 }
 
@@ -1423,7 +1379,8 @@  bool _cfg80211_chandef_usable(struct wiphy *wiphy,
 
 	if (!cfg80211_secondary_chans_ok(wiphy,
 					 ieee80211_chandef_to_khz(chandef),
-					 width, prohibited_flags,
+					 width, chandef->punctured,
+					 prohibited_flags,
 					 permitting_flags))
 		return false;
 
@@ -1431,7 +1388,7 @@  bool _cfg80211_chandef_usable(struct wiphy *wiphy,
 		return true;
 	return cfg80211_secondary_chans_ok(wiphy,
 					   MHZ_TO_KHZ(chandef->center_freq2),
-					   width, prohibited_flags,
+					   width, 0, prohibited_flags,
 					   permitting_flags);
 }
 
diff --git a/net/wireless/core.h b/net/wireless/core.h
index 4c45f994a8c0..f4583e0494d7 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -505,6 +505,18 @@  bool _cfg80211_chandef_usable(struct wiphy *wiphy,
 			      u32 prohibited_flags,
 			      u32 permitting_flags);
 
+#define for_each_subchan(wiphy, center_freq, bandwidth, punctured,	\
+			       subchan)						\
+	for (subchan = ieee80211_next_subchan(wiphy, center_freq, bandwidth,	\
+					      punctured, NULL);			\
+	     subchan && !IS_ERR(subchan);					\
+	     subchan = ieee80211_next_subchan(wiphy, center_freq, bandwidth,	\
+					      punctured, subchan))
+
+struct ieee80211_channel *
+ieee80211_next_subchan(struct wiphy *wiphy, u32 center_freq, u32 bandwidth,
+		       u16 punctured, struct ieee80211_channel *subchan);
+
 static inline unsigned int elapsed_jiffies_msecs(unsigned long start)
 {
 	unsigned long end = jiffies;
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 040d62051eb9..2e2f362b7548 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -184,6 +184,70 @@  struct ieee80211_channel *ieee80211_get_channel_khz(struct wiphy *wiphy,
 }
 EXPORT_SYMBOL(ieee80211_get_channel_khz);
 
+static u32 cfg80211_get_start_freq(u32 center_freq,
+				   u32 bandwidth)
+{
+	u32 start_freq;
+
+	bandwidth = MHZ_TO_KHZ(bandwidth);
+	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(u32 center_freq,
+				 u32 bandwidth)
+{
+	u32 end_freq;
+
+	bandwidth = MHZ_TO_KHZ(bandwidth);
+	if (bandwidth <= MHZ_TO_KHZ(20))
+		end_freq = center_freq;
+	else
+		end_freq = center_freq + bandwidth / 2 - MHZ_TO_KHZ(10);
+
+	return end_freq;
+}
+
+struct ieee80211_channel *ieee80211_next_subchan(struct wiphy *wiphy,
+						 u32 center_freq, u32 bandwidth,
+						 u16 punctured,
+						 struct ieee80211_channel *subchan)
+{
+	struct ieee80211_channel *next_subchan;
+	u32 freq, start_freq, end_freq;
+	int pos;
+
+	start_freq = cfg80211_get_start_freq(center_freq, bandwidth);
+	end_freq = cfg80211_get_end_freq(center_freq, bandwidth);
+
+	if (!subchan) {
+		freq = start_freq;
+	} else {
+		freq = ieee80211_channel_to_khz(subchan);
+		freq += MHZ_TO_KHZ(20);
+	}
+
+	while (freq <= end_freq) {
+		next_subchan = ieee80211_get_channel_khz(wiphy, freq);
+
+		if (!next_subchan)
+			return ERR_PTR(-EINVAL);
+
+		pos = (freq - start_freq) / MHZ_TO_KHZ(20);
+
+		if (!(punctured & BIT(pos)))
+			return next_subchan;
+
+		freq += MHZ_TO_KHZ(20);
+	}
+
+	return NULL;
+}
+
 static void set_mandatory_flags_band(struct ieee80211_supported_band *sband)
 {
 	int i, want;