| // 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]); |
| |
| 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; |
| } |