A NAN channel can be evacuated, i.e. detached from its chanctx, if all chanctxs are used by NAN and a chanctx is needed for something else. For example if the STA interface needs to perform a channel switch. Implement the evacuation: detach the NAN channel from its chanctx, remove all the peer NAN channels that were using this chanctx, and update the driver. Internally, the NAN channel evacuation will be triggered in the scenario described above, and API is provided for the driver to also trigger it. The driver/device is assumed to publish a ULW to notify the peers about the fact that we won't be present on this NAN channel anymore. Also export this as an API for the drivers: if a driver has other resources per channel, it might want to trigger channel evacuation in order to free up such internal resources for other usages. Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit --- include/net/mac80211.h | 15 +++++ net/mac80211/chan.c | 28 ++++++--- net/mac80211/nan.c | 126 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 7 deletions(-) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 0d1b1d726b9c..d909bc1b29ff 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -7905,6 +7905,21 @@ void ieee80211_nan_cluster_joined(struct ieee80211_vif *vif, const u8 *cluster_id, bool new_cluster, gfp_t gfp); +/** + * ieee80211_nan_try_evacuate - try to evacuate a NAN channel + * + * This function tries to evacuate a NAN channel that is using the given + * channel context, to free up channel context resources. + * + * @hw: pointer as obtained from ieee80211_alloc_hw() + * @conf: the channel context configuration to try to evacuate. If %NULL, + * the NAN channel that has the fewest slots scheduled will be evacuated. + * + * Return: %true if a channel was evacuated, %false otherwise + */ +bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *conf); + /** * ieee80211_calc_rx_airtime - calculate estimated transmission airtime for RX. * diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 248531051a4e..9683d3e6e1d2 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -1861,16 +1861,21 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) } if (n_assigned != n_reserved) { - if (n_ready == n_reserved) { - wiphy_info(local->hw.wiphy, - "channel context reservation cannot be finalized because some interfaces aren't switching\n"); - err = -EBUSY; - goto err; - } + if (n_ready != n_reserved) + return -EAGAIN; - return -EAGAIN; + if (n_assigned == n_reserved + 1 && + ieee80211_nan_try_evacuate(&local->hw, + &ctx->replace_ctx->conf)) + goto use_reserved; + + wiphy_info(local->hw.wiphy, + "channel context reservation cannot be finalized because some interfaces aren't switching\n"); + err = -EBUSY; + goto err; } +use_reserved: ctx->conf.radar_enabled = false; for_each_chanctx_user_reserved(local, ctx, &iter) { if (ieee80211_link_has_in_place_reservation(iter.link) && @@ -2178,6 +2183,15 @@ int _ieee80211_link_use_channel(struct ieee80211_link_data *link, ctx = ieee80211_find_or_create_chanctx(sdata, chanreq, mode, assign_on_failure, &reused_ctx); + if (IS_ERR(ctx)) { + /* Try to evacuate a NAN channel to free up a chanctx */ + if (ieee80211_nan_try_evacuate(&local->hw, NULL)) + ctx = ieee80211_find_or_create_chanctx(sdata, chanreq, + mode, + assign_on_failure, + &reused_ctx); + } + if (IS_ERR(ctx)) { ret = PTR_ERR(ctx); goto out; diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c index 4e262b624521..cea620aaee6a 100644 --- a/net/mac80211/nan.c +++ b/net/mac80211/nan.c @@ -334,7 +334,10 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata, sched_idx_to_chan[i] = chan; ieee80211_nan_init_channel(chan, &sched->nan_channels[i]); + } + /* Also a pre-existing channel might have been ULWed, so no chanctx */ + if (!chan->chanctx_conf) { ret = ieee80211_nan_use_chanctx(sdata, chan, false); if (ret) { memset(chan, 0, sizeof(*chan)); @@ -708,3 +711,126 @@ int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata, ieee80211_nan_free_peer_sched(to_free); return ret; } + +static void +ieee80211_nan_evacuate_channel(struct ieee80211_sub_if_data *sdata, + struct ieee80211_nan_channel *nan_channel) +{ + struct ieee80211_chanctx_conf *conf; + struct ieee80211_chanctx *ctx; + + lockdep_assert_wiphy(sdata->local->hw.wiphy); + + if (WARN_ON(!nan_channel || !nan_channel->chanreq.oper.chan)) + return; + + conf = nan_channel->chanctx_conf; + if (WARN_ON(!conf)) + return; + + nan_channel->chanctx_conf = NULL; + + /* Update all peer channels that reference this chanctx */ + ieee80211_nan_update_peer_channels(sdata, conf); + + drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED); + + cfg80211_nan_channel_evac(&sdata->wdev, &nan_channel->chanreq.oper, + GFP_KERNEL); + + /* Update NDI carrier states */ + ieee80211_nan_update_all_ndi_carriers(sdata->local); + + /* Clean up the channel context if no longer used */ + ctx = container_of(conf, struct ieee80211_chanctx, conf); + + if (ieee80211_chanctx_num_assigned(sdata->local, ctx) > 0) { + ieee80211_recalc_chanctx_chantype(sdata->local, ctx); + ieee80211_recalc_smps_chanctx(sdata->local, ctx); + ieee80211_recalc_chanctx_min_def(sdata->local, ctx); + } + + if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0) + ieee80211_free_chanctx(sdata->local, ctx, false); +} + +bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *conf) +{ + struct ieee80211_sub_if_data *sdata = NULL, *tmp; + struct ieee80211_local *local = hw_to_local(hw); + struct ieee80211_nan_channel *evac_chan = NULL; + struct ieee80211_nan_sched_cfg *sched_cfg; + struct ieee80211_chanctx *ctx = NULL; + int min_slot_count = INT_MAX; + int usable_channels = 0; + + lockdep_assert_wiphy(local->hw.wiphy); + + if (conf) + ctx = container_of(conf, struct ieee80211_chanctx, conf); + + /* Find the NAN interface - there can only be one */ + list_for_each_entry(tmp, &local->interfaces, list) { + if (ieee80211_sdata_running(tmp) && + tmp->vif.type == NL80211_IFTYPE_NAN) { + sdata = tmp; + break; + } + } + + if (!sdata) + return false; + + sched_cfg = &sdata->vif.cfg.nan_sched; + + /* Find the channel to evacuate and count usable channels */ + for (int i = 0; i < IEEE80211_NAN_MAX_CHANNELS; i++) { + struct ieee80211_nan_channel *chan = + &sched_cfg->channels[i]; + struct ieee80211_chanctx *chan_ctx; + int slot_count = 0; + + if (!chan->chanreq.oper.chan || !chan->chanctx_conf) + continue; + + usable_channels++; + + chan_ctx = container_of(chan->chanctx_conf, + struct ieee80211_chanctx, conf); + + /* If ctx specified, only consider that specific chanctx */ + if (ctx) { + if (chan_ctx == ctx) + evac_chan = chan; + continue; + } + + /* Can only evacuate channels whose chanctx is NAN-only */ + if (ieee80211_chanctx_refcount(local, chan_ctx) > 1) + continue; + + /* Count how many time slots use this channel */ + for (int s = 0; s < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; s++) + if (sched_cfg->schedule[s] == chan) + slot_count++; + + if (slot_count < min_slot_count) { + min_slot_count = slot_count; + evac_chan = chan; + } + } + + /* No suitable NAN channel found */ + if (!evac_chan) + return false; + + /* NAN needs at least one remaining usable channel after evacuation */ + if (usable_channels < 2) + return false; + + ieee80211_nan_evacuate_channel(sdata, evac_chan); + + return true; +} +EXPORT_SYMBOL(ieee80211_nan_try_evacuate); -- 2.34.1