| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * NAN mode implementation | |
| * Copyright(c) 2025-2026 Intel Corporation | |
| */ | |
| #include <net/mac80211.h> | |
| #include "ieee80211_i.h" | |
| #include "driver-ops.h" | |
| #include "sta_info.h" | |
| static void | |
| ieee80211_nan_init_channel(struct ieee80211_nan_channel *nan_channel, | |
| struct cfg80211_nan_channel *cfg_nan_channel) | |
| { | |
| memset(nan_channel, 0, sizeof(*nan_channel)); | |
| nan_channel->chanreq.oper = cfg_nan_channel->chandef; | |
| memcpy(nan_channel->channel_entry, cfg_nan_channel->channel_entry, | |
| sizeof(nan_channel->channel_entry)); | |
| nan_channel->needed_rx_chains = cfg_nan_channel->rx_nss; | |
| } | |
| static void | |
| ieee80211_nan_update_channel(struct ieee80211_local *local, | |
| struct ieee80211_nan_channel *nan_channel, | |
| struct cfg80211_nan_channel *cfg_nan_channel, | |
| bool deferred) | |
| { | |
| struct ieee80211_chanctx_conf *conf; | |
| bool reducing_nss; | |
| if (WARN_ON(!cfg80211_chandef_identical(&nan_channel->chanreq.oper, | |
| &cfg_nan_channel->chandef))) | |
| return; | |
| if (WARN_ON(memcmp(nan_channel->channel_entry, | |
| cfg_nan_channel->channel_entry, | |
| sizeof(nan_channel->channel_entry)))) | |
| return; | |
| if (nan_channel->needed_rx_chains == cfg_nan_channel->rx_nss) | |
| return; | |
| reducing_nss = nan_channel->needed_rx_chains > cfg_nan_channel->rx_nss; | |
| nan_channel->needed_rx_chains = cfg_nan_channel->rx_nss; | |
| conf = nan_channel->chanctx_conf; | |
| /* | |
| * If we are adding NSSs, we need to be ready before notifying the peer, | |
| * if we are reducing NSSs, we need to wait until the peer is notified. | |
| */ | |
| if (!conf || (deferred && reducing_nss)) | |
| return; | |
| ieee80211_recalc_smps_chanctx(local, container_of(conf, | |
| struct ieee80211_chanctx, | |
| conf)); | |
| } | |
| static int | |
| ieee80211_nan_use_chanctx(struct ieee80211_sub_if_data *sdata, | |
| struct ieee80211_nan_channel *nan_channel, | |
| bool assign_on_failure) | |
| { | |
| struct ieee80211_chanctx *ctx; | |
| bool reused_ctx; | |
| if (!nan_channel->chanreq.oper.chan) | |
| return -EINVAL; | |
| if (ieee80211_check_combinations(sdata, &nan_channel->chanreq.oper, | |
| IEEE80211_CHANCTX_SHARED, 0, -1)) | |
| return -EBUSY; | |
| ctx = ieee80211_find_or_create_chanctx(sdata, &nan_channel->chanreq, | |
| IEEE80211_CHANCTX_SHARED, | |
| assign_on_failure, | |
| &reused_ctx); | |
| if (IS_ERR(ctx)) | |
| return PTR_ERR(ctx); | |
| nan_channel->chanctx_conf = &ctx->conf; | |
| /* | |
| * In case an existing channel context is being used, we marked it as | |
| * will_be_used, now that it is assigned - clear this indication | |
| */ | |
| if (reused_ctx) { | |
| WARN_ON(!ctx->will_be_used); | |
| ctx->will_be_used = false; | |
| } | |
| ieee80211_recalc_chanctx_min_def(sdata->local, ctx); | |
| ieee80211_recalc_smps_chanctx(sdata->local, ctx); | |
| return 0; | |
| } | |
| static void | |
| ieee80211_nan_update_peer_channels(struct ieee80211_sub_if_data *sdata, | |
| struct ieee80211_chanctx_conf *removed_conf) | |
| { | |
| struct ieee80211_local *local = sdata->local; | |
| struct sta_info *sta; | |
| lockdep_assert_wiphy(local->hw.wiphy); | |
| list_for_each_entry(sta, &local->sta_list, list) { | |
| struct ieee80211_nan_peer_sched *peer_sched; | |
| int write_idx = 0; | |
| bool updated = false; | |
| if (sta->sdata != sdata) | |
| continue; | |
| peer_sched = sta->sta.nan_sched; | |
| if (!peer_sched) | |
| continue; | |
| /* NULL out map slots for channels being removed */ | |
| for (int i = 0; i < peer_sched->n_channels; i++) { | |
| if (peer_sched->channels[i].chanctx_conf != removed_conf) | |
| continue; | |
| for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) { | |
| struct ieee80211_nan_peer_map *map = | |
| &peer_sched->maps[m]; | |
| if (map->map_id == CFG80211_NAN_INVALID_MAP_ID) | |
| continue; | |
| for (int s = 0; s < ARRAY_SIZE(map->slots); s++) | |
| if (map->slots[s] == &peer_sched->channels[i]) | |
| map->slots[s] = NULL; | |
| } | |
| } | |
| /* Compact channels array, removing those with removed_conf */ | |
| for (int i = 0; i < peer_sched->n_channels; i++) { | |
| if (peer_sched->channels[i].chanctx_conf == removed_conf) { | |
| updated = true; | |
| continue; | |
| } | |
| if (write_idx != i) { | |
| /* Update map pointers before moving */ | |
| for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) { | |
| struct ieee80211_nan_peer_map *map = | |
| &peer_sched->maps[m]; | |
| if (map->map_id == CFG80211_NAN_INVALID_MAP_ID) | |
| continue; | |
| for (int s = 0; s < ARRAY_SIZE(map->slots); s++) | |
| if (map->slots[s] == &peer_sched->channels[i]) | |
| map->slots[s] = &peer_sched->channels[write_idx]; | |
| } | |
| peer_sched->channels[write_idx] = peer_sched->channels[i]; | |
| } | |
| write_idx++; | |
| } | |
| /* Clear any remaining entries at the end */ | |
| for (int i = write_idx; i < peer_sched->n_channels; i++) | |
| memset(&peer_sched->channels[i], 0, sizeof(peer_sched->channels[i])); | |
| peer_sched->n_channels = write_idx; | |
| if (updated) | |
| drv_nan_peer_sched_changed(local, sdata, sta); | |
| } | |
| } | |
| static void | |
| ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata, | |
| struct ieee80211_nan_channel *nan_channel) | |
| { | |
| struct ieee80211_chanctx_conf *conf; | |
| struct ieee80211_chanctx *ctx; | |
| struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched; | |
| if (WARN_ON(!nan_channel)) | |
| return; | |
| lockdep_assert_wiphy(sdata->local->hw.wiphy); | |
| if (!nan_channel->chanreq.oper.chan) | |
| return; | |
| for (int slot = 0; slot < ARRAY_SIZE(sched_cfg->schedule); slot++) | |
| if (sched_cfg->schedule[slot] == nan_channel) | |
| sched_cfg->schedule[slot] = NULL; | |
| conf = nan_channel->chanctx_conf; | |
| /* If any peer nan schedule uses this chanctx, update them */ | |
| if (conf) | |
| ieee80211_nan_update_peer_channels(sdata, conf); | |
| memset(nan_channel, 0, sizeof(*nan_channel)); | |
| /* Update the driver before (possibly) releasing the channel context */ | |
| drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED); | |
| /* Channel might not have a chanctx if it was ULWed */ | |
| if (!conf) | |
| return; | |
| 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); | |
| } | |
| static void | |
| ieee80211_nan_update_all_ndi_carriers(struct ieee80211_local *local) | |
| { | |
| struct ieee80211_sub_if_data *sdata; | |
| lockdep_assert_wiphy(local->hw.wiphy); | |
| /* Iterate all interfaces and update carrier for NDI interfaces */ | |
| list_for_each_entry(sdata, &local->interfaces, list) { | |
| if (!ieee80211_sdata_running(sdata) || | |
| sdata->vif.type != NL80211_IFTYPE_NAN_DATA) | |
| continue; | |
| ieee80211_nan_update_ndi_carrier(sdata); | |
| } | |
| } | |
| static struct ieee80211_nan_channel * | |
| ieee80211_nan_find_free_channel(struct ieee80211_nan_sched_cfg *sched_cfg) | |
| { | |
| for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) { | |
| if (!sched_cfg->channels[i].chanreq.oper.chan) | |
| return &sched_cfg->channels[i]; | |
| } | |
| return NULL; | |
| } | |
| int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata, | |
| struct cfg80211_nan_local_sched *sched) | |
| { | |
| struct ieee80211_nan_channel *sched_idx_to_chan[IEEE80211_NAN_MAX_CHANNELS] = {}; | |
| struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched; | |
| struct ieee80211_nan_sched_cfg backup_sched; | |
| int ret; | |
| if (sched->n_channels > IEEE80211_NAN_MAX_CHANNELS) | |
| return -EOPNOTSUPP; | |
| if (sched->nan_avail_blob_len > IEEE80211_NAN_AVAIL_BLOB_MAX_LEN) | |
| return -EINVAL; | |
| /* | |
| * If a deferred schedule update is pending completion, new updates are | |
| * not allowed. Only allow to configure an empty schedule so NAN can be | |
| * stopped in the middle of a deferred update. This is fine because | |
| * empty schedule means the local NAN device will not be available for | |
| * peers anymore so there is no need to update peers about a new | |
| * schedule. | |
| */ | |
| if (WARN_ON(sched_cfg->deferred && sched->n_channels)) | |
| return -EBUSY; | |
| bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS); | |
| memcpy(backup_sched.schedule, sched_cfg->schedule, | |
| sizeof(backup_sched.schedule)); | |
| memcpy(backup_sched.channels, sched_cfg->channels, | |
| sizeof(backup_sched.channels)); | |
| memcpy(backup_sched.avail_blob, sched_cfg->avail_blob, | |
| sizeof(backup_sched.avail_blob)); | |
| backup_sched.avail_blob_len = sched_cfg->avail_blob_len; | |
| memcpy(sched_cfg->avail_blob, sched->nan_avail_blob, | |
| sched->nan_avail_blob_len); | |
| sched_cfg->avail_blob_len = sched->nan_avail_blob_len; | |
| /* | |
| * Remove channels that are no longer in the new schedule to free up | |
| * resources before adding new channels. For deferred schedule, channels | |
| * will be removed when the schedule is applied. | |
| * Create a mapping from sched index to sched_cfg channel | |
| */ | |
| for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) { | |
| bool still_needed = false; | |
| if (!sched_cfg->channels[i].chanreq.oper.chan) | |
| continue; | |
| for (int j = 0; j < sched->n_channels; j++) { | |
| if (cfg80211_chandef_identical(&sched_cfg->channels[i].chanreq.oper, | |
| &sched->nan_channels[j].chandef)) { | |
| sched_idx_to_chan[j] = | |
| &sched_cfg->channels[i]; | |
| still_needed = true; | |
| break; | |
| } | |
| } | |
| if (!still_needed) { | |
| __set_bit(i, sdata->u.nan.removed_channels); | |
| if (!sched->deferred) | |
| ieee80211_nan_remove_channel(sdata, | |
| &sched_cfg->channels[i]); | |
| } | |
| } | |
| for (int i = 0; i < sched->n_channels; i++) { | |
| struct ieee80211_nan_channel *chan = sched_idx_to_chan[i]; | |
| if (chan) { | |
| ieee80211_nan_update_channel(sdata->local, chan, | |
| &sched->nan_channels[i], | |
| sched->deferred); | |
| } else { | |
| chan = ieee80211_nan_find_free_channel(sched_cfg); | |
| if (WARN_ON(!chan)) { | |
| ret = -EINVAL; | |
| goto err; | |
| } | |
| 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)); | |
| goto err; | |
| } | |
| } | |
| } | |
| for (int s = 0; s < ARRAY_SIZE(sched_cfg->schedule); s++) { | |
| if (sched->schedule[s] < ARRAY_SIZE(sched_idx_to_chan)) | |
| sched_cfg->schedule[s] = | |
| sched_idx_to_chan[sched->schedule[s]]; | |
| else | |
| sched_cfg->schedule[s] = NULL; | |
| } | |
| sched_cfg->deferred = sched->deferred; | |
| drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED); | |
| /* | |
| * For deferred update, don't update NDI carriers yet as the new | |
| * schedule is not yet applied so common slots don't change. The NDI | |
| * carrier will be updated once the driver notifies the new schedule is | |
| * applied. | |
| */ | |
| if (sched_cfg->deferred) | |
| return 0; | |
| ieee80211_nan_update_all_ndi_carriers(sdata->local); | |
| bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS); | |
| return 0; | |
| err: | |
| /* Remove newly added channels */ | |
| for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) { | |
| struct cfg80211_chan_def *chan_def = | |
| &sched_cfg->channels[i].chanreq.oper; | |
| if (!chan_def->chan) | |
| continue; | |
| if (!cfg80211_chandef_identical(&backup_sched.channels[i].chanreq.oper, | |
| chan_def)) | |
| ieee80211_nan_remove_channel(sdata, | |
| &sched_cfg->channels[i]); | |
| } | |
| /* Re-add all backed up channels */ | |
| for (int i = 0; i < ARRAY_SIZE(backup_sched.channels); i++) { | |
| struct ieee80211_nan_channel *chan = &sched_cfg->channels[i]; | |
| *chan = backup_sched.channels[i]; | |
| /* | |
| * For deferred update, no channels were removed and the channel | |
| * context didn't change, so nothing else to do. | |
| */ | |
| if (!chan->chanctx_conf || sched->deferred) | |
| continue; | |
| if (test_bit(i, sdata->u.nan.removed_channels)) { | |
| /* Clear the stale chanctx pointer */ | |
| chan->chanctx_conf = NULL; | |
| /* | |
| * We removed the newly added channels so we don't lack | |
| * resources. So the only reason that this would fail | |
| * is a FW error which we ignore. Therefore, this | |
| * should never fail. | |
| */ | |
| WARN_ON(ieee80211_nan_use_chanctx(sdata, chan, true)); | |
| } else { | |
| struct ieee80211_chanctx_conf *conf = chan->chanctx_conf; | |
| /* FIXME: detect no-op? */ | |
| /* Channel was not removed but may have been updated */ | |
| ieee80211_recalc_smps_chanctx(sdata->local, | |
| container_of(conf, | |
| struct ieee80211_chanctx, | |
| conf)); | |
| } | |
| } | |
| memcpy(sched_cfg->schedule, backup_sched.schedule, | |
| sizeof(backup_sched.schedule)); | |
| memcpy(sched_cfg->avail_blob, backup_sched.avail_blob, | |
| sizeof(backup_sched.avail_blob)); | |
| sched_cfg->avail_blob_len = backup_sched.avail_blob_len; | |
| sched_cfg->deferred = false; | |
| bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS); | |
| drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED); | |
| ieee80211_nan_update_all_ndi_carriers(sdata->local); | |
| return ret; | |
| } | |
| void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif) | |
| { | |
| struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); | |
| struct ieee80211_nan_sched_cfg *sched_cfg = &vif->cfg.nan_sched; | |
| unsigned int i; | |
| lockdep_assert_wiphy(sdata->local->hw.wiphy); | |
| if (WARN_ON(!sched_cfg->deferred)) | |
| return; | |
| ieee80211_nan_update_all_ndi_carriers(sdata->local); | |
| /* | |
| * Clear the deferred flag before removing channels. Removing channels | |
| * will trigger another schedule update to the driver, and there is no | |
| * need for this update to be deferred since removed channels are not | |
| * part of the schedule anymore, so no need to notify peers about | |
| * removing them. | |
| */ | |
| sched_cfg->deferred = false; | |
| for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) { | |
| struct ieee80211_nan_channel *chan = &sched_cfg->channels[i]; | |
| struct ieee80211_chanctx_conf *conf = chan->chanctx_conf; | |
| if (!chan->chanreq.oper.chan) | |
| continue; | |
| if (test_bit(i, sdata->u.nan.removed_channels)) | |
| ieee80211_nan_remove_channel(sdata, chan); | |
| else if (conf) | |
| /* | |
| * We might have called this already for some channels, | |
| * but this knows to handle a no-op. | |
| */ | |
| ieee80211_recalc_smps_chanctx(sdata->local, | |
| container_of(conf, | |
| struct ieee80211_chanctx, | |
| conf)); | |
| } | |
| bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS); | |
| cfg80211_nan_sched_update_done(ieee80211_vif_to_wdev(vif), true, | |
| GFP_KERNEL); | |
| } | |
| EXPORT_SYMBOL(ieee80211_nan_sched_update_done); | |
| void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched) | |
| { | |
| if (!sched) | |
| return; | |
| kfree(sched->init_ulw); | |
| kfree(sched); | |
| } | |
| static int | |
| ieee80211_nan_init_peer_channel(struct ieee80211_sub_if_data *sdata, | |
| const struct sta_info *sta, | |
| const struct cfg80211_nan_channel *cfg_chan, | |
| struct ieee80211_nan_channel *new_chan) | |
| { | |
| struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched; | |
| /* Find compatible local channel */ | |
| for (int j = 0; j < ARRAY_SIZE(sched_cfg->channels); j++) { | |
| struct ieee80211_nan_channel *local_chan = | |
| &sched_cfg->channels[j]; | |
| const struct cfg80211_chan_def *compat; | |
| if (!local_chan->chanreq.oper.chan) | |
| continue; | |
| compat = cfg80211_chandef_compatible(&local_chan->chanreq.oper, | |
| &cfg_chan->chandef); | |
| if (!compat) | |
| continue; | |
| /* compat is the wider chandef, and we want the narrower one */ | |
| new_chan->chanreq.oper = compat == &local_chan->chanreq.oper ? | |
| cfg_chan->chandef : local_chan->chanreq.oper; | |
| new_chan->needed_rx_chains = min(local_chan->needed_rx_chains, | |
| cfg_chan->rx_nss); | |
| new_chan->chanctx_conf = local_chan->chanctx_conf; | |
| break; | |
| } | |
| /* | |
| * nl80211 already validated that each peer channel is compatible | |
| * with at least one local channel, so this should never happen. | |
| */ | |
| if (WARN_ON(!new_chan->chanreq.oper.chan)) | |
| return -EINVAL; | |
| memcpy(new_chan->channel_entry, cfg_chan->channel_entry, | |
| sizeof(new_chan->channel_entry)); | |
| return 0; | |
| } | |
| static void | |
| ieee80211_nan_init_peer_map(struct ieee80211_nan_peer_sched *peer_sched, | |
| const struct cfg80211_nan_peer_map *cfg_map, | |
| struct ieee80211_nan_peer_map *new_map) | |
| { | |
| new_map->map_id = cfg_map->map_id; | |
| if (new_map->map_id == CFG80211_NAN_INVALID_MAP_ID) | |
| return; | |
| /* Set up the slots array */ | |
| for (int slot = 0; slot < ARRAY_SIZE(new_map->slots); slot++) { | |
| u8 chan_idx = cfg_map->schedule[slot]; | |
| if (chan_idx < peer_sched->n_channels) | |
| new_map->slots[slot] = &peer_sched->channels[chan_idx]; | |
| } | |
| } | |
| /* | |
| * Check if the local schedule and a peer schedule have at least one common | |
| * slot - a slot where both schedules are active on compatible channels. | |
| */ | |
| static bool | |
| ieee80211_nan_has_common_slots(struct ieee80211_sub_if_data *sdata, | |
| struct ieee80211_nan_peer_sched *peer_sched) | |
| { | |
| for (int slot = 0; slot < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; slot++) { | |
| struct ieee80211_nan_channel *local_chan = | |
| sdata->vif.cfg.nan_sched.schedule[slot]; | |
| if (!local_chan || !local_chan->chanctx_conf) | |
| continue; | |
| /* Check all peer maps for this slot */ | |
| for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) { | |
| struct ieee80211_nan_peer_map *map = &peer_sched->maps[m]; | |
| struct ieee80211_nan_channel *peer_chan; | |
| if (map->map_id == CFG80211_NAN_INVALID_MAP_ID) | |
| continue; | |
| peer_chan = map->slots[slot]; | |
| if (!peer_chan) | |
| continue; | |
| if (local_chan->chanctx_conf == peer_chan->chanctx_conf) | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| void ieee80211_nan_update_ndi_carrier(struct ieee80211_sub_if_data *ndi_sdata) | |
| { | |
| struct ieee80211_local *local = ndi_sdata->local; | |
| struct ieee80211_sub_if_data *nmi_sdata; | |
| struct sta_info *sta; | |
| lockdep_assert_wiphy(local->hw.wiphy); | |
| if (WARN_ON(ndi_sdata->vif.type != NL80211_IFTYPE_NAN_DATA || | |
| !ndi_sdata->dev) || !ieee80211_sdata_running(ndi_sdata)) | |
| return; | |
| nmi_sdata = wiphy_dereference(local->hw.wiphy, ndi_sdata->u.nan_data.nmi); | |
| if (WARN_ON(!nmi_sdata)) | |
| return; | |
| list_for_each_entry(sta, &local->sta_list, list) { | |
| struct ieee80211_sta *nmi_sta; | |
| if (sta->sdata != ndi_sdata || | |
| !test_sta_flag(sta, WLAN_STA_AUTHORIZED)) | |
| continue; | |
| nmi_sta = wiphy_dereference(local->hw.wiphy, sta->sta.nmi); | |
| if (WARN_ON(!nmi_sta) || !nmi_sta->nan_sched) | |
| continue; | |
| if (ieee80211_nan_has_common_slots(nmi_sdata, nmi_sta->nan_sched)) { | |
| netif_carrier_on(ndi_sdata->dev); | |
| return; | |
| } | |
| } | |
| netif_carrier_off(ndi_sdata->dev); | |
| } | |
| static void | |
| ieee80211_nan_update_peer_ndis_carrier(struct ieee80211_local *local, | |
| struct sta_info *nmi_sta) | |
| { | |
| struct sta_info *sta; | |
| lockdep_assert_wiphy(local->hw.wiphy); | |
| list_for_each_entry(sta, &local->sta_list, list) { | |
| if (rcu_access_pointer(sta->sta.nmi) == &nmi_sta->sta) | |
| ieee80211_nan_update_ndi_carrier(sta->sdata); | |
| } | |
| } | |
| int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata, | |
| struct cfg80211_nan_peer_sched *sched) | |
| { | |
| struct ieee80211_nan_peer_sched *new_sched, *old_sched, *to_free; | |
| struct sta_info *sta; | |
| int ret; | |
| lockdep_assert_wiphy(sdata->local->hw.wiphy); | |
| if (!sdata->u.nan.started) | |
| return -EINVAL; | |
| sta = sta_info_get(sdata, sched->peer_addr); | |
| if (!sta) | |
| return -ENOENT; | |
| new_sched = kzalloc(struct_size(new_sched, channels, sched->n_channels), | |
| GFP_KERNEL); | |
| if (!new_sched) | |
| return -ENOMEM; | |
| to_free = new_sched; | |
| new_sched->seq_id = sched->seq_id; | |
| new_sched->committed_dw = sched->committed_dw; | |
| new_sched->max_chan_switch = sched->max_chan_switch; | |
| new_sched->n_channels = sched->n_channels; | |
| if (sched->ulw_size && sched->init_ulw) { | |
| new_sched->init_ulw = kmemdup(sched->init_ulw, sched->ulw_size, | |
| GFP_KERNEL); | |
| if (!new_sched->init_ulw) { | |
| ret = -ENOMEM; | |
| goto out; | |
| } | |
| new_sched->ulw_size = sched->ulw_size; | |
| } | |
| for (int i = 0; i < sched->n_channels; i++) { | |
| ret = ieee80211_nan_init_peer_channel(sdata, sta, | |
| &sched->nan_channels[i], | |
| &new_sched->channels[i]); | |
| if (ret) | |
| goto out; | |
| } | |
| for (int m = 0; m < ARRAY_SIZE(sched->maps); m++) | |
| ieee80211_nan_init_peer_map(new_sched, &sched->maps[m], | |
| &new_sched->maps[m]); | |
| /* Install the new schedule before calling the driver */ | |
| old_sched = sta->sta.nan_sched; | |
| sta->sta.nan_sched = new_sched; | |
| ret = drv_nan_peer_sched_changed(sdata->local, sdata, sta); | |
| if (ret) { | |
| /* Revert to old schedule */ | |
| sta->sta.nan_sched = old_sched; | |
| goto out; | |
| } | |
| ieee80211_nan_update_peer_ndis_carrier(sdata->local, sta); | |
| /* Success - free old schedule */ | |
| to_free = old_sched; | |
| ret = 0; | |
| out: | |
| ieee80211_nan_free_peer_sched(to_free); | |
| return ret; | |
| } | |
| 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); | |
| } | |
| struct ieee80211_nan_channel * | |
| ieee80211_nan_find_evac_chan(struct ieee80211_local *local, | |
| struct ieee80211_sub_if_data *sdata, | |
| struct ieee80211_chanctx *ctx) | |
| { | |
| struct ieee80211_nan_sched_cfg *sched_cfg; | |
| struct ieee80211_nan_channel *evac_chan = NULL; | |
| int min_slot_count = INT_MAX; | |
| int usable_channels = 0; | |
| lockdep_assert_wiphy(local->hw.wiphy); | |
| if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_NAN)) | |
| return NULL; | |
| 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 NULL; | |
| /* NAN needs at least one remaining usable channel after evacuation */ | |
| if (usable_channels < 2) | |
| return NULL; | |
| return evac_chan; | |
| } | |
| bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw, | |
| struct ieee80211_chanctx_conf *conf) | |
| { | |
| struct ieee80211_local *local = hw_to_local(hw); | |
| struct ieee80211_sub_if_data *sdata = | |
| ieee80211_find_nan_sdata(local); | |
| struct ieee80211_nan_channel *evac_chan; | |
| struct ieee80211_chanctx *ctx = NULL; | |
| lockdep_assert_wiphy(local->hw.wiphy); | |
| if (!sdata) | |
| return false; | |
| if (conf) | |
| ctx = container_of(conf, struct ieee80211_chanctx, conf); | |
| evac_chan = ieee80211_nan_find_evac_chan(local, sdata, ctx); | |
| if (!evac_chan) | |
| return false; | |
| ieee80211_nan_evacuate_channel(sdata, evac_chan); | |
| return true; | |
| } | |
| EXPORT_SYMBOL(ieee80211_nan_try_evacuate); |