* [PATCH 0/2] wifi: mt76: fix NAT performance degradation on MT799x
From: Dmitry Gomzyakov @ 2026-05-10 10:29 UTC (permalink / raw)
To: linux-wireless; +Cc: Dmitry Gomzyakov
From: Dmitry Gomzyakov <nicerok11@gmail.com>
This series fixes severe IPv4 NAT download performance degradation
(30-40 Mbps limit) observed on MT799x platforms (MT7987A+MT7991A,
MT7992, MT7996).
Patch 1 adds MT7991A (0x7991) to is_mt7996() to ensure correct
chip-specific settings are used.
Patch 2 restores host-driven TXWI filling for all packets, fixing
firmware misinterpretation of NAT-modified IPv4 packets.
Dmitry Gomzyakov (2):
wifi: mt76: connac: add MT7991A (0x7991) to is_mt7996()
wifi: mt76: mt7996: always fill txwi for 802.3 packets
mt76_connac.h | 3 ++-
mt7996/mac.c | 12 +++++-------
2 files changed, 7 insertions(+), 8 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH 1/2] wifi: mt76: connac: add MT7991A (0x7991) to is_mt7996()
From: Dmitry Gomzyakov @ 2026-05-10 10:29 UTC (permalink / raw)
To: linux-wireless; +Cc: Dmitry Gomzyakov
In-Reply-To: <20260510102911.1883849-1-kyoto1337@protonmail.com>
From: Dmitry Gomzyakov <nicerok11@gmail.com>
The MT7991A chipset uses PCI device ID 0x7991 (MT7996_DEVICE_ID_2),
but is_mt7996() only checks for 0x7990. This causes MT7991A devices
to use incorrect chip-specific settings, such as:
- MSDU_CNT_V2 instead of MSDU_CNT in TX descriptors
- Wrong WTBL BMC size (32 instead of 64)
- Incorrect prefetch depth for MCU queues
Fixes: 63a4edf1cb74 ("wifi: mt76: mt7996: add macros for pci decive id")
Signed-off-by: Dmitry Gomzyakov <nicerok11@gmail.com>
---
mt76_connac.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/mt76_connac.h b/mt76_connac.h
index 51423c77..db07f3dc 100644
--- a/mt76_connac.h
+++ b/mt76_connac.h
@@ -230,7 +230,8 @@ static inline bool is_mt798x(struct mt76_dev *dev)
static inline bool is_mt7996(struct mt76_dev *dev)
{
- return mt76_chip(dev) == 0x7990;
+ u16 chip = mt76_chip(dev);
+ return chip == 0x7990 || chip == 0x7991;
}
static inline bool is_mt7992(struct mt76_dev *dev)
--
2.54.0
^ permalink raw reply related
* [PATCH 2/2] wifi: mt76: mt7996: always fill txwi for 802.3 packets
From: Dmitry Gomzyakov @ 2026-05-10 10:29 UTC (permalink / raw)
To: linux-wireless; +Cc: Dmitry Gomzyakov
In-Reply-To: <20260510102911.1883849-1-kyoto1337@protonmail.com>
From: Dmitry Gomzyakov <nicerok11@gmail.com>
Commit 55218e636092 reverted host-driven TXD filling to rely on
Software Defined Offload (SDO). However, SDO cannot correctly parse
IPv4 packets modified by NAT (changed TTL and recalculated IP/TCP
checksums), leaving TXWI mostly zero-filled.
This causes the firmware to misinterpret or drop packets,
resulting in severe throughput degradation (30-40 Mbps limit on
WiFi IPv4 download through NAT).
Always fill TXWI via mt7996_mac_write_txwi() and always set
MT_CT_INFO_APPLY_TXD, matching the behavior of mt7915 and the
proprietary driver.
Fixes: 55218e636092 ("Revert \"wifi: mt76: mt7996: fill txd by host driver\"")
Signed-off-by: Dmitry Gomzyakov <nicerok11@gmail.com>
---
mt7996/mac.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/mt7996/mac.c b/mt7996/mac.c
index 7d454e21..a219b063 100644
--- a/mt7996/mac.c
+++ b/mt7996/mac.c
@@ -1093,10 +1093,9 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
pid = mt76_tx_status_skb_add(mdev, wcid, tx_info->skb);
memset(txwi_ptr, 0, MT_TXD_SIZE);
- /* Transmit non qos data by 802.11 header and need to fill txd by host*/
- if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
- mt7996_mac_write_txwi(dev, txwi_ptr, tx_info->skb, wcid, key,
- pid, qid, 0);
+ /* Always fill txwi for firmware to avoid incorrect header processing */
+ mt7996_mac_write_txwi(dev, txwi_ptr, tx_info->skb, wcid, key,
+ pid, qid, 0);
/* MT7996 and MT7992 require driver to provide the MAC TXP for AddBA
* req
@@ -1145,9 +1144,8 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
txp->fw.nbuf = nbuf;
txp->fw.flags = cpu_to_le16(MT_CT_INFO_FROM_HOST);
-
- if (!is_8023 || pid >= MT_PACKET_ID_FIRST)
- txp->fw.flags |= cpu_to_le16(MT_CT_INFO_APPLY_TXD);
+ /* Always apply TXD to ensure correct firmware processing */
+ txp->fw.flags |= cpu_to_le16(MT_CT_INFO_APPLY_TXD);
if (!key)
txp->fw.flags |= cpu_to_le16(MT_CT_INFO_NONE_CIPHER_FRAME);
--
2.54.0
^ permalink raw reply related
* [PATCH] wifi: wilc1000: fix dma_buffer leak on bus acquire failure
From: Shitalkumar Gandhi @ 2026-05-10 11:25 UTC (permalink / raw)
To: ajay.kathat, claudiu.beznea
Cc: kvalo, linux-wireless, netdev, linux-kernel, Shitalkumar Patil
From: Shitalkumar Patil <shital@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 Patil <shital@cambiumnetworks.com>
---
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 &= ~BIT(10);
--
2.25.1
^ permalink raw reply related
* [PATCH iwlwifi-next 00/15] wifi: iwlwifi: mld: add NAN DATA support - part 1
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless
Hi,
This series adds support for NAN DATA.
Miri
---
Ilan Peer (3):
wifi: iwlwifi: mld: Fix number of antennas in NAN capabilities
wifi: iwlwifi: mld: Do not declare support for NDPE
wifi: iwlwifi: mld: Do not declare NAN support for Extended Key ID
Israel Kozitz (1):
wifi: iwlwifi: mld: fix NAN max channel switch time unit
Johannes Berg (7):
wifi: iwlwifi: mld: tlc: separate from link STA
wifi: iwlwifi: mld: disable queue hang detection for NAN data
wifi: iwlwifi: mld: support NAN and NAN_DATA interfaces
wifi: iwlwifi: mld: add NAN link management
wifi: iwlwifi: add NAN schedule command support
wifi: iwlwifi: mld: implement NAN peer station management
wifi: iwlwifi: mld: add peer schedule support
Miri Korenblit (4):
wifi: iwlwifi: mld: set NAN phy capabilities
wifi: iwlwifi: mld: use host rate for NAN management frames
wifi: iwlwifi: mld: extract NAN capabilities setting to a function
wifi: iwlwifi: mld: don't allow softAP with NAN
.../wireless/intel/iwlwifi/fw/api/mac-cfg.h | 66 +++
.../wireless/intel/iwlwifi/iwl-nvm-parse.c | 46 +-
.../wireless/intel/iwlwifi/iwl-nvm-utils.h | 8 +-
.../net/wireless/intel/iwlwifi/mld/iface.c | 147 +++++-
.../net/wireless/intel/iwlwifi/mld/iface.h | 31 ++
drivers/net/wireless/intel/iwlwifi/mld/link.c | 12 +-
drivers/net/wireless/intel/iwlwifi/mld/link.h | 7 +
.../net/wireless/intel/iwlwifi/mld/mac80211.c | 100 ++--
drivers/net/wireless/intel/iwlwifi/mld/mld.c | 4 +-
drivers/net/wireless/intel/iwlwifi/mld/mld.h | 8 +-
drivers/net/wireless/intel/iwlwifi/mld/nan.c | 452 ++++++++++++++++++
drivers/net/wireless/intel/iwlwifi/mld/nan.h | 27 +-
drivers/net/wireless/intel/iwlwifi/mld/rx.c | 2 +-
drivers/net/wireless/intel/iwlwifi/mld/sta.c | 96 +++-
drivers/net/wireless/intel/iwlwifi/mld/sta.h | 4 +-
.../wireless/intel/iwlwifi/mld/tests/utils.c | 2 -
drivers/net/wireless/intel/iwlwifi/mld/tlc.c | 263 +++++-----
drivers/net/wireless/intel/iwlwifi/mld/tx.c | 27 +-
18 files changed, 1078 insertions(+), 224 deletions(-)
--
2.34.1
^ permalink raw reply
* [PATCH iwlwifi-next 01/15] wifi: iwlwifi: mld: set NAN phy capabilities
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510183938.4182886-1-miriam.rachel.korenblit@intel.com>
Copy the HT, VHT and HE capabilities from the sbands:
- The HT capabilities from the 2.4 GHz sband (there is no difference
between the bands anyway).
- The VHT capabilities from the 5 GHz sband, obviously.
- The HE capabilities from the 2.4 GHz and for NL80211_IFTYPE_STATION.
Fix it up to include also the needed 5 GHz bits.
For HE, there are bits that are band-dependent and iftype-dependent. For
those set to what makes most sense, and leave a comment to re-visit.
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../wireless/intel/iwlwifi/iwl-nvm-parse.c | 46 ++++++++++++++++++-
.../wireless/intel/iwlwifi/iwl-nvm-utils.h | 8 +++-
.../net/wireless/intel/iwlwifi/mld/mac80211.c | 6 ++-
3 files changed, 57 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c
index 2aa47c5fa7d4..0736c8c00d4e 100644
--- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c
+++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2005-2014, 2018-2023, 2025 Intel Corporation
+ * Copyright (C) 2005-2014, 2018-2023, 2025-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
@@ -1053,6 +1053,46 @@ static void iwl_init_he_hw_capab(struct iwl_trans *trans,
iwl_init_he_6ghz_capa(trans, data, sband, tx_chains, rx_chains);
}
+static void
+iwl_init_nan_phy_capa(const struct iwl_fw *fw, struct iwl_nvm_data *data)
+{
+ const struct ieee80211_sta_he_cap *he_cap;
+
+ if (!fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_NAN_SYNC_SUPPORT))
+ return;
+
+ data->nan_phy_capa.ht = data->bands[NL80211_BAND_2GHZ].ht_cap;
+ data->nan_phy_capa.vht = data->bands[NL80211_BAND_5GHZ].vht_cap;
+
+ he_cap = ieee80211_get_he_iftype_cap(&data->bands[NL80211_BAND_2GHZ],
+ NL80211_IFTYPE_STATION);
+ if (he_cap) {
+ data->nan_phy_capa.he = *he_cap;
+ data->nan_phy_capa.he.he_cap_elem.phy_cap_info[0] |=
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G |
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
+ }
+
+ /*
+ * FIXME: we copied HE capabilities from the 2.4 GHz band,
+ * but there are bits that are band-dependent:
+ *
+ * IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_1 - 2.4 GHz - set
+ * IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_3 - 5 GHz - not set
+ * IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G - set
+ * IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G - set
+ * IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G - set
+ *
+ * We copied from STA iftype - so we have the following bits set:
+ * IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS
+ * IEEE80211_HE_PHY_CAP2_MIDAMBLE_RX_TX_MAX_NSTS
+ * IEEE80211_HE_PHY_CAP7_MAX_NC_1
+ * IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO
+ *
+ * Need to check which one should actually be set for NAN.
+ */
+}
+
void iwl_reinit_cab(struct iwl_trans *trans, struct iwl_nvm_data *data,
u8 tx_chains, u8 rx_chains, const struct iwl_fw *fw)
{
@@ -1081,6 +1121,8 @@ void iwl_reinit_cab(struct iwl_trans *trans, struct iwl_nvm_data *data,
if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax)
iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains,
fw);
+
+ iwl_init_nan_phy_capa(fw, data);
}
IWL_EXPORT_SYMBOL(iwl_reinit_cab);
@@ -2105,6 +2147,8 @@ struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans,
iwl_init_sbands(trans, nvm, channel_profile, tx_ant, rx_ant,
sbands_flags, v4, fw);
+ iwl_init_nan_phy_capa(fw, nvm);
+
iwl_free_resp(&hcmd);
return nvm;
diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h
index ac0a29a1c31f..e0c31f328a6c 100644
--- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h
+++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
- * Copyright (C) 2005-2014, 2018, 2020-2023 Intel Corporation
+ * Copyright (C) 2005-2014, 2018, 2020-2023, 2026 Intel Corporation
* Copyright (C) 2015 Intel Mobile Communications GmbH
*/
#ifndef __iwl_eeprom_parse_h__
@@ -55,6 +55,12 @@ struct iwl_nvm_data {
struct ieee80211_sband_iftype_data uhb[2];
} iftd;
+ struct {
+ struct ieee80211_sta_ht_cap ht;
+ struct ieee80211_sta_vht_cap vht;
+ struct ieee80211_sta_he_cap he;
+ } nan_phy_capa;
+
struct ieee80211_channel channels[];
};
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index b48ebec18dd5..e66b304f345a 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.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/mac80211.h>
@@ -360,6 +360,10 @@ static void iwl_mac_hw_set_wiphy(struct iwl_mld *mld)
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;
} else {
wiphy->iface_combinations = iwl_mld_iface_combinations;
/* Do not include NAN combinations */
--
2.34.1
^ permalink raw reply related
* [PATCH iwlwifi-next 02/15] wifi: iwlwifi: mld: tlc: separate from link STA
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510183938.4182886-1-miriam.rachel.korenblit@intel.com>
From: Johannes Berg <johannes.berg@intel.com>
While NAN stations have the deflink link STA and that even
carries their information, having link STAs mostly implies
having real links, and NAN muddies that by having stations
with deflink carrying their capabilities and links at the
NAN level, but no link stations corresponding to NAN links.
Separate out the data needed to build TLC commands into a
new struct iwl_mld_tlc_sta_capa data structure so that the
whole data usage in the TLC code is clarified and we won't
make assumptions, say about being able to look up the link
of an interface from the (NAN) link sta correctly, which
would result in a link but not with a chanctx.
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/tlc.c | 263 ++++++++++---------
1 file changed, 135 insertions(+), 128 deletions(-)
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c
index 78d6162d9297..eefaf3a0c2ad 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tlc.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/mac80211.h>
@@ -15,9 +15,9 @@
#include "fw/api/context.h"
#include "fw/api/dhc.h"
-static u8 iwl_mld_fw_bw_from_sta_bw(const struct ieee80211_link_sta *link_sta)
+static u8 iwl_mld_fw_bw_from_sta_bw(enum ieee80211_sta_rx_bandwidth bandwidth)
{
- switch (link_sta->bandwidth) {
+ switch (bandwidth) {
case IEEE80211_STA_RX_BW_320:
return IWL_TLC_MNG_CH_WIDTH_320MHZ;
case IEEE80211_STA_RX_BW_160:
@@ -32,40 +32,61 @@ static u8 iwl_mld_fw_bw_from_sta_bw(const struct ieee80211_link_sta *link_sta)
}
}
+struct iwl_mld_tlc_sta_capa {
+ u16 non_ht_rates;
+ u16 max_amsdu_len;
+ u8 rx_nss;
+ enum ieee80211_sta_rx_bandwidth bandwidth;
+ enum ieee80211_smps_mode smps_mode;
+ const struct ieee80211_sta_ht_cap *ht_cap;
+ const struct ieee80211_sta_vht_cap *vht_cap;
+ const struct ieee80211_sta_he_cap *he_cap;
+ const struct ieee80211_sta_eht_cap *eht_cap;
+ const struct ieee80211_sta_uhr_cap *uhr_cap;
+
+ const struct ieee80211_sta_he_cap *own_he_cap;
+ const struct ieee80211_sta_eht_cap *own_eht_cap;
+ const struct ieee80211_sta_uhr_cap *own_uhr_cap;
+};
+
static __le16
iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld,
- struct ieee80211_vif *vif,
- struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_he_cap *own_he_cap,
- const struct ieee80211_sta_eht_cap *own_eht_cap,
- const struct ieee80211_sta_uhr_cap *own_uhr_cap)
+ struct iwl_mld_tlc_sta_capa *capa)
{
- struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap;
- struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap;
- struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
- bool has_vht = vht_cap->vht_supported;
+ const struct ieee80211_sta_ht_cap *ht_cap = capa->ht_cap;
+ const struct ieee80211_sta_vht_cap *vht_cap = capa->vht_cap;
+ const struct ieee80211_sta_he_cap *he_cap = capa->he_cap;
+ const struct ieee80211_sta_eht_cap *eht_cap = capa->eht_cap;
+ const struct ieee80211_sta_uhr_cap *uhr_cap = capa->uhr_cap;
+ const struct ieee80211_sta_he_cap *own_he_cap = capa->own_he_cap;
+ const struct ieee80211_sta_eht_cap *own_eht_cap = capa->own_eht_cap;
+ const struct ieee80211_sta_uhr_cap *own_uhr_cap = capa->own_uhr_cap;
+ bool has_vht = vht_cap && vht_cap->vht_supported;
u16 flags = 0;
/* STBC flags */
if (mld->cfg->ht_params.stbc &&
(hweight8(iwl_mld_get_valid_tx_ant(mld)) > 1)) {
- if (he_cap->has_he && he_cap->he_cap_elem.phy_cap_info[2] &
- IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ)
+ if (he_cap && he_cap->has_he &&
+ he_cap->he_cap_elem.phy_cap_info[2] &
+ IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ)
flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK;
- else if (vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK)
+ else if (vht_cap &&
+ vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK)
flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK;
- else if (ht_cap->cap & IEEE80211_HT_CAP_RX_STBC)
+ else if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_RX_STBC)
flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK;
}
/* LDPC */
if (mld->cfg->ht_params.ldpc &&
- ((ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) ||
+ ((ht_cap && ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) ||
(has_vht && (vht_cap->cap & IEEE80211_VHT_CAP_RXLDPC))))
flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK;
- if (he_cap->has_he && (he_cap->he_cap_elem.phy_cap_info[1] &
- IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD))
+ if (he_cap && he_cap->has_he &&
+ (he_cap->he_cap_elem.phy_cap_info[1] &
+ IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD))
flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK;
if (own_he_cap &&
@@ -74,7 +95,7 @@ iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld,
flags &= ~IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK;
/* DCM */
- if (he_cap->has_he &&
+ if (he_cap && he_cap->has_he &&
(he_cap->he_cap_elem.phy_cap_info[3] &
IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_MASK &&
own_he_cap &&
@@ -85,15 +106,15 @@ iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld,
/* Extra EHT LTF */
if (own_eht_cap &&
own_eht_cap->eht_cap_elem.phy_cap_info[5] &
- IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF &&
- link_sta->eht_cap.has_eht &&
- link_sta->eht_cap.eht_cap_elem.phy_cap_info[5] &
- IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF) {
+ IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF &&
+ eht_cap && eht_cap->has_eht &&
+ eht_cap->eht_cap_elem.phy_cap_info[5] &
+ IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF) {
flags |= IWL_TLC_MNG_CFG_FLAGS_EHT_EXTRA_LTF_MSK;
}
- if (link_sta->uhr_cap.has_uhr && own_uhr_cap &&
- link_sta->uhr_cap.phy.cap & IEEE80211_UHR_PHY_CAP_ELR_RX &&
+ if (uhr_cap && uhr_cap->has_uhr && own_uhr_cap &&
+ uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_RX &&
own_uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_TX)
flags |= IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_1_5_MBPS_MSK |
IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_3_MBPS_MSK;
@@ -114,27 +135,27 @@ static u8 iwl_mld_get_fw_chains(struct iwl_mld *mld)
return fw_chains;
}
-static u8 iwl_mld_get_fw_sgi(struct ieee80211_link_sta *link_sta)
+static u8 iwl_mld_get_fw_sgi(struct iwl_mld_tlc_sta_capa *capa)
{
- struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap;
- struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap;
- struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
+ const struct ieee80211_sta_ht_cap *ht_cap = capa->ht_cap;
+ const struct ieee80211_sta_vht_cap *vht_cap = capa->vht_cap;
+ const struct ieee80211_sta_he_cap *he_cap = capa->he_cap;
u8 sgi_chwidths = 0;
/* If the association supports HE, HT/VHT rates will never be used for
* Tx and therefor there's no need to set the
* sgi-per-channel-width-support bits
*/
- if (he_cap->has_he)
+ if (he_cap && he_cap->has_he)
return 0;
- if (ht_cap->cap & IEEE80211_HT_CAP_SGI_20)
+ if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_SGI_20)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_20MHZ);
- if (ht_cap->cap & IEEE80211_HT_CAP_SGI_40)
+ if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_SGI_40)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_40MHZ);
- if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80)
+ if (vht_cap && vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_80MHZ);
- if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160)
+ if (vht_cap && vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_160MHZ);
return sgi_chwidths;
@@ -164,31 +185,30 @@ iwl_mld_get_highest_fw_mcs(const struct ieee80211_sta_vht_cap *vht_cap,
}
static void
-iwl_mld_fill_vht_rates(const struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_vht_cap *vht_cap,
+iwl_mld_fill_vht_rates(struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
u32 supp;
int i, highest_mcs;
- u8 max_nss = link_sta->rx_nss;
+ u8 max_nss = capa->rx_nss;
struct ieee80211_vht_cap ieee_vht_cap = {
- .vht_cap_info = cpu_to_le32(vht_cap->cap),
- .supp_mcs = vht_cap->vht_mcs,
+ .vht_cap_info = cpu_to_le32(capa->vht_cap->cap),
+ .supp_mcs = capa->vht_cap->vht_mcs,
};
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC)
max_nss = 1;
for (i = 0; i < max_nss && i < IWL_TLC_NSS_MAX; i++) {
int nss = i + 1;
- highest_mcs = iwl_mld_get_highest_fw_mcs(vht_cap, nss);
+ highest_mcs = iwl_mld_get_highest_fw_mcs(capa->vht_cap, nss);
if (!highest_mcs)
continue;
supp = BIT(highest_mcs + 1) - 1;
- if (link_sta->bandwidth == IEEE80211_STA_RX_BW_20)
+ if (capa->bandwidth == IEEE80211_STA_RX_BW_20)
supp &= ~BIT(IWL_TLC_MNG_HT_RATE_MCS9);
cmd->ht_rates[i][IWL_TLC_MCS_PER_BW_80] = cpu_to_le32(supp);
@@ -196,7 +216,7 @@ iwl_mld_fill_vht_rates(const struct ieee80211_link_sta *link_sta,
* configuration is supported - only for MCS 0 since we already
* decoded the MCS bits anyway ourselves.
*/
- if (link_sta->bandwidth == IEEE80211_STA_RX_BW_160 &&
+ if (capa->bandwidth == IEEE80211_STA_RX_BW_160 &&
ieee80211_get_vht_max_nss(&ieee_vht_cap,
IEEE80211_VHT_CHANWIDTH_160MHZ,
0, true, nss) >= nss)
@@ -223,20 +243,19 @@ static u32 iwl_mld_he_mac80211_mcs_to_fw_mcs(u16 mcs)
}
static void
-iwl_mld_fill_he_rates(const struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_he_cap *own_he_cap,
+iwl_mld_fill_he_rates(struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
- const struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
+ const struct ieee80211_sta_he_cap *he_cap = capa->he_cap;
u16 mcs_160 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160);
u16 mcs_80 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80);
- u16 tx_mcs_80 = le16_to_cpu(own_he_cap->he_mcs_nss_supp.tx_mcs_80);
- u16 tx_mcs_160 = le16_to_cpu(own_he_cap->he_mcs_nss_supp.tx_mcs_160);
+ u16 tx_mcs_80 = le16_to_cpu(capa->own_he_cap->he_mcs_nss_supp.tx_mcs_80);
+ u16 tx_mcs_160 = le16_to_cpu(capa->own_he_cap->he_mcs_nss_supp.tx_mcs_160);
int i;
- u8 nss = link_sta->rx_nss;
+ u8 nss = capa->rx_nss;
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC)
nss = 1;
for (i = 0; i < nss && i < IWL_TLC_NSS_MAX; i++) {
@@ -313,17 +332,15 @@ static u8 iwl_mld_get_eht_max_nss(u8 rx_nss, u8 tx_nss)
static void
iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
- const struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_he_cap *own_he_cap,
- const struct ieee80211_sta_eht_cap *own_eht_cap,
+ struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
/* peer RX mcs capa */
const struct ieee80211_eht_mcs_nss_supp *eht_rx_mcs =
- &link_sta->eht_cap.eht_mcs_nss_supp;
+ &capa->eht_cap->eht_mcs_nss_supp;
/* our TX mcs capa */
const struct ieee80211_eht_mcs_nss_supp *eht_tx_mcs =
- &own_eht_cap->eht_mcs_nss_supp;
+ &capa->own_eht_cap->eht_mcs_nss_supp;
enum IWL_TLC_MCS_PER_BW bw;
struct ieee80211_eht_mcs_nss_supp_20mhz_only mcs_rx_20;
@@ -331,7 +348,7 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
/* peer is 20 MHz only */
if (vif->type == NL80211_IFTYPE_AP &&
- !(link_sta->he_cap.he_cap_elem.phy_cap_info[0] &
+ !(capa->he_cap->he_cap_elem.phy_cap_info[0] &
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL)) {
mcs_rx_20 = eht_rx_mcs->only_20mhz;
} else {
@@ -346,7 +363,7 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
}
/* NIC is capable of 20 MHz only */
- if (!(own_he_cap->he_cap_elem.phy_cap_info[0] &
+ if (!(capa->own_he_cap->he_cap_elem.phy_cap_info[0] &
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL)) {
mcs_tx_20 = eht_tx_mcs->only_20mhz;
} else {
@@ -402,67 +419,51 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
}
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC ||
- link_sta->rx_nss < 2)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC ||
+ capa->rx_nss < 2)
memset(cmd->ht_rates[IWL_TLC_NSS_2], 0,
sizeof(cmd->ht_rates[IWL_TLC_NSS_2]));
}
static void
-iwl_mld_fill_supp_rates(struct iwl_mld *mld, struct ieee80211_vif *vif,
- struct ieee80211_link_sta *link_sta,
- struct ieee80211_supported_band *sband,
- const struct ieee80211_sta_he_cap *own_he_cap,
- const struct ieee80211_sta_eht_cap *own_eht_cap,
- const struct ieee80211_sta_uhr_cap *own_uhr_cap,
+iwl_mld_fill_supp_rates(struct iwl_mld *mld,
+ struct ieee80211_vif *vif,
+ struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
- int i;
- u16 non_ht_rates = 0;
- unsigned long rates_bitmap;
- const struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap;
- const struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap;
- const struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
-
- /* non HT rates */
- rates_bitmap = link_sta->supp_rates[sband->band];
- for_each_set_bit(i, &rates_bitmap, BITS_PER_LONG)
- non_ht_rates |= BIT(sband->bitrates[i].hw_value);
-
- cmd->non_ht_rates = cpu_to_le16(non_ht_rates);
+ cmd->non_ht_rates = cpu_to_le16(capa->non_ht_rates);
cmd->mode = IWL_TLC_MNG_MODE_NON_HT;
- if (link_sta->uhr_cap.has_uhr && own_uhr_cap) {
+ if (capa->uhr_cap && capa->uhr_cap->has_uhr && capa->own_uhr_cap) {
cmd->mode = IWL_TLC_MNG_MODE_UHR;
/*
* FIXME: spec currently inherits from EHT but has no
* finer MCS bits. Once that's there, need to add them
* to the bitmaps (and maybe copy this to UHR, or so.)
*/
- iwl_mld_fill_eht_rates(vif, link_sta, own_he_cap,
- own_eht_cap, cmd);
- } else if (link_sta->eht_cap.has_eht && own_he_cap && own_eht_cap) {
+ iwl_mld_fill_eht_rates(vif, capa, cmd);
+ } else if (capa->eht_cap && capa->eht_cap->has_eht &&
+ capa->own_he_cap && capa->own_eht_cap) {
cmd->mode = IWL_TLC_MNG_MODE_EHT;
- iwl_mld_fill_eht_rates(vif, link_sta, own_he_cap,
- own_eht_cap, cmd);
- } else if (he_cap->has_he && own_he_cap) {
+ iwl_mld_fill_eht_rates(vif, capa, cmd);
+ } else if (capa->he_cap && capa->he_cap->has_he && capa->own_he_cap) {
cmd->mode = IWL_TLC_MNG_MODE_HE;
- iwl_mld_fill_he_rates(link_sta, own_he_cap, cmd);
- } else if (vht_cap->vht_supported) {
+ iwl_mld_fill_he_rates(capa, cmd);
+ } else if (capa->vht_cap && capa->vht_cap->vht_supported) {
cmd->mode = IWL_TLC_MNG_MODE_VHT;
- iwl_mld_fill_vht_rates(link_sta, vht_cap, cmd);
- } else if (ht_cap->ht_supported) {
+ iwl_mld_fill_vht_rates(capa, cmd);
+ } else if (capa->ht_cap && capa->ht_cap->ht_supported) {
cmd->mode = IWL_TLC_MNG_MODE_HT;
cmd->ht_rates[IWL_TLC_NSS_1][IWL_TLC_MCS_PER_BW_80] =
- cpu_to_le32(ht_cap->mcs.rx_mask[0]);
+ cpu_to_le32(capa->ht_cap->mcs.rx_mask[0]);
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC)
cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_MCS_PER_BW_80] =
0;
else
cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_MCS_PER_BW_80] =
- cpu_to_le32(ht_cap->mcs.rx_mask[1]);
+ cpu_to_le32(capa->ht_cap->mcs.rx_mask[1]);
}
}
@@ -527,60 +528,35 @@ static int iwl_mld_convert_tlc_cmd_to_v4(struct iwl_tlc_config_cmd *cmd,
static void iwl_mld_send_tlc_cmd(struct iwl_mld *mld,
struct ieee80211_vif *vif,
- struct ieee80211_link_sta *link_sta,
- struct ieee80211_bss_conf *link)
+ struct iwl_mld_sta *mld_sta,
+ int fw_sta_id, int phy_id,
+ struct iwl_mld_tlc_sta_capa *capa)
{
- struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
- struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
- enum nl80211_band band = link->chanreq.oper.chan->band;
- struct ieee80211_supported_band *sband = mld->hw->wiphy->bands[band];
- const struct ieee80211_sta_he_cap *own_he_cap =
- ieee80211_get_he_iftype_cap_vif(sband, vif);
- const struct ieee80211_sta_eht_cap *own_eht_cap =
- ieee80211_get_eht_iftype_cap_vif(sband, vif);
- const struct ieee80211_sta_uhr_cap *own_uhr_cap =
- ieee80211_get_uhr_iftype_cap_vif(sband, vif);
struct iwl_tlc_config_cmd cmd = {
/* For AP mode, use 20 MHz until the STA is authorized */
.max_ch_width = mld_sta->sta_state > IEEE80211_STA_ASSOC ?
- iwl_mld_fw_bw_from_sta_bw(link_sta) :
+ iwl_mld_fw_bw_from_sta_bw(capa->bandwidth) :
IWL_TLC_MNG_CH_WIDTH_20MHZ,
- .flags = iwl_mld_get_tlc_cmd_flags(mld, vif, link_sta,
- own_he_cap, own_eht_cap,
- own_uhr_cap),
+ .flags = iwl_mld_get_tlc_cmd_flags(mld, capa),
.chains = iwl_mld_get_fw_chains(mld),
- .sgi_ch_width_supp = iwl_mld_get_fw_sgi(link_sta),
- .max_mpdu_len = cpu_to_le16(link_sta->agg.max_amsdu_len),
+ .sgi_ch_width_supp = iwl_mld_get_fw_sgi(capa),
+ .max_mpdu_len = cpu_to_le16(capa->max_amsdu_len),
};
- int fw_sta_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta);
u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, TLC_MNG_CONFIG_CMD);
u8 cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, cmd_id, 0);
- struct ieee80211_chanctx_conf *chan_ctx;
struct iwl_tlc_config_cmd_v5 cmd_v5 = {};
struct iwl_tlc_config_cmd_v4 cmd_v4 = {};
void *cmd_ptr;
u8 cmd_size;
- u32 phy_id;
int ret;
if (fw_sta_id < 0)
return;
cmd.sta_mask = cpu_to_le32(BIT(fw_sta_id));
-
- if (WARN_ON_ONCE(!mld_link))
- return;
-
- chan_ctx = rcu_dereference_wiphy(mld->wiphy, mld_link->chan_ctx);
- if (WARN_ON(!chan_ctx))
- return;
-
- phy_id = iwl_mld_phy_from_mac80211(chan_ctx)->fw_id;
cmd.phy_id = cpu_to_le32(phy_id);
- iwl_mld_fill_supp_rates(mld, vif, link_sta, sband,
- own_he_cap, own_eht_cap,
- own_uhr_cap, &cmd);
+ iwl_mld_fill_supp_rates(mld, vif, capa, &cmd);
if (cmd_ver == 6) {
cmd_ptr = &cmd;
@@ -647,10 +623,20 @@ void iwl_mld_config_tlc_link(struct iwl_mld *mld,
struct ieee80211_link_sta *link_sta)
{
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
+ struct ieee80211_chanctx_conf *chan_ctx;
+ struct ieee80211_supported_band *sband;
+ struct iwl_mld_tlc_sta_capa capa = {};
+ unsigned long rates_bitmap;
+ enum nl80211_band band;
+ int fw_sta_id, i;
- if (WARN_ON_ONCE(!link_conf->chanreq.oper.chan))
+ chan_ctx = rcu_dereference_wiphy(mld->wiphy, link_conf->chanctx_conf);
+ if (WARN_ON_ONCE(!chan_ctx))
return;
+ band = chan_ctx->def.chan->band;
+ sband = mld->hw->wiphy->bands[band];
+
/* Before we have information about a station, configure the A-MSDU RC
* limit such that iwlmd and mac80211 would not be allowed to build
* A-MSDUs.
@@ -660,7 +646,28 @@ void iwl_mld_config_tlc_link(struct iwl_mld *mld,
ieee80211_sta_recalc_aggregates(link_sta->sta);
}
- iwl_mld_send_tlc_cmd(mld, vif, link_sta, link_conf);
+ /* non HT rates */
+ rates_bitmap = link_sta->supp_rates[sband->band];
+ for_each_set_bit(i, &rates_bitmap, BITS_PER_LONG)
+ capa.non_ht_rates |= BIT(sband->bitrates[i].hw_value);
+
+ capa.rx_nss = link_sta->rx_nss;
+ capa.smps_mode = link_sta->smps_mode;
+ capa.bandwidth = link_sta->bandwidth;
+ capa.max_amsdu_len = link_sta->agg.max_amsdu_len;
+ capa.ht_cap = &link_sta->ht_cap;
+ capa.vht_cap = &link_sta->vht_cap;
+ capa.he_cap = &link_sta->he_cap;
+ capa.eht_cap = &link_sta->eht_cap;
+ capa.uhr_cap = &link_sta->uhr_cap;
+ capa.own_he_cap = ieee80211_get_he_iftype_cap_vif(sband, vif);
+ capa.own_eht_cap = ieee80211_get_eht_iftype_cap_vif(sband, vif);
+ capa.own_uhr_cap = ieee80211_get_uhr_iftype_cap_vif(sband, vif);
+
+ fw_sta_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta);
+ iwl_mld_send_tlc_cmd(mld, vif, mld_sta, fw_sta_id,
+ iwl_mld_phy_from_mac80211(chan_ctx)->fw_id,
+ &capa);
}
void iwl_mld_tlc_update_phy(struct iwl_mld *mld, struct ieee80211_vif *vif,
--
2.34.1
^ permalink raw reply related
* [PATCH iwlwifi-next 03/15] wifi: iwlwifi: mld: disable queue hang detection for NAN data
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510183938.4182886-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
* [PATCH iwlwifi-next 04/15] wifi: iwlwifi: mld: support NAN and NAN_DATA interfaces
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510183938.4182886-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 iwlwifi-next 05/15] wifi: iwlwifi: mld: add NAN link management
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510183938.4182886-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 iwlwifi-next 06/15] wifi: iwlwifi: add NAN schedule command support
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510183938.4182886-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 iwlwifi-next 07/15] wifi: iwlwifi: mld: implement NAN peer station management
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510183938.4182886-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 iwlwifi-next 08/15] wifi: iwlwifi: mld: add peer schedule support
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510183938.4182886-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 iwlwifi-next 09/15] wifi: iwlwifi: mld: use host rate for NAN management frames
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510183938.4182886-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 iwlwifi-next 10/15] wifi: iwlwifi: mld: extract NAN capabilities setting to a function
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260510183938.4182886-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 iwlwifi-next 11/15] wifi: iwlwifi: mld: Fix number of antennas in NAN capabilities
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Ilan Peer
In-Reply-To: <20260510183938.4182886-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 iwlwifi-next 12/15] wifi: iwlwifi: mld: Do not declare support for NDPE
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Ilan Peer
In-Reply-To: <20260510183938.4182886-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 iwlwifi-next 13/15] wifi: iwlwifi: mld: Do not declare NAN support for Extended Key ID
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Ilan Peer
In-Reply-To: <20260510183938.4182886-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 iwlwifi-next 14/15] wifi: iwlwifi: mld: fix NAN max channel switch time unit
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Israel Kozitz, Ilan Peer
In-Reply-To: <20260510183938.4182886-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 iwlwifi-next 15/15] wifi: iwlwifi: mld: don't allow softAP with NAN
From: Miri Korenblit @ 2026-05-10 18:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260510183938.4182886-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] wifi: cfg80211: validate chandef before calling cfg80211_set_monitor_channel
From: Kartik Nair @ 2026-05-10 20:24 UTC (permalink / raw)
To: johannes
Cc: linux-wireless, linux-kernel, Kartik Nair,
syzbot+02a1a03b8622d3c7d1c9
cfg80211_wext_siwfreq() constructs a channel definition for monitor
mode but passes it to cfg80211_set_monitor_channel() without first
validating it with cfg80211_chandef_valid(). This causes a WARN_ON
in cfg80211_chandef_dfs_required() when it receives an invalid chandef.
Add the missing cfg80211_chandef_valid() check before calling
cfg80211_set_monitor_channel() to return -EINVAL early on invalid
channel definitions, consistent with how other callers handle this.
Reported-by: syzbot+02a1a03b8622d3c7d1c9@syzkaller.appspotmail.com
Signed-off-by: Kartik Nair <contact.kartikn@gmail.com>
---
net/wireless/wext-compat.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c
index 22d9d9bae..63d145b52 100644
--- a/net/wireless/wext-compat.c
+++ b/net/wireless/wext-compat.c
@@ -789,6 +789,8 @@ static int cfg80211_wext_siwfreq(struct net_device *dev,
chandef.chan = ieee80211_get_channel(&rdev->wiphy, freq);
if (!chandef.chan)
return -EINVAL;
+ if (!cfg80211_chandef_valid(&chandef))
+ return -EINVAL;
return cfg80211_set_monitor_channel(rdev, dev, &chandef);
case NL80211_IFTYPE_MESH_POINT:
freq = cfg80211_wext_freq(wextfreq);
--
2.50.0
^ permalink raw reply related
* [PATCH v2 iwlwifi-next 00/15] wifi: iwlwifi: mld: add NAN DATA support - part 1
From: Miri Korenblit @ 2026-05-10 20:48 UTC (permalink / raw)
To: linux-wireless
Hi,
This series adds support for NAN DATA.
Miri
---
Ilan Peer (3):
wifi: iwlwifi: mld: Fix number of antennas in NAN capabilities
wifi: iwlwifi: mld: Do not declare support for NDPE
wifi: iwlwifi: mld: Do not declare NAN support for Extended Key ID
Israel Kozitz (1):
wifi: iwlwifi: mld: fix NAN max channel switch time unit
Johannes Berg (7):
wifi: iwlwifi: mld: tlc: separate from link STA
wifi: iwlwifi: mld: disable queue hang detection for NAN data
wifi: iwlwifi: mld: support NAN and NAN_DATA interfaces
wifi: iwlwifi: mld: add NAN link management
wifi: iwlwifi: add NAN schedule command support
wifi: iwlwifi: mld: implement NAN peer station management
wifi: iwlwifi: mld: add peer schedule support
Miri Korenblit (4):
wifi: iwlwifi: mld: set NAN phy capabilities
wifi: iwlwifi: mld: use host rate for NAN management frames
wifi: iwlwifi: mld: extract NAN capabilities setting to a function
wifi: iwlwifi: mld: don't allow softAP with NAN
.../wireless/intel/iwlwifi/fw/api/mac-cfg.h | 66 +++
.../wireless/intel/iwlwifi/iwl-nvm-parse.c | 46 +-
.../wireless/intel/iwlwifi/iwl-nvm-utils.h | 8 +-
.../net/wireless/intel/iwlwifi/mld/iface.c | 147 +++++-
.../net/wireless/intel/iwlwifi/mld/iface.h | 31 ++
drivers/net/wireless/intel/iwlwifi/mld/link.c | 12 +-
drivers/net/wireless/intel/iwlwifi/mld/link.h | 7 +
.../net/wireless/intel/iwlwifi/mld/mac80211.c | 100 ++--
drivers/net/wireless/intel/iwlwifi/mld/mld.c | 4 +-
drivers/net/wireless/intel/iwlwifi/mld/mld.h | 8 +-
drivers/net/wireless/intel/iwlwifi/mld/nan.c | 452 ++++++++++++++++++
drivers/net/wireless/intel/iwlwifi/mld/nan.h | 27 +-
drivers/net/wireless/intel/iwlwifi/mld/rx.c | 2 +-
drivers/net/wireless/intel/iwlwifi/mld/sta.c | 96 +++-
drivers/net/wireless/intel/iwlwifi/mld/sta.h | 4 +-
.../wireless/intel/iwlwifi/mld/tests/utils.c | 2 -
drivers/net/wireless/intel/iwlwifi/mld/tlc.c | 267 ++++++-----
drivers/net/wireless/intel/iwlwifi/mld/tx.c | 27 +-
18 files changed, 1082 insertions(+), 224 deletions(-)
--
2.34.1
---
V2: fixes a bug in "wifi: iwlwifi: mld: tlc: separate from link STA"
^ permalink raw reply
* [PATCH v2 iwlwifi-next 01/15] wifi: iwlwifi: mld: set NAN phy capabilities
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>
Copy the HT, VHT and HE capabilities from the sbands:
- The HT capabilities from the 2.4 GHz sband (there is no difference
between the bands anyway).
- The VHT capabilities from the 5 GHz sband, obviously.
- The HE capabilities from the 2.4 GHz and for NL80211_IFTYPE_STATION.
Fix it up to include also the needed 5 GHz bits.
For HE, there are bits that are band-dependent and iftype-dependent. For
those set to what makes most sense, and leave a comment to re-visit.
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../wireless/intel/iwlwifi/iwl-nvm-parse.c | 46 ++++++++++++++++++-
.../wireless/intel/iwlwifi/iwl-nvm-utils.h | 8 +++-
.../net/wireless/intel/iwlwifi/mld/mac80211.c | 6 ++-
3 files changed, 57 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c
index 2aa47c5fa7d4..0736c8c00d4e 100644
--- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c
+++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2005-2014, 2018-2023, 2025 Intel Corporation
+ * Copyright (C) 2005-2014, 2018-2023, 2025-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
@@ -1053,6 +1053,46 @@ static void iwl_init_he_hw_capab(struct iwl_trans *trans,
iwl_init_he_6ghz_capa(trans, data, sband, tx_chains, rx_chains);
}
+static void
+iwl_init_nan_phy_capa(const struct iwl_fw *fw, struct iwl_nvm_data *data)
+{
+ const struct ieee80211_sta_he_cap *he_cap;
+
+ if (!fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_NAN_SYNC_SUPPORT))
+ return;
+
+ data->nan_phy_capa.ht = data->bands[NL80211_BAND_2GHZ].ht_cap;
+ data->nan_phy_capa.vht = data->bands[NL80211_BAND_5GHZ].vht_cap;
+
+ he_cap = ieee80211_get_he_iftype_cap(&data->bands[NL80211_BAND_2GHZ],
+ NL80211_IFTYPE_STATION);
+ if (he_cap) {
+ data->nan_phy_capa.he = *he_cap;
+ data->nan_phy_capa.he.he_cap_elem.phy_cap_info[0] |=
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G |
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
+ }
+
+ /*
+ * FIXME: we copied HE capabilities from the 2.4 GHz band,
+ * but there are bits that are band-dependent:
+ *
+ * IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_1 - 2.4 GHz - set
+ * IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_3 - 5 GHz - not set
+ * IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G - set
+ * IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G - set
+ * IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G - set
+ *
+ * We copied from STA iftype - so we have the following bits set:
+ * IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS
+ * IEEE80211_HE_PHY_CAP2_MIDAMBLE_RX_TX_MAX_NSTS
+ * IEEE80211_HE_PHY_CAP7_MAX_NC_1
+ * IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO
+ *
+ * Need to check which one should actually be set for NAN.
+ */
+}
+
void iwl_reinit_cab(struct iwl_trans *trans, struct iwl_nvm_data *data,
u8 tx_chains, u8 rx_chains, const struct iwl_fw *fw)
{
@@ -1081,6 +1121,8 @@ void iwl_reinit_cab(struct iwl_trans *trans, struct iwl_nvm_data *data,
if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax)
iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains,
fw);
+
+ iwl_init_nan_phy_capa(fw, data);
}
IWL_EXPORT_SYMBOL(iwl_reinit_cab);
@@ -2105,6 +2147,8 @@ struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans,
iwl_init_sbands(trans, nvm, channel_profile, tx_ant, rx_ant,
sbands_flags, v4, fw);
+ iwl_init_nan_phy_capa(fw, nvm);
+
iwl_free_resp(&hcmd);
return nvm;
diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h
index ac0a29a1c31f..e0c31f328a6c 100644
--- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h
+++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
- * Copyright (C) 2005-2014, 2018, 2020-2023 Intel Corporation
+ * Copyright (C) 2005-2014, 2018, 2020-2023, 2026 Intel Corporation
* Copyright (C) 2015 Intel Mobile Communications GmbH
*/
#ifndef __iwl_eeprom_parse_h__
@@ -55,6 +55,12 @@ struct iwl_nvm_data {
struct ieee80211_sband_iftype_data uhb[2];
} iftd;
+ struct {
+ struct ieee80211_sta_ht_cap ht;
+ struct ieee80211_sta_vht_cap vht;
+ struct ieee80211_sta_he_cap he;
+ } nan_phy_capa;
+
struct ieee80211_channel channels[];
};
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index b48ebec18dd5..e66b304f345a 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.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/mac80211.h>
@@ -360,6 +360,10 @@ static void iwl_mac_hw_set_wiphy(struct iwl_mld *mld)
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;
} else {
wiphy->iface_combinations = iwl_mld_iface_combinations;
/* Do not include NAN combinations */
--
2.34.1
^ permalink raw reply related
* [PATCH v2 iwlwifi-next 02/15] wifi: iwlwifi: mld: tlc: separate from link STA
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>
While NAN stations have the deflink link STA and that even
carries their information, having link STAs mostly implies
having real links, and NAN muddies that by having stations
with deflink carrying their capabilities and links at the
NAN level, but no link stations corresponding to NAN links.
Separate out the data needed to build TLC commands into a
new struct iwl_mld_tlc_sta_capa data structure so that the
whole data usage in the TLC code is clarified and we won't
make assumptions, say about being able to look up the link
of an interface from the (NAN) link sta correctly, which
would result in a link but not with a chanctx.
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/tlc.c | 267 ++++++++++---------
1 file changed, 139 insertions(+), 128 deletions(-)
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c
index 78d6162d9297..b1722982db9c 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tlc.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/mac80211.h>
@@ -15,9 +15,9 @@
#include "fw/api/context.h"
#include "fw/api/dhc.h"
-static u8 iwl_mld_fw_bw_from_sta_bw(const struct ieee80211_link_sta *link_sta)
+static u8 iwl_mld_fw_bw_from_sta_bw(enum ieee80211_sta_rx_bandwidth bandwidth)
{
- switch (link_sta->bandwidth) {
+ switch (bandwidth) {
case IEEE80211_STA_RX_BW_320:
return IWL_TLC_MNG_CH_WIDTH_320MHZ;
case IEEE80211_STA_RX_BW_160:
@@ -32,40 +32,61 @@ static u8 iwl_mld_fw_bw_from_sta_bw(const struct ieee80211_link_sta *link_sta)
}
}
+struct iwl_mld_tlc_sta_capa {
+ u16 non_ht_rates;
+ u16 max_amsdu_len;
+ u8 rx_nss;
+ enum ieee80211_sta_rx_bandwidth bandwidth;
+ enum ieee80211_smps_mode smps_mode;
+ const struct ieee80211_sta_ht_cap *ht_cap;
+ const struct ieee80211_sta_vht_cap *vht_cap;
+ const struct ieee80211_sta_he_cap *he_cap;
+ const struct ieee80211_sta_eht_cap *eht_cap;
+ const struct ieee80211_sta_uhr_cap *uhr_cap;
+
+ const struct ieee80211_sta_he_cap *own_he_cap;
+ const struct ieee80211_sta_eht_cap *own_eht_cap;
+ const struct ieee80211_sta_uhr_cap *own_uhr_cap;
+};
+
static __le16
iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld,
- struct ieee80211_vif *vif,
- struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_he_cap *own_he_cap,
- const struct ieee80211_sta_eht_cap *own_eht_cap,
- const struct ieee80211_sta_uhr_cap *own_uhr_cap)
+ struct iwl_mld_tlc_sta_capa *capa)
{
- struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap;
- struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap;
- struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
- bool has_vht = vht_cap->vht_supported;
+ const struct ieee80211_sta_ht_cap *ht_cap = capa->ht_cap;
+ const struct ieee80211_sta_vht_cap *vht_cap = capa->vht_cap;
+ const struct ieee80211_sta_he_cap *he_cap = capa->he_cap;
+ const struct ieee80211_sta_eht_cap *eht_cap = capa->eht_cap;
+ const struct ieee80211_sta_uhr_cap *uhr_cap = capa->uhr_cap;
+ const struct ieee80211_sta_he_cap *own_he_cap = capa->own_he_cap;
+ const struct ieee80211_sta_eht_cap *own_eht_cap = capa->own_eht_cap;
+ const struct ieee80211_sta_uhr_cap *own_uhr_cap = capa->own_uhr_cap;
+ bool has_vht = vht_cap && vht_cap->vht_supported;
u16 flags = 0;
/* STBC flags */
if (mld->cfg->ht_params.stbc &&
(hweight8(iwl_mld_get_valid_tx_ant(mld)) > 1)) {
- if (he_cap->has_he && he_cap->he_cap_elem.phy_cap_info[2] &
- IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ)
+ if (he_cap && he_cap->has_he &&
+ he_cap->he_cap_elem.phy_cap_info[2] &
+ IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ)
flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK;
- else if (vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK)
+ else if (vht_cap &&
+ vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK)
flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK;
- else if (ht_cap->cap & IEEE80211_HT_CAP_RX_STBC)
+ else if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_RX_STBC)
flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK;
}
/* LDPC */
if (mld->cfg->ht_params.ldpc &&
- ((ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) ||
+ ((ht_cap && ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) ||
(has_vht && (vht_cap->cap & IEEE80211_VHT_CAP_RXLDPC))))
flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK;
- if (he_cap->has_he && (he_cap->he_cap_elem.phy_cap_info[1] &
- IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD))
+ if (he_cap && he_cap->has_he &&
+ (he_cap->he_cap_elem.phy_cap_info[1] &
+ IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD))
flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK;
if (own_he_cap &&
@@ -74,7 +95,7 @@ iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld,
flags &= ~IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK;
/* DCM */
- if (he_cap->has_he &&
+ if (he_cap && he_cap->has_he &&
(he_cap->he_cap_elem.phy_cap_info[3] &
IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_MASK &&
own_he_cap &&
@@ -85,15 +106,15 @@ iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld,
/* Extra EHT LTF */
if (own_eht_cap &&
own_eht_cap->eht_cap_elem.phy_cap_info[5] &
- IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF &&
- link_sta->eht_cap.has_eht &&
- link_sta->eht_cap.eht_cap_elem.phy_cap_info[5] &
- IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF) {
+ IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF &&
+ eht_cap && eht_cap->has_eht &&
+ eht_cap->eht_cap_elem.phy_cap_info[5] &
+ IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF) {
flags |= IWL_TLC_MNG_CFG_FLAGS_EHT_EXTRA_LTF_MSK;
}
- if (link_sta->uhr_cap.has_uhr && own_uhr_cap &&
- link_sta->uhr_cap.phy.cap & IEEE80211_UHR_PHY_CAP_ELR_RX &&
+ if (uhr_cap && uhr_cap->has_uhr && own_uhr_cap &&
+ uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_RX &&
own_uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_TX)
flags |= IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_1_5_MBPS_MSK |
IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_3_MBPS_MSK;
@@ -114,27 +135,27 @@ static u8 iwl_mld_get_fw_chains(struct iwl_mld *mld)
return fw_chains;
}
-static u8 iwl_mld_get_fw_sgi(struct ieee80211_link_sta *link_sta)
+static u8 iwl_mld_get_fw_sgi(struct iwl_mld_tlc_sta_capa *capa)
{
- struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap;
- struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap;
- struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
+ const struct ieee80211_sta_ht_cap *ht_cap = capa->ht_cap;
+ const struct ieee80211_sta_vht_cap *vht_cap = capa->vht_cap;
+ const struct ieee80211_sta_he_cap *he_cap = capa->he_cap;
u8 sgi_chwidths = 0;
/* If the association supports HE, HT/VHT rates will never be used for
* Tx and therefor there's no need to set the
* sgi-per-channel-width-support bits
*/
- if (he_cap->has_he)
+ if (he_cap && he_cap->has_he)
return 0;
- if (ht_cap->cap & IEEE80211_HT_CAP_SGI_20)
+ if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_SGI_20)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_20MHZ);
- if (ht_cap->cap & IEEE80211_HT_CAP_SGI_40)
+ if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_SGI_40)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_40MHZ);
- if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80)
+ if (vht_cap && vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_80MHZ);
- if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160)
+ if (vht_cap && vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_160MHZ);
return sgi_chwidths;
@@ -164,31 +185,30 @@ iwl_mld_get_highest_fw_mcs(const struct ieee80211_sta_vht_cap *vht_cap,
}
static void
-iwl_mld_fill_vht_rates(const struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_vht_cap *vht_cap,
+iwl_mld_fill_vht_rates(struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
u32 supp;
int i, highest_mcs;
- u8 max_nss = link_sta->rx_nss;
+ u8 max_nss = capa->rx_nss;
struct ieee80211_vht_cap ieee_vht_cap = {
- .vht_cap_info = cpu_to_le32(vht_cap->cap),
- .supp_mcs = vht_cap->vht_mcs,
+ .vht_cap_info = cpu_to_le32(capa->vht_cap->cap),
+ .supp_mcs = capa->vht_cap->vht_mcs,
};
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC)
max_nss = 1;
for (i = 0; i < max_nss && i < IWL_TLC_NSS_MAX; i++) {
int nss = i + 1;
- highest_mcs = iwl_mld_get_highest_fw_mcs(vht_cap, nss);
+ highest_mcs = iwl_mld_get_highest_fw_mcs(capa->vht_cap, nss);
if (!highest_mcs)
continue;
supp = BIT(highest_mcs + 1) - 1;
- if (link_sta->bandwidth == IEEE80211_STA_RX_BW_20)
+ if (capa->bandwidth == IEEE80211_STA_RX_BW_20)
supp &= ~BIT(IWL_TLC_MNG_HT_RATE_MCS9);
cmd->ht_rates[i][IWL_TLC_MCS_PER_BW_80] = cpu_to_le32(supp);
@@ -196,7 +216,7 @@ iwl_mld_fill_vht_rates(const struct ieee80211_link_sta *link_sta,
* configuration is supported - only for MCS 0 since we already
* decoded the MCS bits anyway ourselves.
*/
- if (link_sta->bandwidth == IEEE80211_STA_RX_BW_160 &&
+ if (capa->bandwidth == IEEE80211_STA_RX_BW_160 &&
ieee80211_get_vht_max_nss(&ieee_vht_cap,
IEEE80211_VHT_CHANWIDTH_160MHZ,
0, true, nss) >= nss)
@@ -223,20 +243,19 @@ static u32 iwl_mld_he_mac80211_mcs_to_fw_mcs(u16 mcs)
}
static void
-iwl_mld_fill_he_rates(const struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_he_cap *own_he_cap,
+iwl_mld_fill_he_rates(struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
- const struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
+ const struct ieee80211_sta_he_cap *he_cap = capa->he_cap;
u16 mcs_160 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160);
u16 mcs_80 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80);
- u16 tx_mcs_80 = le16_to_cpu(own_he_cap->he_mcs_nss_supp.tx_mcs_80);
- u16 tx_mcs_160 = le16_to_cpu(own_he_cap->he_mcs_nss_supp.tx_mcs_160);
+ u16 tx_mcs_80 = le16_to_cpu(capa->own_he_cap->he_mcs_nss_supp.tx_mcs_80);
+ u16 tx_mcs_160 = le16_to_cpu(capa->own_he_cap->he_mcs_nss_supp.tx_mcs_160);
int i;
- u8 nss = link_sta->rx_nss;
+ u8 nss = capa->rx_nss;
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC)
nss = 1;
for (i = 0; i < nss && i < IWL_TLC_NSS_MAX; i++) {
@@ -313,17 +332,15 @@ static u8 iwl_mld_get_eht_max_nss(u8 rx_nss, u8 tx_nss)
static void
iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
- const struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_he_cap *own_he_cap,
- const struct ieee80211_sta_eht_cap *own_eht_cap,
+ struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
/* peer RX mcs capa */
const struct ieee80211_eht_mcs_nss_supp *eht_rx_mcs =
- &link_sta->eht_cap.eht_mcs_nss_supp;
+ &capa->eht_cap->eht_mcs_nss_supp;
/* our TX mcs capa */
const struct ieee80211_eht_mcs_nss_supp *eht_tx_mcs =
- &own_eht_cap->eht_mcs_nss_supp;
+ &capa->own_eht_cap->eht_mcs_nss_supp;
enum IWL_TLC_MCS_PER_BW bw;
struct ieee80211_eht_mcs_nss_supp_20mhz_only mcs_rx_20;
@@ -331,7 +348,7 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
/* peer is 20 MHz only */
if (vif->type == NL80211_IFTYPE_AP &&
- !(link_sta->he_cap.he_cap_elem.phy_cap_info[0] &
+ !(capa->he_cap->he_cap_elem.phy_cap_info[0] &
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL)) {
mcs_rx_20 = eht_rx_mcs->only_20mhz;
} else {
@@ -346,7 +363,7 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
}
/* NIC is capable of 20 MHz only */
- if (!(own_he_cap->he_cap_elem.phy_cap_info[0] &
+ if (!(capa->own_he_cap->he_cap_elem.phy_cap_info[0] &
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL)) {
mcs_tx_20 = eht_tx_mcs->only_20mhz;
} else {
@@ -402,67 +419,51 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
}
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC ||
- link_sta->rx_nss < 2)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC ||
+ capa->rx_nss < 2)
memset(cmd->ht_rates[IWL_TLC_NSS_2], 0,
sizeof(cmd->ht_rates[IWL_TLC_NSS_2]));
}
static void
-iwl_mld_fill_supp_rates(struct iwl_mld *mld, struct ieee80211_vif *vif,
- struct ieee80211_link_sta *link_sta,
- struct ieee80211_supported_band *sband,
- const struct ieee80211_sta_he_cap *own_he_cap,
- const struct ieee80211_sta_eht_cap *own_eht_cap,
- const struct ieee80211_sta_uhr_cap *own_uhr_cap,
+iwl_mld_fill_supp_rates(struct iwl_mld *mld,
+ struct ieee80211_vif *vif,
+ struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
- int i;
- u16 non_ht_rates = 0;
- unsigned long rates_bitmap;
- const struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap;
- const struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap;
- const struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
-
- /* non HT rates */
- rates_bitmap = link_sta->supp_rates[sband->band];
- for_each_set_bit(i, &rates_bitmap, BITS_PER_LONG)
- non_ht_rates |= BIT(sband->bitrates[i].hw_value);
-
- cmd->non_ht_rates = cpu_to_le16(non_ht_rates);
+ cmd->non_ht_rates = cpu_to_le16(capa->non_ht_rates);
cmd->mode = IWL_TLC_MNG_MODE_NON_HT;
- if (link_sta->uhr_cap.has_uhr && own_uhr_cap) {
+ if (capa->uhr_cap && capa->uhr_cap->has_uhr && capa->own_uhr_cap) {
cmd->mode = IWL_TLC_MNG_MODE_UHR;
/*
* FIXME: spec currently inherits from EHT but has no
* finer MCS bits. Once that's there, need to add them
* to the bitmaps (and maybe copy this to UHR, or so.)
*/
- iwl_mld_fill_eht_rates(vif, link_sta, own_he_cap,
- own_eht_cap, cmd);
- } else if (link_sta->eht_cap.has_eht && own_he_cap && own_eht_cap) {
+ iwl_mld_fill_eht_rates(vif, capa, cmd);
+ } else if (capa->eht_cap && capa->eht_cap->has_eht &&
+ capa->own_he_cap && capa->own_eht_cap) {
cmd->mode = IWL_TLC_MNG_MODE_EHT;
- iwl_mld_fill_eht_rates(vif, link_sta, own_he_cap,
- own_eht_cap, cmd);
- } else if (he_cap->has_he && own_he_cap) {
+ iwl_mld_fill_eht_rates(vif, capa, cmd);
+ } else if (capa->he_cap && capa->he_cap->has_he && capa->own_he_cap) {
cmd->mode = IWL_TLC_MNG_MODE_HE;
- iwl_mld_fill_he_rates(link_sta, own_he_cap, cmd);
- } else if (vht_cap->vht_supported) {
+ iwl_mld_fill_he_rates(capa, cmd);
+ } else if (capa->vht_cap && capa->vht_cap->vht_supported) {
cmd->mode = IWL_TLC_MNG_MODE_VHT;
- iwl_mld_fill_vht_rates(link_sta, vht_cap, cmd);
- } else if (ht_cap->ht_supported) {
+ iwl_mld_fill_vht_rates(capa, cmd);
+ } else if (capa->ht_cap && capa->ht_cap->ht_supported) {
cmd->mode = IWL_TLC_MNG_MODE_HT;
cmd->ht_rates[IWL_TLC_NSS_1][IWL_TLC_MCS_PER_BW_80] =
- cpu_to_le32(ht_cap->mcs.rx_mask[0]);
+ cpu_to_le32(capa->ht_cap->mcs.rx_mask[0]);
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC)
cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_MCS_PER_BW_80] =
0;
else
cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_MCS_PER_BW_80] =
- cpu_to_le32(ht_cap->mcs.rx_mask[1]);
+ cpu_to_le32(capa->ht_cap->mcs.rx_mask[1]);
}
}
@@ -527,60 +528,35 @@ static int iwl_mld_convert_tlc_cmd_to_v4(struct iwl_tlc_config_cmd *cmd,
static void iwl_mld_send_tlc_cmd(struct iwl_mld *mld,
struct ieee80211_vif *vif,
- struct ieee80211_link_sta *link_sta,
- struct ieee80211_bss_conf *link)
+ struct iwl_mld_sta *mld_sta,
+ int fw_sta_id, int phy_id,
+ struct iwl_mld_tlc_sta_capa *capa)
{
- struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
- struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
- enum nl80211_band band = link->chanreq.oper.chan->band;
- struct ieee80211_supported_band *sband = mld->hw->wiphy->bands[band];
- const struct ieee80211_sta_he_cap *own_he_cap =
- ieee80211_get_he_iftype_cap_vif(sband, vif);
- const struct ieee80211_sta_eht_cap *own_eht_cap =
- ieee80211_get_eht_iftype_cap_vif(sband, vif);
- const struct ieee80211_sta_uhr_cap *own_uhr_cap =
- ieee80211_get_uhr_iftype_cap_vif(sband, vif);
struct iwl_tlc_config_cmd cmd = {
/* For AP mode, use 20 MHz until the STA is authorized */
.max_ch_width = mld_sta->sta_state > IEEE80211_STA_ASSOC ?
- iwl_mld_fw_bw_from_sta_bw(link_sta) :
+ iwl_mld_fw_bw_from_sta_bw(capa->bandwidth) :
IWL_TLC_MNG_CH_WIDTH_20MHZ,
- .flags = iwl_mld_get_tlc_cmd_flags(mld, vif, link_sta,
- own_he_cap, own_eht_cap,
- own_uhr_cap),
+ .flags = iwl_mld_get_tlc_cmd_flags(mld, capa),
.chains = iwl_mld_get_fw_chains(mld),
- .sgi_ch_width_supp = iwl_mld_get_fw_sgi(link_sta),
- .max_mpdu_len = cpu_to_le16(link_sta->agg.max_amsdu_len),
+ .sgi_ch_width_supp = iwl_mld_get_fw_sgi(capa),
+ .max_mpdu_len = cpu_to_le16(capa->max_amsdu_len),
};
- int fw_sta_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta);
u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, TLC_MNG_CONFIG_CMD);
u8 cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, cmd_id, 0);
- struct ieee80211_chanctx_conf *chan_ctx;
struct iwl_tlc_config_cmd_v5 cmd_v5 = {};
struct iwl_tlc_config_cmd_v4 cmd_v4 = {};
void *cmd_ptr;
u8 cmd_size;
- u32 phy_id;
int ret;
if (fw_sta_id < 0)
return;
cmd.sta_mask = cpu_to_le32(BIT(fw_sta_id));
-
- if (WARN_ON_ONCE(!mld_link))
- return;
-
- chan_ctx = rcu_dereference_wiphy(mld->wiphy, mld_link->chan_ctx);
- if (WARN_ON(!chan_ctx))
- return;
-
- phy_id = iwl_mld_phy_from_mac80211(chan_ctx)->fw_id;
cmd.phy_id = cpu_to_le32(phy_id);
- iwl_mld_fill_supp_rates(mld, vif, link_sta, sband,
- own_he_cap, own_eht_cap,
- own_uhr_cap, &cmd);
+ iwl_mld_fill_supp_rates(mld, vif, capa, &cmd);
if (cmd_ver == 6) {
cmd_ptr = &cmd;
@@ -647,10 +623,24 @@ void iwl_mld_config_tlc_link(struct iwl_mld *mld,
struct ieee80211_link_sta *link_sta)
{
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
+ struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link_conf);
+ struct ieee80211_chanctx_conf *chan_ctx;
+ struct ieee80211_supported_band *sband;
+ struct iwl_mld_tlc_sta_capa capa = {};
+ unsigned long rates_bitmap;
+ enum nl80211_band band;
+ int fw_sta_id, i;
+
+ if (WARN_ON_ONCE(!mld_link))
+ return;
- if (WARN_ON_ONCE(!link_conf->chanreq.oper.chan))
+ chan_ctx = rcu_dereference_wiphy(mld->wiphy, mld_link->chan_ctx);
+ if (WARN_ON_ONCE(!chan_ctx))
return;
+ band = chan_ctx->def.chan->band;
+ sband = mld->hw->wiphy->bands[band];
+
/* Before we have information about a station, configure the A-MSDU RC
* limit such that iwlmd and mac80211 would not be allowed to build
* A-MSDUs.
@@ -660,7 +650,28 @@ void iwl_mld_config_tlc_link(struct iwl_mld *mld,
ieee80211_sta_recalc_aggregates(link_sta->sta);
}
- iwl_mld_send_tlc_cmd(mld, vif, link_sta, link_conf);
+ /* non HT rates */
+ rates_bitmap = link_sta->supp_rates[sband->band];
+ for_each_set_bit(i, &rates_bitmap, BITS_PER_LONG)
+ capa.non_ht_rates |= BIT(sband->bitrates[i].hw_value);
+
+ capa.rx_nss = link_sta->rx_nss;
+ capa.smps_mode = link_sta->smps_mode;
+ capa.bandwidth = link_sta->bandwidth;
+ capa.max_amsdu_len = link_sta->agg.max_amsdu_len;
+ capa.ht_cap = &link_sta->ht_cap;
+ capa.vht_cap = &link_sta->vht_cap;
+ capa.he_cap = &link_sta->he_cap;
+ capa.eht_cap = &link_sta->eht_cap;
+ capa.uhr_cap = &link_sta->uhr_cap;
+ capa.own_he_cap = ieee80211_get_he_iftype_cap_vif(sband, vif);
+ capa.own_eht_cap = ieee80211_get_eht_iftype_cap_vif(sband, vif);
+ capa.own_uhr_cap = ieee80211_get_uhr_iftype_cap_vif(sband, vif);
+
+ fw_sta_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta);
+ iwl_mld_send_tlc_cmd(mld, vif, mld_sta, fw_sta_id,
+ iwl_mld_phy_from_mac80211(chan_ctx)->fw_id,
+ &capa);
}
void iwl_mld_tlc_update_phy(struct iwl_mld *mld, struct ieee80211_vif *vif,
--
2.34.1
---
v2: use mld_link and not link_conf
^ 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
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox