--- 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;
}
{
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)
--- /dev/null
+From: Issam Hamdi <ih@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@fit.fraunhofer.de>
+Signed-off-by: Issam Hamdi <ih@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@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;
--- /dev/null
+From: Aditya Kumar Singh <quic_adisi@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@quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-2-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg@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;
--- /dev/null
+From: Aditya Kumar Singh <quic_adisi@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@quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-3-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg@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,
--- /dev/null
+From: Aditya Kumar Singh <quic_adisi@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@quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-4-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg@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,
--- /dev/null
+From: Aditya Kumar Singh <quic_adisi@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@quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-5-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg@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(¶ms->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 */
--- /dev/null
+From: Aditya Kumar Singh <quic_adisi@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@quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-6-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg@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,
--- /dev/null
+From: Aditya Kumar Singh <quic_adisi@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@quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-7-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg@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);
+ }
+ }
+ }
--- /dev/null
+From: Aditya Kumar Singh <quic_adisi@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@quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-8-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg@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);
--- /dev/null
+From: Aditya Kumar Singh <quic_adisi@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@quicinc.com>
+Link: https://patch.msgid.link/20240906064426.2101315-9-quic_adisi@quicinc.com
+Signed-off-by: Johannes Berg <johannes.berg@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);
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++;
+ }
+
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:
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)