Linux wireless drivers development
 help / color / mirror / Atom feed
* Complete list of AF_ALG algorithms used by iwd
From: Eric Biggers @ 2026-05-04  4:50 UTC (permalink / raw)
  To: iwd, Marcel Holtmann, James Prestwood
  Cc: linux-crypto, linux-wireless, linux-kernel, Demi Marie Obenour

With AF_ALG being on its way out due to its frequent vulnerabilities,
iwd will need to be fixed to follow the standard practice of using
userspace crypto code (for example, libcrypto) instead.  In the mean
time, AF_ALG is at least going to need to be hardened by removing
unnecessary functionality, for example unnecessary algorithms.

Based on the iwd and libell source code, it appears iwd is relying on
the following algorithm names from AF_ALG.  Please confirm that this
list is correct and that these are the ones we need to keep for iwd:

    cmac(aes)
    hmac(md5)
    hmac(sha1)
    hmac(sha224)
    hmac(sha256)
    hmac(sha384)
    hmac(sha512)
    md4
    md5
    sha1
    sha224
    sha256
    sha384
    sha512
    ecb(aes)
    cbc(aes)
    ctr(aes)
    ecb(des)
    cbc(des)
    cbc(des3_ede)

Also, does iwd always hold CAP_NET_ADMIN?  We're also considering adding
a privilege check to AF_ALG.  (It's TBD whether it would be
unconditional or controlled by a sysctl.)

- Eric

^ permalink raw reply

* [PATCH v4 wireless-next 00/15] wifi: mac80211: more NAN patches
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless

Hi,
This series adds a few more NAN features to mac80211 and mainly hwsim
support.

Thanks,
Miri
---

Andrei Otcheretianski (1):
  wifi: mac80211: Fix a kernel panic in ieee80211_encrypt_tx_skb()

Avraham Stern (1):
  wifi: mac80211: accept protected frames for NAN device

Benjamin Berg (8):
  wifi: mac80211_hwsim: remove unused nan_vif struct member
  wifi: mac80211_hwsim: move NAN related variables into a struct
  wifi: mac80211_hwsim: split NAN handling into separate file
  wifi: mac80211_hwsim: rename and switch simulation time to boottime
  wifi: mac80211_hwsim: move timestamp writing later in the datapath
  wifi: mac80211_hwsim: register beacon timer by calculating TBTT
  wifi: mac80211_hwsim: refactor NAN timer handling
  wifi: mac80211_hwsim: switch to use TXQs

Ilan Peer (2):
  wifi: mac80211: allow userspace TX/RX over NAN Data interfaces
  wifi: mac80211: Allow setting MAC address on interface creation

Miri Korenblit (3):
  wifi: mac80211: track the id of the NAN cluster we joined
  wifi: mac80211: avoid out-of-bounds access in monitor
  wifi: mac80211: add NAN channel evacuation support

 MAINTAINERS                                   |   2 +-
 drivers/net/wireless/virtual/Makefile         |   2 +
 .../net/wireless/virtual/mac80211_hwsim_i.h   | 139 +++++
 ...mac80211_hwsim.c => mac80211_hwsim_main.c} | 508 +++++-------------
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 233 ++++++++
 .../net/wireless/virtual/mac80211_hwsim_nan.h |  36 ++
 include/net/mac80211.h                        |  29 +
 net/mac80211/cfg.c                            |  19 +
 net/mac80211/chan.c                           |  28 +-
 net/mac80211/iface.c                          |   7 +-
 net/mac80211/main.c                           |   4 +
 net/mac80211/nan.c                            | 126 +++++
 net/mac80211/offchannel.c                     |   9 +-
 net/mac80211/rx.c                             |  26 +-
 net/mac80211/status.c                         |   9 +-
 net/mac80211/tx.c                             |  18 +-
 16 files changed, 786 insertions(+), 409 deletions(-)
 create mode 100644 drivers/net/wireless/virtual/mac80211_hwsim_i.h
 rename drivers/net/wireless/virtual/{mac80211_hwsim.c => mac80211_hwsim_main.c} (94%)
 create mode 100644 drivers/net/wireless/virtual/mac80211_hwsim_nan.c
 create mode 100644 drivers/net/wireless/virtual/mac80211_hwsim_nan.h

-- 
2.34.1
---
v2: fix MAINTAINERS file to include the newly added files
v3: fix an "always true" check in commit "split NAN handling into separate file"
v4: fix the unnecessary check for real...

^ permalink raw reply

* [PATCH v4 wireless-next 01/15] wifi: mac80211: track the id of the NAN cluster we joined
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

Currently, we store in nan.conf the cluster id that was configured from
upper layer to be used when the device opens a cluster.
But after we joined a cluster, the configured cluster id is no longer
relevant. Particularly, in reconfig we will give the driver the
(possibly) wrong cluster id.

Add an API to be called by the driver when joined a cluster
in which the cluster id will be updated.
Use the locally stored cluster id instead of cfg80211's copy.

Ignore cluster id updates from cfg80211 if we already have one
configured.

Adjust the drivers that use the cfg80211 API
(cfg80211_nan_cluster_joined) directly, otherwise we break functionality
(i.e. accept frame check won't evaluate to true).

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 include/net/mac80211.h | 14 ++++++++++++++
 net/mac80211/cfg.c     | 19 +++++++++++++++++++
 net/mac80211/rx.c      |  4 ++--
 net/mac80211/tx.c      |  2 +-
 4 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 02318a4be0e1..0d1b1d726b9c 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -7891,6 +7891,20 @@ void ieee80211_nan_func_match(struct ieee80211_vif *vif,
  */
 void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif);
 
+/**
+ * ieee80211_nan_cluster_joined - notify about NAN cluster join.
+ *
+ * This function is used to notify mac80211 about NAN cluster join.
+ *
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ * @cluster_id: the cluster ID that was joined
+ * @new_cluster: true if this is a new cluster
+ * @gfp: allocation flags
+ */
+void ieee80211_nan_cluster_joined(struct ieee80211_vif *vif,
+				  const u8 *cluster_id, bool new_cluster,
+				  gfp_t gfp);
+
 /**
  * ieee80211_calc_rx_airtime - calculate estimated transmission airtime for RX.
  *
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 00261bd6674b..c71af8878562 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -5215,6 +5215,25 @@ void ieee80211_nan_func_match(struct ieee80211_vif *vif,
 }
 EXPORT_SYMBOL(ieee80211_nan_func_match);
 
+void ieee80211_nan_cluster_joined(struct ieee80211_vif *vif,
+				  const u8 *cluster_id, bool new_cluster,
+				  gfp_t gfp)
+{
+	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+
+	if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
+		return;
+
+	if (WARN_ON(!sdata->u.nan.started))
+		return;
+
+	ether_addr_copy(sdata->u.nan.conf.cluster_id, cluster_id);
+
+	cfg80211_nan_cluster_joined(ieee80211_vif_to_wdev(vif), cluster_id,
+				    new_cluster, gfp);
+}
+EXPORT_SYMBOL(ieee80211_nan_cluster_joined);
+
 static int ieee80211_set_multicast_to_unicast(struct wiphy *wiphy,
 					      struct net_device *dev,
 					      const bool enabled)
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 3e5d1c47a5b0..82ea7404f3da 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -4629,7 +4629,7 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
 		 * action frames or authentication frames that are addressed to
 		 * the local NAN interface.
 		 */
-		return memcmp(sdata->wdev.u.nan.cluster_id,
+		return memcmp(sdata->u.nan.conf.cluster_id,
 			      hdr->addr3, ETH_ALEN) == 0 &&
 			(ieee80211_is_public_action(hdr, skb->len) ||
 			 (ieee80211_is_auth(hdr->frame_control) &&
@@ -4646,7 +4646,7 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
 			if (!nmi)
 				return false;
 
-			if (!ether_addr_equal(nmi->wdev.u.nan.cluster_id,
+			if (!ether_addr_equal(nmi->u.nan.conf.cluster_id,
 					      hdr->addr3))
 				return false;
 
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 8e4876d1c544..1702f816419b 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2854,7 +2854,7 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata,
 			ret = -ENOTCONN;
 			goto free;
 		}
-		memcpy(hdr.addr3, nmi->wdev.u.nan.cluster_id, ETH_ALEN);
+		memcpy(hdr.addr3, nmi->u.nan.conf.cluster_id, ETH_ALEN);
 		hdrlen = 24;
 		break;
 	}
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 06/15] wifi: mac80211: avoid out-of-bounds access in monitor
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ilan Peer, Johannes Berg
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

In NAN, we don't know on what band the frame will be sent. Therefore we
set info->band to NUM_NL80211_BANDS. However, this leads to out-of-bound
access in ieee80211_add_tx_radiotap_header when we try to access the
sbands array.

Fix it by not accessing the array if the band is NUM_NL80211_BANDS.
This means that we will not report rate info for legacy rate in NAN.
But nobody really cares about it.

Reviewed-by: Ilan Peer <ilan.peer@intel.com>
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 net/mac80211/status.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index 4b38aa0e902a..8716eda8317d 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -5,7 +5,7 @@
  * Copyright 2006-2007	Jiri Benc <jbenc@suse.cz>
  * Copyright 2008-2010	Johannes Berg <johannes@sipsolutions.net>
  * Copyright 2013-2014  Intel Mobile Communications GmbH
- * Copyright 2021-2025  Intel Corporation
+ * Copyright 2021-2026  Intel Corporation
  */
 
 #include <linux/export.h>
@@ -295,9 +295,10 @@ ieee80211_add_tx_radiotap_header(struct ieee80211_local *local,
 						 RATE_INFO_FLAGS_VHT_MCS |
 						 RATE_INFO_FLAGS_HE_MCS)))
 			legacy_rate = status_rate->rate_idx.legacy;
-	} else if (info->status.rates[0].idx >= 0 &&
-		 !(info->status.rates[0].flags & (IEEE80211_TX_RC_MCS |
-						  IEEE80211_TX_RC_VHT_MCS))) {
+	} else if (info->band < NUM_NL80211_BANDS &&
+		   info->status.rates[0].idx >= 0 &&
+		   !(info->status.rates[0].flags & (IEEE80211_TX_RC_MCS |
+						    IEEE80211_TX_RC_VHT_MCS))) {
 		struct ieee80211_supported_band *sband;
 
 		sband = local->hw.wiphy->bands[info->band];
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 07/15] wifi: mac80211: add NAN channel evacuation support
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

A NAN channel can be evacuated, i.e. detached from its chanctx, if all
chanctxs are used by NAN and a chanctx is needed for something else.
For example if the STA interface needs to perform a channel switch.

Implement the evacuation: detach the NAN channel from its chanctx, remove
all the peer NAN channels that were using this chanctx, and update the
driver.

Internally, the NAN channel evacuation will be triggered in the scenario
described above, and API is provided for the driver to also trigger it.

The driver/device is assumed to publish a ULW to notify the peers about
the fact that we won't be present on this NAN channel anymore.

Also export this as an API for the drivers: if a driver has other
resources per channel, it might want to trigger channel evacuation in
order to free up such internal resources for other usages.

Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 include/net/mac80211.h |  15 +++++
 net/mac80211/chan.c    |  28 ++++++---
 net/mac80211/nan.c     | 126 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 162 insertions(+), 7 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 0d1b1d726b9c..d909bc1b29ff 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -7905,6 +7905,21 @@ void ieee80211_nan_cluster_joined(struct ieee80211_vif *vif,
 				  const u8 *cluster_id, bool new_cluster,
 				  gfp_t gfp);
 
+/**
+ * ieee80211_nan_try_evacuate - try to evacuate a NAN channel
+ *
+ * This function tries to evacuate a NAN channel that is using the given
+ * channel context, to free up channel context resources.
+ *
+ * @hw: pointer as obtained from ieee80211_alloc_hw()
+ * @conf: the channel context configuration to try to evacuate. If %NULL,
+ *	the NAN channel that has the fewest slots scheduled will be evacuated.
+ *
+ * Return: %true if a channel was evacuated, %false otherwise
+ */
+bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw,
+				struct ieee80211_chanctx_conf *conf);
+
 /**
  * ieee80211_calc_rx_airtime - calculate estimated transmission airtime for RX.
  *
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 248531051a4e..9683d3e6e1d2 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -1861,16 +1861,21 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
 		}
 
 		if (n_assigned != n_reserved) {
-			if (n_ready == n_reserved) {
-				wiphy_info(local->hw.wiphy,
-					   "channel context reservation cannot be finalized because some interfaces aren't switching\n");
-				err = -EBUSY;
-				goto err;
-			}
+			if (n_ready != n_reserved)
+				return -EAGAIN;
 
-			return -EAGAIN;
+			if (n_assigned == n_reserved + 1 &&
+			    ieee80211_nan_try_evacuate(&local->hw,
+						       &ctx->replace_ctx->conf))
+				goto use_reserved;
+
+			wiphy_info(local->hw.wiphy,
+				   "channel context reservation cannot be finalized because some interfaces aren't switching\n");
+			err = -EBUSY;
+			goto err;
 		}
 
+use_reserved:
 		ctx->conf.radar_enabled = false;
 		for_each_chanctx_user_reserved(local, ctx, &iter) {
 			if (ieee80211_link_has_in_place_reservation(iter.link) &&
@@ -2178,6 +2183,15 @@ int _ieee80211_link_use_channel(struct ieee80211_link_data *link,
 
 	ctx = ieee80211_find_or_create_chanctx(sdata, chanreq, mode,
 					       assign_on_failure, &reused_ctx);
+	if (IS_ERR(ctx)) {
+		/* Try to evacuate a NAN channel to free up a chanctx */
+		if (ieee80211_nan_try_evacuate(&local->hw, NULL))
+			ctx = ieee80211_find_or_create_chanctx(sdata, chanreq,
+							       mode,
+							       assign_on_failure,
+							       &reused_ctx);
+	}
+
 	if (IS_ERR(ctx)) {
 		ret = PTR_ERR(ctx);
 		goto out;
diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c
index 4e262b624521..cea620aaee6a 100644
--- a/net/mac80211/nan.c
+++ b/net/mac80211/nan.c
@@ -334,7 +334,10 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
 			sched_idx_to_chan[i] = chan;
 			ieee80211_nan_init_channel(chan,
 						   &sched->nan_channels[i]);
+		}
 
+		/* Also a pre-existing channel might have been ULWed, so no chanctx */
+		if (!chan->chanctx_conf) {
 			ret = ieee80211_nan_use_chanctx(sdata, chan, false);
 			if (ret) {
 				memset(chan, 0, sizeof(*chan));
@@ -708,3 +711,126 @@ int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
 	ieee80211_nan_free_peer_sched(to_free);
 	return ret;
 }
+
+static void
+ieee80211_nan_evacuate_channel(struct ieee80211_sub_if_data *sdata,
+			       struct ieee80211_nan_channel *nan_channel)
+{
+	struct ieee80211_chanctx_conf *conf;
+	struct ieee80211_chanctx *ctx;
+
+	lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+	if (WARN_ON(!nan_channel || !nan_channel->chanreq.oper.chan))
+		return;
+
+	conf = nan_channel->chanctx_conf;
+	if (WARN_ON(!conf))
+		return;
+
+	nan_channel->chanctx_conf = NULL;
+
+	/* Update all peer channels that reference this chanctx */
+	ieee80211_nan_update_peer_channels(sdata, conf);
+
+	drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
+
+	cfg80211_nan_channel_evac(&sdata->wdev, &nan_channel->chanreq.oper,
+				  GFP_KERNEL);
+
+	/* Update NDI carrier states */
+	ieee80211_nan_update_all_ndi_carriers(sdata->local);
+
+	/* Clean up the channel context if no longer used */
+	ctx = container_of(conf, struct ieee80211_chanctx, conf);
+
+	if (ieee80211_chanctx_num_assigned(sdata->local, ctx) > 0) {
+		ieee80211_recalc_chanctx_chantype(sdata->local, ctx);
+		ieee80211_recalc_smps_chanctx(sdata->local, ctx);
+		ieee80211_recalc_chanctx_min_def(sdata->local, ctx);
+	}
+
+	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0)
+		ieee80211_free_chanctx(sdata->local, ctx, false);
+}
+
+bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw,
+				struct ieee80211_chanctx_conf *conf)
+{
+	struct ieee80211_sub_if_data *sdata = NULL, *tmp;
+	struct ieee80211_local *local = hw_to_local(hw);
+	struct ieee80211_nan_channel *evac_chan = NULL;
+	struct ieee80211_nan_sched_cfg *sched_cfg;
+	struct ieee80211_chanctx *ctx = NULL;
+	int min_slot_count = INT_MAX;
+	int usable_channels = 0;
+
+	lockdep_assert_wiphy(local->hw.wiphy);
+
+	if (conf)
+		ctx = container_of(conf, struct ieee80211_chanctx, conf);
+
+	/* Find the NAN interface - there can only be one */
+	list_for_each_entry(tmp, &local->interfaces, list) {
+		if (ieee80211_sdata_running(tmp) &&
+		    tmp->vif.type == NL80211_IFTYPE_NAN) {
+			sdata = tmp;
+			break;
+		}
+	}
+
+	if (!sdata)
+		return false;
+
+	sched_cfg = &sdata->vif.cfg.nan_sched;
+
+	/* Find the channel to evacuate and count usable channels */
+	for (int i = 0; i < IEEE80211_NAN_MAX_CHANNELS; i++) {
+		struct ieee80211_nan_channel *chan =
+			&sched_cfg->channels[i];
+		struct ieee80211_chanctx *chan_ctx;
+		int slot_count = 0;
+
+		if (!chan->chanreq.oper.chan || !chan->chanctx_conf)
+			continue;
+
+		usable_channels++;
+
+		chan_ctx = container_of(chan->chanctx_conf,
+					struct ieee80211_chanctx, conf);
+
+		/* If ctx specified, only consider that specific chanctx */
+		if (ctx) {
+			if (chan_ctx == ctx)
+				evac_chan = chan;
+			continue;
+		}
+
+		/* Can only evacuate channels whose chanctx is NAN-only */
+		if (ieee80211_chanctx_refcount(local, chan_ctx) > 1)
+			continue;
+
+		/* Count how many time slots use this channel */
+		for (int s = 0; s < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; s++)
+			if (sched_cfg->schedule[s] == chan)
+				slot_count++;
+
+		if (slot_count < min_slot_count) {
+			min_slot_count = slot_count;
+			evac_chan = chan;
+		}
+	}
+
+	/* No suitable NAN channel found */
+	if (!evac_chan)
+		return false;
+
+	/* NAN needs at least one remaining usable channel after evacuation */
+	if (usable_channels < 2)
+		return false;
+
+	ieee80211_nan_evacuate_channel(sdata, evac_chan);
+
+	return true;
+}
+EXPORT_SYMBOL(ieee80211_nan_try_evacuate);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 02/15] wifi: mac80211: allow userspace TX/RX over NAN Data interfaces
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ilan Peer
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

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

Allow TX/RX of action frames (for NAN action frames) over
NAN Data interfaces to support cases where there's a secure
NDP and NAFs may be exchanged over that.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 net/mac80211/main.c       | 4 ++++
 net/mac80211/offchannel.c | 9 +++++++--
 net/mac80211/tx.c         | 5 +++--
 3 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index f47dd58770ad..8400792d67e2 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -750,6 +750,10 @@ ieee80211_default_mgmt_stypes[NUM_NL80211_IFTYPES] = {
 		.rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
 			BIT(IEEE80211_STYPE_AUTH >> 4),
 	},
+	[NL80211_IFTYPE_NAN_DATA] = {
+		.tx = 0xffff,
+		.rx = BIT(IEEE80211_STYPE_ACTION >> 4),
+	},
 };
 
 static const struct ieee80211_ht_cap mac80211_ht_capa_mod_mask = {
diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c
index f60f6a58948b..10c962d28037 100644
--- a/net/mac80211/offchannel.c
+++ b/net/mac80211/offchannel.c
@@ -8,7 +8,7 @@
  * Copyright 2006-2007	Jiri Benc <jbenc@suse.cz>
  * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
  * Copyright 2009	Johannes Berg <johannes@sipsolutions.net>
- * Copyright (C) 2019, 2022-2025 Intel Corporation
+ * Copyright (C) 2019, 2022-2026 Intel Corporation
  */
 #include <linux/export.h>
 #include <net/mac80211.h>
@@ -898,6 +898,10 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
 		break;
 	case NL80211_IFTYPE_NAN:
 		break;
+	case NL80211_IFTYPE_NAN_DATA:
+		if (is_multicast_ether_addr(mgmt->da))
+			return -EOPNOTSUPP;
+		break;
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -911,7 +915,8 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
 	/* Check if the operating channel is the requested channel */
 	if (!params->chan && mlo_sta) {
 		need_offchan = false;
-	} else if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+	} else if (sdata->vif.type == NL80211_IFTYPE_NAN ||
+		   sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
 		/* Frames can be sent during NAN schedule */
 	} else if (!need_offchan) {
 		struct ieee80211_chanctx_conf *chanctx_conf = NULL;
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 1702f816419b..c18de2cb3769 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -5,7 +5,7 @@
  * Copyright 2006-2007	Jiri Benc <jbenc@suse.cz>
  * Copyright 2007	Johannes Berg <johannes@sipsolutions.net>
  * Copyright 2013-2014  Intel Mobile Communications GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
  *
  * Transmit and frame generation functions.
  */
@@ -6377,7 +6377,8 @@ void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,
 	enum nl80211_band band;
 
 	rcu_read_lock();
-	if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+	if (sdata->vif.type == NL80211_IFTYPE_NAN ||
+	    sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
 		band = NUM_NL80211_BANDS;
 	} else if (!ieee80211_vif_is_mld(&sdata->vif)) {
 		WARN_ON(link_id >= 0);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 03/15] wifi: mac80211: accept protected frames for NAN device
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Avraham Stern
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

From: Avraham Stern <avraham.stern@intel.com>

Some frames sent to the NAN device may be protected, such as
protected action frames (in particular protected dual of
public action).

Accept robust management frames except disassoc on the NAN
device, and clean up the code a little bit.

Signed-off-by: Avraham Stern <avraham.stern@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 net/mac80211/rx.c | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 82ea7404f3da..e1f376e0620c 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -4624,16 +4624,24 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
 		    ieee80211_has_fromds(hdr->frame_control))
 			return false;
 
-		/* Accept only frames that are addressed to the NAN cluster
+		/*
+		 * Accept only frames that are addressed to the NAN cluster
 		 * (based on the Cluster ID). From these frames, accept only
-		 * action frames or authentication frames that are addressed to
-		 * the local NAN interface.
+		 *  - public action frames,
+		 *  - authentication frames to the local address, and
+		 *  - robust management frames except disassoc.
 		 */
-		return memcmp(sdata->u.nan.conf.cluster_id,
-			      hdr->addr3, ETH_ALEN) == 0 &&
-			(ieee80211_is_public_action(hdr, skb->len) ||
-			 (ieee80211_is_auth(hdr->frame_control) &&
-			  ether_addr_equal(sdata->vif.addr, hdr->addr1)));
+		if (!ether_addr_equal(sdata->u.nan.conf.cluster_id, hdr->addr3))
+			return false;
+		if (ieee80211_is_public_action(hdr, skb->len))
+			return true;
+		if (ieee80211_is_auth(hdr->frame_control) &&
+		    ether_addr_equal(sdata->vif.addr, hdr->addr1))
+			return true;
+		if (!ieee80211_is_disassoc(hdr->frame_control) &&
+		    ieee80211_is_robust_mgmt_frame(skb))
+			return true;
+		return false;
 	case NL80211_IFTYPE_NAN_DATA:
 		if (ieee80211_has_tods(hdr->frame_control) ||
 		    ieee80211_has_fromds(hdr->frame_control))
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 05/15] wifi: mac80211: Fix a kernel panic in ieee80211_encrypt_tx_skb()
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Andrei Otcheretianski
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

From: Andrei Otcheretianski <andrei.otcheretianski@intel.com>

skb->dev may be NULL for frames on non-netdev devices. For example, NAN
device frames after pairing. Fix it.

Signed-off-by: Andrei Otcheretianski <andrei.otcheretianski@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 net/mac80211/tx.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index c18de2cb3769..933c86ca21c3 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -5355,7 +5355,7 @@ static int ieee80211_beacon_protect(struct sk_buff *skb,
 int ieee80211_encrypt_tx_skb(struct sk_buff *skb)
 {
 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
-	struct ieee80211_sub_if_data *sdata;
+	struct ieee80211_sub_if_data *sdata = NULL;
 	struct sk_buff *check_skb;
 	struct ieee80211_tx_data tx;
 	ieee80211_tx_result res;
@@ -5370,7 +5370,14 @@ int ieee80211_encrypt_tx_skb(struct sk_buff *skb)
 	__skb_queue_head_init(&tx.skbs);
 	__skb_queue_tail(&tx.skbs, skb);
 
-	sdata = IEEE80211_DEV_TO_SUB_IF(skb->dev);
+	if (skb->dev)
+		sdata = IEEE80211_DEV_TO_SUB_IF(skb->dev);
+	else if (info->control.vif)
+		sdata = vif_to_sdata(info->control.vif);
+
+	if (WARN_ON(!sdata))
+		return -EINVAL;
+
 	tx.sdata = sdata;
 	tx.local = sdata->local;
 	res = ieee80211_tx_h_encrypt(&tx);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 08/15] wifi: mac80211_hwsim: remove unused nan_vif struct member
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

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

The struct also contains nan_device_vif and that is the member that is
being used.

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/virtual/mac80211_hwsim.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.c b/drivers/net/wireless/virtual/mac80211_hwsim.c
index 1fcf5d0d2e13..7ab0765cb482 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim.c
@@ -765,7 +765,6 @@ struct mac80211_hwsim_data {
 	enum nl80211_band nan_curr_dw_band;
 	struct hrtimer nan_timer;
 	bool notify_dw;
-	struct ieee80211_vif *nan_vif;
 };
 
 static const struct rhashtable_params hwsim_rht_params = {
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 04/15] wifi: mac80211: Allow setting MAC address on interface creation
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ilan Peer, Johannes Berg
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

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

Allow setting the interface MAC address for NAN Device interfaces
and P2P Device interfaces on interface creation.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 net/mac80211/iface.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 95b779c4d627..683d8db4da14 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -2280,7 +2280,12 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
 
 		sdata->dev = NULL;
 		strscpy(sdata->name, name, IFNAMSIZ);
-		ieee80211_assign_perm_addr(local, wdev->address, type);
+
+		if (is_valid_ether_addr(params->macaddr))
+			memcpy(wdev->address, params->macaddr, ETH_ALEN);
+		else
+			ieee80211_assign_perm_addr(local, wdev->address, type);
+
 		memcpy(sdata->vif.addr, wdev->address, ETH_ALEN);
 		ether_addr_copy(sdata->vif.bss_conf.addr, sdata->vif.addr);
 
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 09/15] wifi: mac80211_hwsim: move NAN related variables into a struct
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

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

Move it all into a common struct to better segment the code.

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/virtual/mac80211_hwsim.c | 76 ++++++++++---------
 1 file changed, 40 insertions(+), 36 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.c b/drivers/net/wireless/virtual/mac80211_hwsim.c
index 7ab0765cb482..d5b9170f690c 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim.c
@@ -663,6 +663,15 @@ struct mac80211_hwsim_link_data {
 	struct hrtimer beacon_timer;
 };
 
+struct mac80211_hwsim_nan_data {
+	struct ieee80211_vif *device_vif;
+	u8 bands;
+
+	enum nl80211_band curr_dw_band;
+	struct hrtimer timer;
+	bool notify_dw;
+};
+
 struct mac80211_hwsim_data {
 	struct list_head list;
 	struct rhash_head rht;
@@ -759,12 +768,7 @@ struct mac80211_hwsim_data {
 
 	struct mac80211_hwsim_link_data link_data[IEEE80211_MLD_MAX_NUM_LINKS];
 
-	struct ieee80211_vif *nan_device_vif;
-	u8 nan_bands;
-
-	enum nl80211_band nan_curr_dw_band;
-	struct hrtimer nan_timer;
-	bool notify_dw;
+	struct mac80211_hwsim_nan_data nan;
 };
 
 static const struct rhashtable_params hwsim_rht_params = {
@@ -2108,9 +2112,9 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
 		 * on channel 6 or channel 149, unless a ROC is in progress (for
 		 * USD use cases).
 		 */
-		if (data->nan_curr_dw_band == NL80211_BAND_2GHZ)
+		if (data->nan.curr_dw_band == NL80211_BAND_2GHZ)
 			channel = ieee80211_get_channel(hw->wiphy, 2437);
-		else if (data->nan_curr_dw_band == NL80211_BAND_5GHZ)
+		else if (data->nan.curr_dw_band == NL80211_BAND_5GHZ)
 			channel = ieee80211_get_channel(hw->wiphy, 5745);
 		else
 			channel = NULL;
@@ -4131,21 +4135,21 @@ mac80211_hwsim_nan_dw_start(struct hrtimer *timer)
 {
 	struct mac80211_hwsim_data *data =
 		container_of(timer, struct mac80211_hwsim_data,
-			     nan_timer);
+			     nan.timer);
 	struct ieee80211_hw *hw = data->hw;
 	u64 orig_tsf = mac80211_hwsim_get_tsf(hw, NULL), tsf = orig_tsf;
 	u32 dw_int = 512 * 1024;
 	u64 until_dw;
 
-	if (!data->nan_device_vif)
+	if (!data->nan.device_vif)
 		return HRTIMER_NORESTART;
 
-	if (data->nan_bands & BIT(NL80211_BAND_5GHZ)) {
-		if (data->nan_curr_dw_band == NL80211_BAND_2GHZ) {
+	if (data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
+		if (data->nan.curr_dw_band == NL80211_BAND_2GHZ) {
 			dw_int = 128 * 1024;
-			data->nan_curr_dw_band = NL80211_BAND_5GHZ;
-		} else if (data->nan_curr_dw_band == NL80211_BAND_5GHZ) {
-			data->nan_curr_dw_band = NL80211_BAND_2GHZ;
+			data->nan.curr_dw_band = NL80211_BAND_5GHZ;
+		} else if (data->nan.curr_dw_band == NL80211_BAND_5GHZ) {
+			data->nan.curr_dw_band = NL80211_BAND_2GHZ;
 		}
 	}
 
@@ -4162,18 +4166,18 @@ mac80211_hwsim_nan_dw_start(struct hrtimer *timer)
 	 */
 	wiphy_debug(hw->wiphy,
 		    "%s: tsf=%llx, curr_dw_band=%u, next_dw=%llu\n",
-		    __func__, orig_tsf, data->nan_curr_dw_band,
+		    __func__, orig_tsf, data->nan.curr_dw_band,
 		    until_dw);
 
-	hrtimer_forward_now(&data->nan_timer,
+	hrtimer_forward_now(&data->nan.timer,
 			    ns_to_ktime(until_dw * NSEC_PER_USEC));
 
-	if (data->notify_dw) {
+	if (data->nan.notify_dw) {
 		struct ieee80211_channel *ch;
 		struct wireless_dev *wdev =
-			ieee80211_vif_to_wdev(data->nan_device_vif);
+			ieee80211_vif_to_wdev(data->nan.device_vif);
 
-		if (data->nan_curr_dw_band == NL80211_BAND_5GHZ)
+		if (data->nan.curr_dw_band == NL80211_BAND_5GHZ)
 			ch = ieee80211_get_channel(hw->wiphy, 5745);
 		else
 			ch = ieee80211_get_channel(hw->wiphy, 2437);
@@ -4197,18 +4201,18 @@ static int mac80211_hwsim_start_nan(struct ieee80211_hw *hw,
 	if (vif->type != NL80211_IFTYPE_NAN)
 		return -EINVAL;
 
-	if (data->nan_device_vif)
+	if (data->nan.device_vif)
 		return -EALREADY;
 
 	/* set this before starting the timer, as preemption might occur */
-	data->nan_device_vif = vif;
-	data->nan_bands = conf->bands;
-	data->nan_curr_dw_band = NL80211_BAND_2GHZ;
+	data->nan.device_vif = vif;
+	data->nan.bands = conf->bands;
+	data->nan.curr_dw_band = NL80211_BAND_2GHZ;
 
 	wiphy_debug(hw->wiphy, "nan_started, next_dw=%llu\n",
 		    until_dw);
 
-	hrtimer_start(&data->nan_timer,
+	hrtimer_start(&data->nan.timer,
 		      ns_to_ktime(until_dw * NSEC_PER_USEC),
 		      HRTIMER_MODE_REL_SOFT);
 
@@ -4224,7 +4228,7 @@ static int mac80211_hwsim_start_nan(struct ieee80211_hw *hw,
 		hwsim_nan_cluster_id[5] = get_random_u8();
 	}
 
-	data->notify_dw = conf->enable_dw_notification;
+	data->nan.notify_dw = conf->enable_dw_notification;
 
 	cfg80211_nan_cluster_joined(wdev, hwsim_nan_cluster_id, true,
 				    GFP_KERNEL);
@@ -4239,16 +4243,16 @@ static int mac80211_hwsim_stop_nan(struct ieee80211_hw *hw,
 	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)
+	if (vif->type != NL80211_IFTYPE_NAN || !data->nan.device_vif ||
+	    data->nan.device_vif != vif)
 		return -EINVAL;
 
-	hrtimer_cancel(&data->nan_timer);
-	data->nan_device_vif = NULL;
+	hrtimer_cancel(&data->nan.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) {
+		if (data2->nan.device_vif) {
 			nan_cluster_running = true;
 			break;
 		}
@@ -4271,19 +4275,19 @@ static int mac80211_hwsim_change_nan_config(struct ieee80211_hw *hw,
 	if (vif->type != NL80211_IFTYPE_NAN)
 		return -EINVAL;
 
-	if (!data->nan_device_vif)
+	if (!data->nan.device_vif)
 		return -EINVAL;
 
 	wiphy_debug(hw->wiphy, "nan_config_changed: changes=0x%x\n", changes);
 
 	/* Handle only the changes we care about for simulation purposes */
 	if (changes & CFG80211_NAN_CONF_CHANGED_BANDS) {
-		data->nan_bands = conf->bands;
-		data->nan_curr_dw_band = NL80211_BAND_2GHZ;
+		data->nan.bands = conf->bands;
+		data->nan.curr_dw_band = NL80211_BAND_2GHZ;
 	}
 
 	if (changes & CFG80211_NAN_CONF_CHANGED_CONFIG)
-		data->notify_dw = conf->enable_dw_notification;
+		data->nan.notify_dw = conf->enable_dw_notification;
 
 	return 0;
 }
@@ -5716,7 +5720,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 			NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
 			NAN_DEV_CAPA_NDPE_SUPPORTED;
 
-		hrtimer_setup(&data->nan_timer, mac80211_hwsim_nan_dw_start,
+		hrtimer_setup(&data->nan.timer, mac80211_hwsim_nan_dw_start,
 			      CLOCK_MONOTONIC, HRTIMER_MODE_ABS_SOFT);
 	}
 
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 11/15] wifi: mac80211_hwsim: rename and switch simulation time to boottime
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

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

The mac80211_hwsim base time for the simulation of the TSF was based on
the real time of the system. This clock is subject to unexpected
changes. Switch it to use boottime which is always monotonic and also
continues to run through times where the system is suspended.

Also change the function name from tsf_raw to sim_tsf to better
differentiate between the TSF of the mac and the TSF base of the
simulation.

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    | 33 ++++++++++---------
 1 file changed, 18 insertions(+), 15 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 4ad2c6f38663..3bda5532ab62 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1211,22 +1211,25 @@ static netdev_tx_t hwsim_mon_xmit(struct sk_buff *skb,
 	return NETDEV_TX_OK;
 }
 
-static inline u64 mac80211_hwsim_get_tsf_raw(void)
+static inline u64 mac80211_hwsim_get_sim_tsf(void)
 {
-	return ktime_to_us(ktime_get_real());
-}
-
-static __le64 __mac80211_hwsim_get_tsf(struct mac80211_hwsim_data *data)
-{
-	u64 now = mac80211_hwsim_get_tsf_raw();
-	return cpu_to_le64(now + data->tsf_offset);
+	return ktime_to_us(ktime_get_boottime());
 }
 
 u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
 			   struct ieee80211_vif *vif)
 {
 	struct mac80211_hwsim_data *data = hw->priv;
-	return le64_to_cpu(__mac80211_hwsim_get_tsf(data));
+	u64 sim_time = mac80211_hwsim_get_sim_tsf();
+
+	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);
 }
 
 static void mac80211_hwsim_set_tsf(struct ieee80211_hw *hw,
@@ -1778,7 +1781,7 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw,
 	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
 	struct ieee80211_rx_status rx_status;
-	u64 now;
+	u64 sim_tsf;
 
 	memset(&rx_status, 0, sizeof(rx_status));
 	rx_status.flag |= RX_FLAG_MACTIME_START;
@@ -1831,9 +1834,9 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw,
 	if (ieee80211_is_beacon(hdr->frame_control) ||
 	    ieee80211_is_probe_resp(hdr->frame_control)) {
 		rx_status.boottime_ns = ktime_get_boottime_ns();
-		now = data->abs_bcn_ts;
+		sim_tsf = data->abs_bcn_ts;
 	} else {
-		now = mac80211_hwsim_get_tsf_raw();
+		sim_tsf = mac80211_hwsim_get_sim_tsf();
 	}
 
 	/* Copy skb to all enabled radios that are on the current frequency */
@@ -1894,7 +1897,7 @@ 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 = now + data2->tsf_offset;
+		rx_status.mactime = sim_tsf + data2->tsf_offset;
 
 		mac80211_hwsim_rx(data2, &rx_status, nskb);
 	}
@@ -2147,7 +2150,7 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
 		txrate = ieee80211_get_tx_rate(hw, txi);
 		if (txrate)
 			bitrate = txrate->bitrate;
-		ts = mac80211_hwsim_get_tsf_raw();
+		ts = mac80211_hwsim_get_sim_tsf();
 		mgmt->u.probe_resp.timestamp =
 			cpu_to_le64(ts + data->tsf_offset +
 				    24 * 8 * 10 / bitrate);
@@ -2330,7 +2333,7 @@ static void __mac80211_hwsim_beacon_tx(struct ieee80211_bss_conf *link_conf,
 
 	mgmt = (struct ieee80211_mgmt *) skb->data;
 	/* fake header transmission time */
-	data->abs_bcn_ts = mac80211_hwsim_get_tsf_raw();
+	data->abs_bcn_ts = mac80211_hwsim_get_sim_tsf();
 	if (ieee80211_is_s1g_beacon(mgmt->frame_control)) {
 		struct ieee80211_ext *ext = (void *) mgmt;
 
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 12/15] wifi: mac80211_hwsim: move timestamp writing later in the datapath
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

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

By delegating writing the timestamp into beacons and probe responses, we
can remove the abs_bcn_ts from the global data and still avoid any time
offset issues. This also seems conceptually closer to "real" hardware
where the timestamp will be written late in the TX path.

Move sending the SKB to the monitor interface to happen later, so that
the frame timestamp has the value filled in by mac80211_hwsim.

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   |   2 -
 .../wireless/virtual/mac80211_hwsim_main.c    | 110 ++++++++----------
 2 files changed, 49 insertions(+), 63 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index 741eb08f8a85..b4d0a3869619 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -104,8 +104,6 @@ struct mac80211_hwsim_data {
 	/* difference between this hw's clock and the real clock, in usecs */
 	s64 tsf_offset;
 	s64 bcn_delta;
-	/* absolute beacon transmission time. Used to cover up "tx" delay. */
-	u64 abs_bcn_ts;
 
 	/* Stats */
 	u64 tx_pkts;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 3bda5532ab62..5bf6541498ec 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1523,6 +1523,43 @@ static inline u16 trans_tx_rate_flags_ieee2hwsim(struct ieee80211_tx_rate *rate)
 	return result;
 }
 
+static void mac80211_hwsim_write_tsf(struct mac80211_hwsim_data *data,
+				     struct sk_buff *skb, u64 sim_time)
+{
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+	struct ieee80211_rate *txrate;
+	/* TODO: get MCS */
+	int bitrate = 100;
+
+	txrate = ieee80211_get_tx_rate(data->hw, info);
+	if (txrate)
+		bitrate = txrate->bitrate;
+
+	if (skb->len >= offsetofend(typeof(*mgmt), u.probe_resp.timestamp) &&
+	    ieee80211_is_probe_resp(hdr->frame_control)) {
+		mgmt->u.probe_resp.timestamp =
+			cpu_to_le64(sim_time + data->tsf_offset +
+				    24 * 8 * 10 / bitrate);
+	} else if (skb->len >= offsetofend(typeof(*mgmt), u.beacon.timestamp) &&
+		   ieee80211_is_beacon(mgmt->frame_control)) {
+		mgmt->u.beacon.timestamp = cpu_to_le64(sim_time +
+						       data->tsf_offset +
+						       24 * 8 * 10 /
+						       bitrate);
+	} else if (skb->len >= offsetofend(struct ieee80211_ext,
+					   u.s1g_beacon.timestamp) &&
+		   ieee80211_is_s1g_beacon(mgmt->frame_control)) {
+		struct ieee80211_ext *ext = (void *)mgmt;
+
+		ext->u.s1g_beacon.timestamp = cpu_to_le32(sim_time +
+							  data->tsf_offset +
+							  10 * 8 * 10 /
+							  bitrate);
+	}
+}
+
 static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw,
 				       struct sk_buff *my_skb,
 				       int dst_portid,
@@ -1538,6 +1575,7 @@ static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw,
 	struct hwsim_tx_rate tx_attempts[IEEE80211_TX_MAX_RATES];
 	struct hwsim_tx_rate_flag tx_attempts_flags[IEEE80211_TX_MAX_RATES];
 	uintptr_t cookie;
+	u64 sim_tsf;
 
 	if (data->ps != PS_DISABLED)
 		hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM);
@@ -1550,6 +1588,9 @@ static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw,
 		}
 	}
 
+	sim_tsf = mac80211_hwsim_get_sim_tsf();
+	mac80211_hwsim_write_tsf(data, my_skb, sim_tsf);
+
 	skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_ATOMIC);
 	if (skb == NULL)
 		goto nla_put_failure;
@@ -1781,7 +1822,11 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw,
 	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
 	struct ieee80211_rx_status rx_status;
-	u64 sim_tsf;
+	u64 sim_tsf = mac80211_hwsim_get_sim_tsf();
+
+	mac80211_hwsim_write_tsf(data, skb, sim_tsf);
+
+	mac80211_hwsim_monitor_rx(hw, skb, chan);
 
 	memset(&rx_status, 0, sizeof(rx_status));
 	rx_status.flag |= RX_FLAG_MACTIME_START;
@@ -1824,20 +1869,9 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw,
 	skb_ext_reset(skb);
 	nf_reset_ct(skb);
 
-	/*
-	 * Get absolute mactime here so all HWs RX at the "same time", and
-	 * absolute TX time for beacon mactime so the timestamp matches.
-	 * Giving beacons a different mactime than non-beacons looks messy, but
-	 * it helps the Toffset be exact and a ~10us mactime discrepancy
-	 * probably doesn't really matter.
-	 */
 	if (ieee80211_is_beacon(hdr->frame_control) ||
-	    ieee80211_is_probe_resp(hdr->frame_control)) {
+	    ieee80211_is_probe_resp(hdr->frame_control))
 		rx_status.boottime_ns = ktime_get_boottime_ns();
-		sim_tsf = data->abs_bcn_ts;
-	} else {
-		sim_tsf = mac80211_hwsim_get_sim_tsf();
-	}
 
 	/* Copy skb to all enabled radios that are on the current frequency */
 	spin_lock(&hwsim_radio_lock);
@@ -2137,27 +2171,6 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
 			return;
 	}
 
-	if (skb->len >= 24 + 8 &&
-	    ieee80211_is_probe_resp(hdr->frame_control)) {
-		/* fake header transmission time */
-		struct ieee80211_mgmt *mgmt;
-		struct ieee80211_rate *txrate;
-		/* TODO: get MCS */
-		int bitrate = 100;
-		u64 ts;
-
-		mgmt = (struct ieee80211_mgmt *)skb->data;
-		txrate = ieee80211_get_tx_rate(hw, txi);
-		if (txrate)
-			bitrate = txrate->bitrate;
-		ts = mac80211_hwsim_get_sim_tsf();
-		mgmt->u.probe_resp.timestamp =
-			cpu_to_le64(ts + data->tsf_offset +
-				    24 * 8 * 10 / bitrate);
-	}
-
-	mac80211_hwsim_monitor_rx(hw, skb, channel);
-
 	/* wmediumd mode check */
 	_portid = READ_ONCE(data->wmediumd);
 
@@ -2291,8 +2304,6 @@ static void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
 				       ARRAY_SIZE(txi->control.rates));
 	}
 
-	mac80211_hwsim_monitor_rx(hw, skb, chan);
-
 	if (_portid || hwsim_virtio_enabled)
 		return mac80211_hwsim_tx_frame_nl(hw, skb, _portid, chan);
 
@@ -2310,10 +2321,6 @@ static void __mac80211_hwsim_beacon_tx(struct ieee80211_bss_conf *link_conf,
 {
 	struct hwsim_vif_priv *vp = (void *)vif->drv_priv;
 	struct ieee80211_tx_info *info;
-	struct ieee80211_rate *txrate;
-	struct ieee80211_mgmt *mgmt;
-	/* TODO: get MCS */
-	int bitrate = 100;
 
 	if (vp->skip_beacons[link_conf->link_id]) {
 		vp->skip_beacons[link_conf->link_id]--;
@@ -2327,27 +2334,6 @@ static void __mac80211_hwsim_beacon_tx(struct ieee80211_bss_conf *link_conf,
 				       info->control.rates,
 				       ARRAY_SIZE(info->control.rates));
 
-	txrate = ieee80211_get_tx_rate(hw, info);
-	if (txrate)
-		bitrate = txrate->bitrate;
-
-	mgmt = (struct ieee80211_mgmt *) skb->data;
-	/* fake header transmission time */
-	data->abs_bcn_ts = mac80211_hwsim_get_sim_tsf();
-	if (ieee80211_is_s1g_beacon(mgmt->frame_control)) {
-		struct ieee80211_ext *ext = (void *) mgmt;
-
-		ext->u.s1g_beacon.timestamp = cpu_to_le32(data->abs_bcn_ts +
-							  data->tsf_offset +
-							  10 * 8 * 10 /
-							  bitrate);
-	} else {
-		mgmt->u.beacon.timestamp = cpu_to_le64(data->abs_bcn_ts +
-						       data->tsf_offset +
-						       24 * 8 * 10 /
-						       bitrate);
-	}
-
 	mac80211_hwsim_tx_frame(hw, skb,
 			rcu_dereference(link_conf->chanctx_conf)->def.chan);
 }
@@ -6033,6 +6019,8 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2,
 	if (!found)
 		goto out;
 
+	mac80211_hwsim_monitor_rx(data2->hw, skb, data2->channel);
+
 	/* Tx info received because the frame was broadcasted on user space,
 	 so we get all the necessary info: tx attempts and skb control buff */
 
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 10/15] wifi: mac80211_hwsim: split NAN handling into separate file
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

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

Having everything in one file for mac80211_hwsim is starting to get a
lot and it will be even worse if we implement more parts of NAN. Split
the NAN implementation into separate files to improve the code
structuring.

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 MAINTAINERS                                   |   2 +-
 drivers/net/wireless/virtual/Makefile         |   2 +
 .../net/wireless/virtual/mac80211_hwsim_i.h   | 137 ++++++++
 ...mac80211_hwsim.c => mac80211_hwsim_main.c} | 307 ++----------------
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 171 ++++++++++
 .../net/wireless/virtual/mac80211_hwsim_nan.h |  34 ++
 6 files changed, 365 insertions(+), 288 deletions(-)
 create mode 100644 drivers/net/wireless/virtual/mac80211_hwsim_i.h
 rename drivers/net/wireless/virtual/{mac80211_hwsim.c => mac80211_hwsim_main.c} (96%)
 create mode 100644 drivers/net/wireless/virtual/mac80211_hwsim_nan.c
 create mode 100644 drivers/net/wireless/virtual/mac80211_hwsim_nan.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..ac5c0314c870 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15344,7 +15344,7 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless.git
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git
 F:	Documentation/networking/mac80211-injection.rst
 F:	Documentation/networking/mac80211_hwsim/mac80211_hwsim.rst
-F:	drivers/net/wireless/virtual/mac80211_hwsim.[ch]
+F:	drivers/net/wireless/virtual/mac80211_hwsim*.[ch]
 F:	include/net/mac80211.h
 F:	net/mac80211/
 
diff --git a/drivers/net/wireless/virtual/Makefile b/drivers/net/wireless/virtual/Makefile
index 5773cc6d643e..6ad860dd7643 100644
--- a/drivers/net/wireless/virtual/Makefile
+++ b/drivers/net/wireless/virtual/Makefile
@@ -1,3 +1,5 @@
 obj-$(CONFIG_MAC80211_HWSIM)	+= mac80211_hwsim.o
+mac80211_hwsim-objs		+= mac80211_hwsim_main.o
+mac80211_hwsim-objs		+= mac80211_hwsim_nan.o
 
 obj-$(CONFIG_VIRT_WIFI)	+= virt_wifi.o
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
new file mode 100644
index 000000000000..741eb08f8a85
--- /dev/null
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * mac80211_hwsim - software simulator of 802.11 radio(s) for mac80211
+ * 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
+ */
+
+#ifndef __MAC80211_HWSIM_I_H
+#define __MAC80211_HWSIM_I_H
+
+#include <net/mac80211.h>
+#include "mac80211_hwsim.h"
+#include "mac80211_hwsim_nan.h"
+
+struct mac80211_hwsim_link_data {
+	u32 link_id;
+	u64 beacon_int	/* beacon interval in us */;
+	struct hrtimer beacon_timer;
+};
+
+#define HWSIM_NUM_CHANNELS_2GHZ		14
+#define HWSIM_NUM_CHANNELS_5GHZ		40
+#define HWSIM_NUM_CHANNELS_6GHZ		59
+#define HWSIM_NUM_S1G_CHANNELS_US	51
+#define HWSIM_NUM_RATES			12
+#define HWSIM_NUM_CIPHERS		11
+
+struct mac80211_hwsim_data {
+	struct list_head list;
+	struct rhash_head rht;
+	struct ieee80211_hw *hw;
+	struct device *dev;
+	struct ieee80211_supported_band bands[NUM_NL80211_BANDS];
+	struct ieee80211_channel channels_2ghz[HWSIM_NUM_CHANNELS_2GHZ];
+	struct ieee80211_channel channels_5ghz[HWSIM_NUM_CHANNELS_5GHZ];
+	struct ieee80211_channel channels_6ghz[HWSIM_NUM_CHANNELS_6GHZ];
+	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];
+	int n_if_limits;
+	/* Storage space for channels, etc. */
+	struct mac80211_hwsim_phy_data *phy_data;
+
+	struct ieee80211_iface_combination if_combination_radio;
+	struct wiphy_radio_freq_range radio_range[NUM_NL80211_BANDS];
+	struct wiphy_radio radio[NUM_NL80211_BANDS];
+
+	u32 ciphers[HWSIM_NUM_CIPHERS];
+
+	struct mac_address addresses[3];
+	int channels, idx;
+	bool use_chanctx;
+	bool destroy_on_close;
+	u32 portid;
+	char alpha2[2];
+	const struct ieee80211_regdomain *regd;
+
+	struct ieee80211_channel *tmp_chan;
+	struct ieee80211_channel *roc_chan;
+	u32 roc_duration;
+	struct delayed_work roc_start;
+	struct delayed_work roc_done;
+	struct delayed_work hw_scan;
+	struct cfg80211_scan_request *hw_scan_request;
+	struct ieee80211_vif *hw_scan_vif;
+	int scan_chan_idx;
+	u8 scan_addr[ETH_ALEN];
+	struct {
+		struct ieee80211_channel *channel;
+		unsigned long next_start, start, end;
+	} survey_data[HWSIM_NUM_CHANNELS_2GHZ +
+		      HWSIM_NUM_CHANNELS_5GHZ +
+		      HWSIM_NUM_CHANNELS_6GHZ];
+
+	struct ieee80211_channel *channel;
+	enum nl80211_chan_width bw;
+	unsigned int rx_filter;
+	bool started, idle, scanning;
+	struct mutex mutex;
+	enum ps_mode {
+		PS_DISABLED, PS_ENABLED, PS_AUTO_POLL, PS_MANUAL_POLL
+	} ps;
+	bool ps_poll_pending;
+	struct dentry *debugfs;
+	struct cfg80211_chan_def radar_background_chandef;
+
+	atomic_t pending_cookie;
+	struct sk_buff_head pending;	/* packets pending */
+	/*
+	 * Only radios in the same group can communicate together (the
+	 * channel has to match too). Each bit represents a group. A
+	 * radio can be in more than one group.
+	 */
+	u64 group;
+
+	/* group shared by radios created in the same netns */
+	int netgroup;
+	/* wmediumd portid responsible for netgroup of this radio */
+	u32 wmediumd;
+
+	/* difference between this hw's clock and the real clock, in usecs */
+	s64 tsf_offset;
+	s64 bcn_delta;
+	/* absolute beacon transmission time. Used to cover up "tx" delay. */
+	u64 abs_bcn_ts;
+
+	/* Stats */
+	u64 tx_pkts;
+	u64 rx_pkts;
+	u64 tx_bytes;
+	u64 rx_bytes;
+	u64 tx_dropped;
+	u64 tx_failed;
+
+	/* RSSI in rx status of the receiver */
+	int rx_rssi;
+
+	/* only used when pmsr capability is supplied */
+	struct cfg80211_pmsr_capabilities pmsr_capa;
+	struct cfg80211_pmsr_request *pmsr_request;
+	struct wireless_dev *pmsr_request_wdev;
+
+	struct mac80211_hwsim_link_data link_data[IEEE80211_MLD_MAX_NUM_LINKS];
+
+	struct mac80211_hwsim_nan_data nan;
+};
+
+extern spinlock_t hwsim_radio_lock;
+extern struct list_head hwsim_radios;
+
+u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
+			   struct ieee80211_vif *vif);
+
+#endif /* __MAC80211_HWSIM_I_H */
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
similarity index 96%
rename from drivers/net/wireless/virtual/mac80211_hwsim.c
rename to drivers/net/wireless/virtual/mac80211_hwsim_main.c
index d5b9170f690c..4ad2c6f38663 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -39,6 +39,7 @@
 #include <linux/uaccess.h>
 #include <linux/string.h>
 #include "mac80211_hwsim.h"
+#include "mac80211_hwsim_i.h"
 
 #define WARN_QUEUE 100
 #define MAX_QUEUE 200
@@ -379,6 +380,8 @@ static const struct ieee80211_channel hwsim_channels_2ghz[] = {
 	CHAN2G(2472), /* Channel 13 */
 	CHAN2G(2484), /* Channel 14 */
 };
+static_assert(HWSIM_NUM_CHANNELS_2GHZ == ARRAY_SIZE(hwsim_channels_2ghz),
+	      "Inconsistent 2 GHz channel count");
 
 static const struct ieee80211_channel hwsim_channels_5ghz[] = {
 	CHAN5G(5180), /* Channel 36 */
@@ -428,6 +431,8 @@ static const struct ieee80211_channel hwsim_channels_5ghz[] = {
 	CHAN5G(5920), /* Channel 184 */
 	CHAN5G(5925), /* Channel 185 */
 };
+static_assert(HWSIM_NUM_CHANNELS_5GHZ == ARRAY_SIZE(hwsim_channels_5ghz),
+	      "Inconsistent 5 GHz channel count");
 
 static const struct ieee80211_channel hwsim_channels_6ghz[] = {
 	CHAN6G(5955), /* Channel 1 */
@@ -490,9 +495,10 @@ static const struct ieee80211_channel hwsim_channels_6ghz[] = {
 	CHAN6G(7095), /* Channel 229 */
 	CHAN6G(7115), /* Channel 233 */
 };
+static_assert(HWSIM_NUM_CHANNELS_6GHZ == ARRAY_SIZE(hwsim_channels_6ghz),
+	      "Inconsistent 6 GHz channel count");
 
-#define NUM_S1G_CHANS_US 51
-static struct ieee80211_channel hwsim_channels_s1g[NUM_S1G_CHANS_US];
+static struct ieee80211_channel hwsim_channels_s1g[HWSIM_NUM_S1G_CHANNELS_US];
 
 static const struct ieee80211_sta_s1g_cap hwsim_s1g_cap = {
 	.s1g = true,
@@ -525,7 +531,7 @@ static void hwsim_init_s1g_channels(struct ieee80211_channel *chans)
 {
 	int ch, freq;
 
-	for (ch = 0; ch < NUM_S1G_CHANS_US; ch++) {
+	for (ch = 0; ch < ARRAY_SIZE(hwsim_channels_s1g); ch++) {
 		freq = 902000 + (ch + 1) * 500;
 		chans[ch].band = NL80211_BAND_S1GHZ;
 		chans[ch].center_freq = KHZ_TO_MHZ(freq);
@@ -548,6 +554,8 @@ static const struct ieee80211_rate hwsim_rates[] = {
 	{ .bitrate = 480 },
 	{ .bitrate = 540 }
 };
+static_assert(HWSIM_NUM_RATES == ARRAY_SIZE(hwsim_rates),
+	      "Inconsistent rates count");
 
 #define DEFAULT_RX_RSSI -50
 
@@ -564,6 +572,8 @@ static const u32 hwsim_ciphers[] = {
 	WLAN_CIPHER_SUITE_BIP_GMAC_128,
 	WLAN_CIPHER_SUITE_BIP_GMAC_256,
 };
+static_assert(HWSIM_NUM_CIPHERS == ARRAY_SIZE(hwsim_ciphers),
+	      "Inconsistent cipher count");
 
 #define OUI_QCA 0x001374
 #define QCA_NL80211_SUBCMD_TEST 1
@@ -644,12 +654,11 @@ static const struct nl80211_vendor_cmd_info mac80211_hwsim_vendor_events[] = {
 	{ .vendor_id = OUI_QCA, .subcmd = 1 },
 };
 
-static DEFINE_SPINLOCK(hwsim_radio_lock);
-static LIST_HEAD(hwsim_radios);
+DEFINE_SPINLOCK(hwsim_radio_lock);
+LIST_HEAD(hwsim_radios);
 static struct rhashtable hwsim_radios_rht;
 static int hwsim_radio_idx;
 static int hwsim_radios_generation = 1;
-static u8 hwsim_nan_cluster_id[ETH_ALEN];
 
 static struct platform_driver mac80211_hwsim_driver = {
 	.driver = {
@@ -657,120 +666,6 @@ static struct platform_driver mac80211_hwsim_driver = {
 	},
 };
 
-struct mac80211_hwsim_link_data {
-	u32 link_id;
-	u64 beacon_int	/* beacon interval in us */;
-	struct hrtimer beacon_timer;
-};
-
-struct mac80211_hwsim_nan_data {
-	struct ieee80211_vif *device_vif;
-	u8 bands;
-
-	enum nl80211_band curr_dw_band;
-	struct hrtimer timer;
-	bool notify_dw;
-};
-
-struct mac80211_hwsim_data {
-	struct list_head list;
-	struct rhash_head rht;
-	struct ieee80211_hw *hw;
-	struct device *dev;
-	struct ieee80211_supported_band bands[NUM_NL80211_BANDS];
-	struct ieee80211_channel channels_2ghz[ARRAY_SIZE(hwsim_channels_2ghz)];
-	struct ieee80211_channel channels_5ghz[ARRAY_SIZE(hwsim_channels_5ghz)];
-	struct ieee80211_channel channels_6ghz[ARRAY_SIZE(hwsim_channels_6ghz)];
-	struct ieee80211_channel channels_s1g[ARRAY_SIZE(hwsim_channels_s1g)];
-	struct ieee80211_rate rates[ARRAY_SIZE(hwsim_rates)];
-	struct ieee80211_iface_combination if_combination;
-	struct ieee80211_iface_limit if_limits[4];
-	int n_if_limits;
-
-	struct ieee80211_iface_combination if_combination_radio;
-	struct wiphy_radio_freq_range radio_range[NUM_NL80211_BANDS];
-	struct wiphy_radio radio[NUM_NL80211_BANDS];
-
-	u32 ciphers[ARRAY_SIZE(hwsim_ciphers)];
-
-	struct mac_address addresses[3];
-	int channels, idx;
-	bool use_chanctx;
-	bool destroy_on_close;
-	u32 portid;
-	char alpha2[2];
-	const struct ieee80211_regdomain *regd;
-
-	struct ieee80211_channel *tmp_chan;
-	struct ieee80211_channel *roc_chan;
-	u32 roc_duration;
-	struct delayed_work roc_start;
-	struct delayed_work roc_done;
-	struct delayed_work hw_scan;
-	struct cfg80211_scan_request *hw_scan_request;
-	struct ieee80211_vif *hw_scan_vif;
-	int scan_chan_idx;
-	u8 scan_addr[ETH_ALEN];
-	struct {
-		struct ieee80211_channel *channel;
-		unsigned long next_start, start, end;
-	} survey_data[ARRAY_SIZE(hwsim_channels_2ghz) +
-		      ARRAY_SIZE(hwsim_channels_5ghz) +
-		      ARRAY_SIZE(hwsim_channels_6ghz)];
-
-	struct ieee80211_channel *channel;
-	enum nl80211_chan_width bw;
-	unsigned int rx_filter;
-	bool started, idle, scanning;
-	struct mutex mutex;
-	enum ps_mode {
-		PS_DISABLED, PS_ENABLED, PS_AUTO_POLL, PS_MANUAL_POLL
-	} ps;
-	bool ps_poll_pending;
-	struct dentry *debugfs;
-	struct cfg80211_chan_def radar_background_chandef;
-
-	atomic_t pending_cookie;
-	struct sk_buff_head pending;	/* packets pending */
-	/*
-	 * Only radios in the same group can communicate together (the
-	 * channel has to match too). Each bit represents a group. A
-	 * radio can be in more than one group.
-	 */
-	u64 group;
-
-	/* group shared by radios created in the same netns */
-	int netgroup;
-	/* wmediumd portid responsible for netgroup of this radio */
-	u32 wmediumd;
-
-	/* difference between this hw's clock and the real clock, in usecs */
-	s64 tsf_offset;
-	s64 bcn_delta;
-	/* absolute beacon transmission time. Used to cover up "tx" delay. */
-	u64 abs_bcn_ts;
-
-	/* Stats */
-	u64 tx_pkts;
-	u64 rx_pkts;
-	u64 tx_bytes;
-	u64 rx_bytes;
-	u64 tx_dropped;
-	u64 tx_failed;
-
-	/* RSSI in rx status of the receiver */
-	int rx_rssi;
-
-	/* only used when pmsr capability is supplied */
-	struct cfg80211_pmsr_capabilities pmsr_capa;
-	struct cfg80211_pmsr_request *pmsr_request;
-	struct wireless_dev *pmsr_request_wdev;
-
-	struct mac80211_hwsim_link_data link_data[IEEE80211_MLD_MAX_NUM_LINKS];
-
-	struct mac80211_hwsim_nan_data nan;
-};
-
 static const struct rhashtable_params hwsim_rht_params = {
 	.nelem_hint = 2,
 	.automatic_shrinking = true,
@@ -1327,8 +1222,8 @@ static __le64 __mac80211_hwsim_get_tsf(struct mac80211_hwsim_data *data)
 	return cpu_to_le64(now + data->tsf_offset);
 }
 
-static u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
-				  struct ieee80211_vif *vif)
+u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
+			   struct ieee80211_vif *vif)
 {
 	struct mac80211_hwsim_data *data = hw->priv;
 	return le64_to_cpu(__mac80211_hwsim_get_tsf(data));
@@ -4130,168 +4025,6 @@ static int hwsim_pmsr_report_nl(struct sk_buff *msg, struct genl_info *info)
 	return err;
 }
 
-static enum hrtimer_restart
-mac80211_hwsim_nan_dw_start(struct hrtimer *timer)
-{
-	struct mac80211_hwsim_data *data =
-		container_of(timer, struct mac80211_hwsim_data,
-			     nan.timer);
-	struct ieee80211_hw *hw = data->hw;
-	u64 orig_tsf = mac80211_hwsim_get_tsf(hw, NULL), tsf = orig_tsf;
-	u32 dw_int = 512 * 1024;
-	u64 until_dw;
-
-	if (!data->nan.device_vif)
-		return HRTIMER_NORESTART;
-
-	if (data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
-		if (data->nan.curr_dw_band == NL80211_BAND_2GHZ) {
-			dw_int = 128 * 1024;
-			data->nan.curr_dw_band = NL80211_BAND_5GHZ;
-		} else if (data->nan.curr_dw_band == NL80211_BAND_5GHZ) {
-			data->nan.curr_dw_band = NL80211_BAND_2GHZ;
-		}
-	}
-
-	until_dw = dw_int - do_div(tsf, dw_int);
-
-	/* The timer might fire just before the actual DW, in which case
-	 * update the timeout to the actual next DW
-	 */
-	if (until_dw < dw_int / 2)
-		until_dw += dw_int;
-
-	/* The above do_div() call directly modifies the 'tsf' variable, thus,
-	 * use a copy so that the print below would show the original TSF.
-	 */
-	wiphy_debug(hw->wiphy,
-		    "%s: tsf=%llx, curr_dw_band=%u, next_dw=%llu\n",
-		    __func__, orig_tsf, data->nan.curr_dw_band,
-		    until_dw);
-
-	hrtimer_forward_now(&data->nan.timer,
-			    ns_to_ktime(until_dw * NSEC_PER_USEC));
-
-	if (data->nan.notify_dw) {
-		struct ieee80211_channel *ch;
-		struct wireless_dev *wdev =
-			ieee80211_vif_to_wdev(data->nan.device_vif);
-
-		if (data->nan.curr_dw_band == NL80211_BAND_5GHZ)
-			ch = ieee80211_get_channel(hw->wiphy, 5745);
-		else
-			ch = ieee80211_get_channel(hw->wiphy, 2437);
-
-		cfg80211_next_nan_dw_notif(wdev, ch, GFP_ATOMIC);
-	}
-
-	return HRTIMER_RESTART;
-}
-
-static int mac80211_hwsim_start_nan(struct ieee80211_hw *hw,
-				    struct ieee80211_vif *vif,
-				    struct cfg80211_nan_conf *conf)
-{
-	struct mac80211_hwsim_data *data = hw->priv;
-	u64 tsf = mac80211_hwsim_get_tsf(hw, NULL);
-	u32 dw_int = 512 * 1000;
-	u64 until_dw = dw_int - do_div(tsf, dw_int);
-	struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif);
-
-	if (vif->type != NL80211_IFTYPE_NAN)
-		return -EINVAL;
-
-	if (data->nan.device_vif)
-		return -EALREADY;
-
-	/* set this before starting the timer, as preemption might occur */
-	data->nan.device_vif = vif;
-	data->nan.bands = conf->bands;
-	data->nan.curr_dw_band = NL80211_BAND_2GHZ;
-
-	wiphy_debug(hw->wiphy, "nan_started, next_dw=%llu\n",
-		    until_dw);
-
-	hrtimer_start(&data->nan.timer,
-		      ns_to_ktime(until_dw * 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();
-	}
-
-	data->nan.notify_dw = conf->enable_dw_notification;
-
-	cfg80211_nan_cluster_joined(wdev, hwsim_nan_cluster_id, true,
-				    GFP_KERNEL);
-
-	return 0;
-}
-
-static int mac80211_hwsim_stop_nan(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)
-		return -EINVAL;
-
-	hrtimer_cancel(&data->nan.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;
-}
-
-static int mac80211_hwsim_change_nan_config(struct ieee80211_hw *hw,
-					    struct ieee80211_vif *vif,
-					    struct cfg80211_nan_conf *conf,
-					    u32 changes)
-{
-	struct mac80211_hwsim_data *data = hw->priv;
-
-	if (vif->type != NL80211_IFTYPE_NAN)
-		return -EINVAL;
-
-	if (!data->nan.device_vif)
-		return -EINVAL;
-
-	wiphy_debug(hw->wiphy, "nan_config_changed: changes=0x%x\n", changes);
-
-	/* Handle only the changes we care about for simulation purposes */
-	if (changes & CFG80211_NAN_CONF_CHANGED_BANDS) {
-		data->nan.bands = conf->bands;
-		data->nan.curr_dw_band = NL80211_BAND_2GHZ;
-	}
-
-	if (changes & CFG80211_NAN_CONF_CHANGED_CONFIG)
-		data->nan.notify_dw = conf->enable_dw_notification;
-
-	return 0;
-}
-
 static int mac80211_hwsim_set_radar_background(struct ieee80211_hw *hw,
 					       struct cfg80211_chan_def *chan)
 {
@@ -4342,11 +4075,11 @@ static int mac80211_hwsim_set_radar_background(struct ieee80211_hw *hw,
 	.get_et_strings = mac80211_hwsim_get_et_strings,	\
 	.start_pmsr = mac80211_hwsim_start_pmsr,		\
 	.abort_pmsr = mac80211_hwsim_abort_pmsr,		\
-	.start_nan = mac80211_hwsim_start_nan,                  \
-	.stop_nan = mac80211_hwsim_stop_nan,                    \
-	.nan_change_conf = mac80211_hwsim_change_nan_config,    \
 	.set_radar_background = mac80211_hwsim_set_radar_background, \
 	.set_key = mac80211_hwsim_set_key,			\
+	.start_nan = mac80211_hwsim_nan_start,			\
+	.stop_nan = mac80211_hwsim_nan_stop,			\
+	.nan_change_conf = mac80211_hwsim_nan_change_config,	\
 	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
new file mode 100644
index 000000000000..393ea70ee087
--- /dev/null
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * mac80211_hwsim_nan - NAN software simulation for mac80211_hwsim
+ * Copyright (C) 2025 Intel Corporation
+ */
+
+#include "mac80211_hwsim_i.h"
+
+static u8 hwsim_nan_cluster_id[ETH_ALEN];
+
+enum hrtimer_restart
+mac80211_hwsim_nan_dw_start(struct hrtimer *timer)
+{
+	struct mac80211_hwsim_data *data =
+		container_of(timer, struct mac80211_hwsim_data,
+			     nan.timer);
+	struct ieee80211_hw *hw = data->hw;
+	u64 orig_tsf = mac80211_hwsim_get_tsf(hw, NULL), tsf = orig_tsf;
+	u32 dw_int = 512 * 1024;
+	u64 until_dw;
+
+	if (!data->nan.device_vif)
+		return HRTIMER_NORESTART;
+
+	if (data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
+		if (data->nan.curr_dw_band == NL80211_BAND_2GHZ) {
+			dw_int = 128 * 1024;
+			data->nan.curr_dw_band = NL80211_BAND_5GHZ;
+		} else if (data->nan.curr_dw_band == NL80211_BAND_5GHZ) {
+			data->nan.curr_dw_band = NL80211_BAND_2GHZ;
+		}
+	}
+
+	until_dw = dw_int - do_div(tsf, dw_int);
+
+	/* The timer might fire just before the actual DW, in which case
+	 * update the timeout to the actual next DW
+	 */
+	if (until_dw < dw_int / 2)
+		until_dw += dw_int;
+
+	/* The above do_div() call directly modifies the 'tsf' variable, thus,
+	 * use a copy so that the print below would show the original TSF.
+	 */
+	wiphy_debug(hw->wiphy,
+		    "%s: tsf=%llx, curr_dw_band=%u, next_dw=%llu\n",
+		    __func__, orig_tsf, data->nan.curr_dw_band,
+		    until_dw);
+
+	hrtimer_forward_now(&data->nan.timer,
+			    ns_to_ktime(until_dw * NSEC_PER_USEC));
+
+	if (data->nan.notify_dw) {
+		struct ieee80211_channel *ch;
+		struct wireless_dev *wdev =
+			ieee80211_vif_to_wdev(data->nan.device_vif);
+
+		if (data->nan.curr_dw_band == NL80211_BAND_5GHZ)
+			ch = ieee80211_get_channel(hw->wiphy, 5745);
+		else
+			ch = ieee80211_get_channel(hw->wiphy, 2437);
+
+		cfg80211_next_nan_dw_notif(wdev, ch, GFP_ATOMIC);
+	}
+
+	return HRTIMER_RESTART;
+}
+
+int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
+			     struct ieee80211_vif *vif,
+			     struct cfg80211_nan_conf *conf)
+{
+	struct mac80211_hwsim_data *data = hw->priv;
+	u64 tsf = mac80211_hwsim_get_tsf(hw, NULL);
+	u32 dw_int = 512 * 1000;
+	u64 until_dw = dw_int - do_div(tsf, dw_int);
+	struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif);
+
+	if (vif->type != NL80211_IFTYPE_NAN)
+		return -EINVAL;
+
+	if (data->nan.device_vif)
+		return -EALREADY;
+
+	/* set this before starting the timer, as preemption might occur */
+	data->nan.device_vif = vif;
+	data->nan.bands = conf->bands;
+	data->nan.curr_dw_band = NL80211_BAND_2GHZ;
+
+	wiphy_debug(hw->wiphy, "nan_started, next_dw=%llu\n",
+		    until_dw);
+
+	hrtimer_start(&data->nan.timer,
+		      ns_to_ktime(until_dw * 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();
+	}
+
+	data->nan.notify_dw = conf->enable_dw_notification;
+
+	cfg80211_nan_cluster_joined(wdev, hwsim_nan_cluster_id, true,
+				    GFP_KERNEL);
+
+	return 0;
+}
+
+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)
+		return -EINVAL;
+
+	hrtimer_cancel(&data->nan.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;
+}
+
+int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
+				     struct ieee80211_vif *vif,
+				     struct cfg80211_nan_conf *conf,
+				     u32 changes)
+{
+	struct mac80211_hwsim_data *data = hw->priv;
+
+	if (vif->type != NL80211_IFTYPE_NAN)
+		return -EINVAL;
+
+	if (!data->nan.device_vif)
+		return -EINVAL;
+
+	wiphy_debug(hw->wiphy, "nan_config_changed: changes=0x%x\n", changes);
+
+	/* Handle only the changes we care about for simulation purposes */
+	if (changes & CFG80211_NAN_CONF_CHANGED_BANDS) {
+		data->nan.bands = conf->bands;
+		data->nan.curr_dw_band = NL80211_BAND_2GHZ;
+	}
+
+	if (changes & CFG80211_NAN_CONF_CHANGED_CONFIG)
+		data->nan.notify_dw = conf->enable_dw_notification;
+
+	return 0;
+}
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
new file mode 100644
index 000000000000..eac64ac37589
--- /dev/null
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * mac80211_hwsim_nan - NAN software simulation for mac80211_hwsim
+ * Copyright (C) 2025 Intel Corporation
+ */
+
+#ifndef __MAC80211_HWSIM_NAN_H
+#define __MAC80211_HWSIM_NAN_H
+
+struct mac80211_hwsim_nan_data {
+	struct ieee80211_vif *device_vif;
+	u8 bands;
+
+	enum nl80211_band curr_dw_band;
+	struct hrtimer timer;
+	bool notify_dw;
+};
+
+enum hrtimer_restart
+mac80211_hwsim_nan_dw_start(struct hrtimer *timer);
+
+int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
+			     struct ieee80211_vif *vif,
+			     struct cfg80211_nan_conf *conf);
+
+int mac80211_hwsim_nan_stop(struct ieee80211_hw *hw,
+			    struct ieee80211_vif *vif);
+
+int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
+				     struct ieee80211_vif *vif,
+				     struct cfg80211_nan_conf *conf,
+				     u32 changes);
+
+#endif /* __MAC80211_HWSIM_NAN_H */
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 14/15] wifi: mac80211_hwsim: refactor NAN timer handling
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

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

Refactor the NAN timer to more closely track the where in the NAN
schedule the device currently is. Do this by having an hrtimer that
fires at the start of every slot.

For now continue to update the current channel at the start of the DW.
In the future, the correct channel according to the schedule should be
used everywhere.

This is in preparation to more accurately simulate more of the NAN
logic.

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   |   5 +
 .../wireless/virtual/mac80211_hwsim_main.c    |  22 +--
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 160 ++++++++++++------
 .../net/wireless/virtual/mac80211_hwsim_nan.h |   8 +-
 4 files changed, 133 insertions(+), 62 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index d345595ca588..6b2a5dccb106 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -128,6 +128,11 @@ struct mac80211_hwsim_data {
 extern spinlock_t hwsim_radio_lock;
 extern struct list_head hwsim_radios;
 
+ktime_t mac80211_hwsim_tsf_to_boottime(struct mac80211_hwsim_data *data,
+				       u64 tsf);
+u64 mac80211_hwsim_boottime_to_tsf(struct mac80211_hwsim_data *data,
+				   ktime_t ts);
+
 u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
 			   struct ieee80211_vif *vif);
 
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index ba2aa09b37cb..c6d1f841c04d 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1216,12 +1216,18 @@ static inline u64 mac80211_hwsim_get_sim_tsf(void)
 	return ktime_to_us(ktime_get_boottime());
 }
 
-static ktime_t mac80211_hwsim_tsf_to_boottime(struct mac80211_hwsim_data *data,
-					      u64 tsf)
+ktime_t mac80211_hwsim_tsf_to_boottime(struct mac80211_hwsim_data *data,
+				       u64 tsf)
 {
 	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);
+}
+
 u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
 			   struct ieee80211_vif *vif)
 {
@@ -2045,12 +2051,7 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
 		 * on channel 6 or channel 149, unless a ROC is in progress (for
 		 * USD use cases).
 		 */
-		if (data->nan.curr_dw_band == NL80211_BAND_2GHZ)
-			channel = ieee80211_get_channel(hw->wiphy, 2437);
-		else if (data->nan.curr_dw_band == NL80211_BAND_5GHZ)
-			channel = ieee80211_get_channel(hw->wiphy, 5745);
-		else
-			channel = NULL;
+		channel = data->nan.channel;
 
 		if (WARN_ON(!channel)) {
 			ieee80211_free_txskb(hw, skb);
@@ -5451,8 +5452,9 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 			NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
 			NAN_DEV_CAPA_NDPE_SUPPORTED;
 
-		hrtimer_setup(&data->nan.timer, mac80211_hwsim_nan_dw_start,
-			      CLOCK_MONOTONIC, HRTIMER_MODE_ABS_SOFT);
+		hrtimer_setup(&data->nan.slot_timer,
+			      mac80211_hwsim_nan_slot_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 393ea70ee087..aa4aef0920f4 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -6,63 +6,132 @@
 
 #include "mac80211_hwsim_i.h"
 
+/* Defined as the lower 23 bits being zero */
+#define DW0_TSF_MASK		GENMASK(22, 0)
+
+/* DWs are repeated every 512 TUs */
+#define DWST_TU			512
+#define DWST_TSF_MASK		(ieee80211_tu_to_usec(DWST_TU) - 1)
+
+#define SLOT_TU			16
+#define SLOT_TSF_MASK		(ieee80211_tu_to_usec(DWST_TU) - 1)
+
+/* The 2.4 GHz DW is at the start, the 5 GHz is in slot 8 (after 128 TUs) */
+#define DW_5G_OFFSET_TU		128
+
+#define SLOT_24GHZ_DW		0
+#define SLOT_5GHZ_DW		(DW_5G_OFFSET_TU / SLOT_TU)
+
+/* The special DW0 happens every 16 DWSTs (8192 TUs) */
+static_assert(16 * DWST_TU * 1024 == 8192 * 1024);
+static_assert(DW0_TSF_MASK + 1 == 8192 * 1024);
+
 static u8 hwsim_nan_cluster_id[ETH_ALEN];
 
+static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
+{
+	ktime_t expires = hrtimer_get_expires(&data->nan.slot_timer);
+
+	return mac80211_hwsim_boottime_to_tsf(data, expires);
+}
+
+static u8 hwsim_nan_slot_from_tsf(u64 tsf)
+{
+	return (tsf & DWST_TSF_MASK) / ieee80211_tu_to_usec(SLOT_TU);
+}
+
+static void
+mac80211_hwsim_nan_schedule_slot(struct mac80211_hwsim_data *data, u8 slot)
+{
+	u64 tsf = hwsim_nan_get_timer_tsf(data);
+
+	/* Only called by mac80211_hwsim_nan_dw_timer from softirq context */
+	lockdep_assert_in_softirq();
+
+	tsf &= ~DWST_TSF_MASK;
+	tsf += ieee80211_tu_to_usec(slot * SLOT_TU);
+
+	hrtimer_set_expires(&data->nan.slot_timer,
+			    mac80211_hwsim_tsf_to_boottime(data, tsf));
+}
+
+static void
+mac80211_hwsim_nan_exec_state_transitions(struct mac80211_hwsim_data *data)
+{
+	/*
+	 * 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
+	 */
+}
+
 enum hrtimer_restart
-mac80211_hwsim_nan_dw_start(struct hrtimer *timer)
+mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
 {
 	struct mac80211_hwsim_data *data =
 		container_of(timer, struct mac80211_hwsim_data,
-			     nan.timer);
+			     nan.slot_timer);
 	struct ieee80211_hw *hw = data->hw;
-	u64 orig_tsf = mac80211_hwsim_get_tsf(hw, NULL), tsf = orig_tsf;
-	u32 dw_int = 512 * 1024;
-	u64 until_dw;
+	struct ieee80211_channel *notify_dw_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;
 
 	if (!data->nan.device_vif)
 		return HRTIMER_NORESTART;
 
-	if (data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
-		if (data->nan.curr_dw_band == NL80211_BAND_2GHZ) {
-			dw_int = 128 * 1024;
-			data->nan.curr_dw_band = NL80211_BAND_5GHZ;
-		} else if (data->nan.curr_dw_band == NL80211_BAND_5GHZ) {
-			data->nan.curr_dw_band = NL80211_BAND_2GHZ;
-		}
-	}
+	if ((tsf & DW0_TSF_MASK & ~DWST_TSF_MASK) == 0)
+		dwst_of_dw0 = true;
 
-	until_dw = dw_int - do_div(tsf, dw_int);
 
-	/* The timer might fire just before the actual DW, in which case
-	 * update the timeout to the actual next DW
-	 */
-	if (until_dw < dw_int / 2)
-		until_dw += dw_int;
+	switch (slot) {
+	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;
 
-	/* The above do_div() call directly modifies the 'tsf' variable, thus,
-	 * use a copy so that the print below would show the original TSF.
-	 */
-	wiphy_debug(hw->wiphy,
-		    "%s: tsf=%llx, curr_dw_band=%u, next_dw=%llu\n",
-		    __func__, orig_tsf, data->nan.curr_dw_band,
-		    until_dw);
+	case SLOT_24GHZ_DW + 1:
+		if (!(data->nan.bands & BIT(NL80211_BAND_5GHZ))) {
+			notify_dw_chan = ieee80211_get_channel(hw->wiphy, 2437);
+			dw_end = true;
+		} else {
+			notify_dw_chan = ieee80211_get_channel(hw->wiphy, 5745);
+		}
+		break;
 
-	hrtimer_forward_now(&data->nan.timer,
-			    ns_to_ktime(until_dw * NSEC_PER_USEC));
+	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;
+
+	case SLOT_5GHZ_DW + 1:
+		if (data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
+			notify_dw_chan =
+				ieee80211_get_channel(hw->wiphy, 2437);
+			dw_end = true;
+		}
+		break;
+	}
+
+	if (dw_end)
+		mac80211_hwsim_nan_exec_state_transitions(data);
 
-	if (data->nan.notify_dw) {
-		struct ieee80211_channel *ch;
+	if (data->nan.notify_dw && notify_dw_chan) {
 		struct wireless_dev *wdev =
 			ieee80211_vif_to_wdev(data->nan.device_vif);
 
-		if (data->nan.curr_dw_band == NL80211_BAND_5GHZ)
-			ch = ieee80211_get_channel(hw->wiphy, 5745);
-		else
-			ch = ieee80211_get_channel(hw->wiphy, 2437);
-
-		cfg80211_next_nan_dw_notif(wdev, ch, GFP_ATOMIC);
+		cfg80211_next_nan_dw_notif(wdev, notify_dw_chan, GFP_ATOMIC);
 	}
 
+	mac80211_hwsim_nan_schedule_slot(data, slot + 1);
+
 	return HRTIMER_RESTART;
 }
 
@@ -71,9 +140,6 @@ int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
 			     struct cfg80211_nan_conf *conf)
 {
 	struct mac80211_hwsim_data *data = hw->priv;
-	u64 tsf = mac80211_hwsim_get_tsf(hw, NULL);
-	u32 dw_int = 512 * 1000;
-	u64 until_dw = dw_int - do_div(tsf, dw_int);
 	struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif);
 
 	if (vif->type != NL80211_IFTYPE_NAN)
@@ -85,13 +151,11 @@ 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.curr_dw_band = NL80211_BAND_2GHZ;
+	data->nan.channel = ieee80211_get_channel(hw->wiphy, 2437);
 
-	wiphy_debug(hw->wiphy, "nan_started, next_dw=%llu\n",
-		    until_dw);
-
-	hrtimer_start(&data->nan.timer,
-		      ns_to_ktime(until_dw * NSEC_PER_USEC),
+	/* 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) &&
@@ -125,7 +189,7 @@ int mac80211_hwsim_nan_stop(struct ieee80211_hw *hw,
 	    data->nan.device_vif != vif)
 		return -EINVAL;
 
-	hrtimer_cancel(&data->nan.timer);
+	hrtimer_cancel(&data->nan.slot_timer);
 	data->nan.device_vif = NULL;
 
 	spin_lock_bh(&hwsim_radio_lock);
@@ -159,10 +223,8 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
 	wiphy_debug(hw->wiphy, "nan_config_changed: changes=0x%x\n", changes);
 
 	/* Handle only the changes we care about for simulation purposes */
-	if (changes & CFG80211_NAN_CONF_CHANGED_BANDS) {
+	if (changes & CFG80211_NAN_CONF_CHANGED_BANDS)
 		data->nan.bands = conf->bands;
-		data->nan.curr_dw_band = NL80211_BAND_2GHZ;
-	}
 
 	if (changes & CFG80211_NAN_CONF_CHANGED_CONFIG)
 		data->nan.notify_dw = conf->enable_dw_notification;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index eac64ac37589..e86e7f9e9a3c 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -11,13 +11,15 @@ struct mac80211_hwsim_nan_data {
 	struct ieee80211_vif *device_vif;
 	u8 bands;
 
-	enum nl80211_band curr_dw_band;
-	struct hrtimer timer;
+	/* Current channel of the NAN device */
+	struct ieee80211_channel *channel;
+
+	struct hrtimer slot_timer;
 	bool notify_dw;
 };
 
 enum hrtimer_restart
-mac80211_hwsim_nan_dw_start(struct hrtimer *timer);
+mac80211_hwsim_nan_slot_timer(struct hrtimer *timer);
 
 int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
 			     struct ieee80211_vif *vif,
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 13/15] wifi: mac80211_hwsim: register beacon timer by calculating TBTT
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

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

It is easy to calculate the next target beacon transmission time (TBTT)
based on the current TSF and the beacon interval. Use this method to
calculate the time to the next beacon.

With this, the bcn_delta variable can be removed and drift over time due
to the timer firing late is fully avoided.

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    | 39 ++++++++++++-------
 2 files changed, 24 insertions(+), 16 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index b4d0a3869619..d345595ca588 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -103,7 +103,6 @@ struct mac80211_hwsim_data {
 
 	/* difference between this hw's clock and the real clock, in usecs */
 	s64 tsf_offset;
-	s64 bcn_delta;
 
 	/* Stats */
 	u64 tx_pkts;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 5bf6541498ec..ba2aa09b37cb 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1216,6 +1216,12 @@ static inline u64 mac80211_hwsim_get_sim_tsf(void)
 	return ktime_to_us(ktime_get_boottime());
 }
 
+static ktime_t mac80211_hwsim_tsf_to_boottime(struct mac80211_hwsim_data *data,
+					      u64 tsf)
+{
+	return us_to_ktime(tsf - data->tsf_offset);
+}
+
 u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
 			   struct ieee80211_vif *vif)
 {
@@ -1237,8 +1243,6 @@ static void mac80211_hwsim_set_tsf(struct ieee80211_hw *hw,
 {
 	struct mac80211_hwsim_data *data = hw->priv;
 	u64 now = mac80211_hwsim_get_tsf(hw, vif);
-	/* MLD not supported here */
-	u32 bcn_int = data->link_data[0].beacon_int;
 	u64 delta = abs(tsf - now);
 	struct ieee80211_bss_conf *conf;
 
@@ -1247,13 +1251,10 @@ static void mac80211_hwsim_set_tsf(struct ieee80211_hw *hw,
 		return;
 
 	/* adjust after beaconing with new timestamp at old TBTT */
-	if (tsf > now) {
+	if (tsf > now)
 		data->tsf_offset += delta;
-		data->bcn_delta = do_div(delta, bcn_int);
-	} else {
+	else
 		data->tsf_offset -= delta;
-		data->bcn_delta = -(s64)do_div(delta, bcn_int);
-	}
 }
 
 static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw,
@@ -2410,7 +2411,9 @@ mac80211_hwsim_beacon(struct hrtimer *timer)
 		container_of(link_data, struct mac80211_hwsim_data,
 			     link_data[link_data->link_id]);
 	struct ieee80211_hw *hw = data->hw;
-	u64 bcn_int = link_data->beacon_int;
+	u32 remainder;
+	u64 tsf_now;
+	u64 tbtt;
 
 	if (!data->started)
 		return HRTIMER_NORESTART;
@@ -2419,13 +2422,19 @@ mac80211_hwsim_beacon(struct hrtimer *timer)
 		hw, IEEE80211_IFACE_ITER_NORMAL,
 		mac80211_hwsim_beacon_tx, link_data);
 
-	/* beacon at new TBTT + beacon interval */
-	if (data->bcn_delta) {
-		bcn_int -= data->bcn_delta;
-		data->bcn_delta = 0;
-	}
-	hrtimer_forward_now(&link_data->beacon_timer,
-			    ns_to_ktime(bcn_int * NSEC_PER_USEC));
+	/* TSF is the same for all VIFs, parameter is unused */
+	tsf_now = mac80211_hwsim_get_tsf(hw, NULL);
+
+	/* Wrap value to be after the next TBTT */
+	tbtt = tsf_now + link_data->beacon_int;
+
+	/* Round TBTT down to the correct time */
+	div_u64_rem(tbtt, link_data->beacon_int, &remainder);
+	tbtt = tbtt - remainder;
+
+	hrtimer_set_expires(&link_data->beacon_timer,
+			    mac80211_hwsim_tsf_to_boottime(data, tbtt));
+
 	return HRTIMER_RESTART;
 }
 
-- 
2.34.1


^ permalink raw reply related

* [PATCH v4 wireless-next 15/15] wifi: mac80211_hwsim: switch to use TXQs
From: Miri Korenblit @ 2026-05-04  7:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260504072055.1292999-1-miriam.rachel.korenblit@intel.com>

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

Simply immediately TX all available packets on the corresponding queue.

This removes the HWSIM_TM_CMD_STOP_QUEUES/HWSIM_TM_CMD_RESUME_QUEUES
feature for now. It should be reasonably simple to add it back if it is
needed.

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    | 20 ++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index c6d1f841c04d..e510357c1411 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -2198,6 +2198,17 @@ 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)
+{
+	struct ieee80211_tx_control control = {
+		.sta = txq->sta,
+	};
+	struct sk_buff *skb;
+
+	while ((skb = ieee80211_tx_dequeue(hw, txq)))
+		mac80211_hwsim_tx(hw, &control, skb);
+}
 
 static int mac80211_hwsim_start(struct ieee80211_hw *hw)
 {
@@ -2943,11 +2954,7 @@ static int mac80211_hwsim_testmode_cmd(struct ieee80211_hw *hw,
 			goto nla_put_failure;
 		return cfg80211_testmode_reply(skb);
 	case HWSIM_TM_CMD_STOP_QUEUES:
-		ieee80211_stop_queues(hw);
-		return 0;
 	case HWSIM_TM_CMD_WAKE_QUEUES:
-		ieee80211_wake_queues(hw);
-		return 0;
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -4051,7 +4058,7 @@ static int mac80211_hwsim_set_radar_background(struct ieee80211_hw *hw,
 
 #define HWSIM_COMMON_OPS					\
 	.tx = mac80211_hwsim_tx,				\
-	.wake_tx_queue = ieee80211_handle_wake_tx_queue,	\
+	.wake_tx_queue = ieee80211_hwsim_wake_tx_queue,		\
 	.start = mac80211_hwsim_start,				\
 	.stop = mac80211_hwsim_stop,				\
 	.add_interface = mac80211_hwsim_add_interface,		\
@@ -5530,6 +5537,8 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 	ieee80211_hw_set(hw, TDLS_WIDER_BW);
 	ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID);
 	ieee80211_hw_set(hw, STRICT);
+	ieee80211_hw_set(hw, BUFF_MMPDU_TXQ);
+	ieee80211_hw_set(hw, STA_MMPDU_TXQ);
 
 	if (param->mlo) {
 		hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_MLO;
@@ -5588,6 +5597,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 	hw->vif_data_size = sizeof(struct hwsim_vif_priv);
 	hw->sta_data_size = sizeof(struct hwsim_sta_priv);
 	hw->chanctx_data_size = sizeof(struct hwsim_chanctx_priv);
+	hw->txq_data_size = 0;
 
 	memcpy(data->channels_2ghz, hwsim_channels_2ghz,
 		sizeof(hwsim_channels_2ghz));
-- 
2.34.1


^ permalink raw reply related

* Re: nl80211: SET_WIPHY_NETNS does not check caller's CAP_NET_ADMIN  over the target netns
From: Johannes Berg @ 2026-05-04  8:28 UTC (permalink / raw)
  To: Xie Maoyi
  Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org,
	netdev
In-Reply-To: <TYZPR01MB6758FE8FDBB58A6CAA4DC6BBDC302@TYZPR01MB6758.apcprd01.prod.exchangelabs.com>

Hi,

On Sun, 2026-05-03 at 06:55 +0000, Xie Maoyi wrote:
> Hi Johannes,
> 
> I think I have found two related namespace handling gaps in nl80211 on v7.0 mainline. I would appreciate your view on whether they are bugs and whether they are worth fixing. The second one is much narrower than the first.
> 
> Bug A: NL80211_CMD_SET_WIPHY_NETNS does not check the target netns.

I guess that's more a question of convention than anything else?

But I guess we should follow the netdev convention:

> By comparison, net/core/rtnetlink.c::rtnl_get_net_ns_capable() spells out the convention:
> 
>     /* For now, the caller is required to have CAP_NET_ADMIN in
>      * the user namespace owning the target net ns. */
>     if (!sk_ns_capable(sk, net->user_ns, CAP_NET_ADMIN))
>         return ERR_PTR(-EACCES);

which (also?) requires access in the target netns.

> Bug B: nl80211_prepare_wdev_dump() continuation does not re-check netns.
> 
> The first dumpit invocation validates the wdev against the caller via __cfg80211_wdev_from_attrs(..., sock_net(cb->skb->sk), ...). Subsequent invocations look up the wiphy by global index via wiphy_idx_to_wiphy(). They do not re-check sock_net(cb->skb->sk) against the wiphy's current netns.
> 
> Other dump paths in the same file do this check on every iteration. See nl80211_dump_wiphy() at line 3437 and the parallel scheduled scan dump at line 4420.
> 
> If a wiphy moves between dumpit invocations of NL80211_CMD_GET_SCAN via NL80211_CMD_SET_WIPHY_NETNS, the dump silently keeps copying BSS list contents from the wiphy's new netns into the caller's netns. On its own this race needs a separate caller to migrate the wiphy mid-dump. With bug A, the attacker can arrange the race themselves.

This seems ... inconsequential? After all, moving a wireless device
between namespaces doesn't really change the physical layout of the
machine. Perhaps that'd give someone access to the SSID of some hidden
network but that's not really a secret anyway since it's over the air.

Maybe we should fix it for clarity and convention, but I don't see it's
really an issue?

johannes

^ permalink raw reply

* [PATCH wireless-next] wifi: cfg80211: fix max_channel_switch_time documentation unit
From: Miri Korenblit @ 2026-05-04  9:17 UTC (permalink / raw)
  To: linux-wireless; +Cc: Israel Kozitz, Ilan Peer

From: Israel Kozitz <israel.kozitz@intel.com>

The max_channel_switch_time field in struct wiphy_nan_capa was documented
as being in milliseconds, but it is actually in microseconds as defined
in the Wi-Fi Aware specification and as indicated in the nl80211
NL80211_NAN_CAPA_MAX_CHANNEL_SWITCH_TIME attribute.

Fix the documentation to say microseconds.

Signed-off-by: Israel Kozitz <israel.kozitz@intel.com>
Reviewed-by: Ilan Peer <ilan.peer@intel.com>

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 include/net/cfg80211.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index a40ab36b8edb..51eded4204cf 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -6027,7 +6027,7 @@ enum wiphy_nan_flags {
  *     nibble indicates the number of TX antennas and upper nibble indicates the
  *     number of RX antennas. Value 0 indicates the information is not
  *     available.
- * @max_channel_switch_time: maximum channel switch time in milliseconds.
+ * @max_channel_switch_time: maximum channel switch time in microseconds.
  * @dev_capabilities: NAN device capabilities as defined in Wi-Fi Aware (TM)
  *     specification Table 79 (Capabilities field).
  * @phy: Band-agnostic capabilities for NAN data interfaces. Since NAN
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH v4 02/15] firmware: qcom: Add a generic PAS service
From: Harshal Dev @ 2026-05-04  9:20 UTC (permalink / raw)
  To: Sumit Garg
  Cc: linux-arm-msm, devicetree, dri-devel, freedreno, linux-media,
	netdev, linux-wireless, ath12k, linux-remoteproc, robh, krzk+dt,
	conor+dt, robin.clark, sean, akhilpo, lumag, abhinav.kumar,
	jesszhan0024, marijn.suijten, airlied, simona, vikash.garodia,
	dikshita.agarwal, bod, mchehab, elder, andrew+netdev, davem,
	edumazet, kuba, pabeni, jjohnson, mathieu.poirier,
	trilokkumar.soni, mukesh.ojha, pavan.kondeti, jorge.ramirez,
	tonyh, vignesh.viswanathan, srinivas.kandagatla, amirreza.zarrabi,
	op-tee, apurupa, skare, linux-kernel, Sumit Garg, Konrad Dybcio,
	Bjorn Andersson
In-Reply-To: <20260427095603.1157963-3-sumit.garg@kernel.org>



On 4/27/2026 3:25 PM, Sumit Garg via OP-TEE wrote:
> From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> 
> Qcom platforms has the legacy of using non-standard SCM calls
> splintered over the various kernel drivers. These SCM calls aren't
> compliant with the standard SMC calling conventions which is a
> prerequisite to enable migration to the FF-A specifications from Arm.
> 
> OP-TEE as an alternative trusted OS to Qualcomm TEE (QTEE) can't
> support these non-standard SCM calls. And even for newer architectures
> using S-EL2 with Hafnium support, QTEE won't be able to support SCM
> calls either with FF-A requirements coming in. And with both OP-TEE
> and QTEE drivers well integrated in the TEE subsystem, it makes further
> sense to reuse the TEE bus client drivers infrastructure.
> 
> The added benefit of TEE bus infrastructure is that there is support
> for discoverable/enumerable services. With that client drivers don't
> have to manually invoke a special SCM call to know the service status.
> 
> So enable the generic Peripheral Authentication Service (PAS) provided
> by the firmware. It acts as the common layer with different TZ
> backends plugged in whether it's an SCM implementation or a proper
> TEE bus based PAS service implementation.
> 
> Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> ---

Reviewed-by: Harshal Dev <harshal.dev@oss.qualcomm.com>

Regards,
Harshal


^ permalink raw reply

* Re: [PATCH v4 03/15] firmware: qcom_scm: Migrate to generic PAS service
From: Harshal Dev @ 2026-05-04  9:22 UTC (permalink / raw)
  To: Sumit Garg
  Cc: linux-arm-msm, devicetree, dri-devel, freedreno, linux-media,
	netdev, linux-wireless, ath12k, linux-remoteproc, robh, krzk+dt,
	conor+dt, robin.clark, sean, akhilpo, lumag, abhinav.kumar,
	jesszhan0024, marijn.suijten, airlied, simona, vikash.garodia,
	dikshita.agarwal, bod, mchehab, elder, andrew+netdev, davem,
	edumazet, kuba, pabeni, jjohnson, mathieu.poirier,
	trilokkumar.soni, mukesh.ojha, pavan.kondeti, jorge.ramirez,
	tonyh, vignesh.viswanathan, srinivas.kandagatla, amirreza.zarrabi,
	op-tee, apurupa, skare, linux-kernel, Sumit Garg, Konrad Dybcio,
	Bjorn Andersson
In-Reply-To: <20260427095603.1157963-4-sumit.garg@kernel.org>



On 4/27/2026 3:25 PM, Sumit Garg via OP-TEE wrote:
> From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> 
> With the availability of generic PAS service, let's add SCM calls as
> a backend to keep supporting legacy QTEE interfaces. The exported
> qcom_scm* wrappers will get dropped once all the client drivers get
> migrated as part of future patches.
> 
> Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>

Reviewed-by: Harshal Dev <harshal.dev@oss.qualcomm.com>

Regards,
Harshal


^ permalink raw reply

* [PATCH v2 0/3] wifi: iwlwifi: add Device Tree hardware integration information
From: Avinash Bhatt @ 2026-05-04  9:53 UTC (permalink / raw)
  To: devicetree, linux-wireless
  Cc: robh, krzk+dt, conor+dt, johannes, miriam.rachel.korenblit,
	linux-kernel, kobi.guetta, emmanuel.grumbach, avinash.bhatt

Add Device Tree support for Intel Wi-Fi hardware integration information
on platforms that do not provide UEFI variables or ACPI methods.

Patch 1/3 adds the DT binding schema for the Intel iwlwifi compatible
node. Patches 2/3 and 3/3 add the driver infrastructure and integrate DT
as the lowest-priority fallback after UEFI and ACPI.

Changes in v2:
- Drop mailing list from maintainers in YAML binding
- Drop binding-purpose sentence from description
- Fix dtschema 2026.4 validation errors (items/minItems enforcement)
- Fix yamllint line length violations
- Add driver companion patches (2/3 and 3/3) for reference, showing how
  the binding is consumed by the driver; these will be merged through the
  regular iwlwifi upstreaming process, not through the DT tree

Link to v1: https://patchwork.kernel.org/project/linux-wireless/patch/20260429081403.46087-2-avinash.bhatt@intel.com/
Link to RFC: https://patchwork.kernel.org/project/devicetree/patch/20260408055709.11579-2-avinash.bhatt@intel.com/

Avinash Bhatt (3):
  dt-bindings: net: wireless: intel,iwlwifi: add binding
  wifi: iwlwifi: dt: add Device Tree BIOS configuration infrastructure
  wifi: iwlwifi: dt: use Device Tree as fallback BIOS configuration source

 drivers/net/wireless/intel/iwlwifi/Makefile   |   1 +
 drivers/net/wireless/intel/iwlwifi/fw/acpi.h  |   5 +-
 .../wireless/intel/iwlwifi/fw/api/nvm-reg.h   |   4 +-
 drivers/net/wireless/intel/iwlwifi/fw/dt.c    | 321 ++++++++++++++++++
 drivers/net/wireless/intel/iwlwifi/fw/dt.h    | 123 +++++++
 .../wireless/intel/iwlwifi/fw/regulatory.c    |   5 +-
 .../wireless/intel/iwlwifi/fw/regulatory.h    |   3 +-
 7 files changed, 455 insertions(+), 7 deletions(-)
 create mode 100644 drivers/net/wireless/intel/iwlwifi/fw/dt.c
 create mode 100644 drivers/net/wireless/intel/iwlwifi/fw/dt.h

-- 
2.34.1


^ permalink raw reply

* [PATCH v2 1/3] dt-bindings: net: wireless: intel,iwlwifi: add binding
From: Avinash Bhatt @ 2026-05-04  9:53 UTC (permalink / raw)
  To: devicetree, linux-wireless
  Cc: robh, krzk+dt, conor+dt, johannes, miriam.rachel.korenblit,
	linux-kernel, kobi.guetta, emmanuel.grumbach, avinash.bhatt
In-Reply-To: <20260504095327.30892-1-avinash.bhatt@intel.com>

Add a devicetree schema binding for Intel discrete Wi-Fi 7 BE200 PCIe
adapters.

The binding documents OEM platform configuration properties for
platforms that use Device Tree instead of platform firmware
methods. All properties mirror the existing equivalents in
structure and semantics, covering SAR power limits (intel,wrds),
6 GHz AP type support (intel,uats), static power limit
(intel,splc), channel puncturing (intel,wcpe), 320 MHz per-MCC
enablement (intel,wbem), ETSI SRD channel configuration
(intel,srd), 6-7 GHz UHB country enable bitmask (intel,6e-uhb),
and additional regulatory override properties.


Signed-off-by: Avinash Bhatt <avinash.bhatt@intel.com>
---
 .../bindings/net/wireless/intel,iwlwifi.yaml  | 430 ++++++++++++++++++
 1 file changed, 430 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/wireless/intel,iwlwifi.yaml

diff --git a/Documentation/devicetree/bindings/net/wireless/intel,iwlwifi.yaml b/Documentation/devicetree/bindings/net/wireless/intel,iwlwifi.yaml
new file mode 100644
index 000000000000..210063c6183d
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/wireless/intel,iwlwifi.yaml
@@ -0,0 +1,430 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (c) 2026 Intel Corporation
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/wireless/intel,iwlwifi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Intel iwlwifi PCIe Wi-Fi devices
+
+maintainers:
+  - Avinash Bhatt <avinash.bhatt@intel.com>
+
+description:
+  Intel iwlwifi IEEE 802.11be discrete Wi-Fi adapters connected over PCIe.
+
+properties:
+  compatible:
+    enum:
+      - pci8086,272b
+
+  reg:
+    maxItems: 1
+
+  intel,wrds:
+    description: |
+      Wi-Fi Regulatory Domain Settings (WRDS). SAR (Specific Absorption Rate)
+      transmit power limits per antenna chain and frequency subband. Values
+      are 8-bit unsigned in units of 0.125 dBm.
+
+      Revision 3 layout: 4 chains x 12 subbands = 50 cells total.
+      Chain A and Chain B are the two physical antenna paths; CDB Chain A
+      and CDB Chain B carry separate limits for simultaneous dual-band
+      operation.
+
+      Header (2 cells):
+        [0] revision - structure revision, must be 0x03
+        [1] mode     - bit 0: 0 = SAR disabled, 1 = SAR enabled;
+                       bits [8:1]: set to 0
+
+      Followed by 4 chains in order: chain_a, chain_b, cdb_chain_a,
+      cdb_chain_b, each containing 12 subband values:
+
+      Subband index to frequency range mapping:
+        [0]  2.4 GHz  ch  1-13   (2412-2472 MHz)
+        [1]  5 GHz    ch 36-64   (5180-5320 MHz, UNII-1/2)
+        [2]  5 GHz    ch 68-96   (5340-5480 MHz, UNII-2)
+        [3]  5 GHz    ch 100-144 (5500-5720 MHz, UNII-2e)
+        [4]  5 GHz    ch 149-188 (5745-5940 MHz, UNII-3/4)
+        [5]  6 GHz    ch  1-45   (5955-6175 MHz, UNII-5 lower)
+        [6]  6 GHz    ch 49-93   (6195-6415 MHz, UNII-5 upper)
+        [7]  6 GHz    ch 97-115  (6435-6525 MHz, UNII-6)
+        [8]  6 GHz    ch 117-151 (6535-6705 MHz, UNII-7 lower)
+        [9]  6 GHz    ch 153-183 (6715-6865 MHz, UNII-7 upper)
+        [10] 6 GHz    ch 185-233 (6875-7115 MHz, UNII-8)
+        [11] 6 GHz    ch 237-253 (7135-7215 MHz, UNII-9)
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint32-array
+      - minItems: 50
+        maxItems: 50
+
+  intel,uats:
+    description: |
+      UHB (Ultra High Band / 6 GHz) AP Type Support (UATS). Per-country
+      enablement matrix for 6 GHz AP types. Uses byte array format
+      (DTS [ ... ] notation).
+
+      In the 6 GHz band, regulatory rules differ per country and per AP
+      type: AFC (Standard Power), LPI (Low Power Indoor), and VLP (Very
+      Low Power). This matrix encodes which AP types are permitted to
+      operate in each country.
+
+      Revision 1 layout (339 bytes total):
+        [0]   revision    - structure revision, must be 0x01
+        [1+]  country_map - 338-byte matrix encoding AP type allowances
+                            per country.
+
+                            Countries are identified by their ISO 3166-1
+                            alpha-2 code (two letters, A-Z each). The
+                            matrix covers all 26x26 = 676 possible
+                            two-letter combinations (AA..ZZ), most of
+                            which are unused (set to 0x0).
+
+                            Each country entry is 4 bits (a nibble). Two
+                            entries are packed per byte: the low nibble
+                            holds the even-indexed entry, the high nibble
+                            holds the odd-indexed entry. For example,
+                            byte value 0x53 means: entry[even]=0x3,
+                            entry[odd]=0x5.
+
+                            The matrix is stored column-major by first
+                            letter: all 26 second-letter variants for
+                            first letter 'A' occupy bytes [0..12], then
+                            first letter 'B' occupies bytes [13..25],
+                            and so on for all 26 first letters.
+                            26 columns x 13 bytes = 338 bytes total.
+
+                            Each 4-bit nibble encodes AP type allowances
+                            for one country:
+                              bit 0: AFC (Standard Power AP) allowed
+                              bit 1: VLP (Very Low Power AP) allowed
+                              bit 2: LPI (Low Power Indoor AP) allowed
+                              bit 3: reserved, must be 0
+
+                            Note: each bit is only effective when the
+                            corresponding control bit in intel,6e-uhb
+                            is also set (bit 30 for AFC, bit 29
+                            for VLP, bit 31 for LPI country-by-country
+                            mode).
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint8-array
+      - minItems: 339
+        maxItems: 339
+
+  intel,srd:
+    description: |
+      ETSI 5.8 GHz SRD (Short Range Device) channel configuration.
+      Controls how the driver handles the 5725-5875 MHz (5.8 GHz) SRD
+      channels in ETSI regulatory domains.
+
+      Layout (2 cells):
+        [0] revision - structure revision, must be 0x00
+        [1] value    - channel configuration:
+                       0 = active scan permitted (default behaviour)
+                       1 = passive scan only; device may associate and
+                           transfer data but must not transmit probe
+                           requests on SRD channels
+                       2 = SRD channels fully disabled; the device must
+                           not scan, associate, or operate on any of the
+                           5725-5875 MHz SRD channels
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint32-array
+      - items:
+          - const: 0
+          - enum: [0, 1, 2]
+
+  intel,6e-uhb:
+    description: |
+      6-7 GHz Ultra-High Band (UHB) per-country enable bitmask.
+
+      Layout (2 cells):
+        [0] revision - structure revision, must be 0x00
+        [1] bitmap   - UHB enablement control:
+                       bit 0:     override control; 0 = use device defaults,
+                                  1 = force-disable all countries not
+                                  explicitly enabled in bits 1-25
+                       bits 1-25: per-country/region enable flags:
+                                  bit  1 = USA
+                                  bit  2 = Rest of World (ROW)
+                                  bit  3 = EU
+                                  bit  4 = South Korea
+                                  bit  5 = Brazil
+                                  bit  6 = Chile
+                                  bit  7 = Japan
+                                  bit  8 = Canada
+                                  bit  9 = Morocco
+                                  bit 10 = Mongolia
+                                  bit 11 = Malaysia
+                                  bit 12 = Saudi Arabia
+                                  bit 13 = Mexico
+                                  bit 14 = Nigeria
+                                  bit 15 = Thailand
+                                  bit 16 = Singapore
+                                  bit 17 = Taiwan
+                                  bit 18 = South Africa
+                                  bit 19 = Philippines
+                                  bit 20 = Serbia
+                                  bit 21 = Indonesia
+                                  bit 22 = Azerbaijan
+                                  bit 23 = Paraguay
+                                  bit 24 = Vietnam
+                                  bit 25 = India
+                       bit 26:    reserved, must be 0
+                       bit 27:    enable VLP active scan, SoftAP, and
+                                  P2P-GO operation in Japan
+                       bit 28:    reserved, must be 0
+                       bit 29:    enable VLP (Very Low Power) mode per
+                                  country-by-country table
+                       bit 30:    enable AFC (Standard Power) mode per
+                                  country-by-country table
+                       bit 31:    LPI override mode; 0 = use grouping
+                                  mechanism, 1 = use country-by-country table
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint32-array
+      - items:
+          - const: 0
+          - {}
+
+  intel,regulatory-special:
+    description: |
+      Regulatory Special Configurations.
+
+      Layout (2 cells):
+        [0] revision - structure revision, must be 0x00
+        [1] bitmap   - configuration flags:
+                       bits 0-3: reserved, must be 0
+                       bit 4 = Australia UHB extension
+                       bits 5-31: reserved, must be 0
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint32-array
+      - items:
+          - const: 0
+          - {}
+
+  intel,activate-channel:
+    description: |
+      Indoor channel activation bitmask. Sets specific frequency bands to
+      active (rather than passive or disabled) when the platform is
+      confirmed to be operating indoors.
+
+      Layout (2 cells):
+        [0] revision - structure revision, must be 0x00
+        [1] bitmap   - per-region indoor activation flags:
+                       bit 0 = enable EU U-NII-1 (5.2 GHz) for indoors only
+                       bit 1 = enable Japan U-NII-1 (5.2 GHz) for indoors only
+                       bit 2 = enable China Mainland U-NII-1 (5.2 GHz)
+                               for indoors only
+                       bit 3 = enable USA U-NII-4 (5.9 GHz) for indoors only
+                       bit 4 = enable WW U-NII-1 (5.2 GHz) for indoors in any
+                               country where the band is permitted
+                       bit 5 = enable Canada U-NII-4 (5.9 GHz) for indoors only
+                       bit 6 = enable USA + Canada + WW U-NII-4 (5.9 GHz) for
+                               indoors only
+                       bits 7-31: reserved, must be 0
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint32-array
+      - items:
+          - const: 0
+          - {}
+
+  intel,force-disable-channels:
+    description: |
+      Selective Wi-Fi band force-disable bitmask. Allows the platform to
+      permanently disable specific frequency bands regardless of regulatory
+      domain.
+
+      Layout (2 cells):
+        [0] revision - structure revision, must be 0x00
+        [1] bitmap   - per-band force-disable flags:
+                       bit 0  = force disable 2.4 GHz (channels 1-13)
+                       bit 1  = force disable 5.2 GHz (channels 36-48)
+                       bit 2  = force disable 5.3 GHz (channels 52-64)
+                       bit 3  = force disable 5.5 GHz (channels 100-144)
+                       bit 4  = force disable 5.8 GHz (channels 149-165)
+                       bit 5  = force disable 5.9 GHz (channels 169-177)
+                       bit 6  = force disable 6.2 GHz (channels 1-93)
+                       bit 7  = force disable 6.5 GHz (channels 97-113)
+                       bit 8  = force disable 6.6 GHz (channels 117-153)
+                       bit 9  = force disable 6.8 GHz (channels 157-185)
+                       bit 10 = force disable 7.0 GHz (channels 185-233)
+                       bits 11-31: reserved, must be 0
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint32-array
+      - items:
+          - const: 0
+          - {}
+
+  intel,11be:
+    description: |
+      802.11be (Wi-Fi 7) per-country enable bitmask. Controls whether
+      802.11be operation is permitted in specific countries.
+
+      Layout (2 cells):
+        [0] revision - structure revision, must be 0x00
+        [1] bitmap   - per-country enable flags:
+                       bit 0 = enable 802.11be in China (CB/CN)
+                       bit 1 = enable 802.11be in South Korea
+                       bits 2-31: reserved, must be 0
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint32-array
+      - items:
+          - const: 0
+          - {}
+
+  intel,splc:
+    description: |
+      Wi-Fi Static Power Limit Capabilities (SPLC). Sets the platform thermal
+      power limit for the Wi-Fi core in mW. Omit this property entirely if
+      no platform power limit applies; the device will use its certified
+      maximum in that case.
+
+      Layout (2 cells):
+        [0] revision    - structure revision, must be 0x00
+        [1] power_limit - maximum platform power budget in mW, must be
+                          non-zero (a zero value is equivalent to omitting
+                          the property)
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint32-array
+      - items:
+          - const: 0
+          - minimum: 1
+
+  intel,wcpe:
+    description: |
+      Wi-Fi Channel Puncturing Enablement (WCPE). Enables 802.11be channel
+      puncturing for specific regulatory domains.
+
+      Layout (2 cells):
+        [0] revision   - structure revision, must be 0x00
+        [1] puncturing - per-country enable bitmask:
+                         bit 0: 1 = channel puncturing enabled for USA
+                         bit 1: 1 = channel puncturing enabled for Canada
+                         bits 2-31: reserved, must be 0
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint32-array
+      - items:
+          - const: 0
+          - {}
+
+  intel,wbem:
+    description: |
+      Wi-Fi 320 MHz Bandwidth Enablement per MCC (WBEM). Controls whether
+      320 MHz operation is permitted in specific countries.
+
+      Layout (2 cells):
+        [0] revision       - structure revision, must be 0x00
+        [1] wifi320mhz_mcc - per-country enable bitmask:
+                             bit 0: 1 = 320 MHz enabled for Japan
+                             bit 1: 1 = 320 MHz enabled for South Korea
+                             bits 2-31: reserved, must be 0
+
+                             Each bit takes effect only if the installed
+                             module is certified for 320 MHz in that country.
+    allOf:
+      - $ref: /schemas/types.yaml#/definitions/uint32-array
+      - items:
+          - const: 0
+          - {}
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    /* ARM64 platform with Intel Wi-Fi 7 BE200 as discrete PCIe device */
+    pcie {
+        #address-cells = <3>;
+        #size-cells = <2>;
+
+        pcie@0 {
+            device_type = "pci";
+            reg = <0x0 0x0 0x0 0x0 0x0>;
+            #address-cells = <3>;
+            #size-cells = <2>;
+            ranges;
+            bus-range = <0x01 0xff>;
+
+            wifi@0 {
+                compatible = "pci8086,272b";
+                reg = <0x10000 0x0 0x0 0x0 0x0>;
+
+                /*
+                 * Wi-Fi Regulatory Domain Settings (SAR power limits).
+                 * Revision 3: 4 chains x 12 subbands = 50 cells total.
+                 * Layout: revision, mode, then 4 x chain[12].
+                 */
+                intel,wrds = <
+                    0x03 0x01
+                    /* Chain A: 12 subbands */
+                    0x38 0x38 0x38 0x38 0x38 0x38 0x38 0x38 0x38 0x38 0x38 0x38
+                    /* Chain B: 12 subbands */
+                    0x38 0x38 0x38 0x38 0x38 0x38 0x38 0x38 0x38 0x38 0x38 0x38
+                    /* CDB Chain A: 12 subbands */
+                    0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c
+                    /* CDB Chain B: 12 subbands */
+                    0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c 0x3c
+                >;
+
+                /*
+                 * Static Power Limit: 4500 mW cap on this platform.
+                 * 2 u32 cells: revision, power_limit_mw.
+                 */
+                intel,splc = <0x00 0x1194>;
+
+                /*
+                 * Channel Puncturing: enabled for USA and Canada.
+                 * 2 u32 cells: revision, puncturing bitmask (bits 0+1).
+                 */
+                intel,wcpe = <0x00 0x03>;
+
+                /*
+                 * 320 MHz per MCC: Japan and South Korea enabled.
+                 * 2 u32 cells: revision, wifi320mhz_mcc.
+                 */
+                intel,wbem = <0x00 0x03>;
+
+                /* OEM regulatory configuration properties. */
+                intel,srd = <0x00 1>; /* revision=0, passive scan only */
+                intel,activate-channel = <0x00 0x01>; /* EU indoors */
+                intel,force-disable-channels = <0x00 0x00>; /* revision=0 */
+                intel,6e-uhb = <0x00 0x06>; /* revision=0, USA+ROW */
+
+                /*
+                 * UHB AP Type Support (6 GHz country matrix).
+                 * Byte array: revision (0x01), then
+                 * 338 bytes of the 26x13 country enable map (all zeros
+                 * in this example = no countries enabled).
+                 */
+                intel,uats = [01
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00
+                    00 00 00 00 00 00 00 00 00 00 00 00 00];
+            };
+        };
+    };
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 2/3] wifi: iwlwifi: dt: add Device Tree BIOS configuration infrastructure
From: Avinash Bhatt @ 2026-05-04  9:53 UTC (permalink / raw)
  To: devicetree, linux-wireless
  Cc: robh, krzk+dt, conor+dt, johannes, miriam.rachel.korenblit,
	linux-kernel, kobi.guetta, emmanuel.grumbach, avinash.bhatt
In-Reply-To: <20260504095327.30892-1-avinash.bhatt@intel.com>

On platforms that use Device Tree and do not provide UEFI variables or
ACPI methods, discrete Intel Wi-Fi adapters have no way to receive BIOS
configuration. Add Device Tree as a supported configuration source for
SAR limits and 6 GHz AP type support, with stub hooks for TAS and
per-platform antenna gain tables.

DT support is gated on CONFIG_OF and is a no-op on x86. BIOS_SOURCE_DT
is added to enum bios_source to track the configuration origin.

Signed-off-by: Avinash Bhatt <avinash.bhatt@intel.com>
---
 drivers/net/wireless/intel/iwlwifi/Makefile   |   1 +
 drivers/net/wireless/intel/iwlwifi/fw/acpi.h  |   5 +-
 .../wireless/intel/iwlwifi/fw/api/nvm-reg.h   |   4 +-
 drivers/net/wireless/intel/iwlwifi/fw/dt.c    | 321 ++++++++++++++++++
 drivers/net/wireless/intel/iwlwifi/fw/dt.h    | 123 +++++++
 .../wireless/intel/iwlwifi/fw/regulatory.h    |   3 +-
 6 files changed, 451 insertions(+), 6 deletions(-)
 create mode 100644 drivers/net/wireless/intel/iwlwifi/fw/dt.c
 create mode 100644 drivers/net/wireless/intel/iwlwifi/fw/dt.h

diff --git a/drivers/net/wireless/intel/iwlwifi/Makefile b/drivers/net/wireless/intel/iwlwifi/Makefile
index 941257b811b4..445c8a26b6bd 100644
--- a/drivers/net/wireless/intel/iwlwifi/Makefile
+++ b/drivers/net/wireless/intel/iwlwifi/Makefile
@@ -38,6 +38,7 @@ iwlwifi-$(CONFIG_IWLMVM) += fw/paging.o fw/smem.o fw/init.o
 iwlwifi-$(CONFIG_IWLMLD) += fw/smem.o fw/init.o
 iwlwifi-$(CONFIG_ACPI) += fw/acpi.o
 iwlwifi-$(CONFIG_EFI)	+= fw/uefi.o
+iwlwifi-$(CONFIG_OF)	+= fw/dt.o
 iwlwifi-$(CONFIG_IWLWIFI_DEBUGFS) += fw/debugfs.o
 
 iwlwifi-objs += $(iwlwifi-m)
diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.h b/drivers/net/wireless/intel/iwlwifi/fw/acpi.h
index 51a57e57de7a..45eb35ffb637 100644
--- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.h
+++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.h
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
 /*
  * Copyright (C) 2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2023, 2025 Intel Corporation
+ * Copyright (C) 2018-2023, 2025-2026 Intel Corporation
  */
 #ifndef __iwl_fw_acpi__
 #define __iwl_fw_acpi__
@@ -111,9 +111,6 @@
 #define ACPI_PPAG_WIFI_DATA_SIZE_V3	((ACPI_PPAG_NUM_CHAINS * \
 					  ACPI_PPAG_NUM_BANDS_V3) + 2)
 
-#define IWL_SAR_ENABLE_MSK		BIT(0)
-#define IWL_REDUCE_POWER_FLAGS_POS	1
-
 /* The Inidcator whether UEFI WIFI GUID tables are locked is read from ACPI */
 #define UEFI_WIFI_GUID_UNLOCKED		0
 
diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h
index 443a9a416325..39289bf3a193 100644
--- a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h
+++ b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
 /*
- * Copyright (C) 2012-2014, 2018-2025 Intel Corporation
+ * Copyright (C) 2012-2014, 2018-2026 Intel Corporation
  * Copyright (C) 2013-2015 Intel Mobile Communications GmbH
  * Copyright (C) 2016-2017 Intel Deutschland GmbH
  */
@@ -495,11 +495,13 @@ struct iwl_tas_config_cmd_v2_v4 {
  * @BIOS_SOURCE_NONE: BIOS source is not defined
  * @BIOS_SOURCE_ACPI: BIOS source is ACPI
  * @BIOS_SOURCE_UEFI: BIOS source is UEFI
+ * @BIOS_SOURCE_DT: BIOS source is Device Tree
  */
 enum bios_source {
 	BIOS_SOURCE_NONE,
 	BIOS_SOURCE_ACPI,
 	BIOS_SOURCE_UEFI,
+	BIOS_SOURCE_DT,
 };
 
 /**
diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dt.c b/drivers/net/wireless/intel/iwlwifi/fw/dt.c
new file mode 100644
index 000000000000..4f3d098204f0
--- /dev/null
+++ b/drivers/net/wireless/intel/iwlwifi/fw/dt.c
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2026 Intel Corporation
+ */
+#include <linux/of.h>
+#include "iwl-drv.h"
+#include "iwl-debug.h"
+#include "fw/runtime.h"
+#include "fw/regulatory.h"
+#include "dt.h"
+
+/* DT property names */
+#define IWL_DT_PROP_WRDS			"intel,wrds"
+#define IWL_DT_PROP_UATS			"intel,uats"
+#define IWL_DT_PROP_SPLC			"intel,splc"
+#define IWL_DT_PROP_WBEM			"intel,wbem"
+#define IWL_DT_PROP_WCPE			"intel,wcpe"
+#define IWL_DT_WCPE_USA_EN			BIT(0)
+#define IWL_DT_WCPE_CANADA_EN			BIT(1)
+#define IWL_DT_WCPE_MASK (IWL_DT_WCPE_USA_EN | \
+			  IWL_DT_WCPE_CANADA_EN)
+#define IWL_DT_PROP_SRD				"intel,srd"
+#define IWL_DT_PROP_6E_UHB			"intel,6e-uhb"
+#define IWL_DT_PROP_REG_SPECIAL			"intel,regulatory-special"
+#define IWL_DT_PROP_ACTIVATE_CH			"intel,activate-channel"
+#define IWL_DT_PROP_FORCE_DISABLE_CH		"intel,force-disable-channels"
+#define IWL_DT_PROP_11BE			"intel,11be"
+
+/* Supported revision values for each DT property */
+#define IWL_DT_WRDS_REVISION		3
+#define IWL_DT_UATS_REVISION		1
+
+/* SAR layout for "intel,wrds" revision 3 (mirrors ACPI_SAR_NUM_CHAINS_REV2
+ * and UEFI_SAR_SUB_BANDS_NUM_REV3): 4 chains x 12 subbands
+ */
+#define IWL_DT_WRDS_NUM_CHAINS		4
+#define IWL_DT_WRDS_NUM_SUBBANDS	12
+/* Total cell count for "intel,wrds" property: 2-cell header + SAR data */
+#define IWL_DT_WRDS_MAX_CELLS \
+	(2 + IWL_DT_WRDS_NUM_CHAINS * IWL_DT_WRDS_NUM_SUBBANDS)
+
+/**
+ * iwl_dt_get_wrds_table - read SAR power limits from DT "intel,wrds" property
+ * @fwrt: firmware runtime context
+ *
+ * Reads per-chain, per-subband SAR power limits from the "intel,wrds"
+ * Device Tree property and populates the SAR profile.
+ *
+ * Return: 0 on success, -ENOENT if the property or DT node is absent,
+ * or a negative error code for malformed data.
+ */
+int iwl_dt_get_wrds_table(struct iwl_fw_runtime *fwrt)
+{
+	struct device_node *node = dev_of_node(fwrt->dev);
+	u32 buf[IWL_DT_WRDS_MAX_CELLS];
+	u8 num_chains, num_subbands;
+	int ret;
+
+	if (!node)
+		return -ENOENT;
+
+	ret = of_property_read_u32_array(node, IWL_DT_PROP_WRDS, buf,
+					 IWL_DT_WRDS_MAX_CELLS);
+	if (ret) {
+		IWL_DEBUG_RADIO(fwrt, "WRDS DT: read failed (%d)\n", ret);
+		return ret;
+	}
+
+	if (buf[0] != IWL_DT_WRDS_REVISION) {
+		IWL_DEBUG_RADIO(fwrt,
+				"WRDS DT: revision %u not supported (only %u)\n",
+				buf[0], IWL_DT_WRDS_REVISION);
+		return -EINVAL;
+	}
+
+	num_chains   = IWL_DT_WRDS_NUM_CHAINS;
+	num_subbands = IWL_DT_WRDS_NUM_SUBBANDS;
+
+	IWL_DEBUG_RADIO(fwrt,
+			"Reading WRDS from Device Tree (revision %u, %u chains, %u subbands)\n",
+			buf[0], num_chains, num_subbands);
+
+	/* buf[1] bit 0 (IWL_SAR_ENABLE_MSK): enable SAR */
+
+	BUILD_BUG_ON(IWL_DT_WRDS_NUM_CHAINS > BIOS_SAR_MAX_CHAINS_PER_PROFILE);
+	BUILD_BUG_ON(IWL_DT_WRDS_NUM_SUBBANDS > BIOS_SAR_MAX_SUB_BANDS_NUM);
+
+	/* buf[2..]: chains[0..N-1] x subbands[0..M-1] in row-major order */
+	for (int i = 0; i < num_chains; i++) {
+		for (int j = 0; j < num_subbands; j++) {
+			u32 val = buf[2 + i * num_subbands + j];
+
+			if (val > U8_MAX) {
+				IWL_DEBUG_RADIO(fwrt,
+						"DT: WRDS OOB [%u][%u]=%u\n",
+						i, j, val);
+				return -EINVAL;
+			}
+			fwrt->sar_profiles[0].chains[i].subbands[j] = (u8)val;
+		}
+	}
+
+	if (buf[1] & IWL_SAR_ENABLE_MSK)
+		fwrt->sar_profiles[0].enabled = true;
+
+	return 0;
+}
+
+/**
+ * iwl_dt_get_uats_table - read UHB AP type support table from DT "intel,uats"
+ * @fwrt: firmware runtime context
+ *
+ * Reads the UHB AP type support table from the "intel,uats" Device Tree
+ * property and stores the MCC-to-AP-type map for use by the firmware command.
+ */
+void iwl_dt_get_uats_table(struct iwl_fw_runtime *fwrt)
+{
+	size_t map_size = sizeof(fwrt->ap_type_cmd.mcc_to_ap_type_map);
+	struct device_node *node = dev_of_node(fwrt->dev);
+	const u8 *prop_data;
+	int len;
+
+	if (!node)
+		return;
+
+	prop_data = of_get_property(node, IWL_DT_PROP_UATS, &len);
+	if (!prop_data) {
+		IWL_DEBUG_FW(fwrt, "UATS DT: property absent\n");
+		return;
+	}
+
+	if (len != (int)(1 + map_size)) {
+		IWL_DEBUG_FW(fwrt, "UATS DT: bad length %d (expected %zu)\n",
+			     len, 1 + map_size);
+		return;
+	}
+
+	if (prop_data[0] != IWL_DT_UATS_REVISION) {
+		IWL_DEBUG_FW(fwrt,
+			     "UATS DT: revision %u not supported (expected %u)\n",
+			     prop_data[0], IWL_DT_UATS_REVISION);
+		return;
+	}
+
+	IWL_DEBUG_FW(fwrt, "Reading UATS table from Device Tree\n");
+
+	/* prop_data[0]=revision, prop_data[1+]=26x13 MCC to AP type map */
+	memcpy(fwrt->ap_type_cmd.mcc_to_ap_type_map, prop_data + 1, map_size);
+	fwrt->ap_type_cmd_valid = true;
+}
+IWL_EXPORT_SYMBOL(iwl_dt_get_uats_table);
+
+/*
+ * Mapping from DSM function index to Device Tree property name.
+ * Returns the DT property name for a given DSM function, or NULL if the
+ * function has no Device Tree representation.
+ */
+static const char *dsm_func_to_prop_name(enum iwl_dsm_funcs func)
+{
+	switch (func) {
+	case DSM_FUNC_DISABLE_SRD:          return IWL_DT_PROP_SRD;
+	case DSM_FUNC_ENABLE_6E:            return IWL_DT_PROP_6E_UHB;
+	case DSM_FUNC_REGULATORY_CONFIG:    return IWL_DT_PROP_REG_SPECIAL;
+	case DSM_FUNC_ACTIVATE_CHANNEL:     return IWL_DT_PROP_ACTIVATE_CH;
+	case DSM_FUNC_FORCE_DISABLE_CHANNELS:
+		return IWL_DT_PROP_FORCE_DISABLE_CH;
+	case DSM_FUNC_ENABLE_11BE:          return IWL_DT_PROP_11BE;
+	default:                            return NULL;
+	}
+}
+
+/**
+ * iwl_dt_get_dsm - read one OEM DSM function value from Device Tree
+ * @fwrt: firmware runtime context
+ * @func: DSM function index (enum iwl_dsm_funcs)
+ * @value: output value
+ *
+ * Reads the DT property for @func on every call without caching.
+ * Unlike the ACPI/UEFI paths, the shared dsm_funcs_valid/dsm_values[]
+ * fields are not used here.
+ *
+ * Return: 0 on success, -ENOENT if no DT node, -EOPNOTSUPP if the function
+ * has no DT representation, -ENODATA if the property is absent,
+ * -EINVAL if the property revision is unsupported.
+ */
+int iwl_dt_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func,
+		   u32 *value)
+{
+	struct device_node *node = dev_of_node(fwrt->dev);
+	const char *prop;
+	u32 buf[2];
+
+	if (!node)
+		return -ENOENT;
+
+	if (func >= DSM_FUNC_NUM_FUNCS)
+		return -EOPNOTSUPP;
+
+	prop = dsm_func_to_prop_name(func);
+	if (!prop)
+		return -EOPNOTSUPP;
+
+	if (of_property_read_u32_array(node, prop, buf, ARRAY_SIZE(buf)))
+		return -ENODATA;
+
+	if (buf[0] != 0) {
+		IWL_DEBUG_RADIO(fwrt,
+				"DT: DSM func %d (%s) unsupported revision %u\n",
+				func, prop, buf[0]);
+		return -EINVAL;
+	}
+
+	if (fwrt->dsm_source == BIOS_SOURCE_NONE) {
+		fwrt->dsm_source = BIOS_SOURCE_DT;
+		fwrt->dsm_revision = 0;
+	}
+
+	*value = buf[1];
+	IWL_DEBUG_RADIO(fwrt, "DT: DSM func=%d (%s) val=%u\n",
+			func, prop, *value);
+	return 0;
+}
+
+/**
+ * iwl_dt_get_pwr_limit - read static platform power limit from DT "intel,splc"
+ * @fwrt: firmware runtime context
+ * @data: output power limit in mW
+ *
+ * Reads the 2-cell "intel,splc" DT property: [revision, power_limit_mw].
+ * Return: 0 on success, -ENOENT if absent, -EINVAL for unsupported revision.
+ */
+int iwl_dt_get_pwr_limit(struct iwl_fw_runtime *fwrt, u64 *data)
+{
+	struct device_node *node = dev_of_node(fwrt->dev);
+	u32 buf[2];
+	int ret;
+
+	if (!node)
+		return -ENOENT;
+
+	ret = of_property_read_u32_array(node, IWL_DT_PROP_SPLC,
+					 buf, ARRAY_SIZE(buf));
+	if (ret)
+		return ret;
+
+	if (buf[0] != 0) {
+		IWL_DEBUG_RADIO(fwrt, "DT: splc unsupported revision %u\n",
+				buf[0]);
+		return -EINVAL;
+	}
+
+	*data = buf[1];
+	IWL_DEBUG_RADIO(fwrt, "DT: Read SPLC power limit %llu mW\n", *data);
+	return 0;
+}
+
+/**
+ * iwl_dt_get_wbem - read 320 MHz per-MCC enablement from DT "intel,wbem"
+ * @fwrt: firmware runtime context
+ * @data: output bitmask (bit 0 = Japan, bit 1 = South Korea)
+ *
+ * Reads the 2-cell "intel,wbem" DT property: [revision, wifi320mhz_mcc].
+ * Return: 0 on success, -ENOENT if absent, -EINVAL for unsupported revision.
+ */
+int iwl_dt_get_wbem(struct iwl_fw_runtime *fwrt, u32 *data)
+{
+	struct device_node *node = dev_of_node(fwrt->dev);
+	u32 buf[2];
+	int ret;
+
+	if (!node)
+		return -ENOENT;
+
+	ret = of_property_read_u32_array(node, IWL_DT_PROP_WBEM,
+					 buf, ARRAY_SIZE(buf));
+	if (ret)
+		return ret;
+
+	if (buf[0] != 0) {
+		IWL_DEBUG_RADIO(fwrt, "DT: wbem unsupported revision %u\n",
+				buf[0]);
+		return -EINVAL;
+	}
+
+	*data = buf[1];
+	IWL_DEBUG_RADIO(fwrt, "DT: Read WBEM config 0x%x\n", *data);
+	return 0;
+}
+
+/**
+ * iwl_dt_get_puncturing - read channel puncturing config from DT "intel,wcpe"
+ * @fwrt: firmware runtime context
+ *
+ * Reads the 2-cell "intel,wcpe" DT property: [revision, puncturing_bitmap].
+ * Return: the bitmask (bit 0 = USA, bit 1 = Canada), or 0 if absent
+ * or revision unsupported.
+ */
+int iwl_dt_get_puncturing(struct iwl_fw_runtime *fwrt)
+{
+	struct device_node *node = dev_of_node(fwrt->dev);
+	u32 buf[2];
+	int ret;
+
+	if (!node)
+		return 0;
+
+	ret = of_property_read_u32_array(node, IWL_DT_PROP_WCPE,
+					 buf, ARRAY_SIZE(buf));
+	if (ret)
+		return 0;
+
+	if (buf[0] != 0) {
+		IWL_DEBUG_RADIO(fwrt, "DT: wcpe unsupported revision %u\n",
+				buf[0]);
+		return 0;
+	}
+
+	IWL_DEBUG_RADIO(fwrt, "DT: Read WCPE puncturing config 0x%x\n", buf[1]);
+	return buf[1] & IWL_DT_WCPE_MASK;
+}
+IWL_EXPORT_SYMBOL(iwl_dt_get_puncturing);
diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dt.h b/drivers/net/wireless/intel/iwlwifi/fw/dt.h
new file mode 100644
index 000000000000..01d8eef2babc
--- /dev/null
+++ b/drivers/net/wireless/intel/iwlwifi/fw/dt.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * Copyright (C) 2026 Intel Corporation
+ */
+#ifndef __iwl_fw_dt_h__
+#define __iwl_fw_dt_h__
+
+#include "fw/regulatory.h"
+
+/*
+ * Device Tree property parsing for Intel iwlwifi discrete Wi-Fi devices.
+ *
+ * On platforms without UEFI variables or ACPI methods, regulatory and
+ * power configuration can be supplied via Device Tree properties in the
+ * Wi-Fi PCIe device node. The following properties are supported:
+ *
+ *   intel,wrds                 - SAR power limits (WRDS, revision 3)
+ *   intel,uats                 - UHB AP type support table (UATS, revision 1)
+ *   intel,splc                 - static platform power limit
+ *   intel,wbem                 - 320 MHz per-MCC enablement
+ *   intel,wcpe                 - channel puncturing enablement
+ *   intel,srd                  - ETSI SRD channel configuration
+ *   intel,6e-uhb               - 6 GHz UHB country enable bitmask
+ *   intel,regulatory-special   - regulatory special configuration
+ *   intel,activate-channel     - indoor channel activation bitmask
+ *   intel,force-disable-channels - band force-disable bitmask
+ *   intel,11be                 - 802.11be per-country enable bitmask
+ *
+ * DT is the lowest-priority source: UEFI variables take precedence, then
+ * ACPI methods, and finally these DT properties.
+ */
+
+struct iwl_fw_runtime;
+
+#if IS_ENABLED(CONFIG_OF)
+
+/* Functions implemented in fw/dt.c */
+int iwl_dt_get_wrds_table(struct iwl_fw_runtime *fwrt);
+void iwl_dt_get_uats_table(struct iwl_fw_runtime *fwrt);
+int iwl_dt_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func,
+		   u32 *value);
+int iwl_dt_get_pwr_limit(struct iwl_fw_runtime *fwrt, u64 *data);
+int iwl_dt_get_wbem(struct iwl_fw_runtime *fwrt, u32 *data);
+int iwl_dt_get_puncturing(struct iwl_fw_runtime *fwrt);
+
+#else /* !CONFIG_OF */
+
+static inline int iwl_dt_get_wrds_table(struct iwl_fw_runtime *fwrt)
+{
+	return -ENOENT;
+}
+
+static inline void iwl_dt_get_uats_table(struct iwl_fw_runtime *fwrt) {}
+
+static inline int iwl_dt_get_dsm(struct iwl_fw_runtime *fwrt,
+				 enum iwl_dsm_funcs func, u32 *value)
+{
+	return -ENOENT;
+}
+
+static inline int iwl_dt_get_pwr_limit(struct iwl_fw_runtime *fwrt, u64 *data)
+{
+	return -ENOENT;
+}
+
+static inline int iwl_dt_get_wbem(struct iwl_fw_runtime *fwrt, u32 *data)
+{
+	return -ENOENT;
+}
+
+static inline int iwl_dt_get_puncturing(struct iwl_fw_runtime *fwrt)
+{
+	return 0;
+}
+
+#endif /* CONFIG_OF */
+
+/*
+ * The following tables have no Device Tree implementation regardless of
+ * CONFIG_OF. Callers fall back gracefully when these return -ENOENT.
+ */
+static inline int iwl_dt_get_ppag_table(struct iwl_fw_runtime *fwrt)
+{
+	return -ENOENT;
+}
+
+static inline int iwl_dt_get_tas_table(struct iwl_fw_runtime *fwrt,
+				       struct iwl_tas_data *data)
+{
+	return -ENOENT;
+}
+
+static inline int iwl_dt_get_ewrd_table(struct iwl_fw_runtime *fwrt)
+{
+	return -ENOENT;
+}
+
+static inline int iwl_dt_get_wgds_table(struct iwl_fw_runtime *fwrt)
+{
+	return -ENOENT;
+}
+
+static inline int iwl_dt_get_phy_filters(struct iwl_fw_runtime *fwrt)
+{
+	return -ENOENT;
+}
+
+static inline int iwl_dt_get_mcc(struct iwl_fw_runtime *fwrt, char *data)
+{
+	return -ENOENT;
+}
+
+static inline int iwl_dt_get_eckv(struct iwl_fw_runtime *fwrt, u32 *data)
+{
+	return -ENOENT;
+}
+
+static inline int iwl_dt_get_dsbr(struct iwl_fw_runtime *fwrt, u32 *data)
+{
+	return -ENOENT;
+}
+
+#endif /* __iwl_fw_dt_h__ */
diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h
index 6fffc032efd3..22c97c75b83c 100644
--- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h
+++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
 /*
- * Copyright (C) 2023-2025 Intel Corporation
+ * Copyright (C) 2023-2026 Intel Corporation
  */
 
 #ifndef __fw_regulatory_h__
@@ -30,6 +30,7 @@
 #define BIOS_GEO_MIN_PROFILE_NUM	3
 
 #define IWL_SAR_ENABLE_MSK		BIT(0)
+#define IWL_REDUCE_POWER_FLAGS_POS	1
 
 /* PPAG gain value bounds in 1/8 dBm */
 #define IWL_PPAG_MIN_LB	-16
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 3/3] wifi: iwlwifi: dt: use Device Tree as fallback BIOS configuration source
From: Avinash Bhatt @ 2026-05-04  9:53 UTC (permalink / raw)
  To: devicetree, linux-wireless
  Cc: robh, krzk+dt, conor+dt, johannes, miriam.rachel.korenblit,
	linux-kernel, kobi.guetta, emmanuel.grumbach, avinash.bhatt
In-Reply-To: <20260504095327.30892-1-avinash.bhatt@intel.com>

When neither UEFI variables nor ACPI methods provide a BIOS
configuration table, fall back to Device Tree. This preserves the
existing UEFI -> ACPI priority order and extends it with DT as the
lowest-priority source.

Signed-off-by: Avinash Bhatt <avinash.bhatt@intel.com>
---
 drivers/net/wireless/intel/iwlwifi/fw/regulatory.c  | 3 +++
 drivers/net/wireless/intel/iwlwifi/mld/mld.c        | 3 +++
 drivers/net/wireless/intel/iwlwifi/mld/regulatory.c | 5 ++++-
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c
index 55128caac7ed..10bb4d2f64cf 100644
--- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c
+++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c
@@ -8,6 +8,7 @@
 #include "regulatory.h"
 #include "fw/runtime.h"
 #include "fw/uefi.h"
+#include "fw/dt.h"
 
 #define GET_BIOS_TABLE(__name, ...)					\
 do {									\
@@ -16,6 +17,8 @@ do {									\
 		ret = iwl_uefi_get_ ## __name(__VA_ARGS__);		\
 	if (ret < 0)							\
 		ret = iwl_acpi_get_ ## __name(__VA_ARGS__);		\
+	if (ret < 0)							\
+		ret = iwl_dt_get_ ## __name(__VA_ARGS__);		\
 	return ret;							\
 } while (0)
 
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c
index 495e9d8f3af6..a3e93112caff 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c
@@ -12,6 +12,7 @@
 #include "fw/api/coex.h"
 #include "fw/dbg.h"
 #include "fw/uefi.h"
+#include "fw/dt.h"
 
 #include "mld.h"
 #include "mlo.h"
@@ -418,6 +419,8 @@ iwl_op_mode_mld_start(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg,
 	iwl_mld_get_bios_tables(mld);
 	iwl_uefi_get_sgom_table(trans, &mld->fwrt);
 	mld->bios_enable_puncturing = iwl_uefi_get_puncturing(&mld->fwrt);
+	if (!mld->bios_enable_puncturing)
+		mld->bios_enable_puncturing = iwl_dt_get_puncturing(&mld->fwrt);
 
 	iwl_mld_hw_set_regulatory(mld);
 
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c
index 659243ada86c..1c7d2136b2e3 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
 /*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
  */
 
 #include <linux/dmi.h>
@@ -8,6 +8,7 @@
 #include "fw/regulatory.h"
 #include "fw/acpi.h"
 #include "fw/uefi.h"
+#include "fw/dt.h"
 
 #include "regulatory.h"
 #include "mld.h"
@@ -64,6 +65,8 @@ void iwl_mld_get_bios_tables(struct iwl_mld *mld)
 	}
 
 	iwl_uefi_get_uats_table(mld->trans, &mld->fwrt);
+	if (!mld->fwrt.ap_type_cmd_valid)
+		iwl_dt_get_uats_table(&mld->fwrt);
 	iwl_uefi_get_uneb_table(mld->trans, &mld->fwrt);
 
 	iwl_bios_get_phy_filters(&mld->fwrt);
-- 
2.34.1


^ permalink raw reply related


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