* [PATCH wireless-next 01/15] wifi: mac80211: add a TXQ for management frames on NAN devices
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 02/15] wifi: ieee80211: add more NAN definitions Miri Korenblit
` (13 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg, Johannes Berg
From: Benjamin Berg <benjamin.berg@intel.com>
Currently there is no TXQ for non-data frames. Add a new txq_mgmt for
this purpose and create one of these on NAN devices. On NAN devices,
these frames may only be transmitted during the discovery window and it
is therefore helpful to schedule them using a queue.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 2 ++
net/mac80211/iface.c | 28 ++++++++++++++++++++++++++--
net/mac80211/tx.c | 20 ++++++++++++++++----
net/mac80211/util.c | 34 +++++++++++++++++++++++-----------
4 files changed, 67 insertions(+), 17 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 9cc482191ab9..2959736efdf3 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2074,6 +2074,7 @@ enum ieee80211_neg_ttlm_res {
* @drv_priv: data area for driver use, will always be aligned to
* sizeof(void \*).
* @txq: the multicast data TX queue
+ * @txq_mgmt: the mgmt frame TX queue, currently only exists for NAN devices
* @offload_flags: 802.3 -> 802.11 enapsulation offload flags, see
* &enum ieee80211_offload_flags.
*/
@@ -2092,6 +2093,7 @@ struct ieee80211_vif {
u8 hw_queue[IEEE80211_NUM_ACS];
struct ieee80211_txq *txq;
+ struct ieee80211_txq *txq_mgmt;
netdev_features_t netdev_features;
u32 driver_flags;
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 125897717a4c..7518dcbcdf1c 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -682,6 +682,10 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
if (sdata->vif.txq)
ieee80211_txq_purge(sdata->local, to_txq_info(sdata->vif.txq));
+ if (sdata->vif.txq_mgmt)
+ ieee80211_txq_purge(sdata->local,
+ to_txq_info(sdata->vif.txq_mgmt));
+
sdata->bss = NULL;
if (local->open_count == 0)
@@ -2223,10 +2227,16 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
lockdep_assert_wiphy(local->hw.wiphy);
if (type == NL80211_IFTYPE_P2P_DEVICE || type == NL80211_IFTYPE_NAN) {
+ int size = ALIGN(sizeof(*sdata) + local->hw.vif_data_size,
+ sizeof(void *));
struct wireless_dev *wdev;
+ int txq_size = 0;
+
+ if (type == NL80211_IFTYPE_NAN)
+ txq_size = sizeof(struct txq_info) +
+ local->hw.txq_data_size;
- sdata = kzalloc(sizeof(*sdata) + local->hw.vif_data_size,
- GFP_KERNEL);
+ sdata = kzalloc(size + txq_size, GFP_KERNEL);
if (!sdata)
return -ENOMEM;
wdev = &sdata->wdev;
@@ -2236,6 +2246,16 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
ieee80211_assign_perm_addr(local, wdev->address, type);
memcpy(sdata->vif.addr, wdev->address, ETH_ALEN);
ether_addr_copy(sdata->vif.bss_conf.addr, sdata->vif.addr);
+
+ /*
+ * Add a management TXQ for NAN devices which includes frames
+ * that will only be transmitted during discovery windows (DWs)
+ */
+ if (type == NL80211_IFTYPE_NAN) {
+ txqi = (struct txq_info *)((unsigned long)sdata + size);
+ ieee80211_txq_init(sdata, NULL, txqi,
+ IEEE80211_NUM_TIDS);
+ }
} else {
int size = ALIGN(sizeof(*sdata) + local->hw.vif_data_size,
sizeof(void *));
@@ -2386,6 +2406,10 @@ void ieee80211_if_remove(struct ieee80211_sub_if_data *sdata)
if (sdata->vif.txq)
ieee80211_txq_purge(sdata->local, to_txq_info(sdata->vif.txq));
+ if (sdata->vif.txq_mgmt)
+ ieee80211_txq_purge(sdata->local,
+ to_txq_info(sdata->vif.txq_mgmt));
+
synchronize_rcu();
cfg80211_unregister_wdev(&sdata->wdev);
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 3844c7fbb8a8..730c208c3bdf 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -1313,13 +1313,19 @@ static struct txq_info *ieee80211_get_txq(struct ieee80211_local *local,
unlikely(!ieee80211_is_data_present(hdr->frame_control))) {
if ((!ieee80211_is_mgmt(hdr->frame_control) ||
ieee80211_is_bufferable_mmpdu(skb) ||
- vif->type == NL80211_IFTYPE_STATION) &&
+ vif->type == NL80211_IFTYPE_STATION ||
+ vif->type == NL80211_IFTYPE_NAN ||
+ vif->type == NL80211_IFTYPE_NAN_DATA) &&
sta && sta->uploaded) {
/*
* This will be NULL if the driver didn't set the
* opt-in hardware flag.
*/
txq = sta->sta.txq[IEEE80211_NUM_TIDS];
+ } else if ((!ieee80211_is_mgmt(hdr->frame_control) ||
+ ieee80211_is_bufferable_mmpdu(skb)) &&
+ !sta) {
+ txq = vif->txq_mgmt;
}
} else if (sta) {
u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
@@ -1512,9 +1518,15 @@ void ieee80211_txq_init(struct ieee80211_sub_if_data *sdata,
txqi->txq.vif = &sdata->vif;
if (!sta) {
- sdata->vif.txq = &txqi->txq;
- txqi->txq.tid = 0;
- txqi->txq.ac = IEEE80211_AC_BE;
+ txqi->txq.tid = tid;
+
+ if (tid == IEEE80211_NUM_TIDS) {
+ sdata->vif.txq_mgmt = &txqi->txq;
+ txqi->txq.ac = IEEE80211_AC_VO;
+ } else {
+ sdata->vif.txq = &txqi->txq;
+ txqi->txq.ac = IEEE80211_AC_BE;
+ }
return;
}
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 8987a4504520..72e73f4f79c5 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -325,7 +325,7 @@ static void __ieee80211_wake_txqs(struct ieee80211_sub_if_data *sdata, int ac)
struct ieee80211_vif *vif = &sdata->vif;
struct fq *fq = &local->fq;
struct ps_data *ps = NULL;
- struct txq_info *txqi;
+ struct txq_info *txqi = NULL;
struct sta_info *sta;
int i;
@@ -344,37 +344,49 @@ static void __ieee80211_wake_txqs(struct ieee80211_sub_if_data *sdata, int ac)
for (i = 0; i < ARRAY_SIZE(sta->sta.txq); i++) {
struct ieee80211_txq *txq = sta->sta.txq[i];
+ struct txq_info *sta_txqi;
if (!txq)
continue;
- txqi = to_txq_info(txq);
+ sta_txqi = to_txq_info(txq);
if (ac != txq->ac)
continue;
if (!test_and_clear_bit(IEEE80211_TXQ_DIRTY,
- &txqi->flags))
+ &sta_txqi->flags))
continue;
spin_unlock(&fq->lock);
- drv_wake_tx_queue(local, txqi);
+ drv_wake_tx_queue(local, sta_txqi);
spin_lock(&fq->lock);
}
}
- if (!vif->txq)
- goto out;
+ if (vif->txq) {
+ txqi = to_txq_info(vif->txq);
- txqi = to_txq_info(vif->txq);
+ /* txq and txq_mgmt are mutually exclusive */
+ WARN_ON_ONCE(vif->txq_mgmt);
- if (!test_and_clear_bit(IEEE80211_TXQ_DIRTY, &txqi->flags) ||
- (ps && atomic_read(&ps->num_sta_ps)) || ac != vif->txq->ac)
- goto out;
+ if (!test_and_clear_bit(IEEE80211_TXQ_DIRTY, &txqi->flags) ||
+ (ps && atomic_read(&ps->num_sta_ps)) ||
+ ac != vif->txq->ac)
+ txqi = NULL;
+ } else if (vif->txq_mgmt) {
+ txqi = to_txq_info(vif->txq_mgmt);
+
+ if (!test_and_clear_bit(IEEE80211_TXQ_DIRTY, &txqi->flags) ||
+ ac != vif->txq_mgmt->ac)
+ txqi = NULL;
+ }
spin_unlock(&fq->lock);
- drv_wake_tx_queue(local, txqi);
+ if (txqi)
+ drv_wake_tx_queue(local, txqi);
+
local_bh_enable();
return;
out:
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 02/15] wifi: ieee80211: add more NAN definitions
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 01/15] wifi: mac80211: add a TXQ for management frames on NAN devices Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 03/15] wifi: mac80211: export ieee80211_calculate_rx_timestamp Miri Korenblit
` (12 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg, Johannes Berg
From: Benjamin Berg <benjamin.berg@intel.com>
These will be needed to implement NAN synchronization in mac80211_hwsim.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/linux/ieee80211-nan.h | 37 +++++++++++++++++++++++++++++++++++
include/linux/ieee80211.h | 1 +
2 files changed, 38 insertions(+)
diff --git a/include/linux/ieee80211-nan.h b/include/linux/ieee80211-nan.h
index ebf28ea651f9..455033955e54 100644
--- a/include/linux/ieee80211-nan.h
+++ b/include/linux/ieee80211-nan.h
@@ -37,4 +37,41 @@
#define NAN_DEV_CAPA_NDPE_SUPPORTED 0x08
#define NAN_DEV_CAPA_S3_SUPPORTED 0x10
+/* NAN attributes, as defined in Wi-Fi Aware (TM) specification 4.0 Table 42 */
+#define NAN_ATTR_MASTER_INDICATION 0x00
+#define NAN_ATTR_CLUSTER_INFO 0x01
+
+struct ieee80211_nan_attr {
+ u8 attr;
+ __le16 length;
+ u8 data[];
+} __packed;
+
+struct ieee80211_nan_master_indication {
+ u8 master_pref;
+ u8 random_factor;
+} __packed;
+
+struct ieee80211_nan_anchor_master_info {
+ union {
+ __le64 master_rank;
+ struct {
+ u8 master_addr[ETH_ALEN];
+ u8 random_factor;
+ u8 master_pref;
+ } __packed;
+ } __packed;
+ u8 hop_count;
+ __le32 ambtt;
+} __packed;
+
+#define for_each_nan_attr(_attr, _data, _datalen) \
+ for (_attr = (const struct ieee80211_nan_attr *)(_data); \
+ (const u8 *)(_data) + (_datalen) - (const u8 *)_attr >= \
+ (int)sizeof(*_attr) && \
+ (const u8 *)(_data) + (_datalen) - (const u8 *)_attr >= \
+ (int)sizeof(*_attr) + le16_to_cpu(_attr->length); \
+ _attr = (const struct ieee80211_nan_attr *) \
+ (_attr->data + le16_to_cpu(_attr->length)))
+
#endif /* LINUX_IEEE80211_NAN_H */
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index b5d649db123f..ffa8f9f77efe 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -2240,6 +2240,7 @@ struct ieee80211_multiple_bssid_configuration {
#define WLAN_OUI_WFA 0x506f9a
#define WLAN_OUI_TYPE_WFA_P2P 9
+#define WLAN_OUI_TYPE_WFA_NAN 0x13
#define WLAN_OUI_TYPE_WFA_DPP 0x1A
#define WLAN_OUI_MICROSOFT 0x0050f2
#define WLAN_OUI_TYPE_MICROSOFT_WPA 1
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 03/15] wifi: mac80211: export ieee80211_calculate_rx_timestamp
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 01/15] wifi: mac80211: add a TXQ for management frames on NAN devices Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 02/15] wifi: ieee80211: add more NAN definitions Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 04/15] wifi: mac80211: run NAN DE code only when appropriate Miri Korenblit
` (11 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg, Johannes Berg
From: Benjamin Berg <benjamin.berg@intel.com>
The function is quite useful when handling beacon timestamps. Export it
so that it can be used by mac80211_hwsim and others.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 18 ++++++++++++++++++
net/mac80211/ibss.c | 2 +-
net/mac80211/ieee80211_i.h | 4 ----
net/mac80211/mesh_sync.c | 2 +-
net/mac80211/rx.c | 2 +-
net/mac80211/scan.c | 2 +-
net/mac80211/util.c | 18 +++---------------
7 files changed, 25 insertions(+), 23 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 2959736efdf3..ff862d54eb77 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -7393,6 +7393,24 @@ void ieee80211_disable_rssi_reports(struct ieee80211_vif *vif);
*/
int ieee80211_ave_rssi(struct ieee80211_vif *vif, int link_id);
+/**
+ * ieee80211_calculate_rx_timestamp - calculate timestamp in frame
+ * @hw: pointer as obtained from ieee80211_alloc_hw()
+ * @status: RX status
+ * @mpdu_len: total MPDU length (including FCS)
+ * @mpdu_offset: offset into MPDU to calculate timestamp at
+ *
+ * This function calculates the RX timestamp at the given MPDU offset, taking
+ * into account what the RX timestamp was. An offset of 0 will just normalize
+ * the timestamp to TSF at beginning of MPDU reception.
+ *
+ * Returns: the calculated timestamp
+ */
+u64 ieee80211_calculate_rx_timestamp(struct ieee80211_hw *hw,
+ struct ieee80211_rx_status *status,
+ unsigned int mpdu_len,
+ unsigned int mpdu_offset);
+
/**
* ieee80211_report_wowlan_wakeup - report WoWLAN wakeup
* @vif: virtual interface
diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c
index 1e1ab25d9d8d..97292ff51475 100644
--- a/net/mac80211/ibss.c
+++ b/net/mac80211/ibss.c
@@ -1127,7 +1127,7 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
if (ieee80211_have_rx_timestamp(rx_status)) {
/* time when timestamp field was received */
rx_timestamp =
- ieee80211_calculate_rx_timestamp(local, rx_status,
+ ieee80211_calculate_rx_timestamp(&local->hw, rx_status,
len + FCS_LEN, 24);
} else {
/*
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index bacb49ad2817..53d783769642 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1922,10 +1922,6 @@ ieee80211_vif_get_num_mcast_if(struct ieee80211_sub_if_data *sdata)
return -1;
}
-u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
- struct ieee80211_rx_status *status,
- unsigned int mpdu_len,
- unsigned int mpdu_offset);
int ieee80211_hw_config(struct ieee80211_local *local, int radio_idx,
u32 changed);
int ieee80211_hw_conf_chan(struct ieee80211_local *local);
diff --git a/net/mac80211/mesh_sync.c b/net/mac80211/mesh_sync.c
index 3a66b4cefca7..24a68eef7db8 100644
--- a/net/mac80211/mesh_sync.c
+++ b/net/mac80211/mesh_sync.c
@@ -103,7 +103,7 @@ mesh_sync_offset_rx_bcn_presp(struct ieee80211_sub_if_data *sdata, u16 stype,
* section.
*/
if (ieee80211_have_rx_timestamp(rx_status))
- t_r = ieee80211_calculate_rx_timestamp(local, rx_status,
+ t_r = ieee80211_calculate_rx_timestamp(&local->hw, rx_status,
len + FCS_LEN, 24);
else
t_r = drv_get_tsf(local, sdata);
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index d9a654ef082d..dbdd67c181d8 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -404,7 +404,7 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local,
while ((pos - (u8 *)rthdr) & 7)
*pos++ = 0;
put_unaligned_le64(
- ieee80211_calculate_rx_timestamp(local, status,
+ ieee80211_calculate_rx_timestamp(&local->hw, status,
mpdulen, 0),
pos);
rthdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_TSFT));
diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c
index 4823c8d45639..eeff230bd909 100644
--- a/net/mac80211/scan.c
+++ b/net/mac80211/scan.c
@@ -216,7 +216,7 @@ ieee80211_bss_info_update(struct ieee80211_local *local,
if (link_conf) {
bss_meta.parent_tsf =
- ieee80211_calculate_rx_timestamp(local,
+ ieee80211_calculate_rx_timestamp(&local->hw,
rx_status,
len + FCS_LEN,
24);
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 72e73f4f79c5..38b0c42c4c13 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -3423,20 +3423,7 @@ u8 ieee80211_mcs_to_chains(const struct ieee80211_mcs_info *mcs)
return 1;
}
-/**
- * ieee80211_calculate_rx_timestamp - calculate timestamp in frame
- * @local: mac80211 hw info struct
- * @status: RX status
- * @mpdu_len: total MPDU length (including FCS)
- * @mpdu_offset: offset into MPDU to calculate timestamp at
- *
- * This function calculates the RX timestamp at the given MPDU offset, taking
- * into account what the RX timestamp was. An offset of 0 will just normalize
- * the timestamp to TSF at beginning of MPDU reception.
- *
- * Returns: the calculated timestamp
- */
-u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
+u64 ieee80211_calculate_rx_timestamp(struct ieee80211_hw *hw,
struct ieee80211_rx_status *status,
unsigned int mpdu_len,
unsigned int mpdu_offset)
@@ -3555,7 +3542,7 @@ u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
case RX_ENC_LEGACY: {
struct ieee80211_supported_band *sband;
- sband = local->hw.wiphy->bands[status->band];
+ sband = hw->wiphy->bands[status->band];
ri.legacy = sband->bitrates[status->rate_idx].bitrate;
if (mactime_plcp_start) {
@@ -3587,6 +3574,7 @@ u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
return ts;
}
+EXPORT_SYMBOL_GPL(ieee80211_calculate_rx_timestamp);
/* Cancel CAC for the interfaces under the specified @local. If @ctx is
* also provided, only the interfaces using that ctx will be canceled.
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 04/15] wifi: mac80211: run NAN DE code only when appropriate
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (2 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 03/15] wifi: mac80211: export ieee80211_calculate_rx_timestamp Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 05/15] wifi: mac80211: add NAN local schedule support Miri Korenblit
` (10 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
NAN DE (Discovery Engine) may be handled in the device or in user space.
When handled in user space, all the NAN func management code should not
run. Moreover, devices with user space DE should not provide the
add/del_nan_func callbaks. For such devices, ieee80211_reconfig_nan will
always fail.
Make it clear what parts of ieee80211_if_nan are relevant to DE
management, and touch those only when DE is offloaded.
Add a check that makes sure that a driver doesn't register with
add_del/nan_func callbacks if DE is in user space.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 52 ++++++++++++++++++++++++--------------
net/mac80211/ieee80211_i.h | 13 ++++++----
net/mac80211/iface.c | 25 +++++++++++-------
net/mac80211/main.c | 4 ++-
net/mac80211/util.c | 10 +++++---
5 files changed, 67 insertions(+), 37 deletions(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 13132215afb4..e851668f4ef3 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -502,12 +502,15 @@ static int ieee80211_add_nan_func(struct wiphy *wiphy,
if (!ieee80211_sdata_running(sdata))
return -ENETDOWN;
- spin_lock_bh(&sdata->u.nan.func_lock);
+ if (WARN_ON(wiphy->nan_capa.flags & WIPHY_NAN_FLAGS_USERSPACE_DE))
+ return -EOPNOTSUPP;
+
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
- ret = idr_alloc(&sdata->u.nan.function_inst_ids,
+ ret = idr_alloc(&sdata->u.nan.de.function_inst_ids,
nan_func, 1, sdata->local->hw.max_nan_de_entries + 1,
GFP_ATOMIC);
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
if (ret < 0)
return ret;
@@ -518,10 +521,10 @@ static int ieee80211_add_nan_func(struct wiphy *wiphy,
ret = drv_add_nan_func(sdata->local, sdata, nan_func);
if (ret) {
- spin_lock_bh(&sdata->u.nan.func_lock);
- idr_remove(&sdata->u.nan.function_inst_ids,
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
+ idr_remove(&sdata->u.nan.de.function_inst_ids,
nan_func->instance_id);
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
}
return ret;
@@ -534,9 +537,9 @@ ieee80211_find_nan_func_by_cookie(struct ieee80211_sub_if_data *sdata,
struct cfg80211_nan_func *func;
int id;
- lockdep_assert_held(&sdata->u.nan.func_lock);
+ lockdep_assert_held(&sdata->u.nan.de.func_lock);
- idr_for_each_entry(&sdata->u.nan.function_inst_ids, func, id) {
+ idr_for_each_entry(&sdata->u.nan.de.function_inst_ids, func, id) {
if (func->cookie == cookie)
return func;
}
@@ -555,13 +558,16 @@ static void ieee80211_del_nan_func(struct wiphy *wiphy,
!ieee80211_sdata_running(sdata))
return;
- spin_lock_bh(&sdata->u.nan.func_lock);
+ if (WARN_ON(wiphy->nan_capa.flags & WIPHY_NAN_FLAGS_USERSPACE_DE))
+ return;
+
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
func = ieee80211_find_nan_func_by_cookie(sdata, cookie);
if (func)
instance_id = func->instance_id;
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
if (instance_id)
drv_del_nan_func(sdata->local, sdata, instance_id);
@@ -4888,18 +4894,22 @@ void ieee80211_nan_func_terminated(struct ieee80211_vif *vif,
if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
return;
- spin_lock_bh(&sdata->u.nan.func_lock);
+ if (WARN_ON(sdata->local->hw.wiphy->nan_capa.flags &
+ WIPHY_NAN_FLAGS_USERSPACE_DE))
+ return;
+
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
- func = idr_find(&sdata->u.nan.function_inst_ids, inst_id);
+ func = idr_find(&sdata->u.nan.de.function_inst_ids, inst_id);
if (WARN_ON(!func)) {
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
return;
}
cookie = func->cookie;
- idr_remove(&sdata->u.nan.function_inst_ids, inst_id);
+ idr_remove(&sdata->u.nan.de.function_inst_ids, inst_id);
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
cfg80211_free_nan_func(func);
@@ -4918,16 +4928,20 @@ void ieee80211_nan_func_match(struct ieee80211_vif *vif,
if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
return;
- spin_lock_bh(&sdata->u.nan.func_lock);
+ if (WARN_ON(sdata->local->hw.wiphy->nan_capa.flags &
+ WIPHY_NAN_FLAGS_USERSPACE_DE))
+ return;
+
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
- func = idr_find(&sdata->u.nan.function_inst_ids, match->inst_id);
+ func = idr_find(&sdata->u.nan.de.function_inst_ids, match->inst_id);
if (WARN_ON(!func)) {
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
return;
}
match->cookie = func->cookie;
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
cfg80211_nan_match(ieee80211_vif_to_wdev(vif), match, gfp);
}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 53d783769642..8d5f9a725fdf 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -987,16 +987,19 @@ struct ieee80211_if_mntr {
*
* @conf: current NAN configuration
* @started: true iff NAN is started
- * @func_lock: lock for @func_inst_ids
- * @function_inst_ids: a bitmap of available instance_id's
+ * @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
*/
struct ieee80211_if_nan {
struct cfg80211_nan_conf conf;
bool started;
- /* protects function_inst_ids */
- spinlock_t func_lock;
- struct idr function_inst_ids;
+ struct {
+ /* protects function_inst_ids */
+ spinlock_t func_lock;
+ struct idr function_inst_ids;
+ } de;
};
struct ieee80211_link_data_managed {
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 7518dcbcdf1c..f0a5a675c5a5 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -622,15 +622,19 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
break;
case NL80211_IFTYPE_NAN:
/* clean all the functions */
- spin_lock_bh(&sdata->u.nan.func_lock);
+ if (!(local->hw.wiphy->nan_capa.flags &
+ WIPHY_NAN_FLAGS_USERSPACE_DE)) {
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
+
+ idr_for_each_entry(&sdata->u.nan.de.function_inst_ids,
+ func, i) {
+ idr_remove(&sdata->u.nan.de.function_inst_ids, i);
+ cfg80211_free_nan_func(func);
+ }
+ idr_destroy(&sdata->u.nan.de.function_inst_ids);
- idr_for_each_entry(&sdata->u.nan.function_inst_ids, func, i) {
- idr_remove(&sdata->u.nan.function_inst_ids, i);
- cfg80211_free_nan_func(func);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
}
- idr_destroy(&sdata->u.nan.function_inst_ids);
-
- spin_unlock_bh(&sdata->u.nan.func_lock);
break;
default:
wiphy_work_cancel(sdata->local->hw.wiphy, &sdata->work);
@@ -1942,8 +1946,11 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
MONITOR_FLAG_OTHER_BSS;
break;
case NL80211_IFTYPE_NAN:
- idr_init(&sdata->u.nan.function_inst_ids);
- spin_lock_init(&sdata->u.nan.func_lock);
+ if (!(sdata->local->hw.wiphy->nan_capa.flags &
+ WIPHY_NAN_FLAGS_USERSPACE_DE)) {
+ idr_init(&sdata->u.nan.de.function_inst_ids);
+ spin_lock_init(&sdata->u.nan.de.func_lock);
+ }
sdata->vif.bss_conf.bssid = sdata->vif.addr;
break;
case NL80211_IFTYPE_AP_VLAN:
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index d1bb6353908d..f47dd58770ad 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -1157,7 +1157,9 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
if (WARN_ON(local->hw.wiphy->interface_modes &
BIT(NL80211_IFTYPE_NAN) &&
- (!local->ops->start_nan || !local->ops->stop_nan)))
+ ((!local->ops->start_nan || !local->ops->stop_nan) ||
+ (local->hw.wiphy->nan_capa.flags & WIPHY_NAN_FLAGS_USERSPACE_DE &&
+ (local->ops->add_nan_func || local->ops->del_nan_func)))))
return -EINVAL;
if (hw->wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO) {
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 38b0c42c4c13..36795529ff82 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1754,6 +1754,10 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
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;
@@ -1762,12 +1766,12 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
* This is a little bit ugly. We need to call a potentially sleeping
* callback for each NAN function, so we can't hold the spinlock.
*/
- spin_lock_bh(&sdata->u.nan.func_lock);
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
- idr_for_each_entry(&sdata->u.nan.function_inst_ids, func, id)
+ idr_for_each_entry(&sdata->u.nan.de.function_inst_ids, func, id)
funcs[i++] = func;
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
for (i = 0; funcs[i]; i++) {
res = drv_add_nan_func(sdata->local, sdata, funcs[i]);
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 05/15] wifi: mac80211: add NAN local schedule support
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (3 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 04/15] wifi: mac80211: run NAN DE code only when appropriate Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 06/15] wifi: mac80211: support open and close for NAN_DATA interfaces Miri Korenblit
` (9 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Avraham Stern, Johannes Berg
A NAN local schedule consist of a list of NAN channels, and an array
that maps time slots to the channel it is scheduled to (or NULL to indicate
unscheduled).
A NAN channel is the configuration of a channel which is used for NAN
operations. It is a new type of chanctx user (before, the only user is a
link). A NAN channel may not have a chanctx assigned if it is ULWed out.
A NAN channel may or may not be scheduled (for example, user space
may want to prepare the resources before the actual schedule is
configured).
Add management of the NAN local schedule.
Since we introduce a new chanctx user, also adjust the different
for_each_chanctx_user_* macros to visit also the NAN channels and take
those into account.
Co-developed-by: Avraham Stern <avraham.stern@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 66 +++++++
net/mac80211/Makefile | 2 +-
net/mac80211/cfg.c | 13 ++
net/mac80211/chan.c | 132 ++++++++++---
net/mac80211/ieee80211_i.h | 20 +-
net/mac80211/iface.c | 8 +
net/mac80211/nan.c | 378 +++++++++++++++++++++++++++++++++++++
net/mac80211/util.c | 28 ++-
8 files changed, 609 insertions(+), 38 deletions(-)
create mode 100644 net/mac80211/nan.c
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index ff862d54eb77..24685f106757 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -365,6 +365,7 @@ struct ieee80211_vif_chanctx_switch {
* @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,
@@ -402,6 +403,7 @@ enum ieee80211_bss_change {
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 */
};
@@ -866,6 +868,28 @@ struct ieee80211_bss_conf {
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
*
@@ -1917,6 +1941,8 @@ enum ieee80211_offload_flags {
IEEE80211_OFFLOAD_DECAP_ENABLED = BIT(2),
};
+#define IEEE80211_NAN_AVAIL_BLOB_MAX_LEN 54
+
/**
* struct ieee80211_eml_params - EHT Operating mode notification parameters
*
@@ -1942,6 +1968,32 @@ struct ieee80211_eml_params {
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
@@ -1970,6 +2022,7 @@ struct ieee80211_eml_params {
* 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 */
@@ -1988,6 +2041,8 @@ struct ieee80211_vif_cfg {
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
@@ -7754,6 +7809,17 @@ void ieee80211_nan_func_match(struct ieee80211_vif *vif,
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.
*
diff --git a/net/mac80211/Makefile b/net/mac80211/Makefile
index b0e392eb7753..abf46c951299 100644
--- a/net/mac80211/Makefile
+++ b/net/mac80211/Makefile
@@ -36,7 +36,7 @@ mac80211-y := \
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) += \
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index e851668f4ef3..701b111db7df 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -5594,6 +5594,18 @@ ieee80211_set_epcs(struct wiphy *wiphy, struct net_device *dev, bool enable)
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,
@@ -5710,4 +5722,5 @@ const struct cfg80211_ops mac80211_config_ops = {
.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,
};
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 1e4bfcd25697..eca3fed1edbe 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -16,6 +16,8 @@ struct ieee80211_chanctx_user_iter {
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 {
@@ -31,20 +33,38 @@ enum ieee80211_chanctx_iter_type {
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++) {
@@ -64,7 +84,7 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
iter->reserved = false;
iter->radar_required = link->radar_required;
iter->chanreq = &link->conf->chanreq;
- return;
+ return true;
}
fallthrough;
case CHANCTX_ITER_POS_RESERVED:
@@ -77,7 +97,7 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
link->reserved_radar_required;
iter->chanreq = &link->reserved;
- return;
+ return true;
}
fallthrough;
case CHANCTX_ITER_POS_DONE:
@@ -85,6 +105,33 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
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 */
@@ -97,10 +144,18 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
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;
@@ -133,8 +188,8 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
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;
@@ -321,7 +376,7 @@ ieee80211_chanctx_non_reserved_chandef(struct ieee80211_local *local,
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,
@@ -480,7 +535,6 @@ ieee80211_get_width_of_link(struct ieee80211_link_data *link)
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,
@@ -495,6 +549,7 @@ ieee80211_get_width_of_link(struct ieee80211_link_data *link)
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;
@@ -504,6 +559,18 @@ ieee80211_get_width_of_link(struct ieee80211_link_data *link)
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,
@@ -521,7 +588,7 @@ ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
/* 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;
@@ -529,7 +596,7 @@ ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
/* 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);
}
@@ -941,7 +1008,10 @@ ieee80211_new_chanctx(struct ieee80211_local *local,
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);
@@ -962,9 +1032,9 @@ static void ieee80211_del_chanctx(struct ieee80211_local *local,
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);
@@ -1159,6 +1229,7 @@ void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local,
case NL80211_IFTYPE_ADHOC:
case NL80211_IFTYPE_MESH_POINT:
case NL80211_IFTYPE_OCB:
+ case NL80211_IFTYPE_NAN:
break;
default:
continue;
@@ -1169,6 +1240,15 @@ void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local,
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",
@@ -1777,7 +1857,7 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
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++;
@@ -2033,7 +2113,7 @@ void __ieee80211_link_release_channel(struct ieee80211_link_data *link,
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,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 8d5f9a725fdf..92ea8de8a6db 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -990,6 +990,8 @@ struct ieee80211_if_mntr {
* @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;
@@ -1000,6 +1002,8 @@ struct ieee80211_if_nan {
spinlock_t func_lock;
struct idr function_inst_ids;
} de;
+
+ DECLARE_BITMAP(removed_channels, IEEE80211_NAN_MAX_CHANNELS);
};
struct ieee80211_link_data_managed {
@@ -2024,6 +2028,10 @@ int ieee80211_mesh_csa_beacon(struct ieee80211_sub_if_data *sdata,
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,
@@ -2812,7 +2820,17 @@ int ieee80211_max_num_channels(struct ieee80211_local *local, int radio_idx);
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,
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index f0a5a675c5a5..0f3e49cdbb39 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -886,6 +886,14 @@ static void ieee80211_teardown_sdata(struct ieee80211_sub_if_data *sdata)
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)
diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c
new file mode 100644
index 000000000000..2fa55e9a9dab
--- /dev/null
+++ b/net/mac80211/nan.c
@@ -0,0 +1,378 @@
+// 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);
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 36795529ff82..19ac778b704d 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1744,20 +1744,12 @@ static void ieee80211_reconfig_stations(struct ieee80211_sub_if_data *sdata)
}
}
-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;
@@ -1783,6 +1775,22 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
}
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;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 06/15] wifi: mac80211: support open and close for NAN_DATA interfaces
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (4 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 05/15] wifi: mac80211: add NAN local schedule support Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 07/15] wifi: mac80211: handle reconfig for NAN DATA interfaces Miri Korenblit
` (8 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
Support opening and closing a NAN_DATA interface.
Track the NAN (NMI) interface, for convenience.
Allow opening an NAN_DATA interface only if the NAN interface is running
(NAN has started).
When closing the NAN interface, make sure all NAN_DATA interfaces are
closed first, and warn if this is not the case.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/ieee80211_i.h | 11 +++++++++++
net/mac80211/iface.c | 33 ++++++++++++++++++++++++++++++---
2 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 92ea8de8a6db..e3a051beba6a 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1006,6 +1006,16 @@ struct ieee80211_if_nan {
DECLARE_BITMAP(removed_channels, IEEE80211_NAN_MAX_CHANNELS);
};
+/**
+ * struct ieee80211_if_nan_data - NAN data path state
+ *
+ * @nmi: pointer to the NAN management interface sdata. Used for data path,
+ * hence RCU.
+ */
+struct ieee80211_if_nan_data {
+ struct ieee80211_sub_if_data __rcu *nmi;
+};
+
struct ieee80211_link_data_managed {
u8 bssid[ETH_ALEN] __aligned(2);
@@ -1204,6 +1214,7 @@ struct ieee80211_sub_if_data {
struct ieee80211_if_ocb ocb;
struct ieee80211_if_mntr mntr;
struct ieee80211_if_nan nan;
+ struct ieee80211_if_nan_data nan_data;
} u;
struct ieee80211_link_data deflink;
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 0f3e49cdbb39..507c5e016ec8 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -361,6 +361,17 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata,
nsdata->vif.type == NL80211_IFTYPE_OCB))
return -EBUSY;
+ /*
+ * A NAN DATA interface is correlated to the NAN
+ * (management) one
+ */
+ if (iftype == NL80211_IFTYPE_NAN_DATA &&
+ nsdata->vif.type == NL80211_IFTYPE_NAN) {
+ if (!nsdata->u.nan.started)
+ return -EINVAL;
+ rcu_assign_pointer(sdata->u.nan_data.nmi, nsdata);
+ }
+
/*
* Allow only a single IBSS interface to be up at any
* time. This is restricted because beacon distribution
@@ -475,6 +486,7 @@ static int ieee80211_open(struct net_device *dev)
static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_down)
{
struct ieee80211_local *local = sdata->local;
+ struct ieee80211_sub_if_data *iter;
unsigned long flags;
struct sk_buff_head freeq;
struct sk_buff *skb, *tmp;
@@ -621,6 +633,12 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
}
break;
case NL80211_IFTYPE_NAN:
+ /* Check if any open NAN_DATA interfaces */
+ list_for_each_entry(iter, &local->interfaces, list) {
+ WARN_ON(iter->vif.type == NL80211_IFTYPE_NAN_DATA &&
+ ieee80211_sdata_running(iter));
+ }
+
/* clean all the functions */
if (!(local->hw.wiphy->nan_capa.flags &
WIPHY_NAN_FLAGS_USERSPACE_DE)) {
@@ -636,6 +654,9 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
spin_unlock_bh(&sdata->u.nan.de.func_lock);
}
break;
+ case NL80211_IFTYPE_NAN_DATA:
+ RCU_INIT_POINTER(sdata->u.nan_data.nmi, NULL);
+ fallthrough;
default:
wiphy_work_cancel(sdata->local->hw.wiphy, &sdata->work);
/*
@@ -1384,9 +1405,12 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
case NL80211_IFTYPE_P2P_DEVICE:
case NL80211_IFTYPE_OCB:
case NL80211_IFTYPE_NAN:
- case NL80211_IFTYPE_NAN_DATA:
/* no special treatment */
break;
+ case NL80211_IFTYPE_NAN_DATA:
+ if (WARN_ON(!rcu_access_pointer(sdata->u.nan_data.nmi)))
+ return -ENOLINK;
+ break;
case NL80211_IFTYPE_UNSPECIFIED:
case NUM_NL80211_IFTYPES:
case NL80211_IFTYPE_P2P_CLIENT:
@@ -1404,8 +1428,8 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
res = drv_start(local);
if (res) {
/*
- * no need to worry about AP_VLAN cleanup since in that
- * case we can't have open_count == 0
+ * no need to worry about AP_VLAN/NAN_DATA cleanup since
+ * in that case we can't have open_count == 0
*/
return res;
}
@@ -1524,6 +1548,7 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_MESH_POINT:
case NL80211_IFTYPE_OCB:
+ case NL80211_IFTYPE_NAN_DATA:
netif_carrier_off(dev);
break;
case NL80211_IFTYPE_P2P_DEVICE:
@@ -1570,6 +1595,8 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
err_stop:
if (!local->open_count)
drv_stop(local, false);
+ if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ RCU_INIT_POINTER(sdata->u.nan_data.nmi, NULL);
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
list_del(&sdata->u.vlan.list);
/* Might not be initialized yet, but it is harmless */
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 07/15] wifi: mac80211: handle reconfig for NAN DATA interfaces
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (5 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 06/15] wifi: mac80211: support open and close for NAN_DATA interfaces Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 08/15] wifi: mac80211: support NAN stations Miri Korenblit
` (7 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
Make sure these interfaces are added to the driver only after the NAN
one was, and after NAN operation was started.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/util.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 19ac778b704d..925a09246ad9 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1780,10 +1780,11 @@ ieee80211_reconfig_nan_offload_de(struct ieee80211_sub_if_data *sdata)
static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_sub_if_data *ndi_sdata;
int res;
- res = drv_start_nan(sdata->local, sdata,
- &sdata->u.nan.conf);
+ res = drv_start_nan(local, sdata, &sdata->u.nan.conf);
if (WARN_ON(res))
return res;
@@ -1792,6 +1793,15 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
+ /* Now we can add all the NDIs to the driver */
+ list_for_each_entry(ndi_sdata, &local->interfaces, list) {
+ if (ndi_sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
+ res = drv_add_interface(local, ndi_sdata);
+ if (WARN_ON(res))
+ return res;
+ }
+ }
+
return 0;
}
@@ -1945,6 +1955,9 @@ int ieee80211_reconfig(struct ieee80211_local *local)
if (sdata->vif.type == NL80211_IFTYPE_MONITOR &&
!ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
continue;
+ /* These vifs can't be added before NAN was started */
+ if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ continue;
if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
ieee80211_sdata_running(sdata)) {
res = drv_add_interface(local, sdata);
@@ -1962,6 +1975,8 @@ int ieee80211_reconfig(struct ieee80211_local *local)
if (sdata->vif.type == NL80211_IFTYPE_MONITOR &&
!ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
continue;
+ if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ continue;
if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
ieee80211_sdata_running(sdata))
drv_remove_interface(local, sdata);
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 08/15] wifi: mac80211: support NAN stations
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (6 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 07/15] wifi: mac80211: handle reconfig for NAN DATA interfaces Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 09/15] wifi: mac80211: add NAN peer schedule support Miri Korenblit
` (6 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
Add support for both NMI and NDI stations.
The NDI station will be linked to the NMI station of the NAN peer for
which the NDI station is added.
A peer can choose to reuse its NMI address as the NDI address.
Since different keys might be in use for NAN management and for data
frames, we will have 2 different stations, even if they'll have the same
address.
Even though there are no links in NAN, sta->deflink will still be used
to store the one set of capabilities and SMPS mode.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 13 ++++-
net/mac80211/cfg.c | 103 ++++++++++++++++++++++++++++++++++++----
net/mac80211/he.c | 7 ++-
net/mac80211/ht.c | 19 ++++++--
net/mac80211/iface.c | 10 ++--
net/mac80211/sta_info.c | 21 +++++++-
net/mac80211/sta_info.h | 3 +-
net/mac80211/util.c | 41 ++++++++++++++++
net/mac80211/vht.c | 16 ++++++-
9 files changed, 209 insertions(+), 24 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 24685f106757..8656e2cc2b75 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2534,11 +2534,15 @@ struct ieee80211_sta_aggregates {
* @uhr_cap: UHR capabilities of this STA
* @s1g_cap: S1G capabilities of this STA
* @agg: per-link data for multi-link aggregation
- * @bandwidth: current bandwidth the station can receive with
+ * @bandwidth: current bandwidth the station can receive with.
+ * This is the minimum between the peer's capabilities and our own
+ * operating channel width; Invalid for NAN since that is operating on
+ * multiple channels.
* @rx_nss: in HT/VHT, the maximum number of spatial streams the
* station can receive at the moment, changed by operating mode
* notifications and capabilities. The value is only valid after
- * the station moves to associated state.
+ * the station moves to associated state. Invalid for NAN since it
+ * operates on multiple configurations of rx_nss.
* @txpwr: the station tx power configuration
*
*/
@@ -2620,6 +2624,7 @@ struct ieee80211_link_sta {
* @valid_links: bitmap of valid links, or 0 for non-MLO
* @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.
*/
struct ieee80211_sta {
u8 addr[ETH_ALEN] __aligned(2);
@@ -2648,6 +2653,8 @@ struct ieee80211_sta {
struct ieee80211_link_sta deflink;
struct ieee80211_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
+ struct ieee80211_sta __rcu *nmi;
+
/* must be last */
u8 drv_priv[] __aligned(sizeof(void *));
};
@@ -2881,6 +2888,8 @@ struct ieee80211_txq {
* station has a unique address, i.e. each station entry can be identified
* by just its MAC address; this prevents, for example, the same station
* from connecting to two virtual AP interfaces at the same time.
+ * Note that this doesn't apply for NAN, in which the peer's NMI address
+ * can be equal to its NDI address.
*
* @IEEE80211_HW_SUPPORTS_REORDERING_BUFFER: Hardware (or driver) manages the
* reordering buffer internally, guaranteeing mac80211 receives frames in
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 701b111db7df..a1089e3964bd 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2077,7 +2077,7 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
enum sta_link_apply_mode mode,
struct link_station_parameters *params)
{
- struct ieee80211_supported_band *sband;
+ struct ieee80211_supported_band *sband = NULL;
struct ieee80211_sub_if_data *sdata = sta->sdata;
u32 link_id = params->link_id < 0 ? 0 : params->link_id;
struct ieee80211_link_data *link =
@@ -2085,6 +2085,9 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
struct link_sta_info *link_sta =
rcu_dereference_protected(sta->link[link_id],
lockdep_is_held(&local->hw.wiphy->mtx));
+ const struct ieee80211_sta_ht_cap *own_ht_cap;
+ const struct ieee80211_sta_vht_cap *own_vht_cap;
+ const struct ieee80211_sta_he_cap *own_he_cap;
bool changes = params->link_mac ||
params->txpwr_set ||
params->supported_rates_len ||
@@ -2114,10 +2117,27 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
if (!link || !link_sta)
return -EINVAL;
- sband = ieee80211_get_link_sband(link);
- if (!sband)
+ /*
+ * We should not have any changes in NDI station, its capabilities are
+ * copied from the NMI sta
+ */
+ if (WARN_ON(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
return -EINVAL;
+ if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+ own_ht_cap = &local->hw.wiphy->nan_capa.phy.ht;
+ own_vht_cap = &local->hw.wiphy->nan_capa.phy.vht;
+ own_he_cap = &local->hw.wiphy->nan_capa.phy.he;
+ } else {
+ sband = ieee80211_get_link_sband(link);
+ if (!sband)
+ return -EINVAL;
+
+ own_ht_cap = &sband->ht_cap;
+ own_vht_cap = &sband->vht_cap;
+ own_he_cap = ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif);
+ }
+
if (params->link_mac) {
if (mode == STA_LINK_MODE_NEW) {
memcpy(link_sta->addr, params->link_mac, ETH_ALEN);
@@ -2139,6 +2159,27 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
return ret;
}
+ if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+ static const u8 all_ofdm_rates[] = {
+ 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c
+ };
+
+ /* Set the same supported_rates for all bands */
+ for (int i = 0; i < NUM_NL80211_BANDS; i++) {
+ struct ieee80211_supported_band *tmp =
+ sdata->local->hw.wiphy->bands[i];
+
+ if ((i != NL80211_BAND_2GHZ && i != NL80211_BAND_5GHZ) ||
+ !tmp)
+ continue;
+
+ if (!ieee80211_parse_bitrates(tmp, all_ofdm_rates,
+ sizeof(all_ofdm_rates),
+ &link_sta->pub->supp_rates[i]))
+ return -EINVAL;
+ }
+ }
+
if (params->supported_rates &&
params->supported_rates_len &&
!ieee80211_parse_bitrates(sband, params->supported_rates,
@@ -2147,22 +2188,24 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
return -EINVAL;
if (params->ht_capa)
- ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, &sband->ht_cap,
+ ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, own_ht_cap,
params->ht_capa, link_sta);
/* VHT can override some HT caps such as the A-MSDU max length */
if (params->vht_capa)
ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
- &sband->vht_cap,
+ own_vht_cap,
params->vht_capa, NULL,
link_sta);
if (params->he_capa)
- ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband,
- (void *)params->he_capa,
- params->he_capa_len,
- (void *)params->he_6ghz_capa,
- link_sta);
+ _ieee80211_he_cap_ie_to_sta_he_cap(sdata,
+ own_he_cap,
+ (void *)params->he_capa,
+ params->he_capa_len,
+ (sband && sband->band == NL80211_BAND_6GHZ) ?
+ (void *)params->he_6ghz_capa : NULL,
+ link_sta);
if (params->he_capa && params->eht_capa)
ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband,
@@ -2349,6 +2392,32 @@ static int sta_apply_parameters(struct ieee80211_local *local,
if (params->airtime_weight)
sta->airtime_weight = params->airtime_weight;
+ if (params->nmi_mac) {
+ struct ieee80211_sub_if_data *nmi =
+ rcu_dereference_wiphy(local->hw.wiphy,
+ sdata->u.nan_data.nmi);
+ struct sta_info *nmi_sta;
+
+ if (WARN_ON(!nmi))
+ return -EINVAL;
+
+ nmi_sta = sta_info_get(nmi, params->nmi_mac);
+ if (!nmi_sta)
+ return -ENOENT;
+ rcu_assign_pointer(sta->sta.nmi, &nmi_sta->sta);
+
+ /* For NAN_DATA stations, copy capabilities from the NMI station */
+ if (!nmi_sta->deflink.pub->ht_cap.ht_supported)
+ return -EINVAL;
+
+ sta->deflink.pub->ht_cap = nmi_sta->deflink.pub->ht_cap;
+ sta->deflink.pub->vht_cap = nmi_sta->deflink.pub->vht_cap;
+ sta->deflink.pub->he_cap = nmi_sta->deflink.pub->he_cap;
+ memcpy(&sta->deflink.pub->supp_rates,
+ &nmi_sta->deflink.pub->supp_rates,
+ sizeof(sta->deflink.pub->supp_rates));
+ }
+
/* set the STA state after all sta info from usermode has been set */
if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) ||
set & BIT(NL80211_STA_FLAG_ASSOCIATED)) {
@@ -2494,6 +2563,12 @@ static int ieee80211_change_station(struct wiphy *wiphy,
else
statype = CFG80211_STA_AP_CLIENT_UNASSOC;
break;
+ case NL80211_IFTYPE_NAN:
+ statype = CFG80211_STA_NAN_MGMT;
+ break;
+ case NL80211_IFTYPE_NAN_DATA:
+ statype = CFG80211_STA_NAN_DATA;
+ break;
default:
return -EOPNOTSUPP;
}
@@ -2532,6 +2607,14 @@ static int ieee80211_change_station(struct wiphy *wiphy,
}
}
+ /* NAN capabilties should not change */
+ if (statype == CFG80211_STA_NAN_DATA &&
+ sta->deflink.pub->ht_cap.ht_supported &&
+ (params->link_sta_params.ht_capa ||
+ params->link_sta_params.vht_capa ||
+ params->link_sta_params.he_capa))
+ return -EINVAL;
+
err = sta_apply_parameters(local, sta, params);
if (err)
return err;
diff --git a/net/mac80211/he.c b/net/mac80211/he.c
index 93e0342cff4f..a3e16a5bec22 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -127,6 +127,10 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
if (!he_cap_ie || !own_he_cap_ptr || !own_he_cap_ptr->has_he)
return;
+ /* NDI station are using the capabilities from the NMI station */
+ if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+ return;
+
own_he_cap = *own_he_cap_ptr;
/* Make sure size is OK */
@@ -156,7 +160,8 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
he_cap->has_he = true;
link_sta->cur_max_bandwidth = ieee80211_sta_cap_rx_bw(link_sta);
- link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
+ if (sdata->vif.type != NL80211_IFTYPE_NAN)
+ link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
if (he_6ghz_capa)
ieee80211_update_from_he_6ghz_capa(he_6ghz_capa, link_sta);
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index 410e2354f33a..97719298e038 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -154,6 +154,10 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
if (!ht_cap_ie || !own_cap_ptr->ht_supported)
goto apply;
+ /* NDI station are using the capabilities from the NMI station */
+ if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+ return 0;
+
ht_cap.ht_supported = true;
own_cap = *own_cap_ptr;
@@ -254,10 +258,17 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
rcu_read_lock();
link_conf = rcu_dereference(sdata->vif.link_conf[link_sta->link_id]);
- if (WARN_ON(!link_conf))
+ if (WARN_ON(!link_conf)) {
width = NL80211_CHAN_WIDTH_20_NOHT;
- else
+ } else if (sdata->vif.type == NL80211_IFTYPE_NAN ||
+ sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
+ /* In NAN, link_sta->bandwidth is invalid since NAN operates on
+ * multiple channels. Just take the maximum.
+ */
+ width = NL80211_CHAN_WIDTH_320;
+ } else {
width = link_conf->chanreq.oper.width;
+ }
switch (width) {
default:
@@ -285,7 +296,9 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
- sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
+ sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
+ sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
+ sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
enum ieee80211_smps_mode smps_mode;
switch ((ht_cap.cap & IEEE80211_HT_CAP_SM_PS)
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 507c5e016ec8..f1ab85ff326d 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -535,12 +535,14 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
* (because if we remove a STA after ops->remove_interface()
* the driver will have removed the vif info already!)
*
- * For AP_VLANs stations may exist since there's nothing else that
- * would have removed them, but in other modes there shouldn't
- * be any stations.
+ * For AP_VLANs, NAN and NAN_DATA stations may exist since there's
+ * nothing else that would have removed them, but in other modes there
+ * shouldn't be any stations.
*/
flushed = sta_info_flush(sdata, -1);
- WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_AP_VLAN && flushed > 0);
+ WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
+ sdata->vif.type != NL80211_IFTYPE_NAN &&
+ sdata->vif.type != NL80211_IFTYPE_NAN_DATA && flushed > 0);
/* don't count this interface for allmulti while it is down */
if (sdata->flags & IEEE80211_SDATA_ALLMULTI)
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 4259e9c13ed7..017d91365920 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -795,6 +795,7 @@ struct sta_info *sta_info_alloc_with_link(struct ieee80211_sub_if_data *sdata,
static int sta_info_insert_check(struct sta_info *sta)
{
struct ieee80211_sub_if_data *sdata = sta->sdata;
+ struct ieee80211_sta *same_addr_sta;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
@@ -810,13 +811,18 @@ static int sta_info_insert_check(struct sta_info *sta)
!is_valid_ether_addr(sta->sta.addr)))
return -EINVAL;
+ if (!ieee80211_hw_check(&sdata->local->hw, NEEDS_UNIQUE_STA_ADDR))
+ return 0;
+
/* The RCU read lock is required by rhashtable due to
* asynchronous resize/rehash. We also require the mutex
* for correctness.
*/
rcu_read_lock();
- if (ieee80211_hw_check(&sdata->local->hw, NEEDS_UNIQUE_STA_ADDR) &&
- ieee80211_find_sta_by_ifaddr(&sdata->local->hw, sta->addr, NULL)) {
+ same_addr_sta = ieee80211_find_sta_by_ifaddr(&sdata->local->hw,
+ sta->addr, NULL);
+ /* For NAN, a peer can re-use */
+ if (same_addr_sta && same_addr_sta != rcu_access_pointer(sta->sta.nmi)) {
rcu_read_unlock();
return -ENOTUNIQ;
}
@@ -1294,6 +1300,17 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta)
lockdep_assert_wiphy(local->hw.wiphy);
+ if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+ struct sta_info *sta_iter, *tmp;
+
+ /* Remove all NDI stations associated with this NMI STA */
+ list_for_each_entry_safe(sta_iter, tmp, &local->sta_list, list) {
+ if (rcu_access_pointer(sta_iter->sta.nmi) != &sta->sta)
+ continue;
+ sta_info_destroy_addr(sta_iter->sdata, sta_iter->addr);
+ }
+ }
+
/*
* Before removing the station from the driver and
* rate control, it might still start new aggregation
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 58ccbea7f6f6..3e5d003bd31f 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -505,7 +505,8 @@ struct ieee80211_fragment_cache {
* @status_stats.ack_signal_filled: last ACK signal validity
* @status_stats.avg_ack_signal: average ACK signal
* @cur_max_bandwidth: maximum bandwidth to use for TX to the station,
- * taken from HT/VHT capabilities or VHT operating mode notification
+ * taken from HT/VHT capabilities or VHT operating mode notification.
+ * Invalid for NAN since that is operating on multiple bands.
* @rx_omi_bw_rx: RX OMI bandwidth restriction to apply for RX
* @rx_omi_bw_tx: RX OMI bandwidth restriction to apply for TX
* @rx_omi_bw_staging: RX OMI bandwidth restriction to apply later
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 925a09246ad9..a352f73a7ec4 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1782,6 +1782,7 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_sub_if_data *ndi_sdata;
+ struct sta_info *sta;
int res;
res = drv_start_nan(local, sdata, &sdata->u.nan.conf);
@@ -1802,6 +1803,42 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
}
}
+ /* Add NMI stations (stations on the NAN interface) */
+ list_for_each_entry(sta, &local->sta_list, list) {
+ enum ieee80211_sta_state state;
+
+ if (!sta->uploaded || sta->sdata != sdata)
+ continue;
+
+ for (state = IEEE80211_STA_NOTEXIST; state < sta->sta_state;
+ state++) {
+ res = drv_sta_state(local, sdata, sta, state,
+ state + 1);
+ if (WARN_ON(res))
+ return res;
+ }
+ }
+
+ /* Add NDI stations (stations on NAN_DATA interfaces) */
+ list_for_each_entry(sta, &local->sta_list, list) {
+ enum ieee80211_sta_state state;
+
+ if (!sta->uploaded ||
+ sta->sdata->vif.type != NL80211_IFTYPE_NAN_DATA)
+ continue;
+
+ if (WARN_ON(!sta->sta.nmi))
+ continue;
+
+ for (state = IEEE80211_STA_NOTEXIST; state < sta->sta_state;
+ state++) {
+ res = drv_sta_state(local, sta->sdata, sta, state,
+ state + 1);
+ if (WARN_ON(res))
+ return res;
+ }
+ }
+
return 0;
}
@@ -2060,6 +2097,10 @@ int ieee80211_reconfig(struct ieee80211_local *local)
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_MONITOR:
break;
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
+ /* NAN stations are handled later */
+ break;
case NL80211_IFTYPE_ADHOC:
if (sdata->vif.cfg.ibss_joined)
WARN_ON(drv_join_ibss(local, sdata));
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index a6570781740a..f3bb5a561a38 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -133,6 +133,10 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
if (!vht_cap_ie || !own_vht_cap->vht_supported)
return;
+ /* NDI station are using the capabilities from the NMI station */
+ if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+ return;
+
if (sband) {
/* Allow VHT if at least one channel on the sband supports 80 MHz */
bool have_80mhz = false;
@@ -320,7 +324,8 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
IEEE80211_STA_RX_BW_160;
}
- link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
+ if (sdata->vif.type != NL80211_IFTYPE_NAN)
+ link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
/*
* Work around the Cisco 9115 FW 17.3 bug by taking the min of
@@ -373,6 +378,10 @@ __ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
} else {
struct ieee80211_bss_conf *link_conf;
+ if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA ||
+ sdata->vif.type == NL80211_IFTYPE_NAN))
+ return IEEE80211_STA_RX_BW_20;
+
rcu_read_lock();
link_conf = rcu_dereference(sdata->vif.link_conf[link_id]);
band = link_conf->chanreq.oper.chan->band;
@@ -518,6 +527,11 @@ _ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
} else {
struct ieee80211_bss_conf *link_conf;
+ /* NAN operates on multiple channels so a chandef must be given */
+ if (WARN_ON_ONCE(sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
+ sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+ return IEEE80211_STA_RX_BW_20;
+
rcu_read_lock();
link_conf = rcu_dereference(sta->sdata->vif.link_conf[link_sta->link_id]);
if (WARN_ON_ONCE(!link_conf)) {
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 09/15] wifi: mac80211: add NAN peer schedule support
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (7 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 08/15] wifi: mac80211: support NAN stations Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 10/15] wifi: mac80211: update NAN data path state on schedule changes Miri Korenblit
` (5 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
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>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 62 +++++++++-
net/mac80211/cfg.c | 13 +++
net/mac80211/driver-ops.h | 21 ++++
net/mac80211/ieee80211_i.h | 3 +
net/mac80211/nan.c | 226 ++++++++++++++++++++++++++++++++++++-
net/mac80211/sta_info.c | 4 +
net/mac80211/trace.h | 31 +++++
net/mac80211/util.c | 8 ++
8 files changed, 365 insertions(+), 3 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 8656e2cc2b75..783757f14b61 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -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);
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index a1089e3964bd..493f7b5cfd6f 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -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,
};
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 51bf3c7822a7..f1c0b87fddd5 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -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 */
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index e3a051beba6a..23bf00472915 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -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);
diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c
index 2fa55e9a9dab..5e1f9bb7c49d 100644
--- a/net/mac80211/nan.c
+++ b/net/mac80211/nan.c
@@ -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;
+}
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 017d91365920..dd0d92abf60d 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -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;
}
/*
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index e5968d754f8b..71cf88039bd4 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -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
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index a352f73a7ec4..b093bc203c81 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -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) */
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 10/15] wifi: mac80211: update NAN data path state on schedule changes
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (8 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 09/15] wifi: mac80211: add NAN peer schedule support Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 11/15] wifi: mac80211: add support for TX over NAN_DATA interfaces Miri Korenblit
` (4 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
A carrier of an NDI interface is turned on when there is at least one NDI
station that: (1) correlates to this interface (2) is authorized (3) the
NAN peer to which this station belongs has at least one common slot with
the local schedule. Otherwise, it is turned off.
(common slots are slots where both schedules are active on compatible
channels.)
Implement the calculation of the carrier state and trigger it when
needed.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 10 +++-
net/mac80211/ieee80211_i.h | 1 +
net/mac80211/nan.c | 108 +++++++++++++++++++++++++++++++++++++
net/mac80211/sta_info.c | 4 ++
4 files changed, 122 insertions(+), 1 deletion(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 493f7b5cfd6f..8cdbefac1bee 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2502,7 +2502,15 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct wireless_dev *wdev,
test_sta_flag(sta, WLAN_STA_ASSOC))
rate_control_rate_init_all_links(sta);
- return sta_info_insert(sta);
+ err = sta_info_insert(sta);
+
+ /*
+ * ieee80211_nan_update_ndi_carrier was called from sta_apply_parameters,
+ * but then we did not have the STA in the list.
+ */
+ if (!err && sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ ieee80211_nan_update_ndi_carrier(sta->sdata);
+ return err;
}
static int ieee80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 23bf00472915..2a693406294b 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2045,6 +2045,7 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
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);
+void ieee80211_nan_update_ndi_carrier(struct ieee80211_sub_if_data *ndi_sdata);
/* scan/BSS handling */
void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work);
diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c
index 5e1f9bb7c49d..4e262b624521 100644
--- a/net/mac80211/nan.c
+++ b/net/mac80211/nan.c
@@ -220,6 +220,23 @@ ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata,
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)
{
@@ -347,6 +364,7 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
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;
@@ -409,6 +427,7 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
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;
}
@@ -423,6 +442,8 @@ void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif)
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
@@ -531,6 +552,91 @@ ieee80211_nan_init_peer_map(struct ieee80211_nan_peer_sched *peer_sched,
}
}
+/*
+ * 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)
{
@@ -592,6 +698,8 @@ int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
goto out;
}
+ ieee80211_nan_update_peer_ndis_carrier(sdata->local, sta);
+
/* Success - free old schedule */
to_free = old_sched;
ret = 0;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index dd0d92abf60d..de85a7c68bec 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -1454,6 +1454,8 @@ static int _sta_info_move_state(struct sta_info *sta,
} else if (sta->sta_state == IEEE80211_STA_AUTHORIZED) {
ieee80211_vif_dec_num_mcast(sta->sdata);
clear_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
+ if (sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ ieee80211_nan_update_ndi_carrier(sta->sdata);
/*
* If we have encryption offload, flush (station) queues
@@ -1482,6 +1484,8 @@ static int _sta_info_move_state(struct sta_info *sta,
set_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
ieee80211_check_fast_xmit(sta);
ieee80211_check_fast_rx(sta);
+ if (sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ ieee80211_nan_update_ndi_carrier(sta->sdata);
}
if (sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
sta->sdata->vif.type == NL80211_IFTYPE_AP)
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 11/15] wifi: mac80211: add support for TX over NAN_DATA interfaces
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (9 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 10/15] wifi: mac80211: update NAN data path state on schedule changes Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 12/15] wifi: mac80211: Accept frames on NAN DATA interfaces Miri Korenblit
` (3 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
Add support for TXing frames over NAN_DATA interfaces:
- find the NDI station
- populoate the addresses fields
- use NUM_NL80211_BANDS for the band, similar to NAN interfaces.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/tx.c | 32 +++++++++++++++++++++++++++++---
1 file changed, 29 insertions(+), 3 deletions(-)
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 730c208c3bdf..b0eacab6763d 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2541,6 +2541,13 @@ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
if (!sta)
return -ENOLINK;
break;
+ case NL80211_IFTYPE_NAN_DATA:
+ if (is_multicast_ether_addr(skb->data)) {
+ *sta_out = ERR_PTR(-ENOENT);
+ return 0;
+ }
+ sta = sta_info_get(sdata, skb->data);
+ break;
default:
return -EINVAL;
}
@@ -2834,18 +2841,37 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata,
memcpy(hdr.addr3, sdata->u.ibss.bssid, ETH_ALEN);
hdrlen = 24;
break;
+ case NL80211_IFTYPE_NAN_DATA: {
+ struct ieee80211_sub_if_data *nmi;
+
+ /* DA SA Cluster ID */
+ memcpy(hdr.addr1, skb->data, ETH_ALEN);
+ memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN);
+ nmi = rcu_dereference(sdata->u.nan_data.nmi);
+ if (!nmi) {
+ ret = -ENOTCONN;
+ goto free;
+ }
+ memcpy(hdr.addr3, nmi->wdev.u.nan.cluster_id, ETH_ALEN);
+ hdrlen = 24;
+ break;
+ }
default:
ret = -EINVAL;
goto free;
}
if (!chanctx_conf) {
- if (!ieee80211_vif_is_mld(&sdata->vif)) {
+ if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
+ /* NAN operates on multiple bands */
+ band = NUM_NL80211_BANDS;
+ } else if (!ieee80211_vif_is_mld(&sdata->vif)) {
ret = -ENOTCONN;
goto free;
+ } else {
+ /* MLD transmissions must not rely on the band */
+ band = 0;
}
- /* MLD transmissions must not rely on the band */
- band = 0;
} else {
band = chanctx_conf->def.chan->band;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 12/15] wifi: mac80211: Accept frames on NAN DATA interfaces
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (10 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 11/15] wifi: mac80211: add support for TX over NAN_DATA interfaces Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 13/15] wifi: mac80211: allow block ack agreements in NAN Data Miri Korenblit
` (2 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
Accept frames there were received on NAN DATA interfaces:
- Data frames, both multicast or unicast
- Non-Public action frames, both multicast or unicast
- Unicast secure management frames
- FromDS and ToDS are 0.
While at it, check FromDS/ToDS also for NAN management frames.
Accept only data frames from devices that are part of the NAN
cluster.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/rx.c | 37 ++++++++++++++++++++++++++++++++++++-
1 file changed, 36 insertions(+), 1 deletion(-)
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index dbdd67c181d8..a00b73420929 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -4469,6 +4469,9 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
u8 *bssid = ieee80211_get_bssid(hdr, skb->len, sdata->vif.type);
bool multicast = is_multicast_ether_addr(hdr->addr1) ||
ieee80211_is_s1g_beacon(hdr->frame_control);
+ static const u8 nan_network_id[ETH_ALEN] __aligned(2) = {
+ 0x51, 0x6F, 0x9A, 0x01, 0x00, 0x00
+ };
switch (sdata->vif.type) {
case NL80211_IFTYPE_STATION:
@@ -4597,6 +4600,10 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
(ieee80211_is_auth(hdr->frame_control) &&
ether_addr_equal(sdata->vif.addr, hdr->addr1));
case NL80211_IFTYPE_NAN:
+ if (ieee80211_has_tods(hdr->frame_control) ||
+ ieee80211_has_fromds(hdr->frame_control))
+ return false;
+
/* Accept only frames that are addressed to the NAN cluster
* (based on the Cluster ID). From these frames, accept only
* action frames or authentication frames that are addressed to
@@ -4608,7 +4615,35 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
(ieee80211_is_auth(hdr->frame_control) &&
ether_addr_equal(sdata->vif.addr, hdr->addr1)));
case NL80211_IFTYPE_NAN_DATA:
- return false;
+ if (ieee80211_has_tods(hdr->frame_control) ||
+ ieee80211_has_fromds(hdr->frame_control))
+ return false;
+
+ if (ieee80211_is_data(hdr->frame_control)) {
+ struct ieee80211_sub_if_data *nmi;
+
+ nmi = rcu_dereference(sdata->u.nan_data.nmi);
+ if (!nmi)
+ return false;
+
+ if (!ether_addr_equal(nmi->wdev.u.nan.cluster_id,
+ hdr->addr3))
+ return false;
+
+ return multicast ||
+ ether_addr_equal(sdata->vif.addr, hdr->addr1);
+ }
+
+ /* Non-public action frames (unicast or multicast) */
+ if (ieee80211_is_action(hdr->frame_control) &&
+ !ieee80211_is_public_action(hdr, skb->len) &&
+ (ether_addr_equal(nan_network_id, hdr->addr1) ||
+ ether_addr_equal(sdata->vif.addr, hdr->addr1)))
+ return true;
+
+ /* Unicast secure management frames */
+ return ether_addr_equal(sdata->vif.addr, hdr->addr1) &&
+ ieee80211_is_unicast_robust_mgmt_frame(skb);
default:
break;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 13/15] wifi: mac80211: allow block ack agreements in NAN Data
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (11 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 12/15] wifi: mac80211: Accept frames on NAN DATA interfaces Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 14/15] wifi: mac80211: report and drop spurious NAN Data frames Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 15/15] wifi: mac80211: allow add_key on NAN interfaces Miri Korenblit
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
Allow receiving and sending Add Block Ack action frames for NAN Data
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/agg-tx.c | 3 ++-
net/mac80211/rx.c | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c
index 01d927b88264..4833b46770b6 100644
--- a/net/mac80211/agg-tx.c
+++ b/net/mac80211/agg-tx.c
@@ -641,7 +641,8 @@ int ieee80211_start_tx_ba_session(struct ieee80211_sta *pubsta, u16 tid,
sdata->vif.type != NL80211_IFTYPE_MESH_POINT &&
sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
sdata->vif.type != NL80211_IFTYPE_AP &&
- sdata->vif.type != NL80211_IFTYPE_ADHOC)
+ sdata->vif.type != NL80211_IFTYPE_ADHOC &&
+ sdata->vif.type != NL80211_IFTYPE_NAN_DATA)
return -EINVAL;
if (test_sta_flag(sta, WLAN_STA_BLOCK_BA)) {
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index a00b73420929..979ac26d1173 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -3748,7 +3748,8 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
sdata->vif.type != NL80211_IFTYPE_MESH_POINT &&
sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
sdata->vif.type != NL80211_IFTYPE_AP &&
- sdata->vif.type != NL80211_IFTYPE_ADHOC)
+ sdata->vif.type != NL80211_IFTYPE_ADHOC &&
+ sdata->vif.type != NL80211_IFTYPE_NAN_DATA)
break;
/* verify action_code is present */
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 14/15] wifi: mac80211: report and drop spurious NAN Data frames
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (12 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 13/15] wifi: mac80211: allow block ack agreements in NAN Data Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
2026-03-25 21:15 ` [PATCH wireless-next 15/15] wifi: mac80211: allow add_key on NAN interfaces Miri Korenblit
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
According to Wi-Fi Aware (TM) 4.0 specification 6.2.5, in case a frame
is recevied from an address that doesn't belong to any active NDP, the
frame should be dropped and a NAN Data Path Termination should be sent
to the transmitter.
Do it by dropping the frame and calling cfg80211_rx_spurious_frame in
that case.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/rx.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 979ac26d1173..3e5d1c47a5b0 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -1589,6 +1589,25 @@ ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
if (ieee80211_vif_is_mesh(&rx->sdata->vif))
return ieee80211_rx_mesh_check(rx);
+ /*
+ * Wi-Fi Aware (TM) 4.0 specification 6.2.5:
+ * For NAN_DATA, unicast data frames must have A2 (source)
+ * assigned to an active NDP. If not the frame must be dropped
+ * and NAN Data Path termination frame should be sent. Notify
+ * user space so it can do so.
+ */
+ if (rx->sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
+ if (ieee80211_is_data(hdr->frame_control) &&
+ !is_multicast_ether_addr(hdr->addr1) &&
+ (!rx->sta || !test_sta_flag(rx->sta, WLAN_STA_ASSOC))) {
+ if (cfg80211_rx_spurious_frame(rx->sdata->dev, hdr->addr2,
+ rx->link_id, GFP_ATOMIC))
+ return RX_DROP_U_SPURIOUS_NOTIF;
+ return RX_DROP_U_SPURIOUS;
+ }
+ return RX_CONTINUE;
+ }
+
if (unlikely((ieee80211_is_data(hdr->frame_control) ||
ieee80211_is_pspoll(hdr->frame_control)) &&
rx->sdata->vif.type != NL80211_IFTYPE_ADHOC &&
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH wireless-next 15/15] wifi: mac80211: allow add_key on NAN interfaces
2026-03-25 21:15 [PATCH wireless-next 00/15] wifi: mac80211: add NAN support Miri Korenblit
` (13 preceding siblings ...)
2026-03-25 21:15 ` [PATCH wireless-next 14/15] wifi: mac80211: report and drop spurious NAN Data frames Miri Korenblit
@ 2026-03-25 21:15 ` Miri Korenblit
14 siblings, 0 replies; 16+ messages in thread
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless
Cc: Avraham Stern, Ilan Peer, Miriam Rachel Korenblit, Johannes Berg
From: Avraham Stern <avraham.stern@intel.com>
Keys may be added to the NAN interfaces to protect
NAN management frames and data, allow that.
Signed-off-by: Avraham Stern <avraham.stern@intel.com>
Reviewed-by: Ilan Peer <ilan.peer@intel.com>
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 8cdbefac1bee..210f0030f228 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -702,6 +702,8 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
break;
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_AP_VLAN:
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
/* Keys without a station are used for TX only */
if (sta && test_sta_flag(sta, WLAN_STA_MFP))
key->conf.flags |= IEEE80211_KEY_FLAG_RX_MGMT;
@@ -718,13 +720,11 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
case NL80211_IFTYPE_WDS:
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_P2P_DEVICE:
- case NL80211_IFTYPE_NAN:
case NL80211_IFTYPE_UNSPECIFIED:
case NUM_NL80211_IFTYPES:
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_OCB:
- case NL80211_IFTYPE_NAN_DATA:
/* shouldn't happen */
WARN_ON_ONCE(1);
break;
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread