wifi: mac80211: add NAN peer schedule support
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>master
parent
27e9b326b6
commit
840492bf33
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) */
|
||||
|
|
|
|||
Loading…
Reference in New Issue