[openwrt/openwrt] mac80211: backport upstream DFS fixes

LEDE Commits lede-commits at lists.infradead.org
Sat Oct 5 00:46:06 PDT 2024


nbd pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/ec61ccc0d3d53a6cd1ac4920e19e78a2a2dbf67c

commit ec61ccc0d3d53a6cd1ac4920e19e78a2a2dbf67c
Author: Felix Fietkau <nbd at nbd.name>
AuthorDate: Sat Oct 5 09:35:49 2024 +0200

    mac80211: backport upstream DFS fixes
    
    Mostly MLO related
    
    Signed-off-by: Felix Fietkau <nbd at nbd.name>
---
 .../kernel/ath10k-ct/patches/010-api_update.patch  |  31 ++
 ...211-Set-correct-chandef-when-starting-CAC.patch |  64 +++
 ...-mac80211-move-radar-detect-work-to-sdata.patch | 136 +++++++
 ...11-remove-label-usage-in-ieee80211_start_.patch |  50 +++
 ...unlink-rdev_end_cac-trace-event-from-wiph.patch |  42 ++
 ...11-move-DFS-related-members-to-links-in-w.patch | 309 +++++++++++++++
 .../337-wifi-cfg80211-handle-DFS-per-link.patch    | 435 +++++++++++++++++++++
 .../338-wifi-mac80211-handle-DFS-per-link.patch    | 134 +++++++
 ...80211-mac80211-use-proper-link-ID-for-DFS.patch | 168 ++++++++
 ...11-handle-ieee80211_radar_detected-for-ML.patch | 360 +++++++++++++++++
 package/kernel/mt76/patches/100-api_update.patch   |  53 +++
 .../mwlwifi/patches/005-mac80211_update.patch      |  38 ++
 12 files changed, 1820 insertions(+)

diff --git a/package/kernel/ath10k-ct/patches/010-api_update.patch b/package/kernel/ath10k-ct/patches/010-api_update.patch
index c0d2574748..f70d8eb460 100644
--- a/package/kernel/ath10k-ct/patches/010-api_update.patch
+++ b/package/kernel/ath10k-ct/patches/010-api_update.patch
@@ -1,5 +1,14 @@
 --- a/ath10k-6.9/mac.c
 +++ b/ath10k-6.9/mac.c
+@@ -1675,7 +1675,7 @@ static void ath10k_recalc_radar_detectio
+ 		 * 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);
+ 	}
+ }
+ 
 @@ -6238,7 +6238,7 @@ err:
  	return ret;
  }
@@ -9,3 +18,25 @@
  {
  	struct ath10k *ar = hw->priv;
  	u32 opt;
+--- a/ath10k-6.9/debug.c
++++ b/ath10k-6.9/debug.c
+@@ -3319,7 +3319,7 @@ static ssize_t ath10k_write_simulate_rad
+ 	if (!arvif->is_started)
+ 		return -EINVAL;
+ 
+-	ieee80211_radar_detected(ar->hw);
++	ieee80211_radar_detected(ar->hw, NULL);
+ 
+ 	return count;
+ }
+--- a/ath10k-6.9/wmi.c
++++ b/ath10k-6.9/wmi.c
+@@ -4402,7 +4402,7 @@ static void ath10k_radar_detected(struct
+ 	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/package/kernel/mac80211/patches/subsys/332-wifi-cfg80211-Set-correct-chandef-when-starting-CAC.patch b/package/kernel/mac80211/patches/subsys/332-wifi-cfg80211-Set-correct-chandef-when-starting-CAC.patch
new file mode 100644
index 0000000000..72a3510aac
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/332-wifi-cfg80211-Set-correct-chandef-when-starting-CAC.patch
@@ -0,0 +1,64 @@
+From: Issam Hamdi <ih at simonwunderlich.de>
+Date: Fri, 16 Aug 2024 16:24:18 +0200
+Subject: [PATCH] wifi: cfg80211: Set correct chandef when starting CAC
+
+When starting CAC in a mode other than AP mode, it return a
+"WARNING: CPU: 0 PID: 63 at cfg80211_chandef_dfs_usable+0x20/0xaf [cfg80211]"
+caused by the chandef.chan being null at the end of CAC.
+
+Solution: Ensure the channel definition is set for the different modes
+when starting CAC to avoid getting a NULL 'chan' at the end of CAC.
+
+ Call Trace:
+  ? show_regs.part.0+0x14/0x16
+  ? __warn+0x67/0xc0
+  ? cfg80211_chandef_dfs_usable+0x20/0xaf [cfg80211]
+  ? report_bug+0xa7/0x130
+  ? exc_overflow+0x30/0x30
+  ? handle_bug+0x27/0x50
+  ? exc_invalid_op+0x18/0x60
+  ? handle_exception+0xf6/0xf6
+  ? exc_overflow+0x30/0x30
+  ? cfg80211_chandef_dfs_usable+0x20/0xaf [cfg80211]
+  ? exc_overflow+0x30/0x30
+  ? cfg80211_chandef_dfs_usable+0x20/0xaf [cfg80211]
+  ? regulatory_propagate_dfs_state.cold+0x1b/0x4c [cfg80211]
+  ? cfg80211_propagate_cac_done_wk+0x1a/0x30 [cfg80211]
+  ? process_one_work+0x165/0x280
+  ? worker_thread+0x120/0x3f0
+  ? kthread+0xc2/0xf0
+  ? process_one_work+0x280/0x280
+  ? kthread_complete_and_exit+0x20/0x20
+  ? ret_from_fork+0x19/0x24
+
+Reported-by: Kretschmer Mathias <mathias.kretschmer at fit.fraunhofer.de>
+Signed-off-by: Issam Hamdi <ih at simonwunderlich.de>
+Link: https://patch.msgid.link/20240816142418.3381951-1-ih@simonwunderlich.de
+[shorten subject, remove OCB, reorder cases to match previous list]
+Signed-off-by: Johannes Berg <johannes.berg at intel.com>
+---
+
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -10144,7 +10144,20 @@ static int nl80211_start_radar_detection
+ 
+ 	err = rdev_start_radar_detection(rdev, dev, &chandef, cac_time_ms);
+ 	if (!err) {
+-		wdev->links[0].ap.chandef = chandef;
++		switch (wdev->iftype) {
++		case NL80211_IFTYPE_AP:
++		case NL80211_IFTYPE_P2P_GO:
++			wdev->links[0].ap.chandef = chandef;
++			break;
++		case NL80211_IFTYPE_ADHOC:
++			wdev->u.ibss.chandef = chandef;
++			break;
++		case NL80211_IFTYPE_MESH_POINT:
++			wdev->u.mesh.chandef = chandef;
++			break;
++		default:
++			break;
++		}
+ 		wdev->cac_started = true;
+ 		wdev->cac_start_time = jiffies;
+ 		wdev->cac_time_ms = cac_time_ms;
diff --git a/package/kernel/mac80211/patches/subsys/333-Revert-wifi-mac80211-move-radar-detect-work-to-sdata.patch b/package/kernel/mac80211/patches/subsys/333-Revert-wifi-mac80211-move-radar-detect-work-to-sdata.patch
new file mode 100644
index 0000000000..d12df8f53e
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/333-Revert-wifi-mac80211-move-radar-detect-work-to-sdata.patch
@@ -0,0 +1,136 @@
+From: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Date: Fri, 6 Sep 2024 12:14:19 +0530
+Subject: [PATCH] Revert "wifi: mac80211: move radar detect work to sdata"
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This reverts commit ce9e660ef32e ("wifi: mac80211: move radar detect work to sdata").
+
+To enable radar detection with MLO, it’s essential to handle it on a
+per-link basis. This is because when using MLO, multiple links may already
+be active and beaconing. In this scenario, another link should be able to
+initiate a radar detection. Also, if underlying links are associated with
+different hardware devices but grouped together for MLO, they could
+potentially start radar detection simultaneously. Therefore, it makes
+sense to manage radar detection settings separately for each link by moving
+them back to a per-link data structure.
+
+Signed-off-by: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-2-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg at intel.com>
+---
+
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -1658,7 +1658,7 @@ static int ieee80211_stop_ap(struct wiph
+ 
+ 	if (sdata->wdev.cac_started) {
+ 		chandef = link_conf->chanreq.oper;
+-		wiphy_delayed_work_cancel(wiphy, &sdata->dfs_cac_timer_work);
++		wiphy_delayed_work_cancel(wiphy, &link->dfs_cac_timer_work);
+ 		cfg80211_cac_event(sdata->dev, &chandef,
+ 				   NL80211_RADAR_CAC_ABORTED,
+ 				   GFP_KERNEL);
+@@ -3482,7 +3482,7 @@ static int ieee80211_start_radar_detecti
+ 	if (err)
+ 		goto out_unlock;
+ 
+-	wiphy_delayed_work_queue(wiphy, &sdata->dfs_cac_timer_work,
++	wiphy_delayed_work_queue(wiphy, &sdata->deflink.dfs_cac_timer_work,
+ 				 msecs_to_jiffies(cac_time_ms));
+ 
+  out_unlock:
+@@ -3499,7 +3499,7 @@ static void ieee80211_end_cac(struct wip
+ 
+ 	list_for_each_entry(sdata, &local->interfaces, list) {
+ 		wiphy_delayed_work_cancel(wiphy,
+-					  &sdata->dfs_cac_timer_work);
++					  &sdata->deflink.dfs_cac_timer_work);
+ 
+ 		if (sdata->wdev.cac_started) {
+ 			ieee80211_link_release_channel(&sdata->deflink);
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -1069,6 +1069,7 @@ struct ieee80211_link_data {
+ 	int ap_power_level; /* in dBm */
+ 
+ 	bool radar_required;
++	struct wiphy_delayed_work dfs_cac_timer_work;
+ 
+ 	union {
+ 		struct ieee80211_link_data_managed mgd;
+@@ -1167,8 +1168,6 @@ struct ieee80211_sub_if_data {
+ 	struct ieee80211_link_data deflink;
+ 	struct ieee80211_link_data __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
+ 
+-	struct wiphy_delayed_work dfs_cac_timer_work;
+-
+ 	/* for ieee80211_set_active_links_async() */
+ 	struct wiphy_work activate_links_work;
+ 	u16 desired_active_links;
+--- a/net/mac80211/iface.c
++++ b/net/mac80211/iface.c
+@@ -551,7 +551,7 @@ static void ieee80211_do_stop(struct iee
+ 	wiphy_work_cancel(local->hw.wiphy,
+ 			  &sdata->deflink.color_change_finalize_work);
+ 	wiphy_delayed_work_cancel(local->hw.wiphy,
+-				  &sdata->dfs_cac_timer_work);
++				  &sdata->deflink.dfs_cac_timer_work);
+ 
+ 	if (sdata->wdev.cac_started) {
+ 		chandef = sdata->vif.bss_conf.chanreq.oper;
+@@ -1744,8 +1744,6 @@ static void ieee80211_setup_sdata(struct
+ 	wiphy_work_init(&sdata->work, ieee80211_iface_work);
+ 	wiphy_work_init(&sdata->activate_links_work,
+ 			ieee80211_activate_links_work);
+-	wiphy_delayed_work_init(&sdata->dfs_cac_timer_work,
+-				ieee80211_dfs_cac_timer_work);
+ 
+ 	switch (type) {
+ 	case NL80211_IFTYPE_P2P_GO:
+--- a/net/mac80211/link.c
++++ b/net/mac80211/link.c
+@@ -45,6 +45,8 @@ void ieee80211_link_init(struct ieee8021
+ 			  ieee80211_color_collision_detection_work);
+ 	INIT_LIST_HEAD(&link->assigned_chanctx_list);
+ 	INIT_LIST_HEAD(&link->reserved_chanctx_list);
++	wiphy_delayed_work_init(&link->dfs_cac_timer_work,
++				ieee80211_dfs_cac_timer_work);
+ 
+ 	if (!deflink) {
+ 		switch (sdata->vif.type) {
+--- a/net/mac80211/mlme.c
++++ b/net/mac80211/mlme.c
+@@ -3031,15 +3031,16 @@ void ieee80211_dynamic_ps_timer(struct t
+ 
+ void ieee80211_dfs_cac_timer_work(struct wiphy *wiphy, struct wiphy_work *work)
+ {
+-	struct ieee80211_sub_if_data *sdata =
+-		container_of(work, struct ieee80211_sub_if_data,
++	struct ieee80211_link_data *link =
++		container_of(work, struct ieee80211_link_data,
+ 			     dfs_cac_timer_work.work);
+-	struct cfg80211_chan_def chandef = sdata->vif.bss_conf.chanreq.oper;
++	struct cfg80211_chan_def chandef = link->conf->chanreq.oper;
++	struct ieee80211_sub_if_data *sdata = link->sdata;
+ 
+ 	lockdep_assert_wiphy(sdata->local->hw.wiphy);
+ 
+ 	if (sdata->wdev.cac_started) {
+-		ieee80211_link_release_channel(&sdata->deflink);
++		ieee80211_link_release_channel(link);
+ 		cfg80211_cac_event(sdata->dev, &chandef,
+ 				   NL80211_RADAR_CAC_FINISHED,
+ 				   GFP_KERNEL);
+--- a/net/mac80211/util.c
++++ b/net/mac80211/util.c
+@@ -3460,7 +3460,7 @@ void ieee80211_dfs_cac_cancel(struct iee
+ 
+ 	list_for_each_entry(sdata, &local->interfaces, list) {
+ 		wiphy_delayed_work_cancel(local->hw.wiphy,
+-					  &sdata->dfs_cac_timer_work);
++					  &sdata->deflink.dfs_cac_timer_work);
+ 
+ 		if (sdata->wdev.cac_started) {
+ 			chandef = sdata->vif.bss_conf.chanreq.oper;
diff --git a/package/kernel/mac80211/patches/subsys/334-wifi-mac80211-remove-label-usage-in-ieee80211_start_.patch b/package/kernel/mac80211/patches/subsys/334-wifi-mac80211-remove-label-usage-in-ieee80211_start_.patch
new file mode 100644
index 0000000000..329b645462
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/334-wifi-mac80211-remove-label-usage-in-ieee80211_start_.patch
@@ -0,0 +1,50 @@
+From: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Date: Fri, 6 Sep 2024 12:14:20 +0530
+Subject: [PATCH] wifi: mac80211: remove label usage in
+ ieee80211_start_radar_detection()
+
+After locks rework [1], ieee80211_start_radar_detection() function is no
+longer acquiring any lock as such explicitly. Hence, it is not unlocking
+anything as well. However, label "out_unlock" is still used which creates
+confusion. Also, now there is no need of goto label as such.
+
+Get rid of the goto logic and use direct return statements.
+
+[1]: https://lore.kernel.org/all/20230828135928.b1c6efffe9ad.I4aec875e25abc9ef0b5ad1e70b5747fd483fbd3c@changeid/
+
+Signed-off-by: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-3-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg at intel.com>
+---
+
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -3468,10 +3468,8 @@ static int ieee80211_start_radar_detecti
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+-	if (!list_empty(&local->roc_list) || local->scanning) {
+-		err = -EBUSY;
+-		goto out_unlock;
+-	}
++	if (!list_empty(&local->roc_list) || local->scanning)
++		return -EBUSY;
+ 
+ 	/* whatever, but channel contexts should not complain about that one */
+ 	sdata->deflink.smps_mode = IEEE80211_SMPS_OFF;
+@@ -3480,13 +3478,12 @@ static int ieee80211_start_radar_detecti
+ 	err = ieee80211_link_use_channel(&sdata->deflink, &chanreq,
+ 					 IEEE80211_CHANCTX_SHARED);
+ 	if (err)
+-		goto out_unlock;
++		return err;
+ 
+ 	wiphy_delayed_work_queue(wiphy, &sdata->deflink.dfs_cac_timer_work,
+ 				 msecs_to_jiffies(cac_time_ms));
+ 
+- out_unlock:
+-	return err;
++	return 0;
+ }
+ 
+ static void ieee80211_end_cac(struct wiphy *wiphy,
diff --git a/package/kernel/mac80211/patches/subsys/335-wifi-trace-unlink-rdev_end_cac-trace-event-from-wiph.patch b/package/kernel/mac80211/patches/subsys/335-wifi-trace-unlink-rdev_end_cac-trace-event-from-wiph.patch
new file mode 100644
index 0000000000..98bc0c26fd
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/335-wifi-trace-unlink-rdev_end_cac-trace-event-from-wiph.patch
@@ -0,0 +1,42 @@
+From: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Date: Fri, 6 Sep 2024 12:14:21 +0530
+Subject: [PATCH] wifi: trace: unlink rdev_end_cac trace event from
+ wiphy_netdev_evt class
+
+rdev_end_cac trace event is linked with wiphy_netdev_evt event class.
+There is no option to pass link ID currently to wiphy_netdev_evt class.
+A subsequent change would pass link ID to rdev_end_cac event and hence
+it can no longer derive the event class from wiphy_netdev_evt.
+
+Therefore, unlink rdev_end_cac event from wiphy_netdev_evt and define it's
+own independent trace event. Link ID would be passed in subsequent change.
+
+Signed-off-by: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-4-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg at intel.com>
+---
+
+--- a/net/wireless/trace.h
++++ b/net/wireless/trace.h
+@@ -805,9 +805,18 @@ DEFINE_EVENT(wiphy_netdev_evt, rdev_flus
+ 	TP_ARGS(wiphy, netdev)
+ );
+ 
+-DEFINE_EVENT(wiphy_netdev_evt, rdev_end_cac,
+-	     TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
+-	     TP_ARGS(wiphy, netdev)
++TRACE_EVENT(rdev_end_cac,
++	TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
++	TP_ARGS(wiphy, netdev),
++	TP_STRUCT__entry(
++		WIPHY_ENTRY
++		NETDEV_ENTRY
++	),
++	TP_fast_assign(
++		WIPHY_ASSIGN;
++		NETDEV_ASSIGN;
++	),
++	TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT, WIPHY_PR_ARG, NETDEV_PR_ARG)
+ );
+ 
+ DECLARE_EVENT_CLASS(station_add_change,
diff --git a/package/kernel/mac80211/patches/subsys/336-wifi-cfg80211-move-DFS-related-members-to-links-in-w.patch b/package/kernel/mac80211/patches/subsys/336-wifi-cfg80211-move-DFS-related-members-to-links-in-w.patch
new file mode 100644
index 0000000000..95dac04de5
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/336-wifi-cfg80211-move-DFS-related-members-to-links-in-w.patch
@@ -0,0 +1,309 @@
+From: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Date: Fri, 6 Sep 2024 12:14:22 +0530
+Subject: [PATCH] wifi: cfg80211: move DFS related members to links[] in
+ wireless_dev
+
+A few members related to DFS handling are currently under per wireless
+device data structure. However, in order to support DFS with MLO, there is
+a need to have them on a per-link manner.
+
+Hence, as a preliminary step, move members cac_started, cac_start_time
+and cac_time_ms to be on a per-link basis.
+
+Since currently, link ID is not known at all places, use default value of
+0 for now.
+
+Signed-off-by: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-5-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg at intel.com>
+---
+
+--- a/drivers/net/wireless/marvell/mwifiex/11h.c
++++ b/drivers/net/wireless/marvell/mwifiex/11h.c
+@@ -117,7 +117,7 @@ void mwifiex_dfs_cac_work_queue(struct w
+ 				     dfs_cac_work);
+ 
+ 	chandef = priv->dfs_chandef;
+-	if (priv->wdev.cac_started) {
++	if (priv->wdev.links[0].cac_started) {
+ 		mwifiex_dbg(priv->adapter, MSG,
+ 			    "CAC timer finished; No radar detected\n");
+ 		cfg80211_cac_event(priv->netdev, &chandef,
+@@ -174,7 +174,7 @@ int mwifiex_stop_radar_detection(struct
+  */
+ void mwifiex_abort_cac(struct mwifiex_private *priv)
+ {
+-	if (priv->wdev.cac_started) {
++	if (priv->wdev.links[0].cac_started) {
+ 		if (mwifiex_stop_radar_detection(priv, &priv->dfs_chandef))
+ 			mwifiex_dbg(priv->adapter, ERROR,
+ 				    "failed to stop CAC in FW\n");
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -1880,7 +1880,7 @@ mwifiex_cfg80211_del_station(struct wiph
+ 	struct mwifiex_sta_node *sta_node;
+ 	u8 deauth_mac[ETH_ALEN];
+ 
+-	if (!priv->bss_started && priv->wdev.cac_started) {
++	if (!priv->bss_started && priv->wdev.links[0].cac_started) {
+ 		mwifiex_dbg(priv->adapter, INFO, "%s: abort CAC!\n", __func__);
+ 		mwifiex_abort_cac(priv);
+ 	}
+@@ -3978,7 +3978,7 @@ mwifiex_cfg80211_channel_switch(struct w
+ 		return -EBUSY;
+ 	}
+ 
+-	if (priv->wdev.cac_started)
++	if (priv->wdev.links[0].cac_started)
+ 		return -EBUSY;
+ 
+ 	if (cfg80211_chandef_identical(&params->chandef,
+--- a/drivers/net/wireless/quantenna/qtnfmac/event.c
++++ b/drivers/net/wireless/quantenna/qtnfmac/event.c
+@@ -520,21 +520,21 @@ static int qtnf_event_handle_radar(struc
+ 		cfg80211_radar_event(wiphy, &chandef, GFP_KERNEL);
+ 		break;
+ 	case QLINK_RADAR_CAC_FINISHED:
+-		if (!vif->wdev.cac_started)
++		if (!vif->wdev.links[0].cac_started)
+ 			break;
+ 
+ 		cfg80211_cac_event(vif->netdev, &chandef,
+ 				   NL80211_RADAR_CAC_FINISHED, GFP_KERNEL);
+ 		break;
+ 	case QLINK_RADAR_CAC_ABORTED:
+-		if (!vif->wdev.cac_started)
++		if (!vif->wdev.links[0].cac_started)
+ 			break;
+ 
+ 		cfg80211_cac_event(vif->netdev, &chandef,
+ 				   NL80211_RADAR_CAC_ABORTED, GFP_KERNEL);
+ 		break;
+ 	case QLINK_RADAR_CAC_STARTED:
+-		if (vif->wdev.cac_started)
++		if (vif->wdev.links[0].cac_started)
+ 			break;
+ 
+ 		if (!wiphy_ext_feature_isset(wiphy,
+--- a/include/net/cfg80211.h
++++ b/include/net/cfg80211.h
+@@ -6198,9 +6198,6 @@ enum ieee80211_ap_reg_power {
+  * @address: The address for this device, valid only if @netdev is %NULL
+  * @is_running: true if this is a non-netdev device that has been started, e.g.
+  *	the P2P Device.
+- * @cac_started: true if DFS channel availability check has been started
+- * @cac_start_time: timestamp (jiffies) when the dfs state was entered.
+- * @cac_time_ms: CAC time in ms
+  * @ps: powersave mode is enabled
+  * @ps_timeout: dynamic powersave timeout
+  * @ap_unexpected_nlportid: (private) netlink port ID of application
+@@ -6224,6 +6221,11 @@ enum ieee80211_ap_reg_power {
+  *	unprotected beacon report
+  * @links: array of %IEEE80211_MLD_MAX_NUM_LINKS elements containing @addr
+  *	@ap and @client for each link
++ * @links[].cac_started: true if DFS channel availability check has been
++ *	started
++ * @links[].cac_start_time: timestamp (jiffies) when the dfs state was
++ *	entered.
++ * @links[].cac_time_ms: CAC time in ms
+  * @valid_links: bitmap describing what elements of @links are valid
+  */
+ struct wireless_dev {
+@@ -6265,11 +6267,6 @@ struct wireless_dev {
+ 	u32 owner_nlportid;
+ 	bool nl_owner_dead;
+ 
+-	/* FIXME: need to rework radar detection for MLO */
+-	bool cac_started;
+-	unsigned long cac_start_time;
+-	unsigned int cac_time_ms;
+-
+ #ifdef CPTCFG_CFG80211_WEXT
+ 	/* wext data */
+ 	struct {
+@@ -6336,6 +6333,10 @@ struct wireless_dev {
+ 				struct cfg80211_internal_bss *current_bss;
+ 			} client;
+ 		};
++
++		bool cac_started;
++		unsigned long cac_start_time;
++		unsigned int cac_time_ms;
+ 	} links[IEEE80211_MLD_MAX_NUM_LINKS];
+ 	u16 valid_links;
+ };
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -1656,7 +1656,7 @@ static int ieee80211_stop_ap(struct wiph
+ 	ieee80211_link_info_change_notify(sdata, link,
+ 					  BSS_CHANGED_BEACON_ENABLED);
+ 
+-	if (sdata->wdev.cac_started) {
++	if (sdata->wdev.links[0].cac_started) {
+ 		chandef = link_conf->chanreq.oper;
+ 		wiphy_delayed_work_cancel(wiphy, &link->dfs_cac_timer_work);
+ 		cfg80211_cac_event(sdata->dev, &chandef,
+@@ -3498,9 +3498,9 @@ static void ieee80211_end_cac(struct wip
+ 		wiphy_delayed_work_cancel(wiphy,
+ 					  &sdata->deflink.dfs_cac_timer_work);
+ 
+-		if (sdata->wdev.cac_started) {
++		if (sdata->wdev.links[0].cac_started) {
+ 			ieee80211_link_release_channel(&sdata->deflink);
+-			sdata->wdev.cac_started = false;
++			sdata->wdev.links[0].cac_started = false;
+ 		}
+ 	}
+ }
+@@ -3955,7 +3955,7 @@ __ieee80211_channel_switch(struct wiphy
+ 	if (!list_empty(&local->roc_list) || local->scanning)
+ 		return -EBUSY;
+ 
+-	if (sdata->wdev.cac_started)
++	if (sdata->wdev.links[0].cac_started)
+ 		return -EBUSY;
+ 
+ 	if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS))
+--- a/net/mac80211/iface.c
++++ b/net/mac80211/iface.c
+@@ -553,7 +553,7 @@ static void ieee80211_do_stop(struct iee
+ 	wiphy_delayed_work_cancel(local->hw.wiphy,
+ 				  &sdata->deflink.dfs_cac_timer_work);
+ 
+-	if (sdata->wdev.cac_started) {
++	if (sdata->wdev.links[0].cac_started) {
+ 		chandef = sdata->vif.bss_conf.chanreq.oper;
+ 		WARN_ON(local->suspended);
+ 		ieee80211_link_release_channel(&sdata->deflink);
+--- a/net/mac80211/mlme.c
++++ b/net/mac80211/mlme.c
+@@ -3039,7 +3039,7 @@ void ieee80211_dfs_cac_timer_work(struct
+ 
+ 	lockdep_assert_wiphy(sdata->local->hw.wiphy);
+ 
+-	if (sdata->wdev.cac_started) {
++	if (sdata->wdev.links[0].cac_started) {
+ 		ieee80211_link_release_channel(link);
+ 		cfg80211_cac_event(sdata->dev, &chandef,
+ 				   NL80211_RADAR_CAC_FINISHED,
+--- a/net/mac80211/scan.c
++++ b/net/mac80211/scan.c
+@@ -585,7 +585,7 @@ static bool __ieee80211_can_leave_ch(str
+ 		return false;
+ 
+ 	list_for_each_entry(sdata_iter, &local->interfaces, list) {
+-		if (sdata_iter->wdev.cac_started)
++		if (sdata_iter->wdev.links[0].cac_started)
+ 			return false;
+ 	}
+ 
+--- a/net/mac80211/util.c
++++ b/net/mac80211/util.c
+@@ -3462,7 +3462,7 @@ void ieee80211_dfs_cac_cancel(struct iee
+ 		wiphy_delayed_work_cancel(local->hw.wiphy,
+ 					  &sdata->deflink.dfs_cac_timer_work);
+ 
+-		if (sdata->wdev.cac_started) {
++		if (sdata->wdev.links[0].cac_started) {
+ 			chandef = sdata->vif.bss_conf.chanreq.oper;
+ 			ieee80211_link_release_channel(&sdata->deflink);
+ 			cfg80211_cac_event(sdata->dev,
+--- a/net/wireless/ibss.c
++++ b/net/wireless/ibss.c
+@@ -94,7 +94,7 @@ int __cfg80211_join_ibss(struct cfg80211
+ 
+ 	lockdep_assert_held(&rdev->wiphy.mtx);
+ 
+-	if (wdev->cac_started)
++	if (wdev->links[0].cac_started)
+ 		return -EBUSY;
+ 
+ 	if (wdev->u.ibss.ssid_len)
+--- a/net/wireless/mesh.c
++++ b/net/wireless/mesh.c
+@@ -127,7 +127,7 @@ int __cfg80211_join_mesh(struct cfg80211
+ 	if (!rdev->ops->join_mesh)
+ 		return -EOPNOTSUPP;
+ 
+-	if (wdev->cac_started)
++	if (wdev->links[0].cac_started)
+ 		return -EBUSY;
+ 
+ 	if (!setup->chandef.chan) {
+--- a/net/wireless/mlme.c
++++ b/net/wireless/mlme.c
+@@ -1124,13 +1124,14 @@ void cfg80211_cac_event(struct net_devic
+ 
+ 	trace_cfg80211_cac_event(netdev, event);
+ 
+-	if (WARN_ON(!wdev->cac_started && event != NL80211_RADAR_CAC_STARTED))
++	if (WARN_ON(!wdev->links[0].cac_started &&
++		    event != NL80211_RADAR_CAC_STARTED))
+ 		return;
+ 
+ 	switch (event) {
+ 	case NL80211_RADAR_CAC_FINISHED:
+-		timeout = wdev->cac_start_time +
+-			  msecs_to_jiffies(wdev->cac_time_ms);
++		timeout = wdev->links[0].cac_start_time +
++			  msecs_to_jiffies(wdev->links[0].cac_time_ms);
+ 		WARN_ON(!time_after_eq(jiffies, timeout));
+ 		cfg80211_set_dfs_state(wiphy, chandef, NL80211_DFS_AVAILABLE);
+ 		memcpy(&rdev->cac_done_chandef, chandef,
+@@ -1139,10 +1140,10 @@ void cfg80211_cac_event(struct net_devic
+ 		cfg80211_sched_dfs_chan_update(rdev);
+ 		fallthrough;
+ 	case NL80211_RADAR_CAC_ABORTED:
+-		wdev->cac_started = false;
++		wdev->links[0].cac_started = false;
+ 		break;
+ 	case NL80211_RADAR_CAC_STARTED:
+-		wdev->cac_started = true;
++		wdev->links[0].cac_started = true;
+ 		break;
+ 	default:
+ 		WARN_ON(1);
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -6066,7 +6066,7 @@ static int nl80211_start_ap(struct sk_bu
+ 	if (!rdev->ops->start_ap)
+ 		return -EOPNOTSUPP;
+ 
+-	if (wdev->cac_started)
++	if (wdev->links[0].cac_started)
+ 		return -EBUSY;
+ 
+ 	if (wdev->links[link_id].ap.beacon_interval)
+@@ -10122,7 +10122,7 @@ static int nl80211_start_radar_detection
+ 		goto unlock;
+ 	}
+ 
+-	if (cfg80211_beaconing_iface_active(wdev) || wdev->cac_started) {
++	if (cfg80211_beaconing_iface_active(wdev) || wdev->links[0].cac_started) {
+ 		err = -EBUSY;
+ 		goto unlock;
+ 	}
+@@ -10158,9 +10158,9 @@ static int nl80211_start_radar_detection
+ 		default:
+ 			break;
+ 		}
+-		wdev->cac_started = true;
+-		wdev->cac_start_time = jiffies;
+-		wdev->cac_time_ms = cac_time_ms;
++		wdev->links[0].cac_started = true;
++		wdev->links[0].cac_start_time = jiffies;
++		wdev->links[0].cac_time_ms = cac_time_ms;
+ 	}
+ unlock:
+ 	wiphy_unlock(wiphy);
+--- a/net/wireless/reg.c
++++ b/net/wireless/reg.c
+@@ -4241,7 +4241,7 @@ static void cfg80211_check_and_end_cac(s
+ 	list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+ 		struct cfg80211_chan_def *chandef;
+ 
+-		if (!wdev->cac_started)
++		if (!wdev->links[0].cac_started)
+ 			continue;
+ 
+ 		/* FIXME: radar detection is tied to link 0 for now */
diff --git a/package/kernel/mac80211/patches/subsys/337-wifi-cfg80211-handle-DFS-per-link.patch b/package/kernel/mac80211/patches/subsys/337-wifi-cfg80211-handle-DFS-per-link.patch
new file mode 100644
index 0000000000..a4e0333642
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/337-wifi-cfg80211-handle-DFS-per-link.patch
@@ -0,0 +1,435 @@
+From: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Date: Fri, 6 Sep 2024 12:14:23 +0530
+Subject: [PATCH] wifi: cfg80211: handle DFS per link
+
+Currently, during starting a radar detection, no link id information is
+parsed and passed down. In order to support starting radar detection
+during Multi Link Operation, it is required to pass link id as well.
+
+Add changes to first parse and then pass link id in the start radar
+detection path.
+
+Additionally, update notification APIs to allow drivers/mac80211 to
+pass the link ID.
+
+However, everything is handled at link 0 only until all API's are ready to
+handle it per link.
+
+Signed-off-by: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-6-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg at intel.com>
+---
+
+--- a/drivers/net/wireless/marvell/mwifiex/11h.c
++++ b/drivers/net/wireless/marvell/mwifiex/11h.c
+@@ -122,7 +122,7 @@ void mwifiex_dfs_cac_work_queue(struct w
+ 			    "CAC timer finished; No radar detected\n");
+ 		cfg80211_cac_event(priv->netdev, &chandef,
+ 				   NL80211_RADAR_CAC_FINISHED,
+-				   GFP_KERNEL);
++				   GFP_KERNEL, 0);
+ 	}
+ }
+ 
+@@ -182,7 +182,8 @@ void mwifiex_abort_cac(struct mwifiex_pr
+ 			    "Aborting delayed work for CAC.\n");
+ 		cancel_delayed_work_sync(&priv->dfs_cac_work);
+ 		cfg80211_cac_event(priv->netdev, &priv->dfs_chandef,
+-				   NL80211_RADAR_CAC_ABORTED, GFP_KERNEL);
++				   NL80211_RADAR_CAC_ABORTED, GFP_KERNEL,
++				   0);
+ 	}
+ }
+ 
+@@ -221,7 +222,7 @@ int mwifiex_11h_handle_chanrpt_ready(str
+ 				cfg80211_cac_event(priv->netdev,
+ 						   &priv->dfs_chandef,
+ 						   NL80211_RADAR_DETECTED,
+-						   GFP_KERNEL);
++						   GFP_KERNEL, 0);
+ 			}
+ 			break;
+ 		default:
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -4145,7 +4145,7 @@ static int
+ mwifiex_cfg80211_start_radar_detection(struct wiphy *wiphy,
+ 				       struct net_device *dev,
+ 				       struct cfg80211_chan_def *chandef,
+-				       u32 cac_time_ms)
++				       u32 cac_time_ms, int link_id)
+ {
+ 	struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
+ 	struct mwifiex_radar_params radar_params;
+--- a/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
++++ b/drivers/net/wireless/quantenna/qtnfmac/cfg80211.c
+@@ -837,7 +837,7 @@ static int qtnf_channel_switch(struct wi
+ static int qtnf_start_radar_detection(struct wiphy *wiphy,
+ 				      struct net_device *ndev,
+ 				      struct cfg80211_chan_def *chandef,
+-				      u32 cac_time_ms)
++				      u32 cac_time_ms, int link_id)
+ {
+ 	struct qtnf_vif *vif = qtnf_netdev_get_priv(ndev);
+ 	int ret;
+--- a/drivers/net/wireless/quantenna/qtnfmac/event.c
++++ b/drivers/net/wireless/quantenna/qtnfmac/event.c
+@@ -524,14 +524,14 @@ static int qtnf_event_handle_radar(struc
+ 			break;
+ 
+ 		cfg80211_cac_event(vif->netdev, &chandef,
+-				   NL80211_RADAR_CAC_FINISHED, GFP_KERNEL);
++				   NL80211_RADAR_CAC_FINISHED, GFP_KERNEL, 0);
+ 		break;
+ 	case QLINK_RADAR_CAC_ABORTED:
+ 		if (!vif->wdev.links[0].cac_started)
+ 			break;
+ 
+ 		cfg80211_cac_event(vif->netdev, &chandef,
+-				   NL80211_RADAR_CAC_ABORTED, GFP_KERNEL);
++				   NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0);
+ 		break;
+ 	case QLINK_RADAR_CAC_STARTED:
+ 		if (vif->wdev.links[0].cac_started)
+@@ -542,7 +542,7 @@ static int qtnf_event_handle_radar(struc
+ 			break;
+ 
+ 		cfg80211_cac_event(vif->netdev, &chandef,
+-				   NL80211_RADAR_CAC_STARTED, GFP_KERNEL);
++				   NL80211_RADAR_CAC_STARTED, GFP_KERNEL, 0);
+ 		break;
+ 	default:
+ 		pr_warn("%s: unhandled radar event %u\n",
+--- a/include/net/cfg80211.h
++++ b/include/net/cfg80211.h
+@@ -4841,9 +4841,9 @@ struct cfg80211_ops {
+ 	int	(*start_radar_detection)(struct wiphy *wiphy,
+ 					 struct net_device *dev,
+ 					 struct cfg80211_chan_def *chandef,
+-					 u32 cac_time_ms);
++					 u32 cac_time_ms, int link_id);
+ 	void	(*end_cac)(struct wiphy *wiphy,
+-				struct net_device *dev);
++			   struct net_device *dev, unsigned int link_id);
+ 	int	(*update_ft_ies)(struct wiphy *wiphy, struct net_device *dev,
+ 				 struct cfg80211_update_ft_ies_params *ftie);
+ 	int	(*crit_proto_start)(struct wiphy *wiphy,
+@@ -8745,6 +8745,7 @@ void cfg80211_sta_opmode_change_notify(s
+  * @chandef: chandef for the current channel
+  * @event: type of event
+  * @gfp: context flags
++ * @link_id: valid link_id for MLO operation or 0 otherwise.
+  *
+  * This function is called when a Channel availability check (CAC) is finished
+  * or aborted. This must be called to notify the completion of a CAC process,
+@@ -8752,7 +8753,8 @@ void cfg80211_sta_opmode_change_notify(s
+  */
+ void cfg80211_cac_event(struct net_device *netdev,
+ 			const struct cfg80211_chan_def *chandef,
+-			enum nl80211_radar_event event, gfp_t gfp);
++			enum nl80211_radar_event event, gfp_t gfp,
++			unsigned int link_id);
+ 
+ /**
+  * cfg80211_background_cac_abort - Channel Availability Check offchan abort event
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -1661,7 +1661,7 @@ static int ieee80211_stop_ap(struct wiph
+ 		wiphy_delayed_work_cancel(wiphy, &link->dfs_cac_timer_work);
+ 		cfg80211_cac_event(sdata->dev, &chandef,
+ 				   NL80211_RADAR_CAC_ABORTED,
+-				   GFP_KERNEL);
++				   GFP_KERNEL, 0);
+ 	}
+ 
+ 	drv_stop_ap(sdata->local, sdata, link_conf);
+@@ -3459,7 +3459,7 @@ static int ieee80211_set_bitrate_mask(st
+ static int ieee80211_start_radar_detection(struct wiphy *wiphy,
+ 					   struct net_device *dev,
+ 					   struct cfg80211_chan_def *chandef,
+-					   u32 cac_time_ms)
++					   u32 cac_time_ms, int link_id)
+ {
+ 	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+ 	struct ieee80211_chan_req chanreq = { .oper = *chandef };
+@@ -3487,7 +3487,7 @@ static int ieee80211_start_radar_detecti
+ }
+ 
+ static void ieee80211_end_cac(struct wiphy *wiphy,
+-			      struct net_device *dev)
++			      struct net_device *dev, unsigned int link_id)
+ {
+ 	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+ 	struct ieee80211_local *local = sdata->local;
+--- a/net/mac80211/iface.c
++++ b/net/mac80211/iface.c
+@@ -559,7 +559,7 @@ static void ieee80211_do_stop(struct iee
+ 		ieee80211_link_release_channel(&sdata->deflink);
+ 		cfg80211_cac_event(sdata->dev, &chandef,
+ 				   NL80211_RADAR_CAC_ABORTED,
+-				   GFP_KERNEL);
++				   GFP_KERNEL, 0);
+ 	}
+ 
+ 	if (sdata->vif.type == NL80211_IFTYPE_AP) {
+--- a/net/mac80211/mlme.c
++++ b/net/mac80211/mlme.c
+@@ -3043,7 +3043,7 @@ void ieee80211_dfs_cac_timer_work(struct
+ 		ieee80211_link_release_channel(link);
+ 		cfg80211_cac_event(sdata->dev, &chandef,
+ 				   NL80211_RADAR_CAC_FINISHED,
+-				   GFP_KERNEL);
++				   GFP_KERNEL, 0);
+ 	}
+ }
+ 
+--- a/net/mac80211/util.c
++++ b/net/mac80211/util.c
+@@ -3468,7 +3468,7 @@ void ieee80211_dfs_cac_cancel(struct iee
+ 			cfg80211_cac_event(sdata->dev,
+ 					   &chandef,
+ 					   NL80211_RADAR_CAC_ABORTED,
+-					   GFP_KERNEL);
++					   GFP_KERNEL, 0);
+ 		}
+ 	}
+ }
+--- a/net/wireless/mlme.c
++++ b/net/wireless/mlme.c
+@@ -1111,18 +1111,19 @@ EXPORT_SYMBOL(__cfg80211_radar_event);
+ 
+ void cfg80211_cac_event(struct net_device *netdev,
+ 			const struct cfg80211_chan_def *chandef,
+-			enum nl80211_radar_event event, gfp_t gfp)
++			enum nl80211_radar_event event, gfp_t gfp,
++			unsigned int link_id)
+ {
+ 	struct wireless_dev *wdev = netdev->ieee80211_ptr;
+ 	struct wiphy *wiphy = wdev->wiphy;
+ 	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+ 	unsigned long timeout;
+ 
+-	/* not yet supported */
+-	if (wdev->valid_links)
++	if (WARN_ON(wdev->valid_links &&
++		    !(wdev->valid_links & BIT(link_id))))
+ 		return;
+ 
+-	trace_cfg80211_cac_event(netdev, event);
++	trace_cfg80211_cac_event(netdev, event, link_id);
+ 
+ 	if (WARN_ON(!wdev->links[0].cac_started &&
+ 		    event != NL80211_RADAR_CAC_STARTED))
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -10122,7 +10122,20 @@ static int nl80211_start_radar_detection
+ 		goto unlock;
+ 	}
+ 
+-	if (cfg80211_beaconing_iface_active(wdev) || wdev->links[0].cac_started) {
++	if (cfg80211_beaconing_iface_active(wdev)) {
++		/* During MLO other link(s) can beacon, only the current link
++		 * can not already beacon
++		 */
++		if (wdev->valid_links &&
++		    !wdev->links[0].ap.beacon_interval) {
++			/* nothing */
++		} else {
++			err = -EBUSY;
++			goto unlock;
++		}
++	}
++
++	if (wdev->links[0].cac_started) {
+ 		err = -EBUSY;
+ 		goto unlock;
+ 	}
+@@ -10142,7 +10155,8 @@ static int nl80211_start_radar_detection
+ 	if (WARN_ON(!cac_time_ms))
+ 		cac_time_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
+ 
+-	err = rdev_start_radar_detection(rdev, dev, &chandef, cac_time_ms);
++	err = rdev_start_radar_detection(rdev, dev, &chandef, cac_time_ms,
++					 0);
+ 	if (!err) {
+ 		switch (wdev->iftype) {
+ 		case NL80211_IFTYPE_AP:
+@@ -16512,10 +16526,10 @@ nl80211_set_ttlm(struct sk_buff *skb, st
+ 	SELECTOR(__sel, NETDEV_UP_NOTMX,		\
+ 		 NL80211_FLAG_NEED_NETDEV_UP |		\
+ 		 NL80211_FLAG_NO_WIPHY_MTX)		\
+-	SELECTOR(__sel, NETDEV_UP_NOTMX_NOMLO,		\
++	SELECTOR(__sel, NETDEV_UP_NOTMX_MLO,		\
+ 		 NL80211_FLAG_NEED_NETDEV_UP |		\
+ 		 NL80211_FLAG_NO_WIPHY_MTX |		\
+-		 NL80211_FLAG_MLO_UNSUPPORTED)		\
++		 NL80211_FLAG_MLO_VALID_LINK_ID)	\
+ 	SELECTOR(__sel, NETDEV_UP_CLEAR,		\
+ 		 NL80211_FLAG_NEED_NETDEV_UP |		\
+ 		 NL80211_FLAG_CLEAR_SKB)		\
+@@ -17410,7 +17424,7 @@ static const struct genl_small_ops nl802
+ 		.flags = GENL_UNS_ADMIN_PERM,
+ 		.internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
+ 					 NL80211_FLAG_NO_WIPHY_MTX |
+-					 NL80211_FLAG_MLO_UNSUPPORTED),
++					 NL80211_FLAG_MLO_VALID_LINK_ID),
+ 	},
+ 	{
+ 		.cmd = NL80211_CMD_GET_PROTOCOL_FEATURES,
+--- a/net/wireless/rdev-ops.h
++++ b/net/wireless/rdev-ops.h
+@@ -1200,26 +1200,27 @@ static inline int
+ rdev_start_radar_detection(struct cfg80211_registered_device *rdev,
+ 			   struct net_device *dev,
+ 			   struct cfg80211_chan_def *chandef,
+-			   u32 cac_time_ms)
++			   u32 cac_time_ms, int link_id)
+ {
+ 	int ret = -EOPNOTSUPP;
+ 
+ 	trace_rdev_start_radar_detection(&rdev->wiphy, dev, chandef,
+-					 cac_time_ms);
++					 cac_time_ms, link_id);
+ 	if (rdev->ops->start_radar_detection)
+ 		ret = rdev->ops->start_radar_detection(&rdev->wiphy, dev,
+-						       chandef, cac_time_ms);
++						       chandef, cac_time_ms,
++						       link_id);
+ 	trace_rdev_return_int(&rdev->wiphy, ret);
+ 	return ret;
+ }
+ 
+ static inline void
+ rdev_end_cac(struct cfg80211_registered_device *rdev,
+-	     struct net_device *dev)
++	     struct net_device *dev, unsigned int link_id)
+ {
+-	trace_rdev_end_cac(&rdev->wiphy, dev);
++	trace_rdev_end_cac(&rdev->wiphy, dev, link_id);
+ 	if (rdev->ops->end_cac)
+-		rdev->ops->end_cac(&rdev->wiphy, dev);
++		rdev->ops->end_cac(&rdev->wiphy, dev, link_id);
+ 	trace_rdev_return_void(&rdev->wiphy);
+ }
+ 
+--- a/net/wireless/reg.c
++++ b/net/wireless/reg.c
+@@ -4229,6 +4229,8 @@ EXPORT_SYMBOL(regulatory_pre_cac_allowed
+ static void cfg80211_check_and_end_cac(struct cfg80211_registered_device *rdev)
+ {
+ 	struct wireless_dev *wdev;
++	unsigned int link_id;
++
+ 	/* If we finished CAC or received radar, we should end any
+ 	 * CAC running on the same channels.
+ 	 * the check !cfg80211_chandef_dfs_usable contain 2 options:
+@@ -4241,16 +4243,17 @@ static void cfg80211_check_and_end_cac(s
+ 	list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+ 		struct cfg80211_chan_def *chandef;
+ 
+-		if (!wdev->links[0].cac_started)
+-			continue;
++		for_each_valid_link(wdev, link_id) {
++			if (!wdev->links[link_id].cac_started)
++				continue;
+ 
+-		/* FIXME: radar detection is tied to link 0 for now */
+-		chandef = wdev_chandef(wdev, 0);
+-		if (!chandef)
+-			continue;
++			chandef = wdev_chandef(wdev, link_id);
++			if (!chandef)
++				continue;
+ 
+-		if (!cfg80211_chandef_dfs_usable(&rdev->wiphy, chandef))
+-			rdev_end_cac(rdev, wdev->netdev);
++			if (!cfg80211_chandef_dfs_usable(&rdev->wiphy, chandef))
++				rdev_end_cac(rdev, wdev->netdev, link_id);
++		}
+ 	}
+ }
+ 
+--- a/net/wireless/trace.h
++++ b/net/wireless/trace.h
+@@ -806,17 +806,21 @@ DEFINE_EVENT(wiphy_netdev_evt, rdev_flus
+ );
+ 
+ TRACE_EVENT(rdev_end_cac,
+-	TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
+-	TP_ARGS(wiphy, netdev),
++	TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
++		 unsigned int link_id),
++	TP_ARGS(wiphy, netdev, link_id),
+ 	TP_STRUCT__entry(
+ 		WIPHY_ENTRY
+ 		NETDEV_ENTRY
++		__field(unsigned int, link_id)
+ 	),
+ 	TP_fast_assign(
+ 		WIPHY_ASSIGN;
+ 		NETDEV_ASSIGN;
++		__entry->link_id = link_id;
+ 	),
+-	TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT, WIPHY_PR_ARG, NETDEV_PR_ARG)
++	TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", link_id: %d",
++		  WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->link_id)
+ );
+ 
+ DECLARE_EVENT_CLASS(station_add_change,
+@@ -2661,24 +2665,26 @@ TRACE_EVENT(rdev_external_auth,
+ TRACE_EVENT(rdev_start_radar_detection,
+ 	TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+ 		 struct cfg80211_chan_def *chandef,
+-		 u32 cac_time_ms),
+-	TP_ARGS(wiphy, netdev, chandef, cac_time_ms),
++		 u32 cac_time_ms, int link_id),
++	TP_ARGS(wiphy, netdev, chandef, cac_time_ms, link_id),
+ 	TP_STRUCT__entry(
+ 		WIPHY_ENTRY
+ 		NETDEV_ENTRY
+ 		CHAN_DEF_ENTRY
+ 		__field(u32, cac_time_ms)
++		__field(int, link_id)
+ 	),
+ 	TP_fast_assign(
+ 		WIPHY_ASSIGN;
+ 		NETDEV_ASSIGN;
+ 		CHAN_DEF_ASSIGN(chandef);
+ 		__entry->cac_time_ms = cac_time_ms;
++		__entry->link_id = link_id;
+ 	),
+ 	TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " CHAN_DEF_PR_FMT
+-		  ", cac_time_ms=%u",
++		  ", cac_time_ms=%u, link_id=%d",
+ 		  WIPHY_PR_ARG, NETDEV_PR_ARG, CHAN_DEF_PR_ARG,
+-		  __entry->cac_time_ms)
++		  __entry->cac_time_ms, __entry->link_id)
+ );
+ 
+ TRACE_EVENT(rdev_set_mcast_rate,
+@@ -3492,18 +3498,21 @@ TRACE_EVENT(cfg80211_radar_event,
+ );
+ 
+ TRACE_EVENT(cfg80211_cac_event,
+-	TP_PROTO(struct net_device *netdev, enum nl80211_radar_event evt),
+-	TP_ARGS(netdev, evt),
++	TP_PROTO(struct net_device *netdev, enum nl80211_radar_event evt,
++		 unsigned int link_id),
++	TP_ARGS(netdev, evt, link_id),
+ 	TP_STRUCT__entry(
+ 		NETDEV_ENTRY
+ 		__field(enum nl80211_radar_event, evt)
++		__field(unsigned int, link_id)
+ 	),
+ 	TP_fast_assign(
+ 		NETDEV_ASSIGN;
+ 		__entry->evt = evt;
++		__entry->link_id = link_id;
+ 	),
+-	TP_printk(NETDEV_PR_FMT ",  event: %d",
+-		  NETDEV_PR_ARG, __entry->evt)
++	TP_printk(NETDEV_PR_FMT ",  event: %d, link_id=%u",
++		  NETDEV_PR_ARG, __entry->evt, __entry->link_id)
+ );
+ 
+ DECLARE_EVENT_CLASS(cfg80211_rx_evt,
diff --git a/package/kernel/mac80211/patches/subsys/338-wifi-mac80211-handle-DFS-per-link.patch b/package/kernel/mac80211/patches/subsys/338-wifi-mac80211-handle-DFS-per-link.patch
new file mode 100644
index 0000000000..388c0575c8
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/338-wifi-mac80211-handle-DFS-per-link.patch
@@ -0,0 +1,134 @@
+From: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Date: Fri, 6 Sep 2024 12:14:24 +0530
+Subject: [PATCH] wifi: mac80211: handle DFS per link
+
+In order to support DFS with MLO, handle the link ID now passed from
+cfg80211, adjust the code to do everything per link and call the
+notifications to cfg80211 correctly.
+
+Signed-off-by: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-7-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg at intel.com>
+---
+
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -3464,6 +3464,7 @@ static int ieee80211_start_radar_detecti
+ 	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+ 	struct ieee80211_chan_req chanreq = { .oper = *chandef };
+ 	struct ieee80211_local *local = sdata->local;
++	struct ieee80211_link_data *link_data;
+ 	int err;
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+@@ -3471,16 +3472,20 @@ static int ieee80211_start_radar_detecti
+ 	if (!list_empty(&local->roc_list) || local->scanning)
+ 		return -EBUSY;
+ 
++	link_data = sdata_dereference(sdata->link[link_id], sdata);
++	if (!link_data)
++		return -ENOLINK;
++
+ 	/* whatever, but channel contexts should not complain about that one */
+-	sdata->deflink.smps_mode = IEEE80211_SMPS_OFF;
+-	sdata->deflink.needed_rx_chains = local->rx_chains;
++	link_data->smps_mode = IEEE80211_SMPS_OFF;
++	link_data->needed_rx_chains = local->rx_chains;
+ 
+-	err = ieee80211_link_use_channel(&sdata->deflink, &chanreq,
++	err = ieee80211_link_use_channel(link_data, &chanreq,
+ 					 IEEE80211_CHANCTX_SHARED);
+ 	if (err)
+ 		return err;
+ 
+-	wiphy_delayed_work_queue(wiphy, &sdata->deflink.dfs_cac_timer_work,
++	wiphy_delayed_work_queue(wiphy, &link_data->dfs_cac_timer_work,
+ 				 msecs_to_jiffies(cac_time_ms));
+ 
+ 	return 0;
+@@ -3491,16 +3496,21 @@ static void ieee80211_end_cac(struct wip
+ {
+ 	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+ 	struct ieee80211_local *local = sdata->local;
++	struct ieee80211_link_data *link_data;
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+ 	list_for_each_entry(sdata, &local->interfaces, list) {
++		link_data = sdata_dereference(sdata->link[link_id], sdata);
++		if (!link_data)
++			continue;
++
+ 		wiphy_delayed_work_cancel(wiphy,
+-					  &sdata->deflink.dfs_cac_timer_work);
++					  &link_data->dfs_cac_timer_work);
+ 
+-		if (sdata->wdev.links[0].cac_started) {
+-			ieee80211_link_release_channel(&sdata->deflink);
+-			sdata->wdev.links[0].cac_started = false;
++		if (sdata->wdev.links[link_id].cac_started) {
++			ieee80211_link_release_channel(link_data);
++			sdata->wdev.links[link_id].cac_started = false;
+ 		}
+ 	}
+ }
+--- a/net/mac80211/link.c
++++ b/net/mac80211/link.c
+@@ -77,6 +77,16 @@ void ieee80211_link_stop(struct ieee8021
+ 			  &link->color_change_finalize_work);
+ 	wiphy_work_cancel(link->sdata->local->hw.wiphy,
+ 			  &link->csa.finalize_work);
++
++	if (link->sdata->wdev.links[link->link_id].cac_started) {
++		wiphy_delayed_work_cancel(link->sdata->local->hw.wiphy,
++					  &link->dfs_cac_timer_work);
++		cfg80211_cac_event(link->sdata->dev,
++				   &link->conf->chanreq.oper,
++				   NL80211_RADAR_CAC_ABORTED,
++				   GFP_KERNEL, link->link_id);
++	}
++
+ 	ieee80211_link_release_channel(link);
+ }
+ 
+--- a/net/mac80211/util.c
++++ b/net/mac80211/util.c
+@@ -3455,20 +3455,30 @@ void ieee80211_dfs_cac_cancel(struct iee
+ {
+ 	struct ieee80211_sub_if_data *sdata;
+ 	struct cfg80211_chan_def chandef;
++	struct ieee80211_link_data *link;
++	unsigned int link_id;
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+ 	list_for_each_entry(sdata, &local->interfaces, list) {
+-		wiphy_delayed_work_cancel(local->hw.wiphy,
+-					  &sdata->deflink.dfs_cac_timer_work);
++		for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS;
++		     link_id++) {
++			link = sdata_dereference(sdata->link[link_id],
++						 sdata);
++			if (!link)
++				continue;
+ 
+-		if (sdata->wdev.links[0].cac_started) {
+-			chandef = sdata->vif.bss_conf.chanreq.oper;
+-			ieee80211_link_release_channel(&sdata->deflink);
+-			cfg80211_cac_event(sdata->dev,
+-					   &chandef,
++			wiphy_delayed_work_cancel(local->hw.wiphy,
++						  &link->dfs_cac_timer_work);
++
++			if (!sdata->wdev.links[link_id].cac_started)
++				continue;
++
++			chandef = link->conf->chanreq.oper;
++			ieee80211_link_release_channel(link);
++			cfg80211_cac_event(sdata->dev, &chandef,
+ 					   NL80211_RADAR_CAC_ABORTED,
+-					   GFP_KERNEL, 0);
++					   GFP_KERNEL, link_id);
+ 		}
+ 	}
+ }
diff --git a/package/kernel/mac80211/patches/subsys/339-wifi-cfg80211-mac80211-use-proper-link-ID-for-DFS.patch b/package/kernel/mac80211/patches/subsys/339-wifi-cfg80211-mac80211-use-proper-link-ID-for-DFS.patch
new file mode 100644
index 0000000000..53079b6cf6
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/339-wifi-cfg80211-mac80211-use-proper-link-ID-for-DFS.patch
@@ -0,0 +1,168 @@
+From: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Date: Fri, 6 Sep 2024 12:14:25 +0530
+Subject: [PATCH] wifi: cfg80211/mac80211: use proper link ID for DFS
+
+Now that all APIs have support to handle DFS per link, use proper link ID
+instead of 0.
+
+Signed-off-by: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-8-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg at intel.com>
+---
+
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -1656,12 +1656,12 @@ static int ieee80211_stop_ap(struct wiph
+ 	ieee80211_link_info_change_notify(sdata, link,
+ 					  BSS_CHANGED_BEACON_ENABLED);
+ 
+-	if (sdata->wdev.links[0].cac_started) {
++	if (sdata->wdev.links[link_id].cac_started) {
+ 		chandef = link_conf->chanreq.oper;
+ 		wiphy_delayed_work_cancel(wiphy, &link->dfs_cac_timer_work);
+ 		cfg80211_cac_event(sdata->dev, &chandef,
+ 				   NL80211_RADAR_CAC_ABORTED,
+-				   GFP_KERNEL, 0);
++				   GFP_KERNEL, link_id);
+ 	}
+ 
+ 	drv_stop_ap(sdata->local, sdata, link_conf);
+@@ -3965,7 +3965,7 @@ __ieee80211_channel_switch(struct wiphy
+ 	if (!list_empty(&local->roc_list) || local->scanning)
+ 		return -EBUSY;
+ 
+-	if (sdata->wdev.links[0].cac_started)
++	if (sdata->wdev.links[link_id].cac_started)
+ 		return -EBUSY;
+ 
+ 	if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS))
+--- a/net/mac80211/mlme.c
++++ b/net/mac80211/mlme.c
+@@ -3039,11 +3039,11 @@ void ieee80211_dfs_cac_timer_work(struct
+ 
+ 	lockdep_assert_wiphy(sdata->local->hw.wiphy);
+ 
+-	if (sdata->wdev.links[0].cac_started) {
++	if (sdata->wdev.links[link->link_id].cac_started) {
+ 		ieee80211_link_release_channel(link);
+ 		cfg80211_cac_event(sdata->dev, &chandef,
+ 				   NL80211_RADAR_CAC_FINISHED,
+-				   GFP_KERNEL, 0);
++				   GFP_KERNEL, link->link_id);
+ 	}
+ }
+ 
+--- a/net/mac80211/scan.c
++++ b/net/mac80211/scan.c
+@@ -575,6 +575,7 @@ static bool __ieee80211_can_leave_ch(str
+ {
+ 	struct ieee80211_local *local = sdata->local;
+ 	struct ieee80211_sub_if_data *sdata_iter;
++	unsigned int link_id;
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+@@ -585,8 +586,9 @@ static bool __ieee80211_can_leave_ch(str
+ 		return false;
+ 
+ 	list_for_each_entry(sdata_iter, &local->interfaces, list) {
+-		if (sdata_iter->wdev.links[0].cac_started)
+-			return false;
++		for_each_valid_link(&sdata_iter->wdev, link_id)
++			if (sdata_iter->wdev.links[link_id].cac_started)
++				return false;
+ 	}
+ 
+ 	return true;
+--- a/net/wireless/mlme.c
++++ b/net/wireless/mlme.c
+@@ -1125,14 +1125,14 @@ void cfg80211_cac_event(struct net_devic
+ 
+ 	trace_cfg80211_cac_event(netdev, event, link_id);
+ 
+-	if (WARN_ON(!wdev->links[0].cac_started &&
++	if (WARN_ON(!wdev->links[link_id].cac_started &&
+ 		    event != NL80211_RADAR_CAC_STARTED))
+ 		return;
+ 
+ 	switch (event) {
+ 	case NL80211_RADAR_CAC_FINISHED:
+-		timeout = wdev->links[0].cac_start_time +
+-			  msecs_to_jiffies(wdev->links[0].cac_time_ms);
++		timeout = wdev->links[link_id].cac_start_time +
++			  msecs_to_jiffies(wdev->links[link_id].cac_time_ms);
+ 		WARN_ON(!time_after_eq(jiffies, timeout));
+ 		cfg80211_set_dfs_state(wiphy, chandef, NL80211_DFS_AVAILABLE);
+ 		memcpy(&rdev->cac_done_chandef, chandef,
+@@ -1141,10 +1141,10 @@ void cfg80211_cac_event(struct net_devic
+ 		cfg80211_sched_dfs_chan_update(rdev);
+ 		fallthrough;
+ 	case NL80211_RADAR_CAC_ABORTED:
+-		wdev->links[0].cac_started = false;
++		wdev->links[link_id].cac_started = false;
+ 		break;
+ 	case NL80211_RADAR_CAC_STARTED:
+-		wdev->links[0].cac_started = true;
++		wdev->links[link_id].cac_started = true;
+ 		break;
+ 	default:
+ 		WARN_ON(1);
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -6066,7 +6066,7 @@ static int nl80211_start_ap(struct sk_bu
+ 	if (!rdev->ops->start_ap)
+ 		return -EOPNOTSUPP;
+ 
+-	if (wdev->links[0].cac_started)
++	if (wdev->links[link_id].cac_started)
+ 		return -EBUSY;
+ 
+ 	if (wdev->links[link_id].ap.beacon_interval)
+@@ -10073,6 +10073,7 @@ static int nl80211_start_radar_detection
+ 	struct cfg80211_registered_device *rdev = info->user_ptr[0];
+ 	struct net_device *dev = info->user_ptr[1];
+ 	struct wireless_dev *wdev = dev->ieee80211_ptr;
++	int link_id = nl80211_link_id(info->attrs);
+ 	struct wiphy *wiphy = wdev->wiphy;
+ 	struct cfg80211_chan_def chandef;
+ 	enum nl80211_dfs_regions dfs_region;
+@@ -10127,7 +10128,7 @@ static int nl80211_start_radar_detection
+ 		 * can not already beacon
+ 		 */
+ 		if (wdev->valid_links &&
+-		    !wdev->links[0].ap.beacon_interval) {
++		    !wdev->links[link_id].ap.beacon_interval) {
+ 			/* nothing */
+ 		} else {
+ 			err = -EBUSY;
+@@ -10135,7 +10136,7 @@ static int nl80211_start_radar_detection
+ 		}
+ 	}
+ 
+-	if (wdev->links[0].cac_started) {
++	if (wdev->links[link_id].cac_started) {
+ 		err = -EBUSY;
+ 		goto unlock;
+ 	}
+@@ -10156,7 +10157,7 @@ static int nl80211_start_radar_detection
+ 		cac_time_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
+ 
+ 	err = rdev_start_radar_detection(rdev, dev, &chandef, cac_time_ms,
+-					 0);
++					 link_id);
+ 	if (!err) {
+ 		switch (wdev->iftype) {
+ 		case NL80211_IFTYPE_AP:
+@@ -10172,9 +10173,9 @@ static int nl80211_start_radar_detection
+ 		default:
+ 			break;
+ 		}
+-		wdev->links[0].cac_started = true;
+-		wdev->links[0].cac_start_time = jiffies;
+-		wdev->links[0].cac_time_ms = cac_time_ms;
++		wdev->links[link_id].cac_started = true;
++		wdev->links[link_id].cac_start_time = jiffies;
++		wdev->links[link_id].cac_time_ms = cac_time_ms;
+ 	}
+ unlock:
+ 	wiphy_unlock(wiphy);
diff --git a/package/kernel/mac80211/patches/subsys/340-wifi-mac80211-handle-ieee80211_radar_detected-for-ML.patch b/package/kernel/mac80211/patches/subsys/340-wifi-mac80211-handle-ieee80211_radar_detected-for-ML.patch
new file mode 100644
index 0000000000..3f64864bd4
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/340-wifi-mac80211-handle-ieee80211_radar_detected-for-ML.patch
@@ -0,0 +1,360 @@
+From: Aditya Kumar Singh <quic_adisi at quicinc.com>
+Date: Fri, 6 Sep 2024 12:14:26 +0530
+Subject: [PATCH] wifi: mac80211: handle ieee80211_radar_detected() for MLO
+
+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.
+
+Also, when radar is detected in one of the links, other link's CAC should
+not be cancelled.
+
+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, cancel the ongoing CAC by calling
+    ieee80211_dfs_cac_cancel() and then notify cfg80211 via
+    cfg80211_radar_event().
+  * To cancel the CAC, pass the channel context as well where radar is
+    detected to ieee80211_dfs_cac_cancel(). This ensures that CAC is
+    canceled only on the links using the provided context, leaving other
+    links unaffected.
+
+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 at quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-9-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg at intel.com>
+---
+
+--- 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_rad
+ 	if (!arvif->is_started)
+ 		return -EINVAL;
+ 
+-	ieee80211_radar_detected(ar->hw);
++	ieee80211_radar_detected(ar->hw, NULL);
+ 
+ 	return count;
+ }
+--- 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_detectio
+ 		 * 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);
+ 	}
+ }
+ 
+--- 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
+ 	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)
+--- a/drivers/net/wireless/ath/ath11k/wmi.c
++++ b/drivers/net/wireless/ath/ath11k/wmi.c
+@@ -8358,7 +8358,7 @@ ath11k_wmi_pdev_dfs_radar_detected_event
+ 	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();
+--- a/drivers/net/wireless/ath/ath12k/wmi.c
++++ b/drivers/net/wireless/ath/ath12k/wmi.c
+@@ -6789,7 +6789,7 @@ ath12k_wmi_pdev_dfs_radar_detected_event
+ 	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();
+--- 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
+ 	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);
+ }
+ 
+ /*
+--- 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 ath_softc *sc = file->private_data;
+ 
+-	ieee80211_radar_detected(sc->hw);
++	ieee80211_radar_detected(sc->hw, NULL);
+ 
+ 	return count;
+ }
+--- 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 mt76
+ 	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++;
+ }
+ 
+--- 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 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 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;
+--- 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 mt79
+ 						&dev->rdd2_chandef,
+ 						GFP_ATOMIC);
+ 	else
+-		ieee80211_radar_detected(mphy->hw);
++		ieee80211_radar_detected(mphy->hw, NULL);
+ 	dev->hw_pattern++;
+ }
+ 
+--- 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 mt79
+ 						&dev->rdd2_chandef,
+ 						GFP_ATOMIC);
+ 	else
+-		ieee80211_radar_detected(mphy->hw);
++		ieee80211_radar_detected(mphy->hw, NULL);
+ 	dev->hw_pattern++;
+ }
+ 
+--- 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
+ 			    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) {
+--- 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(vo
+ {
+ 	struct mac80211_hwsim_data *data = dat;
+ 
+-	ieee80211_radar_detected(data->hw);
++	ieee80211_radar_detected(data->hw, NULL);
+ 
+ 	return 0;
+ }
+--- a/include/net/mac80211.h
++++ b/include/net/mac80211.h
+@@ -6717,8 +6717,11 @@ void ieee80211_cqm_beacon_loss_notify(st
+  * 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
+--- a/net/mac80211/chan.c
++++ b/net/mac80211/chan.c
+@@ -681,6 +681,7 @@ ieee80211_alloc_chanctx(struct ieee80211
+ 	ctx->mode = mode;
+ 	ctx->conf.radar_enabled = false;
+ 	ctx->conf.radio_idx = radio_idx;
++	ctx->radar_detected = false;
+ 	_ieee80211_recalc_chanctx_min_def(local, ctx, NULL, false);
+ 
+ 	return ctx;
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -895,6 +895,8 @@ struct ieee80211_chanctx {
+ 	struct ieee80211_chan_req req;
+ 
+ 	struct ieee80211_chanctx_conf conf;
++
++	bool radar_detected;
+ };
+ 
+ struct mac80211_qos_map {
+@@ -2632,7 +2634,8 @@ void ieee80211_recalc_chanctx_min_def(st
+ bool ieee80211_is_radar_required(struct ieee80211_local *local);
+ 
+ void ieee80211_dfs_cac_timer_work(struct wiphy *wiphy, struct wiphy_work *work);
+-void ieee80211_dfs_cac_cancel(struct ieee80211_local *local);
++void ieee80211_dfs_cac_cancel(struct ieee80211_local *local,
++			      struct ieee80211_chanctx *chanctx);
+ void ieee80211_dfs_radar_detected_work(struct wiphy *wiphy,
+ 				       struct wiphy_work *work);
+ int ieee80211_send_action_csa(struct ieee80211_sub_if_data *sdata,
+--- a/net/mac80211/pm.c
++++ b/net/mac80211/pm.c
+@@ -32,7 +32,7 @@ int __ieee80211_suspend(struct ieee80211
+ 
+ 	ieee80211_scan_cancel(local);
+ 
+-	ieee80211_dfs_cac_cancel(local);
++	ieee80211_dfs_cac_cancel(local, NULL);
+ 
+ 	ieee80211_roc_purge(local, NULL);
+ 
+--- a/net/mac80211/util.c
++++ b/net/mac80211/util.c
+@@ -3451,11 +3451,16 @@ u64 ieee80211_calculate_rx_timestamp(str
+ 	return ts;
+ }
+ 
+-void ieee80211_dfs_cac_cancel(struct ieee80211_local *local)
++/* Cancel CAC for the interfaces under the specified @local. If @ctx is
++ * also provided, only the interfaces using that ctx will be canceled.
++ */
++void ieee80211_dfs_cac_cancel(struct ieee80211_local *local,
++			      struct ieee80211_chanctx *ctx)
+ {
+ 	struct ieee80211_sub_if_data *sdata;
+ 	struct cfg80211_chan_def chandef;
+ 	struct ieee80211_link_data *link;
++	struct ieee80211_chanctx_conf *chanctx_conf;
+ 	unsigned int link_id;
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+@@ -3468,6 +3473,11 @@ void ieee80211_dfs_cac_cancel(struct iee
+ 			if (!link)
+ 				continue;
+ 
++			chanctx_conf = sdata_dereference(link->conf->chanctx_conf,
++							 sdata);
++			if (ctx && &ctx->conf != chanctx_conf)
++				continue;
++
+ 			wiphy_delayed_work_cancel(local->hw.wiphy,
+ 						  &link->dfs_cac_timer_work);
+ 
+@@ -3488,9 +3498,8 @@ void ieee80211_dfs_radar_detected_work(s
+ {
+ 	struct ieee80211_local *local =
+ 		container_of(work, struct ieee80211_local, radar_detected_work);
+-	struct cfg80211_chan_def chandef = local->hw.conf.chandef;
++	struct cfg80211_chan_def chandef;
+ 	struct ieee80211_chanctx *ctx;
+-	int num_chanctx = 0;
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+@@ -3498,25 +3507,46 @@ void ieee80211_dfs_radar_detected_work(s
+ 		if (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER)
+ 			continue;
+ 
+-		num_chanctx++;
++		if (!ctx->radar_detected)
++			continue;
++
++		ctx->radar_detected = false;
++
+ 		chandef = ctx->conf.def;
++
++		ieee80211_dfs_cac_cancel(local, ctx);
++		cfg80211_radar_event(local->hw.wiphy, &chandef, GFP_KERNEL);
+ 	}
++}
+ 
+-	ieee80211_dfs_cac_cancel(local);
++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);
+ 
+-	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 (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER)
++		return;
++
++	if (data && data != chanctx_conf)
++		return;
++
++	ctx->radar_detected = true;
+ }
+ 
+-void ieee80211_radar_detected(struct ieee80211_hw *hw)
++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);
diff --git a/package/kernel/mt76/patches/100-api_update.patch b/package/kernel/mt76/patches/100-api_update.patch
index 8b528f05f7..4cd19f65b2 100644
--- a/package/kernel/mt76/patches/100-api_update.patch
+++ b/package/kernel/mt76/patches/100-api_update.patch
@@ -9,3 +9,56 @@
  		head = &wcid->tx_offchannel;
  	else
  		head = &wcid->tx_pending;
+--- a/mt7615/mcu.c
++++ b/mt7615/mcu.c
+@@ -394,7 +394,7 @@ mt7615_mcu_rx_radar_detected(struct mt76
+ 	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++;
+ }
+ 
+--- a/mt76x02_dfs.c
++++ b/mt76x02_dfs.c
+@@ -630,7 +630,7 @@ static void mt76x02_dfs_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 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;
+--- a/mt7915/mcu.c
++++ b/mt7915/mcu.c
+@@ -297,7 +297,7 @@ mt7915_mcu_rx_radar_detected(struct mt79
+ 						&dev->rdd2_chandef,
+ 						GFP_ATOMIC);
+ 	else
+-		ieee80211_radar_detected(mphy->hw);
++		ieee80211_radar_detected(mphy->hw, NULL);
+ 	dev->hw_pattern++;
+ }
+ 
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -383,7 +383,7 @@ mt7996_mcu_rx_radar_detected(struct mt79
+ 						&dev->rdd2_chandef,
+ 						GFP_ATOMIC);
+ 	else
+-		ieee80211_radar_detected(mphy->hw);
++		ieee80211_radar_detected(mphy->hw, NULL);
+ 	dev->hw_pattern++;
+ }
+ 
diff --git a/package/kernel/mwlwifi/patches/005-mac80211_update.patch b/package/kernel/mwlwifi/patches/005-mac80211_update.patch
index 54dc4db783..2ceee4732d 100644
--- a/package/kernel/mwlwifi/patches/005-mac80211_update.patch
+++ b/package/kernel/mwlwifi/patches/005-mac80211_update.patch
@@ -68,6 +68,15 @@
  			sta->tdls,
  			sta->tdls_initiator,
  			sta->wme,
+@@ -1158,7 +1158,7 @@ static ssize_t mwl_debugfs_dfs_radar_wri
+ 	struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ 
+ 	wiphy_info(priv->hw->wiphy, "simulate radar detected\n");
+-	ieee80211_radar_detected(priv->hw);
++	ieee80211_radar_detected(priv->hw, NULL);
+ 
+ 	return count;
+ }
 --- a/hif/fwcmd.c
 +++ b/hif/fwcmd.c
 @@ -633,11 +633,15 @@ einval:
@@ -491,3 +500,32 @@
  
  	switch (format) {
  	case TX_RATE_FORMAT_LEGACY:
+--- a/hif/pcie/pcie.c
++++ b/hif/pcie/pcie.c
+@@ -546,7 +546,7 @@ static irqreturn_t pcie_isr_8864(struct
+ 
+ 		if (int_status & MACREG_A2HRIC_BIT_RADAR_DETECT) {
+ 			wiphy_info(hw->wiphy, "radar detected by firmware\n");
+-			ieee80211_radar_detected(hw);
++			ieee80211_radar_detected(hw, NULL);
+ 		}
+ 
+ 		if (int_status & MACREG_A2HRIC_BIT_CHAN_SWITCH) ieee80211_queue_work(hw, &priv->chnl_switch_handle);
+@@ -593,7 +593,7 @@ static irqreturn_t pcie_isr_8997(struct
+ 
+ 		if (int_status & MACREG_A2HRIC_BIT_RADAR_DETECT) {
+ 			wiphy_info(hw->wiphy, "radar detected by firmware\n");
+-			ieee80211_radar_detected(hw);
++			ieee80211_radar_detected(hw, NULL);
+ 		}
+ 
+ 		if (int_status & MACREG_A2HRIC_BIT_CHAN_SWITCH)
+@@ -1071,7 +1071,7 @@ static irqreturn_t pcie_isr_ndp(struct i
+ 
+ 		if (int_status & MACREG_A2HRIC_NEWDP_DFS) {
+ 			wiphy_info(hw->wiphy, "radar detected by firmware\n");
+-			ieee80211_radar_detected(hw);
++			ieee80211_radar_detected(hw, NULL);
+ 		}
+ 
+ 		if (int_status & MACREG_A2HRIC_NEWDP_CHANNEL_SWITCH)




More information about the lede-commits mailing list