Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH v2 v2] wifi: wilc1000: fix dma_buffer leak on bus acquire failure
From: Shitalkumar Gandhi @ 2026-05-11  4:27 UTC (permalink / raw)
  To: ajay.kathat, claudiu.beznea
  Cc: kvalo, linux-wireless, netdev, linux-kernel, Shitalkumar Gandhi
In-Reply-To: <20260510112520.977360-1-shitalkumar.gandhi@cambiumnetworks.com>

wilc_wlan_firmware_download() allocates dma_buffer with kmalloc() at
the top of the function and uses a 'fail:' label to free it via
kfree(dma_buffer) on error.

All later error paths correctly use 'goto fail' to route through this
cleanup. However, the early failure path after the first acquire_bus()
call uses a bare 'return ret;', which leaks dma_buffer whenever the bus
acquire fails.

Replace the early return with goto fail so the existing cleanup path
runs.

Found via a custom Coccinelle semantic patch hunting for kmalloc'd
locals leaked on early-return error paths in driver firmware-download
code.

Fixes: 1241c5650ff7 ("wifi: wilc1000: Fill in missing error handling")
Signed-off-by: Shitalkumar Gandhi <shitalkumar.gandhi@cambiumnetworks.com>
---
Changes since v1:
  - Corrected From: and Signed-off-by: to author's real identity
    (Shitalkumar Gandhi <shitalkumar.gandhi@cambiumnetworks.com>).
    v1 was sent with incorrect author attribution due to a local
    git config mistake. No code changes.

 drivers/net/wireless/microchip/wilc1000/wlan.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/wireless/microchip/wilc1000/wlan.c b/drivers/net/wireless/microchip/wilc1000/wlan.c
index 3fa8592eb250..4b116fe6f9ea 100644
--- a/drivers/net/wireless/microchip/wilc1000/wlan.c
+++ b/drivers/net/wireless/microchip/wilc1000/wlan.c
@@ -1265,7 +1265,7 @@ int wilc_wlan_firmware_download(struct wilc *wilc, const u8 *buffer,
 
 	ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
 	if (ret)
-		return ret;
+		goto fail;
 
 	wilc->hif_func->hif_read_reg(wilc, WILC_GLB_RESET_0, &reg);
 	reg &= ~BIT(10);
-- 
2.25.1


^ permalink raw reply related

* [PATCH wireless-next] wifi: b43legacy: Use flexible array for DMA metadata
From: Rosen Penev @ 2026-05-11  4:19 UTC (permalink / raw)
  To: linux-wireless
  Cc: Kees Cook, Gustavo A. R. Silva,
	open list:B43LEGACY WIRELESS DRIVER, open list,
	open list:KERNEL HARDENING (not covered by other areas):Keyword:b__counted_by(_le|_be)?b

Store the per-descriptor metadata in the DMA ring allocation instead of
allocating it separately.

This ties the metadata lifetime directly to the ring, removes a separate
allocation failure path, and keeps the descriptor count available for
__counted_by() bounds checking.

Assisted-by: Codex:GPT-5.5
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
 drivers/net/wireless/broadcom/b43legacy/dma.c | 24 ++++++++-----------
 drivers/net/wireless/broadcom/b43legacy/dma.h |  4 ++--
 2 files changed, 12 insertions(+), 16 deletions(-)

diff --git a/drivers/net/wireless/broadcom/b43legacy/dma.c b/drivers/net/wireless/broadcom/b43legacy/dma.c
index a9557356c9ab..6c3d847242bb 100644
--- a/drivers/net/wireless/broadcom/b43legacy/dma.c
+++ b/drivers/net/wireless/broadcom/b43legacy/dma.c
@@ -610,25 +610,25 @@ struct b43legacy_dmaring *b43legacy_setup_dmaring(struct b43legacy_wldev *dev,
 	int nr_slots;
 	dma_addr_t dma_test;
 
-	ring = kzalloc_obj(*ring);
+	if (for_tx)
+		nr_slots = B43legacy_TXRING_SLOTS;
+	else
+		nr_slots = B43legacy_RXRING_SLOTS;
+
+	ring = kzalloc_flex(*ring, meta, nr_slots);
 	if (!ring)
 		goto out;
+
+	ring->nr_slots = nr_slots;
 	ring->type = type;
 	ring->dev = dev;
 
-	nr_slots = B43legacy_RXRING_SLOTS;
-	if (for_tx)
-		nr_slots = B43legacy_TXRING_SLOTS;
-
-	ring->meta = kzalloc_objs(struct b43legacy_dmadesc_meta, nr_slots);
-	if (!ring->meta)
-		goto err_kfree_ring;
 	if (for_tx) {
 		ring->txhdr_cache = kcalloc(nr_slots,
 					sizeof(struct b43legacy_txhdr_fw3),
 					GFP_KERNEL);
 		if (!ring->txhdr_cache)
-			goto err_kfree_meta;
+			goto err_kfree_ring;
 
 		/* test for ability to dma to txhdr_cache */
 		dma_test = dma_map_single(dev->dev->dma_dev, ring->txhdr_cache,
@@ -643,7 +643,7 @@ struct b43legacy_dmaring *b43legacy_setup_dmaring(struct b43legacy_wldev *dev,
 					sizeof(struct b43legacy_txhdr_fw3),
 					GFP_KERNEL | GFP_DMA);
 			if (!ring->txhdr_cache)
-				goto err_kfree_meta;
+				goto err_kfree_txhdr_cache;
 
 			dma_test = dma_map_single(dev->dev->dma_dev,
 					ring->txhdr_cache,
@@ -660,7 +660,6 @@ struct b43legacy_dmaring *b43legacy_setup_dmaring(struct b43legacy_wldev *dev,
 				 DMA_TO_DEVICE);
 	}
 
-	ring->nr_slots = nr_slots;
 	ring->mmio_base = b43legacy_dmacontroller_base(type, controller_index);
 	ring->index = controller_index;
 	if (for_tx) {
@@ -694,8 +693,6 @@ struct b43legacy_dmaring *b43legacy_setup_dmaring(struct b43legacy_wldev *dev,
 	free_ringmemory(ring);
 err_kfree_txhdr_cache:
 	kfree(ring->txhdr_cache);
-err_kfree_meta:
-	kfree(ring->meta);
 err_kfree_ring:
 	kfree(ring);
 	ring = NULL;
@@ -720,7 +717,6 @@ static void b43legacy_destroy_dmaring(struct b43legacy_dmaring *ring)
 	free_ringmemory(ring);
 
 	kfree(ring->txhdr_cache);
-	kfree(ring->meta);
 	kfree(ring);
 }
 
diff --git a/drivers/net/wireless/broadcom/b43legacy/dma.h b/drivers/net/wireless/broadcom/b43legacy/dma.h
index b5c1a51db2a4..edd06225b64f 100644
--- a/drivers/net/wireless/broadcom/b43legacy/dma.h
+++ b/drivers/net/wireless/broadcom/b43legacy/dma.h
@@ -122,8 +122,6 @@ enum b43legacy_dmatype {
 struct b43legacy_dmaring {
 	/* Kernel virtual base address of the ring memory. */
 	void *descbase;
-	/* Meta data about all descriptors. */
-	struct b43legacy_dmadesc_meta *meta;
 	/* Cache of TX headers for each slot.
 	 * This is to avoid an allocation on each TX.
 	 * This is NULL for an RX ring.
@@ -161,6 +159,8 @@ struct b43legacy_dmaring {
 	/* Last time we injected a ring overflow. */
 	unsigned long last_injected_overflow;
 #endif /* CONFIG_B43LEGACY_DEBUG*/
+	/* Meta data about all descriptors. */
+	struct b43legacy_dmadesc_meta meta[] __counted_by(nr_slots);
 };
 
 
-- 
2.54.0


^ permalink raw reply related

* [PATCH ath-next v2 2/2] wifi: ath12k: Add debugfs support to simulate incumbent signal interference
From: Amith A @ 2026-05-11  4:02 UTC (permalink / raw)
  To: ath12k; +Cc: linux-wireless, amith.a, Aishwarya R
In-Reply-To: <20260511040242.1351792-1-amith.a@oss.qualcomm.com>

From: Aishwarya R <aishwarya.r@oss.qualcomm.com>

Add debugfs support to simulate incumbent signal interference from the
host for testing purposes. The debugfs entry is created only for 6 GHz
radio when firmware advertises the support through
WMI_TLV_SERVICE_DCS_INCUMBENT_SIGNAL_INTERFERENCE_SUPPORT flag.

Debugfs command:
echo <interference_bitmap> > /sys/kernel/debug/ath12k/pci-000X/macX/simulate_incumbent_signal_interference

Each bit in the interference_bitmap represents a 20 MHz segment. Bit 0
corresponds to the primary 20 MHz segment, regardless of its position
within the operating bandwidth. Bit 1 represents the next adjacent 20 MHz
segment, bit 2 the lower 20 MHz segment of the adjacent 40 MHz segment,
and so on-progressing sequentially across the bandwidth..

Example:
echo 0xF0 > /sys/kernel/debug/ath12k/pci-0002:01:00.0/mac0/simulate_incumbent_signal_interference
This indicates that all the subchannels in the secondary 80 MHz segment
were affected.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.5-01651-QCAHKSWPL_SILICONZ-1

Signed-off-by: Aishwarya R <aishwarya.r@oss.qualcomm.com>
Signed-off-by: Amith A <amith.a@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/debugfs.c | 46 +++++++++++++++++++++++
 drivers/net/wireless/ath/ath12k/wmi.c     | 36 ++++++++++++++++++
 drivers/net/wireless/ath/ath12k/wmi.h     | 14 +++++++
 3 files changed, 96 insertions(+)

diff --git a/drivers/net/wireless/ath/ath12k/debugfs.c b/drivers/net/wireless/ath/ath12k/debugfs.c
index 8c81a1c22449..d17d4a8f1cb7 100644
--- a/drivers/net/wireless/ath/ath12k/debugfs.c
+++ b/drivers/net/wireless/ath/ath12k/debugfs.c
@@ -1450,6 +1450,44 @@ static const struct file_operations fops_pdev_stats = {
 	.llseek = default_llseek,
 };
 
+static ssize_t
+ath12k_write_simulate_incumbent_signal_interference(struct file *file,
+						    const char __user *user_buf,
+						    size_t count, loff_t *ppos)
+{
+	struct ath12k *ar = file->private_data;
+	struct ath12k_hw *ah = ath12k_ar_to_ah(ar);
+	struct wiphy *wiphy = ath12k_ar_to_hw(ar)->wiphy;
+	u32 chan_bw_interference_bitmap;
+	int ret;
+
+	if (ah->state != ATH12K_HW_STATE_ON)
+		return -ENETDOWN;
+
+	/*
+	 * Bitmap uses the firmware primary-based ordering documented in
+	 * ath12k_wmi_transform_interference_bitmap() & intf_map_80.
+	 */
+	if (kstrtou32_from_user(user_buf, count, 0, &chan_bw_interference_bitmap))
+		return -EINVAL;
+
+	wiphy_lock(wiphy);
+	ret = ath12k_wmi_simulate_incumbent_signal_interference(ar, chan_bw_interference_bitmap);
+	if (ret)
+		goto exit;
+
+	ret = count;
+
+exit:
+	wiphy_unlock(wiphy);
+	return ret;
+}
+
+static const struct file_operations fops_simulate_incumbent_signal_interference = {
+	.write = ath12k_write_simulate_incumbent_signal_interference,
+	.open = simple_open,
+};
+
 static
 void ath12k_debugfs_fw_stats_register(struct ath12k *ar)
 {
@@ -1515,6 +1553,14 @@ void ath12k_debugfs_register(struct ath12k *ar)
 			    ar, &fops_tpc_stats_type);
 	init_completion(&ar->debug.tpc_complete);
 
+	if (ar->mac.sbands[NL80211_BAND_6GHZ].channels &&
+	    test_bit(WMI_TLV_SERVICE_DCS_INCUMBENT_SIGNAL_INTERFERENCE_SUPPORT,
+		     ar->ab->wmi_ab.svc_map)) {
+		debugfs_create_file("simulate_incumbent_signal_interference", 0200,
+				    ar->debug.debugfs_pdev, ar,
+				    &fops_simulate_incumbent_signal_interference);
+	}
+
 	ath12k_debugfs_htt_stats_register(ar);
 	ath12k_debugfs_fw_stats_register(ar);
 
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 8609c612aa2c..25e61cc7e5ac 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -10568,6 +10568,42 @@ int ath12k_wmi_send_tpc_stats_request(struct ath12k *ar,
 	return ret;
 }
 
+int ath12k_wmi_simulate_incumbent_signal_interference(struct ath12k *ar,
+						      u32 chan_bw_interference_bitmap)
+{
+	struct wmi_unit_test_arg wmi_ut = {};
+	struct ath12k_link_vif *arvif;
+	struct ath12k_vif *ahvif;
+	bool arvif_found = false;
+
+	list_for_each_entry(arvif, &ar->arvifs, list) {
+		ahvif = arvif->ahvif;
+		if (arvif->is_started && ahvif->vdev_type == WMI_VDEV_TYPE_AP) {
+			arvif_found = true;
+			break;
+		}
+	}
+
+	if (!arvif_found)
+		return -EINVAL;
+
+	wmi_ut.args[ATH12K_WMI_INCUMBENT_SIGNAL_TEST_INTF] =
+		ATH12K_WMI_UNIT_TEST_INCUMBENT_SIGNAL_INTF_TYPE;
+	wmi_ut.args[ATH12K_WMI_INCUMBENT_SIGNAL_TEST_BITMAP] =
+		chan_bw_interference_bitmap;
+
+	wmi_ut.vdev_id = arvif->vdev_id;
+	wmi_ut.module_id = ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_MODULE;
+	wmi_ut.num_args = ATH12K_WMI_INCUMBENT_SIGNAL_MAX_TEST_ARGS;
+	wmi_ut.diag_token = ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_TOKEN;
+
+	ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+		   "Triggering incumbent signal interference simulation, interference bitmap: 0x%x\n",
+		   chan_bw_interference_bitmap);
+
+	return ath12k_wmi_send_unit_test_cmd(ar, &wmi_ut);
+}
+
 int ath12k_wmi_connect(struct ath12k_base *ab)
 {
 	u32 i;
diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
index d74f7fca7678..b5b3e472631c 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.h
+++ b/drivers/net/wireless/ath/ath12k/wmi.h
@@ -2269,6 +2269,8 @@ enum wmi_tlv_service {
 
 	WMI_TLV_SERVICE_REG_CC_EXT_EVENT_SUPPORT = 281,
 
+	WMI_TLV_SERVICE_DCS_INCUMBENT_SIGNAL_INTERFERENCE_SUPPORT = 286,
+
 	WMI_TLV_SERVICE_11BE = 289,
 
 	WMI_TLV_SERVICE_WMSK_COMPACTION_RX_TLVS = 361,
@@ -4244,6 +4246,10 @@ struct wmi_addba_clear_resp_cmd {
 #define DFS_UNIT_TEST_MODULE	0x2b
 #define DFS_UNIT_TEST_TOKEN	0xAA
 
+#define ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_MODULE	0x18
+#define ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_TOKEN	0
+#define ATH12K_WMI_UNIT_TEST_INCUMBENT_SIGNAL_INTF_TYPE	1
+
 enum dfs_test_args_idx {
 	DFS_TEST_CMDID = 0,
 	DFS_TEST_PDEV_ID,
@@ -4251,6 +4257,12 @@ enum dfs_test_args_idx {
 	DFS_MAX_TEST_ARGS,
 };
 
+enum ath12k_wmi_incumbent_signal_test_args_idx {
+	ATH12K_WMI_INCUMBENT_SIGNAL_TEST_INTF,
+	ATH12K_WMI_INCUMBENT_SIGNAL_TEST_BITMAP,
+	ATH12K_WMI_INCUMBENT_SIGNAL_MAX_TEST_ARGS,
+};
+
 /* update if another test command requires more */
 #define WMI_UNIT_TEST_ARGS_MAX DFS_MAX_TEST_ARGS
 
@@ -6682,6 +6694,8 @@ int ath12k_wmi_send_vdev_set_tpc_power(struct ath12k *ar,
 				       struct ath12k_reg_tpc_power_info *param);
 int ath12k_wmi_send_mlo_link_set_active_cmd(struct ath12k_base *ab,
 					    struct wmi_mlo_link_set_active_arg *param);
+int ath12k_wmi_simulate_incumbent_signal_interference(struct ath12k *ar,
+						      u32 chan_bw_interference_bitmap);
 int ath12k_wmi_alloc(void);
 void ath12k_wmi_free(void);
 
-- 
2.34.1


^ permalink raw reply related

* [PATCH ath-next v2 1/2] wifi: ath12k: Add support for handling incumbent signal interference in 6 GHz
From: Amith A @ 2026-05-11  4:02 UTC (permalink / raw)
  To: ath12k; +Cc: linux-wireless, amith.a, Aishwarya R, Hari Chandrakanthan
In-Reply-To: <20260511040242.1351792-1-amith.a@oss.qualcomm.com>

From: Aishwarya R <aishwarya.r@oss.qualcomm.com>

When incumbent signal interference is detected by an AP/mesh interface
operating in the 6 GHz band, as mandated by the FCC, it is expected to
vacate the affected channels. The firmware indicates the interference to
the host using the WMI_DCS_INTERFERENCE_EVENT.

To handle the new WMI event, first parse it to retrieve the interference
information. Next, validate the interference-detected channel and
the interference bitmap. The interference bitmap received from the
firmware uses a mapping where bit 0 corresponds to the primary
20 MHz segment, regardless of its position within the operating
bandwidth. Bit 1 represents the next adjacent 20 MHz segment, bit 2
the lower 20 MHz segment of the adjacent 40 MHz segment, and so
on, progressing sequentially across the bandwidth. However, for userspace
consumption via mac80211, this bitmap must be transformed into a
standardized format such that each bit position directly maps to the
corresponding sub-channel index within the operating bandwidth.
Finally, indicate the transformed interference bitmap to mac80211, which
then notifies userspace of the interference. Once the incumbent signal
interference is detected, firmware suspends TX internally on the affected
operating channel while userspace decides the mitigation action. Userspace
is expected to trigger a channel switch or bandwidth reduction to mitigate
the interference. Also, add a flag handling_in_progress to indicate that
handling of interference is in progress. Set it to true after
indicating to mac80211 about the interference. Reset the flag to false
after the operating channel is switched by userspace. This prevents
processing any further interference events when there is already a
previous event being handled. Hence, further events are processed only
after a channel switch request is received from userspace for the
previous event.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.5-01651-QCAHKSWPL_SILICONZ-1

Signed-off-by: Aishwarya R <aishwarya.r@oss.qualcomm.com>
Co-developed-by: Hari Chandrakanthan <quic_haric@quicinc.com>
Signed-off-by: Hari Chandrakanthan <quic_haric@quicinc.com>
Signed-off-by: Amith A <amith.a@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/core.h |   8 +
 drivers/net/wireless/ath/ath12k/mac.c  |  46 +++
 drivers/net/wireless/ath/ath12k/wmi.c  | 389 +++++++++++++++++++++++++
 drivers/net/wireless/ath/ath12k/wmi.h  |  58 +++-
 4 files changed, 500 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index 8be435535a4e..3aa25db9264e 100644
--- a/drivers/net/wireless/ath/ath12k/core.h
+++ b/drivers/net/wireless/ath/ath12k/core.h
@@ -763,6 +763,14 @@ struct ath12k {
 	struct ath12k_pdev_rssi_offsets rssi_info;
 
 	struct ath12k_thermal thermal;
+
+	/* Protected by ar->data_lock */
+	struct ath12k_incumbent_signal_interference {
+		u32 center_freq;
+		enum nl80211_chan_width width;
+		u32 chan_bw_interference_bitmap;
+		bool handling_in_progress;
+	} incumbent_signal_interference;
 };
 
 struct ath12k_hw {
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 9ce759626f18..75881fccd175 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -9637,6 +9637,10 @@ static int ath12k_mac_start(struct ath12k *ar)
 	ar->allocated_vdev_map = 0;
 	ar->chan_tx_pwr = ATH12K_PDEV_TX_POWER_INVALID;
 
+	spin_lock_bh(&ar->data_lock);
+	ar->incumbent_signal_interference.handling_in_progress = false;
+	spin_unlock_bh(&ar->data_lock);
+
 	/* Configure monitor status ring with default rx_filter to get rx status
 	 * such as rssi, rx_duration.
 	 */
@@ -9850,6 +9854,10 @@ static void ath12k_mac_stop(struct ath12k *ar)
 	synchronize_rcu();
 
 	atomic_set(&ar->num_pending_mgmt_tx, 0);
+
+	spin_lock_bh(&ar->data_lock);
+	ar->incumbent_signal_interference.handling_in_progress = false;
+	spin_unlock_bh(&ar->data_lock);
 }
 
 void ath12k_mac_op_stop(struct ieee80211_hw *hw, bool suspend)
@@ -11436,8 +11444,10 @@ ath12k_mac_update_vif_chan(struct ath12k *ar,
 			   struct ieee80211_vif_chanctx_switch *vifs,
 			   int n_vifs)
 {
+	struct ath12k_incumbent_signal_interference *incumbent;
 	struct ath12k_wmi_vdev_up_params params = {};
 	struct ieee80211_bss_conf *link_conf;
+	struct cfg80211_chan_def *chandef;
 	struct ath12k_base *ab = ar->ab;
 	struct ath12k_link_vif *arvif;
 	struct ieee80211_vif *vif;
@@ -11549,6 +11559,42 @@ ath12k_mac_update_vif_chan(struct ath12k *ar,
 		if (!ath12k_mac_monitor_stop(ar))
 			ath12k_mac_monitor_start(ar);
 	}
+
+	incumbent = &ar->incumbent_signal_interference;
+	spin_lock_bh(&ar->data_lock);
+	if (incumbent->handling_in_progress) {
+		chandef = &vifs[0].new_ctx->def;
+		if (incumbent->chan_bw_interference_bitmap &
+		    ATH12K_WMI_DCS_SEG_PRI20) {
+			if (incumbent->center_freq !=
+			    chandef->chan->center_freq) {
+				incumbent->chan_bw_interference_bitmap = 0;
+				incumbent->handling_in_progress = false;
+				ath12k_dbg(ab, ATH12K_DBG_MAC,
+					   "incumbent signal interference chan switch completed\n");
+			} else {
+				ath12k_warn(ab,
+					    "incumbent signal interference chan switch not done, freq %u\n",
+					    incumbent->center_freq);
+			}
+		} else {
+			if (incumbent->center_freq !=
+			    chandef->chan->center_freq ||
+			    incumbent->width != chandef->width) {
+				incumbent->chan_bw_interference_bitmap = 0;
+				incumbent->handling_in_progress = false;
+				ath12k_dbg(ab, ATH12K_DBG_MAC,
+					   "Bandwidth/channel change due to incumbent signal interference completed\n");
+			} else {
+				ath12k_warn(ab, "Bandwidth/channel change due to incumbent sig intf not done intf_freq %u chan_freq %u intf_width %u chan_width %u\n",
+					    incumbent->center_freq,
+					    chandef->chan->center_freq,
+					    incumbent->width,
+					    chandef->width);
+			}
+		}
+	}
+	spin_unlock_bh(&ar->data_lock);
 }
 
 static void
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index c7559938564c..8609c612aa2c 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -234,6 +234,68 @@ static const int ath12k_hw_mode_pri_map[] = {
 	PRIMAP(WMI_HOST_HW_MODE_MAX),
 };
 
+/*
+ * Interference bitmap transform maps used by
+ * ath12k_wmi_transform_interference_bitmap().
+ *
+ * Firmware reports bitmap bits in a primary-based order where:
+ * - bit 0 is always the primary 20 MHz segment,
+ * - bit 1 is the adjacent 20 MHz in the same 40 MHz block,
+ * - bit 2 is the lower 20 MHz segment of the adjacent 40 MHz segment
+ * - bit 3 is the higher 20 MHz segment of the adjacent 40 MHz segment
+ * - remaining bits continue outward in 80/160/320 MHz groups.
+ *
+ * cfg80211 userspace notification expects absolute frequency order where:
+ * - bit 0 is the lowest-frequency 20 MHz segment in the current chandef,
+ * - bit N increases monotonically toward higher frequency.
+ *
+ * For each bandwidth-specific map:
+ * - row index    = primary 20 MHz index in absolute (low->high) order,
+ * - column index = source bit position from firmware bitmap,
+ * - value        = destination bit position in absolute order bitmap.
+ *
+ * Example for 80 MHz: if primary index is 2 (third 20 MHz chunk from low
+ * frequency), row intf_map_80[2] = { 2, 3, 0, 1 } means firmware bits {0,1,2,3}
+ * are remapped to destination bits {2,3,0,1} before notifying cfg80211.
+ */
+
+static const int intf_map_80[4][4] = {
+	{ 0, 1, 2, 3 },
+	{ 1, 0, 2, 3 },
+	{ 2, 3, 0, 1 },
+	{ 3, 2, 0, 1 }
+};
+
+static const int intf_map_160[8][8] = {
+	{ 0, 1, 2, 3, 4, 5, 6, 7 },
+	{ 1, 0, 2, 3, 4, 5, 6, 7 },
+	{ 2, 3, 0, 1, 4, 5, 6, 7 },
+	{ 3, 2, 0, 1, 4, 5, 6, 7 },
+	{ 4, 5, 6, 7, 0, 1, 2, 3 },
+	{ 5, 4, 6, 7, 0, 1, 2, 3 },
+	{ 6, 7, 4, 5, 0, 1, 2, 3 },
+	{ 7, 6, 4, 5, 0, 1, 2, 3 }
+};
+
+static const int intf_map_320[16][16] = {
+	{ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,  11,  12,  13,  14,  15 },
+	{ 1,  0,  2,  3,  4,  5,  6,  7,  8,  9,  10,  11,  12,  13,  14,  15 },
+	{ 2,  3,  0,  1,  4,  5,  6,  7,  8,  9,  10,  11,  12,  13,  14,  15 },
+	{ 3,  2,  0,  1,  4,  5,  6,  7,  8,  9,  10,  11,  12,  13,  14,  15 },
+	{ 4,  5,  6,  7,  0,  1,  2,  3,  8,  9,  10,  11,  12,  13,  14,  15 },
+	{ 5,  4,  6,  7,  0,  1,  2,  3,  8,  9,  10,  11,  12,  13,  14,  15 },
+	{ 6,  7,  4,  5,  0,  1,  2,  3,  8,  9,  10,  11,  12,  13,  14,  15 },
+	{ 7,  6,  4,  5,  0,  1,  2,  3,  8,  9,  10,  11,  12,  13,  14,  15 },
+	{ 8,  9,  10, 11, 12, 13, 14, 15, 0,  1,  2,   3,   4,   5,   6,   7  },
+	{ 9,  8,  10, 11, 12, 13, 14, 15, 0,  1,  2,   3,   4,   5,   6,   7  },
+	{ 10, 11, 8,  9,  12, 13, 14, 15, 0,  1,  2,   3,   4,   5,   6,   7  },
+	{ 11, 10, 8,  9,  12, 13, 14, 15, 0,  1,  2,   3,   4,   5,   6,   7  },
+	{ 12, 13, 14, 15, 8,  9,  10, 11, 0,  1,  2,   3,   4,   5,   6,   7  },
+	{ 13, 12, 14, 15, 8,  9,  10, 11, 0,  1,  2,   3,   4,   5,   6,   7  },
+	{ 14, 15, 12, 13, 8,  9,  10, 11, 0,  1,  2,   3,   4,   5,   6,   7  },
+	{ 15, 14, 12, 13, 8,  9,  10, 11, 0,  1,  2,   3,   4,   5,   6,   7  }
+};
+
 static int
 ath12k_wmi_tlv_iter(struct ath12k_base *ab, const void *ptr, size_t len,
 		    int (*iter)(struct ath12k_base *ab, u16 tag, u16 len,
@@ -8597,6 +8659,330 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab,
 			    ev->ctl_failsafe_status);
 }
 
+static int
+ath12k_wmi_incumbent_signal_interference_subtlv_parser(struct ath12k_base *ab,
+						       u16 tag, u16 len,
+						       const void *ptr,
+						       void *data)
+{
+	const struct ath12k_wmi_incumbent_signal_interference_params *info;
+	struct ath12k_wmi_incumbent_signal_interference_arg *arg = data;
+
+	switch (tag) {
+	case WMI_TAG_DCS_INCUMBENT_SIGNAL_INTERFERENCE_TYPE:
+		if (len < sizeof(*info)) {
+			ath12k_warn(ab,
+				    "DCS incumbent signal interference subtlv 0x%x invalid len %u\n",
+				    tag, len);
+			return -EINVAL;
+		}
+
+		info = ptr;
+
+		arg->chan_width = le32_to_cpu(info->chan_width);
+		arg->chan_freq = le32_to_cpu(info->chan_freq);
+		arg->center_freq0 = le32_to_cpu(info->center_freq0);
+		arg->center_freq1 = le32_to_cpu(info->center_freq1);
+		arg->chan_bw_interference_bitmap =
+			le32_to_cpu(info->chan_bw_interference_bitmap);
+
+		ath12k_dbg(ab, ATH12K_DBG_WMI,
+			   "incumbent signal interference chan width %u freq %u center_freq0 %u center_freq1 %u bitmap 0x%x\n",
+			   arg->chan_width, arg->chan_freq,
+			   arg->center_freq0, arg->center_freq1,
+			   arg->chan_bw_interference_bitmap);
+		break;
+	default:
+		ath12k_warn(ab, "Received invalid tag 0x%x for WMI DCS interference in subtlvs\n",
+			    tag);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int ath12k_wmi_dcs_interference_event_parser(struct ath12k_base *ab,
+						    u16 tag, u16 len,
+						    const void *ptr, void *data)
+{
+	int ret = 0;
+
+	switch (tag) {
+	case WMI_TAG_DCS_INTERFERENCE_EVENT:
+		/* Fixed param should already be processed */
+		break;
+	case WMI_TAG_ARRAY_STRUCT:
+		ret = ath12k_wmi_tlv_iter(ab, ptr, len,
+					  ath12k_wmi_incumbent_signal_interference_subtlv_parser,
+					  data);
+		break;
+	default:
+		ath12k_warn(ab, "Received invalid tag 0x%x for WMI DCS interference event\n",
+			    tag);
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static bool
+ath12k_wmi_validate_interference_info(struct ath12k *ar,
+				      struct ath12k_wmi_incumbent_signal_interference_arg *info)
+{
+	switch (info->chan_width) {
+	case WMI_CHAN_WIDTH_20:
+		if (info->chan_bw_interference_bitmap > ATH12K_WMI_DCS_SEG_PRI20) {
+			ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+				   "DCS interference event received with wrong chan width bmap 0x%x for 20 MHz",
+				   info->chan_bw_interference_bitmap);
+			return false;
+		}
+		break;
+	case WMI_CHAN_WIDTH_40:
+		if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
+							 ATH12K_WMI_DCS_SEG_SEC20)) {
+			ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+				   "DCS interference event received with wrong chan width bmap 0x%x for 40 MHz",
+				   info->chan_bw_interference_bitmap);
+			return false;
+		}
+		break;
+	case WMI_CHAN_WIDTH_80:
+		if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
+							 ATH12K_WMI_DCS_SEG_SEC20 |
+							 ATH12K_WMI_DCS_SEG_SEC40)) {
+			ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+				   "DCS interference event received with wrong chan width bmap 0x%x for 80 MHz",
+				   info->chan_bw_interference_bitmap);
+			return false;
+		}
+		break;
+	case WMI_CHAN_WIDTH_160:
+		if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
+							 ATH12K_WMI_DCS_SEG_SEC20 |
+							 ATH12K_WMI_DCS_SEG_SEC40 |
+							 ATH12K_WMI_DCS_SEG_SEC80)) {
+			ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+				   "DCS interference event received with wrong chan width bmap 0x%x for 160 MHz",
+				   info->chan_bw_interference_bitmap);
+			return false;
+		}
+		break;
+	case WMI_CHAN_WIDTH_320:
+		if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
+							 ATH12K_WMI_DCS_SEG_SEC20 |
+							 ATH12K_WMI_DCS_SEG_SEC40 |
+							 ATH12K_WMI_DCS_SEG_SEC80 |
+							 ATH12K_WMI_DCS_SEG_SEC160)) {
+			ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+				   "DCS interference event received with wrong chan width bmap 0x%x for 320 MHz",
+				   info->chan_bw_interference_bitmap);
+			return false;
+		}
+		break;
+	default:
+		ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+			   "DCS interference event received with unknown channel width %u",
+			   info->chan_width);
+		return false;
+	}
+	return true;
+}
+
+static u32
+ath12k_wmi_transform_interference_bitmap(int input_bitmap,
+					 struct cfg80211_chan_def *chandef)
+{
+	u16 output_bits[ATH12K_MAX_20MHZ_SEGMENTS] = {};
+	u16 input_bits[ATH12K_MAX_20MHZ_SEGMENTS] = {};
+	u32 start_freq, segment_freq;
+	int primary_index = -1;
+	u32 output_bitmap = 0;
+	u16 num_sub_chans;
+	int bandwidth;
+
+	bandwidth = nl80211_chan_width_to_mhz(chandef->width);
+	if (bandwidth < 0)
+		return 0;
+
+	/*
+	 * Firmware reports bit 0 as primary 20 MHz irrespective of absolute
+	 * frequency position. Convert to standardized lowest-to-highest 20 MHz
+	 * ordering expected by cfg80211/mac80211 userspace consumers.
+	 */
+	num_sub_chans = bandwidth / 20;
+	start_freq = (chandef->center_freq1 - bandwidth / 2) + 10;
+
+	for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; i++) {
+		segment_freq = start_freq + (i * 20);
+		if (segment_freq == chandef->chan->center_freq) {
+			primary_index = i;
+			break;
+		}
+	}
+	if (primary_index == -1)
+		return 0;
+
+	for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; ++i)
+		input_bits[i] = BIT(i) & input_bitmap;
+
+	for (int i = 0; i < num_sub_chans; ++i) {
+		int src = i, dst = i;
+
+		switch (bandwidth) {
+		case 40:
+			if (primary_index == 1)
+				dst = 1 - i;
+			break;
+		case 80:
+			dst = intf_map_80[primary_index][i];
+			break;
+		case 160:
+			dst = intf_map_160[primary_index][i];
+			break;
+		case 320:
+			dst = intf_map_320[primary_index][i];
+			break;
+		}
+		output_bits[dst] = input_bits[src];
+	}
+
+	for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; ++i)
+		output_bitmap |= output_bits[i] ? BIT(i) : 0;
+
+	return output_bitmap;
+}
+
+static void
+ath12k_wmi_process_incumbent_signal_interference_evt(struct ath12k_base *ab,
+						     struct sk_buff *skb,
+						     const struct ath12k_wmi_intf_arg *intf_arg)
+{
+	struct ath12k_wmi_incumbent_signal_interference_arg info = {};
+	struct ath12k_incumbent_signal_interference *incumbent;
+	struct ath12k_mac_get_any_chanctx_conf_arg arg;
+	u32 transformed_intf_bitmap;
+	struct ieee80211_hw *hw;
+	struct ath12k *ar;
+	int ret;
+
+	guard(rcu)();
+
+	ar = ath12k_mac_get_ar_by_pdev_id(ab, intf_arg->pdev_id);
+	if (!ar) {
+		ath12k_warn(ab, "incumbent signal interference detected on invalid pdev %d\n",
+			    intf_arg->pdev_id);
+		return;
+	}
+	if (!ar->supports_6ghz) {
+		ath12k_warn(ab, "pdev does not support 6 GHz, dropping DCS interference event\n");
+		return;
+	}
+
+	incumbent = &ar->incumbent_signal_interference;
+	spin_lock_bh(&ar->data_lock);
+	if (incumbent->handling_in_progress) {
+		spin_unlock_bh(&ar->data_lock);
+		ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+			   "incumbent signal interference handling ongoing, dropping DCS interference event");
+		return;
+	}
+	spin_unlock_bh(&ar->data_lock);
+
+	ret = ath12k_wmi_tlv_iter(ab, skb->data, skb->len,
+				  ath12k_wmi_dcs_interference_event_parser,
+				  &info);
+	if (ret) {
+		ath12k_warn(ab,
+			    "failed to parse incumbent signal interference TLV. Error %d\n",
+			    ret);
+		return;
+	}
+
+	if (!ath12k_wmi_validate_interference_info(ar, &info)) {
+		ath12k_warn(ab, "invalid DCS incumbent signal interference TLV - Skipping event");
+		return;
+	}
+
+	arg.ar = ar;
+	arg.chanctx_conf = NULL;
+	hw = ath12k_ar_to_hw(ar);
+	ieee80211_iter_chan_contexts_atomic(hw,
+					    ath12k_mac_get_any_chanctx_conf_iter,
+					    &arg);
+	if (!arg.chanctx_conf) {
+		ath12k_warn(ab, "failed to find valid chanctx_conf in incumbent signal intf detected event\n");
+		return;
+	}
+
+	if (info.chan_freq != arg.chanctx_conf->def.chan->center_freq) {
+		ath12k_dbg(ab, ATH12K_DBG_WMI,
+			   "dcs interference event received with wrong channel %d (ctx freq %d)",
+			   info.chan_freq, arg.chanctx_conf->def.chan->center_freq);
+		return;
+	}
+
+	spin_lock_bh(&ar->data_lock);
+	incumbent->center_freq = arg.chanctx_conf->def.chan->center_freq;
+	incumbent->width = arg.chanctx_conf->def.width;
+	incumbent->chan_bw_interference_bitmap = info.chan_bw_interference_bitmap;
+	incumbent->handling_in_progress = true;
+	spin_unlock_bh(&ar->data_lock);
+	transformed_intf_bitmap =
+		ath12k_wmi_transform_interference_bitmap(info.chan_bw_interference_bitmap,
+							 &arg.chanctx_conf->def);
+	ath12k_dbg(ab, ATH12K_DBG_WMI,
+		   "incumbent signal interference bitmap 0x%x (transformed 0x%x)\n",
+		   info.chan_bw_interference_bitmap, transformed_intf_bitmap);
+	cfg80211_incumbent_signal_notify(hw->wiphy,
+					 &arg.chanctx_conf->def,
+					 transformed_intf_bitmap,
+					 GFP_ATOMIC);
+}
+
+static void
+ath12k_wmi_dcs_interference_event(struct ath12k_base *ab,
+				  struct sk_buff *skb)
+{
+	const struct ath12k_wmi_dcs_interference_ev_fixed_params *dcs_intf_ev;
+	struct ath12k_wmi_intf_arg dcs_intf_arg;
+	const struct wmi_tlv *tlv;
+	u16 tlv_tag;
+	u8 *ptr;
+
+	if (skb->len < (sizeof(*dcs_intf_ev) + TLV_HDR_SIZE)) {
+		ath12k_warn(ab, "DCS interference event is of incorrect length\n");
+		return;
+	}
+
+	ptr = skb->data;
+	tlv = (struct wmi_tlv *)ptr;
+	tlv_tag = le32_get_bits(tlv->header, WMI_TLV_TAG);
+	ptr += sizeof(*tlv);
+
+	if (tlv_tag != WMI_TAG_DCS_INTERFERENCE_EVENT) {
+		ath12k_warn(ab, "DCS interference event received with wrong tag\n");
+		return;
+	}
+
+	dcs_intf_ev = (struct ath12k_wmi_dcs_interference_ev_fixed_params *)ptr;
+
+	dcs_intf_arg.interference_type =
+		le32_to_cpu(dcs_intf_ev->interference_type);
+	dcs_intf_arg.pdev_id = le32_to_cpu(dcs_intf_ev->pdev_id);
+
+	if (dcs_intf_arg.interference_type ==
+	    ATH12K_WMI_DCS_INCUMBENT_SIGNAL_INTERFERENCE) {
+		ath12k_dbg(ab, ATH12K_DBG_WMI,
+			   "incumbent signal interference (Type %u) detected on pdev %u.",
+			   dcs_intf_arg.interference_type,
+			   dcs_intf_arg.pdev_id);
+		ath12k_wmi_process_incumbent_signal_interference_evt(ab, skb,
+								     &dcs_intf_arg);
+	}
+}
+
 static void
 ath12k_wmi_process_csa_switch_count_event(struct ath12k_base *ab,
 					  const struct ath12k_wmi_pdev_csa_event *ev,
@@ -9961,6 +10347,9 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb)
 	case WMI_OBSS_COLOR_COLLISION_DETECTION_EVENTID:
 		ath12k_wmi_obss_color_collision_event(ab, skb);
 		break;
+	case WMI_DCS_INTERFERENCE_EVENTID:
+		ath12k_wmi_dcs_interference_event(ab, skb);
+		break;
 	/* add Unsupported events (rare) here */
 	case WMI_TBTTOFFSET_EXT_UPDATE_EVENTID:
 	case WMI_PEER_OPER_MODE_CHANGE_EVENTID:
diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
index c644604c1426..d74f7fca7678 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.h
+++ b/drivers/net/wireless/ath/ath12k/wmi.h
@@ -2015,7 +2015,7 @@ enum wmi_tlv_tag {
 	WMI_TAG_VDEV_CH_POWER_INFO,
 	WMI_TAG_MLO_LINK_SET_ACTIVE_CMD = 0x3BE,
 	WMI_TAG_EHT_RATE_SET = 0x3C4,
-	WMI_TAG_DCS_AWGN_INT_TYPE = 0x3C5,
+	WMI_TAG_DCS_INCUMBENT_SIGNAL_INTERFERENCE_TYPE = 0x3C5,
 	WMI_TAG_MLO_TX_SEND_PARAMS,
 	WMI_TAG_MLO_PARTNER_LINK_PARAMS,
 	WMI_TAG_MLO_PARTNER_LINK_PARAMS_PEER_ASSOC,
@@ -4535,6 +4535,62 @@ struct ath12k_wmi_pdev_radar_event {
 	a_sle32 sidx;
 } __packed;
 
+#define ATH12K_WMI_DCS_INCUMBENT_SIGNAL_INTERFERENCE	0x04
+
+struct ath12k_wmi_dcs_interference_ev_fixed_params {
+	__le32 interference_type;
+	__le32 pdev_id;
+} __packed;
+
+struct ath12k_wmi_incumbent_signal_interference_params {
+	__le32 chan_width;
+	__le32 chan_freq;
+	__le32 center_freq0;
+	__le32 center_freq1;
+	__le32 chan_bw_interference_bitmap;
+} __packed;
+
+struct ath12k_wmi_incumbent_signal_interference_arg {
+	u32 chan_width;
+	u32 chan_freq;
+	u32 center_freq0;
+	u32 center_freq1;
+	u32 chan_bw_interference_bitmap;
+};
+
+struct ath12k_wmi_intf_arg {
+	u32 interference_type;
+	u32 pdev_id;
+};
+
+enum ath12k_wmi_dcs_interference_chan_segment {
+	/*
+	 * Firmware reports interference bitmap in primary-based order.
+	 * Bit 0 is the primary 20 MHz, bit 1 is the adjacent 20 MHz within
+	 * the primary 40 MHz. Bits 2-3 cover the secondary 40 MHz, bits 4-7
+	 * cover the secondary 80 MHz, and bits 8-15 cover the secondary 160 MHz.
+	 */
+	ATH12K_WMI_DCS_SEG_PRI20                 = 0x1,
+	ATH12K_WMI_DCS_SEG_SEC20                 = 0x2,
+	ATH12K_WMI_DCS_SEG_SEC40_LOW             = 0x4,
+	ATH12K_WMI_DCS_SEG_SEC40_UP              = 0x8,
+	ATH12K_WMI_DCS_SEG_SEC40                 = 0xC,
+	ATH12K_WMI_DCS_SEG_SEC80_LOW             = 0x10,
+	ATH12K_WMI_DCS_SEG_SEC80_LOW_UP          = 0x20,
+	ATH12K_WMI_DCS_SEG_SEC80_UP_LOW          = 0x40,
+	ATH12K_WMI_DCS_SEG_SEC80_UP              = 0x80,
+	ATH12K_WMI_DCS_SEG_SEC80                 = 0xF0,
+	ATH12K_WMI_DCS_SEG_SEC160_LOW            = 0x0100,
+	ATH12K_WMI_DCS_SEG_SEC160_LOW_UP         = 0x0200,
+	ATH12K_WMI_DCS_SEG_SEC160_LOW_UP_UP      = 0x0400,
+	ATH12K_WMI_DCS_SEG_SEC160_LOW_UP_UP_UP   = 0x0800,
+	ATH12K_WMI_DCS_SEG_SEC160_UP_LOW_LOW_LOW = 0x1000,
+	ATH12K_WMI_DCS_SEG_SEC160_UP_LOW_LOW     = 0x2000,
+	ATH12K_WMI_DCS_SEG_SEC160_UP_LOW         = 0x4000,
+	ATH12K_WMI_DCS_SEG_SEC160_UP             = 0x8000,
+	ATH12K_WMI_DCS_SEG_SEC160                = 0xFF00,
+};
+
 struct wmi_pdev_temperature_event {
 	/* temperature value in Celsius degree */
 	a_sle32 temp;
-- 
2.34.1


^ permalink raw reply related

* [PATCH ath-next v2 0/2] wifi: ath12k: Add support for handling incumbent signal interference in 6 GHz
From: Amith A @ 2026-05-11  4:02 UTC (permalink / raw)
  To: ath12k; +Cc: linux-wireless, amith.a

This patch series adds the implementation of handling of interferences
due to incumbent signals in 6 GHz channels. When an interference is
detected, the firmware indicates it to the host using the
WMI_DCS_INTERFERENCE_EVENT.

The driver is expected to parse the new WMI event to retrieve the
interference information, validate the interference detected channel and
bitmap, and indicate the interference to mac80211, which then notifies
this interference to the userspace.
---
Changes in v2:
    - Added an explicit len check in sub-TLV parser before accessing info.
---
Aishwarya R (2):
  wifi: ath12k: Add support for handling incumbent signal interference
    in 6 GHz
  wifi: ath12k: Add debugfs support to simulate incumbent signal
    interference

 drivers/net/wireless/ath/ath12k/core.h    |   8 +
 drivers/net/wireless/ath/ath12k/debugfs.c |  46 +++
 drivers/net/wireless/ath/ath12k/mac.c     |  46 +++
 drivers/net/wireless/ath/ath12k/wmi.c     | 425 ++++++++++++++++++++++
 drivers/net/wireless/ath/ath12k/wmi.h     |  72 +++-
 5 files changed, 596 insertions(+), 1 deletion(-)


base-commit: e12d2d3983acb150fd987d19ec6a2a530da110df
-- 
2.34.1


^ permalink raw reply

* Re: [PATCH ath-next 1/2] wifi: ath12k: Add support for handling incumbent signal interference in 6 GHz
From: Amith A @ 2026-05-11  3:51 UTC (permalink / raw)
  To: Rameshkumar Sundaram, ath12k
  Cc: linux-wireless, Aishwarya R, Hari Chandrakanthan
In-Reply-To: <6ebbe7ef-b867-4d5f-838c-7f6224e38891@oss.qualcomm.com>



On 5/8/2026 11:43 AM, Rameshkumar Sundaram wrote:
> On 5/5/2026 8:08 PM, Amith A wrote:
>> From: Aishwarya R <aishwarya.r@oss.qualcomm.com>
>>
>> When incumbent signal interference is detected by an AP/mesh interface
>> operating in the 6 GHz band, as mandated by the FCC, it is expected to
>> vacate the affected channels. The firmware indicates the interference to
>> the host using the WMI_DCS_INTERFERENCE_EVENT.
>>
>> To handle the new WMI event, first parse it to retrieve the interference
>> information. Next, validate the interference-detected channel and
>> the interference bitmap. The interference bitmap received from the
>> firmware uses a mapping where bit 0 corresponds to the primary
>> 20 MHz segment, regardless of its position within the operating
>> bandwidth. Bit 1 represents the next adjacent 20 MHz segment, bit 2
>> the lower 20 MHz segment of the adjacent 40 MHz segment, and so
>> on, progressing sequentially across the bandwidth. However, for
>> userspace
>> consumption via mac80211, this bitmap must be transformed into a
>> standardized format such that each bit position directly maps to the
>> corresponding sub-channel index within the operating bandwidth.
>> Finally, indicate the transformed interference bitmap to mac80211, which
>> then notifies userspace of the interference. Once the incumbent signal
>> interference is detected, firmware suspends TX internally on the
>> affected
>> operating channel while userspace decides the mitigation action.
>> Userspace
>> is expected to trigger a channel switch or bandwidth reduction to
>> mitigate
>> the interference. Also, add a flag handling_in_progress to indicate that
>> handling of interference is in progress. Set it to true after
>> indicating to mac80211 about the interference. Reset the flag to false
>> after the operating channel is switched by userspace. This prevents
>> processing any further interference events when there is already a
>> previous event being handled. Hence, further events are processed only
>> after a channel switch request is received from userspace for the
>> previous event.
>>
>> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.5-01651-QCAHKSWPL_SILICONZ-1
>>
>> Signed-off-by: Aishwarya R <aishwarya.r@oss.qualcomm.com>
>> Co-developed-by: Hari Chandrakanthan <quic_haric@quicinc.com>
>> Signed-off-by: Hari Chandrakanthan <quic_haric@quicinc.com>
>> Signed-off-by: Amith A <amith.a@oss.qualcomm.com>
>> ---
>>   drivers/net/wireless/ath/ath12k/core.h |   8 +
>>   drivers/net/wireless/ath/ath12k/mac.c  |  46 +++
>>   drivers/net/wireless/ath/ath12k/wmi.c  | 382 +++++++++++++++++++++++++
>>   drivers/net/wireless/ath/ath12k/wmi.h  |  58 +++-
>>   4 files changed, 493 insertions(+), 1 deletion(-)
>>
>
> { ... }
>
>
>> +static int
>> +ath12k_wmi_incumbent_signal_interference_subtlv_parser(struct
>> ath12k_base *ab,
>> +                               u16 tag, u16 len,
>> +                               const void *ptr,
>> +                               void *data)
>> +{
>> +    const struct ath12k_wmi_incumbent_signal_interference_params *info;
>> +    struct ath12k_wmi_incumbent_signal_interference_arg *arg = data;
>> +
>> +    switch (tag) {
>> +    case WMI_TAG_DCS_INCUMBENT_SIGNAL_INTERFERENCE_TYPE:
>> +        info = ptr;
>> +
>
> should we validate len before accessing info ? or may be add an entry
> for WMI_TAG_DCS_INCUMBENT_SIGNAL_INTERFERENCE_TYPE in
> ath12k_wmi_tlv_policies so that ath12k_wmi_tlv_iter() can take care of
> the validation.
Will add an explicit len < sizeof(*info) check in the parser before
accessing info.
>
>> +        arg->chan_width = le32_to_cpu(info->chan_width);
>> +        arg->chan_freq = le32_to_cpu(info->chan_freq);
>> +        arg->center_freq0 = le32_to_cpu(info->center_freq0);
>> +        arg->center_freq1 = le32_to_cpu(info->center_freq1);
>> +        arg->chan_bw_interference_bitmap =
>> +            le32_to_cpu(info->chan_bw_interference_bitmap);
>> +
>> +        ath12k_dbg(ab, ATH12K_DBG_WMI,
>> +               "incumbent signal interference chan width %u freq %u
>> center_freq0 %u center_freq1 %u bitmap 0x%x\n",
>> +               arg->chan_width, arg->chan_freq,
>> +               arg->center_freq0, arg->center_freq1,
>> +               arg->chan_bw_interference_bitmap);
>> +        break;
>> +    default:
>> +        ath12k_warn(ab, "Received invalid tag 0x%x for WMI DCS
>> interference in subtlvs\n",
>> +                tag);
>> +        return -EINVAL;
>> +    }
>> +
>> +    return 0;
>> +}
>
> -- 
> Ramesh


^ permalink raw reply

* Re: [syzbot] [wireless?] WARNING in ieee80211_sta_current_bw
From: Lachlan Hodges @ 2026-05-11  2:22 UTC (permalink / raw)
  To: syzbot; +Cc: johannes, linux-kernel, linux-wireless, netdev, syzkaller-bugs
In-Reply-To: <6a00ff4e.170a0220.1c0296.021e.GAE@google.com>

> WARNING: ./include/net/mac80211.h:8114 at ieee80211_chan_width_to_rx_bw include/net/mac80211.h:8114 [inline], CPU#1: syz.4.4769/22510
> WARNING: ./include/net/mac80211.h:8114 at ieee80211_sta_current_bw_tx_to_sta net/mac80211/sta_info.c:3719 [inline], CPU#1: syz.4.4769/22510
> WARNING: ./include/net/mac80211.h:8114 at ieee80211_sta_current_bw+0x36d/0x510 net/mac80211/sta_info.c:3745, CPU#1: syz.4.4769/22510
> Modules linked in:
> CPU: 1 UID: 0 PID: 22510 Comm: syz.4.4769 Not tainted syzkaller #0 PREEMPT(full) 
> Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
> RIP: 0010:ieee80211_chan_width_to_rx_bw include/net/mac80211.h:8114 [inline]
> RIP: 0010:ieee80211_sta_current_bw_tx_to_sta net/mac80211/sta_info.c:3719 [inline]
> RIP: 0010:ieee80211_sta_current_bw+0x36d/0x510 net/mac80211/sta_info.c:3745
> Code: 00 00 00 eb 49 41 83 fe 05 74 30 41 83 fe 0d 75 13 e8 47 8f af f6 b8 04 00 00 00 eb 31 e8 3b 8f af f6 eb 28 e8 34 8f af f6 90 <0f> 0b 90 eb 1d e8 29 8f af f6 b8 02 00 00 00 eb 13 e8 1d 8f af f6
> RSP: 0018:ffffc90006f4eed8 EFLAGS: 00010283
> RAX: ffffffff8b161cfc RBX: 1ffff1100d1da030 RCX: 0000000000080000
> RDX: ffffc9000e5d2000 RSI: 0000000000000e31 RDI: 0000000000000e32
> RBP: 0000000000000004 R08: ffff888054ad5c40 R09: 0000000000000007
> R10: 000000000000000d R11: 0000000000000002 R12: ffff888068ed0180
> R13: dffffc0000000000 R14: 0000000000000007 R15: 0000000000000000
> FS:  00007fe58f5f66c0(0000) GS:ffff888125389000(0000) knlGS:0000000000000000
> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 000000110c2c8823 CR3: 0000000038486000 CR4: 00000000003526f0

This looks to be 10MHz given R14 = 7 which seems to be the operand
being compared I think. The 2 patches I sent the other week should
fix this occuring for any S1G bandwidths, not sure about 5 and 10MHz.

Atleast for this situation, it would be the same - we don't wanna
recalc the mindef for 5 and 10MHz since the mindef isn't recalculated
for 5/10MHz like S1G. But then I'm not sure the S1G workaround for
ieee80211_sta_init_nss_bw_capa since maybe nss might be greater than
1 for 5/10MHz?

lachlan

^ permalink raw reply

* RE: [PATCH] wifi: rtw88: add quirk to disable deep LPS for ASUS VivoBook X515JA
From: Ping-Ke Shih @ 2026-05-11  1:16 UTC (permalink / raw)
  To: Gabriel Vinícius da Maia, linux-wireless@vger.kernel.org
In-Reply-To: <CAC1kGwHi0AyV+kxjbRQE4TKvAc-3xq+uHUufCjfAnkfF8j=Y4g@mail.gmail.com>

Gabriel Vinícius da Maia <mailto:gabriel_v_maia@estudante.sesisenai.org.br> wrote:
> I was unable to test the patch in runtime. Building the module against
> my running kernel (6.19) failed due to version mismatch with the tree
> (7.1-rc2), specifically the kmalloc_obj() symbol introduced in the
> newer version.

Did you mean the patch on 6.19 works fine to you?

> 
> I then compiled the full kernel from the tree, but the resulting
> build lacked several modules required by Fedora 43 to boot properly,
> causing the system to hang during initialization.
> 
> However, the patch follows the same logic as commit b2bf9d61e14a,
> which disables deep LPS for an HP laptop with the same RTL8821CE
> chip and identical error messages. The dmesg output from my system
> shows the same pattern:
> 
>   rtw88_8821ce 0000:01:00.0: firmware failed to leave lps state
>   rtw88_8821ce 0000:01:00.0: failed to send h2c command

Maybe you can try to turn off entirely power save by
  iw wlan0 set power save off

But I wonder if these messages can affect your daily use?

> 
> I also investigated whether enabling FW_FEATURE_LPS_C2H would be a
> more general fix, but inspection of rtw8821c_fw.bin (version 24.11.0)
> shows the bit is not set and FW_FEATURE_SIG is also absent, causing
> the driver to discard the feature report entirely.

If FW_FEATURE_LPS_C2H is not set, it goes into 
__rtw_fw_leave_lps_check_reg(). Maybe you can try to enlarge the timeout
time or retry count. 



^ permalink raw reply

* [PATCH] wifi: mt76: disable rx napi before queue cleanup
From: Ruslan Isaev @ 2026-05-10 23:58 UTC (permalink / raw)
  To: linux-wireless
  Cc: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
	Sean Wang

mt76_dma_cleanup() already disables tx napi before deleting it, but
it still removes rx napi instances while they can remain enabled on the
normal device remove path. On mt7915 this triggers a warning.

Disable each rx napi instance before netif_napi_del() and page pool
destruction. This fixes repeated warnings on rmmod mt7915e on
mt7915e/mt7981b.

Signed-off-by: Ruslan Isaev <legale.legale@gmail.com>
---
 drivers/net/wireless/mediatek/mt76/dma.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c
index f8c2fe5f2..12183142f 100644
--- a/drivers/net/wireless/mediatek/mt76/dma.c
+++ b/drivers/net/wireless/mediatek/mt76/dma.c
@@ -1189,6 +1189,7 @@ void mt76_dma_cleanup(struct mt76_dev *dev)
 	mt76_for_each_q_rx(dev, i) {
 		struct mt76_queue *q = &dev->q_rx[i];
 
+		napi_disable(&dev->napi[i]);
 		netif_napi_del(&dev->napi[i]);
 		mt76_dma_rx_cleanup(dev, q);
 
-- 
2.39.5


^ permalink raw reply related

* linux-wireless+subscribe@vger.kernel.org
From: Isaev Ruslan @ 2026-05-10 23:11 UTC (permalink / raw)
  To: linux-wireless

linux-wireless+subscribe@vger.kernel.org

^ permalink raw reply

* [syzbot] [wireless?] WARNING in ieee80211_sta_current_bw
From: syzbot @ 2026-05-10 21:57 UTC (permalink / raw)
  To: johannes, linux-kernel, linux-wireless, netdev, syzkaller-bugs

Hello,

syzbot found the following issue on:

HEAD commit:    2281958e6007 Merge tag 'wireless-next-2026-05-06' of https..
git tree:       net-next
console output: https://syzkaller.appspot.com/x/log.txt?x=1513e696580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=f2b487b72ffad035
dashboard link: https://syzkaller.appspot.com/bug?extid=e2a0da81361722f4df3b
compiler:       Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8

Unfortunately, I don't have any reproducer for this issue yet.

Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/4df17f60254b/disk-2281958e.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/1e93157edd25/vmlinux-2281958e.xz
kernel image: https://storage.googleapis.com/syzbot-assets/60dbf6a0a81c/bzImage-2281958e.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+e2a0da81361722f4df3b@syzkaller.appspotmail.com

------------[ cut here ]------------
1
WARNING: ./include/net/mac80211.h:8114 at ieee80211_chan_width_to_rx_bw include/net/mac80211.h:8114 [inline], CPU#1: syz.4.4769/22510
WARNING: ./include/net/mac80211.h:8114 at ieee80211_sta_current_bw_tx_to_sta net/mac80211/sta_info.c:3719 [inline], CPU#1: syz.4.4769/22510
WARNING: ./include/net/mac80211.h:8114 at ieee80211_sta_current_bw+0x36d/0x510 net/mac80211/sta_info.c:3745, CPU#1: syz.4.4769/22510
Modules linked in:
CPU: 1 UID: 0 PID: 22510 Comm: syz.4.4769 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
RIP: 0010:ieee80211_chan_width_to_rx_bw include/net/mac80211.h:8114 [inline]
RIP: 0010:ieee80211_sta_current_bw_tx_to_sta net/mac80211/sta_info.c:3719 [inline]
RIP: 0010:ieee80211_sta_current_bw+0x36d/0x510 net/mac80211/sta_info.c:3745
Code: 00 00 00 eb 49 41 83 fe 05 74 30 41 83 fe 0d 75 13 e8 47 8f af f6 b8 04 00 00 00 eb 31 e8 3b 8f af f6 eb 28 e8 34 8f af f6 90 <0f> 0b 90 eb 1d e8 29 8f af f6 b8 02 00 00 00 eb 13 e8 1d 8f af f6
RSP: 0018:ffffc90006f4eed8 EFLAGS: 00010283
RAX: ffffffff8b161cfc RBX: 1ffff1100d1da030 RCX: 0000000000080000
RDX: ffffc9000e5d2000 RSI: 0000000000000e31 RDI: 0000000000000e32
RBP: 0000000000000004 R08: ffff888054ad5c40 R09: 0000000000000007
R10: 000000000000000d R11: 0000000000000002 R12: ffff888068ed0180
R13: dffffc0000000000 R14: 0000000000000007 R15: 0000000000000000
FS:  00007fe58f5f66c0(0000) GS:ffff888125389000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000000110c2c8823 CR3: 0000000038486000 CR4: 00000000003526f0
Call Trace:
 <TASK>
 ieee80211_chan_bw_change+0x459/0x740 net/mac80211/chan.c:719
 _ieee80211_recalc_chanctx_min_def net/mac80211/chan.c:758 [inline]
 ieee80211_recalc_chanctx_min_def+0x36/0x70 net/mac80211/chan.c:770
 ieee80211_recalc_min_chandef+0x491/0x580 net/mac80211/util.c:2501
 sta_info_insert_finish net/mac80211/sta_info.c:946 [inline]
 sta_info_insert_rcu+0x15da/0x26b0 net/mac80211/sta_info.c:1029
 sta_info_insert+0x16/0xc0 net/mac80211/sta_info.c:1034
 ieee80211_add_station+0x4c7/0x710 net/mac80211/cfg.c:2666
 rdev_add_station+0xfc/0x290 net/wireless/rdev-ops.h:201
 nl80211_new_station+0x1cab/0x2130 net/wireless/nl80211.c:9490
 genl_family_rcv_msg_doit+0x22a/0x330 net/netlink/genetlink.c:1114
 genl_family_rcv_msg net/netlink/genetlink.c:1194 [inline]
 genl_rcv_msg+0x61c/0x7a0 net/netlink/genetlink.c:1209
 netlink_rcv_skb+0x232/0x4b0 net/netlink/af_netlink.c:2551
 genl_rcv+0x28/0x40 net/netlink/genetlink.c:1218
 netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
 netlink_unicast+0x75c/0x8e0 net/netlink/af_netlink.c:1345
 netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1895
 sock_sendmsg_nosec net/socket.c:787 [inline]
 __sock_sendmsg net/socket.c:802 [inline]
 ____sys_sendmsg+0x972/0x9f0 net/socket.c:2698
 ___sys_sendmsg+0x2a5/0x360 net/socket.c:2752
 __sys_sendmsg net/socket.c:2784 [inline]
 __do_sys_sendmsg net/socket.c:2789 [inline]
 __se_sys_sendmsg net/socket.c:2787 [inline]
 __x64_sys_sendmsg+0x1bd/0x2a0 net/socket.c:2787
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fe59139cdd9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fe58f5f6028 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 00007fe591616090 RCX: 00007fe59139cdd9
RDX: 0000000000000000 RSI: 0000200000001080 RDI: 0000000000000006
RBP: 00007fe591432d69 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fe591616128 R14: 00007fe591616090 R15: 00007fff2b0f3978
 </TASK>


---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.

syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.

If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title

If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)

If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report

If you want to undo deduplication, reply with:
#syz undup

^ permalink raw reply

* [syzbot] [wireless?] WARNING in mac80211_hwsim_tx (2)
From: syzbot @ 2026-05-10 21:02 UTC (permalink / raw)
  To: johannes, linux-kernel, linux-wireless, netdev, syzkaller-bugs

Hello,

syzbot found the following issue on:

HEAD commit:    adc1e5c6203c Merge tag 'efi-fixes-for-v7.1-1' of git://git..
git tree:       upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=1369cd06580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=59da38148f3a3d24
dashboard link: https://syzkaller.appspot.com/bug?extid=435fdb053cf98bfa5778
compiler:       gcc (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44

Unfortunately, I don't have any reproducer for this issue yet.

Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/e1ec8b63537e/disk-adc1e5c6.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/2030fc4d0035/vmlinux-adc1e5c6.xz
kernel image: https://storage.googleapis.com/syzbot-assets/a50679f39f63/bzImage-adc1e5c6.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+435fdb053cf98bfa5778@syzkaller.appspotmail.com

------------[ cut here ]------------
sp->magic != HWSIM_STA_MAGIC
WARNING: drivers/net/wireless/virtual/mac80211_hwsim.c:265 at hwsim_check_sta_magic drivers/net/wireless/virtual/mac80211_hwsim.c:265 [inline], CPU#1: syz.3.8603/32057
WARNING: drivers/net/wireless/virtual/mac80211_hwsim.c:265 at hwsim_check_sta_magic drivers/net/wireless/virtual/mac80211_hwsim.c:262 [inline], CPU#1: syz.3.8603/32057
WARNING: drivers/net/wireless/virtual/mac80211_hwsim.c:265 at mac80211_hwsim_tx+0x2085/0x2b10 drivers/net/wireless/virtual/mac80211_hwsim.c:2213, CPU#1: syz.3.8603/32057
Modules linked in:
CPU: 1 UID: 0 PID: 32057 Comm: syz.3.8603 Tainted: G             L      syzkaller #0 PREEMPT(full) 
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
RIP: 0010:hwsim_check_sta_magic drivers/net/wireless/virtual/mac80211_hwsim.c:265 [inline]
RIP: 0010:hwsim_check_sta_magic drivers/net/wireless/virtual/mac80211_hwsim.c:262 [inline]
RIP: 0010:mac80211_hwsim_tx+0x2085/0x2b10 drivers/net/wireless/virtual/mac80211_hwsim.c:2213
Code: 44 24 20 e8 fd a6 dc fa 48 8d 3d a6 50 b8 09 48 8b 54 24 20 8b 74 24 30 89 d9 67 48 0f b9 3a e9 f7 ec ff ff e8 dc a6 dc fa 90 <0f> 0b 90 e9 a0 e3 ff ff e8 ce a6 dc fa 48 8d bb f1 07 00 00 48 b8
RSP: 0018:ffffc900040eeea0 EFLAGS: 00010283
RAX: 000000000000075e RBX: ffff888083a0eb78 RCX: ffffc90011de4000
RDX: 0000000000080000 RSI: ffffffff872bec44 RDI: ffff8880663e0000
RBP: 0000000000000000 R08: 0000000000000005 R09: 000000006d537749
R10: 0000000000000000 R11: 0000000000000000 R12: ffff888067c460c0
R13: ffff888074694780 R14: ffff8880401231c0 R15: ffff888074694780
FS:  00007f439aedf6c0(0000) GS:ffff888124475000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000200000003540 CR3: 000000003994c000 CR4: 00000000003526f0
Call Trace:
 <TASK>
 drv_tx net/mac80211/driver-ops.h:38 [inline]
 ieee80211_tx_frags+0x5c9/0xa70 net/mac80211/tx.c:1746
 __ieee80211_tx+0x145/0x5b0 net/mac80211/tx.c:1801
 ieee80211_tx+0x336/0x460 net/mac80211/tx.c:1984
 ieee80211_xmit+0x30f/0x3e0 net/mac80211/tx.c:2076
 __ieee80211_tx_skb_tid_band+0x2c2/0x720 net/mac80211/tx.c:6369
 ieee80211_tx_skb_tid+0x1c1/0x550 net/mac80211/tx.c:6399
 ieee80211_mgmt_tx+0x1326/0x2590 net/mac80211/offchannel.c:1029
 rdev_mgmt_tx net/wireless/rdev-ops.h:767 [inline]
 cfg80211_mlme_mgmt_tx+0x803/0x1600 net/wireless/mlme.c:961
 nl80211_tx_mgmt+0x9f9/0xf30 net/wireless/nl80211.c:14358
 genl_family_rcv_msg_doit+0x214/0x300 net/netlink/genetlink.c:1114
 genl_family_rcv_msg net/netlink/genetlink.c:1194 [inline]
 genl_rcv_msg+0x560/0x800 net/netlink/genetlink.c:1209
 netlink_rcv_skb+0x159/0x420 net/netlink/af_netlink.c:2550
 genl_rcv+0x28/0x40 net/netlink/genetlink.c:1218
 netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline]
 netlink_unicast+0x585/0x850 net/netlink/af_netlink.c:1344
 netlink_sendmsg+0x8b0/0xda0 net/netlink/af_netlink.c:1894
 sock_sendmsg_nosec net/socket.c:787 [inline]
 __sock_sendmsg net/socket.c:802 [inline]
 ____sys_sendmsg+0x9e1/0xb70 net/socket.c:2698
 ___sys_sendmsg+0x190/0x1e0 net/socket.c:2752
 __sys_sendmsg+0x170/0x220 net/socket.c:2784
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x10b/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f4399f9cdd9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f439aedf028 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 00007f439a216090 RCX: 00007f4399f9cdd9
RDX: 0000000028004800 RSI: 0000200000003740 RDI: 0000000000000003
RBP: 00007f439a032d69 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f439a216128 R14: 00007f439a216090 R15: 00007ffff74f7008
 </TASK>


---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.

syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.

If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title

If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)

If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report

If you want to undo deduplication, reply with:
#syz undup

^ permalink raw reply

* [PATCH v2 iwlwifi-next 15/15] wifi: iwlwifi: mld: don't allow softAP with NAN
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

SoftAP in concurrency with NAN is not supported. Update the interface
combinations accordingly.

Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/intel/iwlwifi/mld/mac80211.c   | 17 +++--------------
 1 file changed, 3 insertions(+), 14 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index 49c75d4ee9a6..1106ad651cfe 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -69,11 +69,6 @@ static const struct ieee80211_iface_limit iwl_mld_limits_nan[] = {
 		.max = 1,
 		.types = BIT(NL80211_IFTYPE_NAN),
 	},
-	/* Removed when two channels are permitted */
-	{
-		.max = 1,
-		.types = BIT(NL80211_IFTYPE_AP),
-	},
 };
 
 static const struct ieee80211_iface_combination
@@ -90,19 +85,13 @@ iwl_mld_iface_combinations[] = {
 		.limits = iwl_mld_limits_ap,
 		.n_limits = ARRAY_SIZE(iwl_mld_limits_ap),
 	},
-	/* NAN combinations follow, these exclude P2P */
+	/* NAN combination follow, this excludes P2P and AP */
 	{
 		.num_different_channels = 2,
 		.max_interfaces = 3,
 		.limits = iwl_mld_limits_nan,
-		.n_limits = ARRAY_SIZE(iwl_mld_limits_nan) - 1,
-	},
-	{
-		.num_different_channels = 1,
-		.max_interfaces = 4,
-		.limits = iwl_mld_limits_nan,
 		.n_limits = ARRAY_SIZE(iwl_mld_limits_nan),
-	}
+	},
 };
 
 static const u8 ext_capa_base[IWL_MLD_STA_EXT_CAPA_SIZE] = {
@@ -376,7 +365,7 @@ static void iwl_mac_hw_set_wiphy(struct iwl_mld *mld)
 	} else {
 		/* Do not include NAN combinations */
 		wiphy->n_iface_combinations =
-			ARRAY_SIZE(iwl_mld_iface_combinations) - 2;
+			ARRAY_SIZE(iwl_mld_iface_combinations) - 1;
 	}
 
 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_VHT_IBSS);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 14/15] wifi: iwlwifi: mld: fix NAN max channel switch time unit
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Israel Kozitz, Ilan Peer
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

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

The max_channel_switch_time in wiphy_nan_capa is in microseconds, but
the value was set to 4, which is only 4 microseconds instead of the
intended 4 milliseconds.

Fix by using 4 * USEC_PER_MSEC.

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>
---
 drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index cad10f011072..49c75d4ee9a6 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -298,7 +298,7 @@ static void iwl_mld_hw_set_nan(struct iwl_mld *mld)
 		 NAN_DEV_CAPA_NUM_RX_ANT_MASK);
 
 	/* Maximal channel switch time is 4 msec */
-	hw->wiphy->nan_capa.max_channel_switch_time = 4;
+	hw->wiphy->nan_capa.max_channel_switch_time = 4 * USEC_PER_MSEC;
 
 	hw->wiphy->nan_capa.phy.ht = mld->nvm_data->nan_phy_capa.ht;
 	hw->wiphy->nan_capa.phy.vht = mld->nvm_data->nan_phy_capa.vht;
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 13/15] wifi: iwlwifi: mld: Do not declare NAN support for Extended Key ID
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ilan Peer
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

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

Do not declare support for Extended Key ID for NAN, as defined in section
7.4 in the WiFi Aware specification v4.0 (in order to support security
association upgrade).

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

diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index d5deb4a7fab4..cad10f011072 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -299,8 +299,6 @@ static void iwl_mld_hw_set_nan(struct iwl_mld *mld)
 
 	/* Maximal channel switch time is 4 msec */
 	hw->wiphy->nan_capa.max_channel_switch_time = 4;
-	hw->wiphy->nan_capa.dev_capabilities =
-		NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED;
 
 	hw->wiphy->nan_capa.phy.ht = mld->nvm_data->nan_phy_capa.ht;
 	hw->wiphy->nan_capa.phy.vht = mld->nvm_data->nan_phy_capa.vht;
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 12/15] wifi: iwlwifi: mld: Do not declare support for NDPE
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ilan Peer
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

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

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

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

diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index 4ad1d55fd646..d5deb4a7fab4 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -300,8 +300,7 @@ static void iwl_mld_hw_set_nan(struct iwl_mld *mld)
 	/* Maximal channel switch time is 4 msec */
 	hw->wiphy->nan_capa.max_channel_switch_time = 4;
 	hw->wiphy->nan_capa.dev_capabilities =
-		NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
-		NAN_DEV_CAPA_NDPE_SUPPORTED;
+		NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED;
 
 	hw->wiphy->nan_capa.phy.ht = mld->nvm_data->nan_phy_capa.ht;
 	hw->wiphy->nan_capa.phy.vht = mld->nvm_data->nan_phy_capa.vht;
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 11/15] wifi: iwlwifi: mld: Fix number of antennas in NAN capabilities
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ilan Peer
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

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

Instead of hardcoding the number of supported antennas for Tx/Rx, set
them according to hardware capabilities.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index e76421a8a8e6..4ad1d55fd646 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -290,8 +290,12 @@ static void iwl_mld_hw_set_nan(struct iwl_mld *mld)
 				      NAN_OP_MODE_PHY_MODE_HE |
 				      NAN_OP_MODE_160MHZ;
 
-	/* Support 2 antennas for Tx and Rx */
-	hw->wiphy->nan_capa.n_antennas = 0x22;
+	hw->wiphy->nan_capa.n_antennas =
+		(hweight32(hw->wiphy->available_antennas_tx) &
+		 NAN_DEV_CAPA_NUM_TX_ANT_MASK) |
+		((hweight32(hw->wiphy->available_antennas_rx) <<
+		  NAN_DEV_CAPA_NUM_RX_ANT_POS) &
+		 NAN_DEV_CAPA_NUM_RX_ANT_MASK);
 
 	/* Maximal channel switch time is 4 msec */
 	hw->wiphy->nan_capa.max_channel_switch_time = 4;
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 10/15] wifi: iwlwifi: mld: extract NAN capabilities setting to a function
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

There are now quite a lot of capabilities to set, so move it to a
dedicated function, for better clarity.

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/intel/iwlwifi/mld/mac80211.c | 65 ++++++++++---------
 1 file changed, 36 insertions(+), 29 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index cabf47367fda..e76421a8a8e6 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -272,6 +272,38 @@ static void iwl_mac_hw_set_flags(struct iwl_mld *mld)
 	ieee80211_hw_set(hw, TDLS_WIDER_BW);
 }
 
+static void iwl_mld_hw_set_nan(struct iwl_mld *mld)
+{
+	struct ieee80211_hw *hw = mld->hw;
+
+	hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_NAN);
+
+	hw->wiphy->nan_supported_bands = BIT(NL80211_BAND_2GHZ);
+	if (mld->nvm_data->bands[NL80211_BAND_5GHZ].n_channels)
+		hw->wiphy->nan_supported_bands |=
+			BIT(NL80211_BAND_5GHZ);
+
+	hw->wiphy->nan_capa.flags = WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC |
+				    WIPHY_NAN_FLAGS_USERSPACE_DE;
+
+	hw->wiphy->nan_capa.op_mode = NAN_OP_MODE_PHY_MODE_VHT |
+				      NAN_OP_MODE_PHY_MODE_HE |
+				      NAN_OP_MODE_160MHZ;
+
+	/* Support 2 antennas for Tx and Rx */
+	hw->wiphy->nan_capa.n_antennas = 0x22;
+
+	/* Maximal channel switch time is 4 msec */
+	hw->wiphy->nan_capa.max_channel_switch_time = 4;
+	hw->wiphy->nan_capa.dev_capabilities =
+		NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
+		NAN_DEV_CAPA_NDPE_SUPPORTED;
+
+	hw->wiphy->nan_capa.phy.ht = mld->nvm_data->nan_phy_capa.ht;
+	hw->wiphy->nan_capa.phy.vht = mld->nvm_data->nan_phy_capa.vht;
+	hw->wiphy->nan_capa.phy.he = mld->nvm_data->nan_phy_capa.he;
+}
+
 static void iwl_mac_hw_set_wiphy(struct iwl_mld *mld)
 {
 	struct ieee80211_hw *hw = mld->hw;
@@ -334,38 +366,13 @@ static void iwl_mac_hw_set_wiphy(struct iwl_mld *mld)
 
 	wiphy->hw_timestamp_max_peers = 1;
 
+	wiphy->iface_combinations = iwl_mld_iface_combinations;
+
 	if (iwl_mld_nan_supported(mld)) {
-		hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_NAN);
-		hw->wiphy->iface_combinations = iwl_mld_iface_combinations;
-		hw->wiphy->n_iface_combinations =
+		wiphy->n_iface_combinations =
 			ARRAY_SIZE(iwl_mld_iface_combinations);
-
-		hw->wiphy->nan_supported_bands = BIT(NL80211_BAND_2GHZ);
-		if (mld->nvm_data->bands[NL80211_BAND_5GHZ].n_channels)
-			hw->wiphy->nan_supported_bands |=
-				BIT(NL80211_BAND_5GHZ);
-
-		hw->wiphy->nan_capa.flags = WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC |
-					    WIPHY_NAN_FLAGS_USERSPACE_DE;
-
-		hw->wiphy->nan_capa.op_mode = NAN_OP_MODE_PHY_MODE_MASK |
-					      NAN_OP_MODE_80P80MHZ |
-					      NAN_OP_MODE_160MHZ;
-
-		/* Support 2 antenna's for Tx and Rx */
-		hw->wiphy->nan_capa.n_antennas = 0x22;
-
-		/* Maximal channel switch time is 4 msec */
-		hw->wiphy->nan_capa.max_channel_switch_time = 4;
-		hw->wiphy->nan_capa.dev_capabilities =
-			NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
-			NAN_DEV_CAPA_NDPE_SUPPORTED;
-
-		hw->wiphy->nan_capa.phy.ht = mld->nvm_data->nan_phy_capa.ht;
-		hw->wiphy->nan_capa.phy.vht = mld->nvm_data->nan_phy_capa.vht;
-		hw->wiphy->nan_capa.phy.he = mld->nvm_data->nan_phy_capa.he;
+		iwl_mld_hw_set_nan(mld);
 	} else {
-		wiphy->iface_combinations = iwl_mld_iface_combinations;
 		/* Do not include NAN combinations */
 		wiphy->n_iface_combinations =
 			ARRAY_SIZE(iwl_mld_iface_combinations) - 2;
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 09/15] wifi: iwlwifi: mld: use host rate for NAN management frames
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

Frames that are sent to an NMI station are always NAN management frames.
Therefore there is no need to configure TLC for such a station.
Always use host rate for the frames going to that station.

Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/intel/iwlwifi/mld/tx.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c
index 7903ce2b0beb..dec8ecd6b805 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c
@@ -557,10 +557,12 @@ iwl_mld_fill_tx_cmd(struct iwl_mld *mld, struct sk_buff *skb,
 		flags |= IWL_TX_FLAGS_ENCRYPT_DIS;
 
 	/* For data and mgmt packets rate info comes from the fw.
-	 * Only set rate/antenna for injected frames with fixed rate, or
-	 * when no sta is given.
+	 * Only set rate/antenna for:
+	 * - injected frames with fixed rate,
+	 * - when no sta is given.
+	 * - frames that are sent to an NMI sta, which is only used for management.
 	 */
-	if (unlikely(!sta ||
+	if (unlikely(!sta || mld_sta->vif->type == NL80211_IFTYPE_NAN ||
 		     info->control.flags & IEEE80211_TX_CTRL_RATE_INJECT)) {
 		flags |= IWL_TX_FLAGS_CMD_RATE;
 		rate_n_flags = iwl_mld_get_tx_rate_n_flags(mld, info, sta,
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 08/15] wifi: iwlwifi: mld: add peer schedule support
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

From: Johannes Berg <johannes.berg@intel.com>

Add support for NAN peer schedule configuration and update.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Co-developed-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../wireless/intel/iwlwifi/fw/api/mac-cfg.h   | 42 ++++++++++
 .../net/wireless/intel/iwlwifi/mld/mac80211.c |  1 +
 drivers/net/wireless/intel/iwlwifi/mld/mld.c  |  1 +
 drivers/net/wireless/intel/iwlwifi/mld/nan.c  | 84 +++++++++++++++++++
 drivers/net/wireless/intel/iwlwifi/mld/nan.h  |  3 +
 5 files changed, 131 insertions(+)

diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h
index d98c6d991a88..75b477319096 100644
--- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h
+++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h
@@ -76,6 +76,10 @@ enum iwl_mac_conf_subcmd_ids {
 	 * @NAN_SCHEDULE_CMD: &struct iwl_nan_schedule_cmd
 	 */
 	NAN_SCHEDULE_CMD = 0x13,
+	/**
+	 * @NAN_PEER_CMD: &struct iwl_nan_peer_cmd
+	 */
+	NAN_PEER_CMD = 0x14,
 	/**
 	 * @NAN_DW_END_NOTIF: &struct iwl_nan_dw_end_notif
 	 */
@@ -1268,6 +1272,44 @@ struct iwl_nan_schedule_cmd {
 	} __packed channels[NUM_PHY_CTX];
 } __packed; /* NAN_SCHEDULE_CMD_API_S_VER_1 */
 
+/**
+ * struct iwl_nan_peer_cmd - NAN peer command
+ * @nmi_sta_id: NAN management station ID
+ * @sequence_id: NAN Availability attribute sequence ID
+ * @committed_dw_info: committed DW info from the NAN Device
+ *	Capability attribute
+ * @max_channel_switch_time: maximum channel switch time
+ *	(in microseconds); 0 means unavailable
+ * @reserved: (reserved)
+ * @per_phy: per-PHY information for this peer, indexed by PHY ID
+ * @per_phy.availability_map: bitmap of which slots this peer
+ *	is available in on this PHY. 0 indicates the this per-PHY entry
+ *	is unused.
+ * @per_phy.channel_entry: the channel description the peer is using,
+ *	used for comparisons in ULW management
+ * @per_phy.link_id: FW link ID, should be a valid id.
+ * @per_phy.map_id: map ID from peer's NAN Availability attributec
+ * @initial_ulw_size: size of the initial ULW blob
+ * @initial_ulw: initial ULW data from the peer
+ */
+struct iwl_nan_peer_cmd {
+	u8 nmi_sta_id;
+	u8 sequence_id;
+	__le16 committed_dw_info;
+	__le16 max_channel_switch_time;
+	__le16 reserved;
+
+	struct {
+		__le32 availability_map;
+		u8 channel_entry[6];
+		u8 link_id;
+		u8 map_id;
+	} __packed per_phy[NUM_PHY_CTX];
+
+	__le32 initial_ulw_size;
+	u8 initial_ulw[];
+} __packed; /* NAN_PEER_SCHEDULE_CMD_API_S_VER_1 */
+
 /**
  * enum iwl_nan_cluster_notif_flags - flags for the cluster notification
  *
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index 6b4b2683cd1e..cabf47367fda 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -2863,4 +2863,5 @@ const struct ieee80211_ops iwl_mld_hw_ops = {
 	.start_nan = iwl_mld_start_nan,
 	.stop_nan = iwl_mld_stop_nan,
 	.nan_change_conf = iwl_mld_nan_change_config,
+	.nan_peer_sched_changed = iwl_mld_mac802111_nan_peer_sched_changed,
 };
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c
index c038a0cde36b..dfd4798c103a 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c
@@ -237,6 +237,7 @@ static const struct iwl_hcmd_names iwl_mld_mac_conf_names[] = {
 	HCMD_NAME(ROC_CMD),
 	HCMD_NAME(NAN_CFG_CMD),
 	HCMD_NAME(NAN_SCHEDULE_CMD),
+	HCMD_NAME(NAN_PEER_CMD),
 	HCMD_NAME(NAN_DW_END_NOTIF),
 	HCMD_NAME(NAN_JOINED_CLUSTER_NOTIF),
 	HCMD_NAME(MISSED_BEACONS_NOTIF),
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
index 53d39717deab..ceea66c01205 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
@@ -664,3 +664,87 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
 			iwl_mld_rm_vif(mld, vif);
 	}
 }
+
+int iwl_mld_mac802111_nan_peer_sched_changed(struct ieee80211_hw *hw,
+					     struct ieee80211_sta *sta)
+{
+	struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
+	struct ieee80211_nan_peer_sched *sched = sta->nan_sched;
+	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(mld_sta->vif);
+	struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
+	struct iwl_mld_nan_link *nan_link;
+	struct iwl_nan_peer_cmd cmd = {
+		.nmi_sta_id = mld_sta->deflink.fw_id,
+		.sequence_id = sched->seq_id,
+		.committed_dw_info = cpu_to_le16(sched->committed_dw),
+		.max_channel_switch_time = cpu_to_le16(sched->max_chan_switch),
+		.initial_ulw_size = cpu_to_le32(sched->ulw_size),
+		.per_phy[0 ... NUM_PHY_CTX - 1] = {
+			/* unused by FW if availability_map == 0 */
+			.map_id = CFG80211_NAN_INVALID_MAP_ID,
+			.link_id = FW_CTXT_ID_INVALID,
+		},
+		/* .initial_ulw directly provided below by data[1]/len[1] */
+	};
+	struct iwl_host_cmd hcmd = {
+		.id = WIDE_ID(MAC_CONF_GROUP, NAN_PEER_CMD),
+		.data[0] = &cmd,
+		.len[0] = sizeof(cmd),
+		.data[1] = sched->init_ulw,
+		.len[1] = sched->ulw_size,
+		.dataflags[1] = IWL_HCMD_DFL_DUP,
+	};
+
+	for (int i = 0; i < ARRAY_SIZE(sched->maps); i++) {
+		if (sched->maps[i].map_id == CFG80211_NAN_INVALID_MAP_ID)
+			continue;
+
+		BUILD_BUG_ON(ARRAY_SIZE(sched->maps[i].slots) != 32);
+		for (int slot = 0;
+		     slot < ARRAY_SIZE(sched->maps[i].slots);
+		     slot++) {
+			struct ieee80211_chanctx_conf *ctx;
+			struct ieee80211_nan_channel *chan;
+			struct iwl_mld_phy *phy;
+
+			chan = sched->maps[i].slots[slot];
+			if (!chan)
+				continue;
+
+			ctx = chan->chanctx_conf;
+			if (!ctx)
+				continue;
+
+			phy = iwl_mld_phy_from_mac80211(ctx);
+
+			for_each_mld_nan_valid_link(mld_vif, nan_link) {
+				if (nan_link->chanctx == ctx) {
+					cmd.per_phy[phy->fw_id].link_id =
+						nan_link->fw_id;
+					break;
+				}
+			}
+
+			if (WARN_ON(cmd.per_phy[phy->fw_id].link_id ==
+				    FW_CTXT_ID_INVALID))
+				continue;
+
+			/*
+			 * each channel can only appear in one map,
+			 * upper layers enforce that
+			 */
+			if (WARN_ON(cmd.per_phy[phy->fw_id].map_id != CFG80211_NAN_INVALID_MAP_ID &&
+				    cmd.per_phy[phy->fw_id].map_id != sched->maps[i].map_id))
+				continue;
+
+			cmd.per_phy[phy->fw_id].map_id = sched->maps[i].map_id;
+			memcpy(cmd.per_phy[phy->fw_id].channel_entry,
+			       chan->channel_entry,
+			       sizeof(cmd.per_phy[phy->fw_id].channel_entry));
+			cmd.per_phy[phy->fw_id].availability_map |=
+				cpu_to_le32(BIT(slot));
+		}
+	}
+
+	return iwl_mld_send_cmd(mld, &hcmd);
+}
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.h b/drivers/net/wireless/intel/iwlwifi/mld/nan.h
index 933e16c3c274..80e18c4ddb33 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/nan.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.h
@@ -50,4 +50,7 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
 				 struct ieee80211_vif *vif,
 				 u64 changes);
 
+int iwl_mld_mac802111_nan_peer_sched_changed(struct ieee80211_hw *hw,
+					     struct ieee80211_sta *sta);
+
 #endif /* __iwl_mld_nan_h__ */
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 07/15] wifi: iwlwifi: mld: implement NAN peer station management
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

From: Johannes Berg <johannes.berg@intel.com>

Implement peer station management for NAN, i.e. support for adding,
removing, and updating NMI and NDI stations.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Co-developed-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/intel/iwlwifi/mld/mac80211.c |  2 +-
 drivers/net/wireless/intel/iwlwifi/mld/nan.c  | 35 +++++--
 drivers/net/wireless/intel/iwlwifi/mld/sta.c  | 96 +++++++++++++++----
 drivers/net/wireless/intel/iwlwifi/mld/sta.h  |  4 +-
 4 files changed, 109 insertions(+), 28 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index 3c84c6b0faaa..6b4b2683cd1e 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -1841,7 +1841,7 @@ static int iwl_mld_move_sta_state_up(struct iwl_mld *mld,
 		   new_state == IEEE80211_STA_AUTHORIZED) {
 		ret = 0;
 
-		if (!sta->tdls) {
+		if (vif->type == NL80211_IFTYPE_STATION && !sta->tdls) {
 			mld_vif->authorized = true;
 
 			/* Ensure any block due to a non-BSS link is synced */
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
index eba79aca8c06..53d39717deab 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
@@ -464,19 +464,17 @@ static int iwl_mld_nan_link_set_active(struct iwl_mld *mld,
 }
 
 static void iwl_mld_nan_link_remove(struct iwl_mld *mld,
-				    struct iwl_mld_nan_link *nan_link)
+				    struct iwl_mld_nan_link *nan_link,
+				    u32 link_id)
 {
 	struct iwl_link_config_cmd cmd = {
-		.link_id = cpu_to_le32(nan_link->fw_id),
+		.link_id = cpu_to_le32(link_id),
 		.phy_id = cpu_to_le32(FW_CTXT_ID_INVALID),
 	};
 
-	if (WARN_ON_ONCE(nan_link->fw_id == FW_CTXT_ID_INVALID))
-		return;
-
 	iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_REMOVE);
 
-	RCU_INIT_POINTER(mld->fw_id_to_bss_conf[nan_link->fw_id], NULL);
+	RCU_INIT_POINTER(mld->fw_id_to_bss_conf[link_id], NULL);
 	nan_link->fw_id = FW_CTXT_ID_INVALID;
 	nan_link->active = false;
 	nan_link->chanctx = NULL;
@@ -518,6 +516,8 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
 	struct ieee80211_nan_channel **slots = sched_cfg->schedule;
 	bool link_used[ARRAY_SIZE(mld_vif->nan.links)] = {};
 	struct iwl_mld_nan_link *nan_link;
+	unsigned long remove_link_ids = 0;
+	bool added_links = false;
 	bool empty_schedule = true;
 	int ret, i;
 
@@ -588,6 +588,7 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
 
 		/* we have a link, activate it */
 		if (links[i]) {
+			added_links = true;
 			link_used[links[i]->fw_id] = true;
 			iwl_mld_nan_link_set_active(mld, links[i], true);
 		}
@@ -626,14 +627,32 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
 	if (ret)
 		IWL_ERR(mld, "NAN: failed to update schedule (%d)\n", ret);
 
-	/* delete unused links */
+	/* prepare stations for links we'll remove */
 	for_each_mld_nan_valid_link(mld_vif, nan_link) {
 		if (!link_used[nan_link->fw_id]) {
 			iwl_mld_nan_link_set_active(mld, nan_link, false);
-			iwl_mld_nan_link_remove(mld, nan_link);
+			remove_link_ids |= BIT(nan_link->fw_id);
+			/* mark unused for STA updates */
+			nan_link->fw_id = FW_CTXT_ID_INVALID;
+		}
+	}
+
+	if (added_links || remove_link_ids) {
+		struct ieee80211_sta *sta;
+
+		for_each_station(sta, mld->hw) {
+			struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
+
+			if (mld_sta->sta_type == STATION_TYPE_NAN_PEER_NMI ||
+			    mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI)
+				iwl_mld_add_modify_sta_cmd(mld, &sta->deflink);
 		}
 	}
 
+	/* delete unused links */
+	for_each_set_bit(i, &remove_link_ids, ARRAY_SIZE(mld_vif->nan.links))
+		iwl_mld_nan_link_remove(mld, &mld_vif->nan.links[i], i);
+
 	/* remove MAC if needed */
 	if (!previously_empty_schedule && empty_schedule) {
 		/* must have been added */
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.c b/drivers/net/wireless/intel/iwlwifi/mld/sta.c
index 4c97d12ce2d0..f794f80b0fdd 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/sta.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.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/ieee80211.h>
@@ -13,6 +13,7 @@
 #include "key.h"
 #include "agg.h"
 #include "tlc.h"
+#include "nan.h"
 #include "fw/api/sta.h"
 #include "fw/api/mac.h"
 #include "fw/api/rx.h"
@@ -43,13 +44,13 @@ int iwl_mld_fw_sta_id_from_link_sta(struct iwl_mld *mld,
 
 static void
 iwl_mld_fill_ampdu_size_and_dens(struct ieee80211_link_sta *link_sta,
-				 struct ieee80211_bss_conf *link,
+				 bool is_6ghz,
 				 __le32 *tx_ampdu_max_size,
 				 __le32 *tx_ampdu_spacing)
 {
 	u32 agg_size = 0, mpdu_dens = 0;
 
-	if (WARN_ON(!link_sta || !link))
+	if (WARN_ON(!link_sta))
 		return;
 
 	/* Note that we always use only legacy & highest supported PPDUs, so
@@ -63,7 +64,7 @@ iwl_mld_fill_ampdu_size_and_dens(struct ieee80211_link_sta *link_sta,
 		mpdu_dens = link_sta->ht_cap.ampdu_density;
 	}
 
-	if (link->chanreq.oper.chan->band == NL80211_BAND_6GHZ) {
+	if (is_6ghz) {
 		/* overwrite HT values on 6 GHz */
 		mpdu_dens =
 			le16_get_bits(link_sta->he_6ghz_capa.capa,
@@ -439,29 +440,56 @@ static int iwl_mld_send_sta_cmd(struct iwl_mld *mld,
 	return ret;
 }
 
-static int
-iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
-			   struct ieee80211_link_sta *link_sta)
+int iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
+			      struct ieee80211_link_sta *link_sta)
 {
 	struct ieee80211_sta *sta = link_sta->sta;
 	struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
-	struct ieee80211_bss_conf *link;
-	struct iwl_mld_link *mld_link;
 	struct iwl_sta_cfg_cmd cmd = {};
 	int fw_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta);
+	bool is_6ghz, uora_exists;
+	u32 link_mask;
 
 	lockdep_assert_wiphy(mld->wiphy);
 
-	link = link_conf_dereference_protected(mld_sta->vif,
-					       link_sta->link_id);
+	if (WARN_ON(fw_id < 0))
+		return -EINVAL;
+
+	if (mld_sta->sta_type == STATION_TYPE_NAN_PEER_NMI ||
+	    mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI) {
+		struct iwl_mld_nan_link *nan_link;
+		struct iwl_mld_vif *nan_dev;
 
-	mld_link = iwl_mld_link_from_mac80211(link);
+		is_6ghz = false;
+		uora_exists = false;
 
-	if (WARN_ON(!link || !mld_link) || fw_id < 0)
-		return -EINVAL;
+		if (WARN_ON(!mld->nan_device_vif))
+			return -EINVAL;
+
+		nan_dev = iwl_mld_vif_from_mac80211(mld->nan_device_vif);
+
+		link_mask = 0;
+
+		for_each_mld_nan_valid_link(nan_dev, nan_link)
+			link_mask |= BIT(nan_link->fw_id);
+	} else {
+		struct ieee80211_bss_conf *link;
+		struct iwl_mld_link *mld_link;
+
+		link = link_conf_dereference_protected(mld_sta->vif,
+						       link_sta->link_id);
+		mld_link = iwl_mld_link_from_mac80211(link);
+
+		if (WARN_ON(!link || !mld_link))
+			return -EINVAL;
+
+		link_mask = BIT(mld_link->fw_id);
+		is_6ghz = link->chanreq.oper.chan->band == NL80211_BAND_6GHZ;
+		uora_exists = link->uora_exists;
+	}
 
 	cmd.sta_id = cpu_to_le32(fw_id);
-	cmd.link_mask = cpu_to_le32(BIT(mld_link->fw_id));
+	cmd.link_mask = cpu_to_le32(link_mask);
 	cmd.station_type = cpu_to_le32(mld_sta->sta_type);
 
 	memcpy(&cmd.peer_mld_address, sta->addr, ETH_ALEN);
@@ -499,7 +527,7 @@ iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
 		break;
 	}
 
-	iwl_mld_fill_ampdu_size_and_dens(link_sta, link,
+	iwl_mld_fill_ampdu_size_and_dens(link_sta, is_6ghz,
 					 &cmd.tx_ampdu_max_size,
 					 &cmd.tx_ampdu_spacing);
 
@@ -511,7 +539,7 @@ iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
 
 	if (link_sta->he_cap.has_he) {
 		cmd.trig_rnd_alloc =
-			cpu_to_le32(link->uora_exists ? 1 : 0);
+			cpu_to_le32(uora_exists ? 1 : 0);
 
 		/* PPE Thresholds */
 		iwl_mld_fill_pkt_ext(mld, link_sta, &cmd.pkt_ext);
@@ -525,6 +553,25 @@ iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
 			cmd.ack_enabled = cpu_to_le32(1);
 	}
 
+	if (mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI) {
+		struct ieee80211_sta *nmi_sta =
+			wiphy_dereference(mld->wiphy, sta->nmi);
+		int nmi_fw_id;
+
+		/* copy the local NDI address */
+		ether_addr_copy(cmd.ndi_local_addr, mld_sta->vif->addr);
+
+		if (WARN_ON(!nmi_sta))
+			return -EINVAL;
+
+		nmi_fw_id = iwl_mld_fw_sta_id_from_link_sta(mld,
+					&nmi_sta->deflink);
+		if (nmi_fw_id < 0)
+			return -EINVAL;
+
+		cmd.nmi_sta_id = (u8) nmi_fw_id;
+	}
+
 	return iwl_mld_send_sta_cmd(mld, &cmd);
 }
 
@@ -759,10 +806,23 @@ int iwl_mld_add_sta(struct iwl_mld *mld, struct ieee80211_sta *sta,
 {
 	struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
 	struct ieee80211_link_sta *link_sta;
+	enum iwl_fw_sta_type type;
 	int link_id;
 	int ret;
 
-	ret = iwl_mld_init_sta(mld, sta, vif, STATION_TYPE_PEER);
+	switch (vif->type) {
+	case NL80211_IFTYPE_NAN:
+		type = STATION_TYPE_NAN_PEER_NMI;
+		break;
+	case NL80211_IFTYPE_NAN_DATA:
+		type = STATION_TYPE_NAN_PEER_NDI;
+		break;
+	default:
+		type = STATION_TYPE_PEER;
+		break;
+	}
+
+	ret = iwl_mld_init_sta(mld, sta, vif, type);
 	if (ret)
 		return ret;
 
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.h b/drivers/net/wireless/intel/iwlwifi/mld/sta.h
index 36288c2fb38c..13644ffd185d 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/sta.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.h
@@ -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
  */
 
 #ifndef __iwl_mld_sta_h__
@@ -195,6 +195,8 @@ void iwl_mld_remove_sta(struct iwl_mld *mld, struct ieee80211_sta *sta);
 int iwl_mld_fw_sta_id_from_link_sta(struct iwl_mld *mld,
 				    struct ieee80211_link_sta *link_sta);
 u32 iwl_mld_fw_sta_id_mask(struct iwl_mld *mld, struct ieee80211_sta *sta);
+int iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
+			       struct ieee80211_link_sta *link_sta);
 int iwl_mld_update_all_link_stations(struct iwl_mld *mld,
 				     struct ieee80211_sta *sta);
 void iwl_mld_flush_sta_txqs(struct iwl_mld *mld, struct ieee80211_sta *sta);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 06/15] wifi: iwlwifi: add NAN schedule command support
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

From: Johannes Berg <johannes.berg@intel.com>

Add the NAN schedule command API definition and implementation
of the schedule updates.

Co-developed-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../wireless/intel/iwlwifi/fw/api/mac-cfg.h   | 24 ++++++++
 drivers/net/wireless/intel/iwlwifi/mld/mld.c  |  3 +-
 drivers/net/wireless/intel/iwlwifi/mld/nan.c  | 58 +++++++++++++++++--
 3 files changed, 78 insertions(+), 7 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h
index b398c582b867..d98c6d991a88 100644
--- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h
+++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h
@@ -8,6 +8,7 @@
 #define __iwl_fw_api_mac_cfg_h__
 
 #include "mac.h"
+#include "phy-ctxt.h"
 
 /**
  * enum iwl_mac_conf_subcmd_ids - mac configuration command IDs
@@ -71,6 +72,10 @@ enum iwl_mac_conf_subcmd_ids {
 	 * @NAN_CFG_CMD: &struct iwl_nan_config_cmd
 	 */
 	NAN_CFG_CMD = 0x12,
+	/**
+	 * @NAN_SCHEDULE_CMD: &struct iwl_nan_schedule_cmd
+	 */
+	NAN_SCHEDULE_CMD = 0x13,
 	/**
 	 * @NAN_DW_END_NOTIF: &struct iwl_nan_dw_end_notif
 	 */
@@ -1244,6 +1249,25 @@ struct iwl_nan_config_cmd {
 	u8 beacon_data[];
 } __packed; /*  NAN_CONFIG_CMD_API_S_VER_1 */
 
+/**
+ * struct iwl_nan_schedule_cmd - NAN schedule command
+ * @channels: per channel information
+ * @channels.availability_map: bitmap of slots this channel is advertising
+ *	availability on, will be ULW'ed out if no link/inactive link is
+ *	referenced by the link ID below
+ * @channels.channel_entry: NAN channel entry descriptor
+ * @channels.link_id: FW link ID, or %0xFF for unset
+ * @channels.reserved: (reserved)
+ */
+struct iwl_nan_schedule_cmd {
+	struct {
+		__le32 availability_map;
+		u8 channel_entry[6];
+		u8 link_id;
+		u8 reserved;
+	} __packed channels[NUM_PHY_CTX];
+} __packed; /* NAN_SCHEDULE_CMD_API_S_VER_1 */
+
 /**
  * enum iwl_nan_cluster_notif_flags - flags for the cluster notification
  *
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c
index 9af79297c3b6..c038a0cde36b 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.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/rtnetlink.h>
 #include <net/mac80211.h>
@@ -236,6 +236,7 @@ static const struct iwl_hcmd_names iwl_mld_mac_conf_names[] = {
 	HCMD_NAME(STA_REMOVE_CMD),
 	HCMD_NAME(ROC_CMD),
 	HCMD_NAME(NAN_CFG_CMD),
+	HCMD_NAME(NAN_SCHEDULE_CMD),
 	HCMD_NAME(NAN_DW_END_NOTIF),
 	HCMD_NAME(NAN_JOINED_CLUSTER_NOTIF),
 	HCMD_NAME(MISSED_BEACONS_NOTIF),
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
index 6ea11b66a545..eba79aca8c06 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
@@ -401,8 +401,14 @@ iwl_mld_nan_link_add(struct iwl_mld *mld,
 	u8 fw_id;
 	int ret;
 
+	lockdep_assert_wiphy(mld->wiphy);
+
 	ret = iwl_mld_allocate_link_fw_id(mld, &fw_id, ERR_PTR(-ENODEV));
-	if (ret < 0)
+	/*
+	 * We should always have enough links. The schedule contains up to 3,
+	 * and the BSS vif cannot do EMLSR - so can only have 1.
+	 */
+	if (WARN_ON(ret < 0))
 		return NULL;
 
 	nan_link = &mld_vif->nan.links[fw_id];
@@ -504,19 +510,21 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
 				 struct ieee80211_vif *vif,
 				 u64 changes)
 {
+	struct iwl_nan_schedule_cmd cmd = {};
 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
 	bool previously_empty_schedule = !iwl_mld_nan_have_links(mld_vif);
 	struct ieee80211_nan_sched_cfg *sched_cfg = &vif->cfg.nan_sched;
 	struct iwl_mld_nan_link *links[ARRAY_SIZE(sched_cfg->channels)] = {};
+	struct ieee80211_nan_channel **slots = sched_cfg->schedule;
 	bool link_used[ARRAY_SIZE(mld_vif->nan.links)] = {};
 	struct iwl_mld_nan_link *nan_link;
 	bool empty_schedule = true;
-	int ret;
+	int ret, i;
 
 	if (!(changes & BSS_CHANGED_NAN_LOCAL_SCHED))
 		return;
 
-	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+	for (i = 0; i <  ARRAY_SIZE(sched_cfg->channels); i++) {
 		if (!sched_cfg->channels[i].chanreq.oper.chan)
 			continue;
 		empty_schedule = false;
@@ -539,8 +547,12 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
 		return;
 	}
 
+	/* this currently just uses the same index */
+	BUILD_BUG_ON(ARRAY_SIZE(sched_cfg->channels) !=
+		     ARRAY_SIZE(cmd.channels));
+
 	/* find links we can keep (same chanctx/PHY) */
-	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+	for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
 		struct ieee80211_chanctx_conf *chanctx;
 		struct iwl_mld_nan_link *link;
 
@@ -556,7 +568,7 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
 	}
 
 	/* add/reassign links for new channels */
-	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+	for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
 		struct ieee80211_chanctx_conf *chanctx;
 
 		/* already have an existing active link */
@@ -581,6 +593,39 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
 		}
 	}
 
+	/* fill the command */
+	for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+		cmd.channels[i].link_id = FW_CTXT_ID_INVALID;
+
+		if (!sched_cfg->channels[i].chanreq.oper.chan)
+			continue;
+
+		memcpy(cmd.channels[i].channel_entry,
+		       sched_cfg->channels[i].channel_entry, 6);
+		cmd.channels[i].link_id =
+			links[i] ? links[i]->fw_id : FW_CTXT_ID_INVALID;
+	}
+
+	for (i = 0; i < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; i++) {
+		int chan_idx;
+
+		if (!slots[i])
+			continue;
+
+		chan_idx = slots[i] - sched_cfg->channels;
+		if (WARN_ON_ONCE(chan_idx < 0 ||
+				 chan_idx >= ARRAY_SIZE(cmd.channels)))
+			continue;
+
+		cmd.channels[chan_idx].availability_map |= cpu_to_le32(BIT(i));
+	}
+
+	ret = iwl_mld_send_cmd_pdu(mld,
+				   WIDE_ID(MAC_CONF_GROUP, NAN_SCHEDULE_CMD),
+				   &cmd);
+	if (ret)
+		IWL_ERR(mld, "NAN: failed to update schedule (%d)\n", ret);
+
 	/* delete unused links */
 	for_each_mld_nan_valid_link(mld_vif, nan_link) {
 		if (!link_used[nan_link->fw_id]) {
@@ -595,7 +640,8 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
 		WARN_ON_ONCE(!mld_vif->nan.mac_added);
 
 		/* mac80211 should reconfigure same state */
-		if (!WARN_ON_ONCE(mld->fw_status.in_hw_restart))
+		if (!WARN_ON_ONCE(mld->fw_status.in_hw_restart &&
+				  !iwl_mld_error_before_recovery(mld)))
 			iwl_mld_rm_vif(mld, vif);
 	}
 }
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 05/15] wifi: iwlwifi: mld: add NAN link management
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

From: Johannes Berg <johannes.berg@intel.com>

The firmware requires links for NAN which mac80211 doesn't use,
so introduce a new NAN link data structure that the driver has
for itself only, and handle the link command sending code for
NAN using this data structure, most of the bss_conf data isn't
used for NAN anyway, so those structures aren't useful.

With that, add, activate, deactivate or remove links depending
on the local NAN schedule updates.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Co-developed-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/intel/iwlwifi/mld/iface.c    |  15 +-
 .../net/wireless/intel/iwlwifi/mld/iface.h    |   9 +
 drivers/net/wireless/intel/iwlwifi/mld/link.c |  12 +-
 drivers/net/wireless/intel/iwlwifi/mld/link.h |   7 +
 drivers/net/wireless/intel/iwlwifi/mld/mld.h  |   8 +-
 drivers/net/wireless/intel/iwlwifi/mld/nan.c  | 301 +++++++++++++++++-
 drivers/net/wireless/intel/iwlwifi/mld/nan.h  |  19 ++
 drivers/net/wireless/intel/iwlwifi/mld/rx.c   |   2 +-
 .../wireless/intel/iwlwifi/mld/tests/utils.c  |   2 -
 9 files changed, 356 insertions(+), 19 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c
index 150ad095e0ae..5fc3f6729455 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c
@@ -5,6 +5,7 @@
 #include <net/cfg80211.h>
 
 #include "iface.h"
+#include "nan.h"
 #include "hcmd.h"
 #include "key.h"
 #include "mlo.h"
@@ -55,8 +56,12 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
 
 	ieee80211_iter_keys(mld->hw, vif, iwl_mld_cleanup_keys_iter, NULL);
 
-	if (vif->type == NL80211_IFTYPE_NAN)
+	if (vif->type == NL80211_IFTYPE_NAN) {
 		mld_vif->nan.mac_added = false;
+		/* Clean up NAN links */
+		for (int i = 0; i < ARRAY_SIZE(mld_vif->nan.links); i++)
+			iwl_mld_cleanup_nan_link(&mld_vif->nan.links[i]);
+	}
 
 	CLEANUP_STRUCT(mld_vif);
 }
@@ -515,6 +520,14 @@ iwl_mld_init_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
 		wiphy_delayed_work_init(&mld_vif->mlo_scan_start_wk,
 					iwl_mld_mlo_scan_start_wk);
 	}
+
+	if (vif->type == NL80211_IFTYPE_NAN) {
+		for (int i = 0; i < ARRAY_SIZE(mld_vif->nan.links); i++) {
+			memset(&mld_vif->nan.links[i], 0, sizeof(mld_vif->nan.links[i]));
+			mld_vif->nan.links[i].fw_id = FW_CTXT_ID_INVALID;
+		}
+	}
+
 	iwl_mld_init_internal_sta(&mld_vif->aux_sta);
 }
 
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.h b/drivers/net/wireless/intel/iwlwifi/mld/iface.h
index 1ac14996985c..ce4f8ca885cf 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h
@@ -8,6 +8,7 @@
 #include <net/mac80211.h>
 
 #include "link.h"
+#include "nan.h"
 #include "session-protect.h"
 #include "d3.h"
 #include "fw/api/time-event.h"
@@ -153,6 +154,7 @@ struct iwl_mld_emlsr {
  * @aux_sta: station used for remain on channel. Used in P2P device.
  * @mlo_scan_start_wk: worker to start a deferred MLO scan
  * @nan: NAN parameters
+ * @nan.links: NAN links for FW (indexed by FW link ID)
  * @nan.mac_added: track whether or not the MAC was added to FW
  */
 struct iwl_mld_vif {
@@ -179,6 +181,7 @@ struct iwl_mld_vif {
 
 	struct {
 		/* use only with wiphy protection */
+		struct iwl_mld_nan_link links[IWL_FW_MAX_LINKS];
 		bool mac_added;
 	} nan;
 
@@ -242,6 +245,12 @@ static inline bool iwl_mld_vif_fw_id_valid(struct iwl_mld_vif *mld_vif)
 	     link_id++)							\
 		if ((mld_link = iwl_mld_link_dereference_check(mld_vif, link_id)))
 
+#define for_each_mld_nan_valid_link(mld_vif, nan_link)			\
+	for (nan_link = &(mld_vif)->nan.links[0];			\
+	     nan_link < &(mld_vif)->nan.links[ARRAY_SIZE((mld_vif)->nan.links)]; \
+	     nan_link++)						\
+		if (nan_link->fw_id != FW_CTXT_ID_INVALID)
+
 /* Retrieve pointer to mld link from mac80211 structures */
 static inline struct iwl_mld_link *
 iwl_mld_link_from_mac80211(struct ieee80211_bss_conf *bss_conf)
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c
index b66e84d2365f..9e40b334ee1f 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/link.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c
@@ -16,14 +16,17 @@
 #include "fw/api/context.h"
 #include "fw/dbg.h"
 
-static int iwl_mld_send_link_cmd(struct iwl_mld *mld,
-				 struct iwl_link_config_cmd *cmd,
-				 enum iwl_ctxt_action action)
+int iwl_mld_send_link_cmd(struct iwl_mld *mld,
+			  struct iwl_link_config_cmd *cmd,
+			  enum iwl_ctxt_action action)
 {
 	int ret;
 
 	lockdep_assert_wiphy(mld->wiphy);
 
+	if (WARN_ON_ONCE(cmd->link_id == cpu_to_le32(FW_CTXT_ID_INVALID)))
+		return -EINVAL;
+
 	cmd->action = cpu_to_le32(action);
 	ret = iwl_mld_send_cmd_pdu(mld,
 				   WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD),
@@ -437,7 +440,8 @@ iwl_mld_rm_link_from_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link)
 	iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_REMOVE);
 }
 
-static IWL_MLD_ALLOC_FN(link, bss_conf)
+IWL_MLD_ALLOC_FN(link, bss_conf)
+EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_allocate_link_fw_id);
 
 /* Constructor function for struct iwl_mld_link */
 static int
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.h b/drivers/net/wireless/intel/iwlwifi/mld/link.h
index ca691259fc5e..84d9a24134a8 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/link.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/link.h
@@ -99,6 +99,13 @@ iwl_mld_cleanup_link(struct iwl_mld *mld, struct iwl_mld_link *link)
 /* Convert a percentage from [0,100] to [0,255] */
 #define NORMALIZE_PERCENT_TO_255(percentage) ((percentage) * 256 / 100)
 
+int iwl_mld_allocate_link_fw_id(struct iwl_mld *mld, u8 *fw_id,
+				struct ieee80211_bss_conf *mac80211_ptr);
+
+int iwl_mld_send_link_cmd(struct iwl_mld *mld,
+			  struct iwl_link_config_cmd *cmd,
+			  enum iwl_ctxt_action action);
+
 int iwl_mld_add_link(struct iwl_mld *mld,
 		     struct ieee80211_bss_conf *bss_conf);
 void iwl_mld_remove_link(struct iwl_mld *mld,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.h b/drivers/net/wireless/intel/iwlwifi/mld/mld.h
index 606cb64f8ea4..69da3c346394 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mld.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.h
@@ -558,12 +558,18 @@ iwl_mld_allocate_##_type##_fw_id(struct iwl_mld *mld,					\
 static inline struct ieee80211_bss_conf *
 iwl_mld_fw_id_to_link_conf(struct iwl_mld *mld, u8 fw_link_id)
 {
+	struct ieee80211_bss_conf *link;
+
 	if (IWL_FW_CHECK(mld, fw_link_id >= mld->fw->ucode_capa.num_links,
 			 "Invalid fw_link_id: %d\n", fw_link_id))
 		return NULL;
 
-	return wiphy_dereference(mld->wiphy,
+	link = wiphy_dereference(mld->wiphy,
 				 mld->fw_id_to_bss_conf[fw_link_id]);
+	if (IS_ERR(link))
+		return NULL;
+
+	return link;
 }
 
 #define MSEC_TO_TU(_msec)	((_msec) * 1000 / 1024)
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
index 96e79ba5234a..6ea11b66a545 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
@@ -5,8 +5,11 @@
 
 #include "mld.h"
 #include "iface.h"
+#include "link.h"
 #include "mlo.h"
 #include "fw/api/mac-cfg.h"
+#include "fw/api/mac.h"
+#include "fw/api/rs.h"
 
 #define IWL_NAN_DISOVERY_BEACON_INTERNVAL_TU 512
 #define IWL_NAN_RSSI_CLOSE 55
@@ -297,24 +300,302 @@ void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld,
 	cfg80211_next_nan_dw_notif(wdev, chan, GFP_KERNEL);
 }
 
+static void iwl_mld_nan_fill_rates(struct iwl_link_config_cmd *cmd)
+{
+	u32 ofdm = 0;
+
+	/* All OFDM rates - NAN uses OFDM only, no CCK */
+	ofdm |= IWL_RATE_BIT_MSK(6) >> IWL_FIRST_OFDM_RATE;
+	ofdm |= IWL_RATE_BIT_MSK(9) >> IWL_FIRST_OFDM_RATE;
+	ofdm |= IWL_RATE_BIT_MSK(12) >> IWL_FIRST_OFDM_RATE;
+	ofdm |= IWL_RATE_BIT_MSK(18) >> IWL_FIRST_OFDM_RATE;
+	ofdm |= IWL_RATE_BIT_MSK(24) >> IWL_FIRST_OFDM_RATE;
+	ofdm |= IWL_RATE_BIT_MSK(36) >> IWL_FIRST_OFDM_RATE;
+	ofdm |= IWL_RATE_BIT_MSK(48) >> IWL_FIRST_OFDM_RATE;
+	ofdm |= IWL_RATE_BIT_MSK(54) >> IWL_FIRST_OFDM_RATE;
+
+	cmd->ofdm_rates = cpu_to_le32(ofdm);
+	cmd->short_slot = cpu_to_le32(1);
+}
+
+static void iwl_mld_nan_fill_qos(struct iwl_ac_qos *ac, __le32 *qos_flags)
+{
+	/* AC_BK: CWmin=15, CWmax=1023, AIFSN=7, TXOP=0 */
+	ac[AC_BK].cw_min = cpu_to_le16(15);
+	ac[AC_BK].cw_max = cpu_to_le16(1023);
+	ac[AC_BK].aifsn = 7;
+	ac[AC_BK].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_BK);
+	ac[AC_BK].edca_txop = 0;
+
+	/* AC_BE: CWmin=15, CWmax=1023, AIFSN=3, TXOP=0 */
+	ac[AC_BE].cw_min = cpu_to_le16(15);
+	ac[AC_BE].cw_max = cpu_to_le16(1023);
+	ac[AC_BE].aifsn = 3;
+	ac[AC_BE].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_BE);
+	ac[AC_BE].edca_txop = 0;
+
+	/* AC_VI: CWmin=7, CWmax=15, AIFSN=2, TXOP=3008us */
+	ac[AC_VI].cw_min = cpu_to_le16(7);
+	ac[AC_VI].cw_max = cpu_to_le16(15);
+	ac[AC_VI].aifsn = 2;
+	ac[AC_VI].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_VI);
+	ac[AC_VI].edca_txop = cpu_to_le16(3008);
+
+	/* AC_VO: CWmin=3, CWmax=7, AIFSN=2, TXOP=1504us */
+	ac[AC_VO].cw_min = cpu_to_le16(3);
+	ac[AC_VO].cw_max = cpu_to_le16(7);
+	ac[AC_VO].aifsn = 2;
+	ac[AC_VO].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_VO);
+	ac[AC_VO].edca_txop = cpu_to_le16(1504);
+
+	*qos_flags |= cpu_to_le32(MAC_QOS_FLG_UPDATE_EDCA);
+}
+
+static void
+iwl_mld_nan_link_prep_cmd(struct iwl_mld *mld,
+			  struct iwl_mld_nan_link *nan_link,
+			  struct iwl_link_config_cmd *cmd,
+			  u32 modify_flags)
+{
+	struct ieee80211_vif *vif = mld->nan_device_vif;
+	struct iwl_mld_vif *mld_vif;
+
+	if (WARN_ON_ONCE(!vif))
+		return;
+
+	mld_vif = iwl_mld_vif_from_mac80211(vif);
+
+	memset(cmd, 0, sizeof(*cmd));
+
+	if (!nan_link->chanctx) {
+		cmd->phy_id = cpu_to_le32(FW_CTXT_ID_INVALID);
+	} else {
+		struct iwl_mld_phy *mld_phy;
+
+		mld_phy = iwl_mld_phy_from_mac80211(nan_link->chanctx);
+		cmd->phy_id = cpu_to_le32(mld_phy->fw_id);
+	}
+
+	if (modify_flags & LINK_CONTEXT_MODIFY_RATES_INFO)
+		iwl_mld_nan_fill_rates(cmd);
+
+	if (modify_flags & LINK_CONTEXT_MODIFY_QOS_PARAMS)
+		iwl_mld_nan_fill_qos(cmd->ac, &cmd->qos_flags);
+
+	cmd->link_id = cpu_to_le32(nan_link->fw_id);
+	cmd->mac_id = cpu_to_le32(mld_vif->fw_id);
+	cmd->active = cpu_to_le32(nan_link->active);
+
+	ether_addr_copy(cmd->local_link_addr, vif->addr);
+
+	cmd->modify_mask = cpu_to_le32(modify_flags);
+}
+
+static struct iwl_mld_nan_link *
+iwl_mld_nan_link_add(struct iwl_mld *mld,
+		     struct iwl_mld_vif *mld_vif,
+		     struct ieee80211_chanctx_conf *chanctx)
+{
+	struct iwl_mld_nan_link *nan_link;
+	struct iwl_link_config_cmd cmd;
+	u8 fw_id;
+	int ret;
+
+	ret = iwl_mld_allocate_link_fw_id(mld, &fw_id, ERR_PTR(-ENODEV));
+	if (ret < 0)
+		return NULL;
+
+	nan_link = &mld_vif->nan.links[fw_id];
+
+	if (WARN_ON_ONCE(nan_link->fw_id != FW_CTXT_ID_INVALID))
+		goto err;
+
+	nan_link->fw_id = fw_id;
+	nan_link->chanctx = chanctx;
+
+	iwl_mld_nan_link_prep_cmd(mld, nan_link, &cmd,
+				  LINK_CONTEXT_MODIFY_RATES_INFO |
+				  LINK_CONTEXT_MODIFY_QOS_PARAMS);
+
+	ret = iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_ADD);
+	if (ret) {
+		nan_link->fw_id = FW_CTXT_ID_INVALID;
+		nan_link->chanctx = NULL;
+		goto err;
+	}
+
+	return nan_link;
+err:
+	RCU_INIT_POINTER(mld->fw_id_to_bss_conf[fw_id], NULL);
+	return NULL;
+}
+
+static int iwl_mld_nan_link_set_active(struct iwl_mld *mld,
+				       struct iwl_mld_nan_link *nan_link,
+				       bool active)
+{
+	struct iwl_link_config_cmd cmd;
+	int ret;
+
+	if (nan_link->active == active)
+		return 0;
+
+	nan_link->active = active;
+
+	iwl_mld_nan_link_prep_cmd(mld, nan_link, &cmd,
+				  LINK_CONTEXT_MODIFY_ACTIVE);
+
+	ret = iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_MODIFY);
+	if (ret) {
+		nan_link->active = !nan_link->active;
+		return ret;
+	}
+
+	if (!active)
+		nan_link->chanctx = NULL;
+
+	return 0;
+}
+
+static void iwl_mld_nan_link_remove(struct iwl_mld *mld,
+				    struct iwl_mld_nan_link *nan_link)
+{
+	struct iwl_link_config_cmd cmd = {
+		.link_id = cpu_to_le32(nan_link->fw_id),
+		.phy_id = cpu_to_le32(FW_CTXT_ID_INVALID),
+	};
+
+	if (WARN_ON_ONCE(nan_link->fw_id == FW_CTXT_ID_INVALID))
+		return;
+
+	iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_REMOVE);
+
+	RCU_INIT_POINTER(mld->fw_id_to_bss_conf[nan_link->fw_id], NULL);
+	nan_link->fw_id = FW_CTXT_ID_INVALID;
+	nan_link->active = false;
+	nan_link->chanctx = NULL;
+}
+
+static bool iwl_mld_nan_have_links(struct iwl_mld_vif *mld_vif)
+{
+	struct iwl_mld_nan_link *nan_link;
+
+	for_each_mld_nan_valid_link(mld_vif, nan_link)
+		return true;
+
+	return false;
+}
+
+static struct iwl_mld_nan_link *
+iwl_mld_nan_find_link(struct iwl_mld_vif *mld_vif,
+		      struct ieee80211_chanctx_conf *chanctx)
+{
+	struct iwl_mld_nan_link *nan_link;
+
+	for_each_mld_nan_valid_link(mld_vif, nan_link) {
+		if (nan_link->chanctx == chanctx)
+			return nan_link;
+	}
+
+	return NULL;
+}
+
 void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
 				 struct ieee80211_vif *vif,
 				 u64 changes)
 {
-	struct ieee80211_nan_sched_cfg *sched_cfg = &vif->cfg.nan_sched;
 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
-	bool has_sched = memchr_inv(sched_cfg->schedule, 0,
-				    sizeof(sched_cfg->schedule));
-
-	lockdep_assert_wiphy(mld->wiphy);
+	bool previously_empty_schedule = !iwl_mld_nan_have_links(mld_vif);
+	struct ieee80211_nan_sched_cfg *sched_cfg = &vif->cfg.nan_sched;
+	struct iwl_mld_nan_link *links[ARRAY_SIZE(sched_cfg->channels)] = {};
+	bool link_used[ARRAY_SIZE(mld_vif->nan.links)] = {};
+	struct iwl_mld_nan_link *nan_link;
+	bool empty_schedule = true;
+	int ret;
 
 	if (!(changes & BSS_CHANGED_NAN_LOCAL_SCHED))
 		return;
 
-	if (has_sched && !mld_vif->nan.mac_added) {
-		if (iwl_mld_add_nan_vif(mld, vif))
-			IWL_ERR(mld, "Failed to add NAN vif\n");
-	} else if (!has_sched && mld_vif->nan.mac_added) {
-		iwl_mld_rm_vif(mld, vif);
+	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+		if (!sched_cfg->channels[i].chanreq.oper.chan)
+			continue;
+		empty_schedule = false;
+		break;
+	}
+
+	/* add the MAC if needed (before adding links) */
+	if (!empty_schedule && previously_empty_schedule) {
+		WARN_ON(mld_vif->nan.mac_added);
+		ret = iwl_mld_add_nan_vif(mld, vif);
+
+		if (ret) {
+			IWL_ERR(mld, "NAN: failed to add MAC (%d)\n", ret);
+			return;
+		}
+	}
+
+	if (!mld_vif->nan.mac_added) {
+		/* nothing to do */
+		return;
+	}
+
+	/* find links we can keep (same chanctx/PHY) */
+	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+		struct ieee80211_chanctx_conf *chanctx;
+		struct iwl_mld_nan_link *link;
+
+		chanctx = sched_cfg->channels[i].chanctx_conf;
+		/* ULW */
+		if (!chanctx)
+			continue;
+
+		link = iwl_mld_nan_find_link(mld_vif, chanctx);
+		links[i] = link;
+		if (link)
+			link_used[link->fw_id] = true;
+	}
+
+	/* add/reassign links for new channels */
+	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+		struct ieee80211_chanctx_conf *chanctx;
+
+		/* already have an existing active link */
+		if (links[i])
+			continue;
+
+		chanctx = sched_cfg->channels[i].chanctx_conf;
+		/* ULW or unused slot */
+		if (!chanctx)
+			continue;
+
+		/*
+		 * if this fails we still update the schedule, but
+		 * without a valid link we'll always ULW it
+		 */
+		links[i] = iwl_mld_nan_link_add(mld, mld_vif, chanctx);
+
+		/* we have a link, activate it */
+		if (links[i]) {
+			link_used[links[i]->fw_id] = true;
+			iwl_mld_nan_link_set_active(mld, links[i], true);
+		}
+	}
+
+	/* delete unused links */
+	for_each_mld_nan_valid_link(mld_vif, nan_link) {
+		if (!link_used[nan_link->fw_id]) {
+			iwl_mld_nan_link_set_active(mld, nan_link, false);
+			iwl_mld_nan_link_remove(mld, nan_link);
+		}
+	}
+
+	/* remove MAC if needed */
+	if (!previously_empty_schedule && empty_schedule) {
+		/* must have been added */
+		WARN_ON_ONCE(!mld_vif->nan.mac_added);
+
+		/* mac80211 should reconfigure same state */
+		if (!WARN_ON_ONCE(mld->fw_status.in_hw_restart))
+			iwl_mld_rm_vif(mld, vif);
 	}
 }
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.h b/drivers/net/wireless/intel/iwlwifi/mld/nan.h
index 9487155cf6b3..933e16c3c274 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/nan.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.h
@@ -7,6 +7,25 @@
 #include <net/cfg80211.h>
 #include <linux/etherdevice.h>
 
+/**
+ * struct iwl_mld_nan_link - struct representing a NAN link
+ * @chanctx: the channel context
+ * @active: indicates the NAN link is currently active
+ * @fw_id: FW link ID
+ */
+struct iwl_mld_nan_link {
+	struct ieee80211_chanctx_conf *chanctx;
+	bool active;
+	u8 fw_id;
+};
+
+/* Cleanup function for struct iwl_mld_nan_link, will be called in restart */
+static inline void iwl_mld_cleanup_nan_link(struct iwl_mld_nan_link *nan_link)
+{
+	memset(nan_link, 0, sizeof(*nan_link));
+	nan_link->fw_id = FW_CTXT_ID_INVALID;
+}
+
 bool iwl_mld_nan_supported(struct iwl_mld *mld);
 int iwl_mld_start_nan(struct ieee80211_hw *hw,
 		      struct ieee80211_vif *vif,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c
index a2e586c6ea67..b270cf87824d 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c
@@ -158,7 +158,7 @@ static bool iwl_mld_used_average_energy(struct iwl_mld *mld, int link_id,
 	guard(rcu)();
 
 	link_conf = rcu_dereference(mld->fw_id_to_bss_conf[link_id]);
-	if (!link_conf)
+	if (IS_ERR_OR_NULL(link_conf))
 		return false;
 
 	mld_link = iwl_mld_link_from_mac80211(link_conf);
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c
index dce747270167..0cdbbb86dbd9 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c
@@ -68,8 +68,6 @@ int iwlmld_kunit_test_init(struct kunit *test)
 	return 0;
 }
 
-static IWL_MLD_ALLOC_FN(link, bss_conf)
-
 static void iwlmld_kunit_init_link(struct ieee80211_vif *vif,
 				   struct ieee80211_bss_conf *link,
 				   struct iwl_mld_link *mld_link, int link_id)
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 04/15] wifi: iwlwifi: mld: support NAN and NAN_DATA interfaces
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

From: Johannes Berg <johannes.berg@intel.com>

Until now we maintained the NAN vif in the driver only. The fw used the
AUX MAC for sync and discovery operations.
But when we want to configure a local schedule, we need to add the MAC
first.

NAN_DATA interfaces are not added to the FW. Instead, the local
address of these interfaces are configured to the FW via the NAN MAC.

Add the add/remove/update operations for the NAN interface, and fill the
NAN special parameters in it.

Note that this doesn't fully implement the schedule change, but only the
addition/removal of the NAN MAC. The full schedule management
implementation will come in a later patch.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Co-developed-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/intel/iwlwifi/mld/iface.c    | 134 ++++++++++++++++--
 .../net/wireless/intel/iwlwifi/mld/iface.h    |  22 +++
 .../net/wireless/intel/iwlwifi/mld/mac80211.c |  16 ++-
 drivers/net/wireless/intel/iwlwifi/mld/nan.c  |  22 +++
 drivers/net/wireless/intel/iwlwifi/mld/nan.h  |   5 +-
 5 files changed, 184 insertions(+), 15 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c
index 1e85a9168d2b..150ad095e0ae 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.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 <net/cfg80211.h>
 
@@ -55,6 +55,9 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
 
 	ieee80211_iter_keys(mld->hw, vif, iwl_mld_cleanup_keys_iter, NULL);
 
+	if (vif->type == NL80211_IFTYPE_NAN)
+		mld_vif->nan.mac_added = false;
+
 	CLEANUP_STRUCT(mld_vif);
 }
 
@@ -94,6 +97,8 @@ static int iwl_mld_mac80211_iftype_to_fw(const struct ieee80211_vif *vif)
 		return FW_MAC_TYPE_P2P_DEVICE;
 	case NL80211_IFTYPE_ADHOC:
 		return FW_MAC_TYPE_IBSS;
+	case NL80211_IFTYPE_NAN:
+		return FW_MAC_TYPE_NAN;
 	default:
 		WARN_ON_ONCE(1);
 	}
@@ -362,6 +367,42 @@ static void iwl_mld_fill_mac_cmd_ibss(struct iwl_mld *mld,
 					 MAC_CFG_FILTER_ACCEPT_GRP);
 }
 
+static int iwl_mld_fill_mac_cmd_nan(struct iwl_mld *mld,
+				    struct ieee80211_vif *vif,
+				    struct ieee80211_vif *ndi_being_added,
+				    struct iwl_mac_config_cmd *cmd)
+{
+	struct ieee80211_vif *iter;
+	u32 idx = 0;
+
+	cmd->filter_flags = cpu_to_le32(MAC_CFG_FILTER_ACCEPT_CONTROL_AND_MGMT);
+
+	/*
+	 * A NAN_DATA vif might be in the process of being added - it won't
+	 * be found by the iteration below since it's not yet active/in-driver.
+	 * In hw restart, the iteration below will find the ndi_being_added.
+	 */
+	if (ndi_being_added && !mld->fw_status.in_hw_restart) {
+		memcpy(cmd->nan.ndi_addrs[idx].addr, ndi_being_added->addr, ETH_ALEN);
+		idx++;
+	}
+
+	for_each_active_interface(iter, mld->hw) {
+		if (iter->type != NL80211_IFTYPE_NAN_DATA)
+			continue;
+
+		if (WARN_ON_ONCE(idx >= ARRAY_SIZE(cmd->nan.ndi_addrs)))
+			return -EINVAL;
+
+		memcpy(cmd->nan.ndi_addrs[idx].addr, iter->addr, ETH_ALEN);
+		idx++;
+	}
+
+	cmd->nan.ndi_addrs_count = cpu_to_le32(idx);
+
+	return 0;
+}
+
 static int
 iwl_mld_rm_mac_from_fw(struct iwl_mld *mld, struct ieee80211_vif *vif)
 {
@@ -374,16 +415,23 @@ iwl_mld_rm_mac_from_fw(struct iwl_mld *mld, struct ieee80211_vif *vif)
 	return iwl_mld_send_mac_cmd(mld, &cmd);
 }
 
-int iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
-			  u32 action)
+static int
+__iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
+			u32 action, struct ieee80211_vif *ndi_being_added)
 {
 	struct iwl_mac_config_cmd cmd = {};
+	int ret;
 
 	lockdep_assert_wiphy(mld->wiphy);
 
-	/* NAN interface type is not known to FW */
-	if (vif->type == NL80211_IFTYPE_NAN)
-		return 0;
+	/* NAN_DATA interface type is not known to FW */
+	if (WARN_ON(vif->type == NL80211_IFTYPE_NAN_DATA))
+		return -EINVAL;
+
+	/* ndi_being_added is only relevant for NAN and when adding a NAN_DATA interface */
+	if (WARN_ON(ndi_being_added &&
+		    (vif->type != NL80211_IFTYPE_NAN || action != FW_CTXT_ACTION_MODIFY)))
+		return -EINVAL;
 
 	if (action == FW_CTXT_ACTION_REMOVE)
 		return iwl_mld_rm_mac_from_fw(mld, vif);
@@ -411,6 +459,11 @@ int iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
 	case NL80211_IFTYPE_ADHOC:
 		iwl_mld_fill_mac_cmd_ibss(mld, vif, &cmd);
 		break;
+	case NL80211_IFTYPE_NAN:
+		ret = iwl_mld_fill_mac_cmd_nan(mld, vif, ndi_being_added, &cmd);
+		if (ret)
+			return ret;
+		break;
 	default:
 		WARN(1, "not supported yet\n");
 		return -EOPNOTSUPP;
@@ -419,6 +472,12 @@ int iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
 	return iwl_mld_send_mac_cmd(mld, &cmd);
 }
 
+int iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
+			  u32 action)
+{
+	return __iwl_mld_mac_fw_action(mld, vif, action, NULL);
+}
+
 static void iwl_mld_mlo_scan_start_wk(struct wiphy *wiphy,
 				      struct wiphy_work *wk)
 {
@@ -459,6 +518,24 @@ iwl_mld_init_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
 	iwl_mld_init_internal_sta(&mld_vif->aux_sta);
 }
 
+static int iwl_mld_update_nan_mac(struct iwl_mld *mld,
+				  struct ieee80211_vif *ndi_being_added)
+{
+	struct ieee80211_vif *vif = mld->nan_device_vif;
+	struct iwl_mld_vif *mld_vif;
+
+	if (WARN_ON_ONCE(!vif))
+		return -ENODEV;
+
+	mld_vif = iwl_mld_vif_from_mac80211(vif);
+
+	if (!iwl_mld_vif_fw_id_valid(mld_vif))
+		return 0;
+
+	return __iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_MODIFY,
+				       ndi_being_added);
+}
+
 int iwl_mld_add_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
 {
 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
@@ -468,10 +545,14 @@ int iwl_mld_add_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
 
 	iwl_mld_init_vif(mld, vif);
 
-	/* NAN interface type is not known to FW */
+	/* NAN MACs are added to FW only when a schedule is set */
 	if (vif->type == NL80211_IFTYPE_NAN)
 		return 0;
 
+	/* NAN_DATA interface type is not known to FW, but we need to update NAN MAC */
+	if (vif->type == NL80211_IFTYPE_NAN_DATA)
+		return iwl_mld_update_nan_mac(mld, vif);
+
 	ret = iwl_mld_allocate_vif_fw_id(mld, &mld_vif->fw_id, vif);
 	if (ret)
 		return ret;
@@ -483,23 +564,52 @@ int iwl_mld_add_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
 	return ret;
 }
 
+int iwl_mld_add_nan_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
+{
+	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
+	int ret;
+
+	lockdep_assert_wiphy(mld->wiphy);
+
+	if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
+		return -EINVAL;
+
+	ret = iwl_mld_allocate_vif_fw_id(mld, &mld_vif->fw_id, vif);
+	if (ret)
+		return ret;
+
+	ret = iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_ADD);
+	if (ret) {
+		RCU_INIT_POINTER(mld->fw_id_to_vif[mld_vif->fw_id], NULL);
+		return ret;
+	}
+
+	mld_vif->nan.mac_added = true;
+
+	return 0;
+}
+
 void iwl_mld_rm_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
 {
 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
 
 	lockdep_assert_wiphy(mld->wiphy);
 
-	/* NAN interface type is not known to FW */
-	if (vif->type == NL80211_IFTYPE_NAN)
+	if (vif->type == NL80211_IFTYPE_NAN_DATA) {
+		iwl_mld_update_nan_mac(mld, NULL);
 		return;
+	}
 
-	iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_REMOVE);
-
-	if (WARN_ON(mld_vif->fw_id >= ARRAY_SIZE(mld->fw_id_to_vif)))
+	if (!iwl_mld_vif_fw_id_valid(mld_vif))
 		return;
 
+	iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_REMOVE);
+
 	RCU_INIT_POINTER(mld->fw_id_to_vif[mld_vif->fw_id], NULL);
 
+	if (vif->type == NL80211_IFTYPE_NAN)
+		mld_vif->nan.mac_added = false;
+
 	iwl_mld_cancel_notifications_of_object(mld, IWL_MLD_OBJECT_TYPE_VIF,
 					       mld_vif->fw_id);
 }
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.h b/drivers/net/wireless/intel/iwlwifi/mld/iface.h
index 8dfc79fed253..1ac14996985c 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h
@@ -152,6 +152,8 @@ struct iwl_mld_emlsr {
  *	p2p device only. Set to %ROC_NUM_ACTIVITIES when not in use.
  * @aux_sta: station used for remain on channel. Used in P2P device.
  * @mlo_scan_start_wk: worker to start a deferred MLO scan
+ * @nan: NAN parameters
+ * @nan.mac_added: track whether or not the MAC was added to FW
  */
 struct iwl_mld_vif {
 	/* Add here fields that need clean up on restart */
@@ -175,6 +177,11 @@ struct iwl_mld_vif {
 	struct iwl_mld_link deflink;
 	struct iwl_mld_link __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
 
+	struct {
+		/* use only with wiphy protection */
+		bool mac_added;
+	} nan;
+
 	struct iwl_mld_emlsr emlsr;
 
 #ifdef CONFIG_PM_SLEEP
@@ -206,6 +213,20 @@ iwl_mld_vif_to_mac80211(struct iwl_mld_vif *mld_vif)
 /* Call only for interfaces that were added to the driver! */
 static inline bool iwl_mld_vif_fw_id_valid(struct iwl_mld_vif *mld_vif)
 {
+	struct ieee80211_vif *vif = iwl_mld_vif_to_mac80211(mld_vif);
+
+	switch (vif->type) {
+	case NL80211_IFTYPE_NAN_DATA:
+		return false;
+	case NL80211_IFTYPE_NAN:
+		if (!mld_vif->nan.mac_added)
+			return false;
+		break;
+	default:
+		break;
+	}
+
+	/* Should be added to FW */
 	if (WARN_ON(mld_vif->fw_id >= ARRAY_SIZE(mld_vif->mld->fw_id_to_vif)))
 		return false;
 
@@ -235,6 +256,7 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif);
 int iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
 			  u32 action);
 int iwl_mld_add_vif(struct iwl_mld *mld, struct ieee80211_vif *vif);
+int iwl_mld_add_nan_vif(struct iwl_mld *mld, struct ieee80211_vif *vif);
 void iwl_mld_rm_vif(struct iwl_mld *mld, struct ieee80211_vif *vif);
 void iwl_mld_set_vif_associated(struct iwl_mld *mld,
 				struct ieee80211_vif *vif);
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index e66b304f345a..3c84c6b0faaa 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -677,6 +677,9 @@ int iwl_mld_mac80211_add_interface(struct ieee80211_hw *hw,
 	if (ret)
 		return ret;
 
+	if (vif->type == NL80211_IFTYPE_NAN_DATA)
+		return 0;
+
 	/*
 	 * Add the default link, but not if this is an MLD vif as that implies
 	 * the HW is restarting and it will be configured by change_vif_links.
@@ -745,7 +748,7 @@ void iwl_mld_mac80211_remove_interface(struct ieee80211_hw *hw,
 
 	if (vif->type == NL80211_IFTYPE_NAN)
 		mld->nan_device_vif = NULL;
-	else
+	else if (vif->type != NL80211_IFTYPE_NAN_DATA)
 		iwl_mld_remove_link(mld, &vif->bss_conf);
 
 #ifdef CONFIG_IWLWIFI_DEBUGFS
@@ -1371,6 +1374,10 @@ iwl_mld_mac80211_link_info_changed(struct ieee80211_hw *hw,
 		if (changes & BSS_CHANGED_MU_GROUPS)
 			iwl_mld_update_mu_groups(mld, link_conf);
 		break;
+	case NL80211_IFTYPE_NAN:
+	case NL80211_IFTYPE_NAN_DATA:
+		/* NAN has no links */
+		break;
 	default:
 		/* shouldn't happen */
 		WARN_ON_ONCE(1);
@@ -1418,6 +1425,11 @@ void iwl_mld_mac80211_vif_cfg_changed(struct ieee80211_hw *hw,
 
 	lockdep_assert_wiphy(mld->wiphy);
 
+	if (vif->type == NL80211_IFTYPE_NAN) {
+		iwl_mld_nan_vif_cfg_changed(mld, vif, changes);
+		return;
+	}
+
 	if (vif->type != NL80211_IFTYPE_STATION)
 		return;
 
@@ -1613,7 +1625,7 @@ iwl_mld_mac80211_conf_tx(struct ieee80211_hw *hw,
 
 	lockdep_assert_wiphy(mld->wiphy);
 
-	if (vif->type == NL80211_IFTYPE_NAN)
+	if (vif->type == NL80211_IFTYPE_NAN || vif->type == NL80211_IFTYPE_NAN_DATA)
 		return 0;
 
 	link = iwl_mld_link_dereference_check(mld_vif, link_id);
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
index 4d8e85f2bd7c..96e79ba5234a 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
@@ -296,3 +296,25 @@ void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld,
 	wdev = ieee80211_vif_to_wdev(mld->nan_device_vif);
 	cfg80211_next_nan_dw_notif(wdev, chan, GFP_KERNEL);
 }
+
+void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
+				 struct ieee80211_vif *vif,
+				 u64 changes)
+{
+	struct ieee80211_nan_sched_cfg *sched_cfg = &vif->cfg.nan_sched;
+	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
+	bool has_sched = memchr_inv(sched_cfg->schedule, 0,
+				    sizeof(sched_cfg->schedule));
+
+	lockdep_assert_wiphy(mld->wiphy);
+
+	if (!(changes & BSS_CHANGED_NAN_LOCAL_SCHED))
+		return;
+
+	if (has_sched && !mld_vif->nan.mac_added) {
+		if (iwl_mld_add_nan_vif(mld, vif))
+			IWL_ERR(mld, "Failed to add NAN vif\n");
+	} else if (!has_sched && mld_vif->nan.mac_added) {
+		iwl_mld_rm_vif(mld, vif);
+	}
+}
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.h b/drivers/net/wireless/intel/iwlwifi/mld/nan.h
index c04d77208971..9487155cf6b3 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/nan.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.h
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
 /*
- * Copyright (C) 2025 Intel Corporation
+ * Copyright (C) 2025-2026 Intel Corporation
  */
 #ifndef __iwl_mld_nan_h__
 #define __iwl_mld_nan_h__
@@ -27,5 +27,8 @@ bool iwl_mld_cancel_nan_cluster_notif(struct iwl_mld *mld,
 bool iwl_mld_cancel_nan_dw_end_notif(struct iwl_mld *mld,
 				     struct iwl_rx_packet *pkt,
 				     u32 obj_id);
+void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
+				 struct ieee80211_vif *vif,
+				 u64 changes);
 
 #endif /* __iwl_mld_nan_h__ */
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 iwlwifi-next 03/15] wifi: iwlwifi: mld: disable queue hang detection for NAN data
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510204840.133723-1-miriam.rachel.korenblit@intel.com>

From: Johannes Berg <johannes.berg@intel.com>

Since peers on NAN data might just use ULW and/or break the
schedule, disable queue hang detection for them.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/intel/iwlwifi/mld/tx.c | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c
index 546d09a38dab..7903ce2b0beb 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.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 <net/ip.h>
 
@@ -71,14 +71,19 @@ static int iwl_mld_allocate_txq(struct iwl_mld *mld, struct ieee80211_txq *txq)
 {
 	u8 tid = txq->tid == IEEE80211_NUM_TIDS ? IWL_MGMT_TID : txq->tid;
 	u32 fw_sta_mask = iwl_mld_fw_sta_id_mask(mld, txq->sta);
-	/* We can't know when the station is asleep or awake, so we
-	 * must disable the queue hang detection.
-	 */
-	unsigned int watchdog_timeout = txq->vif->type == NL80211_IFTYPE_AP ?
-				IWL_WATCHDOG_DISABLED :
-				mld->trans->mac_cfg->base->wd_timeout;
+	unsigned int watchdog_timeout;
 	int queue, size;
 
+	switch (txq->vif->type) {
+	case NL80211_IFTYPE_AP:		/* STA might go to PS */
+	case NL80211_IFTYPE_NAN_DATA:	/* peer might ULW/break schedule */
+		watchdog_timeout = IWL_WATCHDOG_DISABLED;
+		break;
+	default:
+		watchdog_timeout = mld->trans->mac_cfg->base->wd_timeout;
+		break;
+	}
+
 	lockdep_assert_wiphy(mld->wiphy);
 
 	if (tid == IWL_MGMT_TID)
-- 
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