diff mbox series

[v3,8/8] wifi: mac80211: handle ieee80211_radar_detected() for MLO

Message ID 20240711035147.1896538-9-quic_adisi@quicinc.com
State New
Headers show
Series wifi: cfg80211/mac80211: add DFS support for MLO | expand

Commit Message

Aditya Kumar Singh July 11, 2024, 3:51 a.m. UTC
Currently DFS works under assumption there could be only one channel
context in the hardware. Hence, drivers just calls the function
ieee80211_radar_detected() passing the hardware structure. However, with
MLO, this obviously will not work since number of channel contexts will be
more than one and hence drivers would need to pass the channel information
as well on which the radar is detected.

Hence, in order to support DFS with MLO, do the following changes -
  * Add channel context conf pointer as an argument to the function
    ieee80211_radar_detected(). During MLO, drivers would have to pass on
    which channel context conf radar is detected. Otherwise, drivers could
    just pass NULL.
  * ieee80211_radar_detected() will iterate over all channel contexts
    present and
  	* if channel context conf is passed, only mark that as radar
  	  detected
  	* if NULL is passed, then mark all channel contexts as radar
  	  detected
  	* Then as usual, schedule the radar detected work.
  * In the worker, go over all the contexts again and for all such context
    which is marked with radar detected, add it to a local linked list.
  * Cancel the ongoing CAC.
  * If number of contexts found marked with radar is more than one and if
    the wiphy does not support MLO flag, throw a warning and return.
  * Process the local linked list and call the radar event for each entry.

This would also help in scenarios where there is split phy 5 GHz radio,
which is capable of DFS channels in both lower and upper band. In this
case, simultaneous radars can be detected.

Signed-off-by: Aditya Kumar Singh <quic_adisi@quicinc.com>
---
 drivers/net/wireless/ath/ath10k/debug.c       |  4 +-
 drivers/net/wireless/ath/ath10k/mac.c         |  2 +-
 drivers/net/wireless/ath/ath10k/wmi.c         |  2 +-
 drivers/net/wireless/ath/ath11k/wmi.c         |  2 +-
 drivers/net/wireless/ath/ath12k/wmi.c         |  2 +-
 drivers/net/wireless/ath/ath9k/dfs.c          |  2 +-
 drivers/net/wireless/ath/ath9k/dfs_debug.c    |  2 +-
 .../net/wireless/mediatek/mt76/mt7615/mcu.c   |  2 +-
 .../net/wireless/mediatek/mt76/mt76x02_dfs.c  |  4 +-
 .../net/wireless/mediatek/mt76/mt7915/mcu.c   |  2 +-
 .../net/wireless/mediatek/mt76/mt7996/mcu.c   |  2 +-
 drivers/net/wireless/ti/wl18xx/event.c        |  2 +-
 drivers/net/wireless/virtual/mac80211_hwsim.c |  2 +-
 include/net/mac80211.h                        |  7 +-
 net/mac80211/chan.c                           |  1 +
 net/mac80211/ieee80211_i.h                    |  5 ++
 net/mac80211/util.c                           | 71 ++++++++++++++++---
 17 files changed, 89 insertions(+), 25 deletions(-)

Comments

Johannes Berg Aug. 28, 2024, 10:33 a.m. UTC | #1
On Thu, 2024-07-11 at 09:21 +0530, Aditya Kumar Singh wrote:
> 
>   * In the worker, go over all the contexts again and for all such context
>     which is marked with radar detected, add it to a local linked list.

Why is the local list needed first?

> +++ b/include/net/mac80211.h
> @@ -257,6 +257,7 @@ struct ieee80211_chan_req {
>   *	after RTS/CTS handshake to receive SMPS MIMO transmissions;
>   *	this will always be >= @rx_chains_static.
>   * @radar_enabled: whether radar detection is enabled on this channel.
> + * @radar_detected: whether radar got detected on this channel.
>   * @drv_priv: data area for driver use, will always be aligned to
>   *	sizeof(void *), size is determined in hw information.
>   */
> @@ -269,6 +270,7 @@ struct ieee80211_chanctx_conf {
>  	u8 rx_chains_static, rx_chains_dynamic;
>  
>  	bool radar_enabled;
> +	bool radar_detected;

I'm not sure why you're adding this to the driver visible part of the
chanctx, I don't think that really makes sense since setting it must be
done by mac80211 through the API function to trigger all the work?

> +++ b/net/mac80211/ieee80211_i.h
> @@ -1329,6 +1329,11 @@ enum mac80211_scan_state {
>  
>  DECLARE_STATIC_KEY_FALSE(aql_disable);
>  
> +struct radar_info {
> +	struct list_head list;
> +	struct cfg80211_chan_def chandef;
> +};

If it _really_ is needed this can be local to the C file.

> +	INIT_LIST_HEAD(&radar_info_list);

but really, why?

>  	list_for_each_entry(ctx, &local->chanctx_list, list) {
>  		if (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER)
>  			continue;
>  
> -		num_chanctx++;
> -		chandef = ctx->conf.def;
> +		if (ctx->conf.radar_detected) {
> +			ctx->conf.radar_detected = false;
> +			num_chanctx++;
> +
> +			radar_info = kzalloc(sizeof(*radar_info), GFP_KERNEL);
> +			if (WARN_ON(!radar_info))
> +				continue;

that clearly shouldn't be a WARN_ON,

> +
> +			INIT_LIST_HEAD(&radar_info->list);
> +			radar_info->chandef = ctx->conf.def;
> +			list_add_tail(&radar_info->list, &radar_info_list);

but I also don't really see why you couldn't just call
cfg80211_radar_event() here.

> +	if (num_chanctx > 1) {
> +		/* XXX: multi-channel is not supported yet in case of non-MLO */
> +		if (WARN_ON(!(wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO)))
> +			trigger_event = false;
> +	}

I don't see how that'd happen in the first place?

johannes
Aditya Kumar Singh Aug. 28, 2024, 11:47 a.m. UTC | #2
On 8/28/24 16:03, Johannes Berg wrote:
> On Thu, 2024-07-11 at 09:21 +0530, Aditya Kumar Singh wrote:
>>
>>    * In the worker, go over all the contexts again and for all such context
>>      which is marked with radar detected, add it to a local linked list.
> 
> Why is the local list needed first?

As per the current design and hwsim test cases, 
NL80211_RADAR_CAC_ABORTED event is expected first and then 
NL80211_RADAR_DETECTED event. Now, in the worker, while iterating over 
the contexts and upon finding radar marked contexts, 
cfg80211_radar_event() can not be called immediately. First 
ieee80211_dfs_cac_cancel() needs to be called which in turn will send 
CAC_ABORTED event. Now calling this will delete the channel context, 
hence a local copy is taken and since we could have multiple contexts 
marked with radar, a linked list is used.

> 
>> +++ b/include/net/mac80211.h
>> @@ -257,6 +257,7 @@ struct ieee80211_chan_req {
>>    *	after RTS/CTS handshake to receive SMPS MIMO transmissions;
>>    *	this will always be >= @rx_chains_static.
>>    * @radar_enabled: whether radar detection is enabled on this channel.
>> + * @radar_detected: whether radar got detected on this channel.
>>    * @drv_priv: data area for driver use, will always be aligned to
>>    *	sizeof(void *), size is determined in hw information.
>>    */
>> @@ -269,6 +270,7 @@ struct ieee80211_chanctx_conf {
>>   	u8 rx_chains_static, rx_chains_dynamic;
>>   
>>   	bool radar_enabled;
>> +	bool radar_detected;
> 
> I'm not sure why you're adding this to the driver visible part of the
> chanctx, I don't think that really makes sense since setting it must be
> done by mac80211 through the API function to trigger all the work?

So you want to add this member in struct ieee80211_chanctx? Yeah we can 
move to there as well.

> 
>> +++ b/net/mac80211/ieee80211_i.h
>> @@ -1329,6 +1329,11 @@ enum mac80211_scan_state {
>>   
>>   DECLARE_STATIC_KEY_FALSE(aql_disable);
>>   
>> +struct radar_info {
>> +	struct list_head list;
>> +	struct cfg80211_chan_def chandef;
>> +};
> 
> If it _really_ is needed this can be local to the C file.

Okay sure.

> 
>> +	INIT_LIST_HEAD(&radar_info_list);
> 
> but really, why?

I've answered this above. Please let me know if it is still not clear.

> 
>>   	list_for_each_entry(ctx, &local->chanctx_list, list) {
>>   		if (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER)
>>   			continue;
>>   
>> -		num_chanctx++;
>> -		chandef = ctx->conf.def;
>> +		if (ctx->conf.radar_detected) {
>> +			ctx->conf.radar_detected = false;
>> +			num_chanctx++;
>> +
>> +			radar_info = kzalloc(sizeof(*radar_info), GFP_KERNEL);
>> +			if (WARN_ON(!radar_info))
>> +				continue;
> 
> that clearly shouldn't be a WARN_ON,

If node can not be added, NL event will not be sent to user sapce and 
apt action can not be taken. Will that be fine missing out it without 
any warning?

> 
>> +
>> +			INIT_LIST_HEAD(&radar_info->list);
>> +			radar_info->chandef = ctx->conf.def;
>> +			list_add_tail(&radar_info->list, &radar_info_list);
> 
> but I also don't really see why you couldn't just call
> cfg80211_radar_event() here.

I've answered this as well above. Please let me know if it is still not 
clear.

> 
>> +	if (num_chanctx > 1) {
>> +		/* XXX: multi-channel is not supported yet in case of non-MLO */
>> +		if (WARN_ON(!(wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO)))
>> +			trigger_event = false;
>> +	}
> 
> I don't see how that'd happen in the first place?

This part is keeping the same old code with the new handling with MLO. 
Prior to MLO, if num_chanctx is more than 1, it would throw a warn_on so 
that's why it will throw now as well if MLO is not supported and 
simultaneously two channel contexts are marked as radar detected.

> 
> johannes
Johannes Berg Aug. 28, 2024, 12:04 p.m. UTC | #3
On Wed, 2024-08-28 at 17:17 +0530, Aditya Kumar Singh wrote:
> On 8/28/24 16:03, Johannes Berg wrote:
> > On Thu, 2024-07-11 at 09:21 +0530, Aditya Kumar Singh wrote:
> > > 
> > >    * In the worker, go over all the contexts again and for all such context
> > >      which is marked with radar detected, add it to a local linked list.
> > 
> > Why is the local list needed first?
> 
> As per the current design and hwsim test cases, 
> NL80211_RADAR_CAC_ABORTED event is expected first and then 
> NL80211_RADAR_DETECTED event.

Ouch, OK.

> Now, in the worker, while iterating over 
> the contexts and upon finding radar marked contexts, 
> cfg80211_radar_event() can not be called immediately. First 
> ieee80211_dfs_cac_cancel() needs to be called which in turn will send 
> CAC_ABORTED event. Now calling this will delete the channel context, 
> hence a local copy is taken and since we could have multiple contexts 
> marked with radar, a linked list is used.

Why not do both in ieee80211_dfs_cac_cancel() then? You're effectively
iterating all the chanctx's there anyway, so you could just add

 if (ctx->radar_reported) {
    ctx->radar_reported = false;
    tmp_chandef = ctx->conf.chandef;
    report = true;
 }

 ...
 cac_event()
 if (report)
   cfg80211_radar_event(...)

or so in the loop there?

> > > +			radar_info = kzalloc(sizeof(*radar_info), GFP_KERNEL);
> > > +			if (WARN_ON(!radar_info))
> > > +				continue;
> > 
> > that clearly shouldn't be a WARN_ON,
> 
> If node can not be added, NL event will not be sent to user sapce and 
> apt action can not be taken. Will that be fine missing out it without 
> any warning?

Have you actually _seen_ what happens if the kernel runs out of memory?
Not that it'll actually happen here.

> > > +	if (num_chanctx > 1) {
> > > +		/* XXX: multi-channel is not supported yet in case of non-MLO */
> > > +		if (WARN_ON(!(wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO)))
> > > +			trigger_event = false;
> > > +	}
> > 
> > I don't see how that'd happen in the first place?
> 
> This part is keeping the same old code with the new handling with MLO. 
> Prior to MLO, if num_chanctx is more than 1, it would throw a warn_on so 
> that's why it will throw now as well if MLO is not supported and 
> simultaneously two channel contexts are marked as radar detected.

I don't think it can actually happen, and if keeping the warning around
makes the code this complex I'm not sure I'd want to?

johannes
Johannes Berg Aug. 28, 2024, 12:06 p.m. UTC | #4
Btw,

> +{
> +	struct ieee80211_chanctx *ctx =
> +		container_of(chanctx_conf, struct ieee80211_chanctx,
> +			     conf);
> +	struct ieee80211_chanctx_conf *itr_data =
> +		(struct ieee80211_chanctx_conf *)data;
> +
> +	if (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER)
> +		return;
> +
> +	if (itr_data) {
> +		if (itr_data == chanctx_conf)
> +			chanctx_conf->radar_detected = true;
> +		return;
> +	}
> +
> +	chanctx_conf->radar_detected = true;

It might be better to write the end of this function as

 if (itr_data && &ctx->conf != itr_data)
    return;

 ctx->radar_detected = true;


to avoid the double setting.

johannes
Aditya Kumar Singh Aug. 28, 2024, 12:22 p.m. UTC | #5
On 8/28/24 17:34, Johannes Berg wrote:
> On Wed, 2024-08-28 at 17:17 +0530, Aditya Kumar Singh wrote:
>> On 8/28/24 16:03, Johannes Berg wrote:
>>> On Thu, 2024-07-11 at 09:21 +0530, Aditya Kumar Singh wrote:
>>>>
>>>>     * In the worker, go over all the contexts again and for all such context
>>>>       which is marked with radar detected, add it to a local linked list.
>>>
>>> Why is the local list needed first?
>>
>> As per the current design and hwsim test cases,
>> NL80211_RADAR_CAC_ABORTED event is expected first and then
>> NL80211_RADAR_DETECTED event.
> 
> Ouch, OK.
> 
>> Now, in the worker, while iterating over
>> the contexts and upon finding radar marked contexts,
>> cfg80211_radar_event() can not be called immediately. First
>> ieee80211_dfs_cac_cancel() needs to be called which in turn will send
>> CAC_ABORTED event. Now calling this will delete the channel context,
>> hence a local copy is taken and since we could have multiple contexts
>> marked with radar, a linked list is used.
> 
> Why not do both in ieee80211_dfs_cac_cancel() then? You're effectively
> iterating all the chanctx's there anyway, so you could just add
> 
>   if (ctx->radar_reported) {
>      ctx->radar_reported = false;
>      tmp_chandef = ctx->conf.chandef;
>      report = true;
>   }
> 
>   ...
>   cac_event()
>   if (report)
>     cfg80211_radar_event(...)
> 
> or so in the loop there?

Okay sure. Let me see what I can do here.

> 
>>>> +			radar_info = kzalloc(sizeof(*radar_info), GFP_KERNEL);
>>>> +			if (WARN_ON(!radar_info))
>>>> +				continue;
>>>
>>> that clearly shouldn't be a WARN_ON,
>>
>> If node can not be added, NL event will not be sent to user sapce and
>> apt action can not be taken. Will that be fine missing out it without
>> any warning?
> 
> Have you actually _seen_ what happens if the kernel runs out of memory?
> Not that it'll actually happen here.

:) Okay will remove WARN_ON().

> 
>>>> +	if (num_chanctx > 1) {
>>>> +		/* XXX: multi-channel is not supported yet in case of non-MLO */
>>>> +		if (WARN_ON(!(wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO)))
>>>> +			trigger_event = false;
>>>> +	}
>>>
>>> I don't see how that'd happen in the first place?
>>
>> This part is keeping the same old code with the new handling with MLO.
>> Prior to MLO, if num_chanctx is more than 1, it would throw a warn_on so
>> that's why it will throw now as well if MLO is not supported and
>> simultaneously two channel contexts are marked as radar detected.
> 
> I don't think it can actually happen, and if keeping the warning around
> makes the code this complex I'm not sure I'd want to?

Sure will remove that condition check. Thanks for the suggestions.

> 
> johannes
Aditya Kumar Singh Aug. 28, 2024, 12:23 p.m. UTC | #6
On 8/28/24 17:36, Johannes Berg wrote:
> Btw,
> 
>> +{
>> +	struct ieee80211_chanctx *ctx =
>> +		container_of(chanctx_conf, struct ieee80211_chanctx,
>> +			     conf);
>> +	struct ieee80211_chanctx_conf *itr_data =
>> +		(struct ieee80211_chanctx_conf *)data;
>> +
>> +	if (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER)
>> +		return;
>> +
>> +	if (itr_data) {
>> +		if (itr_data == chanctx_conf)
>> +			chanctx_conf->radar_detected = true;
>> +		return;
>> +	}
>> +
>> +	chanctx_conf->radar_detected = true;
> 
> It might be better to write the end of this function as
> 
>   if (itr_data && &ctx->conf != itr_data)
>      return;
> 
>   ctx->radar_detected = true;
> 
> 
> to avoid the double setting.

Got it. Thanks.

> 
> johannes
>
diff mbox series

Patch

diff --git a/drivers/net/wireless/ath/ath10k/debug.c b/drivers/net/wireless/ath/ath10k/debug.c
index b93a64bf8190..35bfe7232e95 100644
--- a/drivers/net/wireless/ath/ath10k/debug.c
+++ b/drivers/net/wireless/ath/ath10k/debug.c
@@ -3,7 +3,7 @@ 
  * Copyright (c) 2005-2011 Atheros Communications Inc.
  * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
  * Copyright (c) 2018, The Linux Foundation. All rights reserved.
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2022, 2024 Qualcomm Innovation Center, Inc. All rights reserved.
  */
 
 #include <linux/module.h>
@@ -1774,7 +1774,7 @@  static ssize_t ath10k_write_simulate_radar(struct file *file,
 	if (!arvif->is_started)
 		return -EINVAL;
 
-	ieee80211_radar_detected(ar->hw);
+	ieee80211_radar_detected(ar->hw, NULL);
 
 	return count;
 }
diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c
index a5da32e87106..646e1737d4c4 100644
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -1437,7 +1437,7 @@  static void ath10k_recalc_radar_detection(struct ath10k *ar)
 		 * by indicating that radar was detected.
 		 */
 		ath10k_warn(ar, "failed to start CAC: %d\n", ret);
-		ieee80211_radar_detected(ar->hw);
+		ieee80211_radar_detected(ar->hw, NULL);
 	}
 }
 
diff --git a/drivers/net/wireless/ath/ath10k/wmi.c b/drivers/net/wireless/ath/ath10k/wmi.c
index fe2344598364..4861179b2217 100644
--- a/drivers/net/wireless/ath/ath10k/wmi.c
+++ b/drivers/net/wireless/ath/ath10k/wmi.c
@@ -3990,7 +3990,7 @@  static void ath10k_radar_detected(struct ath10k *ar)
 	if (ar->dfs_block_radar_events)
 		ath10k_info(ar, "DFS Radar detected, but ignored as requested\n");
 	else
-		ieee80211_radar_detected(ar->hw);
+		ieee80211_radar_detected(ar->hw, NULL);
 }
 
 static void ath10k_radar_confirmation_work(struct work_struct *work)
diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c
index 38f175dd1557..8839825c22fa 100644
--- a/drivers/net/wireless/ath/ath11k/wmi.c
+++ b/drivers/net/wireless/ath/ath11k/wmi.c
@@ -8356,7 +8356,7 @@  ath11k_wmi_pdev_dfs_radar_detected_event(struct ath11k_base *ab, struct sk_buff
 	if (ar->dfs_block_radar_events)
 		ath11k_info(ab, "DFS Radar detected, but ignored as requested\n");
 	else
-		ieee80211_radar_detected(ar->hw);
+		ieee80211_radar_detected(ar->hw, NULL);
 
 exit:
 	rcu_read_unlock();
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 9f6be557365e..350a87aa93ec 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -6788,7 +6788,7 @@  ath12k_wmi_pdev_dfs_radar_detected_event(struct ath12k_base *ab, struct sk_buff
 	if (ar->dfs_block_radar_events)
 		ath12k_info(ab, "DFS Radar detected, but ignored as requested\n");
 	else
-		ieee80211_radar_detected(ath12k_ar_to_hw(ar));
+		ieee80211_radar_detected(ath12k_ar_to_hw(ar), NULL);
 
 exit:
 	rcu_read_unlock();
diff --git a/drivers/net/wireless/ath/ath9k/dfs.c b/drivers/net/wireless/ath/ath9k/dfs.c
index 11349218bc21..3689e12db9f7 100644
--- a/drivers/net/wireless/ath/ath9k/dfs.c
+++ b/drivers/net/wireless/ath/ath9k/dfs.c
@@ -280,7 +280,7 @@  ath9k_dfs_process_radar_pulse(struct ath_softc *sc, struct pulse_event *pe)
 	if (!pd->add_pulse(pd, pe, NULL))
 		return;
 	DFS_STAT_INC(sc, radar_detected);
-	ieee80211_radar_detected(sc->hw);
+	ieee80211_radar_detected(sc->hw, NULL);
 }
 
 /*
diff --git a/drivers/net/wireless/ath/ath9k/dfs_debug.c b/drivers/net/wireless/ath/ath9k/dfs_debug.c
index 8e18e9b4ef48..426caa057396 100644
--- a/drivers/net/wireless/ath/ath9k/dfs_debug.c
+++ b/drivers/net/wireless/ath/ath9k/dfs_debug.c
@@ -116,7 +116,7 @@  static ssize_t write_file_simulate_radar(struct file *file,
 {
 	struct ath_softc *sc = file->private_data;
 
-	ieee80211_radar_detected(sc->hw);
+	ieee80211_radar_detected(sc->hw, NULL);
 
 	return count;
 }
diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c
index d50d967828be..53c8ebe179dd 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c
@@ -394,7 +394,7 @@  mt7615_mcu_rx_radar_detected(struct mt7615_dev *dev, struct sk_buff *skb)
 	if (mt76_phy_dfs_state(mphy) < MT_DFS_STATE_CAC)
 		return;
 
-	ieee80211_radar_detected(mphy->hw);
+	ieee80211_radar_detected(mphy->hw, NULL);
 	dev->hw_pattern++;
 }
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_dfs.c b/drivers/net/wireless/mediatek/mt76/mt76x02_dfs.c
index 024a5c0a5a57..7a07636d09c6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76x02_dfs.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76x02_dfs.c
@@ -630,7 +630,7 @@  static void mt76x02_dfs_tasklet(struct tasklet_struct *t)
 		radar_detected = mt76x02_dfs_check_detection(dev);
 		if (radar_detected) {
 			/* sw detector rx radar pattern */
-			ieee80211_radar_detected(dev->mt76.hw);
+			ieee80211_radar_detected(dev->mt76.hw, NULL);
 			mt76x02_dfs_detector_reset(dev);
 
 			return;
@@ -658,7 +658,7 @@  static void mt76x02_dfs_tasklet(struct tasklet_struct *t)
 
 		/* hw detector rx radar pattern */
 		dfs_pd->stats[i].hw_pattern++;
-		ieee80211_radar_detected(dev->mt76.hw);
+		ieee80211_radar_detected(dev->mt76.hw, NULL);
 		mt76x02_dfs_detector_reset(dev);
 
 		return;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
index 2185cd24e2e1..5f180851060d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
@@ -293,7 +293,7 @@  mt7915_mcu_rx_radar_detected(struct mt7915_dev *dev, struct sk_buff *skb)
 						&dev->rdd2_chandef,
 						GFP_ATOMIC);
 	else
-		ieee80211_radar_detected(mphy->hw);
+		ieee80211_radar_detected(mphy->hw, NULL);
 	dev->hw_pattern++;
 }
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index 2e4fa9f48dfb..f8921546a5e6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -371,7 +371,7 @@  mt7996_mcu_rx_radar_detected(struct mt7996_dev *dev, struct sk_buff *skb)
 						&dev->rdd2_chandef,
 						GFP_ATOMIC);
 	else
-		ieee80211_radar_detected(mphy->hw);
+		ieee80211_radar_detected(mphy->hw, NULL);
 	dev->hw_pattern++;
 }
 
diff --git a/drivers/net/wireless/ti/wl18xx/event.c b/drivers/net/wireless/ti/wl18xx/event.c
index 34d95f458e1a..a9f090e15cbb 100644
--- a/drivers/net/wireless/ti/wl18xx/event.c
+++ b/drivers/net/wireless/ti/wl18xx/event.c
@@ -142,7 +142,7 @@  int wl18xx_process_mailbox_events(struct wl1271 *wl)
 			    wl18xx_radar_type_decode(mbox->radar_type));
 
 		if (!wl->radar_debug_mode)
-			ieee80211_radar_detected(wl->hw);
+			ieee80211_radar_detected(wl->hw, NULL);
 	}
 
 	if (vector & PERIODIC_SCAN_REPORT_EVENT_ID) {
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.c b/drivers/net/wireless/virtual/mac80211_hwsim.c
index 714e1f04b0cb..d08fff9d26cf 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim.c
@@ -1146,7 +1146,7 @@  static int hwsim_write_simulate_radar(void *dat, u64 val)
 {
 	struct mac80211_hwsim_data *data = dat;
 
-	ieee80211_radar_detected(data->hw);
+	ieee80211_radar_detected(data->hw, NULL);
 
 	return 0;
 }
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index e78ccbe38d6d..a4f2fcf35ce1 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -257,6 +257,7 @@  struct ieee80211_chan_req {
  *	after RTS/CTS handshake to receive SMPS MIMO transmissions;
  *	this will always be >= @rx_chains_static.
  * @radar_enabled: whether radar detection is enabled on this channel.
+ * @radar_detected: whether radar got detected on this channel.
  * @drv_priv: data area for driver use, will always be aligned to
  *	sizeof(void *), size is determined in hw information.
  */
@@ -269,6 +270,7 @@  struct ieee80211_chanctx_conf {
 	u8 rx_chains_static, rx_chains_dynamic;
 
 	bool radar_enabled;
+	bool radar_detected;
 
 	u8 drv_priv[] __aligned(sizeof(void *));
 };
@@ -6716,8 +6718,11 @@  void ieee80211_cqm_beacon_loss_notify(struct ieee80211_vif *vif, gfp_t gfp);
  * ieee80211_radar_detected - inform that a radar was detected
  *
  * @hw: pointer as obtained from ieee80211_alloc_hw()
+ * @chanctx_conf: Channel context on which radar is detected. Mandatory to
+ *	pass a valid pointer during MLO. For non-MLO %NULL can be passed
  */
-void ieee80211_radar_detected(struct ieee80211_hw *hw);
+void ieee80211_radar_detected(struct ieee80211_hw *hw,
+			      struct ieee80211_chanctx_conf *chanctx_conf);
 
 /**
  * ieee80211_chswitch_done - Complete channel switch process
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index e8567723e94d..60417357dc91 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -681,6 +681,7 @@  ieee80211_alloc_chanctx(struct ieee80211_local *local,
 	ctx->mode = mode;
 	ctx->conf.radar_enabled = false;
 	ctx->conf.radio_idx = radio_idx;
+	ctx->conf.radar_detected = false;
 	_ieee80211_recalc_chanctx_min_def(local, ctx, NULL, false);
 
 	return ctx;
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index a2d7c7ee7ce3..7323f81e3d3c 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1329,6 +1329,11 @@  enum mac80211_scan_state {
 
 DECLARE_STATIC_KEY_FALSE(aql_disable);
 
+struct radar_info {
+	struct list_head list;
+	struct cfg80211_chan_def chandef;
+};
+
 struct ieee80211_local {
 	/* embed the driver visible part.
 	 * don't cast (use the static inlines below), but we keep
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 59dde0393f81..ecda005edcd2 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -3495,35 +3495,88 @@  void ieee80211_dfs_radar_detected_work(struct wiphy *wiphy,
 {
 	struct ieee80211_local *local =
 		container_of(work, struct ieee80211_local, radar_detected_work);
-	struct cfg80211_chan_def chandef = local->hw.conf.chandef;
+	struct radar_info *radar_info, *temp;
+	struct list_head radar_info_list;
 	struct ieee80211_chanctx *ctx;
+	bool trigger_event = true;
 	int num_chanctx = 0;
 
 	lockdep_assert_wiphy(local->hw.wiphy);
 
+	INIT_LIST_HEAD(&radar_info_list);
+
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
 		if (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER)
 			continue;
 
-		num_chanctx++;
-		chandef = ctx->conf.def;
+		if (ctx->conf.radar_detected) {
+			ctx->conf.radar_detected = false;
+			num_chanctx++;
+
+			radar_info = kzalloc(sizeof(*radar_info), GFP_KERNEL);
+			if (WARN_ON(!radar_info))
+				continue;
+
+			INIT_LIST_HEAD(&radar_info->list);
+			radar_info->chandef = ctx->conf.def;
+			list_add_tail(&radar_info->list, &radar_info_list);
+		}
 	}
 
 	ieee80211_dfs_cac_cancel(local);
 
-	if (num_chanctx > 1)
-		/* XXX: multi-channel is not supported yet */
-		WARN_ON(1);
-	else
-		cfg80211_radar_event(local->hw.wiphy, &chandef, GFP_KERNEL);
+	if (num_chanctx > 1) {
+		/* XXX: multi-channel is not supported yet in case of non-MLO */
+		if (WARN_ON(!(wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO)))
+			trigger_event = false;
+	}
+
+	/* this will clear the nodes which were created and added above.
+	 * trigger_event decides whether to trigger the radar event or not
+	 */
+	list_for_each_entry_safe(radar_info, temp, &radar_info_list,
+				 list) {
+		if (trigger_event)
+			cfg80211_radar_event(local->hw.wiphy,
+					     &radar_info->chandef,
+					     GFP_KERNEL);
+		kfree(radar_info);
+	}
 }
 
-void ieee80211_radar_detected(struct ieee80211_hw *hw)
+static void
+ieee80211_radar_mark_chan_ctx_iterator(struct ieee80211_hw *hw,
+				       struct ieee80211_chanctx_conf *chanctx_conf,
+				       void *data)
+{
+	struct ieee80211_chanctx *ctx =
+		container_of(chanctx_conf, struct ieee80211_chanctx,
+			     conf);
+	struct ieee80211_chanctx_conf *itr_data =
+		(struct ieee80211_chanctx_conf *)data;
+
+	if (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER)
+		return;
+
+	if (itr_data) {
+		if (itr_data == chanctx_conf)
+			chanctx_conf->radar_detected = true;
+		return;
+	}
+
+	chanctx_conf->radar_detected = true;
+}
+
+void ieee80211_radar_detected(struct ieee80211_hw *hw,
+			      struct ieee80211_chanctx_conf *chanctx_conf)
 {
 	struct ieee80211_local *local = hw_to_local(hw);
 
 	trace_api_radar_detected(local);
 
+	ieee80211_iter_chan_contexts_atomic(hw, ieee80211_radar_mark_chan_ctx_iterator,
+					    chanctx_conf);
+
 	wiphy_work_queue(hw->wiphy, &local->radar_detected_work);
 }
 EXPORT_SYMBOL(ieee80211_radar_detected);