* @BSS_CHANGED_MLD_VALID_LINKS: MLD valid links status changed.
* @BSS_CHANGED_MLD_TTLM: negotiated TID to link mapping was changed
* @BSS_CHANGED_TPE: transmit power envelope changed
+ * @BSS_CHANGED_NAN_LOCAL_SCHED: NAN local schedule changed (NAN mode only)
*/
enum ieee80211_bss_change {
BSS_CHANGED_ASSOC = 1<<0,
BSS_CHANGED_MLD_VALID_LINKS = BIT_ULL(33),
BSS_CHANGED_MLD_TTLM = BIT_ULL(34),
BSS_CHANGED_TPE = BIT_ULL(35),
+ BSS_CHANGED_NAN_LOCAL_SCHED = BIT_ULL(36),
/* when adding here, make sure to change ieee80211_reconfig */
};
u8 s1g_long_beacon_period;
};
+#define IEEE80211_NAN_MAX_CHANNELS 3
+
+/**
+ * struct ieee80211_nan_channel - NAN channel information
+ *
+ * @chanreq: channel request for this NAN channel. Even though this chanreq::ap
+ * is irrelevant for NAN, still store it for convenience - some functions
+ * require it as an argument.
+ * @needed_rx_chains: number of RX chains needed for this NAN channel
+ * @chanctx_conf: chanctx_conf assigned to this NAN channel. Will be %NULL
+ * if the channel is ULWed.
+ * @channel_entry: the Channel Entry blob as defined in Wi-Fi Aware
+ * (TM) 4.0 specification Table 100 (Channel Entry format for the NAN
+ * Availability attribute).
+ */
+struct ieee80211_nan_channel {
+ struct ieee80211_chan_req chanreq;
+ u8 needed_rx_chains;
+ struct ieee80211_chanctx_conf *chanctx_conf;
+ u8 channel_entry[6];
+};
+
/**
* enum mac80211_tx_info_flags - flags to describe transmission information/status
*
IEEE80211_OFFLOAD_DECAP_ENABLED = BIT(2),
};
+#define IEEE80211_NAN_AVAIL_BLOB_MAX_LEN 54
+
/**
* struct ieee80211_eml_params - EHT Operating mode notification parameters
*
u8 emlmr_mcs_map_bw[9];
};
+/**
+ * struct ieee80211_nan_sched_cfg - NAN schedule configuration
+ * @channels: array of NAN channels. A channel entry is in use if
+ * channels[i].chanreq.oper.chan is not NULL.
+ * @schedule: NAN local schedule - mapping of each 16TU time slot to
+ * the NAN channel on which the radio will operate. NULL if unscheduled.
+ * @avail_blob: NAN Availability attribute blob.
+ * @avail_blob_len: length of the @avail_blob in bytes.
+ * @deferred: indicates that the driver should notify peers before applying the
+ * new NAN schedule, and apply the new schedule the second NAN Slot
+ * boundary after it notified the peers, as defined in Wi-Fi Aware (TM) 4.0
+ * specification, section 5.2.2.
+ * The driver must call ieee80211_nan_sched_update_done() after the
+ * schedule has been applied.
+ * If a HW restart happened while a deferred schedule update was pending,
+ * mac80211 will reconfigure the deferred schedule (and wait for the driver
+ * to notify that the schedule has been applied).
+ */
+struct ieee80211_nan_sched_cfg {
+ struct ieee80211_nan_channel channels[IEEE80211_NAN_MAX_CHANNELS];
+ struct ieee80211_nan_channel *schedule[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+ u8 avail_blob[IEEE80211_NAN_AVAIL_BLOB_MAX_LEN];
+ u16 avail_blob_len;
+ bool deferred;
+};
+
/**
* struct ieee80211_vif_cfg - interface configuration
* @assoc: association status
* your driver/device needs to do.
* @ap_addr: AP MLD address, or BSSID for non-MLO connections
* (station mode only)
+ * @nan_sched: NAN schedule parameters. &struct ieee80211_nan_sched_cfg
*/
struct ieee80211_vif_cfg {
/* association related data */
bool s1g;
bool idle;
u8 ap_addr[ETH_ALEN] __aligned(2);
+ /* Protected by the wiphy mutex */
+ struct ieee80211_nan_sched_cfg nan_sched;
};
#define IEEE80211_TTLM_NUM_TIDS 8
struct cfg80211_nan_match_params *match,
gfp_t gfp);
+/**
+ * ieee80211_nan_sched_update_done - notify that NAN schedule update is done
+ *
+ * This function is called by the driver to notify mac80211 that the NAN
+ * schedule update has been applied.
+ * Must be called with wiphy mutex held. May sleep.
+ *
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ */
+void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif);
+
/**
* ieee80211_calc_rx_airtime - calculate estimated transmission airtime for RX.
*
tdls.o \
ocb.o \
airtime.o \
- eht.o uhr.o
+ eht.o uhr.o nan.o
mac80211-$(CONFIG_MAC80211_LEDS) += led.o
mac80211-$(CONFIG_MAC80211_DEBUGFS) += \
return ieee80211_mgd_set_epcs(sdata, enable);
}
+static int
+ieee80211_set_local_nan_sched(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_local_sched *sched)
+{
+ struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+
+ lockdep_assert_wiphy(wiphy);
+
+ return ieee80211_nan_set_local_sched(sdata, sched);
+}
+
const struct cfg80211_ops mac80211_config_ops = {
.add_virtual_intf = ieee80211_add_iface,
.del_virtual_intf = ieee80211_del_iface,
.get_radio_mask = ieee80211_get_radio_mask,
.assoc_ml_reconf = ieee80211_assoc_ml_reconf,
.set_epcs = ieee80211_set_epcs,
+ .nan_set_local_sched = ieee80211_set_local_nan_sched,
};
struct ieee80211_chan_req *chanreq;
struct ieee80211_sub_if_data *sdata;
struct ieee80211_link_data *link;
+ struct ieee80211_nan_channel *nan_channel;
+ int nan_channel_next_idx;
enum nl80211_iftype iftype;
bool reserved, radar_required, done;
enum {
CHANCTX_ITER_ASSIGNED,
};
-static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
- struct ieee80211_chanctx *ctx,
- struct ieee80211_chanctx_user_iter *iter,
- enum ieee80211_chanctx_iter_type type,
- bool start)
+static bool
+ieee80211_chanctx_user_iter_next_nan_channel(struct ieee80211_chanctx *ctx,
+ struct ieee80211_chanctx_user_iter *iter)
{
- lockdep_assert_wiphy(local->hw.wiphy);
+ /* Start from the next index after current position */
+ for (int i = iter->nan_channel_next_idx;
+ i < ARRAY_SIZE(iter->sdata->vif.cfg.nan_sched.channels); i++) {
+ struct ieee80211_nan_channel *nan_channel =
+ &iter->sdata->vif.cfg.nan_sched.channels[i];
- if (start) {
- memset(iter, 0, sizeof(*iter));
- goto next_interface;
+ if (!nan_channel->chanreq.oper.chan)
+ continue;
+
+ if (nan_channel->chanctx_conf != &ctx->conf)
+ continue;
+
+ iter->nan_channel = nan_channel;
+ iter->nan_channel_next_idx = i + 1;
+ iter->chanreq = &nan_channel->chanreq;
+ iter->link = NULL;
+ iter->reserved = false;
+ iter->radar_required = false;
+ return true;
}
+ return false;
+}
-next_link:
+static bool
+ieee80211_chanctx_user_iter_next_link(struct ieee80211_chanctx *ctx,
+ struct ieee80211_chanctx_user_iter *iter,
+ enum ieee80211_chanctx_iter_type type)
+{
for (int link_id = iter->link ? iter->link->link_id : 0;
link_id < ARRAY_SIZE(iter->sdata->link);
link_id++) {
iter->reserved = false;
iter->radar_required = link->radar_required;
iter->chanreq = &link->conf->chanreq;
- return;
+ return true;
}
fallthrough;
case CHANCTX_ITER_POS_RESERVED:
link->reserved_radar_required;
iter->chanreq = &link->reserved;
- return;
+ return true;
}
fallthrough;
case CHANCTX_ITER_POS_DONE:
continue;
}
}
+ return false;
+}
+
+static void
+ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
+ struct ieee80211_chanctx *ctx,
+ struct ieee80211_chanctx_user_iter *iter,
+ enum ieee80211_chanctx_iter_type type,
+ bool start)
+{
+ bool found;
+
+ lockdep_assert_wiphy(local->hw.wiphy);
+
+ if (start) {
+ memset(iter, 0, sizeof(*iter));
+ goto next_interface;
+ }
+
+next_user:
+ if (iter->iftype == NL80211_IFTYPE_NAN)
+ found = ieee80211_chanctx_user_iter_next_nan_channel(ctx, iter);
+ else
+ found = ieee80211_chanctx_user_iter_next_link(ctx, iter, type);
+
+ if (found)
+ return;
next_interface:
/* next (or first) interface */
if (iter->sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
continue;
+ /* NAN channels don't reserve channel context */
+ if (iter->sdata->vif.type == NL80211_IFTYPE_NAN &&
+ type == CHANCTX_ITER_RESERVED)
+ continue;
+
+ iter->nan_channel = NULL;
iter->link = NULL;
- iter->per_link = CHANCTX_ITER_POS_ASSIGNED;
iter->iftype = iter->sdata->vif.type;
- goto next_link;
+ iter->chanreq = NULL;
+ iter->per_link = CHANCTX_ITER_POS_ASSIGNED;
+ iter->nan_channel_next_idx = 0;
+ goto next_user;
}
iter->done = true;
CHANCTX_ITER_ALL, \
false))
-static int ieee80211_chanctx_num_assigned(struct ieee80211_local *local,
- struct ieee80211_chanctx *ctx)
+int ieee80211_chanctx_num_assigned(struct ieee80211_local *local,
+ struct ieee80211_chanctx *ctx)
{
struct ieee80211_chanctx_user_iter iter;
int num = 0;
lockdep_assert_wiphy(local->hw.wiphy);
for_each_chanctx_user_assigned(local, ctx, &iter) {
- if (iter.link->reserved_chanctx)
+ if (iter.link && iter.link->reserved_chanctx)
continue;
comp_def = ieee80211_chanreq_compatible(iter.chanreq,
case NL80211_IFTYPE_AP_VLAN:
return ieee80211_get_max_required_bw(link);
case NL80211_IFTYPE_P2P_DEVICE:
- case NL80211_IFTYPE_NAN:
break;
case NL80211_IFTYPE_MONITOR:
WARN_ON_ONCE(!ieee80211_hw_check(&local->hw,
case NUM_NL80211_IFTYPES:
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
+ case NL80211_IFTYPE_NAN:
case NL80211_IFTYPE_NAN_DATA:
WARN_ON_ONCE(1);
break;
return NL80211_CHAN_WIDTH_20_NOHT;
}
+static enum nl80211_chan_width
+ieee80211_get_width_of_chanctx_user(struct ieee80211_chanctx_user_iter *iter)
+{
+ if (iter->link)
+ return ieee80211_get_width_of_link(iter->link);
+
+ if (WARN_ON_ONCE(!iter->nan_channel || iter->reserved))
+ return NL80211_CHAN_WIDTH_20_NOHT;
+
+ return iter->nan_channel->chanreq.oper.width;
+}
+
static enum nl80211_chan_width
ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
struct ieee80211_chanctx *ctx,
/* When this is true we only care about the reserving links */
if (check_reserved) {
for_each_chanctx_user_reserved(local, ctx, &iter) {
- width = ieee80211_get_width_of_link(iter.link);
+ width = ieee80211_get_width_of_chanctx_user(&iter);
max_bw = max(max_bw, width);
}
goto check_monitor;
/* Consider all assigned links */
for_each_chanctx_user_assigned(local, ctx, &iter) {
- width = ieee80211_get_width_of_link(iter.link);
+ width = ieee80211_get_width_of_chanctx_user(&iter);
max_bw = max(max_bw, width);
}
kfree(ctx);
return ERR_PTR(err);
}
- /* We ignored a driver error, see _ieee80211_set_active_links */
+ /*
+ * We ignored a driver error, see _ieee80211_set_active_links and/or
+ * ieee80211_nan_set_local_sched
+ */
WARN_ON_ONCE(err && !local->in_reconfig);
list_add_rcu(&ctx->list, &local->chanctx_list);
ieee80211_remove_wbrf(local, &ctx->conf.def);
}
-static void ieee80211_free_chanctx(struct ieee80211_local *local,
- struct ieee80211_chanctx *ctx,
- bool skip_idle_recalc)
+void ieee80211_free_chanctx(struct ieee80211_local *local,
+ struct ieee80211_chanctx *ctx,
+ bool skip_idle_recalc)
{
lockdep_assert_wiphy(local->hw.wiphy);
case NL80211_IFTYPE_ADHOC:
case NL80211_IFTYPE_MESH_POINT:
case NL80211_IFTYPE_OCB:
+ case NL80211_IFTYPE_NAN:
break;
default:
continue;
break;
}
+ if (iter.nan_channel) {
+ rx_chains_dynamic = rx_chains_static =
+ iter.nan_channel->needed_rx_chains;
+ break;
+ }
+
+ if (!iter.link)
+ continue;
+
switch (iter.link->smps_mode) {
default:
WARN_ONCE(1, "Invalid SMPS mode %d\n",
for_each_chanctx_user_assigned(local, ctx->replace_ctx, &iter) {
n_assigned++;
- if (iter.link->reserved_chanctx) {
+ if (iter.link && iter.link->reserved_chanctx) {
n_reserved++;
if (iter.link->reserved_ready)
n_ready++;
ieee80211_vif_use_reserved_switch(local);
}
-static struct ieee80211_chanctx *
+struct ieee80211_chanctx *
ieee80211_find_or_create_chanctx(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_chan_req *chanreq,
enum ieee80211_chanctx_mode mode,
* @de: Discovery Engine state (only valid if !WIPHY_NAN_FLAGS_USERSPACE_DE)
* @de.func_lock: lock for @de.function_inst_ids
* @de.function_inst_ids: a bitmap of available instance_id's
+ * @removed_channels: bitmap of channels that should be removed from the NAN
+ * schedule once the deferred schedule update is completed.
*/
struct ieee80211_if_nan {
struct cfg80211_nan_conf conf;
spinlock_t func_lock;
struct idr function_inst_ids;
} de;
+
+ DECLARE_BITMAP(removed_channels, IEEE80211_NAN_MAX_CHANNELS);
};
struct ieee80211_link_data_managed {
int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata,
u64 *changed);
+/* NAN code */
+int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
+ struct cfg80211_nan_local_sched *sched);
+
/* scan/BSS handling */
void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work);
int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata,
u32 ieee80211_get_radio_mask(struct wiphy *wiphy, struct net_device *dev);
void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local,
struct ieee80211_chanctx *ctx);
-
+struct ieee80211_chanctx *
+ieee80211_find_or_create_chanctx(struct ieee80211_sub_if_data *sdata,
+ const struct ieee80211_chan_req *chanreq,
+ enum ieee80211_chanctx_mode mode,
+ bool assign_on_failure,
+ bool *reused_ctx);
+void ieee80211_free_chanctx(struct ieee80211_local *local,
+ struct ieee80211_chanctx *ctx,
+ bool skip_idle_recalc);
+int ieee80211_chanctx_num_assigned(struct ieee80211_local *local,
+ struct ieee80211_chanctx *ctx);
/* TDLS */
int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
const u8 *peer, int link_id,
ieee80211_vif_clear_links(sdata);
ieee80211_link_stop(&sdata->deflink);
+
+ if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+ struct ieee80211_nan_sched_cfg *nan_sched =
+ &sdata->vif.cfg.nan_sched;
+
+ for (int i = 0; i < ARRAY_SIZE(nan_sched->channels); i++)
+ WARN_ON(nan_sched->channels[i].chanreq.oper.chan);
+ }
}
static void ieee80211_uninit(struct net_device *dev)
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * NAN mode implementation
+ * Copyright(c) 2025 Intel Corporation
+ */
+#include <net/mac80211.h>
+
+#include "ieee80211_i.h"
+#include "driver-ops.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_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;
+
+ 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 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;
+
+ 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);
+ 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;
+
+ /*
+ * 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);
}
}
-static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
+static int
+ieee80211_reconfig_nan_offload_de(struct ieee80211_sub_if_data *sdata)
{
struct cfg80211_nan_func *func, **funcs;
int res, id, i = 0;
- res = drv_start_nan(sdata->local, sdata,
- &sdata->u.nan.conf);
- if (WARN_ON(res))
- return res;
-
- if (sdata->local->hw.wiphy->nan_capa.flags &
- WIPHY_NAN_FLAGS_USERSPACE_DE)
- return 0;
-
funcs = kzalloc_objs(*funcs, sdata->local->hw.max_nan_de_entries + 1);
if (!funcs)
return -ENOMEM;
}
kfree(funcs);
+ return res;
+}
+
+static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
+{
+ int res;
+
+ res = drv_start_nan(sdata->local, sdata,
+ &sdata->u.nan.conf);
+ if (WARN_ON(res))
+ return res;
+
+ if (!(sdata->local->hw.wiphy->nan_capa.flags & WIPHY_NAN_FLAGS_USERSPACE_DE))
+ return ieee80211_reconfig_nan_offload_de(sdata);
+
+ drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
return 0;
}