AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/10/06 10:49 flow patch-triage
7m Args:
null

Results:
{
  "EnableConfigs": [
    "CFG80211",
    "MAC80211",
    "MAC80211_HWSIM",
    "CFG80211_CRDA_SUPPORT",
    "CFG80211_DEBUGFS"
  ],
  "FocusSymbols": [
    "nl80211_pre_doit",
    "nl80211_lock_and_recheck",
    "nl80211_prepare_wdev_dump",
    "nl80211_set_reg",
    "cfg80211_dfs_channels_update_work",
    "cfg80211_dfs_chan_expire",
    "regulatory_propagate_dfs_state",
    "hwsim_background_cac_write"
  ],
  "KMSANReasoning": "The patch series refactors cfg80211/nl80211 and driver regulatory code to reduce RTNL dependency by introducing a dedicated cfg80211_mutex, utilizing wiphy mutex locking, and employing RCU read-side critical sections.\n\nThese modifications are strictly related to locking, synchronization, and object lifetime management. Any potential regressions introduced by these changes would be race conditions, use-after-free bugs, lock-order inversions, or missing lock assertions, which are detected by KASAN and LOCKDEP.\n\nThe patch does not introduce or modify any uninitialized memory reads, uninitialized buffer copies to user space, or complex uninitialized struct allocations (the newly introduced dynamic buffer allocation in cfg80211_dfs_channels_update_work uses kvcalloc, zero-initializing all entries). Therefore, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch series introduces major locking and synchronization changes across the wireless core (cfg80211, nl80211, mac80211_hwsim), replacing RTNL lock coverage with a new cfg80211_mutex, RCU lookups, wiphy mutex guards, and lockdep assertions. It also modifies asynchronous DFS channel expiration and dump preparation. These reachable paths are heavily fuzzed via Netlink and virtual devices, making fuzzing critical to catch concurrency bugs, state races, and lockdep splats.",
  "WorthFuzzing": true
}

1/1 2026/10/06 10:49 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 487d4a302c3c079c0ba08d2a624c2282c46c0376\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Oct 6 10:49:10 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/wireless/ath/ath11k/reg.c b/drivers/net/wireless/ath/ath11k/reg.c\nindex 0879a132b67cf..1030e119fd903 100644\n--- a/drivers/net/wireless/ath/ath11k/reg.c\n+++ b/drivers/net/wireless/ath/ath11k/reg.c\n@@ -33,7 +33,9 @@ static bool ath11k_regdom_changes(struct ath11k *ar, char *alpha2)\n {\n \tconst struct ieee80211_regdomain *regd;\n \n-\tregd = rcu_dereference_rtnl(ar-\u003ehw-\u003ewiphy-\u003eregd);\n+\tguard(rcu)();\n+\n+\tregd = get_wiphy_regdom(ar-\u003ehw-\u003ewiphy);\n \t/* This can happen during wiphy registration where the previous\n \t * user request is received before we update the regd received\n \t * from firmware.\ndiff --git a/drivers/net/wireless/ath/ath12k/reg.c b/drivers/net/wireless/ath/ath12k/reg.c\nindex 89abf2e87ad10..7cf3dbc4fc8a9 100644\n--- a/drivers/net/wireless/ath/ath12k/reg.c\n+++ b/drivers/net/wireless/ath/ath12k/reg.c\n@@ -33,7 +33,9 @@ static bool ath12k_regdom_changes(struct ieee80211_hw *hw, char *alpha2)\n {\n \tconst struct ieee80211_regdomain *regd;\n \n-\tregd = rcu_dereference_rtnl(hw-\u003ewiphy-\u003eregd);\n+\tguard(rcu)();\n+\n+\tregd = get_wiphy_regdom(hw-\u003ewiphy);\n \t/* This can happen during wiphy registration where the previous\n \t * user request is received before we update the regd received\n \t * from firmware.\n@@ -122,9 +124,7 @@ ath12k_reg_notifier(struct wiphy *wiphy, struct regulatory_request *request)\n \t\t\t\t\t    \"failed set INIT Country code: %d\\n\", ret);\n \t\t}\n \n-\t\twiphy_lock(wiphy);\n \t\tath12k_mac_11d_scan_stop(ar);\n-\t\twiphy_unlock(wiphy);\n \n \t\tar-\u003eregdom_set_by_user = true;\n \t}\ndiff --git a/drivers/net/wireless/ath/regd.c b/drivers/net/wireless/ath/regd.c\nindex f15e7bd690b5b..cedb5e43b9e4f 100644\n--- a/drivers/net/wireless/ath/regd.c\n+++ b/drivers/net/wireless/ath/regd.c\n@@ -268,6 +268,8 @@ static void ath_force_clear_no_ir_chan(struct wiphy *wiphy,\n {\n \tconst struct ieee80211_reg_rule *reg_rule;\n \n+\tguard(rcu)();\n+\n \treg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(ch-\u003ecenter_freq));\n \tif (IS_ERR(reg_rule))\n \t\treturn;\ndiff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c\nindex cdfe8635c012e..5ca8a07353982 100644\n--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c\n+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c\n@@ -676,6 +676,8 @@ brcms_reg_apply_beaconing_flags(struct wiphy *wiphy,\n \t\t\t\tcontinue;\n \n \t\t\tif (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) {\n+\t\t\t\tguard(rcu)();\n+\n \t\t\t\trule = freq_reg_info(wiphy,\n \t\t\t\t\t\t     MHZ_TO_KHZ(ch-\u003ecenter_freq));\n \t\t\t\tif (IS_ERR(rule))\ndiff --git a/drivers/net/wireless/realtek/rtlwifi/regd.c b/drivers/net/wireless/realtek/rtlwifi/regd.c\nindex fd967006b3e1e..503625b50fc38 100644\n--- a/drivers/net/wireless/realtek/rtlwifi/regd.c\n+++ b/drivers/net/wireless/realtek/rtlwifi/regd.c\n@@ -150,6 +150,8 @@ static void _rtl_reg_apply_beaconing_flags(struct wiphy *wiphy,\n \t\t\t    (ch-\u003eflags \u0026 IEEE80211_CHAN_RADAR))\n \t\t\t\tcontinue;\n \t\t\tif (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) {\n+\t\t\t\tguard(rcu)();\n+\n \t\t\t\treg_rule = freq_reg_info(wiphy,\n \t\t\t\t\t\t\t ch-\u003ecenter_freq);\n \t\t\t\tif (IS_ERR(reg_rule))\n@@ -212,6 +214,8 @@ static void _rtl_reg_apply_active_scan_flags(struct wiphy *wiphy,\n \t *custom regulatory domain.\n \t */\n \n+\tguard(rcu)();\n+\n \tch = \u0026sband-\u003echannels[11];\t/* CH 12 */\n \treg_rule = freq_reg_info(wiphy, ch-\u003ecenter_freq);\n \tif (!IS_ERR(reg_rule)) {\ndiff --git a/drivers/net/wireless/realtek/rtw89/regd.c b/drivers/net/wireless/realtek/rtw89/regd.c\nindex b9a5ce218b1da..983b42e193b52 100644\n--- a/drivers/net/wireless/realtek/rtw89/regd.c\n+++ b/drivers/net/wireless/realtek/rtw89/regd.c\n@@ -898,7 +898,6 @@ void rtw89_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request\n \tstruct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);\n \tstruct rtw89_dev *rtwdev = hw-\u003epriv;\n \n-\twiphy_lock(wiphy);\n \trtw89_leave_ps_mode(rtwdev);\n \n \tif (rtwdev-\u003eregulatory.programmed)\n@@ -916,8 +915,6 @@ void rtw89_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request\n \trtw89_regd_apply_policy_ant_gain(rtwdev);\n \n \trtw89_core_set_chip_txpwr(rtwdev);\n-\n-\twiphy_unlock(wiphy);\n }\n \n /* Maximum Transmit Power field (@raw) can be EIRP or PSD.\ndiff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c\nindex 6cd9542e8b722..b0fd6bdd3cccc 100644\n--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c\n+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c\n@@ -1133,6 +1133,8 @@ static ssize_t hwsim_background_cac_write(struct file *file,\n \tif (copy_from_user(buf, user_buf, count))\n \t\treturn -EFAULT;\n \n+\tguard(wiphy)(data-\u003ehw-\u003ewiphy);\n+\n \t/* Check if background radar channel is configured */\n \tif (!data-\u003eradar_background_chandef.chan)\n \t\treturn -ENOENT;\ndiff --git a/include/net/cfg80211.h b/include/net/cfg80211.h\nindex 05c625d9e0766..378fa52bfbab6 100644\n--- a/include/net/cfg80211.h\n+++ b/include/net/cfg80211.h\n@@ -6362,7 +6362,8 @@ struct wiphy_nan_capa {\n  * @mtx: mutex for the data (structures) of this device\n  * @reg_notifier: the driver's regulatory notification callback,\n  *\tnote that if your driver uses wiphy_apply_custom_regulatory()\n- *\tthe reg_notifier's request can be passed as NULL\n+ *\tthe reg_notifier's request can be passed as NULL.\n+ *\tThis is called under wiphy mutex.\n  * @regd: the driver's regulatory domain, if one was requested via\n  *\tthe regulatory_hint() API. This can be used by the driver\n  *\ton the reg_notifier() if it chooses to ignore future\n@@ -7242,8 +7243,9 @@ enum ieee80211_ap_reg_power {\n  * @mgmt_registrations_need_update: mgmt registrations were updated,\n  *\tneed to propagate the update to the driver\n  * @address: The address for this device, valid only if @netdev is %NULL\n- * @is_running: true if this is a non-netdev device that has been started, e.g.\n- *\tthe P2P Device.\n+ * @is_running: true if the device has been started, e.g. the P2P Device;\n+ *\tfor netdevs, tracked under the wiphy mutex from NETDEV_UP until\n+ *\tNETDEV_GOING_DOWN\n  * @ps: powersave mode is enabled\n  * @ps_timeout: dynamic powersave timeout\n  * @unexpected_nlportid: (private) netlink port ID of application\n@@ -7407,8 +7409,6 @@ static inline const u8 *wdev_address(struct wireless_dev *wdev)\n \n static inline bool wdev_running(struct wireless_dev *wdev)\n {\n-\tif (wdev-\u003enetdev)\n-\t\treturn netif_running(wdev-\u003enetdev);\n \treturn wdev-\u003eis_running;\n }\n \n@@ -8161,9 +8161,9 @@ int regulatory_set_wiphy_regd(struct wiphy *wiphy,\n  * @wiphy: the wireless device we want to process the regulatory domain on\n  * @rd: the regulatory domain information to use for this wiphy\n  *\n- * This functions requires the RTNL and the wiphy mutex to be held and\n- * applies the new regdomain synchronously to this wiphy. For more details\n- * see regulatory_set_wiphy_regd().\n+ * This functions requires the wiphy mutex to be held and applies the new\n+ * regdomain synchronously to this wiphy. For more details see\n+ * regulatory_set_wiphy_regd().\n  *\n  * Return: 0 on success. -EINVAL, -EPERM\n  */\n@@ -8198,6 +8198,8 @@ void wiphy_apply_custom_regulatory(struct wiphy *wiphy,\n  * it wants to follow we respect that unless a country IE has been received\n  * and processed already.\n  *\n+ * Must be called within an RCU read-side critical section.\n+ *\n  * Return: A valid pointer, or, when an error occurs, for example if no rule\n  * can be found, the return value is encoded using ERR_PTR(). Use IS_ERR() to\n  * check and PTR_ERR() to obtain the numeric return value. The numeric return\n@@ -8236,20 +8238,20 @@ bool regulatory_pre_cac_allowed(struct wiphy *wiphy);\n  */\n \n /**\n- * reg_query_regdb_wmm -  Query internal regulatory db for wmm rule\n- * Regulatory self-managed driver can use it to proactively\n- *\n+ * reg_query_regdb_wmm -  Query internal regulatory db for WMM rules\n  * @alpha2: the ISO/IEC 3166 alpha2 wmm rule to be queried.\n  * @freq: the frequency (in MHz) to be queried.\n- * @rule: pointer to store the wmm rule from the regulatory db.\n+ * @rule: pointer to store the WMM rule from the regulatory db.\n  *\n- * Self-managed wireless drivers can use this function to  query\n+ * Self-managed wireless drivers can use this function to query\n  * the internal regulatory database to check whether the given\n- * ISO/IEC 3166 alpha2 country and freq have wmm rule limitations.\n+ * ISO/IEC 3166 alpha2 country and freq have WMM rule limitations.\n  *\n  * Drivers should check the return value, its possible you can get\n  * an -ENODATA.\n  *\n+ * This uses RCU internally so can be called in just about any context.\n+ *\n  * Return: 0 on success. -ENODATA.\n  */\n int reg_query_regdb_wmm(char *alpha2, int freq,\n@@ -9809,6 +9811,7 @@ void cfg80211_cqm_beacon_loss_notify(struct net_device *dev, gfp_t gfp);\n  * @gfp: context flags\n  *\n  * This function is called when a radar is detected on the current chanenl.\n+ * Must be called with the wiphy mutex held.\n  */\n void __cfg80211_radar_event(struct wiphy *wiphy,\n \t\t\t    struct cfg80211_chan_def *chandef,\n@@ -9854,7 +9857,7 @@ void cfg80211_sta_opmode_change_notify(struct net_device *dev, const u8 *mac,\n  *\n  * This function is called when a Channel availability check (CAC) is finished\n  * or aborted. This must be called to notify the completion of a CAC process,\n- * also by full-MAC drivers.\n+ * also by full-MAC drivers. Must be called with the wiphy mutex held.\n  */\n void cfg80211_cac_event(struct net_device *netdev,\n \t\t\tconst struct cfg80211_chan_def *chandef,\ndiff --git a/net/wireless/chan.c b/net/wireless/chan.c\nindex c743b6fb7e309..9183cd19dbdc4 100644\n--- a/net/wireless/chan.c\n+++ b/net/wireless/chan.c\n@@ -1243,7 +1243,7 @@ bool cfg80211_any_wiphy_oper_chan(struct wiphy *wiphy,\n {\n \tstruct cfg80211_registered_device *rdev;\n \n-\tASSERT_RTNL();\n+\tlockdep_assert_held(\u0026cfg80211_mutex);\n \n \tif (!(chan-\u003eflags \u0026 IEEE80211_CHAN_RADAR))\n \t\treturn false;\ndiff --git a/net/wireless/core.c b/net/wireless/core.c\nindex 45a1709cffe7a..01b077909179d 100644\n--- a/net/wireless/core.c\n+++ b/net/wireless/core.c\n@@ -42,7 +42,9 @@ MODULE_LICENSE(\"GPL\");\n MODULE_DESCRIPTION(\"wireless configuration support\");\n MODULE_ALIAS_GENL_FAMILY(NL80211_GENL_NAME);\n \n-/* RCU-protected (and RTNL for writers) */\n+DEFINE_MUTEX(cfg80211_mutex);\n+\n+/* RCU-protected (writes under both RTNL/cfg80211_mutex) */\n LIST_HEAD(cfg80211_rdev_list);\n int cfg80211_rdev_list_generation;\n \n@@ -59,18 +61,14 @@ MODULE_PARM_DESC(cfg80211_disable_40mhz_24ghz,\n \n struct cfg80211_registered_device *cfg80211_rdev_by_wiphy_idx(int wiphy_idx)\n {\n-\tstruct cfg80211_registered_device *result = NULL, *rdev;\n-\n-\tASSERT_RTNL();\n+\tstruct cfg80211_registered_device *rdev;\n \n \tfor_each_rdev(rdev) {\n-\t\tif (rdev-\u003ewiphy_idx == wiphy_idx) {\n-\t\t\tresult = rdev;\n-\t\t\tbreak;\n-\t\t}\n+\t\tif (rdev-\u003ewiphy_idx == wiphy_idx)\n+\t\t\treturn rdev;\n \t}\n \n-\treturn result;\n+\treturn NULL;\n }\n \n int get_wiphy_idx(struct wiphy *wiphy)\n@@ -84,8 +82,6 @@ struct wiphy *wiphy_idx_to_wiphy(int wiphy_idx)\n {\n \tstruct cfg80211_registered_device *rdev;\n \n-\tASSERT_RTNL();\n-\n \trdev = cfg80211_rdev_by_wiphy_idx(wiphy_idx);\n \tif (!rdev)\n \t\treturn NULL;\n@@ -564,13 +560,11 @@ static void cfg80211_propagate_radar_detect_wk(struct work_struct *work)\n \trdev = container_of(work, struct cfg80211_registered_device,\n \t\t\t    propagate_radar_detect_wk);\n \n-\trtnl_lock();\n+\tguard(mutex)(\u0026cfg80211_mutex);\n \n \tregulatory_propagate_dfs_state(\u0026rdev-\u003ewiphy, \u0026rdev-\u003eradar_chandef,\n \t\t\t\t       NL80211_DFS_UNAVAILABLE,\n \t\t\t\t       NL80211_RADAR_DETECTED);\n-\n-\trtnl_unlock();\n }\n \n static void cfg80211_propagate_cac_done_wk(struct work_struct *work)\n@@ -580,13 +574,11 @@ static void cfg80211_propagate_cac_done_wk(struct work_struct *work)\n \trdev = container_of(work, struct cfg80211_registered_device,\n \t\t\t    propagate_cac_done_wk);\n \n-\trtnl_lock();\n+\tguard(mutex)(\u0026cfg80211_mutex);\n \n \tregulatory_propagate_dfs_state(\u0026rdev-\u003ewiphy, \u0026rdev-\u003ecac_done_chandef,\n \t\t\t\t       NL80211_DFS_AVAILABLE,\n \t\t\t\t       NL80211_RADAR_CAC_FINISHED);\n-\n-\trtnl_unlock();\n }\n \n static void cfg80211_wiphy_work(struct work_struct *work)\n@@ -1243,10 +1235,12 @@ int wiphy_register(struct wiphy *wiphy)\n \t\trdev-\u003ewiphy.bss_param_support |= WIPHY_BSS_PARAM_P2P_OPPPS;\n \n \trtnl_lock();\n+\tmutex_lock(\u0026cfg80211_mutex);\n \twiphy_lock(\u0026rdev-\u003ewiphy);\n \tres = device_add(\u0026rdev-\u003ewiphy.dev);\n \tif (res) {\n \t\twiphy_unlock(\u0026rdev-\u003ewiphy);\n+\t\tmutex_unlock(\u0026cfg80211_mutex);\n \t\trtnl_unlock();\n \t\treturn res;\n \t}\n@@ -1271,6 +1265,9 @@ int wiphy_register(struct wiphy *wiphy)\n \t}\n \n \tcfg80211_debugfs_rdev_add(rdev);\n+\n+\trdev-\u003ewiphy.registered = true;\n+\n \tnl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY);\n \twiphy_unlock(\u0026rdev-\u003ewiphy);\n \n@@ -1317,8 +1314,7 @@ int wiphy_register(struct wiphy *wiphy)\n \t\t\t\tbreak;\n \t\t}\n \t}\n-\n-\trdev-\u003ewiphy.registered = true;\n+\tmutex_unlock(\u0026cfg80211_mutex);\n \trtnl_unlock();\n \n \tres = rfkill_register(rdev-\u003ewiphy.rfkill);\n@@ -1390,6 +1386,7 @@ void wiphy_unregister(struct wiphy *wiphy)\n \t\trfkill_unregister(rdev-\u003ewiphy.rfkill);\n \n \trtnl_lock();\n+\tmutex_lock(\u0026cfg80211_mutex);\n \twiphy_lock(\u0026rdev-\u003ewiphy);\n \tnl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY);\n \trdev-\u003ewiphy.registered = false;\n@@ -1421,6 +1418,7 @@ void wiphy_unregister(struct wiphy *wiphy)\n \t/* surely nothing is reachable now, clean up work */\n \tcfg80211_process_wiphy_works(rdev, NULL);\n \twiphy_unlock(\u0026rdev-\u003ewiphy);\n+\tmutex_unlock(\u0026cfg80211_mutex);\n \trtnl_unlock();\n \n \t/* this has nothing to do now but make sure it's gone */\n@@ -1836,6 +1834,8 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,\n \t\t}\n \t\tbreak;\n \tcase NETDEV_GOING_DOWN:\n+\t\tscoped_guard(wiphy, \u0026rdev-\u003ewiphy)\n+\t\t\twdev-\u003eis_running = false;\n \t\tcfg80211_leave(rdev, wdev, -1);\n \t\tscoped_guard(wiphy, \u0026rdev-\u003ewiphy) {\n \t\t\tcfg80211_remove_links(wdev);\n@@ -1866,6 +1866,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,\n \t\tbreak;\n \tcase NETDEV_UP:\n \t\twiphy_lock(\u0026rdev-\u003ewiphy);\n+\t\twdev-\u003eis_running = true;\n \t\tcfg80211_update_iface_num(rdev, wdev-\u003eiftype, 1);\n \t\tswitch (wdev-\u003eiftype) {\n #ifdef CONFIG_CFG80211_WEXT\ndiff --git a/net/wireless/core.h b/net/wireless/core.h\nindex 58445c09026a2..dfcb9ed5035b3 100644\n--- a/net/wireless/core.h\n+++ b/net/wireless/core.h\n@@ -19,6 +19,9 @@\n #include \"reg.h\"\n \n \n+/* protects the wiphy list and regulatory state */\n+extern struct mutex cfg80211_mutex;\n+\n #define WIPHY_IDX_INVALID\t-1\n \n struct cfg80211_scan_request_int {\n@@ -179,15 +182,10 @@ extern struct workqueue_struct *cfg80211_wq;\n extern struct list_head cfg80211_rdev_list;\n extern int cfg80211_rdev_list_generation;\n \n-/* This is constructed like this so it can be used in if/else */\n-static inline int for_each_rdev_check_rtnl(void)\n-{\n-\tASSERT_RTNL();\n-\treturn 0;\n-}\n #define for_each_rdev(rdev)\t\t\t\t\t\t\\\n-\tif (for_each_rdev_check_rtnl()) {} else\t\t\t\t\\\n-\t\tlist_for_each_entry(rdev, \u0026cfg80211_rdev_list, list)\n+\tlist_for_each_entry_rcu(rdev, \u0026cfg80211_rdev_list, list,\t\\\n+\t\t\t\tlockdep_rtnl_is_held() ||\t\t\\\n+\t\t\t\tlockdep_is_held(\u0026cfg80211_mutex))\n \n enum bss_source_type {\n \tBSS_SOURCE_DIRECT = 0,\ndiff --git a/net/wireless/mlme.c b/net/wireless/mlme.c\nindex a0d1cde26f0c9..96fd14f2a305d 100644\n--- a/net/wireless/mlme.c\n+++ b/net/wireless/mlme.c\n@@ -1070,25 +1070,75 @@ void cfg80211_sched_dfs_chan_update(struct cfg80211_registered_device *rdev)\n \tqueue_delayed_work(cfg80211_wq, \u0026rdev-\u003edfs_update_channels_wk, 0);\n }\n \n+struct cfg80211_dfs_chan_expiry {\n+\tstruct ieee80211_channel *chan;\n+\tunsigned long entered;\n+\tenum nl80211_dfs_state state;\n+\tenum nl80211_radar_event event;\n+};\n+\n+static bool cfg80211_dfs_chan_expire(struct cfg80211_registered_device *rdev,\n+\t\t\t\t     struct cfg80211_dfs_chan_expiry *exp,\n+\t\t\t\t     struct cfg80211_chan_def *chandef)\n+{\n+\tstruct ieee80211_channel *c = exp-\u003echan;\n+\n+\tguard(wiphy)(\u0026rdev-\u003ewiphy);\n+\n+\t/* the state may have changed while the wiphy wasn't locked */\n+\tif (c-\u003edfs_state != exp-\u003estate || c-\u003edfs_state_entered != exp-\u003eentered)\n+\t\treturn false;\n+\n+\tc-\u003edfs_state = NL80211_DFS_USABLE;\n+\tc-\u003edfs_state_entered = jiffies;\n+\n+\tcfg80211_chandef_create(chandef, c, NL80211_CHAN_NO_HT);\n+\n+\tnl80211_radar_notify(rdev, chandef, exp-\u003eevent, NULL, GFP_KERNEL);\n+\n+\treturn true;\n+}\n+\n void cfg80211_dfs_channels_update_work(struct work_struct *work)\n {\n \tstruct delayed_work *delayed_work = to_delayed_work(work);\n+\tstruct cfg80211_dfs_chan_expiry *expired;\n \tstruct cfg80211_registered_device *rdev;\n \tstruct cfg80211_chan_def chandef;\n \tstruct ieee80211_supported_band *sband;\n \tstruct ieee80211_channel *c;\n \tstruct wiphy *wiphy;\n-\tbool check_again = false;\n+\tbool check_again = false, pre_cac_allowed;\n \tunsigned long timeout, next_time = 0;\n \tunsigned long time_dfs_update;\n \tenum nl80211_radar_event radar_event;\n+\tunsigned int n_chans = 0, n_expired = 0;\n \tint bandid, i;\n \n \trdev = container_of(delayed_work, struct cfg80211_registered_device,\n \t\t\t    dfs_update_channels_wk);\n \twiphy = \u0026rdev-\u003ewiphy;\n \n-\trtnl_lock();\n+\tmutex_lock(\u0026cfg80211_mutex);\n+\twiphy_lock(wiphy);\n+\tfor (bandid = 0; bandid \u003c NUM_NL80211_BANDS; bandid++) {\n+\t\tif (wiphy-\u003ebands[bandid])\n+\t\t\tn_chans += wiphy-\u003ebands[bandid]-\u003en_channels;\n+\t}\n+\n+\texpired = kvcalloc(n_chans, sizeof(*expired), GFP_KERNEL);\n+\tif (!expired) {\n+\t\t/* hmm - retry later */\n+\t\tqueue_delayed_work(cfg80211_wq,\n+\t\t\t\t   \u0026rdev-\u003edfs_update_channels_wk,\n+\t\t\t\t   HZ);\n+\t\twiphy_unlock(wiphy);\n+\t\tmutex_unlock(\u0026cfg80211_mutex);\n+\t\treturn;\n+\t}\n+\n+\tpre_cac_allowed = regulatory_pre_cac_allowed(wiphy);\n+\n \tfor (bandid = 0; bandid \u003c NUM_NL80211_BANDS; bandid++) {\n \t\tsband = wiphy-\u003ebands[bandid];\n \t\tif (!sband)\n@@ -1108,8 +1158,7 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)\n \t\t\t\ttime_dfs_update = IEEE80211_DFS_MIN_NOP_TIME_MS;\n \t\t\t\tradar_event = NL80211_RADAR_NOP_FINISHED;\n \t\t\t} else {\n-\t\t\t\tif (regulatory_pre_cac_allowed(wiphy) ||\n-\t\t\t\t    cfg80211_any_wiphy_oper_chan(wiphy, c))\n+\t\t\t\tif (pre_cac_allowed)\n \t\t\t\t\tcontinue;\n \n \t\t\t\ttime_dfs_update = REG_PRE_CAC_EXPIRY_GRACE_MS;\n@@ -1120,19 +1169,11 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)\n \t\t\t\t  msecs_to_jiffies(time_dfs_update);\n \n \t\t\tif (time_after_eq(jiffies, timeout)) {\n-\t\t\t\tc-\u003edfs_state = NL80211_DFS_USABLE;\n-\t\t\t\tc-\u003edfs_state_entered = jiffies;\n-\n-\t\t\t\tcfg80211_chandef_create(\u0026chandef, c,\n-\t\t\t\t\t\t\tNL80211_CHAN_NO_HT);\n-\n-\t\t\t\tnl80211_radar_notify(rdev, \u0026chandef,\n-\t\t\t\t\t\t     radar_event, NULL,\n-\t\t\t\t\t\t     GFP_ATOMIC);\n-\n-\t\t\t\tregulatory_propagate_dfs_state(wiphy, \u0026chandef,\n-\t\t\t\t\t\t\t       c-\u003edfs_state,\n-\t\t\t\t\t\t\t       radar_event);\n+\t\t\t\texpired[n_expired].chan = c;\n+\t\t\t\texpired[n_expired].state = c-\u003edfs_state;\n+\t\t\t\texpired[n_expired].entered = c-\u003edfs_state_entered;\n+\t\t\t\texpired[n_expired].event = radar_event;\n+\t\t\t\tn_expired++;\n \t\t\t\tcontinue;\n \t\t\t}\n \n@@ -1143,7 +1184,24 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)\n \t\t\tcheck_again = true;\n \t\t}\n \t}\n-\trtnl_unlock();\n+\twiphy_unlock(wiphy);\n+\n+\t/* these lock the wiphys, so must be done without holding ours */\n+\tfor (i = 0; i \u003c n_expired; i++) {\n+\t\tif (expired[i].event == NL80211_RADAR_PRE_CAC_EXPIRED \u0026\u0026\n+\t\t    cfg80211_any_wiphy_oper_chan(wiphy, expired[i].chan))\n+\t\t\tcontinue;\n+\n+\t\tif (!cfg80211_dfs_chan_expire(rdev, \u0026expired[i], \u0026chandef))\n+\t\t\tcontinue;\n+\n+\t\tregulatory_propagate_dfs_state(wiphy, \u0026chandef,\n+\t\t\t\t\t       NL80211_DFS_USABLE,\n+\t\t\t\t\t       expired[i].event);\n+\t}\n+\tmutex_unlock(\u0026cfg80211_mutex);\n+\n+\tkvfree(expired);\n \n \t/* reschedule if there are other channels waiting to be cleared again */\n \tif (check_again)\ndiff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c\nindex 7ee69ec10b0f9..48f5584d7c2bc 100644\n--- a/net/wireless/nl80211.c\n+++ b/net/wireless/nl80211.c\n@@ -63,7 +63,7 @@ static const struct genl_multicast_group nl80211_mcgrps[] = {\n #endif\n };\n \n-/* returns ERR_PTR values */\n+/* returns ERR_PTR values, requires RCU/cfg80211_mutex if rdev is %NULL */\n static struct wireless_dev *\n __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,\n \t\t\t   struct net *netns, struct nlattr **attrs)\n@@ -105,8 +105,6 @@ __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,\n \t\treturn result ?: ERR_PTR(-ENODEV);\n \t}\n \n-\tASSERT_RTNL();\n-\n \tfor_each_rdev(rdev) {\n \t\tstruct wireless_dev *wdev;\n \n@@ -116,7 +114,8 @@ __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,\n \t\tif (have_wdev_id \u0026\u0026 rdev-\u003ewiphy_idx != wiphy_idx)\n \t\t\tcontinue;\n \n-\t\tlist_for_each_entry(wdev, \u0026rdev-\u003ewiphy.wdev_list, list) {\n+\t\tlist_for_each_entry_rcu(wdev, \u0026rdev-\u003ewiphy.wdev_list, list,\n+\t\t\t\t\tlockdep_rtnl_is_held()) {\n \t\t\tif (have_ifidx \u0026\u0026 wdev-\u003enetdev \u0026\u0026\n \t\t\t    wdev-\u003enetdev-\u003eifindex == ifidx) {\n \t\t\t\tresult = wdev;\n@@ -137,14 +136,13 @@ __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,\n \treturn ERR_PTR(-ENODEV);\n }\n \n+/* requires RCU/cfg80211_mutex */\n static struct cfg80211_registered_device *\n __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)\n {\n \tstruct cfg80211_registered_device *rdev = NULL, *tmp;\n \tstruct net_device *netdev;\n \n-\tASSERT_RTNL();\n-\n \tif (!attrs[NL80211_ATTR_WIPHY] \u0026\u0026\n \t    !attrs[NL80211_ATTR_IFINDEX] \u0026\u0026\n \t    !attrs[NL80211_ATTR_WDEV])\n@@ -162,7 +160,8 @@ __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)\n \t\ttmp = cfg80211_rdev_by_wiphy_idx(wdev_id \u003e\u003e 32);\n \t\tif (tmp) {\n \t\t\t/* make sure wdev exists */\n-\t\t\tlist_for_each_entry(wdev, \u0026tmp-\u003ewiphy.wdev_list, list) {\n+\t\t\tlist_for_each_entry_rcu(wdev, \u0026tmp-\u003ewiphy.wdev_list,\n+\t\t\t\t\t\tlist, lockdep_rtnl_is_held()) {\n \t\t\t\tif (wdev-\u003eidentifier != (u32)wdev_id)\n \t\t\t\t\tcontinue;\n \t\t\t\tfound = true;\n@@ -181,14 +180,16 @@ __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)\n \tif (attrs[NL80211_ATTR_IFINDEX]) {\n \t\tint ifindex = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]);\n \n-\t\tnetdev = __dev_get_by_index(netns, ifindex);\n-\t\tif (netdev) {\n-\t\t\tif (netdev-\u003eieee80211_ptr)\n-\t\t\t\ttmp = wiphy_to_rdev(\n-\t\t\t\t\tnetdev-\u003eieee80211_ptr-\u003ewiphy);\n-\t\t\telse\n-\t\t\t\ttmp = NULL;\n+\t\t/* RCU required for dev_get_by_index_rcu() if we have RTNL */\n+\t\trcu_read_lock();\n+\t\tnetdev = dev_get_by_index_rcu(netns, ifindex);\n+\t\tif (netdev \u0026\u0026 netdev-\u003eieee80211_ptr)\n+\t\t\ttmp = wiphy_to_rdev(netdev-\u003eieee80211_ptr-\u003ewiphy);\n+\t\telse\n+\t\t\ttmp = NULL;\n+\t\trcu_read_unlock();\n \n+\t\tif (netdev) {\n \t\t\t/* not wireless device -- return error */\n \t\t\tif (!tmp)\n \t\t\t\treturn ERR_PTR(-EINVAL);\n@@ -223,6 +224,48 @@ cfg80211_get_dev_from_info(struct net *netns, struct genl_info *info)\n \treturn __cfg80211_rdev_from_attrs(netns, info-\u003eattrs);\n }\n \n+/*\n+ * Lock the given wiphy, put the device and double-check it's\n+ * still valid to use.\n+ *\n+ * Returns with wiphy mutex held unless there was an error.\n+ */\n+static struct wireless_dev *\n+nl80211_lock_and_recheck(struct cfg80211_registered_device *rdev,\n+\t\t\t struct net *netns, u32 wdev_id)\n+{\n+\tstruct wireless_dev *wdev;\n+\n+\twiphy_lock(\u0026rdev-\u003ewiphy);\n+\n+\tif (!rdev-\u003ewiphy.registered ||\n+\t    !net_eq(wiphy_net(\u0026rdev-\u003ewiphy), netns)) {\n+\t\twiphy_unlock(\u0026rdev-\u003ewiphy);\n+\t\tput_device(\u0026rdev-\u003ewiphy.dev);\n+\t\treturn ERR_PTR(-ENODEV);\n+\t}\n+\n+\t/*\n+\t * unregistration blocks on wiphy mutex,\n+\t * so we don't need this now\n+\t */\n+\tput_device(\u0026rdev-\u003ewiphy.dev);\n+\n+\tif (!wdev_id)\n+\t\treturn NULL;\n+\n+\tlist_for_each_entry(wdev, \u0026rdev-\u003ewiphy.wdev_list, list) {\n+\t\tif (wdev-\u003eidentifier != wdev_id)\n+\t\t\tcontinue;\n+\t\tif (wdev-\u003enetdev \u0026\u0026 !net_eq(dev_net(wdev-\u003enetdev), netns))\n+\t\t\tbreak;\n+\t\treturn wdev;\n+\t}\n+\twiphy_unlock(\u0026rdev-\u003ewiphy);\n+\n+\treturn ERR_PTR(-ENODEV);\n+}\n+\n static int validate_beacon_head(const struct nlattr *attr,\n \t\t\t\tstruct netlink_ext_ack *extack)\n {\n@@ -1260,6 +1303,8 @@ static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,\n \t\t\t\t     struct wireless_dev **wdev,\n \t\t\t\t     struct nlattr **attrbuf)\n {\n+\tstruct net *netns = sock_net(cb-\u003eskb-\u003esk);\n+\tu32 wdev_id;\n \tint err;\n \n \tif (!cb-\u003eargs[0]) {\n@@ -1281,60 +1326,37 @@ static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,\n \t\t\treturn err;\n \t\t}\n \n-\t\trtnl_lock();\n-\t\t*wdev = __cfg80211_wdev_from_attrs(NULL, sock_net(cb-\u003eskb-\u003esk),\n-\t\t\t\t\t\t   attrbuf);\n+\t\trcu_read_lock();\n+\t\t*wdev = __cfg80211_wdev_from_attrs(NULL, netns, attrbuf);\n \t\tkfree(attrbuf_free);\n \t\tif (IS_ERR(*wdev)) {\n-\t\t\trtnl_unlock();\n+\t\t\trcu_read_unlock();\n \t\t\treturn PTR_ERR(*wdev);\n \t\t}\n \t\t*rdev = wiphy_to_rdev((*wdev)-\u003ewiphy);\n-\t\tmutex_lock(\u0026(*rdev)-\u003ewiphy.mtx);\n-\t\trtnl_unlock();\n-\t\t/* 0 is the first index - add 1 to parse only once */\n-\t\tcb-\u003eargs[0] = (*rdev)-\u003ewiphy_idx + 1;\n-\t\tcb-\u003eargs[1] = (*wdev)-\u003eidentifier;\n+\t\twdev_id = (*wdev)-\u003eidentifier;\n \t} else {\n \t\t/* subtract the 1 again here */\n-\t\tstruct wiphy *wiphy;\n-\t\tstruct wireless_dev *tmp;\n-\n-\t\trtnl_lock();\n-\t\twiphy = wiphy_idx_to_wiphy(cb-\u003eargs[0] - 1);\n-\t\tif (!wiphy) {\n-\t\t\trtnl_unlock();\n-\t\t\treturn -ENODEV;\n-\t\t}\n-\n-\t\t/*\n-\t\t * The first invocation validated the wdev's netns against\n-\t\t * the caller via __cfg80211_wdev_from_attrs(). The wiphy\n-\t\t * may have moved netns between dumpit invocations (via\n-\t\t * NL80211_CMD_SET_WIPHY_NETNS), so re-check here.\n-\t\t */\n-\t\tif (!net_eq(wiphy_net(wiphy), sock_net(cb-\u003eskb-\u003esk))) {\n-\t\t\trtnl_unlock();\n+\t\trcu_read_lock();\n+\t\t*rdev = cfg80211_rdev_by_wiphy_idx(cb-\u003eargs[0] - 1);\n+\t\tif (!*rdev) {\n+\t\t\trcu_read_unlock();\n \t\t\treturn -ENODEV;\n \t\t}\n+\t\twdev_id = cb-\u003eargs[1];\n+\t}\n \n-\t\t*rdev = wiphy_to_rdev(wiphy);\n-\t\t*wdev = NULL;\n+\tget_device(\u0026(*rdev)-\u003ewiphy.dev);\n+\trcu_read_unlock();\n \n-\t\tlist_for_each_entry(tmp, \u0026(*rdev)-\u003ewiphy.wdev_list, list) {\n-\t\t\tif (tmp-\u003eidentifier == cb-\u003eargs[1]) {\n-\t\t\t\t*wdev = tmp;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t}\n+\t/* things may have changed since the lookup or the last dumpit call */\n+\t*wdev = nl80211_lock_and_recheck(*rdev, netns, wdev_id);\n+\tif (IS_ERR(*wdev))\n+\t\treturn PTR_ERR(*wdev);\n \n-\t\tif (!*wdev) {\n-\t\t\trtnl_unlock();\n-\t\t\treturn -ENODEV;\n-\t\t}\n-\t\tmutex_lock(\u0026(*rdev)-\u003ewiphy.mtx);\n-\t\trtnl_unlock();\n-\t}\n+\t/* 0 is the first index - add 1 to parse only once */\n+\tcb-\u003eargs[0] = (*rdev)-\u003ewiphy_idx + 1;\n+\tcb-\u003eargs[1] = wdev_id;\n \n \treturn 0;\n }\n@@ -1518,9 +1540,10 @@ static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy,\n \t\tgoto nla_put_failure;\n \n \tif (large) {\n-\t\tconst struct ieee80211_reg_rule *rule =\n-\t\t\tfreq_reg_info(wiphy, MHZ_TO_KHZ(chan-\u003ecenter_freq));\n+\t\tconst struct ieee80211_reg_rule *rule;\n \n+\t\tguard(rcu)();\n+\t\trule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan-\u003ecenter_freq));\n \t\tif (!IS_ERR_OR_NULL(rule) \u0026\u0026 rule-\u003ehas_wmm) {\n \t\t\tif (nl80211_msg_put_wmm_rules(msg, rule))\n \t\t\t\tgoto nla_put_failure;\n@@ -10924,7 +10947,7 @@ static int nl80211_get_reg_dump(struct sk_buff *skb,\n \n \t/* the global regdom is idx 0 */\n \treg_idx = 1;\n-\tlist_for_each_entry_rcu(rdev, \u0026cfg80211_rdev_list, list) {\n+\tfor_each_rdev(rdev) {\n \t\tregdom = get_wiphy_regdom(\u0026rdev-\u003ewiphy);\n \t\tif (!regdom)\n \t\t\tcontinue;\n@@ -11026,7 +11049,7 @@ static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)\n \t\t\treturn -EINVAL;\n \t}\n \n-\trtnl_lock();\n+\tmutex_lock(\u0026cfg80211_mutex);\n \tif (!reg_is_valid_request(alpha2)) {\n \t\tr = -EINVAL;\n \t\tgoto out;\n@@ -11074,7 +11097,7 @@ static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)\n  bad_reg:\n \tkfree(rd);\n  out:\n-\trtnl_unlock();\n+\tmutex_unlock(\u0026cfg80211_mutex);\n \treturn r;\n }\n #endif /* CONFIG_CFG80211_CRDA_SUPPORT */\n@@ -19669,6 +19692,18 @@ nl80211_epcs_cfg(struct sk_buff *skb, struct genl_info *info)\n \t\t NL80211_FLAG_NO_WIPHY_MTX |\t\t\\\n \t\t NL80211_FLAG_NEED_RTNL)\n \n+/*\n+ * Note: a\n+ *\tSELECTOR(...,\n+ *\t\t NL80211_FLAG_NO_WIPHY_MTX |\n+ *\t\t NL80211_FLAG_NEED_WDEV)\n+ * or\n+ *\tSELECTOR(..., NL80211_FLAG_NO_WIPHY_MTX)\n+ *\n+ * isn't valid - nothing would ensure the device stays around,\n+ * need at least a netdev, the wiphy mutex, or RTNL.\n+ */\n+\n enum nl80211_internal_flags_selector {\n #define SELECTOR(_, name, value)\tNL80211_IFL_SEL_##name,\n \tINTERNAL_FLAG_SELECTORS(_)\n@@ -19686,41 +19721,88 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,\n \t\t\t    struct genl_info *info)\n {\n \tstruct cfg80211_registered_device *rdev = NULL;\n+\tstruct net *netns = genl_info_net(info);\n \tstruct wireless_dev *wdev = NULL;\n \tstruct net_device *dev = NULL;\n-\tu32 internal_flags;\n+\tbool need_rtnl, locked = false;\n+\tu32 internal_flags, wdev_id = 0;\n \tint err;\n \n \tif (WARN_ON(ops-\u003einternal_flags \u003e= ARRAY_SIZE(nl80211_internal_flags)))\n \t\treturn -EINVAL;\n \n \tinternal_flags = nl80211_internal_flags[ops-\u003einternal_flags];\n+\tneed_rtnl = internal_flags \u0026 NL80211_FLAG_NEED_RTNL;\n+\n+\tif (need_rtnl)\n+\t\trtnl_lock();\n+\telse\n+\t\trcu_read_lock();\n \n-\trtnl_lock();\n \tif (internal_flags \u0026 NL80211_FLAG_NEED_WIPHY) {\n-\t\trdev = cfg80211_get_dev_from_info(genl_info_net(info), info);\n+\t\trdev = cfg80211_get_dev_from_info(netns, info);\n \t\tif (IS_ERR(rdev)) {\n \t\t\terr = PTR_ERR(rdev);\n-\t\t\tgoto out_unlock;\n+\t\t\tgoto out_lookup;\n \t\t}\n-\t\tinfo-\u003euser_ptr[0] = rdev;\n \t} else if (internal_flags \u0026 NL80211_FLAG_NEED_NETDEV ||\n \t\t   internal_flags \u0026 NL80211_FLAG_NEED_WDEV) {\n-\t\twdev = __cfg80211_wdev_from_attrs(NULL, genl_info_net(info),\n-\t\t\t\t\t\t  info-\u003eattrs);\n+\t\twdev = __cfg80211_wdev_from_attrs(NULL, netns, info-\u003eattrs);\n \t\tif (IS_ERR(wdev)) {\n \t\t\terr = PTR_ERR(wdev);\n-\t\t\tgoto out_unlock;\n+\t\t\tgoto out_lookup;\n \t\t}\n \n \t\tdev = wdev-\u003enetdev;\n \t\tdev_hold(dev);\n \t\trdev = wiphy_to_rdev(wdev-\u003ewiphy);\n+\t}\n+\n+\tif (!need_rtnl) {\n+\t\tif (rdev)\n+\t\t\tget_device(\u0026rdev-\u003ewiphy.dev);\n+\t\tif (wdev)\n+\t\t\twdev_id = wdev-\u003eidentifier;\n+\t\trcu_read_unlock();\n+\n+\t\t/* RTM_NEWLINK is sent before NETDEV_UP, wait for dev_open() */\n+\t\tif (internal_flags \u0026 NL80211_FLAG_CHECK_NETDEV_UP \u0026\u0026 dev \u0026\u0026\n+\t\t    !READ_ONCE(wdev-\u003eis_running) \u0026\u0026 netif_running(dev)) {\n+\t\t\trtnl_lock();\n+\t\t\trtnl_unlock();\n+\t\t}\n+\t}\n+\n+\tif (rdev \u0026\u0026 !(internal_flags \u0026 NL80211_FLAG_NO_WIPHY_MTX)) {\n+\t\tif (need_rtnl) {\n+\t\t\twiphy_lock(\u0026rdev-\u003ewiphy);\n+\t\t} else {\n+\t\t\t/* without rtnl, things may have changed since lookup */\n+\t\t\twdev = nl80211_lock_and_recheck(rdev, netns, wdev_id);\n+\t\t\tif (IS_ERR(wdev)) {\n+\t\t\t\terr = PTR_ERR(wdev);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t\tlocked = true;\n+\t} else if (rdev \u0026\u0026 !need_rtnl) {\n+\t\t/*\n+\t\t * Without any mutex need a netdev to keep it alive, see\n+\t\t * also the note about selectors - the WARN can't happen\n+\t\t * without wrong selectors and those don't exist.\n+\t\t */\n+\t\tput_device(\u0026rdev-\u003ewiphy.dev);\n+\t\tif (WARN_ON(!dev)) {\n+\t\t\terr = -EINVAL;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n \n+\tif (wdev) {\n \t\tif (internal_flags \u0026 NL80211_FLAG_NEED_NETDEV) {\n \t\t\tif (!dev) {\n \t\t\t\terr = -EINVAL;\n-\t\t\t\tgoto out_unlock;\n+\t\t\t\tgoto out;\n \t\t\t}\n \n \t\t\tinfo-\u003euser_ptr[1] = dev;\n@@ -19731,10 +19813,8 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,\n \t\tif (internal_flags \u0026 NL80211_FLAG_CHECK_NETDEV_UP \u0026\u0026\n \t\t    !wdev_running(wdev)) {\n \t\t\terr = -ENETDOWN;\n-\t\t\tgoto out_unlock;\n+\t\t\tgoto out;\n \t\t}\n-\n-\t\tinfo-\u003euser_ptr[0] = rdev;\n \t}\n \n \tif (internal_flags \u0026 NL80211_FLAG_MLO_VALID_LINK_ID) {\n@@ -19742,7 +19822,7 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,\n \n \t\tif (!wdev) {\n \t\t\terr = -EINVAL;\n-\t\t\tgoto out_unlock;\n+\t\t\tgoto out;\n \t\t}\n \n \t\t/* MLO -\u003e require valid link ID */\n@@ -19750,13 +19830,13 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,\n \t\t    (!link_id ||\n \t\t     !(wdev-\u003evalid_links \u0026 BIT(nla_get_u8(link_id))))) {\n \t\t\terr = -EINVAL;\n-\t\t\tgoto out_unlock;\n+\t\t\tgoto out;\n \t\t}\n \n \t\t/* non-MLO -\u003e no link ID attribute accepted */\n \t\tif (!wdev-\u003evalid_links \u0026\u0026 link_id) {\n \t\t\terr = -EINVAL;\n-\t\t\tgoto out_unlock;\n+\t\t\tgoto out;\n \t\t}\n \t}\n \n@@ -19764,22 +19844,28 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,\n \t\tif (info-\u003eattrs[NL80211_ATTR_MLO_LINK_ID] ||\n \t\t    (wdev \u0026\u0026 wdev-\u003evalid_links)) {\n \t\t\terr = -EINVAL;\n-\t\t\tgoto out_unlock;\n+\t\t\tgoto out;\n \t\t}\n \t}\n \n-\tif (rdev \u0026\u0026 !(internal_flags \u0026 NL80211_FLAG_NO_WIPHY_MTX)) {\n-\t\twiphy_lock(\u0026rdev-\u003ewiphy);\n-\t\t/* we keep the mutex locked until post_doit */\n+\tinfo-\u003euser_ptr[0] = rdev;\n+\t/* we keep the mutex locked until post_doit */\n+\tif (locked)\n \t\t__release(\u0026rdev-\u003ewiphy.mtx);\n-\t}\n-\tif (!(internal_flags \u0026 NL80211_FLAG_NEED_RTNL))\n-\t\trtnl_unlock();\n \n \treturn 0;\n-out_unlock:\n-\trtnl_unlock();\n+out:\n+\tif (locked)\n+\t\twiphy_unlock(\u0026rdev-\u003ewiphy);\n \tdev_put(dev);\n+\tif (need_rtnl)\n+\t\trtnl_unlock();\n+\treturn err;\n+out_lookup:\n+\tif (need_rtnl)\n+\t\trtnl_unlock();\n+\telse\n+\t\trcu_read_unlock();\n \treturn err;\n }\n \n@@ -21115,7 +21201,10 @@ static bool nl80211_reg_change_event_fill(struct sk_buff *msg,\n \t}\n \n \tif (request-\u003ewiphy_idx != WIPHY_IDX_INVALID) {\n-\t\tstruct wiphy *wiphy = wiphy_idx_to_wiphy(request-\u003ewiphy_idx);\n+\t\tstruct wiphy *wiphy;\n+\n+\t\tguard(rcu)();\n+\t\twiphy = wiphy_idx_to_wiphy(request-\u003ewiphy_idx);\n \n \t\tif (wiphy \u0026\u0026\n \t\t    nla_put_u32(msg, NL80211_ATTR_WIPHY, request-\u003ewiphy_idx))\n@@ -23357,7 +23446,7 @@ static int nl80211_netlink_notify(struct notifier_block * nb,\n \n \trcu_read_lock();\n \n-\tlist_for_each_entry_rcu(rdev, \u0026cfg80211_rdev_list, list) {\n+\tfor_each_rdev(rdev) {\n \t\tstruct cfg80211_sched_scan_request *sched_scan_req;\n \n \t\tlist_for_each_entry_rcu(sched_scan_req,\ndiff --git a/net/wireless/reg.c b/net/wireless/reg.c\nindex b7ef479a74f01..4efb35baeb3e1 100644\n--- a/net/wireless/reg.c\n+++ b/net/wireless/reg.c\n@@ -88,7 +88,7 @@ static struct regulatory_request core_request_world = {\n \n /*\n  * Receipt of information from last regulatory request,\n- * protected by RTNL (and can be accessed with RCU protection)\n+ * protected by cfg80211_mutex (and can be accessed with RCU protection)\n  */\n static struct regulatory_request __rcu *last_request =\n \t(void __force __rcu *)\u0026core_request_world;\n@@ -100,14 +100,14 @@ static struct faux_device *reg_fdev;\n  * Central wireless core regulatory domains, we only need two,\n  * the current one and a world regulatory domain in case we have no\n  * information to give us an alpha2.\n- * (protected by RTNL, can be read under RCU)\n+ * (protected by cfg80211_mutex, can be read under RCU)\n  */\n const struct ieee80211_regdomain __rcu *cfg80211_regdomain;\n \n /*\n  * Number of devices that registered to the core\n  * that support cellular base station regulatory hints\n- * (protected by RTNL)\n+ * (protected by cfg80211_mutex)\n  */\n static int reg_num_devs_support_basehint;\n \n@@ -128,19 +128,19 @@ static void reg_process_hint(struct regulatory_request *reg_request);\n \n static const struct ieee80211_regdomain *get_cfg80211_regdom(void)\n {\n-\treturn rcu_dereference_rtnl(cfg80211_regdomain);\n+\treturn rcu_dereference_check(cfg80211_regdomain,\n+\t\t\t\t     lockdep_is_held(\u0026cfg80211_mutex));\n }\n \n /*\n  * Returns the regulatory domain associated with the wiphy.\n  *\n- * Requires any of RTNL, wiphy mutex or RCU protection.\n+ * Requires the wiphy mutex or RCU protection.\n  */\n const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy)\n {\n \treturn rcu_dereference_check(wiphy-\u003eregd,\n-\t\t\t\t     lockdep_is_held(\u0026wiphy-\u003emtx) ||\n-\t\t\t\t     lockdep_rtnl_is_held());\n+\t\t\t\t     lockdep_is_held(\u0026wiphy-\u003emtx));\n }\n EXPORT_SYMBOL(get_wiphy_regdom);\n \n@@ -204,7 +204,8 @@ static void rcu_free_regdom(const struct ieee80211_regdomain *r)\n \n static struct regulatory_request *get_last_request(void)\n {\n-\treturn rcu_dereference_rtnl(last_request);\n+\treturn rcu_dereference_check(last_request,\n+\t\t\t\t     lockdep_is_held(\u0026cfg80211_mutex));\n }\n \n /* Used to queue up regulatory hints */\n@@ -314,7 +315,7 @@ static void reset_regdomains(bool full_reset,\n {\n \tconst struct ieee80211_regdomain *r;\n \n-\tASSERT_RTNL();\n+\tlockdep_assert_held(\u0026cfg80211_mutex);\n \n \tr = get_cfg80211_regdom();\n \n@@ -456,7 +457,7 @@ reg_copy_regd(const struct ieee80211_regdomain *src_regd)\n \n static void cfg80211_save_user_regdom(const struct ieee80211_regdomain *rd)\n {\n-\tASSERT_RTNL();\n+\tlockdep_assert_held(\u0026cfg80211_mutex);\n \n \tif (!IS_ERR(cfg80211_user_regdom))\n \t\tkfree(cfg80211_user_regdom);\n@@ -475,7 +476,7 @@ static void reg_regdb_apply(struct work_struct *work)\n {\n \tstruct reg_regdb_apply_request *request;\n \n-\trtnl_lock();\n+\tmutex_lock(\u0026cfg80211_mutex);\n \n \tmutex_lock(\u0026reg_regdb_apply_mutex);\n \twhile (!list_empty(\u0026reg_regdb_apply_list)) {\n@@ -489,7 +490,7 @@ static void reg_regdb_apply(struct work_struct *work)\n \t}\n \tmutex_unlock(\u0026reg_regdb_apply_mutex);\n \n-\trtnl_unlock();\n+\tmutex_unlock(\u0026cfg80211_mutex);\n }\n \n static DECLARE_WORK(reg_regdb_work, reg_regdb_apply);\n@@ -526,10 +527,10 @@ static DECLARE_DELAYED_WORK(crda_timeout, crda_timeout_work);\n static void crda_timeout_work(struct work_struct *work)\n {\n \tpr_debug(\"Timeout while waiting for CRDA to reply, restoring regulatory settings\\n\");\n-\trtnl_lock();\n+\tmutex_lock(\u0026cfg80211_mutex);\n \treg_crda_timeouts++;\n \trestore_regulatory_settings(true, false);\n-\trtnl_unlock();\n+\tmutex_unlock(\u0026cfg80211_mutex);\n }\n \n static void cancel_crda_timeout(void)\n@@ -590,7 +591,7 @@ static inline int call_crda(const char *alpha2)\n #endif /* CONFIG_CFG80211_CRDA_SUPPORT */\n \n /* code to directly load a firmware database through request_firmware */\n-static const struct fwdb_header *regdb;\n+static const struct fwdb_header __rcu *global_regdb;\n \n struct fwdb_country {\n \tu8 alpha2[2];\n@@ -892,16 +893,19 @@ static int __regdb_query_wmm(const struct fwdb_header *db,\n \n int reg_query_regdb_wmm(char *alpha2, int freq, struct ieee80211_reg_rule *rule)\n {\n-\tconst struct fwdb_header *hdr = regdb;\n \tconst struct fwdb_country *country;\n+\tconst struct fwdb_header *regdb;\n+\n+\tguard(rcu)();\n \n+\tregdb = rcu_dereference(global_regdb);\n \tif (!regdb)\n \t\treturn -ENODATA;\n \n \tif (IS_ERR(regdb))\n \t\treturn PTR_ERR(regdb);\n \n-\tcountry = \u0026hdr-\u003ecountry[0];\n+\tcountry = \u0026regdb-\u003ecountry[0];\n \twhile (country-\u003ecoll_ptr) {\n \t\tif (alpha2_equal(alpha2, country-\u003ealpha2))\n \t\t\treturn __regdb_query_wmm(regdb, country, freq, rule);\n@@ -970,15 +974,16 @@ static int regdb_query_country(const struct fwdb_header *db,\n \n static int query_regdb(const char *alpha2)\n {\n-\tconst struct fwdb_header *hdr = regdb;\n \tconst struct fwdb_country *country;\n+\tconst struct fwdb_header *regdb;\n \n-\tASSERT_RTNL();\n+\tregdb = rcu_dereference_protected(global_regdb,\n+\t\t\t\t\t  lockdep_is_held(\u0026cfg80211_mutex));\n \n \tif (IS_ERR(regdb))\n \t\treturn PTR_ERR(regdb);\n \n-\tcountry = \u0026hdr-\u003ecountry[0];\n+\tcountry = \u0026regdb-\u003ecountry[0];\n \twhile (country-\u003ecoll_ptr) {\n \t\tif (alpha2_equal(alpha2, country-\u003ealpha2))\n \t\t\treturn regdb_query_country(regdb, country);\n@@ -990,6 +995,7 @@ static int query_regdb(const char *alpha2)\n \n static void regdb_fw_cb(const struct firmware *fw, void *context)\n {\n+\tconst struct fwdb_header *regdb;\n \tint set_error = 0;\n \tbool restore = true;\n \tvoid *db;\n@@ -1002,7 +1008,9 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)\n \t\tset_error = -EINVAL;\n \t}\n \n-\trtnl_lock();\n+\tmutex_lock(\u0026cfg80211_mutex);\n+\tregdb = rcu_dereference_protected(global_regdb,\n+\t\t\t\t\t  lockdep_is_held(\u0026cfg80211_mutex));\n \tif (regdb \u0026\u0026 !IS_ERR(regdb)) {\n \t\t/* negative case - a bug\n \t\t * positive case - can happen due to race in case of multiple cb's in\n@@ -1011,11 +1019,11 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)\n \t\t * Either case, just restore and free new db.\n \t\t */\n \t} else if (set_error) {\n-\t\tregdb = ERR_PTR(set_error);\n+\t\trcu_assign_pointer(global_regdb, ERR_PTR(set_error));\n \t} else if (fw) {\n \t\tdb = kmemdup(fw-\u003edata, fw-\u003esize, GFP_KERNEL);\n \t\tif (db) {\n-\t\t\tregdb = db;\n+\t\t\trcu_assign_pointer(global_regdb, db);\n \t\t\trestore = context \u0026\u0026 query_regdb(context);\n \t\t} else {\n \t\t\trestore = true;\n@@ -1025,7 +1033,7 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)\n \tif (restore)\n \t\trestore_regulatory_settings(true, false);\n \n-\trtnl_unlock();\n+\tmutex_unlock(\u0026cfg80211_mutex);\n \n \tkfree(context);\n \n@@ -1038,9 +1046,9 @@ static int query_regdb_file(const char *alpha2)\n {\n \tint err;\n \n-\tASSERT_RTNL();\n+\tlockdep_assert_held(\u0026cfg80211_mutex);\n \n-\tif (regdb)\n+\tif (rcu_access_pointer(global_regdb))\n \t\treturn query_regdb(alpha2);\n \n \talpha2 = kmemdup(alpha2, 2, GFP_KERNEL);\n@@ -1058,8 +1066,8 @@ static int query_regdb_file(const char *alpha2)\n \n int reg_reload_regdb(void)\n {\n+\tconst struct fwdb_header *db, *old;\n \tconst struct firmware *fw;\n-\tvoid *db;\n \tint err;\n \tconst struct ieee80211_regdomain *current_regdomain;\n \tstruct regulatory_request *request;\n@@ -1079,10 +1087,9 @@ int reg_reload_regdb(void)\n \t\tgoto out;\n \t}\n \n-\trtnl_lock();\n-\tif (!IS_ERR_OR_NULL(regdb))\n-\t\tkfree(regdb);\n-\tregdb = db;\n+\tmutex_lock(\u0026cfg80211_mutex);\n+\told = rcu_replace_pointer(global_regdb, db,\n+\t\t\t\t  lockdep_is_held(\u0026cfg80211_mutex));\n \n \t/* reset regulatory domain */\n \tcurrent_regdomain = get_cfg80211_regdom();\n@@ -1102,7 +1109,12 @@ int reg_reload_regdb(void)\n \treg_process_hint(request);\n \n out_unlock:\n-\trtnl_unlock();\n+\tmutex_unlock(\u0026cfg80211_mutex);\n+\n+\tif (!IS_ERR_OR_NULL(old)) {\n+\t\tsynchronize_rcu();\n+\t\tkfree(old);\n+\t}\n  out:\n \trelease_firmware(fw);\n \treturn err;\n@@ -2141,35 +2153,38 @@ static bool reg_is_world_roaming(struct wiphy *wiphy)\n static void reg_call_notifier(struct wiphy *wiphy,\n \t\t\t      struct regulatory_request *request)\n {\n+\tlockdep_assert_wiphy(wiphy);\n+\n \tif (wiphy-\u003ereg_notifier)\n \t\twiphy-\u003ereg_notifier(wiphy, request);\n }\n \n-static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,\n+static bool handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,\n \t\t\t      struct reg_beacon *reg_beacon)\n {\n \tstruct ieee80211_supported_band *sband;\n \tstruct ieee80211_channel *chan;\n \tbool channel_changed = false;\n \tstruct ieee80211_channel chan_before;\n-\tstruct regulatory_request *lr = get_last_request();\n+\n+\tlockdep_assert_wiphy(wiphy);\n \n \tsband = wiphy-\u003ebands[reg_beacon-\u003echan.band];\n \tchan = \u0026sband-\u003echannels[chan_idx];\n \n \tif (likely(!ieee80211_channel_equal(chan, \u0026reg_beacon-\u003echan)))\n-\t\treturn;\n+\t\treturn false;\n \n \tif (chan-\u003ebeacon_found)\n-\t\treturn;\n+\t\treturn false;\n \n \tchan-\u003ebeacon_found = true;\n \n \tif (!reg_is_world_roaming(wiphy))\n-\t\treturn;\n+\t\treturn false;\n \n \tif (wiphy-\u003eregulatory_flags \u0026 REGULATORY_DISABLE_BEACON_HINTS)\n-\t\treturn;\n+\t\treturn false;\n \n \tchan_before = *chan;\n \n@@ -2178,11 +2193,10 @@ static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,\n \t\tchannel_changed = true;\n \t}\n \n-\tif (channel_changed) {\n+\tif (channel_changed)\n \t\tnl80211_send_beacon_hint_event(wiphy, \u0026chan_before, chan);\n-\t\tif (wiphy-\u003eflags \u0026 WIPHY_FLAG_CHANNEL_CHANGE_ON_BEACON)\n-\t\t\treg_call_notifier(wiphy, lr);\n-\t}\n+\n+\treturn channel_changed;\n }\n \n /*\n@@ -2194,14 +2208,20 @@ static void wiphy_update_new_beacon(struct wiphy *wiphy,\n {\n \tunsigned int i;\n \tstruct ieee80211_supported_band *sband;\n+\tbool changed = false;\n \n \tif (!wiphy-\u003ebands[reg_beacon-\u003echan.band])\n \t\treturn;\n \n \tsband = wiphy-\u003ebands[reg_beacon-\u003echan.band];\n \n+\tguard(wiphy)(wiphy);\n+\n \tfor (i = 0; i \u003c sband-\u003en_channels; i++)\n-\t\thandle_reg_beacon(wiphy, i, reg_beacon);\n+\t\tchanged |= handle_reg_beacon(wiphy, i, reg_beacon);\n+\n+\tif (changed \u0026\u0026 wiphy-\u003eflags \u0026 WIPHY_FLAG_CHANNEL_CHANGE_ON_BEACON)\n+\t\treg_call_notifier(wiphy, get_last_request());\n }\n \n /*\n@@ -2507,7 +2527,7 @@ static void reg_check_chans_work(struct work_struct *work)\n \n \trcu_read_lock();\n \n-\tlist_for_each_entry_rcu(rdev, \u0026cfg80211_rdev_list, list)\n+\tfor_each_rdev(rdev)\n \t\twiphy_work_queue(\u0026rdev-\u003ewiphy, \u0026rdev-\u003ereg_check_chans_wk);\n \n \trcu_read_unlock();\n@@ -2530,6 +2550,8 @@ static void wiphy_update_regulatory(struct wiphy *wiphy,\n \tenum nl80211_band band;\n \tstruct regulatory_request *lr = get_last_request();\n \n+\tlockdep_assert_wiphy(wiphy);\n+\n \tif (ignore_reg_update(wiphy, initiator)) {\n \t\t/*\n \t\t * Regulatory updates set by CORE are ignored for custom\n@@ -2546,23 +2568,27 @@ static void wiphy_update_regulatory(struct wiphy *wiphy,\n \n \tlr-\u003edfs_region = get_cfg80211_regdom()-\u003edfs_region;\n \n+\t/* the notifier is called below, so ignore beacon hint changes */\n \tfor (band = 0; band \u003c NUM_NL80211_BANDS; band++)\n \t\thandle_band(wiphy, initiator, wiphy-\u003ebands[band]);\n \n \treg_process_beacons(wiphy);\n \treg_process_ht_flags(wiphy);\n+\n \treg_call_notifier(wiphy, lr);\n }\n \n static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator)\n {\n \tstruct cfg80211_registered_device *rdev;\n-\tstruct wiphy *wiphy;\n \n-\tASSERT_RTNL();\n+\tlockdep_assert_held(\u0026cfg80211_mutex);\n \n \tfor_each_rdev(rdev) {\n-\t\twiphy = \u0026rdev-\u003ewiphy;\n+\t\tstruct wiphy *wiphy = \u0026rdev-\u003ewiphy;\n+\n+\t\tguard(wiphy)(wiphy);\n+\n \t\twiphy_update_regulatory(wiphy, initiator);\n \t}\n \n@@ -2676,13 +2702,11 @@ void wiphy_apply_custom_regulatory(struct wiphy *wiphy,\n \tif (IS_ERR(new_regd))\n \t\treturn;\n \n-\trtnl_lock();\n \tscoped_guard(wiphy, wiphy) {\n \t\ttmp = get_wiphy_regdom(wiphy);\n \t\trcu_assign_pointer(wiphy-\u003eregd, new_regd);\n \t\trcu_free_regdom(tmp);\n \t}\n-\trtnl_unlock();\n }\n EXPORT_SYMBOL(wiphy_apply_custom_regulatory);\n \n@@ -2849,12 +2873,12 @@ reg_process_hint_driver(struct wiphy *wiphy,\n \t\tif (IS_ERR(regd))\n \t\t\treturn REG_REQ_IGNORE;\n \n-\t\ttmp = get_wiphy_regdom(wiphy);\n-\t\tASSERT_RTNL();\n+\t\tlockdep_assert_held(\u0026cfg80211_mutex);\n \t\tscoped_guard(wiphy, wiphy) {\n+\t\t\ttmp = get_wiphy_regdom(wiphy);\n \t\t\trcu_assign_pointer(wiphy-\u003eregd, regd);\n+\t\t\trcu_free_regdom(tmp);\n \t\t}\n-\t\trcu_free_regdom(tmp);\n \t}\n \n \n@@ -3042,7 +3066,8 @@ static void wiphy_all_share_dfs_chan_state(struct wiphy *wiphy)\n {\n \tstruct cfg80211_registered_device *rdev;\n \n-\tASSERT_RTNL();\n+\tlockdep_assert_held(\u0026cfg80211_mutex);\n+\tlockdep_assert_wiphy(wiphy);\n \n \tfor_each_rdev(rdev) {\n \t\tif (wiphy == \u0026rdev-\u003ewiphy)\n@@ -3094,6 +3119,8 @@ static void reg_process_hint(struct regulatory_request *reg_request)\n \t */\n \tif (treatment == REG_REQ_ALREADY_SET \u0026\u0026 wiphy \u0026\u0026\n \t    wiphy-\u003eregulatory_flags \u0026 REGULATORY_STRICT_REG) {\n+\t\tguard(wiphy)(wiphy);\n+\n \t\twiphy_update_regulatory(wiphy, initiator);\n \t\twiphy_all_share_dfs_chan_state(wiphy);\n \t\treg_check_channels();\n@@ -3108,13 +3135,16 @@ static void reg_process_hint(struct regulatory_request *reg_request)\n static void notify_self_managed_wiphys(struct regulatory_request *request)\n {\n \tstruct cfg80211_registered_device *rdev;\n-\tstruct wiphy *wiphy;\n \n \tfor_each_rdev(rdev) {\n-\t\twiphy = \u0026rdev-\u003ewiphy;\n+\t\tstruct wiphy *wiphy = \u0026rdev-\u003ewiphy;\n+\n \t\tif (wiphy-\u003eregulatory_flags \u0026 REGULATORY_WIPHY_SELF_MANAGED \u0026\u0026\n-\t\t    request-\u003einitiator == NL80211_REGDOM_SET_BY_USER)\n+\t\t    request-\u003einitiator == NL80211_REGDOM_SET_BY_USER) {\n+\t\t\tguard(wiphy)(wiphy);\n+\n \t\t\treg_call_notifier(wiphy, request);\n+\t\t}\n \t}\n }\n \n@@ -3166,12 +3196,14 @@ static void reg_process_pending_beacon_hints(void)\n {\n \tstruct cfg80211_registered_device *rdev;\n \tstruct reg_beacon *pending_beacon, *tmp;\n+\tLIST_HEAD(pending);\n \n \t/* This goes through the _pending_ beacon list */\n \tspin_lock_bh(\u0026reg_pending_beacons_lock);\n+\tlist_splice_tail_init(\u0026reg_pending_beacons, \u0026pending);\n+\tspin_unlock_bh(\u0026reg_pending_beacons_lock);\n \n-\tlist_for_each_entry_safe(pending_beacon, tmp,\n-\t\t\t\t \u0026reg_pending_beacons, list) {\n+\tlist_for_each_entry_safe(pending_beacon, tmp, \u0026pending, list) {\n \t\tlist_del_init(\u0026pending_beacon-\u003elist);\n \n \t\t/* Applies the beacon hint to current wiphys */\n@@ -3181,8 +3213,6 @@ static void reg_process_pending_beacon_hints(void)\n \t\t/* Remembers the beacon hint for new wiphys or reg changes */\n \t\tlist_add_tail(\u0026pending_beacon-\u003elist, \u0026reg_beacon_list);\n \t}\n-\n-\tspin_unlock_bh(\u0026reg_pending_beacons_lock);\n }\n \n static void reg_process_self_managed_hint(struct wiphy *wiphy)\n@@ -3193,7 +3223,6 @@ static void reg_process_self_managed_hint(struct wiphy *wiphy)\n \tenum nl80211_band band;\n \tstruct regulatory_request request = {};\n \n-\tASSERT_RTNL();\n \tlockdep_assert_wiphy(wiphy);\n \n \tspin_lock(\u0026reg_requests_lock);\n@@ -3228,7 +3257,7 @@ static void reg_process_self_managed_hints(void)\n {\n \tstruct cfg80211_registered_device *rdev;\n \n-\tASSERT_RTNL();\n+\tlockdep_assert_held(\u0026cfg80211_mutex);\n \n \tfor_each_rdev(rdev) {\n \t\tguard(wiphy)(\u0026rdev-\u003ewiphy);\n@@ -3241,11 +3270,11 @@ static void reg_process_self_managed_hints(void)\n \n static void reg_todo(struct work_struct *work)\n {\n-\trtnl_lock();\n+\tguard(mutex)(\u0026cfg80211_mutex);\n+\n \treg_process_pending_hints();\n \treg_process_pending_beacon_hints();\n \treg_process_self_managed_hints();\n-\trtnl_unlock();\n }\n \n static void queue_regulatory_request(struct regulatory_request *request)\n@@ -3524,7 +3553,7 @@ static void restore_regulatory_settings(bool reset_user, bool cached)\n \tLIST_HEAD(tmp_reg_req_list);\n \tstruct cfg80211_registered_device *rdev;\n \n-\tASSERT_RTNL();\n+\tlockdep_assert_held(\u0026cfg80211_mutex);\n \n \t/*\n \t * Clear the indoor setting in case that it is not controlled by user\n@@ -3571,8 +3600,11 @@ static void restore_regulatory_settings(bool reset_user, bool cached)\n \tfor_each_rdev(rdev) {\n \t\tif (rdev-\u003ewiphy.regulatory_flags \u0026 REGULATORY_WIPHY_SELF_MANAGED)\n \t\t\tcontinue;\n-\t\tif (rdev-\u003ewiphy.regulatory_flags \u0026 REGULATORY_CUSTOM_REG)\n+\t\tif (rdev-\u003ewiphy.regulatory_flags \u0026 REGULATORY_CUSTOM_REG) {\n+\t\t\tguard(wiphy)(\u0026rdev-\u003ewiphy);\n+\n \t\t\trestore_custom_reg_settings(\u0026rdev-\u003ewiphy);\n+\t\t}\n \t}\n \n \tif (cached \u0026\u0026 (!is_an_alpha2(alpha2) ||\n@@ -3639,6 +3671,8 @@ static bool is_wiphy_all_set_reg_flag(enum ieee80211_regulatory_flags flag)\n \n void regulatory_hint_disconnect(void)\n {\n+\tguard(mutex)(\u0026cfg80211_mutex);\n+\n \t/* Restore of regulatory settings is not required when wiphy(s)\n \t * ignore IE from connected access point but clearance of beacon hints\n \t * is required when wiphy(s) supports beacon hints.\n@@ -3907,7 +3941,7 @@ static int reg_set_rd_driver(const struct ieee80211_regdomain *rd,\n \t\treturn -ENODEV;\n \n \tif (!driver_request-\u003eintersect) {\n-\t\tASSERT_RTNL();\n+\t\tlockdep_assert_held(\u0026cfg80211_mutex);\n \t\tscoped_guard(wiphy, request_wiphy) {\n \t\t\tif (request_wiphy-\u003eregd)\n \t\t\t\ttmp = get_wiphy_regdom(request_wiphy);\n@@ -3933,9 +3967,11 @@ static int reg_set_rd_driver(const struct ieee80211_regdomain *rd,\n \t * However if a driver requested this specific regulatory\n \t * domain we keep it for its private use\n \t */\n-\ttmp = get_wiphy_regdom(request_wiphy);\n-\trcu_assign_pointer(request_wiphy-\u003eregd, rd);\n-\trcu_free_regdom(tmp);\n+\tscoped_guard(wiphy, request_wiphy) {\n+\t\ttmp = get_wiphy_regdom(request_wiphy);\n+\t\trcu_assign_pointer(request_wiphy-\u003eregd, rd);\n+\t\trcu_free_regdom(tmp);\n+\t}\n \n \trd = NULL;\n \n@@ -4108,7 +4144,7 @@ int regulatory_set_wiphy_regd_sync(struct wiphy *wiphy,\n {\n \tint ret;\n \n-\tASSERT_RTNL();\n+\tlockdep_assert_wiphy(wiphy);\n \n \tret = __regulatory_set_wiphy_regd(wiphy, rd);\n \tif (ret)\n@@ -4125,25 +4161,30 @@ void wiphy_regulatory_register(struct wiphy *wiphy)\n {\n \tstruct regulatory_request *lr = get_last_request();\n \n-\t/* self-managed devices ignore beacon hints and country IE */\n-\tif (wiphy-\u003eregulatory_flags \u0026 REGULATORY_WIPHY_SELF_MANAGED) {\n-\t\twiphy-\u003eregulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS |\n-\t\t\t\t\t   REGULATORY_COUNTRY_IE_IGNORE;\n+\tlockdep_assert_held(\u0026cfg80211_mutex);\n \n-\t\t/*\n-\t\t * The last request may have been received before this\n-\t\t * registration call. Call the driver notifier if\n-\t\t * initiator is USER.\n-\t\t */\n-\t\tif (lr-\u003einitiator == NL80211_REGDOM_SET_BY_USER)\n-\t\t\treg_call_notifier(wiphy, lr);\n-\t}\n+\tscoped_guard(wiphy, wiphy) {\n+\t\t/* self-managed devices ignore beacon hints and country IE */\n+\t\tif (wiphy-\u003eregulatory_flags \u0026 REGULATORY_WIPHY_SELF_MANAGED) {\n+\t\t\twiphy-\u003eregulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS |\n+\t\t\t\t\t\t   REGULATORY_COUNTRY_IE_IGNORE;\n \n-\tif (!reg_dev_ignore_cell_hint(wiphy))\n-\t\treg_num_devs_support_basehint++;\n+\t\t\t/*\n+\t\t\t * The last request may have been received before this\n+\t\t\t * registration call. Call the driver notifier if\n+\t\t\t * initiator is USER.\n+\t\t\t */\n+\t\t\tif (lr-\u003einitiator == NL80211_REGDOM_SET_BY_USER)\n+\t\t\t\treg_call_notifier(wiphy, lr);\n+\t\t}\n+\n+\t\tif (!reg_dev_ignore_cell_hint(wiphy))\n+\t\t\treg_num_devs_support_basehint++;\n+\n+\t\twiphy_update_regulatory(wiphy, lr-\u003einitiator);\n+\t\twiphy_all_share_dfs_chan_state(wiphy);\n+\t}\n \n-\twiphy_update_regulatory(wiphy, lr-\u003einitiator);\n-\twiphy_all_share_dfs_chan_state(wiphy);\n \treg_process_self_managed_hints();\n }\n \n@@ -4152,6 +4193,8 @@ void wiphy_regulatory_deregister(struct wiphy *wiphy)\n \tstruct wiphy *request_wiphy = NULL;\n \tstruct regulatory_request *lr;\n \n+\tlockdep_assert_held(\u0026cfg80211_mutex);\n+\n \tlr = get_last_request();\n \n \tif (!reg_dev_ignore_cell_hint(wiphy))\n@@ -4255,7 +4298,7 @@ static void cfg80211_check_and_end_cac(struct cfg80211_registered_device *rdev)\n \tstruct wireless_dev *wdev;\n \tunsigned int link_id;\n \n-\tguard(wiphy)(\u0026rdev-\u003ewiphy);\n+\tlockdep_assert_wiphy(\u0026rdev-\u003ewiphy);\n \n \t/* If we finished CAC or received radar, we should end any\n \t * CAC running on the same channels.\n@@ -4290,7 +4333,7 @@ void regulatory_propagate_dfs_state(struct wiphy *wiphy,\n {\n \tstruct cfg80211_registered_device *rdev;\n \n-\tASSERT_RTNL();\n+\tlockdep_assert_held(\u0026cfg80211_mutex);\n \n \tif (WARN_ON(!cfg80211_chandef_valid(chandef)))\n \t\treturn;\n@@ -4306,6 +4349,8 @@ void regulatory_propagate_dfs_state(struct wiphy *wiphy,\n \t\t\t\t\t   chandef-\u003echan-\u003ecenter_freq))\n \t\t\tcontinue;\n \n+\t\tguard(wiphy)(\u0026rdev-\u003ewiphy);\n+\n \t\tcfg80211_set_dfs_state(\u0026rdev-\u003ewiphy, chandef, dfs_state);\n \n \t\tif (event == NL80211_RADAR_DETECTED ||\n@@ -4390,15 +4435,15 @@ void regulatory_exit(void)\n {\n \tstruct regulatory_request *reg_request, *tmp;\n \tstruct reg_beacon *reg_beacon, *btmp;\n+\tconst struct fwdb_header *db;\n \n \tcancel_work_sync(\u0026reg_work);\n \tcancel_crda_timeout_sync();\n \tcancel_delayed_work_sync(\u0026reg_check_chans);\n \n \t/* Lock to suppress warnings */\n-\trtnl_lock();\n-\treset_regdomains(true, NULL);\n-\trtnl_unlock();\n+\tscoped_guard(mutex, \u0026cfg80211_mutex)\n+\t\treset_regdomains(true, NULL);\n \n \tdev_set_uevent_suppress(\u0026reg_fdev-\u003edev, true);\n \n@@ -4419,8 +4464,10 @@ void regulatory_exit(void)\n \t\tkfree(reg_request);\n \t}\n \n-\tif (!IS_ERR_OR_NULL(regdb))\n-\t\tkfree(regdb);\n+\t/* RCU is irrelevant on the way out */\n+\tdb = rcu_dereference_protected(global_regdb, true);\n+\tif (!IS_ERR_OR_NULL(db))\n+\t\tkfree(db);\n \tif (!IS_ERR_OR_NULL(cfg80211_user_regdom))\n \t\tkfree(cfg80211_user_regdom);\n \n"
}

2/1 2026/10/06 10:49 agent patch-evaluator
6m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "CFG80211",
    "MAC80211",
    "MAC80211_HWSIM",
    "CFG80211_CRDA_SUPPORT",
    "CFG80211_DEBUGFS"
  ],
  "FocusSymbols": [
    "nl80211_pre_doit",
    "nl80211_lock_and_recheck",
    "nl80211_prepare_wdev_dump",
    "nl80211_set_reg",
    "cfg80211_dfs_channels_update_work",
    "cfg80211_dfs_chan_expire",
    "regulatory_propagate_dfs_state",
    "hwsim_background_cac_write"
  ],
  "Reasoning": "The patch series introduces major locking and synchronization changes across the wireless core (cfg80211, nl80211, mac80211_hwsim), replacing RTNL lock coverage with a new cfg80211_mutex, RCU lookups, wiphy mutex guards, and lockdep assertions. It also modifies asynchronous DFS channel expiration and dump preparation. These reachable paths are heavily fuzzed via Netlink and virtual devices, making fuzzing critical to catch concurrency bugs, state races, and lockdep splats.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 487d4a302c3c079c0ba08d2a624c2282c46c0376
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Oct 6 10:49:10 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/net/wireless/ath/ath11k/reg.c b/drivers/net/wireless/ath/ath11k/reg.c
index 0879a132b67cf..1030e119fd903 100644
--- a/drivers/net/wireless/ath/ath11k/reg.c
+++ b/drivers/net/wireless/ath/ath11k/reg.c
@@ -33,7 +33,9 @@ static bool ath11k_regdom_changes(struct ath11k *ar, char *alpha2)
 {
 	const struct ieee80211_regdomain *regd;
 
-	regd = rcu_dereference_rtnl(ar->hw->wiphy->regd);
+	guard(rcu)();
+
+	regd = get_wiphy_regdom(ar->hw->wiphy);
 	/* This can happen during wiphy registration where the previous
 	 * user request is received before we update the regd received
 	 * from firmware.
diff --git a/drivers/net/wireless/ath/ath12k/reg.c b/drivers/net/wireless/ath/ath12k/reg.c
index 89abf2e87ad10..7cf3dbc4fc8a9 100644
--- a/drivers/net/wireless/ath/ath12k/reg.c
+++ b/drivers/net/wireless/ath/ath12k/reg.c
@@ -33,7 +33,9 @@ static bool ath12k_regdom_changes(struct ieee80211_hw *hw, char *alpha2)
 {
 	const struct ieee80211_regdomain *regd;
 
-	regd = rcu_dereference_rtnl(hw->wiphy->regd);
+	guard(rcu)();
+
+	regd = get_wiphy_regdom(hw->wiphy);
 	/* This can happen during wiphy registration where the previous
 	 * user request is received before we update the regd received
 	 * from firmware.
@@ -122,9 +124,7 @@ ath12k_reg_notifier(struct wiphy *wiphy, struct regulatory_request *request)
 					    "failed set INIT Country code: %d\n", ret);
 		}
 
-		wiphy_lock(wiphy);
 		ath12k_mac_11d_scan_stop(ar);
-		wiphy_unlock(wiphy);
 
 		ar->regdom_set_by_user = true;
 	}
diff --git a/drivers/net/wireless/ath/regd.c b/drivers/net/wireless/ath/regd.c
index f15e7bd690b5b..cedb5e43b9e4f 100644
--- a/drivers/net/wireless/ath/regd.c
+++ b/drivers/net/wireless/ath/regd.c
@@ -268,6 +268,8 @@ static void ath_force_clear_no_ir_chan(struct wiphy *wiphy,
 {
 	const struct ieee80211_reg_rule *reg_rule;
 
+	guard(rcu)();
+
 	reg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(ch->center_freq));
 	if (IS_ERR(reg_rule))
 		return;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c
index cdfe8635c012e..5ca8a07353982 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c
@@ -676,6 +676,8 @@ brcms_reg_apply_beaconing_flags(struct wiphy *wiphy,
 				continue;
 
 			if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) {
+				guard(rcu)();
+
 				rule = freq_reg_info(wiphy,
 						     MHZ_TO_KHZ(ch->center_freq));
 				if (IS_ERR(rule))
diff --git a/drivers/net/wireless/realtek/rtlwifi/regd.c b/drivers/net/wireless/realtek/rtlwifi/regd.c
index fd967006b3e1e..503625b50fc38 100644
--- a/drivers/net/wireless/realtek/rtlwifi/regd.c
+++ b/drivers/net/wireless/realtek/rtlwifi/regd.c
@@ -150,6 +150,8 @@ static void _rtl_reg_apply_beaconing_flags(struct wiphy *wiphy,
 			    (ch->flags & IEEE80211_CHAN_RADAR))
 				continue;
 			if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) {
+				guard(rcu)();
+
 				reg_rule = freq_reg_info(wiphy,
 							 ch->center_freq);
 				if (IS_ERR(reg_rule))
@@ -212,6 +214,8 @@ static void _rtl_reg_apply_active_scan_flags(struct wiphy *wiphy,
 	 *custom regulatory domain.
 	 */
 
+	guard(rcu)();
+
 	ch = &sband->channels[11];	/* CH 12 */
 	reg_rule = freq_reg_info(wiphy, ch->center_freq);
 	if (!IS_ERR(reg_rule)) {
diff --git a/drivers/net/wireless/realtek/rtw89/regd.c b/drivers/net/wireless/realtek/rtw89/regd.c
index b9a5ce218b1da..983b42e193b52 100644
--- a/drivers/net/wireless/realtek/rtw89/regd.c
+++ b/drivers/net/wireless/realtek/rtw89/regd.c
@@ -898,7 +898,6 @@ void rtw89_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request
 	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
 	struct rtw89_dev *rtwdev = hw->priv;
 
-	wiphy_lock(wiphy);
 	rtw89_leave_ps_mode(rtwdev);
 
 	if (rtwdev->regulatory.programmed)
@@ -916,8 +915,6 @@ void rtw89_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request
 	rtw89_regd_apply_policy_ant_gain(rtwdev);
 
 	rtw89_core_set_chip_txpwr(rtwdev);
-
-	wiphy_unlock(wiphy);
 }
 
 /* Maximum Transmit Power field (@raw) can be EIRP or PSD.
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 6cd9542e8b722..b0fd6bdd3cccc 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1133,6 +1133,8 @@ static ssize_t hwsim_background_cac_write(struct file *file,
 	if (copy_from_user(buf, user_buf, count))
 		return -EFAULT;
 
+	guard(wiphy)(data->hw->wiphy);
+
 	/* Check if background radar channel is configured */
 	if (!data->radar_background_chandef.chan)
 		return -ENOENT;
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 05c625d9e0766..378fa52bfbab6 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -6362,7 +6362,8 @@ struct wiphy_nan_capa {
  * @mtx: mutex for the data (structures) of this device
  * @reg_notifier: the driver's regulatory notification callback,
  *	note that if your driver uses wiphy_apply_custom_regulatory()
- *	the reg_notifier's request can be passed as NULL
+ *	the reg_notifier's request can be passed as NULL.
+ *	This is called under wiphy mutex.
  * @regd: the driver's regulatory domain, if one was requested via
  *	the regulatory_hint() API. This can be used by the driver
  *	on the reg_notifier() if it chooses to ignore future
@@ -7242,8 +7243,9 @@ enum ieee80211_ap_reg_power {
  * @mgmt_registrations_need_update: mgmt registrations were updated,
  *	need to propagate the update to the driver
  * @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.
+ * @is_running: true if the device has been started, e.g. the P2P Device;
+ *	for netdevs, tracked under the wiphy mutex from NETDEV_UP until
+ *	NETDEV_GOING_DOWN
  * @ps: powersave mode is enabled
  * @ps_timeout: dynamic powersave timeout
  * @unexpected_nlportid: (private) netlink port ID of application
@@ -7407,8 +7409,6 @@ static inline const u8 *wdev_address(struct wireless_dev *wdev)
 
 static inline bool wdev_running(struct wireless_dev *wdev)
 {
-	if (wdev->netdev)
-		return netif_running(wdev->netdev);
 	return wdev->is_running;
 }
 
@@ -8161,9 +8161,9 @@ int regulatory_set_wiphy_regd(struct wiphy *wiphy,
  * @wiphy: the wireless device we want to process the regulatory domain on
  * @rd: the regulatory domain information to use for this wiphy
  *
- * This functions requires the RTNL and the wiphy mutex to be held and
- * applies the new regdomain synchronously to this wiphy. For more details
- * see regulatory_set_wiphy_regd().
+ * This functions requires the wiphy mutex to be held and applies the new
+ * regdomain synchronously to this wiphy. For more details see
+ * regulatory_set_wiphy_regd().
  *
  * Return: 0 on success. -EINVAL, -EPERM
  */
@@ -8198,6 +8198,8 @@ void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
  * it wants to follow we respect that unless a country IE has been received
  * and processed already.
  *
+ * Must be called within an RCU read-side critical section.
+ *
  * Return: A valid pointer, or, when an error occurs, for example if no rule
  * can be found, the return value is encoded using ERR_PTR(). Use IS_ERR() to
  * check and PTR_ERR() to obtain the numeric return value. The numeric return
@@ -8236,20 +8238,20 @@ bool regulatory_pre_cac_allowed(struct wiphy *wiphy);
  */
 
 /**
- * reg_query_regdb_wmm -  Query internal regulatory db for wmm rule
- * Regulatory self-managed driver can use it to proactively
- *
+ * reg_query_regdb_wmm -  Query internal regulatory db for WMM rules
  * @alpha2: the ISO/IEC 3166 alpha2 wmm rule to be queried.
  * @freq: the frequency (in MHz) to be queried.
- * @rule: pointer to store the wmm rule from the regulatory db.
+ * @rule: pointer to store the WMM rule from the regulatory db.
  *
- * Self-managed wireless drivers can use this function to  query
+ * Self-managed wireless drivers can use this function to query
  * the internal regulatory database to check whether the given
- * ISO/IEC 3166 alpha2 country and freq have wmm rule limitations.
+ * ISO/IEC 3166 alpha2 country and freq have WMM rule limitations.
  *
  * Drivers should check the return value, its possible you can get
  * an -ENODATA.
  *
+ * This uses RCU internally so can be called in just about any context.
+ *
  * Return: 0 on success. -ENODATA.
  */
 int reg_query_regdb_wmm(char *alpha2, int freq,
@@ -9809,6 +9811,7 @@ void cfg80211_cqm_beacon_loss_notify(struct net_device *dev, gfp_t gfp);
  * @gfp: context flags
  *
  * This function is called when a radar is detected on the current chanenl.
+ * Must be called with the wiphy mutex held.
  */
 void __cfg80211_radar_event(struct wiphy *wiphy,
 			    struct cfg80211_chan_def *chandef,
@@ -9854,7 +9857,7 @@ void cfg80211_sta_opmode_change_notify(struct net_device *dev, const u8 *mac,
  *
  * 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,
- * also by full-MAC drivers.
+ * also by full-MAC drivers. Must be called with the wiphy mutex held.
  */
 void cfg80211_cac_event(struct net_device *netdev,
 			const struct cfg80211_chan_def *chandef,
diff --git a/net/wireless/chan.c b/net/wireless/chan.c
index c743b6fb7e309..9183cd19dbdc4 100644
--- a/net/wireless/chan.c
+++ b/net/wireless/chan.c
@@ -1243,7 +1243,7 @@ bool cfg80211_any_wiphy_oper_chan(struct wiphy *wiphy,
 {
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	if (!(chan->flags & IEEE80211_CHAN_RADAR))
 		return false;
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 45a1709cffe7a..01b077909179d 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -42,7 +42,9 @@ MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("wireless configuration support");
 MODULE_ALIAS_GENL_FAMILY(NL80211_GENL_NAME);
 
-/* RCU-protected (and RTNL for writers) */
+DEFINE_MUTEX(cfg80211_mutex);
+
+/* RCU-protected (writes under both RTNL/cfg80211_mutex) */
 LIST_HEAD(cfg80211_rdev_list);
 int cfg80211_rdev_list_generation;
 
@@ -59,18 +61,14 @@ MODULE_PARM_DESC(cfg80211_disable_40mhz_24ghz,
 
 struct cfg80211_registered_device *cfg80211_rdev_by_wiphy_idx(int wiphy_idx)
 {
-	struct cfg80211_registered_device *result = NULL, *rdev;
-
-	ASSERT_RTNL();
+	struct cfg80211_registered_device *rdev;
 
 	for_each_rdev(rdev) {
-		if (rdev->wiphy_idx == wiphy_idx) {
-			result = rdev;
-			break;
-		}
+		if (rdev->wiphy_idx == wiphy_idx)
+			return rdev;
 	}
 
-	return result;
+	return NULL;
 }
 
 int get_wiphy_idx(struct wiphy *wiphy)
@@ -84,8 +82,6 @@ struct wiphy *wiphy_idx_to_wiphy(int wiphy_idx)
 {
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
-
 	rdev = cfg80211_rdev_by_wiphy_idx(wiphy_idx);
 	if (!rdev)
 		return NULL;
@@ -564,13 +560,11 @@ static void cfg80211_propagate_radar_detect_wk(struct work_struct *work)
 	rdev = container_of(work, struct cfg80211_registered_device,
 			    propagate_radar_detect_wk);
 
-	rtnl_lock();
+	guard(mutex)(&cfg80211_mutex);
 
 	regulatory_propagate_dfs_state(&rdev->wiphy, &rdev->radar_chandef,
 				       NL80211_DFS_UNAVAILABLE,
 				       NL80211_RADAR_DETECTED);
-
-	rtnl_unlock();
 }
 
 static void cfg80211_propagate_cac_done_wk(struct work_struct *work)
@@ -580,13 +574,11 @@ static void cfg80211_propagate_cac_done_wk(struct work_struct *work)
 	rdev = container_of(work, struct cfg80211_registered_device,
 			    propagate_cac_done_wk);
 
-	rtnl_lock();
+	guard(mutex)(&cfg80211_mutex);
 
 	regulatory_propagate_dfs_state(&rdev->wiphy, &rdev->cac_done_chandef,
 				       NL80211_DFS_AVAILABLE,
 				       NL80211_RADAR_CAC_FINISHED);
-
-	rtnl_unlock();
 }
 
 static void cfg80211_wiphy_work(struct work_struct *work)
@@ -1243,10 +1235,12 @@ int wiphy_register(struct wiphy *wiphy)
 		rdev->wiphy.bss_param_support |= WIPHY_BSS_PARAM_P2P_OPPPS;
 
 	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
 	wiphy_lock(&rdev->wiphy);
 	res = device_add(&rdev->wiphy.dev);
 	if (res) {
 		wiphy_unlock(&rdev->wiphy);
+		mutex_unlock(&cfg80211_mutex);
 		rtnl_unlock();
 		return res;
 	}
@@ -1271,6 +1265,9 @@ int wiphy_register(struct wiphy *wiphy)
 	}
 
 	cfg80211_debugfs_rdev_add(rdev);
+
+	rdev->wiphy.registered = true;
+
 	nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY);
 	wiphy_unlock(&rdev->wiphy);
 
@@ -1317,8 +1314,7 @@ int wiphy_register(struct wiphy *wiphy)
 				break;
 		}
 	}
-
-	rdev->wiphy.registered = true;
+	mutex_unlock(&cfg80211_mutex);
 	rtnl_unlock();
 
 	res = rfkill_register(rdev->wiphy.rfkill);
@@ -1390,6 +1386,7 @@ void wiphy_unregister(struct wiphy *wiphy)
 		rfkill_unregister(rdev->wiphy.rfkill);
 
 	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
 	wiphy_lock(&rdev->wiphy);
 	nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY);
 	rdev->wiphy.registered = false;
@@ -1421,6 +1418,7 @@ void wiphy_unregister(struct wiphy *wiphy)
 	/* surely nothing is reachable now, clean up work */
 	cfg80211_process_wiphy_works(rdev, NULL);
 	wiphy_unlock(&rdev->wiphy);
+	mutex_unlock(&cfg80211_mutex);
 	rtnl_unlock();
 
 	/* this has nothing to do now but make sure it's gone */
@@ -1836,6 +1834,8 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
 		}
 		break;
 	case NETDEV_GOING_DOWN:
+		scoped_guard(wiphy, &rdev->wiphy)
+			wdev->is_running = false;
 		cfg80211_leave(rdev, wdev, -1);
 		scoped_guard(wiphy, &rdev->wiphy) {
 			cfg80211_remove_links(wdev);
@@ -1866,6 +1866,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
 		break;
 	case NETDEV_UP:
 		wiphy_lock(&rdev->wiphy);
+		wdev->is_running = true;
 		cfg80211_update_iface_num(rdev, wdev->iftype, 1);
 		switch (wdev->iftype) {
 #ifdef CONFIG_CFG80211_WEXT
diff --git a/net/wireless/core.h b/net/wireless/core.h
index 58445c09026a2..dfcb9ed5035b3 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -19,6 +19,9 @@
 #include "reg.h"
 
 
+/* protects the wiphy list and regulatory state */
+extern struct mutex cfg80211_mutex;
+
 #define WIPHY_IDX_INVALID	-1
 
 struct cfg80211_scan_request_int {
@@ -179,15 +182,10 @@ extern struct workqueue_struct *cfg80211_wq;
 extern struct list_head cfg80211_rdev_list;
 extern int cfg80211_rdev_list_generation;
 
-/* This is constructed like this so it can be used in if/else */
-static inline int for_each_rdev_check_rtnl(void)
-{
-	ASSERT_RTNL();
-	return 0;
-}
 #define for_each_rdev(rdev)						\
-	if (for_each_rdev_check_rtnl()) {} else				\
-		list_for_each_entry(rdev, &cfg80211_rdev_list, list)
+	list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list,	\
+				lockdep_rtnl_is_held() ||		\
+				lockdep_is_held(&cfg80211_mutex))
 
 enum bss_source_type {
 	BSS_SOURCE_DIRECT = 0,
diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index a0d1cde26f0c9..96fd14f2a305d 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -1070,25 +1070,75 @@ void cfg80211_sched_dfs_chan_update(struct cfg80211_registered_device *rdev)
 	queue_delayed_work(cfg80211_wq, &rdev->dfs_update_channels_wk, 0);
 }
 
+struct cfg80211_dfs_chan_expiry {
+	struct ieee80211_channel *chan;
+	unsigned long entered;
+	enum nl80211_dfs_state state;
+	enum nl80211_radar_event event;
+};
+
+static bool cfg80211_dfs_chan_expire(struct cfg80211_registered_device *rdev,
+				     struct cfg80211_dfs_chan_expiry *exp,
+				     struct cfg80211_chan_def *chandef)
+{
+	struct ieee80211_channel *c = exp->chan;
+
+	guard(wiphy)(&rdev->wiphy);
+
+	/* the state may have changed while the wiphy wasn't locked */
+	if (c->dfs_state != exp->state || c->dfs_state_entered != exp->entered)
+		return false;
+
+	c->dfs_state = NL80211_DFS_USABLE;
+	c->dfs_state_entered = jiffies;
+
+	cfg80211_chandef_create(chandef, c, NL80211_CHAN_NO_HT);
+
+	nl80211_radar_notify(rdev, chandef, exp->event, NULL, GFP_KERNEL);
+
+	return true;
+}
+
 void cfg80211_dfs_channels_update_work(struct work_struct *work)
 {
 	struct delayed_work *delayed_work = to_delayed_work(work);
+	struct cfg80211_dfs_chan_expiry *expired;
 	struct cfg80211_registered_device *rdev;
 	struct cfg80211_chan_def chandef;
 	struct ieee80211_supported_band *sband;
 	struct ieee80211_channel *c;
 	struct wiphy *wiphy;
-	bool check_again = false;
+	bool check_again = false, pre_cac_allowed;
 	unsigned long timeout, next_time = 0;
 	unsigned long time_dfs_update;
 	enum nl80211_radar_event radar_event;
+	unsigned int n_chans = 0, n_expired = 0;
 	int bandid, i;
 
 	rdev = container_of(delayed_work, struct cfg80211_registered_device,
 			    dfs_update_channels_wk);
 	wiphy = &rdev->wiphy;
 
-	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
+	wiphy_lock(wiphy);
+	for (bandid = 0; bandid < NUM_NL80211_BANDS; bandid++) {
+		if (wiphy->bands[bandid])
+			n_chans += wiphy->bands[bandid]->n_channels;
+	}
+
+	expired = kvcalloc(n_chans, sizeof(*expired), GFP_KERNEL);
+	if (!expired) {
+		/* hmm - retry later */
+		queue_delayed_work(cfg80211_wq,
+				   &rdev->dfs_update_channels_wk,
+				   HZ);
+		wiphy_unlock(wiphy);
+		mutex_unlock(&cfg80211_mutex);
+		return;
+	}
+
+	pre_cac_allowed = regulatory_pre_cac_allowed(wiphy);
+
 	for (bandid = 0; bandid < NUM_NL80211_BANDS; bandid++) {
 		sband = wiphy->bands[bandid];
 		if (!sband)
@@ -1108,8 +1158,7 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)
 				time_dfs_update = IEEE80211_DFS_MIN_NOP_TIME_MS;
 				radar_event = NL80211_RADAR_NOP_FINISHED;
 			} else {
-				if (regulatory_pre_cac_allowed(wiphy) ||
-				    cfg80211_any_wiphy_oper_chan(wiphy, c))
+				if (pre_cac_allowed)
 					continue;
 
 				time_dfs_update = REG_PRE_CAC_EXPIRY_GRACE_MS;
@@ -1120,19 +1169,11 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)
 				  msecs_to_jiffies(time_dfs_update);
 
 			if (time_after_eq(jiffies, timeout)) {
-				c->dfs_state = NL80211_DFS_USABLE;
-				c->dfs_state_entered = jiffies;
-
-				cfg80211_chandef_create(&chandef, c,
-							NL80211_CHAN_NO_HT);
-
-				nl80211_radar_notify(rdev, &chandef,
-						     radar_event, NULL,
-						     GFP_ATOMIC);
-
-				regulatory_propagate_dfs_state(wiphy, &chandef,
-							       c->dfs_state,
-							       radar_event);
+				expired[n_expired].chan = c;
+				expired[n_expired].state = c->dfs_state;
+				expired[n_expired].entered = c->dfs_state_entered;
+				expired[n_expired].event = radar_event;
+				n_expired++;
 				continue;
 			}
 
@@ -1143,7 +1184,24 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)
 			check_again = true;
 		}
 	}
-	rtnl_unlock();
+	wiphy_unlock(wiphy);
+
+	/* these lock the wiphys, so must be done without holding ours */
+	for (i = 0; i < n_expired; i++) {
+		if (expired[i].event == NL80211_RADAR_PRE_CAC_EXPIRED &&
+		    cfg80211_any_wiphy_oper_chan(wiphy, expired[i].chan))
+			continue;
+
+		if (!cfg80211_dfs_chan_expire(rdev, &expired[i], &chandef))
+			continue;
+
+		regulatory_propagate_dfs_state(wiphy, &chandef,
+					       NL80211_DFS_USABLE,
+					       expired[i].event);
+	}
+	mutex_unlock(&cfg80211_mutex);
+
+	kvfree(expired);
 
 	/* reschedule if there are other channels waiting to be cleared again */
 	if (check_again)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 7ee69ec10b0f9..48f5584d7c2bc 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -63,7 +63,7 @@ static const struct genl_multicast_group nl80211_mcgrps[] = {
 #endif
 };
 
-/* returns ERR_PTR values */
+/* returns ERR_PTR values, requires RCU/cfg80211_mutex if rdev is %NULL */
 static struct wireless_dev *
 __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,
 			   struct net *netns, struct nlattr **attrs)
@@ -105,8 +105,6 @@ __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,
 		return result ?: ERR_PTR(-ENODEV);
 	}
 
-	ASSERT_RTNL();
-
 	for_each_rdev(rdev) {
 		struct wireless_dev *wdev;
 
@@ -116,7 +114,8 @@ __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,
 		if (have_wdev_id && rdev->wiphy_idx != wiphy_idx)
 			continue;
 
-		list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+		list_for_each_entry_rcu(wdev, &rdev->wiphy.wdev_list, list,
+					lockdep_rtnl_is_held()) {
 			if (have_ifidx && wdev->netdev &&
 			    wdev->netdev->ifindex == ifidx) {
 				result = wdev;
@@ -137,14 +136,13 @@ __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,
 	return ERR_PTR(-ENODEV);
 }
 
+/* requires RCU/cfg80211_mutex */
 static struct cfg80211_registered_device *
 __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)
 {
 	struct cfg80211_registered_device *rdev = NULL, *tmp;
 	struct net_device *netdev;
 
-	ASSERT_RTNL();
-
 	if (!attrs[NL80211_ATTR_WIPHY] &&
 	    !attrs[NL80211_ATTR_IFINDEX] &&
 	    !attrs[NL80211_ATTR_WDEV])
@@ -162,7 +160,8 @@ __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)
 		tmp = cfg80211_rdev_by_wiphy_idx(wdev_id >> 32);
 		if (tmp) {
 			/* make sure wdev exists */
-			list_for_each_entry(wdev, &tmp->wiphy.wdev_list, list) {
+			list_for_each_entry_rcu(wdev, &tmp->wiphy.wdev_list,
+						list, lockdep_rtnl_is_held()) {
 				if (wdev->identifier != (u32)wdev_id)
 					continue;
 				found = true;
@@ -181,14 +180,16 @@ __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)
 	if (attrs[NL80211_ATTR_IFINDEX]) {
 		int ifindex = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]);
 
-		netdev = __dev_get_by_index(netns, ifindex);
-		if (netdev) {
-			if (netdev->ieee80211_ptr)
-				tmp = wiphy_to_rdev(
-					netdev->ieee80211_ptr->wiphy);
-			else
-				tmp = NULL;
+		/* RCU required for dev_get_by_index_rcu() if we have RTNL */
+		rcu_read_lock();
+		netdev = dev_get_by_index_rcu(netns, ifindex);
+		if (netdev && netdev->ieee80211_ptr)
+			tmp = wiphy_to_rdev(netdev->ieee80211_ptr->wiphy);
+		else
+			tmp = NULL;
+		rcu_read_unlock();
 
+		if (netdev) {
 			/* not wireless device -- return error */
 			if (!tmp)
 				return ERR_PTR(-EINVAL);
@@ -223,6 +224,48 @@ cfg80211_get_dev_from_info(struct net *netns, struct genl_info *info)
 	return __cfg80211_rdev_from_attrs(netns, info->attrs);
 }
 
+/*
+ * Lock the given wiphy, put the device and double-check it's
+ * still valid to use.
+ *
+ * Returns with wiphy mutex held unless there was an error.
+ */
+static struct wireless_dev *
+nl80211_lock_and_recheck(struct cfg80211_registered_device *rdev,
+			 struct net *netns, u32 wdev_id)
+{
+	struct wireless_dev *wdev;
+
+	wiphy_lock(&rdev->wiphy);
+
+	if (!rdev->wiphy.registered ||
+	    !net_eq(wiphy_net(&rdev->wiphy), netns)) {
+		wiphy_unlock(&rdev->wiphy);
+		put_device(&rdev->wiphy.dev);
+		return ERR_PTR(-ENODEV);
+	}
+
+	/*
+	 * unregistration blocks on wiphy mutex,
+	 * so we don't need this now
+	 */
+	put_device(&rdev->wiphy.dev);
+
+	if (!wdev_id)
+		return NULL;
+
+	list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+		if (wdev->identifier != wdev_id)
+			continue;
+		if (wdev->netdev && !net_eq(dev_net(wdev->netdev), netns))
+			break;
+		return wdev;
+	}
+	wiphy_unlock(&rdev->wiphy);
+
+	return ERR_PTR(-ENODEV);
+}
+
 static int validate_beacon_head(const struct nlattr *attr,
 				struct netlink_ext_ack *extack)
 {
@@ -1260,6 +1303,8 @@ static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,
 				     struct wireless_dev **wdev,
 				     struct nlattr **attrbuf)
 {
+	struct net *netns = sock_net(cb->skb->sk);
+	u32 wdev_id;
 	int err;
 
 	if (!cb->args[0]) {
@@ -1281,60 +1326,37 @@ static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,
 			return err;
 		}
 
-		rtnl_lock();
-		*wdev = __cfg80211_wdev_from_attrs(NULL, sock_net(cb->skb->sk),
-						   attrbuf);
+		rcu_read_lock();
+		*wdev = __cfg80211_wdev_from_attrs(NULL, netns, attrbuf);
 		kfree(attrbuf_free);
 		if (IS_ERR(*wdev)) {
-			rtnl_unlock();
+			rcu_read_unlock();
 			return PTR_ERR(*wdev);
 		}
 		*rdev = wiphy_to_rdev((*wdev)->wiphy);
-		mutex_lock(&(*rdev)->wiphy.mtx);
-		rtnl_unlock();
-		/* 0 is the first index - add 1 to parse only once */
-		cb->args[0] = (*rdev)->wiphy_idx + 1;
-		cb->args[1] = (*wdev)->identifier;
+		wdev_id = (*wdev)->identifier;
 	} else {
 		/* subtract the 1 again here */
-		struct wiphy *wiphy;
-		struct wireless_dev *tmp;
-
-		rtnl_lock();
-		wiphy = wiphy_idx_to_wiphy(cb->args[0] - 1);
-		if (!wiphy) {
-			rtnl_unlock();
-			return -ENODEV;
-		}
-
-		/*
-		 * The first invocation validated the wdev's netns against
-		 * the caller via __cfg80211_wdev_from_attrs(). The wiphy
-		 * may have moved netns between dumpit invocations (via
-		 * NL80211_CMD_SET_WIPHY_NETNS), so re-check here.
-		 */
-		if (!net_eq(wiphy_net(wiphy), sock_net(cb->skb->sk))) {
-			rtnl_unlock();
+		rcu_read_lock();
+		*rdev = cfg80211_rdev_by_wiphy_idx(cb->args[0] - 1);
+		if (!*rdev) {
+			rcu_read_unlock();
 			return -ENODEV;
 		}
+		wdev_id = cb->args[1];
+	}
 
-		*rdev = wiphy_to_rdev(wiphy);
-		*wdev = NULL;
+	get_device(&(*rdev)->wiphy.dev);
+	rcu_read_unlock();
 
-		list_for_each_entry(tmp, &(*rdev)->wiphy.wdev_list, list) {
-			if (tmp->identifier == cb->args[1]) {
-				*wdev = tmp;
-				break;
-			}
-		}
+	/* things may have changed since the lookup or the last dumpit call */
+	*wdev = nl80211_lock_and_recheck(*rdev, netns, wdev_id);
+	if (IS_ERR(*wdev))
+		return PTR_ERR(*wdev);
 
-		if (!*wdev) {
-			rtnl_unlock();
-			return -ENODEV;
-		}
-		mutex_lock(&(*rdev)->wiphy.mtx);
-		rtnl_unlock();
-	}
+	/* 0 is the first index - add 1 to parse only once */
+	cb->args[0] = (*rdev)->wiphy_idx + 1;
+	cb->args[1] = wdev_id;
 
 	return 0;
 }
@@ -1518,9 +1540,10 @@ static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy,
 		goto nla_put_failure;
 
 	if (large) {
-		const struct ieee80211_reg_rule *rule =
-			freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq));
+		const struct ieee80211_reg_rule *rule;
 
+		guard(rcu)();
+		rule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq));
 		if (!IS_ERR_OR_NULL(rule) && rule->has_wmm) {
 			if (nl80211_msg_put_wmm_rules(msg, rule))
 				goto nla_put_failure;
@@ -10924,7 +10947,7 @@ static int nl80211_get_reg_dump(struct sk_buff *skb,
 
 	/* the global regdom is idx 0 */
 	reg_idx = 1;
-	list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) {
+	for_each_rdev(rdev) {
 		regdom = get_wiphy_regdom(&rdev->wiphy);
 		if (!regdom)
 			continue;
@@ -11026,7 +11049,7 @@ static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
 			return -EINVAL;
 	}
 
-	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
 	if (!reg_is_valid_request(alpha2)) {
 		r = -EINVAL;
 		goto out;
@@ -11074,7 +11097,7 @@ static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
  bad_reg:
 	kfree(rd);
  out:
-	rtnl_unlock();
+	mutex_unlock(&cfg80211_mutex);
 	return r;
 }
 #endif /* CONFIG_CFG80211_CRDA_SUPPORT */
@@ -19669,6 +19692,18 @@ nl80211_epcs_cfg(struct sk_buff *skb, struct genl_info *info)
 		 NL80211_FLAG_NO_WIPHY_MTX |		\
 		 NL80211_FLAG_NEED_RTNL)
 
+/*
+ * Note: a
+ *	SELECTOR(...,
+ *		 NL80211_FLAG_NO_WIPHY_MTX |
+ *		 NL80211_FLAG_NEED_WDEV)
+ * or
+ *	SELECTOR(..., NL80211_FLAG_NO_WIPHY_MTX)
+ *
+ * isn't valid - nothing would ensure the device stays around,
+ * need at least a netdev, the wiphy mutex, or RTNL.
+ */
+
 enum nl80211_internal_flags_selector {
 #define SELECTOR(_, name, value)	NL80211_IFL_SEL_##name,
 	INTERNAL_FLAG_SELECTORS(_)
@@ -19686,41 +19721,88 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,
 			    struct genl_info *info)
 {
 	struct cfg80211_registered_device *rdev = NULL;
+	struct net *netns = genl_info_net(info);
 	struct wireless_dev *wdev = NULL;
 	struct net_device *dev = NULL;
-	u32 internal_flags;
+	bool need_rtnl, locked = false;
+	u32 internal_flags, wdev_id = 0;
 	int err;
 
 	if (WARN_ON(ops->internal_flags >= ARRAY_SIZE(nl80211_internal_flags)))
 		return -EINVAL;
 
 	internal_flags = nl80211_internal_flags[ops->internal_flags];
+	need_rtnl = internal_flags & NL80211_FLAG_NEED_RTNL;
+
+	if (need_rtnl)
+		rtnl_lock();
+	else
+		rcu_read_lock();
 
-	rtnl_lock();
 	if (internal_flags & NL80211_FLAG_NEED_WIPHY) {
-		rdev = cfg80211_get_dev_from_info(genl_info_net(info), info);
+		rdev = cfg80211_get_dev_from_info(netns, info);
 		if (IS_ERR(rdev)) {
 			err = PTR_ERR(rdev);
-			goto out_unlock;
+			goto out_lookup;
 		}
-		info->user_ptr[0] = rdev;
 	} else if (internal_flags & NL80211_FLAG_NEED_NETDEV ||
 		   internal_flags & NL80211_FLAG_NEED_WDEV) {
-		wdev = __cfg80211_wdev_from_attrs(NULL, genl_info_net(info),
-						  info->attrs);
+		wdev = __cfg80211_wdev_from_attrs(NULL, netns, info->attrs);
 		if (IS_ERR(wdev)) {
 			err = PTR_ERR(wdev);
-			goto out_unlock;
+			goto out_lookup;
 		}
 
 		dev = wdev->netdev;
 		dev_hold(dev);
 		rdev = wiphy_to_rdev(wdev->wiphy);
+	}
+
+	if (!need_rtnl) {
+		if (rdev)
+			get_device(&rdev->wiphy.dev);
+		if (wdev)
+			wdev_id = wdev->identifier;
+		rcu_read_unlock();
+
+		/* RTM_NEWLINK is sent before NETDEV_UP, wait for dev_open() */
+		if (internal_flags & NL80211_FLAG_CHECK_NETDEV_UP && dev &&
+		    !READ_ONCE(wdev->is_running) && netif_running(dev)) {
+			rtnl_lock();
+			rtnl_unlock();
+		}
+	}
+
+	if (rdev && !(internal_flags & NL80211_FLAG_NO_WIPHY_MTX)) {
+		if (need_rtnl) {
+			wiphy_lock(&rdev->wiphy);
+		} else {
+			/* without rtnl, things may have changed since lookup */
+			wdev = nl80211_lock_and_recheck(rdev, netns, wdev_id);
+			if (IS_ERR(wdev)) {
+				err = PTR_ERR(wdev);
+				goto out;
+			}
+		}
+		locked = true;
+	} else if (rdev && !need_rtnl) {
+		/*
+		 * Without any mutex need a netdev to keep it alive, see
+		 * also the note about selectors - the WARN can't happen
+		 * without wrong selectors and those don't exist.
+		 */
+		put_device(&rdev->wiphy.dev);
+		if (WARN_ON(!dev)) {
+			err = -EINVAL;
+			goto out;
+		}
+	}
 
+	if (wdev) {
 		if (internal_flags & NL80211_FLAG_NEED_NETDEV) {
 			if (!dev) {
 				err = -EINVAL;
-				goto out_unlock;
+				goto out;
 			}
 
 			info->user_ptr[1] = dev;
@@ -19731,10 +19813,8 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,
 		if (internal_flags & NL80211_FLAG_CHECK_NETDEV_UP &&
 		    !wdev_running(wdev)) {
 			err = -ENETDOWN;
-			goto out_unlock;
+			goto out;
 		}
-
-		info->user_ptr[0] = rdev;
 	}
 
 	if (internal_flags & NL80211_FLAG_MLO_VALID_LINK_ID) {
@@ -19742,7 +19822,7 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,
 
 		if (!wdev) {
 			err = -EINVAL;
-			goto out_unlock;
+			goto out;
 		}
 
 		/* MLO -> require valid link ID */
@@ -19750,13 +19830,13 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,
 		    (!link_id ||
 		     !(wdev->valid_links & BIT(nla_get_u8(link_id))))) {
 			err = -EINVAL;
-			goto out_unlock;
+			goto out;
 		}
 
 		/* non-MLO -> no link ID attribute accepted */
 		if (!wdev->valid_links && link_id) {
 			err = -EINVAL;
-			goto out_unlock;
+			goto out;
 		}
 	}
 
@@ -19764,22 +19844,28 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,
 		if (info->attrs[NL80211_ATTR_MLO_LINK_ID] ||
 		    (wdev && wdev->valid_links)) {
 			err = -EINVAL;
-			goto out_unlock;
+			goto out;
 		}
 	}
 
-	if (rdev && !(internal_flags & NL80211_FLAG_NO_WIPHY_MTX)) {
-		wiphy_lock(&rdev->wiphy);
-		/* we keep the mutex locked until post_doit */
+	info->user_ptr[0] = rdev;
+	/* we keep the mutex locked until post_doit */
+	if (locked)
 		__release(&rdev->wiphy.mtx);
-	}
-	if (!(internal_flags & NL80211_FLAG_NEED_RTNL))
-		rtnl_unlock();
 
 	return 0;
-out_unlock:
-	rtnl_unlock();
+out:
+	if (locked)
+		wiphy_unlock(&rdev->wiphy);
 	dev_put(dev);
+	if (need_rtnl)
+		rtnl_unlock();
+	return err;
+out_lookup:
+	if (need_rtnl)
+		rtnl_unlock();
+	else
+		rcu_read_unlock();
 	return err;
 }
 
@@ -21115,7 +21201,10 @@ static bool nl80211_reg_change_event_fill(struct sk_buff *msg,
 	}
 
 	if (request->wiphy_idx != WIPHY_IDX_INVALID) {
-		struct wiphy *wiphy = wiphy_idx_to_wiphy(request->wiphy_idx);
+		struct wiphy *wiphy;
+
+		guard(rcu)();
+		wiphy = wiphy_idx_to_wiphy(request->wiphy_idx);
 
 		if (wiphy &&
 		    nla_put_u32(msg, NL80211_ATTR_WIPHY, request->wiphy_idx))
@@ -23357,7 +23446,7 @@ static int nl80211_netlink_notify(struct notifier_block * nb,
 
 	rcu_read_lock();
 
-	list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) {
+	for_each_rdev(rdev) {
 		struct cfg80211_sched_scan_request *sched_scan_req;
 
 		list_for_each_entry_rcu(sched_scan_req,
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index b7ef479a74f01..4efb35baeb3e1 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -88,7 +88,7 @@ static struct regulatory_request core_request_world = {
 
 /*
  * Receipt of information from last regulatory request,
- * protected by RTNL (and can be accessed with RCU protection)
+ * protected by cfg80211_mutex (and can be accessed with RCU protection)
  */
 static struct regulatory_request __rcu *last_request =
 	(void __force __rcu *)&core_request_world;
@@ -100,14 +100,14 @@ static struct faux_device *reg_fdev;
  * Central wireless core regulatory domains, we only need two,
  * the current one and a world regulatory domain in case we have no
  * information to give us an alpha2.
- * (protected by RTNL, can be read under RCU)
+ * (protected by cfg80211_mutex, can be read under RCU)
  */
 const struct ieee80211_regdomain __rcu *cfg80211_regdomain;
 
 /*
  * Number of devices that registered to the core
  * that support cellular base station regulatory hints
- * (protected by RTNL)
+ * (protected by cfg80211_mutex)
  */
 static int reg_num_devs_support_basehint;
 
@@ -128,19 +128,19 @@ static void reg_process_hint(struct regulatory_request *reg_request);
 
 static const struct ieee80211_regdomain *get_cfg80211_regdom(void)
 {
-	return rcu_dereference_rtnl(cfg80211_regdomain);
+	return rcu_dereference_check(cfg80211_regdomain,
+				     lockdep_is_held(&cfg80211_mutex));
 }
 
 /*
  * Returns the regulatory domain associated with the wiphy.
  *
- * Requires any of RTNL, wiphy mutex or RCU protection.
+ * Requires the wiphy mutex or RCU protection.
  */
 const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy)
 {
 	return rcu_dereference_check(wiphy->regd,
-				     lockdep_is_held(&wiphy->mtx) ||
-				     lockdep_rtnl_is_held());
+				     lockdep_is_held(&wiphy->mtx));
 }
 EXPORT_SYMBOL(get_wiphy_regdom);
 
@@ -204,7 +204,8 @@ static void rcu_free_regdom(const struct ieee80211_regdomain *r)
 
 static struct regulatory_request *get_last_request(void)
 {
-	return rcu_dereference_rtnl(last_request);
+	return rcu_dereference_check(last_request,
+				     lockdep_is_held(&cfg80211_mutex));
 }
 
 /* Used to queue up regulatory hints */
@@ -314,7 +315,7 @@ static void reset_regdomains(bool full_reset,
 {
 	const struct ieee80211_regdomain *r;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	r = get_cfg80211_regdom();
 
@@ -456,7 +457,7 @@ reg_copy_regd(const struct ieee80211_regdomain *src_regd)
 
 static void cfg80211_save_user_regdom(const struct ieee80211_regdomain *rd)
 {
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	if (!IS_ERR(cfg80211_user_regdom))
 		kfree(cfg80211_user_regdom);
@@ -475,7 +476,7 @@ static void reg_regdb_apply(struct work_struct *work)
 {
 	struct reg_regdb_apply_request *request;
 
-	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
 
 	mutex_lock(&reg_regdb_apply_mutex);
 	while (!list_empty(&reg_regdb_apply_list)) {
@@ -489,7 +490,7 @@ static void reg_regdb_apply(struct work_struct *work)
 	}
 	mutex_unlock(&reg_regdb_apply_mutex);
 
-	rtnl_unlock();
+	mutex_unlock(&cfg80211_mutex);
 }
 
 static DECLARE_WORK(reg_regdb_work, reg_regdb_apply);
@@ -526,10 +527,10 @@ static DECLARE_DELAYED_WORK(crda_timeout, crda_timeout_work);
 static void crda_timeout_work(struct work_struct *work)
 {
 	pr_debug("Timeout while waiting for CRDA to reply, restoring regulatory settings\n");
-	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
 	reg_crda_timeouts++;
 	restore_regulatory_settings(true, false);
-	rtnl_unlock();
+	mutex_unlock(&cfg80211_mutex);
 }
 
 static void cancel_crda_timeout(void)
@@ -590,7 +591,7 @@ static inline int call_crda(const char *alpha2)
 #endif /* CONFIG_CFG80211_CRDA_SUPPORT */
 
 /* code to directly load a firmware database through request_firmware */
-static const struct fwdb_header *regdb;
+static const struct fwdb_header __rcu *global_regdb;
 
 struct fwdb_country {
 	u8 alpha2[2];
@@ -892,16 +893,19 @@ static int __regdb_query_wmm(const struct fwdb_header *db,
 
 int reg_query_regdb_wmm(char *alpha2, int freq, struct ieee80211_reg_rule *rule)
 {
-	const struct fwdb_header *hdr = regdb;
 	const struct fwdb_country *country;
+	const struct fwdb_header *regdb;
+
+	guard(rcu)();
 
+	regdb = rcu_dereference(global_regdb);
 	if (!regdb)
 		return -ENODATA;
 
 	if (IS_ERR(regdb))
 		return PTR_ERR(regdb);
 
-	country = &hdr->country[0];
+	country = &regdb->country[0];
 	while (country->coll_ptr) {
 		if (alpha2_equal(alpha2, country->alpha2))
 			return __regdb_query_wmm(regdb, country, freq, rule);
@@ -970,15 +974,16 @@ static int regdb_query_country(const struct fwdb_header *db,
 
 static int query_regdb(const char *alpha2)
 {
-	const struct fwdb_header *hdr = regdb;
 	const struct fwdb_country *country;
+	const struct fwdb_header *regdb;
 
-	ASSERT_RTNL();
+	regdb = rcu_dereference_protected(global_regdb,
+					  lockdep_is_held(&cfg80211_mutex));
 
 	if (IS_ERR(regdb))
 		return PTR_ERR(regdb);
 
-	country = &hdr->country[0];
+	country = &regdb->country[0];
 	while (country->coll_ptr) {
 		if (alpha2_equal(alpha2, country->alpha2))
 			return regdb_query_country(regdb, country);
@@ -990,6 +995,7 @@ static int query_regdb(const char *alpha2)
 
 static void regdb_fw_cb(const struct firmware *fw, void *context)
 {
+	const struct fwdb_header *regdb;
 	int set_error = 0;
 	bool restore = true;
 	void *db;
@@ -1002,7 +1008,9 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)
 		set_error = -EINVAL;
 	}
 
-	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
+	regdb = rcu_dereference_protected(global_regdb,
+					  lockdep_is_held(&cfg80211_mutex));
 	if (regdb && !IS_ERR(regdb)) {
 		/* negative case - a bug
 		 * positive case - can happen due to race in case of multiple cb's in
@@ -1011,11 +1019,11 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)
 		 * Either case, just restore and free new db.
 		 */
 	} else if (set_error) {
-		regdb = ERR_PTR(set_error);
+		rcu_assign_pointer(global_regdb, ERR_PTR(set_error));
 	} else if (fw) {
 		db = kmemdup(fw->data, fw->size, GFP_KERNEL);
 		if (db) {
-			regdb = db;
+			rcu_assign_pointer(global_regdb, db);
 			restore = context && query_regdb(context);
 		} else {
 			restore = true;
@@ -1025,7 +1033,7 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)
 	if (restore)
 		restore_regulatory_settings(true, false);
 
-	rtnl_unlock();
+	mutex_unlock(&cfg80211_mutex);
 
 	kfree(context);
 
@@ -1038,9 +1046,9 @@ static int query_regdb_file(const char *alpha2)
 {
 	int err;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
-	if (regdb)
+	if (rcu_access_pointer(global_regdb))
 		return query_regdb(alpha2);
 
 	alpha2 = kmemdup(alpha2, 2, GFP_KERNEL);
@@ -1058,8 +1066,8 @@ static int query_regdb_file(const char *alpha2)
 
 int reg_reload_regdb(void)
 {
+	const struct fwdb_header *db, *old;
 	const struct firmware *fw;
-	void *db;
 	int err;
 	const struct ieee80211_regdomain *current_regdomain;
 	struct regulatory_request *request;
@@ -1079,10 +1087,9 @@ int reg_reload_regdb(void)
 		goto out;
 	}
 
-	rtnl_lock();
-	if (!IS_ERR_OR_NULL(regdb))
-		kfree(regdb);
-	regdb = db;
+	mutex_lock(&cfg80211_mutex);
+	old = rcu_replace_pointer(global_regdb, db,
+				  lockdep_is_held(&cfg80211_mutex));
 
 	/* reset regulatory domain */
 	current_regdomain = get_cfg80211_regdom();
@@ -1102,7 +1109,12 @@ int reg_reload_regdb(void)
 	reg_process_hint(request);
 
 out_unlock:
-	rtnl_unlock();
+	mutex_unlock(&cfg80211_mutex);
+
+	if (!IS_ERR_OR_NULL(old)) {
+		synchronize_rcu();
+		kfree(old);
+	}
  out:
 	release_firmware(fw);
 	return err;
@@ -2141,35 +2153,38 @@ static bool reg_is_world_roaming(struct wiphy *wiphy)
 static void reg_call_notifier(struct wiphy *wiphy,
 			      struct regulatory_request *request)
 {
+	lockdep_assert_wiphy(wiphy);
+
 	if (wiphy->reg_notifier)
 		wiphy->reg_notifier(wiphy, request);
 }
 
-static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,
+static bool handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,
 			      struct reg_beacon *reg_beacon)
 {
 	struct ieee80211_supported_band *sband;
 	struct ieee80211_channel *chan;
 	bool channel_changed = false;
 	struct ieee80211_channel chan_before;
-	struct regulatory_request *lr = get_last_request();
+
+	lockdep_assert_wiphy(wiphy);
 
 	sband = wiphy->bands[reg_beacon->chan.band];
 	chan = &sband->channels[chan_idx];
 
 	if (likely(!ieee80211_channel_equal(chan, &reg_beacon->chan)))
-		return;
+		return false;
 
 	if (chan->beacon_found)
-		return;
+		return false;
 
 	chan->beacon_found = true;
 
 	if (!reg_is_world_roaming(wiphy))
-		return;
+		return false;
 
 	if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS)
-		return;
+		return false;
 
 	chan_before = *chan;
 
@@ -2178,11 +2193,10 @@ static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,
 		channel_changed = true;
 	}
 
-	if (channel_changed) {
+	if (channel_changed)
 		nl80211_send_beacon_hint_event(wiphy, &chan_before, chan);
-		if (wiphy->flags & WIPHY_FLAG_CHANNEL_CHANGE_ON_BEACON)
-			reg_call_notifier(wiphy, lr);
-	}
+
+	return channel_changed;
 }
 
 /*
@@ -2194,14 +2208,20 @@ static void wiphy_update_new_beacon(struct wiphy *wiphy,
 {
 	unsigned int i;
 	struct ieee80211_supported_band *sband;
+	bool changed = false;
 
 	if (!wiphy->bands[reg_beacon->chan.band])
 		return;
 
 	sband = wiphy->bands[reg_beacon->chan.band];
 
+	guard(wiphy)(wiphy);
+
 	for (i = 0; i < sband->n_channels; i++)
-		handle_reg_beacon(wiphy, i, reg_beacon);
+		changed |= handle_reg_beacon(wiphy, i, reg_beacon);
+
+	if (changed && wiphy->flags & WIPHY_FLAG_CHANNEL_CHANGE_ON_BEACON)
+		reg_call_notifier(wiphy, get_last_request());
 }
 
 /*
@@ -2507,7 +2527,7 @@ static void reg_check_chans_work(struct work_struct *work)
 
 	rcu_read_lock();
 
-	list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list)
+	for_each_rdev(rdev)
 		wiphy_work_queue(&rdev->wiphy, &rdev->reg_check_chans_wk);
 
 	rcu_read_unlock();
@@ -2530,6 +2550,8 @@ static void wiphy_update_regulatory(struct wiphy *wiphy,
 	enum nl80211_band band;
 	struct regulatory_request *lr = get_last_request();
 
+	lockdep_assert_wiphy(wiphy);
+
 	if (ignore_reg_update(wiphy, initiator)) {
 		/*
 		 * Regulatory updates set by CORE are ignored for custom
@@ -2546,23 +2568,27 @@ static void wiphy_update_regulatory(struct wiphy *wiphy,
 
 	lr->dfs_region = get_cfg80211_regdom()->dfs_region;
 
+	/* the notifier is called below, so ignore beacon hint changes */
 	for (band = 0; band < NUM_NL80211_BANDS; band++)
 		handle_band(wiphy, initiator, wiphy->bands[band]);
 
 	reg_process_beacons(wiphy);
 	reg_process_ht_flags(wiphy);
+
 	reg_call_notifier(wiphy, lr);
 }
 
 static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator)
 {
 	struct cfg80211_registered_device *rdev;
-	struct wiphy *wiphy;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	for_each_rdev(rdev) {
-		wiphy = &rdev->wiphy;
+		struct wiphy *wiphy = &rdev->wiphy;
+
+		guard(wiphy)(wiphy);
+
 		wiphy_update_regulatory(wiphy, initiator);
 	}
 
@@ -2676,13 +2702,11 @@ void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
 	if (IS_ERR(new_regd))
 		return;
 
-	rtnl_lock();
 	scoped_guard(wiphy, wiphy) {
 		tmp = get_wiphy_regdom(wiphy);
 		rcu_assign_pointer(wiphy->regd, new_regd);
 		rcu_free_regdom(tmp);
 	}
-	rtnl_unlock();
 }
 EXPORT_SYMBOL(wiphy_apply_custom_regulatory);
 
@@ -2849,12 +2873,12 @@ reg_process_hint_driver(struct wiphy *wiphy,
 		if (IS_ERR(regd))
 			return REG_REQ_IGNORE;
 
-		tmp = get_wiphy_regdom(wiphy);
-		ASSERT_RTNL();
+		lockdep_assert_held(&cfg80211_mutex);
 		scoped_guard(wiphy, wiphy) {
+			tmp = get_wiphy_regdom(wiphy);
 			rcu_assign_pointer(wiphy->regd, regd);
+			rcu_free_regdom(tmp);
 		}
-		rcu_free_regdom(tmp);
 	}
 
 
@@ -3042,7 +3066,8 @@ static void wiphy_all_share_dfs_chan_state(struct wiphy *wiphy)
 {
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
+	lockdep_assert_wiphy(wiphy);
 
 	for_each_rdev(rdev) {
 		if (wiphy == &rdev->wiphy)
@@ -3094,6 +3119,8 @@ static void reg_process_hint(struct regulatory_request *reg_request)
 	 */
 	if (treatment == REG_REQ_ALREADY_SET && wiphy &&
 	    wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
+		guard(wiphy)(wiphy);
+
 		wiphy_update_regulatory(wiphy, initiator);
 		wiphy_all_share_dfs_chan_state(wiphy);
 		reg_check_channels();
@@ -3108,13 +3135,16 @@ static void reg_process_hint(struct regulatory_request *reg_request)
 static void notify_self_managed_wiphys(struct regulatory_request *request)
 {
 	struct cfg80211_registered_device *rdev;
-	struct wiphy *wiphy;
 
 	for_each_rdev(rdev) {
-		wiphy = &rdev->wiphy;
+		struct wiphy *wiphy = &rdev->wiphy;
+
 		if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED &&
-		    request->initiator == NL80211_REGDOM_SET_BY_USER)
+		    request->initiator == NL80211_REGDOM_SET_BY_USER) {
+			guard(wiphy)(wiphy);
+
 			reg_call_notifier(wiphy, request);
+		}
 	}
 }
 
@@ -3166,12 +3196,14 @@ static void reg_process_pending_beacon_hints(void)
 {
 	struct cfg80211_registered_device *rdev;
 	struct reg_beacon *pending_beacon, *tmp;
+	LIST_HEAD(pending);
 
 	/* This goes through the _pending_ beacon list */
 	spin_lock_bh(&reg_pending_beacons_lock);
+	list_splice_tail_init(&reg_pending_beacons, &pending);
+	spin_unlock_bh(&reg_pending_beacons_lock);
 
-	list_for_each_entry_safe(pending_beacon, tmp,
-				 &reg_pending_beacons, list) {
+	list_for_each_entry_safe(pending_beacon, tmp, &pending, list) {
 		list_del_init(&pending_beacon->list);
 
 		/* Applies the beacon hint to current wiphys */
@@ -3181,8 +3213,6 @@ static void reg_process_pending_beacon_hints(void)
 		/* Remembers the beacon hint for new wiphys or reg changes */
 		list_add_tail(&pending_beacon->list, &reg_beacon_list);
 	}
-
-	spin_unlock_bh(&reg_pending_beacons_lock);
 }
 
 static void reg_process_self_managed_hint(struct wiphy *wiphy)
@@ -3193,7 +3223,6 @@ static void reg_process_self_managed_hint(struct wiphy *wiphy)
 	enum nl80211_band band;
 	struct regulatory_request request = {};
 
-	ASSERT_RTNL();
 	lockdep_assert_wiphy(wiphy);
 
 	spin_lock(&reg_requests_lock);
@@ -3228,7 +3257,7 @@ static void reg_process_self_managed_hints(void)
 {
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	for_each_rdev(rdev) {
 		guard(wiphy)(&rdev->wiphy);
@@ -3241,11 +3270,11 @@ static void reg_process_self_managed_hints(void)
 
 static void reg_todo(struct work_struct *work)
 {
-	rtnl_lock();
+	guard(mutex)(&cfg80211_mutex);
+
 	reg_process_pending_hints();
 	reg_process_pending_beacon_hints();
 	reg_process_self_managed_hints();
-	rtnl_unlock();
 }
 
 static void queue_regulatory_request(struct regulatory_request *request)
@@ -3524,7 +3553,7 @@ static void restore_regulatory_settings(bool reset_user, bool cached)
 	LIST_HEAD(tmp_reg_req_list);
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	/*
 	 * Clear the indoor setting in case that it is not controlled by user
@@ -3571,8 +3600,11 @@ static void restore_regulatory_settings(bool reset_user, bool cached)
 	for_each_rdev(rdev) {
 		if (rdev->wiphy.regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
 			continue;
-		if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG)
+		if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG) {
+			guard(wiphy)(&rdev->wiphy);
+
 			restore_custom_reg_settings(&rdev->wiphy);
+		}
 	}
 
 	if (cached && (!is_an_alpha2(alpha2) ||
@@ -3639,6 +3671,8 @@ static bool is_wiphy_all_set_reg_flag(enum ieee80211_regulatory_flags flag)
 
 void regulatory_hint_disconnect(void)
 {
+	guard(mutex)(&cfg80211_mutex);
+
 	/* Restore of regulatory settings is not required when wiphy(s)
 	 * ignore IE from connected access point but clearance of beacon hints
 	 * is required when wiphy(s) supports beacon hints.
@@ -3907,7 +3941,7 @@ static int reg_set_rd_driver(const struct ieee80211_regdomain *rd,
 		return -ENODEV;
 
 	if (!driver_request->intersect) {
-		ASSERT_RTNL();
+		lockdep_assert_held(&cfg80211_mutex);
 		scoped_guard(wiphy, request_wiphy) {
 			if (request_wiphy->regd)
 				tmp = get_wiphy_regdom(request_wiphy);
@@ -3933,9 +3967,11 @@ static int reg_set_rd_driver(const struct ieee80211_regdomain *rd,
 	 * However if a driver requested this specific regulatory
 	 * domain we keep it for its private use
 	 */
-	tmp = get_wiphy_regdom(request_wiphy);
-	rcu_assign_pointer(request_wiphy->regd, rd);
-	rcu_free_regdom(tmp);
+	scoped_guard(wiphy, request_wiphy) {
+		tmp = get_wiphy_regdom(request_wiphy);
+		rcu_assign_pointer(request_wiphy->regd, rd);
+		rcu_free_regdom(tmp);
+	}
 
 	rd = NULL;
 
@@ -4108,7 +4144,7 @@ int regulatory_set_wiphy_regd_sync(struct wiphy *wiphy,
 {
 	int ret;
 
-	ASSERT_RTNL();
+	lockdep_assert_wiphy(wiphy);
 
 	ret = __regulatory_set_wiphy_regd(wiphy, rd);
 	if (ret)
@@ -4125,25 +4161,30 @@ void wiphy_regulatory_register(struct wiphy *wiphy)
 {
 	struct regulatory_request *lr = get_last_request();
 
-	/* self-managed devices ignore beacon hints and country IE */
-	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
-		wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS |
-					   REGULATORY_COUNTRY_IE_IGNORE;
+	lockdep_assert_held(&cfg80211_mutex);
 
-		/*
-		 * The last request may have been received before this
-		 * registration call. Call the driver notifier if
-		 * initiator is USER.
-		 */
-		if (lr->initiator == NL80211_REGDOM_SET_BY_USER)
-			reg_call_notifier(wiphy, lr);
-	}
+	scoped_guard(wiphy, wiphy) {
+		/* self-managed devices ignore beacon hints and country IE */
+		if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
+			wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS |
+						   REGULATORY_COUNTRY_IE_IGNORE;
 
-	if (!reg_dev_ignore_cell_hint(wiphy))
-		reg_num_devs_support_basehint++;
+			/*
+			 * The last request may have been received before this
+			 * registration call. Call the driver notifier if
+			 * initiator is USER.
+			 */
+			if (lr->initiator == NL80211_REGDOM_SET_BY_USER)
+				reg_call_notifier(wiphy, lr);
+		}
+
+		if (!reg_dev_ignore_cell_hint(wiphy))
+			reg_num_devs_support_basehint++;
+
+		wiphy_update_regulatory(wiphy, lr->initiator);
+		wiphy_all_share_dfs_chan_state(wiphy);
+	}
 
-	wiphy_update_regulatory(wiphy, lr->initiator);
-	wiphy_all_share_dfs_chan_state(wiphy);
 	reg_process_self_managed_hints();
 }
 
@@ -4152,6 +4193,8 @@ void wiphy_regulatory_deregister(struct wiphy *wiphy)
 	struct wiphy *request_wiphy = NULL;
 	struct regulatory_request *lr;
 
+	lockdep_assert_held(&cfg80211_mutex);
+
 	lr = get_last_request();
 
 	if (!reg_dev_ignore_cell_hint(wiphy))
@@ -4255,7 +4298,7 @@ static void cfg80211_check_and_end_cac(struct cfg80211_registered_device *rdev)
 	struct wireless_dev *wdev;
 	unsigned int link_id;
 
-	guard(wiphy)(&rdev->wiphy);
+	lockdep_assert_wiphy(&rdev->wiphy);
 
 	/* If we finished CAC or received radar, we should end any
 	 * CAC running on the same channels.
@@ -4290,7 +4333,7 @@ void regulatory_propagate_dfs_state(struct wiphy *wiphy,
 {
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	if (WARN_ON(!cfg80211_chandef_valid(chandef)))
 		return;
@@ -4306,6 +4349,8 @@ void regulatory_propagate_dfs_state(struct wiphy *wiphy,
 					   chandef->chan->center_freq))
 			continue;
 
+		guard(wiphy)(&rdev->wiphy);
+
 		cfg80211_set_dfs_state(&rdev->wiphy, chandef, dfs_state);
 
 		if (event == NL80211_RADAR_DETECTED ||
@@ -4390,15 +4435,15 @@ void regulatory_exit(void)
 {
 	struct regulatory_request *reg_request, *tmp;
 	struct reg_beacon *reg_beacon, *btmp;
+	const struct fwdb_header *db;
 
 	cancel_work_sync(&reg_work);
 	cancel_crda_timeout_sync();
 	cancel_delayed_work_sync(&reg_check_chans);
 
 	/* Lock to suppress warnings */
-	rtnl_lock();
-	reset_regdomains(true, NULL);
-	rtnl_unlock();
+	scoped_guard(mutex, &cfg80211_mutex)
+		reset_regdomains(true, NULL);
 
 	dev_set_uevent_suppress(&reg_fdev->dev, true);
 
@@ -4419,8 +4464,10 @@ void regulatory_exit(void)
 		kfree(reg_request);
 	}
 
-	if (!IS_ERR_OR_NULL(regdb))
-		kfree(regdb);
+	/* RCU is irrelevant on the way out */
+	db = rcu_dereference_protected(global_regdb, true);
+	if (!IS_ERR_OR_NULL(db))
+		kfree(db);
 	if (!IS_ERR_OR_NULL(cfg80211_user_regdom))
 		kfree(cfg80211_user_regdom);
 

3/2 2026/10/06 10:49 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 25665
							output: 33
							thoughts: 1220
						

Thoughts:
**Fuzzing Relevance Assessment: Wireless Subsystem Locking Rework**

Okay, so I need to assess this git commit for kernel fuzzing potential, specifically targeting amd64. My initial thought is to dive into the changes and figure out the impact.

First, I see a substantial number of modified files across the wireless subsystem, specifically: `drivers/net/wireless/ath/`, `drivers/net/wireless/broadcom/`, `drivers/net/wireless/realtek/`, `drivers/net/wireless/virtual/`, `include/net/cfg80211.h`, and `net/wireless/`. The fact that it touches drivers *and* core wireless code is interesting, especially the inclusion of `mac80211_hwsim_main.c`. That virtual driver is a gold mine for fuzzing, given its accessibility in QEMU and syzkaller's frequent interaction with it and nl80211.

Now, I'll need to understand the *why* of these changes. I'll check the commit message and surrounding context for this patch series. My tools are limited, but I have `grepper`, `read-file`, `codesearch-dir-index`, and `set-results` at my disposal. Let's start with those.

Looking closer, I'm seeing changes focusing on:

*   `mac80211_hwsim_main.c`: The presence of this file is great! Easily testable.
*   `net/wireless/nl80211.c`: Specifically, I'm seeing a refactoring of locking within `nl80211_pre_doit`, `nl80211_prepare_wdev_dump`, `nl80211_lock_and_recheck`. This appears to be a big move away from relying solely on the RTNL lock and towards more granular locking with RCU, `cfg80211_mutex`, and `wiphy_lock()`. I also see changes to `net/wireless/reg.c`, `net/wireless/mlme.c`, and `net/wireless/core.c` (specifically, the addition of `DEFINE_MUTEX(cfg80211_mutex);`).

This is a **major** locking rework within the `cfg80211` and `nl80211` wireless subsystem! Given that cfg80211/nl80211 is reachable via generic netlink (nl80211) from unprivileged userspace (or standard users via CAP\_NET\_ADMIN, network namespaces) *and* with the availability of virtual interfaces like mac80211\_hwsim, this is HIGHLY fuzzable. Syzkaller heavily exercises this area already!

The locking strategy within `nl80211_pre_doit()` now looks like: instead of immediately using `rtnl_lock()`, it checks `NL80211_FLAG_NEED_RTNL`. If RTNL isn't needed, RCU read lock is used with device refcount, and `wiphy_lock()` or `nl80211_lock_and_recheck()` is done. Also, `wdev->is_running` handling, `cfg80211_mutex` usage, and DFS update work refactoring are all appearing. I'm also noticing lockdep assertions like `lockdep_assert_held(&cfg80211_mutex)` and `lockdep_assert_wiphy(wiphy)`.

This screams "worth fuzzing" to me. The very nature of the changes (locking, synchronization, and subtle race conditions) means we're likely to uncover subtle races, deadlocks, lockdep splats, use-after-free conditions, and even unregister races.

Now, I need to verify:

*   **WorthFuzzing:** Definitely true!
*   **Reachable?** Absolutely. nl80211 is a Generic Netlink family, and mac80211\_hwsim is easily loaded in a syzkaller test environment.
*   **Locking Changes?** Yes, spanning `net/wireless/nl80211.c`, `net/wireless/reg.c`, `net/wireless/core.c`, and `net/wireless/mlme.c`.

Next, I need to figure out the required kernel config options. I'll need to investigate `net/wireless/Kconfig` and the virtual driver's Kconfig file (wherever mac80211\_hwsim resides) to identify the specific configuration options we'll need to enable.



4/2 2026/10/06 10:49 tool grepper
0m Args:
{
  "Expression": "config CFG80211",
  "PathPrefixes": [
    "net/wireless/"
  ]
}

Results:
{
  "Output": "net/wireless/Kconfig=14=config WEXT_PRIV\n--\nnet/wireless/Kconfig-16-\nnet/wireless/Kconfig:17:config CFG80211\nnet/wireless/Kconfig-18-\ttristate \"cfg80211 - wireless configuration API\"\n--\nnet/wireless/Kconfig=38=config NL80211_TESTMODE\n--\nnet/wireless/Kconfig-51-\nnet/wireless/Kconfig:52:config CFG80211_DEVELOPER_WARNINGS\nnet/wireless/Kconfig-53-\tbool \"enable developer warnings\"\n--\nnet/wireless/Kconfig-67-\nnet/wireless/Kconfig:68:config CFG80211_CERTIFICATION_ONUS\nnet/wireless/Kconfig-69-\tbool \"cfg80211 certification onus\"\n--\nnet/wireless/Kconfig-88-\nnet/wireless/Kconfig:89:config CFG80211_REQUIRE_SIGNED_REGDB\nnet/wireless/Kconfig-90-\tbool \"require regdb signature\" if CFG80211_CERTIFICATION_ONUS\n--\nnet/wireless/Kconfig-98-\nnet/wireless/Kconfig:99:config CFG80211_USE_KERNEL_REGDB_KEYS\nnet/wireless/Kconfig-100-\tbool \"allow regdb keys shipped with the kernel\" if CFG80211_CERTIFICATION_ONUS\n--\nnet/wireless/Kconfig-110-\nnet/wireless/Kconfig:111:config CFG80211_EXTRA_REGDB_KEYDIR\nnet/wireless/Kconfig-112-\tstring \"additional regdb key directory\" if CFG80211_CERTIFICATION_ONUS\n--\nnet/wireless/Kconfig-121-\nnet/wireless/Kconfig:122:config CFG80211_REG_CELLULAR_HINTS\nnet/wireless/Kconfig-123-\tbool \"cfg80211 regulatory support for cellular base station hints\"\n--\nnet/wireless/Kconfig-134-\nnet/wireless/Kconfig:135:config CFG80211_REG_RELAX_NO_IR\nnet/wireless/Kconfig-136-\tbool \"cfg80211 support for NO_IR relaxation\"\n--\nnet/wireless/Kconfig-158-\nnet/wireless/Kconfig:159:config CFG80211_DEFAULT_PS\nnet/wireless/Kconfig-160-\tbool \"enable powersave by default\"\n--\nnet/wireless/Kconfig-168-\nnet/wireless/Kconfig:169:config CFG80211_DEBUGFS\nnet/wireless/Kconfig-170-\tbool \"cfg80211 DebugFS entries\"\n--\nnet/wireless/Kconfig-176-\nnet/wireless/Kconfig:177:config CFG80211_CRDA_SUPPORT\nnet/wireless/Kconfig-178-\tbool \"support CRDA\" if EXPERT\n--\nnet/wireless/Kconfig-186-\nnet/wireless/Kconfig:187:config CFG80211_WEXT\nnet/wireless/Kconfig-188-\tbool \"cfg80211 wireless extensions compatibility\"\n--\nnet/wireless/Kconfig-193-\nnet/wireless/Kconfig:194:config CFG80211_KUNIT_TEST\nnet/wireless/Kconfig-195-\ttristate \"KUnit tests for cfg80211\" if !KUNIT_ALL_TESTS\n"
}

5/2 2026/10/06 10:49 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 28851
							output: 32
							thoughts: 31
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach that kernel configuration investigation:

**Investigating CFG80211 Dependencies**

Alright, time to get to the heart of this. My first thought is to take a look at `net/wireless/Kconfig`. Specifically, let's focus on lines 1 through 30. That's usually a good starting point to identify what CFG80211, which is always a crucial piece for wireless networking, hinges upon. This should quickly reveal the prerequisite configurations and modules that I need to be aware of. I'm expecting to see dependencies on things like core networking components, perhaps specific hardware drivers, or architectural considerations. Knowing these dependencies early on will allow me to assess the overall impact of my changes and ensure I don't inadvertently break existing functionality. It will also help me narrow down the specific areas that I need to investigate in more detail if I encounter any issues later on. Let's see what the Kconfig file has to say.



6/2 2026/10/06 10:49 tool read-file
0m Args:
{
  "File": "net/wireless/Kconfig",
  "FirstLine": 1,
  "LineCount": 40
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\tconfig WIRELESS_EXT\n   3:\t\tbool\n   4:\t\n   5:\tconfig WEXT_CORE\n   6:\t\tdef_bool y\n   7:\t\tdepends on CFG80211_WEXT || WIRELESS_EXT\n   8:\t\n   9:\tconfig WEXT_PROC\n  10:\t\tdef_bool y\n  11:\t\tdepends on PROC_FS\n  12:\t\tdepends on WEXT_CORE\n  13:\t\n  14:\tconfig WEXT_PRIV\n  15:\t\tbool\n  16:\t\n  17:\tconfig CFG80211\n  18:\t\ttristate \"cfg80211 - wireless configuration API\"\n  19:\t\tdepends on RFKILL || !RFKILL\n  20:\t\tselect FW_LOADER\n  21:\t\tselect CRC32\n  22:\t\t# may need to update this when certificates are changed and are\n  23:\t\t# using a different algorithm, though right now they shouldn't\n  24:\t\t# (this is here rather than below to allow it to be a module)\n  25:\t\tselect CRYPTO_SHA256 if CFG80211_USE_KERNEL_REGDB_KEYS\n  26:\t\thelp\n  27:\t\t  cfg80211 is the Linux wireless LAN (802.11) configuration API.\n  28:\t\t  Enable this if you have a wireless device.\n  29:\t\n  30:\t\t  For more information refer to documentation on the wireless wiki:\n  31:\t\n  32:\t\t  https://wireless.wiki.kernel.org/en/developers/Documentation/cfg80211\n  33:\t\n  34:\t\t  When built as a module it will be called cfg80211.\n  35:\t\n  36:\tif CFG80211\n  37:\t\n  38:\tconfig NL80211_TESTMODE\n  39:\t\tbool \"nl80211 testmode command\"\n  40:\t\thelp\n"
}

7/2 2026/10/06 10:49 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29671
							output: 37
							thoughts: 18
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:

**Diving into the MAC80211_HWSIM Kconfig**

Alright, time to shift gears. We've been looking at X, Y, and Z, but it's crucial we don't overlook a potential sticking point. My gut is telling me there's something to be gleaned from a review of the `MAC80211_HWSIM Kconfig` file. It's often the unsung hero, the configuration file, that holds the keys to understanding and troubleshooting how hardware simulation is set up within the MAC80211 framework. I need to make sure we've accounted for every possible configuration variable and their potential impact on our current work. Let's see if it's going to offer any immediate clues.



8/2 2026/10/06 10:49 tool grepper
0m Args:
{
  "Expression": "MAC80211_HWSIM",
  "PathPrefixes": [
    "drivers/net/wireless/"
  ]
}

Results:
{
  "Output": "\nFull output is too long, showing 200 out of 258 lines.\nUse more precise expression or PathPrefixes if possible.\n\nNumber of matching lines per file (7 files in total):\ndrivers/net/wireless/virtual/Kconfig:1\ndrivers/net/wireless/virtual/Makefile:1\ndrivers/net/wireless/virtual/mac80211_hwsim.h:25\ndrivers/net/wireless/virtual/mac80211_hwsim_i.h:3\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:22\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:23\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.h:9\n\ndrivers/net/wireless/virtual/Kconfig:1:config MAC80211_HWSIM\ndrivers/net/wireless/virtual/Kconfig-2-\ttristate \"Simulated radio testing tool for mac80211\"\n--\ndrivers/net/wireless/virtual/Makefile:1:obj-$(CONFIG_MAC80211_HWSIM)\t+= mac80211_hwsim.o\ndrivers/net/wireless/virtual/Makefile-2-mac80211_hwsim-objs\t\t+= mac80211_hwsim_main.o\n--\ndrivers/net/wireless/virtual/mac80211_hwsim.h-8-\ndrivers/net/wireless/virtual/mac80211_hwsim.h:9:#ifndef __MAC80211_HWSIM_H\ndrivers/net/wireless/virtual/mac80211_hwsim.h:10:#define __MAC80211_HWSIM_H\ndrivers/net/wireless/virtual/mac80211_hwsim.h-11-\n--\ndrivers/net/wireless/virtual/mac80211_hwsim.h=220=struct hwsim_tx_rate {\n--\ndrivers/net/wireless/virtual/mac80211_hwsim.h-233- *\ndrivers/net/wireless/virtual/mac80211_hwsim.h:234: * @MAC80211_HWSIM_TX_RC_USE_RTS_CTS: Use RTS/CTS exchange for this rate.\ndrivers/net/wireless/virtual/mac80211_hwsim.h:235: * @MAC80211_HWSIM_TX_RC_USE_CTS_PROTECT: CTS-to-self protection is required.\ndrivers/net/wireless/virtual/mac80211_hwsim.h-236- *\tThis is set if the current BSS requires ERP protection.\ndrivers/net/wireless/virtual/mac80211_hwsim.h:237: * @MAC80211_HWSIM_TX_RC_USE_SHORT_PREAMBLE: Use short preamble.\ndrivers/net/wireless/virtual/mac80211_hwsim.h:238: * @MAC80211_HWSIM_TX_RC_MCS: HT rate.\ndrivers/net/wireless/virtual/mac80211_hwsim.h:239: * @MAC80211_HWSIM_TX_RC_VHT_MCS: VHT MCS rate, in this case the idx field is\ndrivers/net/wireless/virtual/mac80211_hwsim.h-240- *\tsplit into a higher 4 bits (Nss) and lower 4 bits (MCS number)\ndrivers/net/wireless/virtual/mac80211_hwsim.h:241: * @MAC80211_HWSIM_TX_RC_GREEN_FIELD: Indicates whether this rate should be used\ndrivers/net/wireless/virtual/mac80211_hwsim.h-242- *\tin Greenfield mode.\ndrivers/net/wireless/virtual/mac80211_hwsim.h:243: * @MAC80211_HWSIM_TX_RC_40_MHZ_WIDTH: Indicates if the Channel Width should be\ndrivers/net/wireless/virtual/mac80211_hwsim.h-244- *\t40 MHz.\ndrivers/net/wireless/virtual/mac80211_hwsim.h:245: * @MAC80211_HWSIM_TX_RC_80_MHZ_WIDTH: Indicates 80 MHz transmission\ndrivers/net/wireless/virtual/mac80211_hwsim.h:246: * @MAC80211_HWSIM_TX_RC_160_MHZ_WIDTH: Indicates 160 MHz transmission\ndrivers/net/wireless/virtual/mac80211_hwsim.h-247- *\t(80+80 isn't supported yet)\ndrivers/net/wireless/virtual/mac80211_hwsim.h:248: * @MAC80211_HWSIM_TX_RC_DUP_DATA: The frame should be transmitted on both of\ndrivers/net/wireless/virtual/mac80211_hwsim.h-249- *\tthe adjacent 20 MHz channels, if the current channel type is\ndrivers/net/wireless/virtual/mac80211_hwsim.h-250- *\tNL80211_CHAN_HT40MINUS or NL80211_CHAN_HT40PLUS.\ndrivers/net/wireless/virtual/mac80211_hwsim.h:251: * @MAC80211_HWSIM_TX_RC_SHORT_GI: Short Guard interval should be used for this\ndrivers/net/wireless/virtual/mac80211_hwsim.h-252- *\trate.\n--\ndrivers/net/wireless/virtual/mac80211_hwsim.h=254=enum hwsim_tx_rate_flags {\ndrivers/net/wireless/virtual/mac80211_hwsim.h:255:\tMAC80211_HWSIM_TX_RC_USE_RTS_CTS\t\t= BIT(0),\ndrivers/net/wireless/virtual/mac80211_hwsim.h:256:\tMAC80211_HWSIM_TX_RC_USE_CTS_PROTECT\t\t= BIT(1),\ndrivers/net/wireless/virtual/mac80211_hwsim.h:257:\tMAC80211_HWSIM_TX_RC_USE_SHORT_PREAMBLE\t= BIT(2),\ndrivers/net/wireless/virtual/mac80211_hwsim.h-258-\ndrivers/net/wireless/virtual/mac80211_hwsim.h-259-\t/* rate index is an HT/VHT MCS instead of an index */\ndrivers/net/wireless/virtual/mac80211_hwsim.h:260:\tMAC80211_HWSIM_TX_RC_MCS\t\t\t= BIT(3),\ndrivers/net/wireless/virtual/mac80211_hwsim.h:261:\tMAC80211_HWSIM_TX_RC_GREEN_FIELD\t\t= BIT(4),\ndrivers/net/wireless/virtual/mac80211_hwsim.h:262:\tMAC80211_HWSIM_TX_RC_40_MHZ_WIDTH\t\t= BIT(5),\ndrivers/net/wireless/virtual/mac80211_hwsim.h:263:\tMAC80211_HWSIM_TX_RC_DUP_DATA\t\t= BIT(6),\ndrivers/net/wireless/virtual/mac80211_hwsim.h:264:\tMAC80211_HWSIM_TX_RC_SHORT_GI\t\t= BIT(7),\ndrivers/net/wireless/virtual/mac80211_hwsim.h:265:\tMAC80211_HWSIM_TX_RC_VHT_MCS\t\t\t= BIT(8),\ndrivers/net/wireless/virtual/mac80211_hwsim.h:266:\tMAC80211_HWSIM_TX_RC_80_MHZ_WIDTH\t\t= BIT(9),\ndrivers/net/wireless/virtual/mac80211_hwsim.h:267:\tMAC80211_HWSIM_TX_RC_160_MHZ_WIDTH\t\t= BIT(10),\ndrivers/net/wireless/virtual/mac80211_hwsim.h-268-};\n--\ndrivers/net/wireless/virtual/mac80211_hwsim.h=331=enum hwsim_rate_info_attributes {\n--\ndrivers/net/wireless/virtual/mac80211_hwsim.h-350-\ndrivers/net/wireless/virtual/mac80211_hwsim.h:351:#endif /* __MAC80211_HWSIM_H */\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_i.h-9-\ndrivers/net/wireless/virtual/mac80211_hwsim_i.h:10:#ifndef __MAC80211_HWSIM_I_H\ndrivers/net/wireless/virtual/mac80211_hwsim_i.h:11:#define __MAC80211_HWSIM_I_H\ndrivers/net/wireless/virtual/mac80211_hwsim_i.h-12-\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_i.h=165=void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_i.h-167-\ndrivers/net/wireless/virtual/mac80211_hwsim_i.h:168:#endif /* __MAC80211_HWSIM_I_H */\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=712=static struct mac80211_hwsim_data *get_hwsim_data_ref_from_addr(const u8 *addr)\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-716-\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:717:/* MAC80211_HWSIM netlink family */\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-718-static struct genl_family hwsim_genl_family;\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=724=static const struct genl_multicast_group hwsim_mcgrps[] = {\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-727-\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:728:/* MAC80211_HWSIM netlink policy */\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-729-\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=865=static const struct nla_policy hwsim_genl_policy[HWSIM_ATTR_MAX + 1] = {\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-902-\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:903:/* MAC80211_HWSIM virtio queues */\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-904-static struct virtqueue *hwsim_vqs[HWSIM_NUM_VQS];\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=1577=static inline u16 trans_tx_rate_flags_ieee2hwsim(struct ieee80211_tx_rate *rate)\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1581-\tif (rate-\u003eflags \u0026 IEEE80211_TX_RC_USE_RTS_CTS)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1582:\t\tresult |= MAC80211_HWSIM_TX_RC_USE_RTS_CTS;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1583-\tif (rate-\u003eflags \u0026 IEEE80211_TX_RC_USE_CTS_PROTECT)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1584:\t\tresult |= MAC80211_HWSIM_TX_RC_USE_CTS_PROTECT;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1585-\tif (rate-\u003eflags \u0026 IEEE80211_TX_RC_USE_SHORT_PREAMBLE)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1586:\t\tresult |= MAC80211_HWSIM_TX_RC_USE_SHORT_PREAMBLE;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1587-\tif (rate-\u003eflags \u0026 IEEE80211_TX_RC_MCS)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1588:\t\tresult |= MAC80211_HWSIM_TX_RC_MCS;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1589-\tif (rate-\u003eflags \u0026 IEEE80211_TX_RC_GREEN_FIELD)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1590:\t\tresult |= MAC80211_HWSIM_TX_RC_GREEN_FIELD;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1591-\tif (rate-\u003eflags \u0026 IEEE80211_TX_RC_40_MHZ_WIDTH)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1592:\t\tresult |= MAC80211_HWSIM_TX_RC_40_MHZ_WIDTH;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1593-\tif (rate-\u003eflags \u0026 IEEE80211_TX_RC_DUP_DATA)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1594:\t\tresult |= MAC80211_HWSIM_TX_RC_DUP_DATA;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1595-\tif (rate-\u003eflags \u0026 IEEE80211_TX_RC_SHORT_GI)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1596:\t\tresult |= MAC80211_HWSIM_TX_RC_SHORT_GI;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1597-\tif (rate-\u003eflags \u0026 IEEE80211_TX_RC_VHT_MCS)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1598:\t\tresult |= MAC80211_HWSIM_TX_RC_VHT_MCS;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1599-\tif (rate-\u003eflags \u0026 IEEE80211_TX_RC_80_MHZ_WIDTH)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1600:\t\tresult |= MAC80211_HWSIM_TX_RC_80_MHZ_WIDTH;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1601-\tif (rate-\u003eflags \u0026 IEEE80211_TX_RC_160_MHZ_WIDTH)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1602:\t\tresult |= MAC80211_HWSIM_TX_RC_160_MHZ_WIDTH;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1603-\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=3494=static const char mac80211_hwsim_gstrings_stats[][ETH_GSTRING_LEN] = {\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-3504-\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:3505:#define MAC80211_HWSIM_SSTATS_LEN ARRAY_SIZE(mac80211_hwsim_gstrings_stats)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-3506-\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=3516=static int mac80211_hwsim_get_et_sset_count(struct ieee80211_hw *hw,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-3519-\tif (sset == ETH_SS_STATS)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:3520:\t\treturn MAC80211_HWSIM_SSTATS_LEN;\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-3521-\treturn 0;\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=3524=static void mac80211_hwsim_get_et_stats(struct ieee80211_hw *hw,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-3539-\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:3540:\tWARN_ON(i != MAC80211_HWSIM_SSTATS_LEN);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-3541-}\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=5666=static const u8 iftypes_ext_capa_ap[] = {\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-5674-\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:5675:#define MAC80211_HWSIM_MLD_CAPA_OPS\t\t\t\t\\\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-5676-\tFIELD_PREP_CONST(IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP, \\\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=5681=static const struct wiphy_iftype_ext_capab mac80211_hwsim_iftypes_ext_capa[] = {\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-5688-\t\t\t\t    IEEE80211_EML_CAP_EMLMR_SUPPORT,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:5689:\t\t.mld_capa_and_ops = MAC80211_HWSIM_MLD_CAPA_OPS,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-5690-\t\t/* CIP works as hwsim does not have control frames */\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=7249=static struct genl_family hwsim_genl_family __ro_after_init = {\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:7250:\t.name = \"MAC80211_HWSIM\",\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-7251-\t.version = 1,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=7569=static void hwsim_virtio_remove(struct virtio_device *vdev)\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-7577-\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:7578:/* MAC80211_HWSIM virtio device id table */\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-7579-static const struct virtio_device_id id_table[] = {\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:7580:\t{ VIRTIO_ID_MAC80211_HWSIM, VIRTIO_DEV_ANY_ID },\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-7581-\t{ 0 }\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c=84=static u64 hwsim_nan_get_master_rank(struct mac80211_hwsim_data *data)\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-88-\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:89:\tif (data-\u003enan.phase == MAC80211_HWSIM_NAN_PHASE_UP) {\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-90-\t\tmaster_pref = data-\u003enan.master_pref;\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c=253=void mac80211_hwsim_nan_rx(struct ieee80211_hw *hw,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-323-\t\t\t\tdata-\u003enan.phase ==\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:324:\t\t\t\tMAC80211_HWSIM_NAN_PHASE_SCAN;\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-325-\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-477-\t\t    (cg == own_cg \u0026\u0026 frame_amr \u003e curr_amr) ||\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:478:\t\t    data-\u003enan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN) {\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-479-\t\t\t/* Avoid a state transition */\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-509-\t\t\t */\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:510:\t\t\tdata-\u003enan.phase = MAC80211_HWSIM_NAN_PHASE_UP;\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-511-\t\t\tdata-\u003enan.random_factor_valid_dwst = 0;\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c=653=mac80211_hwsim_nan_exec_state_transitions(struct mac80211_hwsim_data *data)\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-666-\tif (data-\u003enan.master_transition_score \u003c 3)\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:667:\t\tdata-\u003enan.role = MAC80211_HWSIM_NAN_ROLE_MASTER;\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:668:\telse if (data-\u003enan.role == MAC80211_HWSIM_NAN_ROLE_MASTER \u0026\u0026\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-669-\t\t data-\u003enan.master_transition_score \u003e= 3)\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:670:\t\tdata-\u003enan.role = MAC80211_HWSIM_NAN_ROLE_SYNC;\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:671:\telse if (data-\u003enan.role == MAC80211_HWSIM_NAN_ROLE_SYNC \u0026\u0026\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-672-\t\t data-\u003enan.sync_transition_score \u003e= 3)\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:673:\t\tdata-\u003enan.role = MAC80211_HWSIM_NAN_ROLE_NON_SYNC;\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:674:\telse if (data-\u003enan.role == MAC80211_HWSIM_NAN_ROLE_NON_SYNC \u0026\u0026\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-675-\t\t data-\u003enan.sync_transition_score \u003c 3)\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:676:\t\tdata-\u003enan.role = MAC80211_HWSIM_NAN_ROLE_SYNC;\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-677-\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-683-\t */\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:684:\tif ((data-\u003enan.role == MAC80211_HWSIM_NAN_ROLE_MASTER ||\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-685-\t     data-\u003enan.instant_comm) \u0026\u0026\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-696-\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:697:\t\tif (data-\u003enan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN) {\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:698:\t\t\tdata-\u003enan.phase = MAC80211_HWSIM_NAN_PHASE_WARMUP;\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-699-\t\t\tdata-\u003enan.random_factor_valid_dwst = NAN_WARMUP_DWST;\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-702-\t\t} else {\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c:703:\t\t\tdata-\u003enan.phase = MAC80211_HWSIM_NAN_PHASE_UP;\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c-704-\t\t\tdata-\u003enan.random_factor_valid_dwst =\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_nan.c=738=mac80211_hwsim_nan_tx_beacon(struct mac80211_hwsim_data *data,\n\n"
}

9/2 2026/10/06 10:49 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37225
							output: 36
							thoughts: 16
						

Thoughts:
Here's a summary of my thought process, as requested:

**Navigating the Wireless Network Driver Configuration**

Okay, so I need to find something related to the wireless network drivers. Where would I start? Let's see... a good first step, particularly when dealing with kernel configuration, is usually the `Kconfig` files. These are the blueprints, so to speak, that define what options are available and how they relate to each other. Alright, let's target the likely location... `drivers/net/wireless/virtual/Kconfig`. That sounds about right. It's the virtual driver section of wireless network drivers so it's probably the right Kconfig file to get started. I need to dive in and see what's in there. I bet I can find what I'm looking for in that file.



10/2 2026/10/06 10:49 tool read-file
0m Args:
{
  "File": "drivers/net/wireless/virtual/Kconfig",
  "FirstLine": 1,
  "LineCount": 25
}

Results:
{
  "Contents": "   1:\tconfig MAC80211_HWSIM\n   2:\t\ttristate \"Simulated radio testing tool for mac80211\"\n   3:\t\tdepends on MAC80211\n   4:\t\thelp\n   5:\t\t  This driver is a developer testing tool that can be used to test\n   6:\t\t  IEEE 802.11 networking stack (mac80211) functionality. This is not\n   7:\t\t  needed for normal wireless LAN usage and is only for testing. See\n   8:\t\t  Documentation/networking/mac80211_hwsim for more information on how\n   9:\t\t  to use this tool.\n  10:\t\n  11:\t\t  To compile this driver as a module, choose M here: the module will be\n  12:\t\t  called mac80211_hwsim.  If unsure, say N.\n  13:\t\n  14:\tconfig VIRT_WIFI\n  15:\t\ttristate \"Wifi wrapper for ethernet drivers\"\n  16:\t\tdepends on CFG80211\n  17:\t\thelp\n  18:\t\t  This option adds support for ethernet connections to appear as if they\n  19:\t\t  are wifi connections through a special rtnetlink device.\n  20:\t\n"
}

11/2 2026/10/06 10:49 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37776
							output: 39
							thoughts: 12
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:

**MAC80211 Dependency Check: Let's Get This Straight**

Alright, before we dive into anything further, it's that nagging voice again – *always* the dependencies. Time to make sure we're not overlooking something crucial. With MAC80211, it's even more critical; it's the beating heart of our wireless stack, so overlooking a dependency can be catastrophic down the road. Before I even think about making any changes or starting any project, I want to make sure I'm crystal clear on all the pieces that support MAC80211. This means I'm going to start mentally stepping through the build process, the kernel modules, the user space applications... essentially, I'm thinking, "what needs to be there, and what *depends* on it being there, for everything to play nicely?" I'm mentally running through the various drivers, the regulatory aspects, the cryptography, the core protocol implementations... We're gonna avoid hours of debugging later by being precise now. Now's the time to refresh the mental model of how all the pieces fit together!



12/2 2026/10/06 10:50 tool grepper
0m Args:
{
  "Expression": "config MAC80211\\b",
  "PathPrefixes": [
    "net/mac80211/"
  ]
}

Results:
{
  "Output": "net/mac80211/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nnet/mac80211/Kconfig:2:config MAC80211\nnet/mac80211/Kconfig-3-\ttristate \"Generic IEEE 802.11 Networking Stack (mac80211)\"\n"
}

13/2 2026/10/06 10:50 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38189
							output: 37
							thoughts: 25
						

Thoughts:
Okay, here's my interpretation of what I'm about to read in the Kconfig file, framed as my own thought process.

**Analyzing the WiFi Configuration (Lines 1-20)**

Alright, time to dive into `net/mac80211/Kconfig`. Based on the file path, I expect this to be the main configuration file for the mac80211 wireless subsystem within the Linux kernel. I'm going to quickly skim the first twenty lines to get the lay of the land, probably focusing on the top-level configuration options and how they're structured. My initial goal is to understand the scope of what we can configure here - is it broad, or is it specific to certain drivers or features? Are there any obvious dependencies on other kernel components? I’m looking for the overall framework and how the choices are presented to the user. I'll likely be assessing the use of `config` statements, `tristate` versus `bool` options, and the general way options are grouped and described. I’m also looking for any early hints on how modular the mac80211 implementation is; are we talking about a massive, monolithic block, or is there a clear architectural division that the Kconfig reveals? I need to build a mental map of the configuration space here, so I can dive deeper into specific areas later if necessary.



14/2 2026/10/06 10:50 tool read-file
0m Args:
{
  "File": "net/mac80211/Kconfig",
  "FirstLine": 1,
  "LineCount": 20
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\tconfig MAC80211\n   3:\t\ttristate \"Generic IEEE 802.11 Networking Stack (mac80211)\"\n   4:\t\tdepends on CFG80211\n   5:\t\tselect CRYPTO\n   6:\t\tselect CRYPTO_LIB_AES_CBC_MACS\n   7:\t\tselect CRYPTO_LIB_ARC4\n   8:\t\tselect CRYPTO_AES\n   9:\t\tselect CRYPTO_CCM\n  10:\t\tselect CRYPTO_GCM\n  11:\t\tselect CRC32\n  12:\t\thelp\n  13:\t\t  This option enables the hardware independent IEEE 802.11\n  14:\t\t  networking stack.\n  15:\t\n  16:\tcomment \"CFG80211 needs to be enabled for MAC80211\"\n  17:\t\tdepends on CFG80211=n\n  18:\t\n  19:\tif MAC80211 != n\n  20:\t\n"
}

15/2 2026/10/06 10:50 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38823
							output: 20
							thoughts: 131
						

Thoughts:
Okay, here's my breakdown of this.

**Investigating the MAC80211 Dependency Chain**

Right, so I'm looking at dependencies here. `MAC80211` relies on `CFG80211`, that much is clear. And, `MAC80211_HWSIM`? It's pretty straightforward, it depends on `MAC80211`. That's not the critical piece though. I need to understand what _CFG80211_ depends on, and how it's brought into the build. It's the lynchpin, isn't it?

Okay, time to check the Kconfig files. I'm going to look in `net/Kconfig` and `drivers/net/wireless/Kconfig`. Let's start with `drivers/net/wireless/Kconfig`. Aha! There it is: `config CFG80211: depends on RFKILL || !RFKILL`. That's interesting, essentially making it always enabled. Not the focus of this thought process.

Now, I'm at the point where I have to understand what includes `net/wireless/Kconfig` so I can understand the bigger picture of where CFG80211 is ultimately pulled in.



16/2 2026/10/06 10:50 tool grepper
0m Args:
{
  "Expression": "net/wireless/Kconfig"
}

Results:
{
  "Output": "drivers/net/Kconfig=534=source \"drivers/net/usb/Kconfig\"\ndrivers/net/Kconfig-535-\ndrivers/net/Kconfig:536:source \"drivers/net/wireless/Kconfig\"\ndrivers/net/Kconfig-537-\n--\nnet/Kconfig=430=if WIRELESS\nnet/Kconfig-431-\nnet/Kconfig:432:source \"net/wireless/Kconfig\"\nnet/Kconfig-433-source \"net/mac80211/Kconfig\"\n"
}

17/2 2026/10/06 10:50 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 39405
							output: 28
							thoughts: 57
						

Thoughts:
Here's my summary, as the thought process unfolds:

**Examining Wireless Configuration**

Okay, I'm digging into the kernel's network configuration, specifically within `net/Kconfig`. I see a conditional block: `if WIRELESS`.  That immediately piques my interest. It looks like the kernel's wireless features are modularized, which makes sense for flexibility. So, *if* the `WIRELESS` config option is enabled, it pulls in two other Kconfig files:  `net/wireless/Kconfig` and `net/mac80211/Kconfig`.  My next step? Let's check the definition and implications of the `WIRELESS` config option in `net/Kconfig` itself. I need to understand what triggers this entire chain.



18/2 2026/10/06 10:51 tool grepper
0m Args:
{
  "Expression": "config WIRELESS\\b",
  "PathPrefixes": [
    "net/"
  ]
}

Results:
{
  "Output": "net/Kconfig=422=config FIB_RULES\n--\nnet/Kconfig-424-\nnet/Kconfig:425:menuconfig WIRELESS\nnet/Kconfig-426-\tbool \"Wireless\"\n"
}

19/2 2026/10/06 10:51 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 39725
							output: 42
							thoughts: 241
						

Thoughts:
**My Configuration Musings**

Okay, so I need to make sure `WIRELESS` is enabled; that's fundamental. And naturally, `CFG80211` must be activated as well. Now, for fuzzing virtual wireless devices to really put `mac80211` and `cfg80211` through their paces, I'll need `MAC80211` and `MAC80211_HWSIM`. They're essential for simulating that hardware layer.

Hold on a second... is the `CFG80211_CRDA_SUPPORT` configuration option involved here? Best to double-check the patch, let's see... Ah, there it is: `#endif /* CONFIG_CFG80211_CRDA_SUPPORT */`. That confirms what I suspected.

Let me look closely at line 11026:

```c
#ifdef CONFIG_CFG80211_CRDA_SUPPORT
...
static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
```

Right, *`nl80211_set_reg` is definitely enclosed by `#ifdef CONFIG_CFG80211_CRDA_SUPPORT`*! This is important to remember. To be sure, I should check `net/wireless/nl80211.c` around that line, just to be completely certain.



20/2 2026/10/06 10:51 tool read-file
0m Args:
{
  "File": "net/wireless/nl80211.c",
  "FirstLine": 11020,
  "LineCount": 40
}

Results:
{
  "Contents": "11020:\t\n11021:\t\treturn 0;\n11022:\t}\n11023:\t\n11024:\tstatic int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)\n11025:\t{\n11026:\t\tstruct nlattr *tb[NL80211_REG_RULE_ATTR_MAX + 1];\n11027:\t\tstruct nlattr *nl_reg_rule;\n11028:\t\tchar *alpha2;\n11029:\t\tint rem_reg_rules, r;\n11030:\t\tu32 num_rules = 0, rule_idx = 0;\n11031:\t\tenum nl80211_dfs_regions dfs_region = NL80211_DFS_UNSET;\n11032:\t\tstruct ieee80211_regdomain *rd;\n11033:\t\n11034:\t\tif (!info-\u003eattrs[NL80211_ATTR_REG_ALPHA2])\n11035:\t\t\treturn -EINVAL;\n11036:\t\n11037:\t\tif (!info-\u003eattrs[NL80211_ATTR_REG_RULES])\n11038:\t\t\treturn -EINVAL;\n11039:\t\n11040:\t\talpha2 = nla_data(info-\u003eattrs[NL80211_ATTR_REG_ALPHA2]);\n11041:\t\n11042:\t\tif (info-\u003eattrs[NL80211_ATTR_DFS_REGION])\n11043:\t\t\tdfs_region = nla_get_u8(info-\u003eattrs[NL80211_ATTR_DFS_REGION]);\n11044:\t\n11045:\t\tnla_for_each_nested(nl_reg_rule, info-\u003eattrs[NL80211_ATTR_REG_RULES],\n11046:\t\t\t\t    rem_reg_rules) {\n11047:\t\t\tnum_rules++;\n11048:\t\t\tif (num_rules \u003e NL80211_MAX_SUPP_REG_RULES)\n11049:\t\t\t\treturn -EINVAL;\n11050:\t\t}\n11051:\t\n11052:\t\tmutex_lock(\u0026cfg80211_mutex);\n11053:\t\tif (!reg_is_valid_request(alpha2)) {\n11054:\t\t\tr = -EINVAL;\n11055:\t\t\tgoto out;\n11056:\t\t}\n11057:\t\n11058:\t\trd = kzalloc_flex(*rd, reg_rules, num_rules);\n11059:\t\tif (!rd) {\n"
}

21/2 2026/10/06 10:51 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 41064
							output: 47
							thoughts: 67
						

Thoughts:
Here's a first-person summary, tailored for an expert audience, based on the provided text:

**Analyzing Conditional Compilation in Wireless Driver Code**

Alright, take a look at this! Specifically, I'm zeroing in on this `nl80211_set_reg` function call. My initial reaction is that it's nestled within an `#ifdef CONFIG_CFG80211_CRDA_SUPPORT` block, which immediately piques my interest. I know that usually indicates a dependency on the CRDA (Central Regulatory Domain Agent) framework, which is standard in Linux wireless to enable dynamic regulatory domain changes.

So, my immediate next step is to quickly determine exactly where this conditional compilation starts. Knowing the context of that `#ifdef` block will likely shed light on the design choices made here. I want to trace the origin of the `#ifdef` to understand the conditions under which this `nl80211_set_reg` will or will not be compiled into the driver.



22/2 2026/10/06 10:51 tool grepper
0m Args:
{
  "Expression": "CONFIG_CFG80211_CRDA_SUPPORT",
  "PathPrefixes": [
    "net/wireless/nl80211.c"
  ]
}

Results:
{
  "Output": "net/wireless/nl80211.c=10931=static int nl80211_get_reg_dump(struct sk_buff *skb,\n--\nnet/wireless/nl80211.c-10972-\nnet/wireless/nl80211.c:10973:#ifdef CONFIG_CFG80211_CRDA_SUPPORT\nnet/wireless/nl80211.c-10974-static const struct nla_policy reg_rule_policy[NL80211_REG_RULE_ATTR_MAX + 1] = {\n--\nnet/wireless/nl80211.c=11024=static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)\n--\nnet/wireless/nl80211.c-11102-}\nnet/wireless/nl80211.c:11103:#endif /* CONFIG_CFG80211_CRDA_SUPPORT */\nnet/wireless/nl80211.c-11104-\n--\nnet/wireless/nl80211.c=20041=static const struct genl_small_ops nl80211_small_ops[] = {\n--\nnet/wireless/nl80211.c-20214-\t},\nnet/wireless/nl80211.c:20215:#ifdef CONFIG_CFG80211_CRDA_SUPPORT\nnet/wireless/nl80211.c-20216-\t{\n"
}

23/2 2026/10/06 10:51 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 41811
							output: 50
							thoughts: 225
						

Thoughts:
**Analyzing the CRDA Support Configuration**

Okay, so I'm looking at this patch and my initial thought is that enabling `CONFIG_CFG80211_CRDA_SUPPORT` is definitely a smart move, since `nl80211_set_reg` is compiled under it. Always a good idea to make sure those regulatory commands are available. To confirm, I'll quickly check `net/wireless/Kconfig` to see the specifics of `CFG80211_CRDA_SUPPORT`. Ah, right, it's defined there: `config CFG80211_CRDA_SUPPORT`, which is a `bool "support CRDA" if EXPERT` with a `default y`. So, it's enabled by default, but explicitly declaring it here is always good practice.

Now, let's take a look at the other files this patch is touching to ensure I don't miss anything.

Okay, let's dig into the code and see what else is being changed.



24/2 2026/10/06 10:51 tool read-file
0m Args:
{
  "File": "drivers/net/wireless/virtual/mac80211_hwsim_main.c",
  "FirstLine": 1130,
  "LineCount": 25
}

Results:
{
  "Contents": "1130:\t\tif (count \u003e= sizeof(buf))\n1131:\t\t\treturn -EINVAL;\n1132:\t\n1133:\t\tif (copy_from_user(buf, user_buf, count))\n1134:\t\t\treturn -EFAULT;\n1135:\t\n1136:\t\tguard(wiphy)(data-\u003ehw-\u003ewiphy);\n1137:\t\n1138:\t\t/* Check if background radar channel is configured */\n1139:\t\tif (!data-\u003eradar_background_chandef.chan)\n1140:\t\t\treturn -ENOENT;\n1141:\t\n1142:\t\tif (sysfs_streq(buf, \"radar\"))\n1143:\t\t\tcfg80211_background_radar_event(data-\u003ehw-\u003ewiphy,\n1144:\t\t\t\t\t\t\t\u0026data-\u003eradar_background_chandef,\n1145:\t\t\t\t\t\t\tGFP_KERNEL);\n1146:\t\telse if (sysfs_streq(buf, \"cancel\"))\n1147:\t\t\tcfg80211_background_cac_abort(data-\u003ehw-\u003ewiphy);\n1148:\t\telse\n1149:\t\t\treturn -EINVAL;\n1150:\t\n1151:\t\treturn count;\n1152:\t}\n1153:\t\n1154:\tstatic const struct file_operations hwsim_background_cac_ops = {\n"
}

25/2 2026/10/06 10:51 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 42677
							output: 37
							thoughts: 59
						

Thoughts:
Okay, let's see what I can make of this.

**Investigating the `hwsim_background_cac_ops` Debugfs File**

Right, `hwsim_background_cac_ops`… that's a debugfs file, isn't it? Let me think. My initial thought is to figure out what config option actually *enables* this thing. Is it a direct dependency on something within `mac80211_hwsim`, or does it rely on a more obscure setting? I should start by digging into the Kconfig.

Next step: trace it back. Where is `hwsim_background_cac_ops` being *registered*? The registration point will give me context, reveal the surrounding code, and hopefully shed light on its intended use and any prerequisites. Following the registration should clarify the dependencies and its place in the broader scheme of the driver.



26/2 2026/10/06 10:52 tool grepper
0m Args:
{
  "Expression": "hwsim_background_cac_ops",
  "PathPrefixes": [
    "drivers/net/wireless/virtual/"
  ]
}

Results:
{
  "Output": "drivers/net/wireless/virtual/mac80211_hwsim_main.c=1123=static ssize_t hwsim_background_cac_write(struct file *file,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1153-\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1154:static const struct file_operations hwsim_background_cac_ops = {\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1155-\t.write = hwsim_background_cac_write,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=5720=static int mac80211_hwsim_new_radio(struct genl_info *info,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6213-\t\t\t\t    data-\u003edebugfs,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6214:\t\t\t\t    data, \u0026hwsim_background_cac_ops);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6215-\tdebugfs_create_file(\"simulate_incumbent_signal_interference\", 0200,\n"
}

27/2 2026/10/06 10:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 43319
							output: 44
							thoughts: 2237
						

Thoughts:
**Analyzing Concurrency and Regulatory Changes in the Wireless Subsystem**

Okay, so I'm looking at this patch and immediately, I'm thinking, "radio creation triggered this, let's dive in!" My initial reaction is that this is a significant refactoring of concurrency primitives, particularly around regulatory domain handling in the wireless subsystem. I see a shift from relying heavily on the RTNL lock to a more nuanced approach involving a `cfg80211_mutex` and RCU reads. This looks like a big deal.

First, I examine the driver changes in `drivers/net/wireless/` to get a sense of the scope of affected areas. I'm scanning through the file list, noting the key functions like `ath11k_regdom_changes`, `brcms_reg_apply_beaconing_flags`, and so on.

Next, I'm digging into `net/wireless/nl80211.c` and `net/wireless/reg.c`. My expert eye is drawn to the functions like `nl80211_pre_doit`, and everything related to `cfg80211_mutex` and `wiphy_lock`. I see a trend here: a lot of the core regulatory functionality is getting refactored to use this new mutex, which implies a substantial reworking of the locking strategy. This clearly has high impact!

I'm noting how `nl80211_pre_doit` is changing. The use of `rcu_read_lock()` for looking up the `rdev` / `wdev` is an important detail. I immediately start to think about the potential for races and deadlocks. What happens if a wiphy unregisters mid-operation? Or if interfaces are moved between namespaces? How are we handling DFS channel expiration? I'm already mentally constructing test cases to cover those scenarios. The reliance on `wiphy_lock(&rdev->wiphy)` and the checks for registration and netns match in `nl80211_lock_and_recheck` are key.

Now, looking at `net/wireless/mlme.c`, I focus on `cfg80211_dfs_channels_update_work`. The use of the lock for collecting expired channels and calling `cfg80211_dfs_chan_expire` catches my attention. I'm considering scenarios where memory allocation might fail, and how that is handled. The fact that DFS update calls and notifications used to hold the RTNL lock and now holds `cfg80211_mutex` and `wiphy_lock(wiphy)` means it's definitely a candidate for thorough testing.

I'm thinking about the concurrency implications. This is a classic example of a high-impact synchronization overhaul in the kernel. The use of RCU, particularly the `rcu_read_lock()` and `synchronize_rcu()`, raises flags for race conditions. These are all touchpoints that need rigorous fuzzing.

My thought process shifts to fuzzing strategy and "FocusSymbols". I need to identify which specific functions would be the most valuable for a fuzzer. I need to avoid ubiquitous functions, and instead, target the logic. I am immediately going to try and target the non-static functions in the `.c` files, especially in `nl80211.c`, and note that since it is a function pointer in a `genl_family` callback, `nl80211_pre_doit` is definitely available to be targeted.



28/2 2026/10/06 10:52 tool grepper
0m Args:
{
  "Expression": "nl80211_pre_doit",
  "PathPrefixes": [
    "net/wireless/nl80211.c"
  ]
}

Results:
{
  "Output": "net/wireless/nl80211.c=19713=static u32 nl80211_internal_flags[] = {\n--\nnet/wireless/nl80211.c-19718-\nnet/wireless/nl80211.c:19719:static int nl80211_pre_doit(const struct genl_split_ops *ops,\nnet/wireless/nl80211.c-19720-\t\t\t    struct sk_buff *skb,\n--\nnet/wireless/nl80211.c=20925=static struct genl_family nl80211_fam __ro_after_init = {\n--\nnet/wireless/nl80211.c-20931-\t.netnsok = true,\nnet/wireless/nl80211.c:20932:\t.pre_doit = nl80211_pre_doit,\nnet/wireless/nl80211.c-20933-\t.post_doit = nl80211_post_doit,\n"
}

29/2 2026/10/06 10:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 46657
							output: 46
							thoughts: 103
						

Thoughts:
Here's my thought process, as if I were analyzing this code:

**Analyzing `nl80211` Module Callbacks**

Okay, so `nl80211_pre_doit` is a callback function, and the way it's being referenced, with the address-taken syntax, `.pre_doit = nl80211_pre_doit`, makes it a distinct symbol within the `nl80211.o` object file. That's good to know, confirms it's not some inline thing. I need to be aware of this distinction when considering how it's linked and potentially patched.

Now, what about `nl80211_prepare_wdev_dump`? I'm wondering if it's used in a similar manner, maybe as a `.start = nl80211_prepare_wdev_dump` or something along those lines? It's essential I verify its usage to understand the overall structure and how the module's functionality is organized. Gotta check where *that* is referenced next. Gotta find that .start reference, or equivalent.



30/2 2026/10/06 10:53 tool grepper
0m Args:
{
  "Expression": "nl80211_prepare_wdev_dump",
  "PathPrefixes": [
    "net/wireless/nl80211.c"
  ]
}

Results:
{
  "Output": "net/wireless/nl80211.c=1295=nl80211_packet_pattern_policy[MAX_NL80211_PKTPAT + 1] = {\n--\nnet/wireless/nl80211.c-1300-\nnet/wireless/nl80211.c:1301:static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,\nnet/wireless/nl80211.c-1302-\t\t\t\t     struct cfg80211_registered_device **rdev,\n--\nnet/wireless/nl80211.c=8606=static int nl80211_dump_station(struct sk_buff *skb,\n--\nnet/wireless/nl80211.c-8620-\nnet/wireless/nl80211.c:8621:\terr = nl80211_prepare_wdev_dump(cb, \u0026rdev, \u0026wdev, attrbuf);\nnet/wireless/nl80211.c-8622-\tif (err)\nnet/wireless/nl80211.c-8623-\t\treturn err;\nnet/wireless/nl80211.c:8624:\t/* nl80211_prepare_wdev_dump acquired it in the successful case */\nnet/wireless/nl80211.c-8625-\t__acquire(\u0026rdev-\u003ewiphy.mtx);\n--\nnet/wireless/nl80211.c=9937=static int nl80211_dump_mpath(struct sk_buff *skb,\n--\nnet/wireless/nl80211.c-9947-\nnet/wireless/nl80211.c:9948:\terr = nl80211_prepare_wdev_dump(cb, \u0026rdev, \u0026wdev, NULL);\nnet/wireless/nl80211.c-9949-\tif (err)\nnet/wireless/nl80211.c-9950-\t\treturn err;\nnet/wireless/nl80211.c:9951:\t/* nl80211_prepare_wdev_dump acquired it in the successful case */\nnet/wireless/nl80211.c-9952-\t__acquire(\u0026rdev-\u003ewiphy.mtx);\n--\nnet/wireless/nl80211.c=10137=static int nl80211_dump_mpp(struct sk_buff *skb,\n--\nnet/wireless/nl80211.c-10147-\nnet/wireless/nl80211.c:10148:\terr = nl80211_prepare_wdev_dump(cb, \u0026rdev, \u0026wdev, NULL);\nnet/wireless/nl80211.c-10149-\tif (err)\nnet/wireless/nl80211.c-10150-\t\treturn err;\nnet/wireless/nl80211.c:10151:\t/* nl80211_prepare_wdev_dump acquired it in the successful case */\nnet/wireless/nl80211.c-10152-\t__acquire(\u0026rdev-\u003ewiphy.mtx);\n--\nnet/wireless/nl80211.c=12695=static int nl80211_dump_scan(struct sk_buff *skb, struct netlink_callback *cb)\n--\nnet/wireless/nl80211.c-12708-\nnet/wireless/nl80211.c:12709:\terr = nl80211_prepare_wdev_dump(cb, \u0026rdev, \u0026wdev, attrbuf);\nnet/wireless/nl80211.c-12710-\tif (err) {\n--\nnet/wireless/nl80211.c-12713-\t}\nnet/wireless/nl80211.c:12714:\t/* nl80211_prepare_wdev_dump acquired it in the successful case */\nnet/wireless/nl80211.c-12715-\t__acquire(\u0026rdev-\u003ewiphy.mtx);\n--\nnet/wireless/nl80211.c=12835=static int nl80211_dump_survey(struct sk_buff *skb, struct netlink_callback *cb)\n--\nnet/wireless/nl80211.c-12848-\nnet/wireless/nl80211.c:12849:\tres = nl80211_prepare_wdev_dump(cb, \u0026rdev, \u0026wdev, attrbuf);\nnet/wireless/nl80211.c-12850-\tif (res) {\n--\nnet/wireless/nl80211.c-12853-\t}\nnet/wireless/nl80211.c:12854:\t/* nl80211_prepare_wdev_dump acquired it in the successful case */\nnet/wireless/nl80211.c-12855-\t__acquire(\u0026rdev-\u003ewiphy.mtx);\n"
}

31/2 2026/10/06 10:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 48413
							output: 44
							thoughts: 577
						

Thoughts:
**Analyzing the Changes in the Wireless Subsystem**

Alright, let's get down to brass tacks. I need to get a handle on the scope of these modifications, so I'm going to start by reviewing the exported and non-static functions that have been altered or added. It looks like things have been touched in several key areas within the wireless subsystem.

First, within `net/wireless/nl80211.c`, I've got: `nl80211_pre_doit`, `nl80211_lock_and_recheck`, `nl80211_prepare_wdev_dump`, `nl80211_set_reg`, `nl80211_get_reg_dump`, `__cfg80211_wdev_from_attrs`, and `__cfg80211_rdev_from_attrs`. These modifications are likely related to regulatory domain handling and netlink interface interaction, particularly around device registration and information retrieval. I'll need to scrutinize these functions to see how these might impact our current design and understand if any of the locks are handled correctly.

Next, within `net/wireless/reg.c`, we see a significant number of changes, and this is where it gets interesting. I've got: `wiphy_apply_custom_regulatory`, `regulatory_set_wiphy_regd_sync`, `get_wiphy_regdom`, `reg_reload_regdb`, `reg_query_regdb_wmm`, `regulatory_hint_disconnect`, `wiphy_regulatory_register`, `wiphy_regulatory_deregister`, `regulatory_propagate_dfs_state`, `wiphy_update_regulatory`, `update_all_wiphy_regulatory`, `reg_todo`, and `reg_check_chans_work`. These are all crucial pieces related to regulatory domain management, DFS handling (likely important), and potentially interactions with the regulatory database. The exported symbols here are particularly relevant because these are APIs we might be calling and we need to understand the implications of their modifications.

In `net/wireless/mlme.c`, there's `cfg80211_dfs_channels_update_work`, `cfg80211_dfs_chan_expire`, and `cfg80211_sched_dfs_chan_update`. These are almost certainly related to Dynamic Frequency Selection and channel management, so these require close attention.

Then, moving into `net/wireless/core.c`, I see a couple of functions I'm familiar with, specifically: `wiphy_register`, `wiphy_unregister`, `cfg80211_propagate_radar_detect_wk`, `cfg80211_propagate_cac_done_wk`, `cfg80211_rdev_by_wiphy_idx`, `wiphy_idx_to_wiphy`, and `cfg80211_netdev_notifier_call`. This section is really the heart of the cfg80211 interface, and given the nature of the changes, I'll need to understand how these updates affect the core registration and event propagation within the wireless stack.

Finally, there's `hwsim_background_cac_write` in `drivers/net/wireless/virtual/mac80211_hwsim_main.c`. This suggests changes potentially in the simulation/testing environment related to Channel Availability Check. Might not be critical for now unless it impacts our tests.

Just to be thorough, I'm going to check `nl80211_post_doit` in `net/wireless/nl80211.c` to see how the lock is being unlocked. It's easy to overlook these details, so best to make sure the lock isn't being improperly held or released.



32/2 2026/10/06 10:53 tool grepper
0m Args:
{
  "Expression": "nl80211_post_doit",
  "PathPrefixes": [
    "net/wireless/nl80211.c"
  ]
}

Results:
{
  "Output": "net/wireless/nl80211.c=5290=static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)\n--\nnet/wireless/nl80211.c-5310-\t * If we remove a wireless device without a netdev then clear\nnet/wireless/nl80211.c:5311:\t * user_ptr[1] so that nl80211_post_doit won't dereference it\nnet/wireless/nl80211.c-5312-\t * to check if it needs to do dev_put(). Otherwise it crashes\n--\nnet/wireless/nl80211.c=19719=static int nl80211_pre_doit(const struct genl_split_ops *ops,\n--\nnet/wireless/nl80211.c-19871-\nnet/wireless/nl80211.c:19872:static void nl80211_post_doit(const struct genl_split_ops *ops,\nnet/wireless/nl80211.c-19873-\t\t\t      struct sk_buff *skb,\n--\nnet/wireless/nl80211.c=20925=static struct genl_family nl80211_fam __ro_after_init = {\n--\nnet/wireless/nl80211.c-20932-\t.pre_doit = nl80211_pre_doit,\nnet/wireless/nl80211.c:20933:\t.post_doit = nl80211_post_doit,\nnet/wireless/nl80211.c-20934-\t.module = THIS_MODULE,\n"
}

33/2 2026/10/06 10:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 50350
							output: 42
							thoughts: 17
						

Thoughts:
Okay, here's my interpretation of what's going on, formatted as you requested:

**Deconstructing `nl80211_post_doit`**

Alright, let's dive into this `nl80211_post_doit` function. I'm immediately thinking, given my familiarity with the Netlink infrastructure and wireless configuration, that this is likely where the *aftermath* of a Netlink command related to 802.11 is handled. Specifically, I bet this is where we transition from the core Netlink handling to the actions that actually *change* the state of the wireless device, or propagate the results back to userspace.

My intuition is screaming "cleanup, configuration, and notification" here. I suspect this function receives the result of a Netlink command execution and orchestrates the actions that follow, given the particular command. Think of it like the "finalizer" or the "post-processing pipeline" for an 802.11 configuration change.

I'm mentally mapping the possible scenarios: it could be updating regulatory information, setting a new channel, adjusting power levels, or handling an association/disassociation event. It's likely involved with updating the internal wireless device state and notifying any listening processes or user space clients about that state change. I bet it has some form of dispatch, potentially a large switch statement, to handle those different scenarios.

It has to also deal with error handling. Did the Netlink command succeed? If not, how are we going to propagate that back to the calling user process? What logging is in place? I'm anticipating some thorough error checking and propagation of the error codes, probably via Netlink error messages or signals.



34/2 2026/10/06 10:53 tool read-file
0m Args:
{
  "File": "net/wireless/nl80211.c",
  "FirstLine": 19870,
  "LineCount": 35
}

Results:
{
  "Contents": "19870:\t}\n19871:\t\n19872:\tstatic void nl80211_post_doit(const struct genl_split_ops *ops,\n19873:\t\t\t\t      struct sk_buff *skb,\n19874:\t\t\t\t      struct genl_info *info)\n19875:\t{\n19876:\t\tu32 internal_flags = nl80211_internal_flags[ops-\u003einternal_flags];\n19877:\t\n19878:\t\tif (info-\u003euser_ptr[1]) {\n19879:\t\t\tif (internal_flags \u0026 NL80211_FLAG_NEED_WDEV) {\n19880:\t\t\t\tstruct wireless_dev *wdev = info-\u003euser_ptr[1];\n19881:\t\n19882:\t\t\t\tdev_put(wdev-\u003enetdev);\n19883:\t\t\t} else {\n19884:\t\t\t\tdev_put(info-\u003euser_ptr[1]);\n19885:\t\t\t}\n19886:\t\t}\n19887:\t\n19888:\t\tif (info-\u003euser_ptr[0] \u0026\u0026\n19889:\t\t    !(internal_flags \u0026 NL80211_FLAG_NO_WIPHY_MTX)) {\n19890:\t\t\tstruct cfg80211_registered_device *rdev = info-\u003euser_ptr[0];\n19891:\t\n19892:\t\t\t/* we kept the mutex locked since pre_doit */\n19893:\t\t\t__acquire(\u0026rdev-\u003ewiphy.mtx);\n19894:\t\t\twiphy_unlock(\u0026rdev-\u003ewiphy);\n19895:\t\t}\n19896:\t\n19897:\t\tif (internal_flags \u0026 NL80211_FLAG_NEED_RTNL)\n19898:\t\t\trtnl_unlock();\n19899:\t\n19900:\t\t/* If needed, clear the netlink message payload from the SKB\n19901:\t\t * as it might contain key data that shouldn't stick around on\n19902:\t\t * the heap after the SKB is freed. The netlink message header\n19903:\t\t * is still needed for further processing, so leave it intact.\n19904:\t\t */\n"
}

35/2 2026/10/06 10:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 51385
							output: 39
							thoughts: 1322
						

Thoughts:
**Analyzing Recent Changes in the nl80211 Subsystem**

Okay, so I've been digging into some recent updates within the nl80211 subsystem, and here's what I've found. The primary area of focus seems to be around locking and unlocking mechanisms, particularly regarding the `rtnl_lock` and `wiphy_lock` primitives. In `nl80211_pre_doit`, the code has seen a significant overhaul. The core change revolves around how these locks are handled based on the `NL80211_FLAG_NEED_RTNL` flag. Essentially, if `NEED_RTNL` is set, `rtnl_lock` is called, otherwise, `rcu_read_lock` is used. Furthermore, there's a conditional check for `NL80211_FLAG_CHECK_NETDEV_UP`, which seems to involve locking and unlocking `rtnl` again under certain network device states. The code also calls `nl80211_lock_and_recheck` if `need_rtnl` is not set, which calls `wiphy_lock`.

`nl80211_post_doit` correspondingly unlocks the wiphy and RTNL.

The `nl80211_lock_and_recheck` function is interesting because it re-validates the `wiphy` state after the initial RCU lookup, using `wiphy_lock` for protection. This is a crucial addition to prevent race conditions. It checks for wiphy registration and net namespace.

In addition, changes in `nl80211_prepare_wdev_dump` show the replacement of holding `rtnl_lock` across the dump callback setup with RCU lookup, `get_device`, and `nl80211_lock_and_recheck`. This implies a move towards a more fine-grained locking strategy and potentially better concurrency.

I also see modifications in `net/wireless/mlme.c`, specifically in `cfg80211_dfs_channels_update_work` and `cfg80211_dfs_chan_expire`, including the allocation of dynamic memory and locking around channel expiration, as well as modifications in `net/wireless/reg.c` with the replacement of `RTNL` assertions by `lockdep_assert_held` and `lockdep_assert_wiphy` within the regulatory code paths such as `reg_todo`, `update_all_wiphy_regulatory`, `restore_regulatory_settings`, `wiphy_regulatory_register`, and `wiphy_regulatory_deregister`, indicating a focus on improved debugging and locking verification.

Finally, I need to check the required configurations. I'm focusing on `WIRELESS`, `CFG80211`, and `MAC80211` which are critical, but `NL80211_TESTMODE` and `CFG80211_CRDA_SUPPORT` are also relevant. It's also worth noting the `MAC80211_HWSIM` driver, which is a simulated radio device and is likely very relevant in a virtual testing environment. It depends on `MAC80211` and ultimately `CFG80211` and `WIRELESS`.



36/2 2026/10/06 10:54 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "MAC80211_HWSIM",
  "PathPrefixes": [
    "drivers/net/wireless/Kconfig"
  ]
}

Results:
{
  "error": "no matches"
}

37/2 2026/10/06 10:54 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 53479
							output: 30
							thoughts: 30
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:

**Investigating Kconfig Inclusion in the Wireless Network Driver Subsystem**

Alright, I'm diving in to understand how the virtual wireless network driver Kconfig file (`drivers/net/wireless/virtual/Kconfig`) is integrated into the larger wireless network driver configuration. My initial hypothesis is that it's sourced from within the main `drivers/net/wireless/Kconfig` file, but I need to confirm this and potentially understand any conditional logic involved in the inclusion. Knowing this allows me to evaluate the impact of changing it or the code it relates to. This is standard operating procedure when I'm reviewing or debugging any change in the Kconfig system. I'm assuming that this is a simple "source" directive, but I need to be sure. I'll need to double check the actual contents of these files for any complex logic or dependencies. Ultimately, I am searching for the key to unlock the configuration options of this driver.



38/2 2026/10/06 10:54 tool grepper
0m Args:
{
  "Expression": "virtual",
  "PathPrefixes": [
    "drivers/net/wireless/Kconfig"
  ]
}

Results:
{
  "Output": "drivers/net/wireless/Kconfig=40=source \"drivers/net/wireless/quantenna/Kconfig\"\ndrivers/net/wireless/Kconfig-41-\ndrivers/net/wireless/Kconfig:42:source \"drivers/net/wireless/virtual/Kconfig\"\ndrivers/net/wireless/Kconfig-43-\n"
}

39/2 2026/10/06 10:54 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 53848
							output: 34
							thoughts: 20
						

Thoughts:
Here's a summary of my thought process, tailored for an expert like you:

**Diving into the Wireless Kernel Configuration**

Okay, so I need to zero in on a specific area within the wireless networking kernel configuration. The usual suspects come to mind, and I'm going to start with `drivers/net/wireless/Kconfig`. It's the central configuration file for most wireless drivers, so it's the logical place to begin. My internal mental map suggests that line 42 might be a key area of interest, perhaps related to a specific feature or configuration option I have in mind. I'm not entirely sure *why* line 42 right now, but it is as good a place to start as any to investigate. With my knowledge, this is a reasonable starting point. Let's see what's there and what clues it offers.



40/2 2026/10/06 10:54 tool read-file
0m Args:
{
  "File": "drivers/net/wireless/Kconfig",
  "FirstLine": 1,
  "LineCount": 45
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\t#\n   3:\t# Wireless LAN device configuration\n   4:\t#\n   5:\t\n   6:\tmenuconfig WLAN\n   7:\t\tbool \"Wireless LAN\"\n   8:\t\tdepends on !S390\n   9:\t\tdepends on NET\n  10:\t\tselect WIRELESS\n  11:\t\tdefault y\n  12:\t\thelp\n  13:\t\t  This section contains all the pre 802.11 and 802.11 wireless\n  14:\t\t  device drivers. For a complete list of drivers and documentation\n  15:\t\t  on them refer to the wireless wiki:\n  16:\t\n  17:\t\t  https://wireless.wiki.kernel.org/en/users/Drivers\n  18:\t\n  19:\tif WLAN\n  20:\t\n  21:\tsource \"drivers/net/wireless/admtek/Kconfig\"\n  22:\tsource \"drivers/net/wireless/ath/Kconfig\"\n  23:\tsource \"drivers/net/wireless/atmel/Kconfig\"\n  24:\tsource \"drivers/net/wireless/broadcom/Kconfig\"\n  25:\tsource \"drivers/net/wireless/intel/Kconfig\"\n  26:\tsource \"drivers/net/wireless/intersil/Kconfig\"\n  27:\tsource \"drivers/net/wireless/marvell/Kconfig\"\n  28:\tsource \"drivers/net/wireless/mediatek/Kconfig\"\n  29:\tsource \"drivers/net/wireless/microchip/Kconfig\"\n  30:\tsource \"drivers/net/wireless/morsemicro/Kconfig\"\n  31:\tsource \"drivers/net/wireless/nxp/Kconfig\"\n  32:\tsource \"drivers/net/wireless/purelifi/Kconfig\"\n  33:\tsource \"drivers/net/wireless/ralink/Kconfig\"\n  34:\tsource \"drivers/net/wireless/realtek/Kconfig\"\n  35:\tsource \"drivers/net/wireless/rsi/Kconfig\"\n  36:\tsource \"drivers/net/wireless/silabs/Kconfig\"\n  37:\tsource \"drivers/net/wireless/st/Kconfig\"\n  38:\tsource \"drivers/net/wireless/ti/Kconfig\"\n  39:\tsource \"drivers/net/wireless/zydas/Kconfig\"\n  40:\tsource \"drivers/net/wireless/quantenna/Kconfig\"\n  41:\t\n  42:\tsource \"drivers/net/wireless/virtual/Kconfig\"\n  43:\t\n  44:\tendif # WLAN\n"
}

41/2 2026/10/06 10:54 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 54753
							output: 44
							thoughts: 1090
						

Thoughts:
**Focusing Fuzzing on Wireless Core Logic**

Okay, so `menuconfig WLAN` selects `WIRELESS`, which in turn pulls in the `WLAN`, `CFG80211`, `MAC80211`, `MAC80211_HWSIM`, and `CFG80211_CRDA_SUPPORT` configurations. I need to figure out the best approach here. My initial thought is to identify the crucial areas for fuzzing, given these dependencies.

I see that some driver updates are present (`ath`, `rtlwifi`, `rtw89`), but they mostly involve trivial locking changes (`guard(rcu)();` or `wiphy_lock(wiphy);`). The really interesting stuff seems to be concentrated in `net/wireless/` specifically (`nl80211.c`, `core.c`, `reg.c`, `mlme.c`) and `mac80211_hwsim_main.c`. These are the core wireless management, configuration, and simulation areas that I need to focus on.

Now, let's nail down some `FocusSymbols`. I need to select key, non-ubiquitous kernel functions that are directly tied to the changed logic.

Here's my initial list:
*   `nl80211_pre_doit`: This is the nl80211 callback dispatch, and should exercise all the new locking and rechecking that occurs for every nl80211 command.
*   `nl80211_lock_and_recheck`: This is the function introduced to lock and revalidate things.
*   `nl80211_prepare_wdev_dump`: Heavily modified wdev dump preparation using RCU and our new lock-and-recheck code.
*   `nl80211_set_reg`: This was switched from `rtnl_lock` to `cfg80211_mutex`
*   `cfg80211_dfs_channels_update_work`: Rewritten DFS channel update work.
*   `cfg80211_dfs_chan_expire`: New DFS channel expiration helper under `wiphy` guard.
*   `update_all_wiphy_regulatory`: Switched to `cfg80211_mutex` and per-`wiphy` guard.
*   `reg_todo`: Work handler for regulatory tasks, switched from `rtnl_lock` to `cfg80211_mutex`
*   `restore_regulatory_settings`: Now uses `cfg80211_mutex` and per-`wiphy` locking.
*   `regulatory_propagate_dfs_state`: Switched to `cfg80211_mutex`, per-`wiphy` locking.
*   `hwsim_background_cac_write`: Added a guard(wiphy)

I'll also keep these in mind:
*   `wiphy_regulatory_register`
*   `wiphy_regulatory_deregister`
*   `reg_reload_regdb`

Ideally, I want a focused set of around 5-10 specific functions. Now, a quick review:
Are all of these FocusSymbols non-ubiquitous? Yes, they're all directly within `cfg80211`, `nl80211`, or `mac80211_hwsim`. They are not hot paths used everywhere like `kmalloc` or page faults.

Now, regarding the `EnableConfigs`. We already have `CFG80211`, `MAC80211`, `MAC80211_HWSIM`, and `CFG80211_CRDA_SUPPORT`. Do I need anything else? I'm wondering about `CFG80211_DEBUGFS`. Let's check. Ah, right, `mac80211_hwsim_new_radio` creates `simulate_incumbent_signal_interference` and `hwsim_background_cac_ops` inside `data->debugfs`. So that's important for the hwsim part. I need to confirm what config controls debugfs for the `mac80211_hwsim` specifically, and it looks like `CFG80211_DEBUGFS` should cover it.



42/2 2026/10/06 10:55 tool grepper
0m Args:
{
  "Expression": "debugfs",
  "PathPrefixes": [
    "drivers/net/wireless/virtual/mac80211_hwsim_main.c"
  ]
}

Results:
{
  "Output": "drivers/net/wireless/virtual/mac80211_hwsim_main.c-27-#include \u003clinux/platform_device.h\u003e\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:28:#include \u003clinux/debugfs.h\u003e\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-29-#include \u003clinux/module.h\u003e\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=2373=static void\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:2374:mac80211_hwsim_link_add_debugfs(struct ieee80211_hw *hw,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-2375-\t\t\t\tstruct ieee80211_vif *vif,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-2380-\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:2381:\tdebugfs_create_u32(\"skip_beacons\", 0600, dir,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-2382-\t\t\t   \u0026vp-\u003eskip_beacons[link_conf-\u003elink_id]);\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=4284=static int mac80211_hwsim_set_radar_background(struct ieee80211_hw *hw,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-4303-#define HWSIM_DEBUGFS_OPS\t\t\t\t\t\\\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:4304:\t.link_add_debugfs = mac80211_hwsim_link_add_debugfs,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-4305-#else\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=5720=static int mac80211_hwsim_new_radio(struct genl_info *info,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6200-\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6201:\tdata-\u003edebugfs = debugfs_create_dir(\"hwsim\", hw-\u003ewiphy-\u003edebugfsdir);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6202:\tdebugfs_create_file(\"ps\", 0666, data-\u003edebugfs, data, \u0026hwsim_fops_ps);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6203:\tdebugfs_create_file(\"group\", 0666, data-\u003edebugfs, data,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6204-\t\t\t    \u0026hwsim_fops_group);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6205:\tdebugfs_create_file(\"rx_rssi\", 0666, data-\u003edebugfs, data,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6206-\t\t\t    \u0026hwsim_fops_rx_rssi);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6207-\tif (!data-\u003euse_chanctx)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6208:\t\tdebugfs_create_file(\"dfs_simulate_radar\", 0222,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6209:\t\t\t\t    data-\u003edebugfs,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6210-\t\t\t\t    data, \u0026hwsim_simulate_radar);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6211-\tif (param-\u003ebackground_radar)\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6212:\t\tdebugfs_create_file(\"dfs_background_cac\", 0200,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6213:\t\t\t\t    data-\u003edebugfs,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6214-\t\t\t\t    data, \u0026hwsim_background_cac_ops);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6215:\tdebugfs_create_file(\"simulate_incumbent_signal_interference\", 0200,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6216:\t\t\t    data-\u003edebugfs,\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6217-\t\t\t    data, \u0026hwsim_simulate_incumbent_signal_fops);\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6245-failed_final_insert:\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6246:\tdebugfs_remove_recursive(data-\u003edebugfs);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6247-\tieee80211_unregister_hw(data-\u003ehw);\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=6293=static void mac80211_hwsim_del_radio(struct mac80211_hwsim_data *data,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6297-\thwsim_mcast_del_radio(data-\u003eidx, hwname, wiphy_net(data-\u003ehw-\u003ewiphy), info);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:6298:\tdebugfs_remove_recursive(data-\u003edebugfs);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-6299-\tieee80211_unregister_hw(data-\u003ehw);\n"
}

43/2 2026/10/06 10:55 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 58577
							output: 315
							thoughts: 495
						

Thoughts:
**Fuzzing Decision for the Patch Series**

Okay, so I need to decide if this patch series warrants fuzzing. The first thing that comes to mind is the potential presence of debugfs entries, specifically `hw->wiphy->debugfsdir`. It seems this directory is created if both `CFG80211_DEBUGFS` and `DEBUG_FS` are enabled. I'll quickly confirm that `CFG80211_DEBUGFS` depends on `CFG80211` and `DEBUG_FS` and defaults to "y", which looks correct, as checked in `net/wireless/Kconfig`. That's good to know.

Now, let's review the parameters for `set-results`. I need to set `WorthFuzzing` to `true`, then provide a concise `Reasoning`, a `FocusSymbols` list with non-ubiquitous functions, and the `EnableConfigs` as kernel configs without the `CONFIG_` prefix.

Let me make sure I'm in line with the fuzzing goals: finding crashes, regressions, and such introduced by the patch. Crucially, the code needs to be reachable in standard virtualized environments. No blindly trusting "no functional change" or "refactoring" claims here. This means *any* new or modified assertions in reachable code *must* be fuzzed. And this patch touches executable code, including refactorings, assertions, and drivers, so `WorthFuzzing` is definitely `true`.

I need to extract specific kernel functions into `FocusSymbols`, avoiding ubiquitous hot-paths and targeting specific feature logic and entrypoints, especially when dealing with static inline functions. Furthermore, I need to list the necessary `CONFIG_` options for compilation. Alright, I think I have a clear understanding of everything and am ready to call `set-results`.



44/2 2026/10/06 10:55 tool set-results
0m Args:
{
  "EnableConfigs": [
    "CFG80211",
    "MAC80211",
    "MAC80211_HWSIM",
    "CFG80211_CRDA_SUPPORT",
    "CFG80211_DEBUGFS"
  ],
  "FocusSymbols": [
    "nl80211_pre_doit",
    "nl80211_lock_and_recheck",
    "nl80211_prepare_wdev_dump",
    "nl80211_set_reg",
    "cfg80211_dfs_channels_update_work",
    "cfg80211_dfs_chan_expire",
    "regulatory_propagate_dfs_state",
    "hwsim_background_cac_write"
  ],
  "Reasoning": "The patch series introduces major locking and synchronization changes across the wireless core (cfg80211, nl80211, mac80211_hwsim), replacing RTNL lock coverage with a new cfg80211_mutex, RCU lookups, wiphy mutex guards, and lockdep assertions. It also modifies asynchronous DFS channel expiration and dump preparation. These reachable paths are heavily fuzzed via Netlink and virtual devices, making fuzzing critical to catch concurrency bugs, state races, and lockdep splats.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "CFG80211",
    "MAC80211",
    "MAC80211_HWSIM",
    "CFG80211_CRDA_SUPPORT",
    "CFG80211_DEBUGFS"
  ],
  "FocusSymbols": [
    "nl80211_pre_doit",
    "nl80211_lock_and_recheck",
    "nl80211_prepare_wdev_dump",
    "nl80211_set_reg",
    "cfg80211_dfs_channels_update_work",
    "cfg80211_dfs_chan_expire",
    "regulatory_propagate_dfs_state",
    "hwsim_background_cac_write"
  ],
  "Reasoning": "The patch series introduces major locking and synchronization changes across the wireless core (cfg80211, nl80211, mac80211_hwsim), replacing RTNL lock coverage with a new cfg80211_mutex, RCU lookups, wiphy mutex guards, and lockdep assertions. It also modifies asynchronous DFS channel expiration and dump preparation. These reachable paths are heavily fuzzed via Netlink and virtual devices, making fuzzing critical to catch concurrency bugs, state races, and lockdep splats.",
  "WorthFuzzing": true
}

45/1 2026/10/06 10:55 action If
1m Args:
{
  "WorthFuzzing": true
}

Results:
null

46/2 2026/10/06 10:55 agent kmsan-evaluator
1m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch series refactors cfg80211/nl80211 and driver regulatory code to reduce RTNL dependency by introducing a dedicated cfg80211_mutex, utilizing wiphy mutex locking, and employing RCU read-side critical sections.\n\nThese modifications are strictly related to locking, synchronization, and object lifetime management. Any potential regressions introduced by these changes would be race conditions, use-after-free bugs, lock-order inversions, or missing lock assertions, which are detected by KASAN and LOCKDEP.\n\nThe patch does not introduce or modify any uninitialized memory reads, uninitialized buffer copies to user space, or complex uninitialized struct allocations (the newly introduced dynamic buffer allocation in cfg80211_dfs_channels_update_work uses kvcalloc, zero-initializing all entries). Therefore, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 487d4a302c3c079c0ba08d2a624c2282c46c0376
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Oct 6 10:49:10 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/net/wireless/ath/ath11k/reg.c b/drivers/net/wireless/ath/ath11k/reg.c
index 0879a132b67cf..1030e119fd903 100644
--- a/drivers/net/wireless/ath/ath11k/reg.c
+++ b/drivers/net/wireless/ath/ath11k/reg.c
@@ -33,7 +33,9 @@ static bool ath11k_regdom_changes(struct ath11k *ar, char *alpha2)
 {
 	const struct ieee80211_regdomain *regd;
 
-	regd = rcu_dereference_rtnl(ar->hw->wiphy->regd);
+	guard(rcu)();
+
+	regd = get_wiphy_regdom(ar->hw->wiphy);
 	/* This can happen during wiphy registration where the previous
 	 * user request is received before we update the regd received
 	 * from firmware.
diff --git a/drivers/net/wireless/ath/ath12k/reg.c b/drivers/net/wireless/ath/ath12k/reg.c
index 89abf2e87ad10..7cf3dbc4fc8a9 100644
--- a/drivers/net/wireless/ath/ath12k/reg.c
+++ b/drivers/net/wireless/ath/ath12k/reg.c
@@ -33,7 +33,9 @@ static bool ath12k_regdom_changes(struct ieee80211_hw *hw, char *alpha2)
 {
 	const struct ieee80211_regdomain *regd;
 
-	regd = rcu_dereference_rtnl(hw->wiphy->regd);
+	guard(rcu)();
+
+	regd = get_wiphy_regdom(hw->wiphy);
 	/* This can happen during wiphy registration where the previous
 	 * user request is received before we update the regd received
 	 * from firmware.
@@ -122,9 +124,7 @@ ath12k_reg_notifier(struct wiphy *wiphy, struct regulatory_request *request)
 					    "failed set INIT Country code: %d\n", ret);
 		}
 
-		wiphy_lock(wiphy);
 		ath12k_mac_11d_scan_stop(ar);
-		wiphy_unlock(wiphy);
 
 		ar->regdom_set_by_user = true;
 	}
diff --git a/drivers/net/wireless/ath/regd.c b/drivers/net/wireless/ath/regd.c
index f15e7bd690b5b..cedb5e43b9e4f 100644
--- a/drivers/net/wireless/ath/regd.c
+++ b/drivers/net/wireless/ath/regd.c
@@ -268,6 +268,8 @@ static void ath_force_clear_no_ir_chan(struct wiphy *wiphy,
 {
 	const struct ieee80211_reg_rule *reg_rule;
 
+	guard(rcu)();
+
 	reg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(ch->center_freq));
 	if (IS_ERR(reg_rule))
 		return;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c
index cdfe8635c012e..5ca8a07353982 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/channel.c
@@ -676,6 +676,8 @@ brcms_reg_apply_beaconing_flags(struct wiphy *wiphy,
 				continue;
 
 			if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) {
+				guard(rcu)();
+
 				rule = freq_reg_info(wiphy,
 						     MHZ_TO_KHZ(ch->center_freq));
 				if (IS_ERR(rule))
diff --git a/drivers/net/wireless/realtek/rtlwifi/regd.c b/drivers/net/wireless/realtek/rtlwifi/regd.c
index fd967006b3e1e..503625b50fc38 100644
--- a/drivers/net/wireless/realtek/rtlwifi/regd.c
+++ b/drivers/net/wireless/realtek/rtlwifi/regd.c
@@ -150,6 +150,8 @@ static void _rtl_reg_apply_beaconing_flags(struct wiphy *wiphy,
 			    (ch->flags & IEEE80211_CHAN_RADAR))
 				continue;
 			if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) {
+				guard(rcu)();
+
 				reg_rule = freq_reg_info(wiphy,
 							 ch->center_freq);
 				if (IS_ERR(reg_rule))
@@ -212,6 +214,8 @@ static void _rtl_reg_apply_active_scan_flags(struct wiphy *wiphy,
 	 *custom regulatory domain.
 	 */
 
+	guard(rcu)();
+
 	ch = &sband->channels[11];	/* CH 12 */
 	reg_rule = freq_reg_info(wiphy, ch->center_freq);
 	if (!IS_ERR(reg_rule)) {
diff --git a/drivers/net/wireless/realtek/rtw89/regd.c b/drivers/net/wireless/realtek/rtw89/regd.c
index b9a5ce218b1da..983b42e193b52 100644
--- a/drivers/net/wireless/realtek/rtw89/regd.c
+++ b/drivers/net/wireless/realtek/rtw89/regd.c
@@ -898,7 +898,6 @@ void rtw89_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request
 	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
 	struct rtw89_dev *rtwdev = hw->priv;
 
-	wiphy_lock(wiphy);
 	rtw89_leave_ps_mode(rtwdev);
 
 	if (rtwdev->regulatory.programmed)
@@ -916,8 +915,6 @@ void rtw89_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request
 	rtw89_regd_apply_policy_ant_gain(rtwdev);
 
 	rtw89_core_set_chip_txpwr(rtwdev);
-
-	wiphy_unlock(wiphy);
 }
 
 /* Maximum Transmit Power field (@raw) can be EIRP or PSD.
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 6cd9542e8b722..b0fd6bdd3cccc 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1133,6 +1133,8 @@ static ssize_t hwsim_background_cac_write(struct file *file,
 	if (copy_from_user(buf, user_buf, count))
 		return -EFAULT;
 
+	guard(wiphy)(data->hw->wiphy);
+
 	/* Check if background radar channel is configured */
 	if (!data->radar_background_chandef.chan)
 		return -ENOENT;
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 05c625d9e0766..378fa52bfbab6 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -6362,7 +6362,8 @@ struct wiphy_nan_capa {
  * @mtx: mutex for the data (structures) of this device
  * @reg_notifier: the driver's regulatory notification callback,
  *	note that if your driver uses wiphy_apply_custom_regulatory()
- *	the reg_notifier's request can be passed as NULL
+ *	the reg_notifier's request can be passed as NULL.
+ *	This is called under wiphy mutex.
  * @regd: the driver's regulatory domain, if one was requested via
  *	the regulatory_hint() API. This can be used by the driver
  *	on the reg_notifier() if it chooses to ignore future
@@ -7242,8 +7243,9 @@ enum ieee80211_ap_reg_power {
  * @mgmt_registrations_need_update: mgmt registrations were updated,
  *	need to propagate the update to the driver
  * @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.
+ * @is_running: true if the device has been started, e.g. the P2P Device;
+ *	for netdevs, tracked under the wiphy mutex from NETDEV_UP until
+ *	NETDEV_GOING_DOWN
  * @ps: powersave mode is enabled
  * @ps_timeout: dynamic powersave timeout
  * @unexpected_nlportid: (private) netlink port ID of application
@@ -7407,8 +7409,6 @@ static inline const u8 *wdev_address(struct wireless_dev *wdev)
 
 static inline bool wdev_running(struct wireless_dev *wdev)
 {
-	if (wdev->netdev)
-		return netif_running(wdev->netdev);
 	return wdev->is_running;
 }
 
@@ -8161,9 +8161,9 @@ int regulatory_set_wiphy_regd(struct wiphy *wiphy,
  * @wiphy: the wireless device we want to process the regulatory domain on
  * @rd: the regulatory domain information to use for this wiphy
  *
- * This functions requires the RTNL and the wiphy mutex to be held and
- * applies the new regdomain synchronously to this wiphy. For more details
- * see regulatory_set_wiphy_regd().
+ * This functions requires the wiphy mutex to be held and applies the new
+ * regdomain synchronously to this wiphy. For more details see
+ * regulatory_set_wiphy_regd().
  *
  * Return: 0 on success. -EINVAL, -EPERM
  */
@@ -8198,6 +8198,8 @@ void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
  * it wants to follow we respect that unless a country IE has been received
  * and processed already.
  *
+ * Must be called within an RCU read-side critical section.
+ *
  * Return: A valid pointer, or, when an error occurs, for example if no rule
  * can be found, the return value is encoded using ERR_PTR(). Use IS_ERR() to
  * check and PTR_ERR() to obtain the numeric return value. The numeric return
@@ -8236,20 +8238,20 @@ bool regulatory_pre_cac_allowed(struct wiphy *wiphy);
  */
 
 /**
- * reg_query_regdb_wmm -  Query internal regulatory db for wmm rule
- * Regulatory self-managed driver can use it to proactively
- *
+ * reg_query_regdb_wmm -  Query internal regulatory db for WMM rules
  * @alpha2: the ISO/IEC 3166 alpha2 wmm rule to be queried.
  * @freq: the frequency (in MHz) to be queried.
- * @rule: pointer to store the wmm rule from the regulatory db.
+ * @rule: pointer to store the WMM rule from the regulatory db.
  *
- * Self-managed wireless drivers can use this function to  query
+ * Self-managed wireless drivers can use this function to query
  * the internal regulatory database to check whether the given
- * ISO/IEC 3166 alpha2 country and freq have wmm rule limitations.
+ * ISO/IEC 3166 alpha2 country and freq have WMM rule limitations.
  *
  * Drivers should check the return value, its possible you can get
  * an -ENODATA.
  *
+ * This uses RCU internally so can be called in just about any context.
+ *
  * Return: 0 on success. -ENODATA.
  */
 int reg_query_regdb_wmm(char *alpha2, int freq,
@@ -9809,6 +9811,7 @@ void cfg80211_cqm_beacon_loss_notify(struct net_device *dev, gfp_t gfp);
  * @gfp: context flags
  *
  * This function is called when a radar is detected on the current chanenl.
+ * Must be called with the wiphy mutex held.
  */
 void __cfg80211_radar_event(struct wiphy *wiphy,
 			    struct cfg80211_chan_def *chandef,
@@ -9854,7 +9857,7 @@ void cfg80211_sta_opmode_change_notify(struct net_device *dev, const u8 *mac,
  *
  * 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,
- * also by full-MAC drivers.
+ * also by full-MAC drivers. Must be called with the wiphy mutex held.
  */
 void cfg80211_cac_event(struct net_device *netdev,
 			const struct cfg80211_chan_def *chandef,
diff --git a/net/wireless/chan.c b/net/wireless/chan.c
index c743b6fb7e309..9183cd19dbdc4 100644
--- a/net/wireless/chan.c
+++ b/net/wireless/chan.c
@@ -1243,7 +1243,7 @@ bool cfg80211_any_wiphy_oper_chan(struct wiphy *wiphy,
 {
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	if (!(chan->flags & IEEE80211_CHAN_RADAR))
 		return false;
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 45a1709cffe7a..01b077909179d 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -42,7 +42,9 @@ MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("wireless configuration support");
 MODULE_ALIAS_GENL_FAMILY(NL80211_GENL_NAME);
 
-/* RCU-protected (and RTNL for writers) */
+DEFINE_MUTEX(cfg80211_mutex);
+
+/* RCU-protected (writes under both RTNL/cfg80211_mutex) */
 LIST_HEAD(cfg80211_rdev_list);
 int cfg80211_rdev_list_generation;
 
@@ -59,18 +61,14 @@ MODULE_PARM_DESC(cfg80211_disable_40mhz_24ghz,
 
 struct cfg80211_registered_device *cfg80211_rdev_by_wiphy_idx(int wiphy_idx)
 {
-	struct cfg80211_registered_device *result = NULL, *rdev;
-
-	ASSERT_RTNL();
+	struct cfg80211_registered_device *rdev;
 
 	for_each_rdev(rdev) {
-		if (rdev->wiphy_idx == wiphy_idx) {
-			result = rdev;
-			break;
-		}
+		if (rdev->wiphy_idx == wiphy_idx)
+			return rdev;
 	}
 
-	return result;
+	return NULL;
 }
 
 int get_wiphy_idx(struct wiphy *wiphy)
@@ -84,8 +82,6 @@ struct wiphy *wiphy_idx_to_wiphy(int wiphy_idx)
 {
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
-
 	rdev = cfg80211_rdev_by_wiphy_idx(wiphy_idx);
 	if (!rdev)
 		return NULL;
@@ -564,13 +560,11 @@ static void cfg80211_propagate_radar_detect_wk(struct work_struct *work)
 	rdev = container_of(work, struct cfg80211_registered_device,
 			    propagate_radar_detect_wk);
 
-	rtnl_lock();
+	guard(mutex)(&cfg80211_mutex);
 
 	regulatory_propagate_dfs_state(&rdev->wiphy, &rdev->radar_chandef,
 				       NL80211_DFS_UNAVAILABLE,
 				       NL80211_RADAR_DETECTED);
-
-	rtnl_unlock();
 }
 
 static void cfg80211_propagate_cac_done_wk(struct work_struct *work)
@@ -580,13 +574,11 @@ static void cfg80211_propagate_cac_done_wk(struct work_struct *work)
 	rdev = container_of(work, struct cfg80211_registered_device,
 			    propagate_cac_done_wk);
 
-	rtnl_lock();
+	guard(mutex)(&cfg80211_mutex);
 
 	regulatory_propagate_dfs_state(&rdev->wiphy, &rdev->cac_done_chandef,
 				       NL80211_DFS_AVAILABLE,
 				       NL80211_RADAR_CAC_FINISHED);
-
-	rtnl_unlock();
 }
 
 static void cfg80211_wiphy_work(struct work_struct *work)
@@ -1243,10 +1235,12 @@ int wiphy_register(struct wiphy *wiphy)
 		rdev->wiphy.bss_param_support |= WIPHY_BSS_PARAM_P2P_OPPPS;
 
 	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
 	wiphy_lock(&rdev->wiphy);
 	res = device_add(&rdev->wiphy.dev);
 	if (res) {
 		wiphy_unlock(&rdev->wiphy);
+		mutex_unlock(&cfg80211_mutex);
 		rtnl_unlock();
 		return res;
 	}
@@ -1271,6 +1265,9 @@ int wiphy_register(struct wiphy *wiphy)
 	}
 
 	cfg80211_debugfs_rdev_add(rdev);
+
+	rdev->wiphy.registered = true;
+
 	nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY);
 	wiphy_unlock(&rdev->wiphy);
 
@@ -1317,8 +1314,7 @@ int wiphy_register(struct wiphy *wiphy)
 				break;
 		}
 	}
-
-	rdev->wiphy.registered = true;
+	mutex_unlock(&cfg80211_mutex);
 	rtnl_unlock();
 
 	res = rfkill_register(rdev->wiphy.rfkill);
@@ -1390,6 +1386,7 @@ void wiphy_unregister(struct wiphy *wiphy)
 		rfkill_unregister(rdev->wiphy.rfkill);
 
 	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
 	wiphy_lock(&rdev->wiphy);
 	nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY);
 	rdev->wiphy.registered = false;
@@ -1421,6 +1418,7 @@ void wiphy_unregister(struct wiphy *wiphy)
 	/* surely nothing is reachable now, clean up work */
 	cfg80211_process_wiphy_works(rdev, NULL);
 	wiphy_unlock(&rdev->wiphy);
+	mutex_unlock(&cfg80211_mutex);
 	rtnl_unlock();
 
 	/* this has nothing to do now but make sure it's gone */
@@ -1836,6 +1834,8 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
 		}
 		break;
 	case NETDEV_GOING_DOWN:
+		scoped_guard(wiphy, &rdev->wiphy)
+			wdev->is_running = false;
 		cfg80211_leave(rdev, wdev, -1);
 		scoped_guard(wiphy, &rdev->wiphy) {
 			cfg80211_remove_links(wdev);
@@ -1866,6 +1866,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
 		break;
 	case NETDEV_UP:
 		wiphy_lock(&rdev->wiphy);
+		wdev->is_running = true;
 		cfg80211_update_iface_num(rdev, wdev->iftype, 1);
 		switch (wdev->iftype) {
 #ifdef CONFIG_CFG80211_WEXT
diff --git a/net/wireless/core.h b/net/wireless/core.h
index 58445c09026a2..dfcb9ed5035b3 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -19,6 +19,9 @@
 #include "reg.h"
 
 
+/* protects the wiphy list and regulatory state */
+extern struct mutex cfg80211_mutex;
+
 #define WIPHY_IDX_INVALID	-1
 
 struct cfg80211_scan_request_int {
@@ -179,15 +182,10 @@ extern struct workqueue_struct *cfg80211_wq;
 extern struct list_head cfg80211_rdev_list;
 extern int cfg80211_rdev_list_generation;
 
-/* This is constructed like this so it can be used in if/else */
-static inline int for_each_rdev_check_rtnl(void)
-{
-	ASSERT_RTNL();
-	return 0;
-}
 #define for_each_rdev(rdev)						\
-	if (for_each_rdev_check_rtnl()) {} else				\
-		list_for_each_entry(rdev, &cfg80211_rdev_list, list)
+	list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list,	\
+				lockdep_rtnl_is_held() ||		\
+				lockdep_is_held(&cfg80211_mutex))
 
 enum bss_source_type {
 	BSS_SOURCE_DIRECT = 0,
diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index a0d1cde26f0c9..96fd14f2a305d 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -1070,25 +1070,75 @@ void cfg80211_sched_dfs_chan_update(struct cfg80211_registered_device *rdev)
 	queue_delayed_work(cfg80211_wq, &rdev->dfs_update_channels_wk, 0);
 }
 
+struct cfg80211_dfs_chan_expiry {
+	struct ieee80211_channel *chan;
+	unsigned long entered;
+	enum nl80211_dfs_state state;
+	enum nl80211_radar_event event;
+};
+
+static bool cfg80211_dfs_chan_expire(struct cfg80211_registered_device *rdev,
+				     struct cfg80211_dfs_chan_expiry *exp,
+				     struct cfg80211_chan_def *chandef)
+{
+	struct ieee80211_channel *c = exp->chan;
+
+	guard(wiphy)(&rdev->wiphy);
+
+	/* the state may have changed while the wiphy wasn't locked */
+	if (c->dfs_state != exp->state || c->dfs_state_entered != exp->entered)
+		return false;
+
+	c->dfs_state = NL80211_DFS_USABLE;
+	c->dfs_state_entered = jiffies;
+
+	cfg80211_chandef_create(chandef, c, NL80211_CHAN_NO_HT);
+
+	nl80211_radar_notify(rdev, chandef, exp->event, NULL, GFP_KERNEL);
+
+	return true;
+}
+
 void cfg80211_dfs_channels_update_work(struct work_struct *work)
 {
 	struct delayed_work *delayed_work = to_delayed_work(work);
+	struct cfg80211_dfs_chan_expiry *expired;
 	struct cfg80211_registered_device *rdev;
 	struct cfg80211_chan_def chandef;
 	struct ieee80211_supported_band *sband;
 	struct ieee80211_channel *c;
 	struct wiphy *wiphy;
-	bool check_again = false;
+	bool check_again = false, pre_cac_allowed;
 	unsigned long timeout, next_time = 0;
 	unsigned long time_dfs_update;
 	enum nl80211_radar_event radar_event;
+	unsigned int n_chans = 0, n_expired = 0;
 	int bandid, i;
 
 	rdev = container_of(delayed_work, struct cfg80211_registered_device,
 			    dfs_update_channels_wk);
 	wiphy = &rdev->wiphy;
 
-	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
+	wiphy_lock(wiphy);
+	for (bandid = 0; bandid < NUM_NL80211_BANDS; bandid++) {
+		if (wiphy->bands[bandid])
+			n_chans += wiphy->bands[bandid]->n_channels;
+	}
+
+	expired = kvcalloc(n_chans, sizeof(*expired), GFP_KERNEL);
+	if (!expired) {
+		/* hmm - retry later */
+		queue_delayed_work(cfg80211_wq,
+				   &rdev->dfs_update_channels_wk,
+				   HZ);
+		wiphy_unlock(wiphy);
+		mutex_unlock(&cfg80211_mutex);
+		return;
+	}
+
+	pre_cac_allowed = regulatory_pre_cac_allowed(wiphy);
+
 	for (bandid = 0; bandid < NUM_NL80211_BANDS; bandid++) {
 		sband = wiphy->bands[bandid];
 		if (!sband)
@@ -1108,8 +1158,7 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)
 				time_dfs_update = IEEE80211_DFS_MIN_NOP_TIME_MS;
 				radar_event = NL80211_RADAR_NOP_FINISHED;
 			} else {
-				if (regulatory_pre_cac_allowed(wiphy) ||
-				    cfg80211_any_wiphy_oper_chan(wiphy, c))
+				if (pre_cac_allowed)
 					continue;
 
 				time_dfs_update = REG_PRE_CAC_EXPIRY_GRACE_MS;
@@ -1120,19 +1169,11 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)
 				  msecs_to_jiffies(time_dfs_update);
 
 			if (time_after_eq(jiffies, timeout)) {
-				c->dfs_state = NL80211_DFS_USABLE;
-				c->dfs_state_entered = jiffies;
-
-				cfg80211_chandef_create(&chandef, c,
-							NL80211_CHAN_NO_HT);
-
-				nl80211_radar_notify(rdev, &chandef,
-						     radar_event, NULL,
-						     GFP_ATOMIC);
-
-				regulatory_propagate_dfs_state(wiphy, &chandef,
-							       c->dfs_state,
-							       radar_event);
+				expired[n_expired].chan = c;
+				expired[n_expired].state = c->dfs_state;
+				expired[n_expired].entered = c->dfs_state_entered;
+				expired[n_expired].event = radar_event;
+				n_expired++;
 				continue;
 			}
 
@@ -1143,7 +1184,24 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work)
 			check_again = true;
 		}
 	}
-	rtnl_unlock();
+	wiphy_unlock(wiphy);
+
+	/* these lock the wiphys, so must be done without holding ours */
+	for (i = 0; i < n_expired; i++) {
+		if (expired[i].event == NL80211_RADAR_PRE_CAC_EXPIRED &&
+		    cfg80211_any_wiphy_oper_chan(wiphy, expired[i].chan))
+			continue;
+
+		if (!cfg80211_dfs_chan_expire(rdev, &expired[i], &chandef))
+			continue;
+
+		regulatory_propagate_dfs_state(wiphy, &chandef,
+					       NL80211_DFS_USABLE,
+					       expired[i].event);
+	}
+	mutex_unlock(&cfg80211_mutex);
+
+	kvfree(expired);
 
 	/* reschedule if there are other channels waiting to be cleared again */
 	if (check_again)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 7ee69ec10b0f9..48f5584d7c2bc 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -63,7 +63,7 @@ static const struct genl_multicast_group nl80211_mcgrps[] = {
 #endif
 };
 
-/* returns ERR_PTR values */
+/* returns ERR_PTR values, requires RCU/cfg80211_mutex if rdev is %NULL */
 static struct wireless_dev *
 __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,
 			   struct net *netns, struct nlattr **attrs)
@@ -105,8 +105,6 @@ __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,
 		return result ?: ERR_PTR(-ENODEV);
 	}
 
-	ASSERT_RTNL();
-
 	for_each_rdev(rdev) {
 		struct wireless_dev *wdev;
 
@@ -116,7 +114,8 @@ __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,
 		if (have_wdev_id && rdev->wiphy_idx != wiphy_idx)
 			continue;
 
-		list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+		list_for_each_entry_rcu(wdev, &rdev->wiphy.wdev_list, list,
+					lockdep_rtnl_is_held()) {
 			if (have_ifidx && wdev->netdev &&
 			    wdev->netdev->ifindex == ifidx) {
 				result = wdev;
@@ -137,14 +136,13 @@ __cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,
 	return ERR_PTR(-ENODEV);
 }
 
+/* requires RCU/cfg80211_mutex */
 static struct cfg80211_registered_device *
 __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)
 {
 	struct cfg80211_registered_device *rdev = NULL, *tmp;
 	struct net_device *netdev;
 
-	ASSERT_RTNL();
-
 	if (!attrs[NL80211_ATTR_WIPHY] &&
 	    !attrs[NL80211_ATTR_IFINDEX] &&
 	    !attrs[NL80211_ATTR_WDEV])
@@ -162,7 +160,8 @@ __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)
 		tmp = cfg80211_rdev_by_wiphy_idx(wdev_id >> 32);
 		if (tmp) {
 			/* make sure wdev exists */
-			list_for_each_entry(wdev, &tmp->wiphy.wdev_list, list) {
+			list_for_each_entry_rcu(wdev, &tmp->wiphy.wdev_list,
+						list, lockdep_rtnl_is_held()) {
 				if (wdev->identifier != (u32)wdev_id)
 					continue;
 				found = true;
@@ -181,14 +180,16 @@ __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)
 	if (attrs[NL80211_ATTR_IFINDEX]) {
 		int ifindex = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]);
 
-		netdev = __dev_get_by_index(netns, ifindex);
-		if (netdev) {
-			if (netdev->ieee80211_ptr)
-				tmp = wiphy_to_rdev(
-					netdev->ieee80211_ptr->wiphy);
-			else
-				tmp = NULL;
+		/* RCU required for dev_get_by_index_rcu() if we have RTNL */
+		rcu_read_lock();
+		netdev = dev_get_by_index_rcu(netns, ifindex);
+		if (netdev && netdev->ieee80211_ptr)
+			tmp = wiphy_to_rdev(netdev->ieee80211_ptr->wiphy);
+		else
+			tmp = NULL;
+		rcu_read_unlock();
 
+		if (netdev) {
 			/* not wireless device -- return error */
 			if (!tmp)
 				return ERR_PTR(-EINVAL);
@@ -223,6 +224,48 @@ cfg80211_get_dev_from_info(struct net *netns, struct genl_info *info)
 	return __cfg80211_rdev_from_attrs(netns, info->attrs);
 }
 
+/*
+ * Lock the given wiphy, put the device and double-check it's
+ * still valid to use.
+ *
+ * Returns with wiphy mutex held unless there was an error.
+ */
+static struct wireless_dev *
+nl80211_lock_and_recheck(struct cfg80211_registered_device *rdev,
+			 struct net *netns, u32 wdev_id)
+{
+	struct wireless_dev *wdev;
+
+	wiphy_lock(&rdev->wiphy);
+
+	if (!rdev->wiphy.registered ||
+	    !net_eq(wiphy_net(&rdev->wiphy), netns)) {
+		wiphy_unlock(&rdev->wiphy);
+		put_device(&rdev->wiphy.dev);
+		return ERR_PTR(-ENODEV);
+	}
+
+	/*
+	 * unregistration blocks on wiphy mutex,
+	 * so we don't need this now
+	 */
+	put_device(&rdev->wiphy.dev);
+
+	if (!wdev_id)
+		return NULL;
+
+	list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+		if (wdev->identifier != wdev_id)
+			continue;
+		if (wdev->netdev && !net_eq(dev_net(wdev->netdev), netns))
+			break;
+		return wdev;
+	}
+	wiphy_unlock(&rdev->wiphy);
+
+	return ERR_PTR(-ENODEV);
+}
+
 static int validate_beacon_head(const struct nlattr *attr,
 				struct netlink_ext_ack *extack)
 {
@@ -1260,6 +1303,8 @@ static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,
 				     struct wireless_dev **wdev,
 				     struct nlattr **attrbuf)
 {
+	struct net *netns = sock_net(cb->skb->sk);
+	u32 wdev_id;
 	int err;
 
 	if (!cb->args[0]) {
@@ -1281,60 +1326,37 @@ static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,
 			return err;
 		}
 
-		rtnl_lock();
-		*wdev = __cfg80211_wdev_from_attrs(NULL, sock_net(cb->skb->sk),
-						   attrbuf);
+		rcu_read_lock();
+		*wdev = __cfg80211_wdev_from_attrs(NULL, netns, attrbuf);
 		kfree(attrbuf_free);
 		if (IS_ERR(*wdev)) {
-			rtnl_unlock();
+			rcu_read_unlock();
 			return PTR_ERR(*wdev);
 		}
 		*rdev = wiphy_to_rdev((*wdev)->wiphy);
-		mutex_lock(&(*rdev)->wiphy.mtx);
-		rtnl_unlock();
-		/* 0 is the first index - add 1 to parse only once */
-		cb->args[0] = (*rdev)->wiphy_idx + 1;
-		cb->args[1] = (*wdev)->identifier;
+		wdev_id = (*wdev)->identifier;
 	} else {
 		/* subtract the 1 again here */
-		struct wiphy *wiphy;
-		struct wireless_dev *tmp;
-
-		rtnl_lock();
-		wiphy = wiphy_idx_to_wiphy(cb->args[0] - 1);
-		if (!wiphy) {
-			rtnl_unlock();
-			return -ENODEV;
-		}
-
-		/*
-		 * The first invocation validated the wdev's netns against
-		 * the caller via __cfg80211_wdev_from_attrs(). The wiphy
-		 * may have moved netns between dumpit invocations (via
-		 * NL80211_CMD_SET_WIPHY_NETNS), so re-check here.
-		 */
-		if (!net_eq(wiphy_net(wiphy), sock_net(cb->skb->sk))) {
-			rtnl_unlock();
+		rcu_read_lock();
+		*rdev = cfg80211_rdev_by_wiphy_idx(cb->args[0] - 1);
+		if (!*rdev) {
+			rcu_read_unlock();
 			return -ENODEV;
 		}
+		wdev_id = cb->args[1];
+	}
 
-		*rdev = wiphy_to_rdev(wiphy);
-		*wdev = NULL;
+	get_device(&(*rdev)->wiphy.dev);
+	rcu_read_unlock();
 
-		list_for_each_entry(tmp, &(*rdev)->wiphy.wdev_list, list) {
-			if (tmp->identifier == cb->args[1]) {
-				*wdev = tmp;
-				break;
-			}
-		}
+	/* things may have changed since the lookup or the last dumpit call */
+	*wdev = nl80211_lock_and_recheck(*rdev, netns, wdev_id);
+	if (IS_ERR(*wdev))
+		return PTR_ERR(*wdev);
 
-		if (!*wdev) {
-			rtnl_unlock();
-			return -ENODEV;
-		}
-		mutex_lock(&(*rdev)->wiphy.mtx);
-		rtnl_unlock();
-	}
+	/* 0 is the first index - add 1 to parse only once */
+	cb->args[0] = (*rdev)->wiphy_idx + 1;
+	cb->args[1] = wdev_id;
 
 	return 0;
 }
@@ -1518,9 +1540,10 @@ static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy,
 		goto nla_put_failure;
 
 	if (large) {
-		const struct ieee80211_reg_rule *rule =
-			freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq));
+		const struct ieee80211_reg_rule *rule;
 
+		guard(rcu)();
+		rule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq));
 		if (!IS_ERR_OR_NULL(rule) && rule->has_wmm) {
 			if (nl80211_msg_put_wmm_rules(msg, rule))
 				goto nla_put_failure;
@@ -10924,7 +10947,7 @@ static int nl80211_get_reg_dump(struct sk_buff *skb,
 
 	/* the global regdom is idx 0 */
 	reg_idx = 1;
-	list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) {
+	for_each_rdev(rdev) {
 		regdom = get_wiphy_regdom(&rdev->wiphy);
 		if (!regdom)
 			continue;
@@ -11026,7 +11049,7 @@ static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
 			return -EINVAL;
 	}
 
-	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
 	if (!reg_is_valid_request(alpha2)) {
 		r = -EINVAL;
 		goto out;
@@ -11074,7 +11097,7 @@ static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
  bad_reg:
 	kfree(rd);
  out:
-	rtnl_unlock();
+	mutex_unlock(&cfg80211_mutex);
 	return r;
 }
 #endif /* CONFIG_CFG80211_CRDA_SUPPORT */
@@ -19669,6 +19692,18 @@ nl80211_epcs_cfg(struct sk_buff *skb, struct genl_info *info)
 		 NL80211_FLAG_NO_WIPHY_MTX |		\
 		 NL80211_FLAG_NEED_RTNL)
 
+/*
+ * Note: a
+ *	SELECTOR(...,
+ *		 NL80211_FLAG_NO_WIPHY_MTX |
+ *		 NL80211_FLAG_NEED_WDEV)
+ * or
+ *	SELECTOR(..., NL80211_FLAG_NO_WIPHY_MTX)
+ *
+ * isn't valid - nothing would ensure the device stays around,
+ * need at least a netdev, the wiphy mutex, or RTNL.
+ */
+
 enum nl80211_internal_flags_selector {
 #define SELECTOR(_, name, value)	NL80211_IFL_SEL_##name,
 	INTERNAL_FLAG_SELECTORS(_)
@@ -19686,41 +19721,88 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,
 			    struct genl_info *info)
 {
 	struct cfg80211_registered_device *rdev = NULL;
+	struct net *netns = genl_info_net(info);
 	struct wireless_dev *wdev = NULL;
 	struct net_device *dev = NULL;
-	u32 internal_flags;
+	bool need_rtnl, locked = false;
+	u32 internal_flags, wdev_id = 0;
 	int err;
 
 	if (WARN_ON(ops->internal_flags >= ARRAY_SIZE(nl80211_internal_flags)))
 		return -EINVAL;
 
 	internal_flags = nl80211_internal_flags[ops->internal_flags];
+	need_rtnl = internal_flags & NL80211_FLAG_NEED_RTNL;
+
+	if (need_rtnl)
+		rtnl_lock();
+	else
+		rcu_read_lock();
 
-	rtnl_lock();
 	if (internal_flags & NL80211_FLAG_NEED_WIPHY) {
-		rdev = cfg80211_get_dev_from_info(genl_info_net(info), info);
+		rdev = cfg80211_get_dev_from_info(netns, info);
 		if (IS_ERR(rdev)) {
 			err = PTR_ERR(rdev);
-			goto out_unlock;
+			goto out_lookup;
 		}
-		info->user_ptr[0] = rdev;
 	} else if (internal_flags & NL80211_FLAG_NEED_NETDEV ||
 		   internal_flags & NL80211_FLAG_NEED_WDEV) {
-		wdev = __cfg80211_wdev_from_attrs(NULL, genl_info_net(info),
-						  info->attrs);
+		wdev = __cfg80211_wdev_from_attrs(NULL, netns, info->attrs);
 		if (IS_ERR(wdev)) {
 			err = PTR_ERR(wdev);
-			goto out_unlock;
+			goto out_lookup;
 		}
 
 		dev = wdev->netdev;
 		dev_hold(dev);
 		rdev = wiphy_to_rdev(wdev->wiphy);
+	}
+
+	if (!need_rtnl) {
+		if (rdev)
+			get_device(&rdev->wiphy.dev);
+		if (wdev)
+			wdev_id = wdev->identifier;
+		rcu_read_unlock();
+
+		/* RTM_NEWLINK is sent before NETDEV_UP, wait for dev_open() */
+		if (internal_flags & NL80211_FLAG_CHECK_NETDEV_UP && dev &&
+		    !READ_ONCE(wdev->is_running) && netif_running(dev)) {
+			rtnl_lock();
+			rtnl_unlock();
+		}
+	}
+
+	if (rdev && !(internal_flags & NL80211_FLAG_NO_WIPHY_MTX)) {
+		if (need_rtnl) {
+			wiphy_lock(&rdev->wiphy);
+		} else {
+			/* without rtnl, things may have changed since lookup */
+			wdev = nl80211_lock_and_recheck(rdev, netns, wdev_id);
+			if (IS_ERR(wdev)) {
+				err = PTR_ERR(wdev);
+				goto out;
+			}
+		}
+		locked = true;
+	} else if (rdev && !need_rtnl) {
+		/*
+		 * Without any mutex need a netdev to keep it alive, see
+		 * also the note about selectors - the WARN can't happen
+		 * without wrong selectors and those don't exist.
+		 */
+		put_device(&rdev->wiphy.dev);
+		if (WARN_ON(!dev)) {
+			err = -EINVAL;
+			goto out;
+		}
+	}
 
+	if (wdev) {
 		if (internal_flags & NL80211_FLAG_NEED_NETDEV) {
 			if (!dev) {
 				err = -EINVAL;
-				goto out_unlock;
+				goto out;
 			}
 
 			info->user_ptr[1] = dev;
@@ -19731,10 +19813,8 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,
 		if (internal_flags & NL80211_FLAG_CHECK_NETDEV_UP &&
 		    !wdev_running(wdev)) {
 			err = -ENETDOWN;
-			goto out_unlock;
+			goto out;
 		}
-
-		info->user_ptr[0] = rdev;
 	}
 
 	if (internal_flags & NL80211_FLAG_MLO_VALID_LINK_ID) {
@@ -19742,7 +19822,7 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,
 
 		if (!wdev) {
 			err = -EINVAL;
-			goto out_unlock;
+			goto out;
 		}
 
 		/* MLO -> require valid link ID */
@@ -19750,13 +19830,13 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,
 		    (!link_id ||
 		     !(wdev->valid_links & BIT(nla_get_u8(link_id))))) {
 			err = -EINVAL;
-			goto out_unlock;
+			goto out;
 		}
 
 		/* non-MLO -> no link ID attribute accepted */
 		if (!wdev->valid_links && link_id) {
 			err = -EINVAL;
-			goto out_unlock;
+			goto out;
 		}
 	}
 
@@ -19764,22 +19844,28 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,
 		if (info->attrs[NL80211_ATTR_MLO_LINK_ID] ||
 		    (wdev && wdev->valid_links)) {
 			err = -EINVAL;
-			goto out_unlock;
+			goto out;
 		}
 	}
 
-	if (rdev && !(internal_flags & NL80211_FLAG_NO_WIPHY_MTX)) {
-		wiphy_lock(&rdev->wiphy);
-		/* we keep the mutex locked until post_doit */
+	info->user_ptr[0] = rdev;
+	/* we keep the mutex locked until post_doit */
+	if (locked)
 		__release(&rdev->wiphy.mtx);
-	}
-	if (!(internal_flags & NL80211_FLAG_NEED_RTNL))
-		rtnl_unlock();
 
 	return 0;
-out_unlock:
-	rtnl_unlock();
+out:
+	if (locked)
+		wiphy_unlock(&rdev->wiphy);
 	dev_put(dev);
+	if (need_rtnl)
+		rtnl_unlock();
+	return err;
+out_lookup:
+	if (need_rtnl)
+		rtnl_unlock();
+	else
+		rcu_read_unlock();
 	return err;
 }
 
@@ -21115,7 +21201,10 @@ static bool nl80211_reg_change_event_fill(struct sk_buff *msg,
 	}
 
 	if (request->wiphy_idx != WIPHY_IDX_INVALID) {
-		struct wiphy *wiphy = wiphy_idx_to_wiphy(request->wiphy_idx);
+		struct wiphy *wiphy;
+
+		guard(rcu)();
+		wiphy = wiphy_idx_to_wiphy(request->wiphy_idx);
 
 		if (wiphy &&
 		    nla_put_u32(msg, NL80211_ATTR_WIPHY, request->wiphy_idx))
@@ -23357,7 +23446,7 @@ static int nl80211_netlink_notify(struct notifier_block * nb,
 
 	rcu_read_lock();
 
-	list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) {
+	for_each_rdev(rdev) {
 		struct cfg80211_sched_scan_request *sched_scan_req;
 
 		list_for_each_entry_rcu(sched_scan_req,
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index b7ef479a74f01..4efb35baeb3e1 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -88,7 +88,7 @@ static struct regulatory_request core_request_world = {
 
 /*
  * Receipt of information from last regulatory request,
- * protected by RTNL (and can be accessed with RCU protection)
+ * protected by cfg80211_mutex (and can be accessed with RCU protection)
  */
 static struct regulatory_request __rcu *last_request =
 	(void __force __rcu *)&core_request_world;
@@ -100,14 +100,14 @@ static struct faux_device *reg_fdev;
  * Central wireless core regulatory domains, we only need two,
  * the current one and a world regulatory domain in case we have no
  * information to give us an alpha2.
- * (protected by RTNL, can be read under RCU)
+ * (protected by cfg80211_mutex, can be read under RCU)
  */
 const struct ieee80211_regdomain __rcu *cfg80211_regdomain;
 
 /*
  * Number of devices that registered to the core
  * that support cellular base station regulatory hints
- * (protected by RTNL)
+ * (protected by cfg80211_mutex)
  */
 static int reg_num_devs_support_basehint;
 
@@ -128,19 +128,19 @@ static void reg_process_hint(struct regulatory_request *reg_request);
 
 static const struct ieee80211_regdomain *get_cfg80211_regdom(void)
 {
-	return rcu_dereference_rtnl(cfg80211_regdomain);
+	return rcu_dereference_check(cfg80211_regdomain,
+				     lockdep_is_held(&cfg80211_mutex));
 }
 
 /*
  * Returns the regulatory domain associated with the wiphy.
  *
- * Requires any of RTNL, wiphy mutex or RCU protection.
+ * Requires the wiphy mutex or RCU protection.
  */
 const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy)
 {
 	return rcu_dereference_check(wiphy->regd,
-				     lockdep_is_held(&wiphy->mtx) ||
-				     lockdep_rtnl_is_held());
+				     lockdep_is_held(&wiphy->mtx));
 }
 EXPORT_SYMBOL(get_wiphy_regdom);
 
@@ -204,7 +204,8 @@ static void rcu_free_regdom(const struct ieee80211_regdomain *r)
 
 static struct regulatory_request *get_last_request(void)
 {
-	return rcu_dereference_rtnl(last_request);
+	return rcu_dereference_check(last_request,
+				     lockdep_is_held(&cfg80211_mutex));
 }
 
 /* Used to queue up regulatory hints */
@@ -314,7 +315,7 @@ static void reset_regdomains(bool full_reset,
 {
 	const struct ieee80211_regdomain *r;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	r = get_cfg80211_regdom();
 
@@ -456,7 +457,7 @@ reg_copy_regd(const struct ieee80211_regdomain *src_regd)
 
 static void cfg80211_save_user_regdom(const struct ieee80211_regdomain *rd)
 {
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	if (!IS_ERR(cfg80211_user_regdom))
 		kfree(cfg80211_user_regdom);
@@ -475,7 +476,7 @@ static void reg_regdb_apply(struct work_struct *work)
 {
 	struct reg_regdb_apply_request *request;
 
-	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
 
 	mutex_lock(&reg_regdb_apply_mutex);
 	while (!list_empty(&reg_regdb_apply_list)) {
@@ -489,7 +490,7 @@ static void reg_regdb_apply(struct work_struct *work)
 	}
 	mutex_unlock(&reg_regdb_apply_mutex);
 
-	rtnl_unlock();
+	mutex_unlock(&cfg80211_mutex);
 }
 
 static DECLARE_WORK(reg_regdb_work, reg_regdb_apply);
@@ -526,10 +527,10 @@ static DECLARE_DELAYED_WORK(crda_timeout, crda_timeout_work);
 static void crda_timeout_work(struct work_struct *work)
 {
 	pr_debug("Timeout while waiting for CRDA to reply, restoring regulatory settings\n");
-	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
 	reg_crda_timeouts++;
 	restore_regulatory_settings(true, false);
-	rtnl_unlock();
+	mutex_unlock(&cfg80211_mutex);
 }
 
 static void cancel_crda_timeout(void)
@@ -590,7 +591,7 @@ static inline int call_crda(const char *alpha2)
 #endif /* CONFIG_CFG80211_CRDA_SUPPORT */
 
 /* code to directly load a firmware database through request_firmware */
-static const struct fwdb_header *regdb;
+static const struct fwdb_header __rcu *global_regdb;
 
 struct fwdb_country {
 	u8 alpha2[2];
@@ -892,16 +893,19 @@ static int __regdb_query_wmm(const struct fwdb_header *db,
 
 int reg_query_regdb_wmm(char *alpha2, int freq, struct ieee80211_reg_rule *rule)
 {
-	const struct fwdb_header *hdr = regdb;
 	const struct fwdb_country *country;
+	const struct fwdb_header *regdb;
+
+	guard(rcu)();
 
+	regdb = rcu_dereference(global_regdb);
 	if (!regdb)
 		return -ENODATA;
 
 	if (IS_ERR(regdb))
 		return PTR_ERR(regdb);
 
-	country = &hdr->country[0];
+	country = &regdb->country[0];
 	while (country->coll_ptr) {
 		if (alpha2_equal(alpha2, country->alpha2))
 			return __regdb_query_wmm(regdb, country, freq, rule);
@@ -970,15 +974,16 @@ static int regdb_query_country(const struct fwdb_header *db,
 
 static int query_regdb(const char *alpha2)
 {
-	const struct fwdb_header *hdr = regdb;
 	const struct fwdb_country *country;
+	const struct fwdb_header *regdb;
 
-	ASSERT_RTNL();
+	regdb = rcu_dereference_protected(global_regdb,
+					  lockdep_is_held(&cfg80211_mutex));
 
 	if (IS_ERR(regdb))
 		return PTR_ERR(regdb);
 
-	country = &hdr->country[0];
+	country = &regdb->country[0];
 	while (country->coll_ptr) {
 		if (alpha2_equal(alpha2, country->alpha2))
 			return regdb_query_country(regdb, country);
@@ -990,6 +995,7 @@ static int query_regdb(const char *alpha2)
 
 static void regdb_fw_cb(const struct firmware *fw, void *context)
 {
+	const struct fwdb_header *regdb;
 	int set_error = 0;
 	bool restore = true;
 	void *db;
@@ -1002,7 +1008,9 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)
 		set_error = -EINVAL;
 	}
 
-	rtnl_lock();
+	mutex_lock(&cfg80211_mutex);
+	regdb = rcu_dereference_protected(global_regdb,
+					  lockdep_is_held(&cfg80211_mutex));
 	if (regdb && !IS_ERR(regdb)) {
 		/* negative case - a bug
 		 * positive case - can happen due to race in case of multiple cb's in
@@ -1011,11 +1019,11 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)
 		 * Either case, just restore and free new db.
 		 */
 	} else if (set_error) {
-		regdb = ERR_PTR(set_error);
+		rcu_assign_pointer(global_regdb, ERR_PTR(set_error));
 	} else if (fw) {
 		db = kmemdup(fw->data, fw->size, GFP_KERNEL);
 		if (db) {
-			regdb = db;
+			rcu_assign_pointer(global_regdb, db);
 			restore = context && query_regdb(context);
 		} else {
 			restore = true;
@@ -1025,7 +1033,7 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)
 	if (restore)
 		restore_regulatory_settings(true, false);
 
-	rtnl_unlock();
+	mutex_unlock(&cfg80211_mutex);
 
 	kfree(context);
 
@@ -1038,9 +1046,9 @@ static int query_regdb_file(const char *alpha2)
 {
 	int err;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
-	if (regdb)
+	if (rcu_access_pointer(global_regdb))
 		return query_regdb(alpha2);
 
 	alpha2 = kmemdup(alpha2, 2, GFP_KERNEL);
@@ -1058,8 +1066,8 @@ static int query_regdb_file(const char *alpha2)
 
 int reg_reload_regdb(void)
 {
+	const struct fwdb_header *db, *old;
 	const struct firmware *fw;
-	void *db;
 	int err;
 	const struct ieee80211_regdomain *current_regdomain;
 	struct regulatory_request *request;
@@ -1079,10 +1087,9 @@ int reg_reload_regdb(void)
 		goto out;
 	}
 
-	rtnl_lock();
-	if (!IS_ERR_OR_NULL(regdb))
-		kfree(regdb);
-	regdb = db;
+	mutex_lock(&cfg80211_mutex);
+	old = rcu_replace_pointer(global_regdb, db,
+				  lockdep_is_held(&cfg80211_mutex));
 
 	/* reset regulatory domain */
 	current_regdomain = get_cfg80211_regdom();
@@ -1102,7 +1109,12 @@ int reg_reload_regdb(void)
 	reg_process_hint(request);
 
 out_unlock:
-	rtnl_unlock();
+	mutex_unlock(&cfg80211_mutex);
+
+	if (!IS_ERR_OR_NULL(old)) {
+		synchronize_rcu();
+		kfree(old);
+	}
  out:
 	release_firmware(fw);
 	return err;
@@ -2141,35 +2153,38 @@ static bool reg_is_world_roaming(struct wiphy *wiphy)
 static void reg_call_notifier(struct wiphy *wiphy,
 			      struct regulatory_request *request)
 {
+	lockdep_assert_wiphy(wiphy);
+
 	if (wiphy->reg_notifier)
 		wiphy->reg_notifier(wiphy, request);
 }
 
-static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,
+static bool handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,
 			      struct reg_beacon *reg_beacon)
 {
 	struct ieee80211_supported_band *sband;
 	struct ieee80211_channel *chan;
 	bool channel_changed = false;
 	struct ieee80211_channel chan_before;
-	struct regulatory_request *lr = get_last_request();
+
+	lockdep_assert_wiphy(wiphy);
 
 	sband = wiphy->bands[reg_beacon->chan.band];
 	chan = &sband->channels[chan_idx];
 
 	if (likely(!ieee80211_channel_equal(chan, &reg_beacon->chan)))
-		return;
+		return false;
 
 	if (chan->beacon_found)
-		return;
+		return false;
 
 	chan->beacon_found = true;
 
 	if (!reg_is_world_roaming(wiphy))
-		return;
+		return false;
 
 	if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS)
-		return;
+		return false;
 
 	chan_before = *chan;
 
@@ -2178,11 +2193,10 @@ static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,
 		channel_changed = true;
 	}
 
-	if (channel_changed) {
+	if (channel_changed)
 		nl80211_send_beacon_hint_event(wiphy, &chan_before, chan);
-		if (wiphy->flags & WIPHY_FLAG_CHANNEL_CHANGE_ON_BEACON)
-			reg_call_notifier(wiphy, lr);
-	}
+
+	return channel_changed;
 }
 
 /*
@@ -2194,14 +2208,20 @@ static void wiphy_update_new_beacon(struct wiphy *wiphy,
 {
 	unsigned int i;
 	struct ieee80211_supported_band *sband;
+	bool changed = false;
 
 	if (!wiphy->bands[reg_beacon->chan.band])
 		return;
 
 	sband = wiphy->bands[reg_beacon->chan.band];
 
+	guard(wiphy)(wiphy);
+
 	for (i = 0; i < sband->n_channels; i++)
-		handle_reg_beacon(wiphy, i, reg_beacon);
+		changed |= handle_reg_beacon(wiphy, i, reg_beacon);
+
+	if (changed && wiphy->flags & WIPHY_FLAG_CHANNEL_CHANGE_ON_BEACON)
+		reg_call_notifier(wiphy, get_last_request());
 }
 
 /*
@@ -2507,7 +2527,7 @@ static void reg_check_chans_work(struct work_struct *work)
 
 	rcu_read_lock();
 
-	list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list)
+	for_each_rdev(rdev)
 		wiphy_work_queue(&rdev->wiphy, &rdev->reg_check_chans_wk);
 
 	rcu_read_unlock();
@@ -2530,6 +2550,8 @@ static void wiphy_update_regulatory(struct wiphy *wiphy,
 	enum nl80211_band band;
 	struct regulatory_request *lr = get_last_request();
 
+	lockdep_assert_wiphy(wiphy);
+
 	if (ignore_reg_update(wiphy, initiator)) {
 		/*
 		 * Regulatory updates set by CORE are ignored for custom
@@ -2546,23 +2568,27 @@ static void wiphy_update_regulatory(struct wiphy *wiphy,
 
 	lr->dfs_region = get_cfg80211_regdom()->dfs_region;
 
+	/* the notifier is called below, so ignore beacon hint changes */
 	for (band = 0; band < NUM_NL80211_BANDS; band++)
 		handle_band(wiphy, initiator, wiphy->bands[band]);
 
 	reg_process_beacons(wiphy);
 	reg_process_ht_flags(wiphy);
+
 	reg_call_notifier(wiphy, lr);
 }
 
 static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator)
 {
 	struct cfg80211_registered_device *rdev;
-	struct wiphy *wiphy;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	for_each_rdev(rdev) {
-		wiphy = &rdev->wiphy;
+		struct wiphy *wiphy = &rdev->wiphy;
+
+		guard(wiphy)(wiphy);
+
 		wiphy_update_regulatory(wiphy, initiator);
 	}
 
@@ -2676,13 +2702,11 @@ void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
 	if (IS_ERR(new_regd))
 		return;
 
-	rtnl_lock();
 	scoped_guard(wiphy, wiphy) {
 		tmp = get_wiphy_regdom(wiphy);
 		rcu_assign_pointer(wiphy->regd, new_regd);
 		rcu_free_regdom(tmp);
 	}
-	rtnl_unlock();
 }
 EXPORT_SYMBOL(wiphy_apply_custom_regulatory);
 
@@ -2849,12 +2873,12 @@ reg_process_hint_driver(struct wiphy *wiphy,
 		if (IS_ERR(regd))
 			return REG_REQ_IGNORE;
 
-		tmp = get_wiphy_regdom(wiphy);
-		ASSERT_RTNL();
+		lockdep_assert_held(&cfg80211_mutex);
 		scoped_guard(wiphy, wiphy) {
+			tmp = get_wiphy_regdom(wiphy);
 			rcu_assign_pointer(wiphy->regd, regd);
+			rcu_free_regdom(tmp);
 		}
-		rcu_free_regdom(tmp);
 	}
 
 
@@ -3042,7 +3066,8 @@ static void wiphy_all_share_dfs_chan_state(struct wiphy *wiphy)
 {
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
+	lockdep_assert_wiphy(wiphy);
 
 	for_each_rdev(rdev) {
 		if (wiphy == &rdev->wiphy)
@@ -3094,6 +3119,8 @@ static void reg_process_hint(struct regulatory_request *reg_request)
 	 */
 	if (treatment == REG_REQ_ALREADY_SET && wiphy &&
 	    wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
+		guard(wiphy)(wiphy);
+
 		wiphy_update_regulatory(wiphy, initiator);
 		wiphy_all_share_dfs_chan_state(wiphy);
 		reg_check_channels();
@@ -3108,13 +3135,16 @@ static void reg_process_hint(struct regulatory_request *reg_request)
 static void notify_self_managed_wiphys(struct regulatory_request *request)
 {
 	struct cfg80211_registered_device *rdev;
-	struct wiphy *wiphy;
 
 	for_each_rdev(rdev) {
-		wiphy = &rdev->wiphy;
+		struct wiphy *wiphy = &rdev->wiphy;
+
 		if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED &&
-		    request->initiator == NL80211_REGDOM_SET_BY_USER)
+		    request->initiator == NL80211_REGDOM_SET_BY_USER) {
+			guard(wiphy)(wiphy);
+
 			reg_call_notifier(wiphy, request);
+		}
 	}
 }
 
@@ -3166,12 +3196,14 @@ static void reg_process_pending_beacon_hints(void)
 {
 	struct cfg80211_registered_device *rdev;
 	struct reg_beacon *pending_beacon, *tmp;
+	LIST_HEAD(pending);
 
 	/* This goes through the _pending_ beacon list */
 	spin_lock_bh(&reg_pending_beacons_lock);
+	list_splice_tail_init(&reg_pending_beacons, &pending);
+	spin_unlock_bh(&reg_pending_beacons_lock);
 
-	list_for_each_entry_safe(pending_beacon, tmp,
-				 &reg_pending_beacons, list) {
+	list_for_each_entry_safe(pending_beacon, tmp, &pending, list) {
 		list_del_init(&pending_beacon->list);
 
 		/* Applies the beacon hint to current wiphys */
@@ -3181,8 +3213,6 @@ static void reg_process_pending_beacon_hints(void)
 		/* Remembers the beacon hint for new wiphys or reg changes */
 		list_add_tail(&pending_beacon->list, &reg_beacon_list);
 	}
-
-	spin_unlock_bh(&reg_pending_beacons_lock);
 }
 
 static void reg_process_self_managed_hint(struct wiphy *wiphy)
@@ -3193,7 +3223,6 @@ static void reg_process_self_managed_hint(struct wiphy *wiphy)
 	enum nl80211_band band;
 	struct regulatory_request request = {};
 
-	ASSERT_RTNL();
 	lockdep_assert_wiphy(wiphy);
 
 	spin_lock(&reg_requests_lock);
@@ -3228,7 +3257,7 @@ static void reg_process_self_managed_hints(void)
 {
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	for_each_rdev(rdev) {
 		guard(wiphy)(&rdev->wiphy);
@@ -3241,11 +3270,11 @@ static void reg_process_self_managed_hints(void)
 
 static void reg_todo(struct work_struct *work)
 {
-	rtnl_lock();
+	guard(mutex)(&cfg80211_mutex);
+
 	reg_process_pending_hints();
 	reg_process_pending_beacon_hints();
 	reg_process_self_managed_hints();
-	rtnl_unlock();
 }
 
 static void queue_regulatory_request(struct regulatory_request *request)
@@ -3524,7 +3553,7 @@ static void restore_regulatory_settings(bool reset_user, bool cached)
 	LIST_HEAD(tmp_reg_req_list);
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	/*
 	 * Clear the indoor setting in case that it is not controlled by user
@@ -3571,8 +3600,11 @@ static void restore_regulatory_settings(bool reset_user, bool cached)
 	for_each_rdev(rdev) {
 		if (rdev->wiphy.regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
 			continue;
-		if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG)
+		if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG) {
+			guard(wiphy)(&rdev->wiphy);
+
 			restore_custom_reg_settings(&rdev->wiphy);
+		}
 	}
 
 	if (cached && (!is_an_alpha2(alpha2) ||
@@ -3639,6 +3671,8 @@ static bool is_wiphy_all_set_reg_flag(enum ieee80211_regulatory_flags flag)
 
 void regulatory_hint_disconnect(void)
 {
+	guard(mutex)(&cfg80211_mutex);
+
 	/* Restore of regulatory settings is not required when wiphy(s)
 	 * ignore IE from connected access point but clearance of beacon hints
 	 * is required when wiphy(s) supports beacon hints.
@@ -3907,7 +3941,7 @@ static int reg_set_rd_driver(const struct ieee80211_regdomain *rd,
 		return -ENODEV;
 
 	if (!driver_request->intersect) {
-		ASSERT_RTNL();
+		lockdep_assert_held(&cfg80211_mutex);
 		scoped_guard(wiphy, request_wiphy) {
 			if (request_wiphy->regd)
 				tmp = get_wiphy_regdom(request_wiphy);
@@ -3933,9 +3967,11 @@ static int reg_set_rd_driver(const struct ieee80211_regdomain *rd,
 	 * However if a driver requested this specific regulatory
 	 * domain we keep it for its private use
 	 */
-	tmp = get_wiphy_regdom(request_wiphy);
-	rcu_assign_pointer(request_wiphy->regd, rd);
-	rcu_free_regdom(tmp);
+	scoped_guard(wiphy, request_wiphy) {
+		tmp = get_wiphy_regdom(request_wiphy);
+		rcu_assign_pointer(request_wiphy->regd, rd);
+		rcu_free_regdom(tmp);
+	}
 
 	rd = NULL;
 
@@ -4108,7 +4144,7 @@ int regulatory_set_wiphy_regd_sync(struct wiphy *wiphy,
 {
 	int ret;
 
-	ASSERT_RTNL();
+	lockdep_assert_wiphy(wiphy);
 
 	ret = __regulatory_set_wiphy_regd(wiphy, rd);
 	if (ret)
@@ -4125,25 +4161,30 @@ void wiphy_regulatory_register(struct wiphy *wiphy)
 {
 	struct regulatory_request *lr = get_last_request();
 
-	/* self-managed devices ignore beacon hints and country IE */
-	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
-		wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS |
-					   REGULATORY_COUNTRY_IE_IGNORE;
+	lockdep_assert_held(&cfg80211_mutex);
 
-		/*
-		 * The last request may have been received before this
-		 * registration call. Call the driver notifier if
-		 * initiator is USER.
-		 */
-		if (lr->initiator == NL80211_REGDOM_SET_BY_USER)
-			reg_call_notifier(wiphy, lr);
-	}
+	scoped_guard(wiphy, wiphy) {
+		/* self-managed devices ignore beacon hints and country IE */
+		if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
+			wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS |
+						   REGULATORY_COUNTRY_IE_IGNORE;
 
-	if (!reg_dev_ignore_cell_hint(wiphy))
-		reg_num_devs_support_basehint++;
+			/*
+			 * The last request may have been received before this
+			 * registration call. Call the driver notifier if
+			 * initiator is USER.
+			 */
+			if (lr->initiator == NL80211_REGDOM_SET_BY_USER)
+				reg_call_notifier(wiphy, lr);
+		}
+
+		if (!reg_dev_ignore_cell_hint(wiphy))
+			reg_num_devs_support_basehint++;
+
+		wiphy_update_regulatory(wiphy, lr->initiator);
+		wiphy_all_share_dfs_chan_state(wiphy);
+	}
 
-	wiphy_update_regulatory(wiphy, lr->initiator);
-	wiphy_all_share_dfs_chan_state(wiphy);
 	reg_process_self_managed_hints();
 }
 
@@ -4152,6 +4193,8 @@ void wiphy_regulatory_deregister(struct wiphy *wiphy)
 	struct wiphy *request_wiphy = NULL;
 	struct regulatory_request *lr;
 
+	lockdep_assert_held(&cfg80211_mutex);
+
 	lr = get_last_request();
 
 	if (!reg_dev_ignore_cell_hint(wiphy))
@@ -4255,7 +4298,7 @@ static void cfg80211_check_and_end_cac(struct cfg80211_registered_device *rdev)
 	struct wireless_dev *wdev;
 	unsigned int link_id;
 
-	guard(wiphy)(&rdev->wiphy);
+	lockdep_assert_wiphy(&rdev->wiphy);
 
 	/* If we finished CAC or received radar, we should end any
 	 * CAC running on the same channels.
@@ -4290,7 +4333,7 @@ void regulatory_propagate_dfs_state(struct wiphy *wiphy,
 {
 	struct cfg80211_registered_device *rdev;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&cfg80211_mutex);
 
 	if (WARN_ON(!cfg80211_chandef_valid(chandef)))
 		return;
@@ -4306,6 +4349,8 @@ void regulatory_propagate_dfs_state(struct wiphy *wiphy,
 					   chandef->chan->center_freq))
 			continue;
 
+		guard(wiphy)(&rdev->wiphy);
+
 		cfg80211_set_dfs_state(&rdev->wiphy, chandef, dfs_state);
 
 		if (event == NL80211_RADAR_DETECTED ||
@@ -4390,15 +4435,15 @@ void regulatory_exit(void)
 {
 	struct regulatory_request *reg_request, *tmp;
 	struct reg_beacon *reg_beacon, *btmp;
+	const struct fwdb_header *db;
 
 	cancel_work_sync(&reg_work);
 	cancel_crda_timeout_sync();
 	cancel_delayed_work_sync(&reg_check_chans);
 
 	/* Lock to suppress warnings */
-	rtnl_lock();
-	reset_regdomains(true, NULL);
-	rtnl_unlock();
+	scoped_guard(mutex, &cfg80211_mutex)
+		reset_regdomains(true, NULL);
 
 	dev_set_uevent_suppress(&reg_fdev->dev, true);
 
@@ -4419,8 +4464,10 @@ void regulatory_exit(void)
 		kfree(reg_request);
 	}
 
-	if (!IS_ERR_OR_NULL(regdb))
-		kfree(regdb);
+	/* RCU is irrelevant on the way out */
+	db = rcu_dereference_protected(global_regdb, true);
+	if (!IS_ERR_OR_NULL(db))
+		kfree(db);
 	if (!IS_ERR_OR_NULL(cfg80211_user_regdom))
 		kfree(cfg80211_user_regdom);
 

47/3 2026/10/06 10:55 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24877
							output: 26
							thoughts: 2193
						

Thoughts:
**KMSAN Evaluation: Wireless Regulatory and Locking Changes**

Alright, let's break this down. My task is to decide if this patch series warrants a dedicated KMSAN fuzzing session. First, I need to understand the goals. I'm focusing on detecting uninitialized memory reads, particularly in the context of kernel-to-user information leaks, as that's what KMSAN is designed for, and this patch series primarily deals with changes to wireless regulatory and locking mechanisms. Standard bug detectors like KASAN and LOCKDEP handle the usual suspects: out-of-bounds, use-after-free, etc., so those are a "no-go" for justifying KMSAN.

The guiding rules are clear: KMSAN is warranted when new or modified code touches kernel structures that are sent to user space where uninitialized fields could sneak through. Conditional logic or branching dependent on uninitialized data also raises a flag. Likewise, allocations and complex structures with uninitialized fields that *could* be read later are also suspect, as are code patterns that could lead to reading outside the bounds of a buffer.

Conversely, if all new structures are fully initialized (zeroed), or if the risks are primarily OOB reads, NULL pointer dereferences, locking issues, or UAFs, then KMSAN is not necessary.

Now, let's dive into the patch series. A quick overview indicates a shift from RTNL locking to a finer-grained mutex-based scheme (`cfg80211_mutex`) for various regulatory and wireless operations, alongside improvements to RCU protection. Several files (`ath11k/reg.c`, `ath12k/reg.c`, `regd.c`, `chan.c`, `rtlwifi/regd.c`, `rtw89/regd.c`, `hwsim_main.c`, and several others) have been updated, mostly using `guard(rcu)()`,  and removing some `wiphy_lock`/`unlock` calls. Some modifications to locking documentation and the introduction of a mutex (e.g. `DEFINE_MUTEX(cfg80211_mutex)`) have also been performed.

The core of my analysis focuses on whether this introduces any new opportunities for uninitialized memory use or info-leaks. Specifically, the refactoring in `net/wireless/mlme.c` around `cfg80211_dfs_chan_expiry` and the associated `chandef` structure is where my attention is drawn. I see the allocation `expired = kvcalloc(n_chans, sizeof(*expired), GFP_KERNEL);` which *should* zero the memory, which is a good sign. And then the code iterates and calls `cfg80211_dfs_chan_expire()` which has a call to `cfg80211_chandef_create()`. Let's confirm that `cfg80211_chandef_create()` uses fully initialized `chandef`. If it does, and nothing else is copying uninitialized data to userspace, then this series doesn't seem to justify a KMSAN run because the `kvcalloc` *should* zero the memory, and the other lock changes are improvements, and not regressions. Therefore, after an inspection, it looks like **NeedsKMSAN=false**.



48/3 2026/10/06 10:56 tool grepper
0m Args:
{
  "Expression": "void cfg80211_chandef_create"
}

Results:
{
  "Output": "include/net/cfg80211.h=985=cfg80211_get_chandef_type(const struct cfg80211_chan_def *chandef)\n--\ninclude/net/cfg80211.h-1009- */\ninclude/net/cfg80211.h:1010:void cfg80211_chandef_create(struct cfg80211_chan_def *chandef,\ninclude/net/cfg80211.h-1011-\t\t\t     struct ieee80211_channel *channel,\n--\nnet/wireless/chan.c=18=static bool cfg80211_valid_60g_freq(u32 freq)\n--\nnet/wireless/chan.c-22-\nnet/wireless/chan.c:23:void cfg80211_chandef_create(struct cfg80211_chan_def *chandef,\nnet/wireless/chan.c-24-\t\t\t     struct ieee80211_channel *chan,\n"
}

49/3 2026/10/06 10:56 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 28080
							output: 34
							thoughts: 28
						

Thoughts:
Here's my take on the thought process, tailored for an expert audience:

**Diving into Channel Definition Creation**

Okay, so I need to understand how channel definitions are constructed within the `cfg80211` framework. My focus is going to be the `cfg80211_chandef_create` function, found in `net/wireless/chan.c`. I know this is the core of how the regulatory domain data interacts with the operating channel. I'm especially interested in the nuances of how it handles different frequency bands, channel widths, and regulatory constraints. I'm anticipating that this is where the sanity checks and validation of channel parameters really take place, and where the specific channel characteristics are effectively codified for the lower layers. I want to see if the arguments passed to this function are validated, if the resultant structure is consistent, if it's handling the details I have in mind. Let's dig in and see what the code reveals.



50/3 2026/10/06 10:56 tool read-file
0m Args:
{
  "File": "net/wireless/chan.c",
  "FirstLine": 23,
  "LineCount": 20
}

Results:
{
  "Contents": "  23:\tvoid cfg80211_chandef_create(struct cfg80211_chan_def *chandef,\n  24:\t\t\t\t     struct ieee80211_channel *chan,\n  25:\t\t\t\t     enum nl80211_channel_type chan_type)\n  26:\t{\n  27:\t\tif (WARN_ON(!chan))\n  28:\t\t\treturn;\n  29:\t\n  30:\t\t*chandef = (struct cfg80211_chan_def) {\n  31:\t\t\t.chan = chan,\n  32:\t\t};\n  33:\t\n  34:\t\tWARN_ON(chan-\u003eband == NL80211_BAND_60GHZ ||\n  35:\t\t\tchan-\u003eband == NL80211_BAND_S1GHZ);\n  36:\t\n  37:\t\tswitch (chan_type) {\n  38:\t\tcase NL80211_CHAN_NO_HT:\n  39:\t\t\tchandef-\u003ewidth = NL80211_CHAN_WIDTH_20_NOHT;\n  40:\t\t\tchandef-\u003ecenter_freq1 = chan-\u003ecenter_freq;\n  41:\t\t\tbreak;\n  42:\t\tcase NL80211_CHAN_HT20:\n"
}

51/3 2026/10/06 10:56 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 28685
							output: 213
							thoughts: 1872
						

Thoughts:
**Concurrency Refactoring in cfg80211: A Deep Dive**

Okay, let's break this down. My initial scan focused on identifying potential vulnerabilities related to uninitialized memory. This is crucial when dealing with concurrency changes, as races can lead to accessing garbage data. I started by carefully examining the provided code snippets and other parts of the patch series to confirm the code's intended behavior with respect to initialization.

First, I looked at `cfg80211_chandef_create`. This function correctly initializes the `chandef` struct to zero, and it is consistently used with valid data. There's no exposure to uninitialized values here, even when branching. The usage in the example code is also safe, as the struct is used consistently. If it isn't used it's because of a guard condition.

Next, I investigated `nl80211_prepare_wdev_dump`. The variables `wdev_id` and `*wdev` within the function are always assigned. There are no exposed risks here.

Then, I looked at `nl80211_pre_doit`. All local variables are initialized to reasonable values. The relevant variables are explicitly initialized.

I then went through `wiphy_update_new_beacon` and `reg_process_self_managed_hint`, which both initialize local variables to zero. Similarly, `reg_reload_regdb` showed no issues with uninitialized memory.

The core of this patch series is clearly about concurrency and synchronization. It's about replacing the global RTNL lock with more fine-grained locking mechanisms: `cfg80211_mutex`, wiphy mutexes, and RCU read locks. The focus is on protecting shared data structures like the `global_regdb` and `cfg80211_rdev_list` from race conditions. The primary risk from this refactoring is concurrency issues such as locking bugs (deadlocks, ABBA locks), use-after-free conditions, race conditions, or NULL pointer dereferences.

I've confirmed that this patch series does *not* introduce or modify anything that could lead to user-space exposure of uninitialized kernel data, conditional logic dependent on uninitialized data, or complex data structure allocation issues related to initialization. I see no evidence of uninitialized memory being read, used in bounds checks or length calculations, or in general, any potential misuse of memory that could lead to security issues.

Therefore, KMSAN is not applicable here. KASAN and LOCKDEP are the relevant detectors to use to ensure there are no lock related issues.

I'm confident that this is a safe concurrency refactoring that is properly implemented.



52/3 2026/10/06 10:56 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series refactors cfg80211/nl80211 and driver regulatory code to reduce RTNL dependency by introducing a dedicated cfg80211_mutex, utilizing wiphy mutex locking, and employing RCU read-side critical sections.\n\nThese modifications are strictly related to locking, synchronization, and object lifetime management. Any potential regressions introduced by these changes would be race conditions, use-after-free bugs, lock-order inversions, or missing lock assertions, which are detected by KASAN and LOCKDEP.\n\nThe patch does not introduce or modify any uninitialized memory reads, uninitialized buffer copies to user space, or complex uninitialized struct allocations (the newly introduced dynamic buffer allocation in cfg80211_dfs_channels_update_work uses kvcalloc, zero-initializing all entries). Therefore, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series refactors cfg80211/nl80211 and driver regulatory code to reduce RTNL dependency by introducing a dedicated cfg80211_mutex, utilizing wiphy mutex locking, and employing RCU read-side critical sections.\n\nThese modifications are strictly related to locking, synchronization, and object lifetime management. Any potential regressions introduced by these changes would be race conditions, use-after-free bugs, lock-order inversions, or missing lock assertions, which are detected by KASAN and LOCKDEP.\n\nThe patch does not introduce or modify any uninitialized memory reads, uninitialized buffer copies to user space, or complex uninitialized struct allocations (the newly introduced dynamic buffer allocation in cfg80211_dfs_channels_update_work uses kvcalloc, zero-initializing all entries). Therefore, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)