Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches
@ 2026-05-06  3:44 Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 01/14] wifi: mac80211_hwsim: limit TX of frames to the NAN DW Miri Korenblit
                   ` (13 more replies)
  0 siblings, 14 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless

Hi,

This series completes the hwsim support for NAN.

Thanks,
Miri
---

Benjamin Berg (5):
  wifi: mac80211_hwsim: limit TX of frames to the NAN DW
  wifi: mac80211_hwsim: select NAN TX channel based on current TSF
  wifi: mac80211_hwsim: only RX on NAN when active on a slot
  wifi: mac80211_hwsim: protect tsf_offset using a spinlock
  wifi: mac80211_hwsim: implement NAN synchronization

Daniel Gabay (7):
  wifi: mac80211_hwsim: add NAN_DATA interface limits
  wifi: mac80211_hwsim: add NAN PHY capabilities
  wifi: mac80211_hwsim: implement NAN schedule callbacks
  wifi: mac80211_hwsim: set HAS_RATE_CONTROL when using NAN
  wifi: mac80211_hwsim: add NAN data path TX/RX support
  wifi: mac80211_hwsim: Declare support for secure NAN
  wifi: mac80211_hwsim: enable NAN_DATA interface simulation support

Ilan Peer (2):
  wifi: mac80211_hwsim: Do not declare support for NDPE
  wifi: mac80211_hwsim: Support Tx of multicast data on NAN

 .../net/wireless/virtual/mac80211_hwsim_i.h   |   33 +-
 .../wireless/virtual/mac80211_hwsim_main.c    |  263 +++-
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 1191 ++++++++++++++++-
 .../net/wireless/virtual/mac80211_hwsim_nan.h |   74 +-
 4 files changed, 1454 insertions(+), 107 deletions(-)

-- 
2.34.1
---
v2: fix checkpatch in one of the patches


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 01/14] wifi: mac80211_hwsim: limit TX of frames to the NAN DW
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 02/14] wifi: mac80211_hwsim: select NAN TX channel based on current TSF Miri Korenblit
                   ` (12 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg

From: Benjamin Berg <benjamin.berg@intel.com>

Frames submitted on the NAN device interface should only be transmitted
during one of the discovery windows (DWs). It is assumed that software
submits frames from the DW end notifications for the next DW period.

Simulate this behaviour by checking that we are currently in a DW before
transmitting from ieee80211_hwsim_wake_tx_queue. As frames will be
queued up at the start of a DW, wake the management TX queue every time
a DW is started. Do so with a randomized offset just to avoid every
client transmitting at the same time.

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/virtual/mac80211_hwsim_i.h   |  3 +
 .../wireless/virtual/mac80211_hwsim_main.c    | 11 ++-
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 79 +++++++++++++++++++
 .../net/wireless/virtual/mac80211_hwsim_nan.h |  6 ++
 4 files changed, 97 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index 6b2a5dccb106..5432de92beab 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -136,4 +136,7 @@ u64 mac80211_hwsim_boottime_to_tsf(struct mac80211_hwsim_data *data,
 u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
 			   struct ieee80211_vif *vif);
 
+void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,
+				   struct ieee80211_txq *txq);
+
 #endif /* __MAC80211_HWSIM_I_H */
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 3a0c4366dfdb..6740504b30e6 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -2235,14 +2235,18 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
 	ieee80211_tx_status_irqsafe(hw, skb);
 }
 
-static void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,
-					  struct ieee80211_txq *txq)
+void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,
+				   struct ieee80211_txq *txq)
 {
 	struct ieee80211_tx_control control = {
 		.sta = txq->sta,
 	};
 	struct sk_buff *skb;
 
+	if (txq->vif->type == NL80211_IFTYPE_NAN &&
+	    !mac80211_hwsim_nan_txq_transmitting(hw, txq))
+		return;
+
 	while ((skb = ieee80211_tx_dequeue(hw, txq)))
 		mac80211_hwsim_tx(hw, &control, skb);
 }
@@ -5603,6 +5607,9 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 		hrtimer_setup(&data->nan.slot_timer,
 			      mac80211_hwsim_nan_slot_timer,
 			      CLOCK_BOOTTIME, HRTIMER_MODE_ABS_SOFT);
+		hrtimer_setup(&data->nan.resume_txqs_timer,
+			      mac80211_hwsim_nan_resume_txqs_timer,
+			      CLOCK_BOOTTIME, HRTIMER_MODE_ABS_SOFT);
 	}
 
 	data->if_combination.radar_detect_widths =
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index aa4aef0920f4..22805c3723e6 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -26,8 +26,13 @@
 static_assert(16 * DWST_TU * 1024 == 8192 * 1024);
 static_assert(DW0_TSF_MASK + 1 == 8192 * 1024);
 
+/* Quiet time at the end of each slot where TX is suppressed */
+#define NAN_CHAN_SWITCH_TIME_US		256
+
 static u8 hwsim_nan_cluster_id[ETH_ALEN];
 
+static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data);
+
 static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
 {
 	ktime_t expires = hrtimer_get_expires(&data->nan.slot_timer);
@@ -130,6 +135,8 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
 		cfg80211_next_nan_dw_notif(wdev, notify_dw_chan, GFP_ATOMIC);
 	}
 
+	mac80211_hwsim_nan_resume_txqs(data);
+
 	mac80211_hwsim_nan_schedule_slot(data, slot + 1);
 
 	return HRTIMER_RESTART;
@@ -190,6 +197,7 @@ int mac80211_hwsim_nan_stop(struct ieee80211_hw *hw,
 		return -EINVAL;
 
 	hrtimer_cancel(&data->nan.slot_timer);
+	hrtimer_cancel(&data->nan.resume_txqs_timer);
 	data->nan.device_vif = NULL;
 
 	spin_lock_bh(&hwsim_radio_lock);
@@ -231,3 +239,74 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
 
 	return 0;
 }
+
+static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data)
+{
+	u32 timeout_ns;
+
+	/* Nothing to do if we are not in a DW */
+	if (!mac80211_hwsim_nan_txq_transmitting(data->hw,
+						 data->nan.device_vif->txq_mgmt))
+		return;
+
+	/*
+	 * Wait a bit and also randomize things so that not everyone is TXing
+	 * at the same time. Each slot is 16 TU long, this waits between 100 us
+	 * and 5 ms before starting to TX (unless a new frame arrives).
+	 */
+	timeout_ns = get_random_u32_inclusive(100 * NSEC_PER_USEC,
+					      5 * NSEC_PER_MSEC);
+
+	hrtimer_start(&data->nan.resume_txqs_timer,
+		      ns_to_ktime(timeout_ns),
+		      HRTIMER_MODE_REL_SOFT);
+}
+
+enum hrtimer_restart
+mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
+{
+	struct mac80211_hwsim_data *data =
+		container_of(timer, struct mac80211_hwsim_data,
+			     nan.resume_txqs_timer);
+
+	guard(rcu)();
+
+	/* Wake TX queue for management frames on the NAN device interface */
+	if (mac80211_hwsim_nan_txq_transmitting(data->hw,
+						data->nan.device_vif->txq_mgmt))
+		ieee80211_hwsim_wake_tx_queue(data->hw,
+					      data->nan.device_vif->txq_mgmt);
+
+	return HRTIMER_NORESTART;
+}
+
+bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
+					 struct ieee80211_txq *txq)
+{
+	struct mac80211_hwsim_data *data = hw->priv;
+	u64 tsf;
+	u8 slot;
+
+	if (WARN_ON_ONCE(!data->nan.device_vif))
+		return true;
+
+	tsf = mac80211_hwsim_get_tsf(hw, data->nan.device_vif);
+	slot = hwsim_nan_slot_from_tsf(tsf);
+
+	/* Enforce a maximum channel switch time and guard against TX delays */
+	if (slot != hwsim_nan_slot_from_tsf(tsf + NAN_CHAN_SWITCH_TIME_US))
+		return false;
+
+	/* Check NAN device interface management frame transmission */
+	if (!txq->sta) {
+		/* Only transmit these during one of the DWs */
+		if (slot == SLOT_24GHZ_DW ||
+		    (slot == SLOT_5GHZ_DW &&
+		     (data->nan.bands & BIT(NL80211_BAND_5GHZ))))
+			return true;
+
+		return false;
+	}
+
+	return true;
+}
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index e86e7f9e9a3c..6a0780797273 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -15,11 +15,14 @@ struct mac80211_hwsim_nan_data {
 	struct ieee80211_channel *channel;
 
 	struct hrtimer slot_timer;
+	struct hrtimer resume_txqs_timer;
 	bool notify_dw;
 };
 
 enum hrtimer_restart
 mac80211_hwsim_nan_slot_timer(struct hrtimer *timer);
+enum hrtimer_restart
+mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer);
 
 int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
 			     struct ieee80211_vif *vif,
@@ -33,4 +36,7 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
 				     struct cfg80211_nan_conf *conf,
 				     u32 changes);
 
+bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
+					 struct ieee80211_txq *txq);
+
 #endif /* __MAC80211_HWSIM_NAN_H */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 02/14] wifi: mac80211_hwsim: select NAN TX channel based on current TSF
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 01/14] wifi: mac80211_hwsim: limit TX of frames to the NAN DW Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 03/14] wifi: mac80211_hwsim: only RX on NAN when active on a slot Miri Korenblit
                   ` (11 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg

From: Benjamin Berg <benjamin.berg@intel.com>

Move the TX channel selection into the NAN specific file and select the
channel based on the current slot.

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/virtual/mac80211_hwsim_main.c |  6 +-----
 .../net/wireless/virtual/mac80211_hwsim_nan.c  | 18 ++++++++++++++++++
 .../net/wireless/virtual/mac80211_hwsim_nan.h  |  3 +++
 3 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 6740504b30e6..f0f6cb7fa894 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -2084,11 +2084,7 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
 	hdr = (void *)skb->data;
 
 	if (vif && vif->type == NL80211_IFTYPE_NAN && !data->tmp_chan) {
-		/* For NAN Device simulation purposes, assume that NAN is always
-		 * on channel 6 or channel 149, unless a ROC is in progress (for
-		 * USD use cases).
-		 */
-		channel = data->nan.channel;
+		channel = mac80211_hwsim_nan_get_tx_channel(hw);
 
 		if (WARN_ON(!channel)) {
 			ieee80211_free_txskb(hw, skb);
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 22805c3723e6..10bbac9a4b55 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -310,3 +310,21 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 
 	return true;
 }
+
+struct ieee80211_channel *
+mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw)
+{
+	struct mac80211_hwsim_data *data = hw->priv;
+	u64 tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
+	u8 slot = hwsim_nan_slot_from_tsf(tsf);
+
+	if (slot == SLOT_24GHZ_DW)
+		return ieee80211_get_channel(hw->wiphy, 2437);
+
+	if (slot == SLOT_5GHZ_DW &&
+	    data->nan.bands & BIT(NL80211_BAND_5GHZ))
+		return ieee80211_get_channel(hw->wiphy, 5745);
+
+	/* drop frame and warn, NAN_CHAN_SWITCH_TIME_US should avoid races */
+	return NULL;
+}
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index 6a0780797273..796cc17d194e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -39,4 +39,7 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
 bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 					 struct ieee80211_txq *txq);
 
+struct ieee80211_channel *
+mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw);
+
 #endif /* __MAC80211_HWSIM_NAN_H */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 03/14] wifi: mac80211_hwsim: only RX on NAN when active on a slot
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 01/14] wifi: mac80211_hwsim: limit TX of frames to the NAN DW Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 02/14] wifi: mac80211_hwsim: select NAN TX channel based on current TSF Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 04/14] wifi: mac80211_hwsim: protect tsf_offset using a spinlock Miri Korenblit
                   ` (10 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg

From: Benjamin Berg <benjamin.berg@intel.com>

This moves the NAN receive into the main code and changes it so that
frame RX only happens when the device is active on the channel. This
limits RX to the DW slots as there is currently no datapath.

With this the globally stored channel is obsolete, remove it.

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../wireless/virtual/mac80211_hwsim_main.c    | 26 ++++++++----
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 41 +++++++++++++++++--
 .../net/wireless/virtual/mac80211_hwsim_nan.h |  7 ++--
 3 files changed, 58 insertions(+), 16 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index f0f6cb7fa894..76f1028bc42e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1731,6 +1731,8 @@ static bool hwsim_chans_compat(struct ieee80211_channel *c1,
 
 struct tx_iter_data {
 	struct ieee80211_channel *channel;
+	struct ieee80211_rx_status *rx_status;
+	struct ieee80211_hw *hw;
 	bool receive;
 };
 
@@ -1740,13 +1742,10 @@ static void mac80211_hwsim_tx_iter(void *_data, u8 *addr,
 	struct tx_iter_data *data = _data;
 	int i;
 
-	/* For NAN Device simulation purposes, assume that NAN is always
-	 * on channel 6 or channel 149.
-	 */
 	if (vif->type == NL80211_IFTYPE_NAN) {
-		data->receive = (data->channel &&
-				 (data->channel->center_freq == 2437 ||
-				  data->channel->center_freq == 5745));
+		data->receive = mac80211_hwsim_nan_receive(data->hw,
+							   data->channel,
+							   data->rx_status);
 		return;
 	}
 
@@ -1923,7 +1922,9 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw,
 		struct sk_buff *nskb;
 		struct tx_iter_data tx_iter_data = {
 			.receive = false,
+			.hw = data2->hw,
 			.channel = chan,
+			.rx_status = &rx_status,
 		};
 
 		if (data == data2)
@@ -1939,6 +1940,12 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw,
 		if (data->netgroup != data2->netgroup)
 			continue;
 
+		/*
+		 * Set mactime early since NAN RX filtering relies on it
+		 * for slot calculation
+		 */
+		rx_status.mactime = sim_tsf + data2->tsf_offset;
+
 		if (!hwsim_chans_compat(chan, data2->tmp_chan) &&
 		    !hwsim_chans_compat(chan, data2->channel)) {
 			ieee80211_iterate_active_interfaces_atomic(
@@ -1975,8 +1982,6 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw,
 		if (mac80211_hwsim_addr_match(data2, hdr->addr1))
 			ack = true;
 
-		rx_status.mactime = sim_tsf + data2->tsf_offset;
-
 		mac80211_hwsim_rx(data2, &rx_status, nskb);
 	}
 	spin_unlock(&hwsim_radio_lock);
@@ -6286,7 +6291,10 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2,
 	/* A frame is received from user space */
 	memset(&rx_status, 0, sizeof(rx_status));
 	if (info->attrs[HWSIM_ATTR_FREQ]) {
-		struct tx_iter_data iter_data = {};
+		struct tx_iter_data iter_data = {
+			.hw = data2->hw,
+			.rx_status = &rx_status,
+		};
 
 		/* throw away off-channel packets, but allow both the temporary
 		 * ("hw" scan/remain-on-channel), regular channels and links,
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 10bbac9a4b55..805848172605 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -96,7 +96,6 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
 	case SLOT_24GHZ_DW:
 		wiphy_dbg(data->hw->wiphy, "Start of 2.4 GHz DW, is DW0=%d\n",
 			  dwst_of_dw0);
-		data->nan.channel = ieee80211_get_channel(hw->wiphy, 2437);
 		break;
 
 	case SLOT_24GHZ_DW + 1:
@@ -111,8 +110,6 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
 	case SLOT_5GHZ_DW:
 		if (data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
 			wiphy_dbg(data->hw->wiphy, "Start of 5 GHz DW\n");
-			data->nan.channel =
-				ieee80211_get_channel(hw->wiphy, 5745);
 		}
 		break;
 
@@ -158,7 +155,6 @@ int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
 	/* set this before starting the timer, as preemption might occur */
 	data->nan.device_vif = vif;
 	data->nan.bands = conf->bands;
-	data->nan.channel = ieee80211_get_channel(hw->wiphy, 2437);
 
 	/* Just run this "soon" and start in a random schedule position */
 	hrtimer_start(&data->nan.slot_timer,
@@ -328,3 +324,40 @@ mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw)
 	/* drop frame and warn, NAN_CHAN_SWITCH_TIME_US should avoid races */
 	return NULL;
 }
+
+bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
+				struct ieee80211_channel *channel,
+				struct ieee80211_rx_status *rx_status)
+{
+	struct mac80211_hwsim_data *data = hw->priv;
+	u8 slot;
+
+	if (WARN_ON_ONCE(!data->nan.device_vif))
+		return false;
+
+	if (rx_status->rx_flags & RX_FLAG_MACTIME) {
+		slot = hwsim_nan_slot_from_tsf(rx_status->mactime);
+	} else {
+		u64 tsf;
+
+		/*
+		 * This is not perfect, but that should be fine.
+		 *
+		 * Assume the frame might be a bit early in relation to our
+		 * own TSF. This is largely because the TSF sync is going to be
+		 * pretty bad when the frame was RXed via NL and the beacon as
+		 * well as RX timestamps are not accurate.
+		 */
+		tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
+		slot = hwsim_nan_slot_from_tsf(tsf + 128);
+	}
+
+	if (slot == SLOT_24GHZ_DW && channel->center_freq == 2437)
+		return true;
+
+	if (slot == SLOT_5GHZ_DW && data->nan.bands & BIT(NL80211_BAND_5GHZ) &&
+	    channel->center_freq == 5745)
+		return true;
+
+	return false;
+}
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index 796cc17d194e..af8dd7ff00cc 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -11,9 +11,6 @@ struct mac80211_hwsim_nan_data {
 	struct ieee80211_vif *device_vif;
 	u8 bands;
 
-	/* Current channel of the NAN device */
-	struct ieee80211_channel *channel;
-
 	struct hrtimer slot_timer;
 	struct hrtimer resume_txqs_timer;
 	bool notify_dw;
@@ -42,4 +39,8 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 struct ieee80211_channel *
 mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw);
 
+bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
+				struct ieee80211_channel *channel,
+				struct ieee80211_rx_status *rx_status);
+
 #endif /* __MAC80211_HWSIM_NAN_H */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 04/14] wifi: mac80211_hwsim: protect tsf_offset using a spinlock
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
                   ` (2 preceding siblings ...)
  2026-05-06  3:44 ` [PATCH v2 wireless-next 03/14] wifi: mac80211_hwsim: only RX on NAN when active on a slot Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 05/14] wifi: mac80211_hwsim: implement NAN synchronization Miri Korenblit
                   ` (9 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg

From: Benjamin Berg <benjamin.berg@intel.com>

To implement NAN synchronization in hwsim, the TSF needs to be adjusted
regularly from the RX path. Add a spinlock so that this can be done in a
safe manner.

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/virtual/mac80211_hwsim_i.h   |  1 +
 .../wireless/virtual/mac80211_hwsim_main.c    | 34 ++++++++++++++-----
 2 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index 5432de92beab..7a6495ea79e7 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -102,6 +102,7 @@ struct mac80211_hwsim_data {
 	u32 wmediumd;
 
 	/* difference between this hw's clock and the real clock, in usecs */
+	spinlock_t tsf_offset_lock;
 	s64 tsf_offset;
 
 	/* Stats */
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 76f1028bc42e..2de44c5fb1ff 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1256,13 +1256,17 @@ static inline u64 mac80211_hwsim_get_sim_tsf(void)
 ktime_t mac80211_hwsim_tsf_to_boottime(struct mac80211_hwsim_data *data,
 				       u64 tsf)
 {
-	return us_to_ktime(tsf - data->tsf_offset);
+	scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+		return us_to_ktime(tsf - data->tsf_offset);
+	}
 }
 
 u64 mac80211_hwsim_boottime_to_tsf(struct mac80211_hwsim_data *data,
 				   ktime_t ts)
 {
-	return ktime_to_us(ts + data->tsf_offset);
+	scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+		return ktime_to_us(ts) + data->tsf_offset;
+	}
 }
 
 u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
@@ -1271,14 +1275,18 @@ u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
 	struct mac80211_hwsim_data *data = hw->priv;
 	u64 sim_time = mac80211_hwsim_get_sim_tsf();
 
-	return sim_time + data->tsf_offset;
+	scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+		return sim_time + data->tsf_offset;
+	}
 }
 
 static __le64 __mac80211_hwsim_get_tsf(struct mac80211_hwsim_data *data)
 {
 	u64 sim_time = mac80211_hwsim_get_sim_tsf();
 
-	return cpu_to_le64(sim_time + data->tsf_offset);
+	scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+		return cpu_to_le64(sim_time + data->tsf_offset);
+	}
 }
 
 static void mac80211_hwsim_set_tsf(struct ieee80211_hw *hw,
@@ -1293,11 +1301,13 @@ static void mac80211_hwsim_set_tsf(struct ieee80211_hw *hw,
 	if (conf && !conf->enable_beacon)
 		return;
 
-	/* adjust after beaconing with new timestamp at old TBTT */
-	if (tsf > now)
-		data->tsf_offset += delta;
-	else
-		data->tsf_offset -= delta;
+	scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+		/* adjust after beaconing with new timestamp at old TBTT */
+		if (tsf > now)
+			data->tsf_offset += delta;
+		else
+			data->tsf_offset -= delta;
+	}
 }
 
 static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw,
@@ -1577,6 +1587,8 @@ static void mac80211_hwsim_write_tsf(struct mac80211_hwsim_data *data,
 	/* TODO: get MCS */
 	int bitrate = 100;
 
+	spin_lock_bh(&data->tsf_offset_lock);
+
 	txrate = ieee80211_get_tx_rate(data->hw, info);
 	if (txrate)
 		bitrate = txrate->bitrate;
@@ -1602,6 +1614,8 @@ static void mac80211_hwsim_write_tsf(struct mac80211_hwsim_data *data,
 							  10 * 8 * 10 /
 							  bitrate);
 	}
+
+	spin_unlock_bh(&data->tsf_offset_lock);
 }
 
 static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw,
@@ -5664,6 +5678,8 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 	hw->wiphy->mbssid_max_interfaces = 8;
 	hw->wiphy->ema_max_profile_periodicity = 3;
 
+	spin_lock_init(&data->tsf_offset_lock);
+
 	data->rx_rssi = DEFAULT_RX_RSSI;
 
 	INIT_DELAYED_WORK(&data->roc_start, hw_roc_start);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 05/14] wifi: mac80211_hwsim: implement NAN synchronization
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
                   ` (3 preceding siblings ...)
  2026-05-06  3:44 ` [PATCH v2 wireless-next 04/14] wifi: mac80211_hwsim: protect tsf_offset using a spinlock Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 06/14] wifi: mac80211_hwsim: add NAN_DATA interface limits Miri Korenblit
                   ` (8 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg

From: Benjamin Berg <benjamin.berg@intel.com>

Add all the handling to do NAN synchronization on 2.4 GHz including
sending out beacons. With this, the mac80211_hwsim NAN device also works
when used in conjunction with an external medium simulation.

Note that the TSF sync is not ideal in case of an external medium
simulation. This is because the mactime for received frames needs to be
estimated and the simulation may not update the timestamp of beacons
to the actual time that the frame was transmitted.

The implementation has an initial short phase where it scans for
clusters. This facilitates cluster joining and avoids creating a new
cluster immediately, which would result in two cluster join
notifications. It does not scan otherwise and will only see another
cluster appearing if a discovery beacon happens to be sent during the
2.4 GHz discovery window (DW).

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/virtual/mac80211_hwsim_i.h   |   4 +
 .../wireless/virtual/mac80211_hwsim_main.c    |  18 +-
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 723 +++++++++++++++++-
 .../net/wireless/virtual/mac80211_hwsim_nan.h |  43 ++
 4 files changed, 743 insertions(+), 45 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index 7a6495ea79e7..5378f721c299 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -137,6 +137,10 @@ u64 mac80211_hwsim_boottime_to_tsf(struct mac80211_hwsim_data *data,
 u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
 			   struct ieee80211_vif *vif);
 
+void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
+			     struct sk_buff *skb,
+			     struct ieee80211_channel *chan);
+
 void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,
 				   struct ieee80211_txq *txq);
 
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 2de44c5fb1ff..969ebc28cb1e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -957,10 +957,6 @@ static int hwsim_get_chanwidth(enum nl80211_chan_width bw)
 	return INT_MAX;
 }
 
-static void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
-				    struct sk_buff *skb,
-				    struct ieee80211_channel *chan);
-
 /* sysfs attributes */
 static void hwsim_send_ps_poll(void *dat, u8 *mac, struct ieee80211_vif *vif)
 {
@@ -1865,6 +1861,9 @@ static void mac80211_hwsim_rx(struct mac80211_hwsim_data *data,
 
 	mac80211_hwsim_add_vendor_rtap(skb);
 
+	if (data->nan.device_vif)
+		mac80211_hwsim_nan_rx(data->hw, skb);
+
 	data->rx_pkts++;
 	data->rx_bytes += skb->len;
 	ieee80211_rx_irqsafe(data->hw, skb);
@@ -2359,9 +2358,9 @@ static void mac80211_hwsim_remove_interface(
 		mac80211_hwsim_config_mac_nl(hw, vif->addr, false);
 }
 
-static void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
-				    struct sk_buff *skb,
-				    struct ieee80211_channel *chan)
+void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
+			     struct sk_buff *skb,
+			     struct ieee80211_channel *chan)
 {
 	struct mac80211_hwsim_data *data = hw->priv;
 	u32 _portid = READ_ONCE(data->wmediumd);
@@ -5625,6 +5624,11 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 		hrtimer_setup(&data->nan.resume_txqs_timer,
 			      mac80211_hwsim_nan_resume_txqs_timer,
 			      CLOCK_BOOTTIME, HRTIMER_MODE_ABS_SOFT);
+		hrtimer_setup(&data->nan.discovery_beacon_timer,
+			      mac80211_hwsim_nan_discovery_beacon_timer,
+			      CLOCK_BOOTTIME, HRTIMER_MODE_ABS_SOFT);
+
+		spin_lock_init(&data->nan.state_lock);
 	}
 
 	data->if_combination.radar_detect_widths =
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 805848172605..6053b6f8f91f 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -1,9 +1,10 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
  * mac80211_hwsim_nan - NAN software simulation for mac80211_hwsim
- * Copyright (C) 2025 Intel Corporation
+ * Copyright (C) 2025-2026 Intel Corporation
  */
 
+#include <net/cfg80211.h>
 #include "mac80211_hwsim_i.h"
 
 /* Defined as the lower 23 bits being zero */
@@ -26,11 +27,15 @@
 static_assert(16 * DWST_TU * 1024 == 8192 * 1024);
 static_assert(DW0_TSF_MASK + 1 == 8192 * 1024);
 
+/* warmup phase should be 120 seconds, which is approximately 225 DWSTs */
+#define NAN_WARMUP_DWST		225
+
+#define NAN_RSSI_CLOSE (-60)
+#define NAN_RSSI_MIDDLE (-75)
+
 /* Quiet time at the end of each slot where TX is suppressed */
 #define NAN_CHAN_SWITCH_TIME_US		256
 
-static u8 hwsim_nan_cluster_id[ETH_ALEN];
-
 static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data);
 
 static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
@@ -45,10 +50,43 @@ static u8 hwsim_nan_slot_from_tsf(u64 tsf)
 	return (tsf & DWST_TSF_MASK) / ieee80211_tu_to_usec(SLOT_TU);
 }
 
+static u64 hwsim_nan_encode_master_rank(u8 master_pref, u8 random_factor,
+					const u8 *addr)
+{
+	return ((u64)master_pref << 56) +
+		((u64)random_factor << 48) +
+		((u64)addr[5] << 40) +
+		((u64)addr[4] << 32) +
+		((u64)addr[3] << 24) +
+		((u64)addr[2] << 16) +
+		((u64)addr[1] << 8) +
+		((u64)addr[0] << 0);
+}
+
+static u64 hwsim_nan_get_master_rank(struct mac80211_hwsim_data *data)
+{
+	u8 master_pref = 0;
+	u8 random_factor = 0;
+
+	if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_UP) {
+		master_pref = data->nan.master_pref;
+		random_factor = data->nan.random_factor;
+	}
+
+	return hwsim_nan_encode_master_rank(master_pref, random_factor,
+					    data->nan.device_vif->addr);
+}
+
 static void
-mac80211_hwsim_nan_schedule_slot(struct mac80211_hwsim_data *data, u8 slot)
+mac80211_hwsim_nan_schedule_slot(struct mac80211_hwsim_data *data, u8 slot,
+				 bool discontinuity)
 {
-	u64 tsf = hwsim_nan_get_timer_tsf(data);
+	u64 tsf;
+
+	if (!discontinuity)
+		tsf = hwsim_nan_get_timer_tsf(data);
+	else
+		tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
 
 	/* Only called by mac80211_hwsim_nan_dw_timer from softirq context */
 	lockdep_assert_in_softirq();
@@ -60,16 +98,561 @@ mac80211_hwsim_nan_schedule_slot(struct mac80211_hwsim_data *data, u8 slot)
 			    mac80211_hwsim_tsf_to_boottime(data, tsf));
 }
 
+void mac80211_hwsim_nan_rx(struct ieee80211_hw *hw,
+			   struct sk_buff *skb)
+{
+	struct mac80211_hwsim_data *data = hw->priv;
+	const struct ieee80211_mgmt *mgmt = (void *)skb->data;
+	struct element *nan_elem = (void *)mgmt->u.beacon.variable;
+	struct ieee80211_nan_anchor_master_info *ami = NULL;
+	const struct ieee80211_nan_attr *nan_attr;
+	struct ieee80211_rx_status rx_status;
+	bool joined_cluster = false;
+	bool adopt_tsf = false;
+	bool is_sync_beacon;
+	bool is_same_cluster;
+	u64 master_rank = 0;
+	ssize_t data_len;
+	u8 slot;
+
+	/* Need a NAN vendor element at the start */
+	if (skb->len < (offsetofend(struct ieee80211_mgmt, u.beacon) + 6) ||
+	    !ieee80211_is_beacon(mgmt->frame_control))
+		return;
+
+	data_len = skb->len - offsetofend(struct ieee80211_mgmt, u.beacon);
+
+	/* Copy the RX status to add a MAC timestamp if needed */
+	memcpy(&rx_status, IEEE80211_SKB_RXCB(skb),
+	       sizeof(struct ieee80211_rx_status));
+
+	/* And deal with the lack of mac time stamp */
+	if ((rx_status.flag & RX_FLAG_MACTIME) != RX_FLAG_MACTIME_START) {
+		u64 tsf = mac80211_hwsim_get_tsf(hw, data->nan.device_vif);;
+
+		/* In that case there should be no timestamp */
+		WARN_ON_ONCE(rx_status.flag & RX_FLAG_MACTIME);
+
+		/* No mac timestamp, set current TSF for the frame end */
+		rx_status.flag |= RX_FLAG_MACTIME_END;
+		rx_status.mactime = tsf;
+
+		/* And translate to the start for the rest of the code */
+		rx_status.mactime =
+			ieee80211_calculate_rx_timestamp(hw, &rx_status,
+							 skb->len, 0);
+		rx_status.flag &= ~RX_FLAG_MACTIME;
+		rx_status.flag |= RX_FLAG_MACTIME_START;
+
+		/* Match mac80211_hwsim_nan_receive, see comment there */
+		slot = hwsim_nan_slot_from_tsf(tsf + 128);
+	} else {
+		slot = hwsim_nan_slot_from_tsf(rx_status.mactime);
+	}
+
+	/*
+	 * (overly) simplify things, only track 2.4 GHz here. Also, ignore
+	 * frames outside of the 2.4 GHz DW slot, unless in the initial SCAN
+	 * phase.
+	 */
+	if ((slot != SLOT_24GHZ_DW &&
+	     data->nan.phase != MAC80211_HWSIM_NAN_PHASE_SCAN) ||
+	    rx_status.freq != 2437)
+		return;
+
+	/* Just ignore low RSSI beacons that we cannot sync to */
+	if (rx_status.signal < NAN_RSSI_MIDDLE)
+		return;
+
+	/* Needs to be a valid NAN cluster ID in A3 */
+	if (get_unaligned_be32(mgmt->bssid) != ((WLAN_OUI_WFA << 8) | 0x01))
+		return;
+
+	/* We are only interested in NAN beacons */
+	if (nan_elem->id != WLAN_EID_VENDOR_SPECIFIC ||
+	    nan_elem->datalen < 4 ||
+	    get_unaligned_be32(nan_elem->data) !=
+	    (WLAN_OUI_WFA << 8 | WLAN_OUI_TYPE_WFA_NAN))
+		return;
+
+	u8 *nan_defragmented __free(kfree) = kzalloc(data_len, GFP_ATOMIC);
+	if (!nan_defragmented)
+		return;
+
+	data_len = cfg80211_defragment_element(nan_elem,
+					       mgmt->u.beacon.variable,
+					       data_len,
+					       nan_defragmented, data_len,
+					       WLAN_EID_FRAGMENT);
+
+	if (data_len < 0)
+		return;
+
+	/* Assume it is a synchronization beacon if beacon_int is 512 TUs */
+	is_sync_beacon = le16_to_cpu(mgmt->u.beacon.beacon_int) == DWST_TU;
+	is_same_cluster = ether_addr_equal(mgmt->bssid, data->nan.cluster_id);
+
+	for_each_nan_attr(nan_attr, nan_defragmented + 4, data_len - 4) {
+		if (nan_attr->attr == NAN_ATTR_MASTER_INDICATION &&
+		    le16_to_cpu(nan_attr->length) >=
+		    sizeof(struct ieee80211_nan_master_indication)) {
+			struct ieee80211_nan_master_indication *mi =
+				(void *)nan_attr->data;
+
+			master_rank =
+				hwsim_nan_encode_master_rank(mi->master_pref,
+							     mi->random_factor,
+							     mgmt->sa);
+		}
+
+		if (nan_attr->attr == NAN_ATTR_CLUSTER_INFO &&
+		    le16_to_cpu(nan_attr->length) >=
+		    sizeof(struct ieee80211_nan_anchor_master_info)) {
+			ami = (void *)nan_attr->data;
+
+			/*
+			 * The AMBTT should be set to the beacon timestamp when
+			 * the sender is the anchor master. We can simply
+			 * modify the structure because we created a copy when
+			 * defragmenting the NAN element.
+			 */
+			if (ami->hop_count == 0)
+				ami->ambtt = cpu_to_le32(
+					le64_to_cpu(mgmt->u.beacon.timestamp));
+		}
+	}
+
+	/* Do the rest of the processing under lock */
+	spin_lock_bh(&data->nan.state_lock);
+
+	/*
+	 * sync beacon should be discarded if the master rank is the same
+	 * and the AMBTT is older than 16 * 512 TUs compared to our own TSF.
+	 *
+	 * Subtract the AMBTT from the lowered TSF. If the AMBTT is older
+	 * (smaller) then the calculation will not underflow.
+	 */
+	if (is_sync_beacon && ami &&
+	    ami->master_rank == data->nan.current_ami.master_rank &&
+	    (((u32)rx_status.mactime -
+	      ieee80211_tu_to_usec(16 * 512)) -
+	     le32_to_cpu(ami->ambtt)) < 0x8000000) {
+		wiphy_dbg(hw->wiphy,
+			  "NAN: ignoring sync beacon with old AMBTT\n");
+		is_sync_beacon = false;
+	}
+
+	if (is_same_cluster && is_sync_beacon &&
+	    master_rank > hwsim_nan_get_master_rank(data)) {
+		if (rx_status.signal > NAN_RSSI_CLOSE)
+			data->nan.master_transition_score += 3;
+		else
+			data->nan.master_transition_score += 1;
+	}
+
+	if (is_same_cluster && is_sync_beacon && ami &&
+	    ((ami->master_rank == data->nan.current_ami.master_rank &&
+	      ami->hop_count < data->nan.current_ami.hop_count) ||
+	     (master_rank > hwsim_nan_get_master_rank(data) &&
+	      ami->hop_count == data->nan.current_ami.hop_count))) {
+		if (rx_status.signal > NAN_RSSI_CLOSE)
+			data->nan.sync_transition_score += 3;
+		else
+			data->nan.sync_transition_score += 1;
+	}
+
+	/*
+	 * Decide on TSF adjustments before updating any other state
+	 */
+	if (is_same_cluster && is_sync_beacon && ami &&
+	    data->nan.current_ami.hop_count != 0) {
+		if (le64_to_cpu(ami->master_rank) >
+		    le64_to_cpu(data->nan.current_ami.master_rank) &&
+		    ami->master_rank != data->nan.last_ami.master_rank)
+			adopt_tsf = true;
+
+		if (le64_to_cpu(ami->master_rank) >
+		    le64_to_cpu(data->nan.current_ami.master_rank) &&
+		    ami->master_rank == data->nan.last_ami.master_rank &&
+		    le32_to_cpu(ami->ambtt) >
+		    le32_to_cpu(data->nan.last_ami.ambtt))
+			adopt_tsf = true;
+
+		if (le64_to_cpu(ami->master_rank) <
+		    le64_to_cpu(data->nan.current_ami.master_rank) &&
+		    le64_to_cpu(ami->master_rank) >
+		    hwsim_nan_get_master_rank(data) &&
+		    ether_addr_equal(ami->master_addr,
+				     data->nan.current_ami.master_addr))
+			adopt_tsf = true;
+
+		if (ami->master_rank == data->nan.current_ami.master_rank &&
+		    le32_to_cpu(ami->ambtt) >
+		    le32_to_cpu(data->nan.current_ami.ambtt))
+			adopt_tsf = true;
+
+		/* Anchor Master case is handled below */
+	}
+
+	/*
+	 * NAN Cluster merging
+	 */
+	if (!is_same_cluster && ami) {
+		u64 curr_amr;
+		u64 own_cg;
+		u64 frame_amr;
+		u64 cg;
+
+		/* Shifted down by 19 bits compared to spec */
+		frame_amr = le64_to_cpu(ami->master_rank);
+		cg = (u64)ami->master_pref << (64 - 19);
+		cg += le64_to_cpu(mgmt->u.beacon.timestamp) >> 19;
+
+		curr_amr = le64_to_cpu(data->nan.current_ami.master_rank);
+		own_cg = (u64)data->nan.current_ami.master_pref << (64 - 19);
+		own_cg += rx_status.mactime >> 19;
+
+		/*
+		 * Check if the cluster shall be joined
+		 *
+		 * When in the "scan" phase, just join immediately.
+		 */
+		if (cg > own_cg ||
+		    (cg == own_cg && frame_amr > curr_amr) ||
+		    data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN) {
+			/* Avoid a state transition */
+			data->nan.master_transition_score = 0;
+			data->nan.sync_transition_score = 0;
+
+			/*
+			 * NOTE: The spec says we should TX sync beacons on the
+			 * old schedule after joining. We do not implement this.
+			 */
+
+			wiphy_dbg(hw->wiphy, "NAN: joining cluster %pM\n",
+				  mgmt->bssid);
+
+			joined_cluster = true;
+			adopt_tsf = true;
+
+			memcpy(&data->nan.last_ami, &data->nan.current_ami,
+			       sizeof(data->nan.last_ami));
+			memcpy(&data->nan.current_ami, ami,
+			       sizeof(data->nan.last_ami));
+			data->nan.current_ami.hop_count += 1;
+
+			memcpy(data->nan.cluster_id, mgmt->bssid, ETH_ALEN);
+
+			/*
+			 * Assume we are UP if we joined a cluster.
+			 *
+			 * If the other anchor master is still in the warmup
+			 * phase, then we may temporarily become the anchor
+			 * master until it sets its own master preference to
+			 * be non-zero.
+			 */
+			data->nan.phase = MAC80211_HWSIM_NAN_PHASE_UP;
+			data->nan.random_factor_valid_dwst = 0;
+		}
+	}
+
+	/*
+	 * Anchor master selection
+	 */
+	/* We are not anchor master */
+	if (is_same_cluster && is_sync_beacon && ami &&
+	    data->nan.current_ami.hop_count != 0) {
+		if (le64_to_cpu(data->nan.current_ami.master_rank) <
+		    le64_to_cpu(ami->master_rank)) {
+			if (ami->master_rank == data->nan.last_ami.master_rank &&
+			    le32_to_cpu(ami->ambtt) <=
+			    le32_to_cpu(data->nan.last_ami.ambtt)) {
+				/* disregard frame */
+			} else {
+				memcpy(&data->nan.last_ami,
+				       &data->nan.current_ami,
+				       sizeof(data->nan.last_ami));
+				memcpy(&data->nan.current_ami, ami,
+				       sizeof(data->nan.last_ami));
+				data->nan.current_ami.hop_count += 1;
+			}
+		}
+
+		if (le64_to_cpu(data->nan.current_ami.master_rank) >
+		    le64_to_cpu(ami->master_rank)) {
+			if (!ether_addr_equal(data->nan.current_ami.master_addr,
+					      ami->master_addr)) {
+				/* disregard frame */
+			} else {
+				u64 amr = hwsim_nan_get_master_rank(data);
+
+				if (amr > le64_to_cpu(ami->master_rank)) {
+					/* assume ourselves as anchor master */
+					wiphy_dbg(hw->wiphy,
+						  "NAN: assume anchor master role\n");
+					data->nan.current_ami.master_rank =
+						cpu_to_le64(amr);
+					data->nan.current_ami.hop_count = 0;
+					memset(&data->nan.last_ami, 0,
+					       sizeof(data->nan.last_ami));
+					data->nan.last_ami.ambtt =
+						data->nan.current_ami.ambtt;
+					data->nan.current_ami.ambtt = 0;
+				} else {
+					memcpy(&data->nan.last_ami,
+					       &data->nan.current_ami,
+					       sizeof(data->nan.last_ami));
+					memcpy(&data->nan.current_ami, ami,
+					       sizeof(data->nan.last_ami));
+					data->nan.current_ami.hop_count += 1;
+				}
+			}
+		}
+
+		if (data->nan.current_ami.master_rank == ami->master_rank) {
+			if (le32_to_cpu(data->nan.current_ami.ambtt) <
+			    le32_to_cpu(ami->ambtt)) {
+				data->nan.current_ami.ambtt = ami->ambtt;
+			}
+
+			if (data->nan.current_ami.hop_count >
+			    ami->hop_count + 1) {
+				data->nan.current_ami.hop_count =
+					ami->hop_count + 1;
+			}
+		}
+	}
+
+	/* We are anchor master */
+	if (is_same_cluster && is_sync_beacon && ami &&
+	    data->nan.current_ami.hop_count == 0) {
+		WARN_ON_ONCE(!ether_addr_equal(data->nan.current_ami.master_addr,
+					       data->nan.device_vif->addr));
+
+		if (le64_to_cpu(ami->master_rank) <
+		    le64_to_cpu(data->nan.current_ami.master_rank) ||
+		    ether_addr_equal(ami->master_addr,
+				     data->nan.current_ami.master_addr)) {
+			/* disregard */
+		} else {
+			wiphy_dbg(hw->wiphy, "NAN: lost anchor master role\n");
+			adopt_tsf = true;
+			memcpy(&data->nan.last_ami, &data->nan.current_ami,
+			       sizeof(data->nan.last_ami));
+			memcpy(&data->nan.current_ami, ami,
+			       sizeof(data->nan.last_ami));
+			data->nan.current_ami.hop_count += 1;
+		}
+	}
+
+	if (adopt_tsf && !data->nan.tsf_adjusted) {
+		int threshold = 5;
+		s64 adjustment;
+
+		/* Timestamp is likely inaccurate (and late) in this case */
+		if (!(IEEE80211_SKB_RXCB(skb)->flag & RX_FLAG_MACTIME))
+			threshold = 128;
+
+		adjustment =
+			le64_to_cpu(mgmt->u.beacon.timestamp) -
+			ieee80211_calculate_rx_timestamp(hw, &rx_status,
+							 skb->len, 24);
+
+		scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+			if (adjustment < -threshold || adjustment > threshold) {
+				if (adjustment < -(s64)ieee80211_tu_to_usec(4) ||
+				    adjustment > (s64)ieee80211_tu_to_usec(4))
+					data->nan.tsf_discontinuity = true;
+
+				wiphy_debug(hw->wiphy,
+					    "NAN: Adjusting TSF by +/- %d us or more: %lld us (discontinuity: %d, from: %pM, old offset: %lld)\n",
+					    threshold, adjustment,
+					    data->nan.tsf_discontinuity, mgmt->sa,
+					    data->tsf_offset);
+			} else {
+				/* smooth things out a little bit */
+				adjustment /= 2;
+			}
+
+			/*
+			 * Do the TSF adjustment
+			 * The flag prevents further adjustments until the next
+			 * 2.4 GHz DW starts to avoid race conditions for
+			 * in-flight packets.
+			 */
+			data->nan.tsf_adjusted = true;
+			data->tsf_offset += adjustment;
+		}
+	}
+
+	spin_unlock_bh(&data->nan.state_lock);
+
+	if (joined_cluster)
+		ieee80211_nan_cluster_joined(data->nan.device_vif,
+					     data->nan.cluster_id, false,
+					     GFP_ATOMIC);
+}
+
 static void
 mac80211_hwsim_nan_exec_state_transitions(struct mac80211_hwsim_data *data)
 {
+	bool notify_join = false;
+
 	/*
 	 * Handle NAN role and state transitions at the end of the DW period
 	 * in accordance to Wi-Fi Aware version 4.0 section 3.3.7 point 2, i.e.
 	 * end of 5 GHz DW if enabled else at the end of the 2.4 GHz DW.
-	 *
-	 * TODO: Implement
 	 */
+
+	spin_lock(&data->nan.state_lock);
+
+	/* Handle role transitions, Wi-Fi Aware version 4.0 section 3.3.6  */
+	if (data->nan.master_transition_score < 3)
+		data->nan.role = MAC80211_HWSIM_NAN_ROLE_MASTER;
+	else if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_MASTER &&
+		 data->nan.master_transition_score >= 3)
+		data->nan.role = MAC80211_HWSIM_NAN_ROLE_SYNC;
+	else if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_SYNC &&
+		 data->nan.sync_transition_score >= 3)
+		data->nan.role = MAC80211_HWSIM_NAN_ROLE_NON_SYNC;
+	else if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_NON_SYNC &&
+		 data->nan.sync_transition_score < 3)
+		data->nan.role = MAC80211_HWSIM_NAN_ROLE_SYNC;
+
+	/*
+	 * The discovery beacon timer will stop automatically. Make sure it is
+	 * running if we are master. Do not bother with a proper alignment it
+	 * will sync itself to the TSF after the first TX.
+	 */
+	if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_MASTER &&
+	    !hrtimer_active(&data->nan.discovery_beacon_timer))
+		hrtimer_start(&data->nan.discovery_beacon_timer,
+			      ns_to_ktime(10 * NSEC_PER_USEC),
+			      HRTIMER_MODE_REL_SOFT);
+
+	data->nan.master_transition_score = 0;
+	data->nan.sync_transition_score = 0;
+
+	if (data->nan.random_factor_valid_dwst == 0) {
+		u64 amr;
+
+		if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN) {
+			data->nan.phase = MAC80211_HWSIM_NAN_PHASE_WARMUP;
+			data->nan.random_factor_valid_dwst = NAN_WARMUP_DWST;
+
+			notify_join = true;
+		} else {
+			data->nan.phase = MAC80211_HWSIM_NAN_PHASE_UP;
+			data->nan.random_factor_valid_dwst =
+				get_random_u32_inclusive(120, 240);
+			data->nan.random_factor = get_random_u8();
+		}
+
+		amr = hwsim_nan_get_master_rank(data);
+
+		if (data->nan.current_ami.hop_count == 0) {
+			/* Update if we are already anchor master */
+			data->nan.current_ami.master_rank = cpu_to_le64(amr);
+		} else if (le64_to_cpu(data->nan.current_ami.master_rank) < amr) {
+			/* assume role if we have a higher rank */
+			wiphy_dbg(data->hw->wiphy,
+				  "NAN: assume anchor master role\n");
+			data->nan.current_ami.master_rank = cpu_to_le64(amr);
+			data->nan.current_ami.hop_count = 0;
+			memset(&data->nan.last_ami, 0,
+			       sizeof(data->nan.last_ami));
+			data->nan.last_ami.ambtt = data->nan.current_ami.ambtt;
+			data->nan.current_ami.ambtt = 0;
+		}
+	} else {
+		data->nan.random_factor_valid_dwst--;
+	}
+
+	spin_unlock(&data->nan.state_lock);
+
+	if (notify_join)
+		ieee80211_nan_cluster_joined(data->nan.device_vif,
+					     data->nan.cluster_id, true,
+					     GFP_ATOMIC);
+}
+
+static void
+mac80211_hwsim_nan_tx_beacon(struct mac80211_hwsim_data *data,
+			     bool is_discovery,
+			     struct ieee80211_channel *channel)
+{
+	struct ieee80211_vendor_ie nan_ie = {
+		.element_id = WLAN_EID_VENDOR_SPECIFIC,
+		.len = 27 - 2,
+		.oui = { u32_get_bits(WLAN_OUI_WFA, 0xff0000),
+			 u32_get_bits(WLAN_OUI_WFA, 0xff00),
+			 u32_get_bits(WLAN_OUI_WFA, 0xff) },
+		.oui_type = WLAN_OUI_TYPE_WFA_NAN,
+	};
+	size_t alloc_size =
+		IEEE80211_TX_STATUS_HEADROOM +
+		offsetofend(struct ieee80211_mgmt, u.beacon) +
+		27 /* size of NAN vendor element */;
+	struct ieee80211_nan_master_indication master_indication;
+	struct ieee80211_nan_attr nan_attr;
+	struct ieee80211_mgmt *mgmt;
+	struct sk_buff *skb;
+
+	/*
+	 * TODO: Should the configured vendor elements or NAN attributes be
+	 * included in some of these beacons?
+	 */
+
+	skb = alloc_skb(alloc_size, GFP_ATOMIC);
+	if (!skb)
+		return;
+
+	spin_lock(&data->nan.state_lock);
+
+	skb_reserve(skb, IEEE80211_TX_STATUS_HEADROOM);
+	mgmt = skb_put(skb, offsetofend(struct ieee80211_mgmt, u.beacon));
+
+	memset(mgmt, 0, offsetofend(struct ieee80211_mgmt, u.beacon));
+	memcpy(mgmt->sa, data->nan.device_vif->addr, ETH_ALEN);
+	memset(mgmt->da, 0xff, ETH_ALEN);
+	memcpy(mgmt->bssid, data->nan.cluster_id, ETH_ALEN);
+
+	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+					  IEEE80211_STYPE_BEACON);
+	mgmt->u.beacon.beacon_int = cpu_to_le16(is_discovery ? 100 : DWST_TU);
+	mgmt->u.beacon.capab_info =
+		cpu_to_le16(WLAN_CAPABILITY_SHORT_SLOT_TIME |
+			    WLAN_CAPABILITY_SHORT_PREAMBLE);
+
+	/* FIXME: set these to saner values? */
+	mgmt->duration = 0;
+	mgmt->seq_ctrl = 0;
+
+	/* Put the NAN element */
+	skb_put_data(skb, &nan_ie, sizeof(nan_ie));
+
+	nan_attr.attr = NAN_ATTR_MASTER_INDICATION;
+	nan_attr.length = cpu_to_le16(sizeof(master_indication));
+	if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_UP) {
+		master_indication.master_pref = data->nan.master_pref;
+		master_indication.random_factor = data->nan.random_factor;
+	} else {
+		master_indication.master_pref = 0;
+		master_indication.random_factor = 0;
+	}
+
+	skb_put_data(skb, &nan_attr, sizeof(nan_attr));
+	skb_put_data(skb, &master_indication, sizeof(master_indication));
+
+	nan_attr.attr = NAN_ATTR_CLUSTER_INFO;
+	nan_attr.length = cpu_to_le16(sizeof(data->nan.current_ami));
+	skb_put_data(skb, &nan_attr, sizeof(nan_attr));
+	skb_put_data(skb, &data->nan.current_ami,
+		     sizeof(data->nan.current_ami));
+
+	spin_unlock(&data->nan.state_lock);
+
+	mac80211_hwsim_tx_frame(data->hw, skb, channel);
 }
 
 enum hrtimer_restart
@@ -80,10 +663,12 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
 			     nan.slot_timer);
 	struct ieee80211_hw *hw = data->hw;
 	struct ieee80211_channel *notify_dw_chan = NULL;
+	struct ieee80211_channel *beacon_sync_chan = NULL;
 	u64 tsf = hwsim_nan_get_timer_tsf(data);
 	u8 slot = hwsim_nan_slot_from_tsf(tsf);
 	bool dwst_of_dw0 = false;
 	bool dw_end = false;
+	bool tx_sync_beacon;
 
 	if (!data->nan.device_vif)
 		return HRTIMER_NORESTART;
@@ -92,10 +677,28 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
 		dwst_of_dw0 = true;
 
 
+	scoped_guard(spinlock, &data->nan.state_lock) {
+		if (data->nan.tsf_discontinuity) {
+			data->nan.tsf_discontinuity = false;
+
+			mac80211_hwsim_nan_schedule_slot(data, 32, true);
+
+			return HRTIMER_RESTART;
+		}
+
+		if (slot == SLOT_24GHZ_DW)
+			data->nan.tsf_adjusted = false;
+
+		tx_sync_beacon =
+			data->nan.phase != MAC80211_HWSIM_NAN_PHASE_SCAN &&
+			data->nan.role != MAC80211_HWSIM_NAN_ROLE_NON_SYNC;
+	}
+
 	switch (slot) {
 	case SLOT_24GHZ_DW:
 		wiphy_dbg(data->hw->wiphy, "Start of 2.4 GHz DW, is DW0=%d\n",
 			  dwst_of_dw0);
+		beacon_sync_chan = ieee80211_get_channel(hw->wiphy, 2437);
 		break;
 
 	case SLOT_24GHZ_DW + 1:
@@ -110,6 +713,8 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
 	case SLOT_5GHZ_DW:
 		if (data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
 			wiphy_dbg(data->hw->wiphy, "Start of 5 GHz DW\n");
+			beacon_sync_chan =
+				ieee80211_get_channel(hw->wiphy, 5745);
 		}
 		break;
 
@@ -122,6 +727,10 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
 		break;
 	}
 
+	/* TODO: This does not implement DW contention mitigation */
+	if (beacon_sync_chan && tx_sync_beacon)
+		mac80211_hwsim_nan_tx_beacon(data, false, beacon_sync_chan);
+
 	if (dw_end)
 		mac80211_hwsim_nan_exec_state_transitions(data);
 
@@ -134,7 +743,50 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
 
 	mac80211_hwsim_nan_resume_txqs(data);
 
-	mac80211_hwsim_nan_schedule_slot(data, slot + 1);
+	mac80211_hwsim_nan_schedule_slot(data, slot + 1, false);
+
+	return HRTIMER_RESTART;
+}
+
+enum hrtimer_restart
+mac80211_hwsim_nan_discovery_beacon_timer(struct hrtimer *timer)
+{
+	struct mac80211_hwsim_data *data =
+		container_of(timer, struct mac80211_hwsim_data,
+			     nan.discovery_beacon_timer);
+	u32 remainder;
+	u64 tsf_now;
+	u64 tbtt;
+
+	if (!data->nan.device_vif)
+		return HRTIMER_NORESTART;
+
+	scoped_guard(spinlock, &data->nan.state_lock) {
+		if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN ||
+		    data->nan.role != MAC80211_HWSIM_NAN_ROLE_MASTER)
+			return HRTIMER_NORESTART;
+	}
+
+	mac80211_hwsim_nan_tx_beacon(
+		data, true, ieee80211_get_channel(data->hw->wiphy, 2437));
+
+	if (data->nan.bands & BIT(NL80211_BAND_5GHZ))
+		mac80211_hwsim_nan_tx_beacon(
+			data, true,
+			ieee80211_get_channel(data->hw->wiphy, 5745));
+
+	/* Read the TSF from the current time in case of adjustments */
+	tsf_now = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
+
+	/* Wrap value to be after the next TBTT */
+	tbtt = tsf_now + ieee80211_tu_to_usec(100);
+
+	/* Round TBTT down to the correct time */
+	div_u64_rem(tbtt, ieee80211_tu_to_usec(100), &remainder);
+	tbtt = tbtt - remainder;
+
+	hrtimer_set_expires(&data->nan.discovery_beacon_timer,
+			    mac80211_hwsim_tsf_to_boottime(data, tbtt));
 
 	return HRTIMER_RESTART;
 }
@@ -144,7 +796,6 @@ int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
 			     struct cfg80211_nan_conf *conf)
 {
 	struct mac80211_hwsim_data *data = hw->priv;
-	struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif);
 
 	if (vif->type != NL80211_IFTYPE_NAN)
 		return -EINVAL;
@@ -156,28 +807,29 @@ int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
 	data->nan.device_vif = vif;
 	data->nan.bands = conf->bands;
 
+	scoped_guard(spinlock_bh, &data->nan.state_lock) {
+		/* Start in the "scan" phase and stay there for a little bit */
+		data->nan.phase = MAC80211_HWSIM_NAN_PHASE_SCAN;
+		data->nan.random_factor_valid_dwst = 1;
+		data->nan.random_factor = 0;
+		data->nan.master_pref = conf->master_pref;
+		data->nan.role = MAC80211_HWSIM_NAN_ROLE_MASTER;
+		memset(&data->nan.current_ami, 0,
+		       sizeof(data->nan.current_ami));
+		memset(&data->nan.last_ami, 0, sizeof(data->nan.last_ami));
+		data->nan.current_ami.master_rank =
+			cpu_to_le64(hwsim_nan_get_master_rank(data));
+	}
+
 	/* Just run this "soon" and start in a random schedule position */
 	hrtimer_start(&data->nan.slot_timer,
 		      ns_to_ktime(10 * NSEC_PER_USEC),
 		      HRTIMER_MODE_REL_SOFT);
 
-	if (!is_zero_ether_addr(conf->cluster_id) &&
-	    is_zero_ether_addr(hwsim_nan_cluster_id)) {
-		memcpy(hwsim_nan_cluster_id, conf->cluster_id, ETH_ALEN);
-	} else if (is_zero_ether_addr(hwsim_nan_cluster_id)) {
-		hwsim_nan_cluster_id[0] = 0x50;
-		hwsim_nan_cluster_id[1] = 0x6f;
-		hwsim_nan_cluster_id[2] = 0x9a;
-		hwsim_nan_cluster_id[3] = 0x01;
-		hwsim_nan_cluster_id[4] = get_random_u8();
-		hwsim_nan_cluster_id[5] = get_random_u8();
-	}
+	ether_addr_copy(data->nan.cluster_id, conf->cluster_id);
 
 	data->nan.notify_dw = conf->enable_dw_notification;
 
-	cfg80211_nan_cluster_joined(wdev, hwsim_nan_cluster_id, true,
-				    GFP_KERNEL);
-
 	return 0;
 }
 
@@ -185,8 +837,6 @@ int mac80211_hwsim_nan_stop(struct ieee80211_hw *hw,
 			    struct ieee80211_vif *vif)
 {
 	struct mac80211_hwsim_data *data = hw->priv;
-	struct mac80211_hwsim_data *data2;
-	bool nan_cluster_running = false;
 
 	if (vif->type != NL80211_IFTYPE_NAN || !data->nan.device_vif ||
 	    data->nan.device_vif != vif)
@@ -194,20 +844,9 @@ int mac80211_hwsim_nan_stop(struct ieee80211_hw *hw,
 
 	hrtimer_cancel(&data->nan.slot_timer);
 	hrtimer_cancel(&data->nan.resume_txqs_timer);
+	hrtimer_cancel(&data->nan.discovery_beacon_timer);
 	data->nan.device_vif = NULL;
 
-	spin_lock_bh(&hwsim_radio_lock);
-	list_for_each_entry(data2, &hwsim_radios, list) {
-		if (data2->nan.device_vif) {
-			nan_cluster_running = true;
-			break;
-		}
-	}
-	spin_unlock_bh(&hwsim_radio_lock);
-
-	if (!nan_cluster_running)
-		memset(hwsim_nan_cluster_id, 0, ETH_ALEN);
-
 	return 0;
 }
 
@@ -233,6 +872,11 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
 	if (changes & CFG80211_NAN_CONF_CHANGED_CONFIG)
 		data->nan.notify_dw = conf->enable_dw_notification;
 
+	if (changes & CFG80211_NAN_CONF_CHANGED_PREF) {
+		scoped_guard(spinlock_bh, &data->nan.state_lock)
+			data->nan.master_pref = conf->master_pref;
+	}
+
 	return 0;
 }
 
@@ -335,7 +979,10 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
 	if (WARN_ON_ONCE(!data->nan.device_vif))
 		return false;
 
-	if (rx_status->rx_flags & RX_FLAG_MACTIME) {
+	if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN)
+		return channel->center_freq == 2437;
+
+	if (rx_status->flag & RX_FLAG_MACTIME) {
 		slot = hwsim_nan_slot_from_tsf(rx_status->mactime);
 	} else {
 		u64 tsf;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index af8dd7ff00cc..3199e5c5376b 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -7,6 +7,18 @@
 #ifndef __MAC80211_HWSIM_NAN_H
 #define __MAC80211_HWSIM_NAN_H
 
+enum mac80211_hwsim_nan_phase {
+	MAC80211_HWSIM_NAN_PHASE_SCAN,
+	MAC80211_HWSIM_NAN_PHASE_WARMUP,
+	MAC80211_HWSIM_NAN_PHASE_UP,
+};
+
+enum mac80211_hwsim_nan_role {
+	MAC80211_HWSIM_NAN_ROLE_MASTER,
+	MAC80211_HWSIM_NAN_ROLE_SYNC,
+	MAC80211_HWSIM_NAN_ROLE_NON_SYNC,
+};
+
 struct mac80211_hwsim_nan_data {
 	struct ieee80211_vif *device_vif;
 	u8 bands;
@@ -14,12 +26,40 @@ struct mac80211_hwsim_nan_data {
 	struct hrtimer slot_timer;
 	struct hrtimer resume_txqs_timer;
 	bool notify_dw;
+
+	struct hrtimer discovery_beacon_timer;
+
+	/* Later members are protected by this lock */
+	spinlock_t state_lock;
+
+	u8 master_pref;
+	u8 random_factor;
+
+	u8 random_factor_valid_dwst;
+
+	enum mac80211_hwsim_nan_phase phase;
+	enum mac80211_hwsim_nan_role role;
+
+	u8 cluster_id[ETH_ALEN];
+
+	struct ieee80211_nan_anchor_master_info current_ami;
+	struct ieee80211_nan_anchor_master_info last_ami;
+
+	/* Wi-Fi Aware version 4.0, section 3.3.6.1 and 3.3.6.2 */
+	int master_transition_score;
+	/* Wi-Fi Aware version 4.0, section 3.3.6.3 and 3.3.6.4 */
+	int sync_transition_score;
+
+	bool tsf_adjusted;
+	bool tsf_discontinuity;
 };
 
 enum hrtimer_restart
 mac80211_hwsim_nan_slot_timer(struct hrtimer *timer);
 enum hrtimer_restart
 mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer);
+enum hrtimer_restart
+mac80211_hwsim_nan_discovery_beacon_timer(struct hrtimer *timer);
 
 int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
 			     struct ieee80211_vif *vif,
@@ -43,4 +83,7 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
 				struct ieee80211_channel *channel,
 				struct ieee80211_rx_status *rx_status);
 
+void mac80211_hwsim_nan_rx(struct ieee80211_hw *hw,
+			   struct sk_buff *skb);
+
 #endif /* __MAC80211_HWSIM_NAN_H */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 06/14] wifi: mac80211_hwsim: add NAN_DATA interface limits
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
                   ` (4 preceding siblings ...)
  2026-05-06  3:44 ` [PATCH v2 wireless-next 05/14] wifi: mac80211_hwsim: implement NAN synchronization Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 07/14] wifi: mac80211_hwsim: add NAN PHY capabilities Miri Korenblit
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay

From: Daniel Gabay <daniel.gabay@intel.com>

Increase interface limits for NAN_DATA interface.

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/virtual/mac80211_hwsim_i.h    | 4 ++--
 drivers/net/wireless/virtual/mac80211_hwsim_main.c | 8 +++++++-
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index 5378f721c299..d182b5117bfb 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -4,7 +4,7 @@
  * Copyright (c) 2008, Jouni Malinen <j@w1.fi>
  * Copyright (c) 2011, Javier Lopez <jlopex@gmail.com>
  * Copyright (c) 2016 - 2017 Intel Deutschland GmbH
- * Copyright (C) 2018 - 2025 Intel Corporation
+ * Copyright (C) 2018 - 2026 Intel Corporation
  */
 
 #ifndef __MAC80211_HWSIM_I_H
@@ -39,7 +39,7 @@ struct mac80211_hwsim_data {
 	struct ieee80211_channel channels_s1g[HWSIM_NUM_S1G_CHANNELS_US];
 	struct ieee80211_rate rates[HWSIM_NUM_RATES];
 	struct ieee80211_iface_combination if_combination;
-	struct ieee80211_iface_limit if_limits[4];
+	struct ieee80211_iface_limit if_limits[5];
 	int n_if_limits;
 	/* Storage space for channels, etc. */
 	struct mac80211_hwsim_phy_data *phy_data;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 969ebc28cb1e..3397acfe61c8 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -4,7 +4,7 @@
  * Copyright (c) 2008, Jouni Malinen <j@w1.fi>
  * Copyright (c) 2011, Javier Lopez <jlopex@gmail.com>
  * Copyright (c) 2016 - 2017 Intel Deutschland GmbH
- * Copyright (C) 2018 - 2025 Intel Corporation
+ * Copyright (C) 2018 - 2026 Intel Corporation
  */
 
 /*
@@ -5631,6 +5631,12 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 		spin_lock_init(&data->nan.state_lock);
 	}
 
+	if (param->iftypes & BIT(NL80211_IFTYPE_NAN_DATA)) {
+		data->if_limits[n_limits].max = 2;
+		data->if_limits[n_limits].types = BIT(NL80211_IFTYPE_NAN_DATA);
+		n_limits++;
+	}
+
 	data->if_combination.radar_detect_widths =
 				BIT(NL80211_CHAN_WIDTH_5) |
 				BIT(NL80211_CHAN_WIDTH_10) |
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 07/14] wifi: mac80211_hwsim: add NAN PHY capabilities
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
                   ` (5 preceding siblings ...)
  2026-05-06  3:44 ` [PATCH v2 wireless-next 06/14] wifi: mac80211_hwsim: add NAN_DATA interface limits Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 08/14] wifi: mac80211_hwsim: implement NAN schedule callbacks Miri Korenblit
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay

From: Daniel Gabay <daniel.gabay@intel.com>

Add static HT, VHT and HE PHY capabilities to the NAN capabilities
structure. These are based on the existing band capability structures
and initialization in mac80211_hwsim.

The NAN PHY capabilities are used by mac80211 and nl80211 to
advertise device capabilities for NAN data interfaces.

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../wireless/virtual/mac80211_hwsim_main.c    | 102 ++++++++++++++++--
 1 file changed, 94 insertions(+), 8 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 3397acfe61c8..fc940b38c52c 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -5412,6 +5412,95 @@ static const struct ieee80211_sband_iftype_data sband_capa_6ghz[] = {
 #endif
 };
 
+#define HWSIM_VHT_MCS_MAP				\
+	(IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 2 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 4 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 6 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 8 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 10 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 12 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 14)
+
+static const struct ieee80211_sta_ht_cap hwsim_nan_ht_cap = {
+	.ht_supported = true,
+	.cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
+	       IEEE80211_HT_CAP_GRN_FLD |
+	       IEEE80211_HT_CAP_SGI_20 |
+	       IEEE80211_HT_CAP_SGI_40 |
+	       IEEE80211_HT_CAP_DSSSCCK40,
+	.ampdu_factor = 0x3,
+	.ampdu_density = 0x6,
+	.mcs = {
+		.rx_mask = { 0xff, 0xff },
+		.tx_params = IEEE80211_HT_MCS_TX_DEFINED,
+	},
+};
+
+static const struct ieee80211_sta_vht_cap hwsim_nan_vht_cap = {
+	.vht_supported = true,
+	.cap = IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454 |
+	       IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ |
+	       IEEE80211_VHT_CAP_RXLDPC |
+	       IEEE80211_VHT_CAP_SHORT_GI_80 |
+	       IEEE80211_VHT_CAP_SHORT_GI_160 |
+	       IEEE80211_VHT_CAP_TXSTBC |
+	       IEEE80211_VHT_CAP_RXSTBC_4 |
+	       IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK,
+	.vht_mcs = {
+		.rx_mcs_map = cpu_to_le16(HWSIM_VHT_MCS_MAP),
+		.tx_mcs_map = cpu_to_le16(HWSIM_VHT_MCS_MAP),
+	},
+};
+
+static const struct ieee80211_sta_he_cap hwsim_nan_he_cap = {
+	.has_he = true,
+	.he_cap_elem = {
+		.mac_cap_info[0] =
+			IEEE80211_HE_MAC_CAP0_HTC_HE,
+		.mac_cap_info[1] =
+			IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US |
+			IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8,
+		.mac_cap_info[2] =
+			IEEE80211_HE_MAC_CAP2_BSR |
+			IEEE80211_HE_MAC_CAP2_MU_CASCADING |
+			IEEE80211_HE_MAC_CAP2_ACK_EN,
+		.mac_cap_info[3] =
+			IEEE80211_HE_MAC_CAP3_OMI_CONTROL |
+			IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_3,
+		.mac_cap_info[4] = IEEE80211_HE_MAC_CAP4_AMSDU_IN_AMPDU,
+		.phy_cap_info[0] =
+			IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G |
+			IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G |
+			IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G,
+		.phy_cap_info[1] =
+			IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_MASK |
+			IEEE80211_HE_PHY_CAP1_DEVICE_CLASS_A |
+			IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD |
+			IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS,
+		.phy_cap_info[2] =
+			IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US |
+			IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ |
+			IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ |
+			IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO |
+			IEEE80211_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO,
+
+		/*
+		 * Leave all the other PHY capability bytes
+		 * unset, as DCM, beam forming, RU and PPE
+		 * threshold information are not supported
+		 */
+	},
+	.he_mcs_nss_supp = {
+		.rx_mcs_80 = cpu_to_le16(0xfffa),
+		.tx_mcs_80 = cpu_to_le16(0xfffa),
+		.rx_mcs_160 = cpu_to_le16(0xfffa),
+		.tx_mcs_160 = cpu_to_le16(0xfffa),
+		.rx_mcs_80p80 = cpu_to_le16(0xfffa),
+		.tx_mcs_80p80 = cpu_to_le16(0xfffa),
+	},
+};
+
 static void mac80211_hwsim_sband_capab(struct ieee80211_supported_band *sband)
 {
 	switch (sband->band) {
@@ -5635,6 +5724,10 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 		data->if_limits[n_limits].max = 2;
 		data->if_limits[n_limits].types = BIT(NL80211_IFTYPE_NAN_DATA);
 		n_limits++;
+
+		hw->wiphy->nan_capa.phy.ht = hwsim_nan_ht_cap;
+		hw->wiphy->nan_capa.phy.vht = hwsim_nan_vht_cap;
+		hw->wiphy->nan_capa.phy.he = hwsim_nan_he_cap;
 	}
 
 	data->if_combination.radar_detect_widths =
@@ -5816,14 +5909,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 				IEEE80211_VHT_CAP_RXSTBC_4 |
 				IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK;
 			sband->vht_cap.vht_mcs.rx_mcs_map =
-				cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 2 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 4 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 6 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 8 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 10 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 12 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 14);
+				cpu_to_le16(HWSIM_VHT_MCS_MAP);
 			sband->vht_cap.vht_mcs.tx_mcs_map =
 				sband->vht_cap.vht_mcs.rx_mcs_map;
 			break;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 08/14] wifi: mac80211_hwsim: implement NAN schedule callbacks
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
                   ` (6 preceding siblings ...)
  2026-05-06  3:44 ` [PATCH v2 wireless-next 07/14] wifi: mac80211_hwsim: add NAN PHY capabilities Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 09/14] wifi: mac80211_hwsim: set HAS_RATE_CONTROL when using NAN Miri Korenblit
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay

From: Daniel Gabay <daniel.gabay@intel.com>

Implement mac80211 schedule callbacks for NAN Data Path support:

- Track local schedule via BSS_CHANGED_NAN_LOCAL_SCHED, caching
  the channel for each 16TU time slot.
- Copy peer schedule to driver-private storage in
  nan_peer_sched_changed callback for use in TX availability
  decisions.

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/virtual/mac80211_hwsim_i.h   | 21 +++++
 .../wireless/virtual/mac80211_hwsim_main.c    | 14 ++--
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 80 +++++++++++++++++++
 .../net/wireless/virtual/mac80211_hwsim_nan.h | 15 +++-
 4 files changed, 121 insertions(+), 9 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index d182b5117bfb..0f0f2ac6d80e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -14,6 +14,27 @@
 #include "mac80211_hwsim.h"
 #include "mac80211_hwsim_nan.h"
 
+struct hwsim_sta_nan_sched {
+	/* Later members are protected by this lock */
+	spinlock_t lock;
+	u16 committed_dw;
+	struct {
+		u8 map_id;
+		struct cfg80211_chan_def chans[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+	} maps[CFG80211_NAN_MAX_PEER_MAPS];
+};
+
+struct hwsim_sta_priv {
+	u32 magic;
+	unsigned int last_link;
+	u16 active_links_rx;
+
+	/* NAN peer schedule - must be accessed under nan_sched.lock */
+	struct hwsim_sta_nan_sched nan_sched;
+};
+
+#define HWSIM_STA_MAGIC	0x6d537749
+
 struct mac80211_hwsim_link_data {
 	u32 link_id;
 	u64 beacon_int	/* beacon interval in us */;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index fc940b38c52c..1ea33ec577dd 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -252,14 +252,6 @@ static inline void hwsim_clear_magic(struct ieee80211_vif *vif)
 	vp->magic = 0;
 }
 
-struct hwsim_sta_priv {
-	u32 magic;
-	unsigned int last_link;
-	u16 active_links_rx;
-};
-
-#define HWSIM_STA_MAGIC	0x6d537749
-
 static inline void hwsim_check_sta_magic(struct ieee80211_sta *sta)
 {
 	struct hwsim_sta_priv *sp = (void *)sta->drv_priv;
@@ -2652,6 +2644,9 @@ static void mac80211_hwsim_vif_info_changed(struct ieee80211_hw *hw,
 		vp->aid = vif->cfg.aid;
 	}
 
+	if (changed & BSS_CHANGED_NAN_LOCAL_SCHED)
+		mac80211_hwsim_nan_local_sched_changed(hw, vif);
+
 	if (vif->type == NL80211_IFTYPE_STATION &&
 	    changed & (BSS_CHANGED_MLD_VALID_LINKS | BSS_CHANGED_MLD_TTLM)) {
 		u16 usable_links = ieee80211_vif_usable_links(vif);
@@ -2818,6 +2813,8 @@ static int mac80211_hwsim_sta_add(struct ieee80211_hw *hw,
 		sp->active_links_rx = sta->valid_links;
 	}
 
+	spin_lock_init(&sp->nan_sched.lock);
+
 	return 0;
 }
 
@@ -4245,6 +4242,7 @@ static int mac80211_hwsim_set_radar_background(struct ieee80211_hw *hw,
 	.start_nan = mac80211_hwsim_nan_start,			\
 	.stop_nan = mac80211_hwsim_nan_stop,			\
 	.nan_change_conf = mac80211_hwsim_nan_change_config,	\
+	.nan_peer_sched_changed = mac80211_hwsim_nan_peer_sched_changed, \
 	HWSIM_DEBUGFS_OPS
 
 #define HWSIM_NON_MLO_OPS					\
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 6053b6f8f91f..16883edd2215 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -1008,3 +1008,83 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
 
 	return false;
 }
+
+void mac80211_hwsim_nan_local_sched_changed(struct ieee80211_hw *hw,
+					    struct ieee80211_vif *vif)
+{
+	struct mac80211_hwsim_data *data = hw->priv;
+	struct ieee80211_nan_channel **slots = vif->cfg.nan_sched.schedule;
+
+	if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
+		return;
+
+	spin_lock_bh(&data->nan.state_lock);
+
+	for (int i = 0; i < ARRAY_SIZE(data->nan.local_sched); i++) {
+		struct ieee80211_chanctx_conf *chanctx;
+
+		if (!slots[i] || IS_ERR(slots[i])) {
+			memset(&data->nan.local_sched[i], 0,
+			       sizeof(data->nan.local_sched[i]));
+			continue;
+		}
+
+		chanctx = slots[i]->chanctx_conf;
+		if (!chanctx) {
+			memset(&data->nan.local_sched[i], 0,
+			       sizeof(data->nan.local_sched[i]));
+			continue;
+		}
+
+		data->nan.local_sched[i] = chanctx->def;
+	}
+
+	spin_unlock_bh(&data->nan.state_lock);
+}
+
+int mac80211_hwsim_nan_peer_sched_changed(struct ieee80211_hw *hw,
+					  struct ieee80211_sta *sta)
+{
+	struct hwsim_sta_priv *sp = (void *)sta->drv_priv;
+	struct ieee80211_nan_peer_sched *sched = sta->nan_sched;
+
+	spin_lock_bh(&sp->nan_sched.lock);
+
+	/* Clear existing schedule */
+	sp->nan_sched.committed_dw = 0;
+	for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
+		sp->nan_sched.maps[i].map_id = CFG80211_NAN_INVALID_MAP_ID;
+		memset(sp->nan_sched.maps[i].chans, 0,
+		       sizeof(sp->nan_sched.maps[i].chans));
+	}
+
+	if (!sched)
+		goto out;
+
+	sp->nan_sched.committed_dw = sched->committed_dw;
+
+	for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
+		struct ieee80211_nan_peer_map *map = &sched->maps[i];
+
+		if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+			continue;
+
+		sp->nan_sched.maps[i].map_id = map->map_id;
+
+		for (int j = 0; j < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; j++) {
+			struct ieee80211_nan_channel *peer_chan =
+				map->slots[j];
+
+			if (peer_chan && peer_chan->chanreq.oper.chan)
+				sp->nan_sched.maps[i].chans[j] =
+					peer_chan->chanreq.oper;
+			else
+				memset(&sp->nan_sched.maps[i].chans[j], 0,
+				       sizeof(sp->nan_sched.maps[i].chans[j]));
+		}
+	}
+
+out:
+	spin_unlock_bh(&sp->nan_sched.lock);
+	return 0;
+}
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index 3199e5c5376b..eb53bacee206 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
  * mac80211_hwsim_nan - NAN software simulation for mac80211_hwsim
- * Copyright (C) 2025 Intel Corporation
+ * Copyright (C) 2025-2026 Intel Corporation
  */
 
 #ifndef __MAC80211_HWSIM_NAN_H
@@ -52,6 +52,13 @@ struct mac80211_hwsim_nan_data {
 
 	bool tsf_adjusted;
 	bool tsf_discontinuity;
+
+	/*
+	 * Local schedule - stores channel definition for each 16TU slot.
+	 * Derived from NMI vif->cfg.nan_schedule. chan == NULL means not
+	 * available in that slot (except DW which is implicit).
+	 */
+	struct cfg80211_chan_def local_sched[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
 };
 
 enum hrtimer_restart
@@ -73,6 +80,9 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
 				     struct cfg80211_nan_conf *conf,
 				     u32 changes);
 
+int mac80211_hwsim_nan_peer_sched_changed(struct ieee80211_hw *hw,
+					  struct ieee80211_sta *sta);
+
 bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 					 struct ieee80211_txq *txq);
 
@@ -86,4 +96,7 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
 void mac80211_hwsim_nan_rx(struct ieee80211_hw *hw,
 			   struct sk_buff *skb);
 
+void mac80211_hwsim_nan_local_sched_changed(struct ieee80211_hw *hw,
+					    struct ieee80211_vif *vif);
+
 #endif /* __MAC80211_HWSIM_NAN_H */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 09/14] wifi: mac80211_hwsim: set HAS_RATE_CONTROL when using NAN
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
                   ` (7 preceding siblings ...)
  2026-05-06  3:44 ` [PATCH v2 wireless-next 08/14] wifi: mac80211_hwsim: implement NAN schedule callbacks Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 10/14] wifi: mac80211_hwsim: add NAN data path TX/RX support Miri Korenblit
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay

From: Daniel Gabay <daniel.gabay@intel.com>

- NAN switches between bands/channels per its schedule, so mac80211
  rate control can't work, set HAS_RATE_CONTROL instead.
- Skip rate control checks for NAN interfaces in
  mac80211_hwsim_sta_rc_update() as it's not relevant.
- Move set_rts_threshold stub to HWSIM_COMMON_OPS and return 0 instead
  of -EOPNOTSUPP to prevent failures in non-MLO tests that set RTS
  threshold (hwsim ignores the use_rts instruction from mac80211
  anyway).

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/virtual/mac80211_hwsim_main.c    | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 1ea33ec577dd..107d47d31073 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -2747,6 +2747,10 @@ mac80211_hwsim_sta_rc_update(struct ieee80211_hw *hw,
 	u32 bw = U32_MAX;
 	int link_id;
 
+	if (vif->type == NL80211_IFTYPE_NAN ||
+	    vif->type == NL80211_IFTYPE_NAN_DATA)
+		return;
+
 	rcu_read_lock();
 	for (link_id = 0;
 	     link_id < ARRAY_SIZE(vif->link_conf);
@@ -3475,7 +3479,8 @@ static int mac80211_hwsim_tx_last_beacon(struct ieee80211_hw *hw)
 static int mac80211_hwsim_set_rts_threshold(struct ieee80211_hw *hw,
 					    int radio_idx, u32 value)
 {
-	return -EOPNOTSUPP;
+	/* hwsim ignores the use_rts instruction from mac80211 anyway */
+	return 0;
 }
 
 static int mac80211_hwsim_change_vif_links(struct ieee80211_hw *hw,
@@ -4239,6 +4244,7 @@ static int mac80211_hwsim_set_radar_background(struct ieee80211_hw *hw,
 	.abort_pmsr = mac80211_hwsim_abort_pmsr,		\
 	.set_radar_background = mac80211_hwsim_set_radar_background, \
 	.set_key = mac80211_hwsim_set_key,			\
+	.set_rts_threshold = mac80211_hwsim_set_rts_threshold,	\
 	.start_nan = mac80211_hwsim_nan_start,			\
 	.stop_nan = mac80211_hwsim_nan_stop,			\
 	.nan_change_conf = mac80211_hwsim_nan_change_config,	\
@@ -4284,7 +4290,6 @@ static const struct ieee80211_ops mac80211_hwsim_mchan_ops = {
 static const struct ieee80211_ops mac80211_hwsim_mlo_ops = {
 	HWSIM_COMMON_OPS
 	HWSIM_CHANCTX_OPS
-	.set_rts_threshold = mac80211_hwsim_set_rts_threshold,
 	.change_vif_links = mac80211_hwsim_change_vif_links,
 	.change_sta_links = mac80211_hwsim_change_sta_links,
 	.sta_state = mac80211_hwsim_sta_state,
@@ -5726,6 +5731,12 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 		hw->wiphy->nan_capa.phy.ht = hwsim_nan_ht_cap;
 		hw->wiphy->nan_capa.phy.vht = hwsim_nan_vht_cap;
 		hw->wiphy->nan_capa.phy.he = hwsim_nan_he_cap;
+
+		/*
+		 * NAN switches between bands/channels per its schedule,
+		 * so mac80211 rate control can't work here.
+		 */
+		ieee80211_hw_set(hw, HAS_RATE_CONTROL);
 	}
 
 	data->if_combination.radar_detect_widths =
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 10/14] wifi: mac80211_hwsim: add NAN data path TX/RX support
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
                   ` (8 preceding siblings ...)
  2026-05-06  3:44 ` [PATCH v2 wireless-next 09/14] wifi: mac80211_hwsim: set HAS_RATE_CONTROL when using NAN Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 11/14] wifi: mac80211_hwsim: Declare support for secure NAN Miri Korenblit
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay

From: Daniel Gabay <daniel.gabay@intel.com>

Implement TX and RX path handling for NAN Data Path (NDP) frames,
enabling data communication between NAN peers during scheduled
availability windows.

TX path:
- Select TX channel based on current time slot: use DW channel
  during Discovery Windows, or FAW channel from local
  schedule during Further Availability Windows.
- Verify peer availability before transmission by checking committed
  DW schedule or FAW of the peer schedule.

RX path:
- Extend NAN receive filtering to handle NAN_DATA interface frames.
- Accept incoming frames during FAW slots when channel matches local
  schedule.

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../wireless/virtual/mac80211_hwsim_main.c    |  18 +-
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 250 ++++++++++++++++--
 .../net/wireless/virtual/mac80211_hwsim_nan.h |   4 +-
 3 files changed, 243 insertions(+), 29 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 107d47d31073..66cc8c528c6b 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1744,7 +1744,8 @@ static void mac80211_hwsim_tx_iter(void *_data, u8 *addr,
 	struct tx_iter_data *data = _data;
 	int i;
 
-	if (vif->type == NL80211_IFTYPE_NAN) {
+	if (vif->type == NL80211_IFTYPE_NAN ||
+	    vif->type == NL80211_IFTYPE_NAN_DATA) {
 		data->receive = mac80211_hwsim_nan_receive(data->hw,
 							   data->channel,
 							   data->rx_status);
@@ -2093,13 +2094,19 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
 	/* re-assign hdr since skb data may have shifted after encryption */
 	hdr = (void *)skb->data;
 
-	if (vif && vif->type == NL80211_IFTYPE_NAN && !data->tmp_chan) {
-		channel = mac80211_hwsim_nan_get_tx_channel(hw);
+	if (vif && !data->tmp_chan &&
+	    (vif->type == NL80211_IFTYPE_NAN ||
+	     vif->type == NL80211_IFTYPE_NAN_DATA)) {
+		struct cfg80211_chan_def chandef;
 
-		if (WARN_ON(!channel)) {
+		mac80211_hwsim_nan_get_tx_chandef(hw, &chandef);
+		if (WARN_ON(!chandef.chan)) {
+			/* No valid channel in current slot, drop frame */
 			ieee80211_free_txskb(hw, skb);
 			return;
 		}
+		channel = chandef.chan;
+		confbw = chandef.width;
 	} else if (!data->use_chanctx) {
 		channel = data->channel;
 		confbw = data->bw;
@@ -2249,7 +2256,8 @@ void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,
 	};
 	struct sk_buff *skb;
 
-	if (txq->vif->type == NL80211_IFTYPE_NAN &&
+	if ((txq->vif->type == NL80211_IFTYPE_NAN ||
+	     txq->vif->type == NL80211_IFTYPE_NAN_DATA) &&
 	    !mac80211_hwsim_nan_txq_transmitting(hw, txq))
 		return;
 
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 16883edd2215..0397c43724fe 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -36,6 +36,11 @@ static_assert(DW0_TSF_MASK + 1 == 8192 * 1024);
 /* Quiet time at the end of each slot where TX is suppressed */
 #define NAN_CHAN_SWITCH_TIME_US		256
 
+struct hwsim_nan_sta_iter_ctx {
+	struct ieee80211_hw *hw;
+	bool can_tx;
+};
+
 static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data);
 
 static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
@@ -77,6 +82,109 @@ static u64 hwsim_nan_get_master_rank(struct mac80211_hwsim_data *data)
 					    data->nan.device_vif->addr);
 }
 
+static bool mac80211_hwsim_nan_is_dw_slot(struct mac80211_hwsim_data *data,
+					  u8 slot)
+{
+	return slot == SLOT_24GHZ_DW ||
+		(slot == SLOT_5GHZ_DW &&
+		 (data->nan.bands & BIT(NL80211_BAND_5GHZ)));
+}
+
+static bool
+hwsim_nan_rx_chandef_compatible(struct mac80211_hwsim_data *data, u8 slot,
+				struct ieee80211_channel *rx_chan, u8 rx_bw)
+{
+	static const int bw_to_mhz[] = {
+		[RATE_INFO_BW_20] = 20, [RATE_INFO_BW_40] = 40,
+		[RATE_INFO_BW_80] = 80, [RATE_INFO_BW_160] = 160,
+	};
+	struct cfg80211_chan_def sched_chandef;
+	int rx_mhz, sched_mhz;
+
+	scoped_guard(spinlock_bh, &data->nan.state_lock)
+		sched_chandef = data->nan.local_sched[slot];
+
+	if (!sched_chandef.chan ||
+	    sched_chandef.chan->center_freq != rx_chan->center_freq)
+		return false;
+
+	if (rx_bw >= ARRAY_SIZE(bw_to_mhz) || !bw_to_mhz[rx_bw])
+		return false;
+
+	rx_mhz = bw_to_mhz[rx_bw];
+	sched_mhz = cfg80211_chandef_get_width(&sched_chandef);
+
+	/* Accept RX at narrower or equal bandwidth */
+	return rx_mhz <= sched_mhz;
+}
+
+static bool hwsim_nan_peer_present_in_dw(struct hwsim_sta_priv *sp, u64 tsf)
+{
+	u8 slot = hwsim_nan_slot_from_tsf(tsf);
+	u8 cdw = 0;
+	u8 dw_index, wake_interval;
+	u16 committed_dw;
+
+	scoped_guard(spinlock_bh, &sp->nan_sched.lock)
+		committed_dw = sp->nan_sched.committed_dw;
+
+	/* If peer doesn't advertise committed DW, assume presence in
+	 * all 2.4 GHz DW slots
+	 */
+	if (!committed_dw)
+		return slot == SLOT_24GHZ_DW;
+
+	/* Get DW index (0-15) within the 16-DWST DW0 cycle */
+	dw_index = (tsf / ieee80211_tu_to_usec(DWST_TU)) & 0xf;
+
+	/* Extract CDW for the appropriate band (spec Table 80) */
+	if (slot == SLOT_24GHZ_DW)
+		cdw = committed_dw & 0x7;
+	else if (slot == SLOT_5GHZ_DW)
+		cdw = (committed_dw >> 3) & 0x7;
+
+	if (cdw == 0)
+		return false;
+
+	/* Peer wakes every 2^(cdw-1) DWs: 1, 2, 4, 8, or 16 */
+	wake_interval = 1 << (cdw - 1);
+
+	return (dw_index % wake_interval) == 0;
+}
+
+static bool
+hwsim_nan_peer_present_in_faw(struct hwsim_sta_priv *sp,
+			      struct mac80211_hwsim_data *data, u8 slot)
+{
+	struct cfg80211_chan_def local_chandef;
+
+	scoped_guard(spinlock_bh, &data->nan.state_lock)
+		local_chandef = data->nan.local_sched[slot];
+
+	if (!local_chandef.chan)
+		return false;
+
+	scoped_guard(spinlock_bh, &sp->nan_sched.lock) {
+		for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
+			struct cfg80211_chan_def *peer_chandef;
+
+			if (sp->nan_sched.maps[i].map_id ==
+			    CFG80211_NAN_INVALID_MAP_ID)
+				continue;
+
+			peer_chandef = &sp->nan_sched.maps[i].chans[slot];
+			if (!peer_chandef->chan)
+				continue;
+
+			if (cfg80211_chandef_compatible(&local_chandef,
+							peer_chandef))
+				return true;
+		}
+	}
+
+	return false;
+}
+
 static void
 mac80211_hwsim_nan_schedule_slot(struct mac80211_hwsim_data *data, u8 slot,
 				 bool discontinuity)
@@ -880,15 +988,65 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
 	return 0;
 }
 
+static void hwsim_nan_can_sta_transmit(void *_ctx, struct ieee80211_sta *sta)
+{
+	struct hwsim_nan_sta_iter_ctx *ctx = _ctx;
+
+	if (ctx->can_tx)
+		return;
+
+	for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) {
+		struct ieee80211_txq *txq = sta->txq[i];
+
+		if (!txq)
+			continue;
+
+		if (txq->vif->type != NL80211_IFTYPE_NAN &&
+		    txq->vif->type != NL80211_IFTYPE_NAN_DATA)
+			return;
+
+		if (mac80211_hwsim_nan_txq_transmitting(ctx->hw, txq)) {
+			ctx->can_tx = true;
+			return;
+		}
+	}
+}
+
 static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data)
 {
+	u64 tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
+	u8 slot = hwsim_nan_slot_from_tsf(tsf);
+	bool is_dw_slot = mac80211_hwsim_nan_is_dw_slot(data, slot);
+	struct hwsim_nan_sta_iter_ctx ctx = {
+		.hw = data->hw,
+		.can_tx = false,
+	};
 	u32 timeout_ns;
 
-	/* Nothing to do if we are not in a DW */
-	if (!mac80211_hwsim_nan_txq_transmitting(data->hw,
-						 data->nan.device_vif->txq_mgmt))
+	/* Outside DW, require local FAW schedule to proceed */
+	if (!is_dw_slot) {
+		scoped_guard(spinlock_bh, &data->nan.state_lock) {
+			if (!data->nan.local_sched[slot].chan)
+				return;
+		}
+	}
+
+	guard(rcu)();
+
+	/* Check if management queue can transmit */
+	if (mac80211_hwsim_nan_txq_transmitting(data->hw,
+						data->nan.device_vif->txq_mgmt))
+		goto resume_txqs_timer;
+
+	/* Check if any STA queue can transmit */
+	ieee80211_iterate_stations_atomic(data->hw,
+					  hwsim_nan_can_sta_transmit,
+					  &ctx);
+
+	if (!ctx.can_tx)
 		return;
 
+resume_txqs_timer:
 	/*
 	 * Wait a bit and also randomize things so that not everyone is TXing
 	 * at the same time. Each slot is 16 TU long, this waits between 100 us
@@ -902,6 +1060,26 @@ static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data)
 		      HRTIMER_MODE_REL_SOFT);
 }
 
+static void hwsim_nan_wake_sta_iter(void *_data, struct ieee80211_sta *sta)
+{
+	struct ieee80211_hw *hw = _data;
+
+	for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) {
+		struct ieee80211_txq *txq = sta->txq[i];
+
+		if (!txq)
+			continue;
+
+		/* exit early if non-NAN */
+		if (txq->vif->type != NL80211_IFTYPE_NAN &&
+		    txq->vif->type != NL80211_IFTYPE_NAN_DATA)
+			return;
+
+		if (mac80211_hwsim_nan_txq_transmitting(hw, txq))
+			ieee80211_hwsim_wake_tx_queue(hw, txq);
+	}
+}
+
 enum hrtimer_restart
 mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
 {
@@ -917,6 +1095,11 @@ mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
 		ieee80211_hwsim_wake_tx_queue(data->hw,
 					      data->nan.device_vif->txq_mgmt);
 
+	/* Wake TX queues for all stations */
+	ieee80211_iterate_stations_atomic(data->hw,
+					  hwsim_nan_wake_sta_iter,
+					  data->hw);
+
 	return HRTIMER_NORESTART;
 }
 
@@ -924,6 +1107,9 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 					 struct ieee80211_txq *txq)
 {
 	struct mac80211_hwsim_data *data = hw->priv;
+	struct ieee80211_sta *nmi_sta;
+	struct hwsim_sta_priv *sp;
+	bool is_dw_slot;
 	u64 tsf;
 	u8 slot;
 
@@ -937,36 +1123,54 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 	if (slot != hwsim_nan_slot_from_tsf(tsf + NAN_CHAN_SWITCH_TIME_US))
 		return false;
 
-	/* Check NAN device interface management frame transmission */
-	if (!txq->sta) {
-		/* Only transmit these during one of the DWs */
-		if (slot == SLOT_24GHZ_DW ||
-		    (slot == SLOT_5GHZ_DW &&
-		     (data->nan.bands & BIT(NL80211_BAND_5GHZ))))
-			return true;
+	is_dw_slot = mac80211_hwsim_nan_is_dw_slot(data, slot);
 
-		return false;
+	/* Non-STA TXQ: allow management frames during DW */
+	if (!txq->sta)
+		return is_dw_slot;
+
+	/* STA TXQ: need peer schedule for availability check */
+	nmi_sta = rcu_dereference(txq->sta->nmi) ?: txq->sta;
+	sp = (void *)nmi_sta->drv_priv;
+
+	/* DW slot: NDI can TX only mgmt but not worth checking,
+	 * NMI checks peer's committed DW
+	 */
+	if (is_dw_slot) {
+		if (txq->vif->type == NL80211_IFTYPE_NAN_DATA)
+			return false;
+		return hwsim_nan_peer_present_in_dw(sp, tsf);
 	}
 
-	return true;
+	/* FAW slot: verify local schedule and peer availability */
+	return hwsim_nan_peer_present_in_faw(sp, data, slot);
 }
 
-struct ieee80211_channel *
-mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw)
+void mac80211_hwsim_nan_get_tx_chandef(struct ieee80211_hw *hw,
+				       struct cfg80211_chan_def *chandef)
 {
 	struct mac80211_hwsim_data *data = hw->priv;
 	u64 tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
 	u8 slot = hwsim_nan_slot_from_tsf(tsf);
 
-	if (slot == SLOT_24GHZ_DW)
-		return ieee80211_get_channel(hw->wiphy, 2437);
+	/* DW slots are always 20 MHz */
+	if (slot == SLOT_24GHZ_DW) {
+		cfg80211_chandef_create(chandef,
+					ieee80211_get_channel(hw->wiphy, 2437),
+					NL80211_CHAN_NO_HT);
+		return;
+	}
 
-	if (slot == SLOT_5GHZ_DW &&
-	    data->nan.bands & BIT(NL80211_BAND_5GHZ))
-		return ieee80211_get_channel(hw->wiphy, 5745);
+	if (slot == SLOT_5GHZ_DW && data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
+		cfg80211_chandef_create(chandef,
+					ieee80211_get_channel(hw->wiphy, 5745),
+					NL80211_CHAN_NO_HT);
+		return;
+	}
 
-	/* drop frame and warn, NAN_CHAN_SWITCH_TIME_US should avoid races */
-	return NULL;
+	/* FAW slot: copy local schedule for this slot */
+	scoped_guard(spinlock_bh, &data->nan.state_lock)
+		*chandef = data->nan.local_sched[slot];
 }
 
 bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
@@ -1006,7 +1210,9 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
 	    channel->center_freq == 5745)
 		return true;
 
-	return false;
+	/* Accept frames during FAW slots if chandef is compatible */
+	return hwsim_nan_rx_chandef_compatible(data, slot, channel,
+					       rx_status->bw);
 }
 
 void mac80211_hwsim_nan_local_sched_changed(struct ieee80211_hw *hw,
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index eb53bacee206..81e105ac7b8e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -86,8 +86,8 @@ int mac80211_hwsim_nan_peer_sched_changed(struct ieee80211_hw *hw,
 bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 					 struct ieee80211_txq *txq);
 
-struct ieee80211_channel *
-mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw);
+void mac80211_hwsim_nan_get_tx_chandef(struct ieee80211_hw *hw,
+				       struct cfg80211_chan_def *chandef);
 
 bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
 				struct ieee80211_channel *channel,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 11/14] wifi: mac80211_hwsim: Declare support for secure NAN
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
                   ` (9 preceding siblings ...)
  2026-05-06  3:44 ` [PATCH v2 wireless-next 10/14] wifi: mac80211_hwsim: add NAN data path TX/RX support Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 12/14] wifi: mac80211_hwsim: enable NAN_DATA interface simulation support Miri Korenblit
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay, Avraham Stern

From: Daniel Gabay <daniel.gabay@intel.com>

Advertise NL80211_EXT_FEATURE_SECURE_NAN to indicate support for
NAN Pairing, enabling peer authentication and secure data path
establishment.

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Reviewed-by: Avraham Stern <avraham.stern@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/virtual/mac80211_hwsim_main.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 66cc8c528c6b..2b228ae3029a 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -5718,6 +5718,9 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 			NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
 			NAN_DEV_CAPA_NDPE_SUPPORTED;
 
+		wiphy_ext_feature_set(hw->wiphy,
+				      NL80211_EXT_FEATURE_SECURE_NAN);
+
 		hrtimer_setup(&data->nan.slot_timer,
 			      mac80211_hwsim_nan_slot_timer,
 			      CLOCK_BOOTTIME, HRTIMER_MODE_ABS_SOFT);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 12/14] wifi: mac80211_hwsim: enable NAN_DATA interface simulation support
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
                   ` (10 preceding siblings ...)
  2026-05-06  3:44 ` [PATCH v2 wireless-next 11/14] wifi: mac80211_hwsim: Declare support for secure NAN Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 13/14] wifi: mac80211_hwsim: Do not declare support for NDPE Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 14/14] wifi: mac80211_hwsim: Support Tx of multicast data on NAN Miri Korenblit
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay

From: Daniel Gabay <daniel.gabay@intel.com>

Enable NAN_DATA interface simulation support by adding it to the
supported interface types. This completes the NAN Data Path
simulation introduced in the previous patches.

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/virtual/mac80211_hwsim_main.c | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 2b228ae3029a..bf45d48a3fe0 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -6765,12 +6765,9 @@ static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info)
 		param.p2p_device = true;
 	}
 
-	/* ensure both flag and iftype support is honored */
-	if (param.nan_device ||
-	    param.iftypes & BIT(NL80211_IFTYPE_NAN)) {
-		param.iftypes |= BIT(NL80211_IFTYPE_NAN);
-		param.nan_device = true;
-	}
+	if (param.nan_device)
+		param.iftypes |= BIT(NL80211_IFTYPE_NAN) |
+				 BIT(NL80211_IFTYPE_NAN_DATA);
 
 	if (info->attrs[HWSIM_ATTR_CIPHER_SUPPORT]) {
 		u32 len = nla_len(info->attrs[HWSIM_ATTR_CIPHER_SUPPORT]);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 13/14] wifi: mac80211_hwsim: Do not declare support for NDPE
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
                   ` (11 preceding siblings ...)
  2026-05-06  3:44 ` [PATCH v2 wireless-next 12/14] wifi: mac80211_hwsim: enable NAN_DATA interface simulation support Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  2026-05-06  3:44 ` [PATCH v2 wireless-next 14/14] wifi: mac80211_hwsim: Support Tx of multicast data on NAN Miri Korenblit
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ilan Peer

From: Ilan Peer <ilan.peer@intel.com>

Do not declare support for NAN Data Path Extension attribute
as this is handled by user space and should be set by it.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/virtual/mac80211_hwsim_main.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index bf45d48a3fe0..a214a3a18841 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -5715,8 +5715,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 		hw->wiphy->nan_capa.n_antennas = 0x22;
 		hw->wiphy->nan_capa.max_channel_switch_time = 0;
 		hw->wiphy->nan_capa.dev_capabilities =
-			NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
-			NAN_DEV_CAPA_NDPE_SUPPORTED;
+			NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED;
 
 		wiphy_ext_feature_set(hw->wiphy,
 				      NL80211_EXT_FEATURE_SECURE_NAN);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 wireless-next 14/14] wifi: mac80211_hwsim: Support Tx of multicast data on NAN
  2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
                   ` (12 preceding siblings ...)
  2026-05-06  3:44 ` [PATCH v2 wireless-next 13/14] wifi: mac80211_hwsim: Do not declare support for NDPE Miri Korenblit
@ 2026-05-06  3:44 ` Miri Korenblit
  13 siblings, 0 replies; 15+ messages in thread
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ilan Peer, Benjamin Berg

From: Ilan Peer <ilan.peer@intel.com>

Add support for transmitting multicast data frames. These
frames can be transmitted when all the peer NDI stations
on the interface are available at the current slot.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Reviewed-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 56 ++++++++++++++++++-
 1 file changed, 53 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 0397c43724fe..7be64c45babf 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -41,6 +41,13 @@ struct hwsim_nan_sta_iter_ctx {
 	bool can_tx;
 };
 
+struct hwsim_nan_mcast_data_iter_ctx {
+	struct ieee80211_hw *hw;
+	struct ieee80211_vif *vif;
+	size_t n_vif_sta;
+	size_t n_sta_can_tx;
+};
+
 static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data);
 
 static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
@@ -1103,6 +1110,42 @@ mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
 	return HRTIMER_NORESTART;
 }
 
+static void
+hwsim_nan_can_mcast_sta_transmit(void *_ctx, struct ieee80211_sta *sta)
+{
+	struct hwsim_nan_mcast_data_iter_ctx *ctx = _ctx;
+	struct ieee80211_txq *txq = sta->txq[0];
+
+	if (!txq || txq->vif != ctx->vif)
+		return;
+
+	ctx->n_vif_sta++;
+	if (mac80211_hwsim_nan_txq_transmitting(ctx->hw, txq))
+		ctx->n_sta_can_tx++;
+}
+
+static bool
+mac80211_hwsim_nan_mcast_data_transmitting(struct ieee80211_hw *hw,
+					   struct ieee80211_txq *txq)
+{
+	struct mac80211_hwsim_data *data = hw->priv;
+	struct hwsim_nan_mcast_data_iter_ctx ctx = {
+		.hw = hw,
+		.vif = txq->vif,
+		.n_sta_can_tx = 0,
+		.n_vif_sta = 0,
+	};
+
+	/* Check if all the stations associated with the current
+	 * interface are available.
+	 */
+	ieee80211_iterate_stations_atomic(data->hw,
+					  hwsim_nan_can_mcast_sta_transmit,
+					  &ctx);
+
+	return ctx.n_vif_sta && ctx.n_sta_can_tx == ctx.n_vif_sta;
+}
+
 bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 					 struct ieee80211_txq *txq)
 {
@@ -1125,9 +1168,16 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 
 	is_dw_slot = mac80211_hwsim_nan_is_dw_slot(data, slot);
 
-	/* Non-STA TXQ: allow management frames during DW */
-	if (!txq->sta)
-		return is_dw_slot;
+	if (!txq->sta) {
+		/* Non-STA TXQ: allow management frames during DW */
+		if (txq->vif->type == NL80211_IFTYPE_NAN)
+			return is_dw_slot;
+
+		/* Allow multicast data when all the peers are available
+		 * on this slot
+		 */
+		return mac80211_hwsim_nan_mcast_data_transmitting(hw, txq);
+	}
 
 	/* STA TXQ: need peer schedule for availability check */
 	nmi_sta = rcu_dereference(txq->sta->nmi) ?: txq->sta;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2026-05-06  3:45 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-06  3:44 [PATCH v2 wireless-next 00/14] wifi: mac80211_hwsim: some more NAN patches Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 01/14] wifi: mac80211_hwsim: limit TX of frames to the NAN DW Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 02/14] wifi: mac80211_hwsim: select NAN TX channel based on current TSF Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 03/14] wifi: mac80211_hwsim: only RX on NAN when active on a slot Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 04/14] wifi: mac80211_hwsim: protect tsf_offset using a spinlock Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 05/14] wifi: mac80211_hwsim: implement NAN synchronization Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 06/14] wifi: mac80211_hwsim: add NAN_DATA interface limits Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 07/14] wifi: mac80211_hwsim: add NAN PHY capabilities Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 08/14] wifi: mac80211_hwsim: implement NAN schedule callbacks Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 09/14] wifi: mac80211_hwsim: set HAS_RATE_CONTROL when using NAN Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 10/14] wifi: mac80211_hwsim: add NAN data path TX/RX support Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 11/14] wifi: mac80211_hwsim: Declare support for secure NAN Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 12/14] wifi: mac80211_hwsim: enable NAN_DATA interface simulation support Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 13/14] wifi: mac80211_hwsim: Do not declare support for NDPE Miri Korenblit
2026-05-06  3:44 ` [PATCH v2 wireless-next 14/14] wifi: mac80211_hwsim: Support Tx of multicast data on NAN Miri Korenblit

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox