diff --git a/net/wireless/core.c b/net/wireless/core.c index 8bb2cbd66b48..668380deec7d 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -645,6 +645,8 @@ struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv, INIT_WORK(&rdev->destroy_work, cfg80211_destroy_iface_wk); wiphy_work_init(&rdev->sched_scan_stop_wk, cfg80211_sched_scan_stop_wk); INIT_WORK(&rdev->sched_scan_res_wk, cfg80211_sched_scan_results_wk); + wiphy_work_init(&rdev->reg_check_chans_wk, reg_leave_invalid_chans_wk); + INIT_WORK(&rdev->reg_leave_nan_wk, reg_leave_invalid_nan_wk); INIT_WORK(&rdev->propagate_radar_detect_wk, cfg80211_propagate_radar_detect_wk); INIT_WORK(&rdev->propagate_cac_done_wk, cfg80211_propagate_cac_done_wk); @@ -1344,6 +1346,7 @@ void wiphy_unregister(struct wiphy *wiphy) cancel_delayed_work_sync(&rdev->dfs_update_channels_wk); cancel_delayed_work_sync(&rdev->background_cac_done_wk); flush_work(&rdev->destroy_work); + flush_work(&rdev->reg_leave_nan_wk); flush_work(&rdev->propagate_radar_detect_wk); flush_work(&rdev->propagate_cac_done_wk); flush_work(&rdev->mgmt_registrations_update_wk); diff --git a/net/wireless/core.h b/net/wireless/core.h index 85dfb3ac803b..6138d207caf4 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -114,6 +114,8 @@ struct cfg80211_registered_device { struct work_struct destroy_work; struct wiphy_work sched_scan_stop_wk; struct work_struct sched_scan_res_wk; + struct wiphy_work reg_check_chans_wk; + struct work_struct reg_leave_nan_wk; struct cfg80211_chan_def radar_chandef; struct work_struct propagate_radar_detect_wk; diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 9910b080b402..11665e0a7efc 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -2446,19 +2446,52 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) return true; } -static void reg_leave_invalid_chans(struct wiphy *wiphy) +void reg_leave_invalid_nan_wk(struct work_struct *work) { + struct cfg80211_registered_device *rdev; struct wireless_dev *wdev; - struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + + rdev = container_of(work, struct cfg80211_registered_device, + reg_leave_nan_wk); + + /* stopping NAN closes its data interfaces, which needs the RTNL */ + rtnl_lock(); list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { bool valid; - scoped_guard(wiphy, wiphy) - valid = reg_wdev_chan_valid(wiphy, wdev); + if (wdev->iftype != NL80211_IFTYPE_NAN) + continue; + + scoped_guard(wiphy, &rdev->wiphy) + valid = reg_wdev_chan_valid(&rdev->wiphy, wdev); if (!valid) cfg80211_leave(rdev, wdev, -1); } + + rtnl_unlock(); +} + +void reg_leave_invalid_chans_wk(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + struct wireless_dev *wdev; + + lockdep_assert_held(&wiphy->mtx); + + list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { + if (reg_wdev_chan_valid(wiphy, wdev)) + continue; + + /* + * Tearing down NAN needs the RTNL for closing NAN_DATA + * interfaces, handle that separately. + */ + if (wdev->iftype == NL80211_IFTYPE_NAN) + schedule_work(&rdev->reg_leave_nan_wk); + else + cfg80211_leave_locked(rdev, wdev, -1); + } } static void reg_check_chans_work(struct work_struct *work) @@ -2466,12 +2499,13 @@ static void reg_check_chans_work(struct work_struct *work) struct cfg80211_registered_device *rdev; pr_debug("Verifying active interfaces after reg change\n"); - rtnl_lock(); - for_each_rdev(rdev) - reg_leave_invalid_chans(&rdev->wiphy); + rcu_read_lock(); - rtnl_unlock(); + list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) + wiphy_work_queue(&rdev->wiphy, &rdev->reg_check_chans_wk); + + rcu_read_unlock(); } void reg_check_channels(void) diff --git a/net/wireless/reg.h b/net/wireless/reg.h index fc31c5f9a61a..c587079ead8f 100644 --- a/net/wireless/reg.h +++ b/net/wireless/reg.h @@ -178,6 +178,22 @@ int reg_reload_regdb(void); */ void reg_check_channels(void); +/** + * reg_leave_invalid_chans_wk - check if channels are no longer usable and leave + * @wiphy: the wiphy to check + * @work: the work struct + */ +void reg_leave_invalid_chans_wk(struct wiphy *wiphy, struct wiphy_work *work); + +/** + * reg_leave_invalid_nan_wk - check channels and tear down NAN when unusable + * @work: the work struct + * + * Stopping a NAN interface needs the RTNL, so it cannot be done from + * reg_leave_invalid_chans_wk() which runs with the wiphy mutex held. + */ +void reg_leave_invalid_nan_wk(struct work_struct *work); + extern const u8 shipped_regdb_certs[]; extern unsigned int shipped_regdb_certs_len; extern const u8 extra_regdb_certs[];