]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: mac80211: add NAN peer schedule support
authorMiri Korenblit <miriam.rachel.korenblit@intel.com>
Thu, 26 Mar 2026 10:14:39 +0000 (12:14 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 7 Apr 2026 13:36:03 +0000 (15:36 +0200)
Peer schedules specify which channels the peer is available on and when.
Add support for configuring peer NAN schedules:
- build and store the schedule and maps
- for each channel, make sure that it fits into the capabilities, and
  take the minimum between it and the local compatible nan channel.
- configure the driver

Note that the removal of a peer schedule should be done by the driver
upon NMI station removal.

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260326121156.185ff2283fa6.I0345eb665be8ccf4a77eb1aca9a421eb8d2432e2@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/mac80211.h
net/mac80211/cfg.c
net/mac80211/driver-ops.h
net/mac80211/ieee80211_i.h
net/mac80211/nan.c
net/mac80211/sta_info.c
net/mac80211/trace.h
net/mac80211/util.c

index b190d9035182d985cb8776aa26783fb01717fee6..40cb20d9309c0e29e82a78a8301cc3a90d2cde5c 100644 (file)
@@ -877,8 +877,11 @@ struct ieee80211_bss_conf {
  *     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.
+ * @chanctx_conf: chanctx_conf assigned to this NAN channel.
+ *     If a local channel is being ULWed (because we needed this chanctx for
+ *     something else), the local NAN channel that used this chanctx,
+ *     will have this pointer set to %NULL.
+ *     A peer NAN channel should never have this pointer set to %NULL.
  * @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).
@@ -890,6 +893,49 @@ struct ieee80211_nan_channel {
        u8 channel_entry[6];
 };
 
+/**
+ * struct ieee80211_nan_peer_map - NAN peer schedule map
+ *
+ * This stores a single map from a peer's schedule. Each peer can have
+ * multiple maps.
+ *
+ * @map_id: the map ID from the peer schedule, %CFG80211_NAN_INVALID_MAP_ID
+ *     if unused
+ * @slots: mapping of time slots to channel configurations in the schedule's
+ *     channels array
+ */
+struct ieee80211_nan_peer_map {
+       u8 map_id;
+       struct ieee80211_nan_channel *slots[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+};
+
+/**
+ * struct ieee80211_nan_peer_sched - NAN peer schedule
+ *
+ * This stores the complete schedule from a peer. Contains peer-level
+ * parameters and an array of schedule maps.
+ *
+ * @seq_id: the sequence ID from the peer schedule
+ * @committed_dw: committed DW as published by the peer
+ * @max_chan_switch: maximum channel switch time in microseconds
+ * @init_ulw: initial ULWs as published by the peer (copied)
+ * @ulw_size: number of bytes in @init_ulw
+ * @maps: array of peer schedule maps. Invalid slots have map_id set to
+ *     %CFG80211_NAN_INVALID_MAP_ID.
+ * @n_channels: number of valid channel entries in @channels
+ * @channels: flexible array of negotiated peer channels for this schedule
+ */
+struct ieee80211_nan_peer_sched {
+       u8 seq_id;
+       u16 committed_dw;
+       u16 max_chan_switch;
+       const u8 *init_ulw;
+       u16 ulw_size;
+       struct ieee80211_nan_peer_map maps[CFG80211_NAN_MAX_PEER_MAPS];
+       u8 n_channels;
+       struct ieee80211_nan_channel channels[] __counted_by(n_channels);
+};
+
 /**
  * enum mac80211_tx_info_flags - flags to describe transmission information/status
  *
@@ -2625,6 +2671,7 @@ struct ieee80211_link_sta {
  * @spp_amsdu: indicates whether the STA uses SPP A-MSDU or not.
  * @epp_peer: indicates that the peer is an EPP peer.
  * @nmi: For NDI stations, pointer to the NMI station of the peer.
+ * @nan_sched: NAN peer schedule for this station. Valid only for NMI stations.
  */
 struct ieee80211_sta {
        u8 addr[ETH_ALEN] __aligned(2);
@@ -2655,6 +2702,9 @@ struct ieee80211_sta {
 
        struct ieee80211_sta __rcu *nmi;
 
+       /* should only be accessed with the wiphy mutex held */
+       struct ieee80211_nan_peer_sched *nan_sched;
+
        /* must be last */
        u8 drv_priv[] __aligned(sizeof(void *));
 };
@@ -4556,6 +4606,12 @@ struct ieee80211_prep_tx_info {
  * @del_nan_func: Remove a NAN function. The driver must call
  *     ieee80211_nan_func_terminated() with
  *     NL80211_NAN_FUNC_TERM_REASON_USER_REQUEST reason code upon removal.
+ * @nan_peer_sched_changed: Notifies the driver that the peer NAN schedule
+ *     has changed. The new schedule is available via sta->nan_sched.
+ *     Note that the channel_entry blob might not match the actual chandef
+ *     since the bandwidth of the chandef is the minimum of the local and peer
+ *     bandwidth. It is the driver responsibility to remove the peer schedule
+ *     when the NMI station is removed.
  * @can_aggregate_in_amsdu: Called in order to determine if HW supports
  *     aggregating two specific frames in the same A-MSDU. The relation
  *     between the skbs should be symmetric and transitive. Note that while
@@ -4961,6 +5017,8 @@ struct ieee80211_ops {
        void (*del_nan_func)(struct ieee80211_hw *hw,
                            struct ieee80211_vif *vif,
                            u8 instance_id);
+       int (*nan_peer_sched_changed)(struct ieee80211_hw *hw,
+                                     struct ieee80211_sta *sta);
        bool (*can_aggregate_in_amsdu)(struct ieee80211_hw *hw,
                                       struct sk_buff *head,
                                       struct sk_buff *skb);
index 607f3bd3f8f8c59b356d86bed87d6d7458a3de23..0c1439d31c93e800a61ca712e1c5294a93b6e182 100644 (file)
@@ -5689,6 +5689,18 @@ ieee80211_set_local_nan_sched(struct wiphy *wiphy,
        return ieee80211_nan_set_local_sched(sdata, sched);
 }
 
+static int
+ieee80211_set_peer_nan_sched(struct wiphy *wiphy,
+                            struct wireless_dev *wdev,
+                            struct cfg80211_nan_peer_sched *sched)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+
+       lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+       return ieee80211_nan_set_peer_sched(sdata, sched);
+}
+
 const struct cfg80211_ops mac80211_config_ops = {
        .add_virtual_intf = ieee80211_add_iface,
        .del_virtual_intf = ieee80211_del_iface,
@@ -5806,4 +5818,5 @@ const struct cfg80211_ops mac80211_config_ops = {
        .assoc_ml_reconf = ieee80211_assoc_ml_reconf,
        .set_epcs = ieee80211_set_epcs,
        .nan_set_local_sched = ieee80211_set_local_nan_sched,
+       .nan_set_peer_sched = ieee80211_set_peer_nan_sched,
 };
index 51bf3c7822a76007e6ec59fb70e97fa50d43632b..f1c0b87fddd5f9f006faf95a7b19b76eda80ddbe 100644 (file)
@@ -1793,4 +1793,25 @@ static inline int drv_set_eml_op_mode(struct ieee80211_sub_if_data *sdata,
        return ret;
 }
 
+static inline int
+drv_nan_peer_sched_changed(struct ieee80211_local *local,
+                          struct ieee80211_sub_if_data *sdata,
+                          struct sta_info *sta)
+{
+       int ret;
+
+       might_sleep();
+       lockdep_assert_wiphy(local->hw.wiphy);
+       check_sdata_in_driver(sdata);
+
+       if (!local->ops->nan_peer_sched_changed)
+               return -EOPNOTSUPP;
+
+       trace_drv_nan_peer_sched_changed(local, sdata, &sta->sta);
+       ret = local->ops->nan_peer_sched_changed(&local->hw, &sta->sta);
+       trace_drv_return_int(local, ret);
+
+       return ret;
+}
+
 #endif /* __MAC80211_DRIVER_OPS */
index e3a051beba6a307b70b005ee1abcadd74bbad561..23bf00472915014f29bf1598339644b0f9c9bcf3 100644 (file)
@@ -2042,6 +2042,9 @@ int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata,
 /* NAN code */
 int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
                                  struct cfg80211_nan_local_sched *sched);
+int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
+                                struct cfg80211_nan_peer_sched *sched);
+void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched);
 
 /* scan/BSS handling */
 void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work);
index 2fa55e9a9dab5dd052889a3d46efe5f762006b86..5e1f9bb7c49d8451bba700e8167171d3ded83901 100644 (file)
@@ -1,12 +1,13 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
  * NAN mode implementation
- * Copyright(c) 2025 Intel Corporation
+ * 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,
@@ -96,6 +97,82 @@ ieee80211_nan_use_chanctx(struct ieee80211_sub_if_data *sdata,
        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)
@@ -118,6 +195,10 @@ ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata,
 
        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 */
@@ -376,3 +457,146 @@ void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif)
                                       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];
+       }
+}
+
+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;
+       }
+
+       /* Success - free old schedule */
+       to_free = old_sched;
+       ret = 0;
+
+out:
+       ieee80211_nan_free_peer_sched(to_free);
+       return ret;
+}
index 580cd7a68beb59ad2dfc91407843f5b822d4b560..fb0cfd4d16d38994b964eb156ad3e2cd55b71051 100644 (file)
@@ -1309,6 +1309,10 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta)
                                continue;
                        sta_info_destroy_addr(sta_iter->sdata, sta_iter->addr);
                }
+
+               /* Free and clear the local peer schedule */
+               ieee80211_nan_free_peer_sched(sta->sta.nan_sched);
+               sta->sta.nan_sched = NULL;
        }
 
        /*
index e5968d754f8be985a555d65735290a57ca2ee8b1..71cf88039bd46387e83438ac89c3a11e9e87395a 100644 (file)
@@ -3366,6 +3366,37 @@ TRACE_EVENT(drv_set_eml_op_mode,
        )
 );
 
+TRACE_EVENT(drv_nan_peer_sched_changed,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata,
+                struct ieee80211_sta *sta),
+
+       TP_ARGS(local, sdata, sta),
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               VIF_ENTRY
+               STA_ENTRY
+               __array(u8, map_ids, CFG80211_NAN_MAX_PEER_MAPS)
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+               VIF_ASSIGN;
+               STA_ASSIGN;
+               for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++)
+                       __entry->map_ids[i] = sta->nan_sched ?
+                                             sta->nan_sched->maps[i].map_id :
+                                             0xff;
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT  VIF_PR_FMT  STA_PR_FMT
+               " map_ids=[%u, %u]",
+               LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG,
+               __entry->map_ids[0], __entry->map_ids[1]
+       )
+);
+
 #endif /* !__MAC80211_DRIVER_TRACE || TRACE_HEADER_MULTI_READ */
 
 #undef TRACE_INCLUDE_PATH
index a352f73a7ec4ec9528a729f2c820a7374d5ef2ac..b093bc203c815939257bd9285836be82767163bf 100644 (file)
@@ -1817,6 +1817,14 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
                        if (WARN_ON(res))
                                return res;
                }
+
+               /* Add peer schedules for NMI stations that have them */
+               if (!sta->sta.nan_sched)
+                       continue;
+
+               res = drv_nan_peer_sched_changed(local, sdata, sta);
+               if (WARN_ON(res))
+                       return res;
        }
 
        /* Add NDI stations (stations on NAN_DATA interfaces) */