--- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -3525,6 +3525,27 @@ static int ieee80211_set_bitrate_mask(st return 0; } +bool ieee80211_scanning_busy(struct ieee80211_local *local, + struct cfg80211_chan_def *chandef) +{ + struct cfg80211_scan_request *scan_req; + struct wiphy *wiphy = local->hw.wiphy; + u32 mask; + + if (list_empty(&local->roc_list) && !local->scanning) + return false; + + if (!wiphy->n_radio) + return true; + + mask = ieee80211_offchannel_radio_mask(local); + scan_req = wiphy_dereference(wiphy, local->scan_req); + if (scan_req) + mask |= ieee80211_scan_req_radio_mask(local, scan_req); + + return mask & ieee80211_chandef_radio_mask(local, chandef); +} + static int ieee80211_start_radar_detection(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_chan_def *chandef, @@ -3538,7 +3559,7 @@ static int ieee80211_start_radar_detecti lockdep_assert_wiphy(local->hw.wiphy); - if (!list_empty(&local->roc_list) || local->scanning) + if (ieee80211_scanning_busy(local, chandef)) return -EBUSY; link_data = sdata_dereference(sdata->link[link_id], sdata); @@ -4030,7 +4051,7 @@ __ieee80211_channel_switch(struct wiphy lockdep_assert_wiphy(local->hw.wiphy); - if (!list_empty(&local->roc_list) || local->scanning) + if (ieee80211_scanning_busy(local, ¶ms->chandef)) return -EBUSY; if (sdata->wdev.links[link_id].cac_started) --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -644,14 +644,24 @@ ieee80211_find_chanctx(struct ieee80211_ return NULL; } -bool ieee80211_is_radar_required(struct ieee80211_local *local) +bool ieee80211_is_radar_required(struct ieee80211_local *local, u32 radio_mask) { + struct ieee80211_chanctx_conf *conf; struct ieee80211_link_data *link; lockdep_assert_wiphy(local->hw.wiphy); for_each_sdata_link(local, link) { - if (link->radar_required) + if (!link->radar_required) + continue; + if (!local->hw.wiphy->n_radio) + return true; + + conf = wiphy_dereference(local->hw.wiphy, link->conf->chanctx_conf); + if (!conf) + continue; + + if (conf->radio_idx >= 0 && (radio_mask & BIT(conf->radio_idx))) return true; } --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1958,6 +1958,12 @@ int ieee80211_mesh_finish_csa(struct iee u64 *changed); /* scan/BSS handling */ +u32 ieee80211_scan_req_radio_mask(struct ieee80211_local *local, + struct cfg80211_scan_request *req); +bool ieee80211_scanning_busy(struct ieee80211_local *local, + struct cfg80211_chan_def *chandef); +u32 ieee80211_can_leave_ch(struct ieee80211_sub_if_data *sdata, + u32 radio_mask); void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work); int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata, const u8 *ssid, u8 ssid_len, @@ -1996,6 +2002,7 @@ void ieee80211_sched_scan_stopped_work(s /* off-channel/mgmt-tx */ void ieee80211_offchannel_stop_vifs(struct ieee80211_local *local); void ieee80211_offchannel_return(struct ieee80211_local *local); +u32 ieee80211_offchannel_radio_mask(struct ieee80211_local *local); void ieee80211_roc_setup(struct ieee80211_local *local); void ieee80211_start_next_roc(struct ieee80211_local *local); void ieee80211_reconfig_roc(struct ieee80211_local *local); @@ -2640,6 +2647,8 @@ bool ieee80211_chandef_s1g_oper(const st struct cfg80211_chan_def *chandef); void ieee80211_chandef_downgrade(struct cfg80211_chan_def *chandef, struct ieee80211_conn_settings *conn); +u32 ieee80211_chandef_radio_mask(struct ieee80211_local *local, + struct cfg80211_chan_def *chandef); static inline void ieee80211_chanreq_downgrade(struct ieee80211_chan_req *chanreq, struct ieee80211_conn_settings *conn) @@ -2696,7 +2705,7 @@ void ieee80211_recalc_chanctx_min_def(st struct ieee80211_chanctx *ctx, struct ieee80211_link_data *rsvd_for, bool check_reserved); -bool ieee80211_is_radar_required(struct ieee80211_local *local); +bool ieee80211_is_radar_required(struct ieee80211_local *local, u32 radio_mask); void ieee80211_dfs_cac_timer_work(struct wiphy *wiphy, struct wiphy_work *work); void ieee80211_dfs_cac_cancel(struct ieee80211_local *local, --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -168,6 +168,35 @@ void ieee80211_offchannel_return(struct false); } +u32 ieee80211_offchannel_radio_mask(struct ieee80211_local *local) +{ + const struct wiphy_radio *radio; + struct ieee80211_roc_work *roc; + u32 mask = 0; + int r; + + for (r = 0; r < local->hw.wiphy->n_radio; r++) { + radio = &local->hw.wiphy->radio[r]; + + list_for_each_entry(roc, &local->roc_list, list) { + struct cfg80211_chan_def chandef = {}; + + if (!roc->started) + continue; + + cfg80211_chandef_create(&chandef, roc->chan, + NL80211_CHAN_NO_HT); + if (!cfg80211_radio_chandef_valid(radio, &chandef)) + continue; + + mask |= BIT(r); + break; + } + } + + return mask; +} + static void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc) { /* was never transmitted */ @@ -566,7 +595,9 @@ static int ieee80211_start_roc_work(stru enum ieee80211_roc_type type) { struct ieee80211_roc_work *roc, *tmp; + struct cfg80211_chan_def chandef = {}; bool queued = false, combine_started = true; + u32 radio_mask; int ret; lockdep_assert_wiphy(local->hw.wiphy); @@ -578,6 +609,12 @@ static int ieee80211_start_roc_work(stru if (!local->emulate_chanctx && !local->ops->remain_on_channel) return -EOPNOTSUPP; + cfg80211_chandef_create(&chandef, channel, NL80211_CHAN_NO_HT); + radio_mask = ieee80211_chandef_radio_mask(local, &chandef); + if (!ieee80211_can_leave_ch(sdata, radio_mask) && + !ieee80211_scanning_busy(local, &chandef)) + return -EBUSY; + roc = kzalloc(sizeof(*roc), GFP_KERNEL); if (!roc) return -ENOMEM; @@ -613,8 +650,7 @@ static int ieee80211_start_roc_work(stru } /* if there's no need to queue, handle it immediately */ - if (list_empty(&local->roc_list) && - !local->scanning && !ieee80211_is_radar_required(local)) { + if (list_empty(&local->roc_list) && !local->scanning) { /* if not HW assist, just queue & schedule work */ if (!local->ops->remain_on_channel) { list_add_tail(&roc->list, &local->roc_list); --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -571,36 +571,83 @@ static int ieee80211_start_sw_scan(struc return 0; } -static bool __ieee80211_can_leave_ch(struct ieee80211_sub_if_data *sdata) +u32 ieee80211_scan_req_radio_mask(struct ieee80211_local *local, + struct cfg80211_scan_request *req) +{ + const struct wiphy_radio *radio; + u32 mask = 0; + int i, r; + + for (r = 0; r < local->hw.wiphy->n_radio; r++) { + radio = &local->hw.wiphy->radio[r]; + + for (i = 0; i < req->n_channels; i++) { + struct cfg80211_chan_def chandef = {}; + + chandef.chan = req->channels[i]; + cfg80211_chandef_create(&chandef, req->channels[i], + NL80211_CHAN_NO_HT); + if (!cfg80211_radio_chandef_valid(radio, &chandef)) + continue; + + mask |= BIT(r); + break; + } + } + + return mask; +} + +u32 ieee80211_can_leave_ch(struct ieee80211_sub_if_data *sdata, + u32 radio_mask) { struct ieee80211_local *local = sdata->local; struct ieee80211_sub_if_data *sdata_iter; + struct wiphy *wiphy = local->hw.wiphy; + struct ieee80211_chanctx_conf *conf; + struct ieee80211_link_data *link; unsigned int link_id; lockdep_assert_wiphy(local->hw.wiphy); - if (!ieee80211_is_radar_required(local)) + if (!ieee80211_is_radar_required(local, radio_mask)) return true; if (!regulatory_pre_cac_allowed(local->hw.wiphy)) return false; list_for_each_entry(sdata_iter, &local->interfaces, list) { - for_each_valid_link(&sdata_iter->wdev, link_id) - if (sdata_iter->wdev.links[link_id].cac_started) + for_each_valid_link(&sdata_iter->wdev, link_id) { + if (!sdata_iter->wdev.links[link_id].cac_started) + continue; + + if (!wiphy->n_radio) return false; + + link = sdata_dereference(sdata->link[link_id], sdata); + if (!link) + continue; + + conf = wiphy_dereference(wiphy, link->conf->chanctx_conf); + if (!conf) + continue; + + if (conf->radio_idx >= 0 && + (radio_mask & BIT(conf->radio_idx))) + return false; + } } return true; } static bool ieee80211_can_scan(struct ieee80211_local *local, - struct ieee80211_sub_if_data *sdata) + struct ieee80211_sub_if_data *sdata, + u32 radio_mask) { - if (!__ieee80211_can_leave_ch(sdata)) - return false; - - if (!list_empty(&local->roc_list)) + if (!list_empty(&local->roc_list) && + (!local->hw.wiphy->n_radio || + (radio_mask & ieee80211_offchannel_radio_mask(local)))) return false; if (sdata->vif.type == NL80211_IFTYPE_STATION && @@ -612,15 +659,22 @@ static bool ieee80211_can_scan(struct ie void ieee80211_run_deferred_scan(struct ieee80211_local *local) { + struct ieee80211_sub_if_data *sdata; + struct cfg80211_scan_request *req; + u32 radio_mask; + lockdep_assert_wiphy(local->hw.wiphy); - if (!local->scan_req || local->scanning) + req = wiphy_dereference(local->hw.wiphy, local->scan_req); + if (!req || local->scanning) + return; + + radio_mask = ieee80211_scan_req_radio_mask(local, req); + sdata = wiphy_dereference(local->hw.wiphy, local->scan_sdata); + if (!ieee80211_can_leave_ch(sdata, radio_mask)) return; - if (!ieee80211_can_scan(local, - rcu_dereference_protected( - local->scan_sdata, - lockdep_is_held(&local->hw.wiphy->mtx)))) + if (!ieee80211_can_scan(local, sdata, radio_mask)) return; wiphy_delayed_work_queue(local->hw.wiphy, &local->scan_work, @@ -703,6 +757,7 @@ static int __ieee80211_start_scan(struct { struct ieee80211_local *local = sdata->local; bool hw_scan = local->ops->hw_scan; + u32 radio_mask; int rc; lockdep_assert_wiphy(local->hw.wiphy); @@ -717,10 +772,11 @@ static int __ieee80211_start_scan(struct !(sdata->vif.active_links & BIT(req->tsf_report_link_id))) return -EINVAL; - if (!__ieee80211_can_leave_ch(sdata)) + radio_mask = ieee80211_scan_req_radio_mask(local, req); + if (!ieee80211_can_leave_ch(sdata, radio_mask)) return -EBUSY; - if (!ieee80211_can_scan(local, sdata)) { + if (!ieee80211_can_scan(local, sdata, radio_mask)) { /* wait for the work to finish/time out */ rcu_assign_pointer(local->scan_req, req); rcu_assign_pointer(local->scan_sdata, sdata); --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -877,6 +877,23 @@ struct wireless_dev *ieee80211_vif_to_wd } EXPORT_SYMBOL_GPL(ieee80211_vif_to_wdev); +u32 ieee80211_chandef_radio_mask(struct ieee80211_local *local, + struct cfg80211_chan_def *chandef) +{ + struct wiphy *wiphy = local->hw.wiphy; + const struct wiphy_radio *radio; + u32 mask = 0; + int i; + + for (i = 0; i < wiphy->n_radio; i++) { + radio = &wiphy->radio[i]; + if (cfg80211_radio_chandef_valid(radio, chandef)) + mask |= BIT(i); + } + + return mask; +} + /* * Nothing should have been stuffed into the workqueue during * the suspend->resume cycle. Since we can't check each caller --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -2907,6 +2907,9 @@ bool cfg80211_radio_chandef_valid(const { u32 freq, width; + if (!cfg80211_chandef_valid(chandef)) + return false; + freq = ieee80211_chandef_to_khz(chandef); width = nl80211_chan_width_to_mhz(chandef->width); if (!ieee80211_radio_freq_range_valid(radio, freq, width))