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);