From c4f518736472c8cfbf1d304e01c631babd2bbf34 Mon Sep 17 00:00:00 2001 From: pengdonglin Date: Tue, 16 Sep 2025 12:47:35 +0800 Subject: [PATCH 001/143] wifi: ath9k: Remove redundant rcu_read_lock/unlock() in spin_lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit a8bb74acd8efe ("rcu: Consolidate RCU-sched update-side function definitions") there is no difference between rcu_read_lock(), rcu_read_lock_bh() and rcu_read_lock_sched() in terms of RCU read section and the relevant grace period. That means that spin_lock(), which implies rcu_read_lock_sched(), also implies rcu_read_lock(). There is no need no explicitly start a RCU read section if one has already been started implicitly by spin_lock(). Simplify the code and remove the inner rcu_read_lock() invocation. Cc: Toke Cc: Jakub Kicinski Signed-off-by: pengdonglin Signed-off-by: pengdonglin Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20250916044735.2316171-15-dolinux.peng@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath9k/xmit.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c index 0ac9212e42f7..4a0f465aa2fe 100644 --- a/drivers/net/wireless/ath/ath9k/xmit.c +++ b/drivers/net/wireless/ath/ath9k/xmit.c @@ -1993,7 +1993,6 @@ void ath_txq_schedule(struct ath_softc *sc, struct ath_txq *txq) ieee80211_txq_schedule_start(hw, txq->mac80211_qnum); spin_lock_bh(&sc->chan_lock); - rcu_read_lock(); if (sc->cur_chan->stopped) goto out; @@ -2011,7 +2010,6 @@ void ath_txq_schedule(struct ath_softc *sc, struct ath_txq *txq) } out: - rcu_read_unlock(); spin_unlock_bh(&sc->chan_lock); ieee80211_txq_schedule_end(hw, txq->mac80211_qnum); } From 22e8b91d18972f5e1de13af7f55555e550ad1818 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 22 Feb 2026 18:50:20 -0800 Subject: [PATCH 002/143] wifi: ath9k: use non devm for nvmem_cell_get MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There's absolutely no need to extend the lifetime of cell to post removal of the driver. It's only used in this function. Signed-off-by: Rosen Penev Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260223025021.19008-1-rosenp@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath9k/init.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath9k/init.c b/drivers/net/wireless/ath/ath9k/init.c index ee951493e993..ebd463079388 100644 --- a/drivers/net/wireless/ath/ath9k/init.c +++ b/drivers/net/wireless/ath/ath9k/init.c @@ -576,7 +576,7 @@ static int ath9k_nvmem_request_eeprom(struct ath_softc *sc) size_t len; int err; - cell = devm_nvmem_cell_get(sc->dev, "calibration"); + cell = nvmem_cell_get(sc->dev, "calibration"); if (IS_ERR(cell)) { err = PTR_ERR(cell); @@ -593,6 +593,7 @@ static int ath9k_nvmem_request_eeprom(struct ath_softc *sc) } buf = nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); if (IS_ERR(buf)) return PTR_ERR(buf); From 7b2338d5ec6fee29f92fae791219ab9fbd206567 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 23 Feb 2026 14:42:54 -0800 Subject: [PATCH 003/143] wifi: ath9k: owl: move name into owl_nvmem_probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is no need for dynamic allocation for a simple string. request_firmware_nowait copies the string internally anyway. The error message on failure is also wrong. It's an allocation failure, not a find failure. Signed-off-by: Rosen Penev Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260223224254.27081-1-rosenp@gmail.com Signed-off-by: Jeff Johnson --- .../wireless/ath/ath9k/ath9k_pci_owl_loader.c | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c b/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c index fe1013a3a588..b9ef34709202 100644 --- a/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c +++ b/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c @@ -137,24 +137,6 @@ static void owl_fw_cb(const struct firmware *fw, void *context) release_firmware(fw); } -static const char *owl_get_eeprom_name(struct pci_dev *pdev) -{ - struct device *dev = &pdev->dev; - char *eeprom_name; - - dev_dbg(dev, "using auto-generated eeprom filename\n"); - - eeprom_name = devm_kzalloc(dev, EEPROM_FILENAME_LEN, GFP_KERNEL); - if (!eeprom_name) - return NULL; - - /* this should match the pattern used in ath9k/init.c */ - scnprintf(eeprom_name, EEPROM_FILENAME_LEN, "ath9k-eeprom-pci-%s.bin", - dev_name(dev)); - - return eeprom_name; -} - static void owl_nvmem_work(struct work_struct *work) { struct owl_ctx *ctx = container_of(work, struct owl_ctx, work); @@ -195,8 +177,9 @@ static int owl_nvmem_probe(struct owl_ctx *ctx) static int owl_probe(struct pci_dev *pdev, const struct pci_device_id *id) { + char eeprom_name[EEPROM_FILENAME_LEN]; + struct device *dev = &pdev->dev; struct owl_ctx *ctx; - const char *eeprom_name; int err = 0; if (pci_enable_device(pdev)) @@ -215,11 +198,11 @@ static int owl_probe(struct pci_dev *pdev, if (err <= 0) return err; - eeprom_name = owl_get_eeprom_name(pdev); - if (!eeprom_name) { - dev_err(&pdev->dev, "no eeprom filename found.\n"); - return -ENODEV; - } + dev_dbg(dev, "using auto-generated eeprom filename\n"); + + /* this should match the pattern used in ath9k/init.c */ + scnprintf(eeprom_name, sizeof(eeprom_name), "ath9k-eeprom-pci-%s.bin", + dev_name(dev)); err = request_firmware_nowait(THIS_MODULE, true, eeprom_name, &pdev->dev, GFP_KERNEL, ctx, owl_fw_cb); From 3042a9d403b9ead7c35b55e697eeae7974e04653 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 23 Feb 2026 14:44:45 -0800 Subject: [PATCH 004/143] wifi: ath9k: use kmemdup and kcalloc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplifies the code slightly by removing temporary variables. multiplication overflow is also gained for free. Signed-off-by: Rosen Penev Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260223224445.27352-1-rosenp@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath9k/ar9002_hw.c | 6 +++--- drivers/net/wireless/ath/ath9k/common-init.c | 8 ++------ drivers/net/wireless/ath/ath9k/init.c | 8 +++----- drivers/net/wireless/ath/ath9k/recv.c | 4 +--- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/ath/ath9k/ar9002_hw.c b/drivers/net/wireless/ath/ath9k/ar9002_hw.c index b26224480041..0f24539b75ec 100644 --- a/drivers/net/wireless/ath/ath9k/ar9002_hw.c +++ b/drivers/net/wireless/ath/ath9k/ar9002_hw.c @@ -80,14 +80,14 @@ static int ar9002_hw_init_mode_regs(struct ath_hw *ah) /* iniAddac needs to be modified for these chips */ if (AR_SREV_9160(ah) || !AR_SREV_5416_22_OR_LATER(ah)) { struct ar5416IniArray *addac = &ah->iniAddac; - u32 size = sizeof(u32) * addac->ia_rows * addac->ia_columns; + u32 n = addac->ia_rows * addac->ia_columns; u32 *data; - data = devm_kzalloc(ah->dev, size, GFP_KERNEL); + data = devm_kmemdup_array(ah->dev, addac->ia_array, n, sizeof(u32), + GFP_KERNEL); if (!data) return -ENOMEM; - memcpy(data, addac->ia_array, size); addac->ia_array = data; if (!AR_SREV_5416_22_OR_LATER(ah)) { diff --git a/drivers/net/wireless/ath/ath9k/common-init.c b/drivers/net/wireless/ath/ath9k/common-init.c index da102c791712..52e02e0752d8 100644 --- a/drivers/net/wireless/ath/ath9k/common-init.c +++ b/drivers/net/wireless/ath/ath9k/common-init.c @@ -133,13 +133,11 @@ int ath9k_cmn_init_channels_rates(struct ath_common *common) ATH9K_NUM_CHANNELS); if (ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) { - channels = devm_kzalloc(ah->dev, + channels = devm_kmemdup(ah->dev, ath9k_2ghz_chantable, sizeof(ath9k_2ghz_chantable), GFP_KERNEL); if (!channels) return -ENOMEM; - memcpy(channels, ath9k_2ghz_chantable, - sizeof(ath9k_2ghz_chantable)); common->sbands[NL80211_BAND_2GHZ].channels = channels; common->sbands[NL80211_BAND_2GHZ].band = NL80211_BAND_2GHZ; common->sbands[NL80211_BAND_2GHZ].n_channels = @@ -150,13 +148,11 @@ int ath9k_cmn_init_channels_rates(struct ath_common *common) } if (ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) { - channels = devm_kzalloc(ah->dev, + channels = devm_kmemdup(ah->dev, ath9k_5ghz_chantable, sizeof(ath9k_5ghz_chantable), GFP_KERNEL); if (!channels) return -ENOMEM; - memcpy(channels, ath9k_5ghz_chantable, - sizeof(ath9k_5ghz_chantable)); common->sbands[NL80211_BAND_5GHZ].channels = channels; common->sbands[NL80211_BAND_5GHZ].band = NL80211_BAND_5GHZ; common->sbands[NL80211_BAND_5GHZ].n_channels = diff --git a/drivers/net/wireless/ath/ath9k/init.c b/drivers/net/wireless/ath/ath9k/init.c index ebd463079388..e1a67e8ed09f 100644 --- a/drivers/net/wireless/ath/ath9k/init.c +++ b/drivers/net/wireless/ath/ath9k/init.c @@ -285,7 +285,7 @@ int ath_descdma_setup(struct ath_softc *sc, struct ath_descdma *dd, { struct ath_common *common = ath9k_hw_common(sc->sc_ah); u8 *ds; - int i, bsize, desc_len; + int i, desc_len; ath_dbg(common, CONFIG, "%s DMA: %u buffers %u desc/buf\n", name, nbuf, ndesc); @@ -339,8 +339,7 @@ int ath_descdma_setup(struct ath_softc *sc, struct ath_descdma *dd, if (is_tx) { struct ath_buf *bf; - bsize = sizeof(struct ath_buf) * nbuf; - bf = devm_kzalloc(sc->dev, bsize, GFP_KERNEL); + bf = devm_kcalloc(sc->dev, sizeof(*bf), nbuf, GFP_KERNEL); if (!bf) return -ENOMEM; @@ -370,8 +369,7 @@ int ath_descdma_setup(struct ath_softc *sc, struct ath_descdma *dd, } else { struct ath_rxbuf *bf; - bsize = sizeof(struct ath_rxbuf) * nbuf; - bf = devm_kzalloc(sc->dev, bsize, GFP_KERNEL); + bf = devm_kcalloc(sc->dev, sizeof(struct ath_rxbuf), nbuf, GFP_KERNEL); if (!bf) return -ENOMEM; diff --git a/drivers/net/wireless/ath/ath9k/recv.c b/drivers/net/wireless/ath/ath9k/recv.c index 34c74ed99b7b..93b41a1bb2af 100644 --- a/drivers/net/wireless/ath/ath9k/recv.c +++ b/drivers/net/wireless/ath/ath9k/recv.c @@ -202,7 +202,6 @@ static int ath_rx_edma_init(struct ath_softc *sc, int nbufs) struct sk_buff *skb; struct ath_rxbuf *bf; int error = 0, i; - u32 size; ath9k_hw_set_rx_bufsize(ah, common->rx_bufsize - ah->caps.rx_status_len); @@ -212,8 +211,7 @@ static int ath_rx_edma_init(struct ath_softc *sc, int nbufs) ath_rx_edma_init_queue(&sc->rx.rx_edma[ATH9K_RX_QUEUE_HP], ah->caps.rx_hp_qdepth); - size = sizeof(struct ath_rxbuf) * nbufs; - bf = devm_kzalloc(sc->dev, size, GFP_KERNEL); + bf = devm_kcalloc(sc->dev, sizeof(struct ath_rxbuf), nbufs, GFP_KERNEL); if (!bf) return -ENOMEM; From 7ce2f118a2389e8f0a64068c6fe7cc7d40639be0 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Wed, 15 Apr 2026 22:23:43 +0000 Subject: [PATCH 005/143] wifi: ath9k: fix OOB access from firmware tx status queue ID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ath_tx_edma_tasklet() accesses sc->tx.txq[ts.qid] where ts.qid is a 4-bit hardware field (0-15), but the txq array only has ATH9K_NUM_TX_QUEUES (10) entries. A qid >= 10 causes an OOB array access. Add a bounds check on ts.qid before using it as an array index. Fixes: fce041beb03f ("ath9k: unify edma and non-edma tx code, improve tx fifo handling") Signed-off-by: Tristan Madani Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260415222343.1540564-1-tristmd@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath9k/xmit.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c index 4a0f465aa2fe..89d8b3178784 100644 --- a/drivers/net/wireless/ath/ath9k/xmit.c +++ b/drivers/net/wireless/ath/ath9k/xmit.c @@ -2744,6 +2744,11 @@ void ath_tx_edma_tasklet(struct ath_softc *sc) continue; } + if (ts.qid >= ATH9K_NUM_TX_QUEUES) { + ath_dbg(common, XMIT, "invalid qid %d\n", ts.qid); + continue; + } + txq = &sc->tx.txq[ts.qid]; ath_txq_lock(sc, txq); From 0e39eea57626e545c596734c0d250c1735f1a7e5 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 16 Mar 2026 18:14:12 +0100 Subject: [PATCH 006/143] wifi: ath: Use the unified QMI service ID instead of defining it locally Instead of defining a local macro with a custom name for the QMI service identifier, use the one provided in qmi.h and remove the locally defined macro. Reviewed-by: Dmitry Baryshkov Signed-off-by: Daniel Lezcano Acked-by: Jeff Johnson Link: https://patch.msgid.link/20260316171419.2619620-3-daniel.lezcano@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/qmi.c | 2 +- drivers/net/wireless/ath/ath10k/qmi_wlfw_v01.h | 1 - drivers/net/wireless/ath/ath11k/qmi.c | 2 +- drivers/net/wireless/ath/ath11k/qmi.h | 1 - drivers/net/wireless/ath/ath12k/qmi.c | 2 +- drivers/net/wireless/ath/ath12k/qmi.h | 1 - 6 files changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/qmi.c b/drivers/net/wireless/ath/ath10k/qmi.c index e7f90fd9e9b8..af58f88c27e1 100644 --- a/drivers/net/wireless/ath/ath10k/qmi.c +++ b/drivers/net/wireless/ath/ath10k/qmi.c @@ -1112,7 +1112,7 @@ int ath10k_qmi_init(struct ath10k *ar, u32 msa_size) spin_lock_init(&qmi->event_lock); INIT_WORK(&qmi->event_work, ath10k_qmi_driver_event_work); - ret = qmi_add_lookup(&qmi->qmi_hdl, WLFW_SERVICE_ID_V01, + ret = qmi_add_lookup(&qmi->qmi_hdl, QMI_SERVICE_ID_WLFW, WLFW_SERVICE_VERS_V01, 0); if (ret) goto err_qmi_lookup; diff --git a/drivers/net/wireless/ath/ath10k/qmi_wlfw_v01.h b/drivers/net/wireless/ath/ath10k/qmi_wlfw_v01.h index 9f311f3bc9e7..88d58f78989d 100644 --- a/drivers/net/wireless/ath/ath10k/qmi_wlfw_v01.h +++ b/drivers/net/wireless/ath/ath10k/qmi_wlfw_v01.h @@ -7,7 +7,6 @@ #ifndef WCN3990_QMI_SVC_V01_H #define WCN3990_QMI_SVC_V01_H -#define WLFW_SERVICE_ID_V01 0x45 #define WLFW_SERVICE_VERS_V01 0x01 #define QMI_WLFW_BDF_DOWNLOAD_REQ_V01 0x0025 diff --git a/drivers/net/wireless/ath/ath11k/qmi.c b/drivers/net/wireless/ath/ath11k/qmi.c index feebbc30f3df..1397756d6251 100644 --- a/drivers/net/wireless/ath/ath11k/qmi.c +++ b/drivers/net/wireless/ath/ath11k/qmi.c @@ -3337,7 +3337,7 @@ int ath11k_qmi_init_service(struct ath11k_base *ab) spin_lock_init(&ab->qmi.event_lock); INIT_WORK(&ab->qmi.event_work, ath11k_qmi_driver_event_work); - ret = qmi_add_lookup(&ab->qmi.handle, ATH11K_QMI_WLFW_SERVICE_ID_V01, + ret = qmi_add_lookup(&ab->qmi.handle, QMI_SERVICE_ID_WLFW, ATH11K_QMI_WLFW_SERVICE_VERS_V01, ab->qmi.service_ins_id); if (ret < 0) { diff --git a/drivers/net/wireless/ath/ath11k/qmi.h b/drivers/net/wireless/ath/ath11k/qmi.h index 7968ab122b65..eae416db8b52 100644 --- a/drivers/net/wireless/ath/ath11k/qmi.h +++ b/drivers/net/wireless/ath/ath11k/qmi.h @@ -15,7 +15,6 @@ #define ATH11K_QMI_MAX_BDF_FILE_NAME_SIZE 64 #define ATH11K_QMI_CALDB_ADDRESS 0x4BA00000 #define ATH11K_QMI_WLANFW_MAX_BUILD_ID_LEN_V01 128 -#define ATH11K_QMI_WLFW_SERVICE_ID_V01 0x45 #define ATH11K_QMI_WLFW_SERVICE_VERS_V01 0x01 #define ATH11K_QMI_WLFW_SERVICE_INS_ID_V01 0x02 #define ATH11K_QMI_WLFW_SERVICE_INS_ID_V01_QCA6390 0x01 diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index 8c5dacf227da..fd762b5d7bb5 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -4061,7 +4061,7 @@ int ath12k_qmi_init_service(struct ath12k_base *ab) spin_lock_init(&ab->qmi.event_lock); INIT_WORK(&ab->qmi.event_work, ath12k_qmi_driver_event_work); - ret = qmi_add_lookup(&ab->qmi.handle, ATH12K_QMI_WLFW_SERVICE_ID_V01, + ret = qmi_add_lookup(&ab->qmi.handle, QMI_SERVICE_ID_WLFW, ATH12K_QMI_WLFW_SERVICE_VERS_V01, ab->qmi.service_ins_id); if (ret < 0) { diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index b5a4a01391cb..2a63e214eb42 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -15,7 +15,6 @@ #define ATH12K_QMI_MAX_BDF_FILE_NAME_SIZE 64 #define ATH12K_QMI_CALDB_ADDRESS 0x4BA00000 #define ATH12K_QMI_WLANFW_MAX_BUILD_ID_LEN_V01 128 -#define ATH12K_QMI_WLFW_SERVICE_ID_V01 0x45 #define ATH12K_QMI_WLFW_SERVICE_VERS_V01 0x01 #define ATH12K_QMI_WLFW_SERVICE_INS_ID_V01 0x02 #define ATH12K_QMI_WLFW_SERVICE_INS_ID_V01_WCN7850 0x1 From 2419d92ad387ff34376e7c3afc300cff651038da Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 27 Apr 2026 09:00:41 +0200 Subject: [PATCH 007/143] wifi: ath: Unify user-visible "Qualcomm" name Various names for Qualcomm as a company are used in user-visible config options: QCOM, Qualcomm and Qualcomm Technologies. Switch to unified "Qualcomm" so it will be easier for users to identify the options when for example running menuconfig. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260427070040.17928-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/Kconfig | 2 +- drivers/net/wireless/ath/ath12k/Kconfig | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/Kconfig b/drivers/net/wireless/ath/ath11k/Kconfig index 385513cfdc30..122726f84492 100644 --- a/drivers/net/wireless/ath/ath11k/Kconfig +++ b/drivers/net/wireless/ath/ath11k/Kconfig @@ -1,6 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause-Clear config ATH11K - tristate "Qualcomm Technologies 802.11ax chipset support" + tristate "Qualcomm 802.11ax chipset support" depends on MAC80211 && HAS_DMA select ATH_COMMON select QCOM_QMI_HELPERS diff --git a/drivers/net/wireless/ath/ath12k/Kconfig b/drivers/net/wireless/ath/ath12k/Kconfig index d39c075758bd..4a2b240f967a 100644 --- a/drivers/net/wireless/ath/ath12k/Kconfig +++ b/drivers/net/wireless/ath/ath12k/Kconfig @@ -1,6 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause-Clear config ATH12K - tristate "Qualcomm Technologies Wi-Fi 7 support (ath12k)" + tristate "Qualcomm Wi-Fi 7 support (ath12k)" depends on MAC80211 && HAS_DMA && PCI select QCOM_QMI_HELPERS select MHI_BUS @@ -15,7 +15,7 @@ config ATH12K If you choose to build a module, it'll be called ath12k. config ATH12K_AHB - bool "QTI ath12k AHB support" + bool "Qualcomm ath12k AHB support" depends on ATH12K && REMOTEPROC select QCOM_MDT_LOADER select QCOM_SCM @@ -33,7 +33,7 @@ config ATH12K_DEBUG you want optimal performance choose N. config ATH12K_DEBUGFS - bool "QTI ath12k debugfs support" + bool "Qualcomm ath12k debugfs support" depends on ATH12K && MAC80211_DEBUGFS help Enable ath12k debugfs support From c4aa897fe234a7bbfc0ba246df34137e3a88beb8 Mon Sep 17 00:00:00 2001 From: Tamizh Chelvam Raja Date: Sat, 18 Apr 2026 22:06:20 +0530 Subject: [PATCH 008/143] wifi: ath12k: Handle DP_RX_DECAP_TYPE_8023 type in Rx path The driver does not currently set any rx_flag for frames received with decap type DP_RX_DECAP_TYPE_8023. When the hardware reports LLC-encapsulated packets whose length field is below 0x0600, the MSDU_END descriptor may indicate decap type DP_RX_DECAP_TYPE_8023. These frames are effectively equivalent to Ethernet-II (DIX) packets, similar to those decoded as DP_RX_DECAP_TYPE_ETHERNET2_DIX. If the driver does not set RX_FLAG_8023 for these frames, mac80211 will misinterpret them as 802.11 frames. This causes valid frames such as Bridge Protocol Data Units (BPDUs) to be dropped. BPDUs are exchanged between switches to maintain and manage network topology, and must be treated as Ethernet frames. Set RX_FLAG_8023 for decap type DP_RX_DECAP_TYPE_8023 in ath12k_dp_rx_h_undecap() to ensure mac80211 handles these frames correctly. Also add multicast packet handling support for the DP_RX_DECAP_TYPE_8023 decap type. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Tamizh Chelvam Raja Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260418163620.3633959-1-tamizh.raja@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_rx.c | 57 ++++++++++++++++--------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index 250459facff3..cae00e0539df 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -1092,7 +1092,8 @@ static void ath12k_get_dot11_hdr_from_rx_desc(struct ath12k_pdev_dp *dp_pdev, static void ath12k_dp_rx_h_undecap_eth(struct ath12k_pdev_dp *dp_pdev, struct sk_buff *msdu, enum hal_encrypt_type enctype, - struct hal_rx_desc_data *rx_info) + struct hal_rx_desc_data *rx_info, + enum ath12k_dp_rx_decap_type decap_type) { struct ieee80211_hdr *hdr; struct ethhdr *eth; @@ -1100,12 +1101,24 @@ static void ath12k_dp_rx_h_undecap_eth(struct ath12k_pdev_dp *dp_pdev, u8 sa[ETH_ALEN]; struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu); struct ath12k_dp_rx_rfc1042_hdr rfc = {0xaa, 0xaa, 0x03, {0x00, 0x00, 0x00}}; + struct ath12k_dp_rx_rfc1042_hdr *llc; eth = (struct ethhdr *)msdu->data; ether_addr_copy(da, eth->h_dest); ether_addr_copy(sa, eth->h_source); - rfc.snap_type = eth->h_proto; - skb_pull(msdu, sizeof(*eth)); + if (decap_type == DP_RX_DECAP_TYPE_8023) { + /* + * For 802.3 frames, eth->h_proto carries a length field, not + * an EtherType. The actual EtherType is in the LLC/SNAP header + * that follows the Ethernet header. + */ + llc = (struct ath12k_dp_rx_rfc1042_hdr *)(msdu->data + sizeof(*eth)); + rfc.snap_type = llc->snap_type; + skb_pull(msdu, sizeof(*eth) + sizeof(*llc)); + } else { + rfc.snap_type = eth->h_proto; + skb_pull(msdu, sizeof(*eth)); + } memcpy(skb_push(msdu, sizeof(rfc)), &rfc, sizeof(rfc)); ath12k_get_dot11_hdr_from_rx_desc(dp_pdev, msdu, rxcb, enctype, rx_info); @@ -1123,9 +1136,10 @@ void ath12k_dp_rx_h_undecap(struct ath12k_pdev_dp *dp_pdev, struct sk_buff *msdu bool decrypted, struct hal_rx_desc_data *rx_info) { + enum ath12k_dp_rx_decap_type decap_type = rx_info->decap_type; struct ethhdr *ehdr; - switch (rx_info->decap_type) { + switch (decap_type) { case DP_RX_DECAP_TYPE_NATIVE_WIFI: ath12k_dp_rx_h_undecap_nwifi(dp_pdev, msdu, enctype, rx_info); break; @@ -1139,19 +1153,33 @@ void ath12k_dp_rx_h_undecap(struct ath12k_pdev_dp *dp_pdev, struct sk_buff *msdu /* mac80211 allows fast path only for authorized STA */ if (ehdr->h_proto == cpu_to_be16(ETH_P_PAE)) { ATH12K_SKB_RXCB(msdu)->is_eapol = true; - ath12k_dp_rx_h_undecap_eth(dp_pdev, msdu, enctype, rx_info); + ath12k_dp_rx_h_undecap_eth(dp_pdev, msdu, enctype, rx_info, + decap_type); break; } /* PN for mcast packets will be validated in mac80211; * remove eth header and add 802.11 header. */ - if (ATH12K_SKB_RXCB(msdu)->is_mcbc && decrypted) - ath12k_dp_rx_h_undecap_eth(dp_pdev, msdu, enctype, rx_info); + if (ATH12K_SKB_RXCB(msdu)->is_mcbc && decrypted) { + ath12k_dp_rx_h_undecap_eth(dp_pdev, msdu, enctype, rx_info, + decap_type); + break; + } + + rx_info->rx_status->flag |= RX_FLAG_8023; break; case DP_RX_DECAP_TYPE_8023: - /* TODO: Handle undecap for these formats */ - break; + /* + * Note that ethernet decap format indicates that the decapped + * packet is either Ethernet 2 (DIX) or 802.3 (uses SNAP/LLC). + */ + if (ATH12K_SKB_RXCB(msdu)->is_mcbc && decrypted) { + ath12k_dp_rx_h_undecap_eth(dp_pdev, msdu, enctype, rx_info, + decap_type); + break; + } + rx_info->rx_status->flag |= RX_FLAG_8023; } } EXPORT_SYMBOL(ath12k_dp_rx_h_undecap); @@ -1333,9 +1361,7 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc struct ath12k_dp_peer *peer; struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu); struct ieee80211_rx_status *status = rx_info->rx_status; - u8 decap = rx_info->decap_type; bool is_mcbc = rxcb->is_mcbc; - bool is_eapol = rxcb->is_eapol; peer = ath12k_dp_peer_find_by_peerid(dp_pdev, rx_info->peer_id); @@ -1380,15 +1406,6 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc /* TODO: trace rx packet */ - /* PN for multicast packets are not validate in HW, - * so skip 802.3 rx path - * Also, fast_rx expects the STA to be authorized, hence - * eapol packets are sent in slow path. - */ - if (decap == DP_RX_DECAP_TYPE_ETHERNET2_DIX && !is_eapol && - !(is_mcbc && rx_status->flag & RX_FLAG_DECRYPTED)) - rx_status->flag |= RX_FLAG_8023; - ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi); } EXPORT_SYMBOL(ath12k_dp_rx_deliver_msdu); From d88c3a5117c58e1d93c20a7c723a6ad23136a430 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 21 Apr 2026 14:35:44 -0700 Subject: [PATCH 009/143] wifi: ath12k: use kzalloc_flex Convert kzalloc_obj + kcalloc to kzalloc_flex to save an allocation. Add __counted_by to get extra runtime analysis. Move counting variable assignment immediately after allocation before any potential accesses. kzalloc_flex does this anyway for GCC >= 15. Signed-off-by: Rosen Penev Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260421213544.6238-1-rosenp@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 29 +++++++-------------------- drivers/net/wireless/ath/ath12k/wmi.h | 2 +- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index fbdfe6424fd7..32d590504a80 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -5611,12 +5611,14 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw, if (ret) goto exit; - arg = kzalloc_obj(*arg); + arg = kzalloc_flex(*arg, chan_list, n_channels); if (!arg) { ret = -ENOMEM; goto exit; } + arg->num_chan = n_channels; + ath12k_wmi_start_scan_init(ar, arg); arg->vdev_id = arvif->vdev_id; arg->scan_id = ATH12K_SCAN_ID; @@ -5638,18 +5640,8 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw, arg->scan_f_passive = 1; } - if (n_channels) { - arg->num_chan = n_channels; - arg->chan_list = kcalloc(arg->num_chan, sizeof(*arg->chan_list), - GFP_KERNEL); - if (!arg->chan_list) { - ret = -ENOMEM; - goto exit; - } - - for (i = 0; i < arg->num_chan; i++) - arg->chan_list[i] = chan_list[i]->center_freq; - } + for (i = 0; i < arg->num_chan; i++) + arg->chan_list[i] = chan_list[i]->center_freq; ret = ath12k_start_scan(ar, arg); if (ret) { @@ -5674,7 +5666,6 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw, exit: if (arg) { - kfree(arg->chan_list); kfree(arg->extraie.ptr); kfree(arg); } @@ -13735,19 +13726,13 @@ int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw, scan_time_msec = hw->wiphy->max_remain_on_channel_duration * 2; struct ath12k_wmi_scan_req_arg *arg __free(kfree) = - kzalloc_obj(*arg); + kzalloc_flex(*arg, chan_list, 1); if (!arg) return -ENOMEM; - ath12k_wmi_start_scan_init(ar, arg); arg->num_chan = 1; + ath12k_wmi_start_scan_init(ar, arg); - u32 *chan_list __free(kfree) = kcalloc(arg->num_chan, sizeof(*chan_list), - GFP_KERNEL); - if (!chan_list) - return -ENOMEM; - - arg->chan_list = chan_list; arg->vdev_id = arvif->vdev_id; arg->scan_id = ATH12K_SCAN_ID; arg->chan_list[0] = chan->center_freq; diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 4a34b2ca99ea..28ab8129a29f 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -3586,7 +3586,6 @@ struct ath12k_wmi_scan_req_arg { u32 num_bssid; u32 num_ssids; u32 n_probes; - u32 *chan_list; u32 notify_scan_events; struct cfg80211_ssid ssid[WLAN_SCAN_MAX_NUM_SSID]; struct ath12k_wmi_mac_addr_params bssid_list[WLAN_SCAN_MAX_NUM_BSSID]; @@ -3595,6 +3594,7 @@ struct ath12k_wmi_scan_req_arg { u32 num_hint_bssid; struct ath12k_wmi_hint_short_ssid_arg hint_s_ssid[WLAN_SCAN_MAX_HINT_S_SSID]; struct ath12k_wmi_hint_bssid_arg hint_bssid[WLAN_SCAN_MAX_HINT_BSSID]; + u32 chan_list[] __counted_by(num_chan); }; struct wmi_ssid_arg { From 8c79aac429b583301f387374ff37c59be671df87 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Fri, 3 Apr 2026 21:30:50 -0700 Subject: [PATCH 010/143] wifi: ath11k: cancel SSR work items during PCI shutdown A reboot can crash the kernel if it overlaps with WLAN firmware crash recovery (SSR). The crash is a NULL pointer dereference in the MHI teardown path while freeing DMA-backed MHI contexts. Simplified trace: dma_free_attrs mhi_deinit_dev_ctxt [mhi] ath11k_pci_power_down [ath11k_pci] ath11k_pci_shutdown [ath11k_pci] device_shutdown kernel_restart On the host side, SSR is driven by the MHI RDDM callback, which queues reset_work to perform device recovery. reset_work power-cycles the device by calling ath11k_hif_power_down() followed by ath11k_hif_power_up(). The power-down phase deinitializes MHI and frees DMA resources. Shutdown/reboot runs fully asynchronously with this RDDM-driven SSR recovery flow. As a result, the shutdown path (ath11k_pci_shutdown() -> ath11k_pci_power_down()) can race with the SSR recovery sequence. Fix this by canceling SSR-related work items during PCI shutdown, marking the device as unregistering, and serializing the RDDM callback path that checks and queues reset_work. This ensures that no new SSR recovery work can be queued once teardown has started, and that any in-flight recovery work is fully synchronized before device power-down, preventing MHI teardown and DMA resource freeing from running more than once. Note: This issue only affects PCI/MHI-based devices. AHB-based ath11k devices do not queue reset_work in normal SSR flows. Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-04866.5-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1 Fixes: 13da397f884d ("ath11k: add support for device recovery for QCA6390/WCN6855") Fixes: 5edbb148bc57 ("wifi: ath11k: Add firmware coredump collection support") Signed-off-by: Wei Zhang Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260404043050.3433754-1-wei.zhang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/mhi.c | 4 +++- drivers/net/wireless/ath/ath11k/pci.c | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath11k/mhi.c b/drivers/net/wireless/ath/ath11k/mhi.c index f994233df2bb..a6c9ff112c68 100644 --- a/drivers/net/wireless/ath/ath11k/mhi.c +++ b/drivers/net/wireless/ath/ath11k/mhi.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2020 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include @@ -282,8 +282,10 @@ static void ath11k_mhi_op_status_cb(struct mhi_controller *mhi_cntrl, break; } + spin_lock_bh(&ab->base_lock); if (!(test_bit(ATH11K_FLAG_UNREGISTERING, &ab->dev_flags))) queue_work(ab->workqueue_aux, &ab->reset_work); + spin_unlock_bh(&ab->base_lock); break; default: diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c index 7114eca8810d..35bb9e7a63a2 100644 --- a/drivers/net/wireless/ath/ath11k/pci.c +++ b/drivers/net/wireless/ath/ath11k/pci.c @@ -1210,6 +1210,14 @@ static void ath11k_pci_shutdown(struct pci_dev *pdev) struct ath11k_pci *ab_pci = ath11k_pci_priv(ab); ath11k_pci_set_irq_affinity_hint(ab_pci, NULL); + + spin_lock_bh(&ab->base_lock); + set_bit(ATH11K_FLAG_UNREGISTERING, &ab->dev_flags); + spin_unlock_bh(&ab->base_lock); + + cancel_work_sync(&ab->reset_work); + cancel_work_sync(&ab->dump_work); + ath11k_pci_power_down(ab, false); } From 31d4f8d427f2b9e423d7e177fb15f934cf0e37dd Mon Sep 17 00:00:00 2001 From: Nicolas Escande Date: Wed, 22 Apr 2026 18:32:07 +0200 Subject: [PATCH 011/143] wifi: ath12k: unify error handling in some ath12k_wmi_xxx() functions This is purely cosmetic changes that simplifies & standardizes error handling for functions that ends with a ath12k_wmi_cmd_send() followed by trivial error handling. Saves a few lines of code too. Compile tested only. Signed-off-by: Nicolas Escande Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20260422163208.3013496-1-nico.escande@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 32 +++++++-------------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 484fdd3b1b7f..fdac05ee11f0 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -4199,12 +4199,9 @@ int ath12k_wmi_pdev_lro_cfg(struct ath12k *ar, if (ret) { ath12k_warn(ar->ab, "failed to send lro cfg req wmi cmd\n"); - goto err; + dev_kfree_skb(skb); } - return 0; -err: - dev_kfree_skb(skb); return ret; } @@ -4335,12 +4332,9 @@ int ath12k_wmi_vdev_spectral_conf(struct ath12k *ar, if (ret) { ath12k_warn(ar->ab, "failed to send spectral scan config wmi cmd\n"); - goto err; + dev_kfree_skb(skb); } - return 0; -err: - dev_kfree_skb(skb); return ret; } @@ -4372,12 +4366,9 @@ int ath12k_wmi_vdev_spectral_enable(struct ath12k *ar, u32 vdev_id, if (ret) { ath12k_warn(ar->ab, "failed to send spectral enable wmi cmd\n"); - goto err; + dev_kfree_skb(skb); } - return 0; -err: - dev_kfree_skb(skb); return ret; } @@ -4418,12 +4409,9 @@ int ath12k_wmi_pdev_dma_ring_cfg(struct ath12k *ar, if (ret) { ath12k_warn(ar->ab, "failed to send dma ring cfg req wmi cmd\n"); - goto err; + dev_kfree_skb(skb); } - return 0; -err: - dev_kfree_skb(skb); return ret; } @@ -10801,10 +10789,9 @@ int ath12k_wmi_mlo_setup(struct ath12k *ar, struct wmi_mlo_setup_arg *mlo_params ath12k_warn(ar->ab, "failed to submit WMI_MLO_SETUP_CMDID command: %d\n", ret); dev_kfree_skb(skb); - return ret; } - return 0; + return ret; } int ath12k_wmi_mlo_ready(struct ath12k *ar) @@ -10829,10 +10816,9 @@ int ath12k_wmi_mlo_ready(struct ath12k *ar) ath12k_warn(ar->ab, "failed to submit WMI_MLO_READY_CMDID command: %d\n", ret); dev_kfree_skb(skb); - return ret; } - return 0; + return ret; } int ath12k_wmi_mlo_teardown(struct ath12k *ar) @@ -10858,10 +10844,9 @@ int ath12k_wmi_mlo_teardown(struct ath12k *ar) ath12k_warn(ar->ab, "failed to submit WMI MLO teardown command: %d\n", ret); dev_kfree_skb(skb); - return ret; } - return 0; + return ret; } bool ath12k_wmi_supports_6ghz_cc_ext(struct ath12k *ar) @@ -10924,10 +10909,9 @@ int ath12k_wmi_send_vdev_set_tpc_power(struct ath12k *ar, if (ret) { ath12k_warn(ar->ab, "failed to send WMI_VDEV_SET_TPC_POWER_CMDID\n"); dev_kfree_skb(skb); - return ret; } - return 0; + return ret; } static int From 845b613b57c626ac85fdefd47196acfb3172fd49 Mon Sep 17 00:00:00 2001 From: Maharaja Kennadyrajan Date: Mon, 13 Apr 2026 21:08:36 +0530 Subject: [PATCH 012/143] wifi: ath12k: handle thermal throttle stats WMI event Add handling for WMI_THERM_THROT_STATS_EVENTID by defining the wmi_therm_throt_stats_event TLV and parsing pdev_id, temperature and throttle level. The firmware can emit this event periodically, including when the throttle level is 0. Log the received thermal throttle stats to get the current temperature level, temperature and thermal throttling levels. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Maharaja Kennadyrajan Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260413153840.1969931-2-maharaja.kennadyrajan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 39 +++++++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/wmi.h | 8 ++++++ 2 files changed, 47 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index fdac05ee11f0..0a0c07ac0c48 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -8750,6 +8750,42 @@ ath12k_wmi_pdev_temperature_event(struct ath12k_base *ab, rcu_read_unlock(); } +static void ath12k_wmi_thermal_throt_stats_event(struct ath12k_base *ab, + struct sk_buff *skb) +{ + const struct wmi_therm_throt_stats_event *ev; + struct ath12k *ar; + const void **tb; + + tb = ath12k_wmi_tlv_parse(ab, skb); + if (IS_ERR(tb)) { + ath12k_err(ab, "failed to parse thermal throttling stats tlv: %ld\n", + PTR_ERR(tb)); + return; + } + + ev = tb[WMI_TAG_THERM_THROT_STATS_EVENT]; + if (!ev) { + ath12k_err(ab, "failed to fetch thermal throt stats ev\n"); + return; + } + + rcu_read_lock(); + ar = ath12k_mac_get_ar_by_pdev_id(ab, le32_to_cpu(ev->pdev_id)); + if (!ar) { + ath12k_warn(ab, "received thermal_throt_stats in invalid pdev %u\n", + le32_to_cpu(ev->pdev_id)); + rcu_read_unlock(); + return; + } + rcu_read_unlock(); + + ath12k_dbg(ab, ATH12K_DBG_WMI, + "thermal stats ev level %u pdev_id %u temp %u throt_levels %u\n", + le32_to_cpu(ev->level), le32_to_cpu(ev->pdev_id), + le32_to_cpu(ev->temp), le32_to_cpu(ev->therm_throt_levels)); +} + static void ath12k_fils_discovery_event(struct ath12k_base *ab, struct sk_buff *skb) { @@ -9799,6 +9835,9 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb) case WMI_PDEV_TEMPERATURE_EVENTID: ath12k_wmi_pdev_temperature_event(ab, skb); break; + case WMI_THERM_THROT_STATS_EVENTID: + ath12k_wmi_thermal_throt_stats_event(ab, skb); + break; case WMI_PDEV_DMA_RING_BUF_RELEASE_EVENTID: ath12k_wmi_pdev_dma_ring_buf_release_event(ab, skb); break; diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 28ab8129a29f..bbda2c441d0b 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -870,6 +870,7 @@ enum wmi_tlv_event_id { WMI_READ_DATA_FROM_FLASH_EVENTID, WMI_REPORT_RX_AGGR_FAILURE_EVENTID, WMI_PKGID_EVENTID, + WMI_THERM_THROT_STATS_EVENTID, WMI_GPIO_INPUT_EVENTID = WMI_TLV_CMD(WMI_GRP_GPIO), WMI_UPLOADH_EVENTID, WMI_CAPTUREH_EVENTID, @@ -4120,6 +4121,13 @@ enum set_init_cc_flags { ALPHA_IS_SET, }; +struct wmi_therm_throt_stats_event { + __le32 pdev_id; + __le32 temp; + __le32 level; + __le32 therm_throt_levels; +} __packed; + struct ath12k_wmi_init_country_arg { union { u16 country_code; From 612556eb774f19f0ad64b5f72e890943b95a1339 Mon Sep 17 00:00:00 2001 From: Maharaja Kennadyrajan Date: Mon, 13 Apr 2026 21:08:37 +0530 Subject: [PATCH 013/143] wifi: ath12k: configure firmware thermal throttling via WMI Ath12k firmware supports thermal-throttling but requires the host to explicitly program throttle levels and mitigation actions via WMI. Without this configuration, firmware-driven thermal mitigation remains inactive or relies on platform-specific defaults. Add host-side support to build and send thermal-throttle configuration using WMI_THERM_THROT_SET_CONF_CMDID during MAC radio start, ensuring thermal parameters are programmed before data traffic begins. Maintain per-radio storage for thermal throttle levels and provide conservative default level tables for Internal Power Amplifier Device (IPA) and External Power Amplifier Device or External Front End Module (XFEM) targets. The appropriate default table is selected based on firmware-advertised service bits, allowing the host to align with target thermal mitigation capabilities. If the WMI TLV service WMI_TLV_SERVICE_IS_TARGET_IPA bit is set, then host selects the thermal throttle values from IPA index from the table and selects values from XFEM index from the table if this WMI TLV service bit is not set. Build and send the thermal throttle configuration request with either 4 or 5 levels depending on firmware capability, and populate optional fields (pout reduction and tx chain mask) only when the corresponding service bits are advertised. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Maharaja Kennadyrajan Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260413153840.1969931-3-maharaja.kennadyrajan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 8 +++ drivers/net/wireless/ath/ath12k/thermal.c | 64 +++++++++++++++++++++ drivers/net/wireless/ath/ath12k/thermal.h | 21 +++++++ drivers/net/wireless/ath/ath12k/wmi.c | 69 +++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/wmi.h | 42 ++++++++++++++ 5 files changed, 204 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 32d590504a80..99e6cc6d6d7f 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -9664,6 +9664,12 @@ static int ath12k_mac_start(struct ath12k *ar) } } + ret = ath12k_thermal_throttling_config_default(ar); + if (ret) { + ath12k_err(ab, "failed to set thermal throttle: %d\n", ret); + goto err; + } + rcu_assign_pointer(ab->pdevs_active[ar->pdev_idx], &ab->pdevs[ar->pdev_idx]); @@ -14446,6 +14452,8 @@ static int ath12k_mac_setup_register(struct ath12k *ar, ar->rssi_info.temp_offset = 0; ar->rssi_info.noise_floor = ar->rssi_info.min_nf_dbm + ar->rssi_info.temp_offset; + ath12k_thermal_init_configs(ar); + return 0; } diff --git a/drivers/net/wireless/ath/ath12k/thermal.c b/drivers/net/wireless/ath/ath12k/thermal.c index a764d2112a3c..4f76622e8117 100644 --- a/drivers/net/wireless/ath/ath12k/thermal.c +++ b/drivers/net/wireless/ath/ath12k/thermal.c @@ -12,6 +12,70 @@ #include "core.h" #include "debug.h" +static const struct ath12k_wmi_tt_level_config_param +tt_level_configs[ATH12K_TT_CFG_IDX_MAX][ENHANCED_THERMAL_LEVELS] = { + [ATH12K_TT_CFG_IDX_IPA] = { + [0] = { .tmplwm = -100, .tmphwm = 115, .dcoffpercent = 0, + .pout_reduction_db = 0 }, + [1] = { .tmplwm = 110, .tmphwm = 120, .dcoffpercent = 0, + .pout_reduction_db = 12 }, + [2] = { .tmplwm = 115, .tmphwm = 125, .dcoffpercent = 50, + .pout_reduction_db = 12 }, + [3] = { .tmplwm = 120, .tmphwm = 130, .dcoffpercent = 90, + .pout_reduction_db = 12 }, + [4] = { .tmplwm = 125, .tmphwm = 130, .dcoffpercent = 100, + .pout_reduction_db = 12 }, + }, + [ATH12K_TT_CFG_IDX_XFEM] = { + [0] = { .tmplwm = -100, .tmphwm = 105, .dcoffpercent = 0, + .pout_reduction_db = 0 }, + [1] = { .tmplwm = 100, .tmphwm = 110, .dcoffpercent = 0, + .pout_reduction_db = 0 }, + [2] = { .tmplwm = 105, .tmphwm = 115, .dcoffpercent = 50, + .pout_reduction_db = 0 }, + [3] = { .tmplwm = 110, .tmphwm = 120, .dcoffpercent = 90, + .pout_reduction_db = 0 }, + [4] = { .tmplwm = 115, .tmphwm = 120, .dcoffpercent = 100, + .pout_reduction_db = 0 }, + }, +}; + +static enum ath12k_thermal_cfg_idx ath12k_thermal_cfg_index(struct ath12k *ar) +{ + if (test_bit(WMI_TLV_SERVICE_IS_TARGET_IPA, ar->ab->wmi_ab.svc_map)) + return ATH12K_TT_CFG_IDX_IPA; + + return ATH12K_TT_CFG_IDX_XFEM; +} + +int ath12k_thermal_throttling_config_default(struct ath12k *ar) +{ + struct ath12k_wmi_thermal_mitigation_arg param = {}; + int ret; + + if (test_bit(WMI_TLV_SERVICE_THERM_THROT_5_LEVELS, ar->ab->wmi_ab.svc_map)) + param.num_levels = ENHANCED_THERMAL_LEVELS; + else + param.num_levels = THERMAL_LEVELS; + + param.levelconf = ar->thermal.tt_level_configs; + + ret = ath12k_wmi_send_thermal_mitigation_cmd(ar, ¶m); + if (ret) + ath12k_warn(ar->ab, + "failed to send thermal mitigation cmd for default config: %d\n", + ret); + return ret; +} + +void ath12k_thermal_init_configs(struct ath12k *ar) +{ + enum ath12k_thermal_cfg_idx cfg_idx; + + cfg_idx = ath12k_thermal_cfg_index(ar); + ar->thermal.tt_level_configs = &tt_level_configs[cfg_idx][0]; +} + static ssize_t ath12k_thermal_temp_show(struct device *dev, struct device_attribute *attr, char *buf) diff --git a/drivers/net/wireless/ath/ath12k/thermal.h b/drivers/net/wireless/ath/ath12k/thermal.h index 9d84056188e1..33231bb3683c 100644 --- a/drivers/net/wireless/ath/ath12k/thermal.h +++ b/drivers/net/wireless/ath/ath12k/thermal.h @@ -9,18 +9,31 @@ #define ATH12K_THERMAL_SYNC_TIMEOUT_HZ (5 * HZ) +#define ATH12K_THERMAL_DEFAULT_DUTY_CYCLE 100 + +enum ath12k_thermal_cfg_idx { + /* Internal Power Amplifier Device */ + ATH12K_TT_CFG_IDX_IPA, + /* External Power Amplifier Device or External Front End Module */ + ATH12K_TT_CFG_IDX_XFEM, + ATH12K_TT_CFG_IDX_MAX, +}; + struct ath12k_thermal { struct completion wmi_sync; /* temperature value in Celsius degree protected by data_lock. */ int temperature; struct device *hwmon_dev; + const struct ath12k_wmi_tt_level_config_param *tt_level_configs; }; #if IS_REACHABLE(CONFIG_THERMAL) int ath12k_thermal_register(struct ath12k_base *ab); void ath12k_thermal_unregister(struct ath12k_base *ab); void ath12k_thermal_event_temperature(struct ath12k *ar, int temperature); +int ath12k_thermal_throttling_config_default(struct ath12k *ar); +void ath12k_thermal_init_configs(struct ath12k *ar); #else static inline int ath12k_thermal_register(struct ath12k_base *ab) { @@ -36,5 +49,13 @@ static inline void ath12k_thermal_event_temperature(struct ath12k *ar, { } +static inline int ath12k_thermal_throttling_config_default(struct ath12k *ar) +{ + return 0; +} + +static inline void ath12k_thermal_init_configs(struct ath12k *ar) +{ +} #endif #endif /* _ATH12K_THERMAL_ */ diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 0a0c07ac0c48..c7559938564c 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -3380,6 +3380,75 @@ int ath12k_wmi_send_set_current_country_cmd(struct ath12k *ar, return ret; } +int +ath12k_wmi_send_thermal_mitigation_cmd(struct ath12k *ar, + struct ath12k_wmi_thermal_mitigation_arg *arg) +{ + struct ath12k_wmi_therm_throt_level_config_param *lvl_conf; + struct ath12k_wmi_therm_throt_config_request_cmd *cmd; + struct ath12k_wmi_pdev *wmi = ar->wmi; + struct wmi_tlv *tlv; + struct sk_buff *skb; + int i, ret, len; + + len = sizeof(*cmd) + TLV_HDR_SIZE + (arg->num_levels * sizeof(*lvl_conf)); + + skb = ath12k_wmi_alloc_skb(wmi->wmi_ab, len); + if (!skb) + return -ENOMEM; + + cmd = (struct ath12k_wmi_therm_throt_config_request_cmd *)skb->data; + cmd->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_THERM_THROT_CONFIG_REQUEST, + sizeof(*cmd)); + cmd->pdev_id = cpu_to_le32(ar->pdev->pdev_id); + cmd->enable = cpu_to_le32(1); + cmd->dc = cpu_to_le32(100); + cmd->dc_per_event = cpu_to_le32(0xffffffff); + cmd->therm_throt_levels = cpu_to_le32(arg->num_levels); + + tlv = (struct wmi_tlv *)(skb->data + sizeof(*cmd)); + tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT, + arg->num_levels * sizeof(*lvl_conf)); + + lvl_conf = (struct ath12k_wmi_therm_throt_level_config_param *)tlv->value; + + for (i = 0; i < arg->num_levels; i++) { + lvl_conf->tlv_header = + ath12k_wmi_tlv_cmd_hdr(WMI_TAG_THERM_THROT_LEVEL_CONFIG_INFO, + sizeof(*lvl_conf)); + + lvl_conf->temp_lwm = a_cpu_to_sle32(arg->levelconf[i].tmplwm); + lvl_conf->temp_hwm = a_cpu_to_sle32(arg->levelconf[i].tmphwm); + lvl_conf->dc_off_percent = cpu_to_le32(arg->levelconf[i].dcoffpercent); + + if (test_bit(WMI_TLV_SERVICE_THERM_THROT_POUT_REDUCTION, + ar->ab->wmi_ab.svc_map)) + lvl_conf->pout_reduction_25db = + cpu_to_le32(arg->levelconf[i].pout_reduction_db); + + if (test_bit(WMI_TLV_SERVICE_THERM_THROT_TX_CHAIN_MASK, + ar->ab->wmi_ab.svc_map)) + lvl_conf->tx_chain_mask = cpu_to_le32(ar->cfg_tx_chainmask); + + lvl_conf->duty_cycle = cpu_to_le32(ATH12K_THERMAL_DEFAULT_DUTY_CYCLE); + lvl_conf++; + } + + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, + "WMI vdev set thermal throt pdev_id %u enable dc 100 dc_per_event 0xffffffff levels %d\n", + ar->pdev->pdev_id, arg->num_levels); + + ret = ath12k_wmi_cmd_send(wmi, skb, WMI_THERM_THROT_SET_CONF_CMDID); + if (ret) { + ath12k_warn(ar->ab, + "failed to send WMI_THERM_THROT_SET_CONF cmd: %d\n", + ret); + dev_kfree_skb(skb); + } + + return ret; +} + int ath12k_wmi_send_11d_scan_start_cmd(struct ath12k *ar, struct wmi_11d_scan_start_arg *arg) { diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index bbda2c441d0b..c644604c1426 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2274,6 +2274,10 @@ enum wmi_tlv_service { WMI_TLV_SERVICE_WMSK_COMPACTION_RX_TLVS = 361, WMI_TLV_SERVICE_PEER_METADATA_V1A_V1B_SUPPORT = 365, + WMI_TLV_SERVICE_THERM_THROT_POUT_REDUCTION = 410, + WMI_TLV_SERVICE_IS_TARGET_IPA = 425, + WMI_TLV_SERVICE_THERM_THROT_TX_CHAIN_MASK = 426, + WMI_TLV_SERVICE_THERM_THROT_5_LEVELS = 429, WMI_TLV_SERVICE_ETH_OFFLOAD = 461, WMI_MAX_EXT2_SERVICE, @@ -4128,6 +4132,42 @@ struct wmi_therm_throt_stats_event { __le32 therm_throt_levels; } __packed; +#define THERMAL_LEVELS 4 +#define ENHANCED_THERMAL_LEVELS 5 + +struct ath12k_wmi_tt_level_config_param { + s32 tmplwm; + s32 tmphwm; + u32 dcoffpercent; + u32 pout_reduction_db; +}; + +struct ath12k_wmi_therm_throt_config_request_cmd { + __le32 tlv_header; + __le32 pdev_id; + __le32 enable; + __le32 dc; + /* After how many duty cycles the firmware sends stats to host */ + __le32 dc_per_event; + __le32 therm_throt_levels; +} __packed; + +struct ath12k_wmi_therm_throt_level_config_param { + __le32 tlv_header; + a_sle32 temp_lwm; + a_sle32 temp_hwm; + __le32 dc_off_percent; + __le32 prio; + __le32 pout_reduction_25db; + __le32 tx_chain_mask; + __le32 duty_cycle; +} __packed; + +struct ath12k_wmi_thermal_mitigation_arg { + int num_levels; + const struct ath12k_wmi_tt_level_config_param *levelconf; +}; + struct ath12k_wmi_init_country_arg { union { u16 country_code; @@ -6522,6 +6562,8 @@ __le32 ath12k_wmi_tlv_hdr(u32 cmd, u32 len); int ath12k_wmi_send_tpc_stats_request(struct ath12k *ar, enum wmi_halphy_ctrl_path_stats_id tpc_stats_type); void ath12k_wmi_free_tpc_stats_mem(struct ath12k *ar); +int ath12k_wmi_send_thermal_mitigation_cmd(struct ath12k *ar, + struct ath12k_wmi_thermal_mitigation_arg *arg); static inline u32 ath12k_wmi_caps_ext_get_pdev_id(const struct ath12k_wmi_caps_ext_params *param) From cd93e8c23ebbd72e9aa799199b14c8433585f747 Mon Sep 17 00:00:00 2001 From: Maharaja Kennadyrajan Date: Mon, 13 Apr 2026 21:08:38 +0530 Subject: [PATCH 014/143] wifi: ath12k: refactor per-radio thermal hwmon setup and cleanup Both the error path in thermal registration and the normal thermal unregister path performed the same hwmon device unregistration and pointer cleanup. Consolidate this logic into a single helper to reduce code duplication and ensure consistent cleanup across all paths. Add a helper to set up the hwmon registration during thermal registration to keep symmetry with thermal cleanup. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Maharaja Kennadyrajan Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260413153840.1969931-4-maharaja.kennadyrajan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/thermal.c | 87 +++++++++++++---------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/thermal.c b/drivers/net/wireless/ath/ath12k/thermal.c index 4f76622e8117..6f70c11c1098 100644 --- a/drivers/net/wireless/ath/ath12k/thermal.c +++ b/drivers/net/wireless/ath/ath12k/thermal.c @@ -130,59 +130,70 @@ static struct attribute *ath12k_hwmon_attrs[] = { }; ATTRIBUTE_GROUPS(ath12k_hwmon); -int ath12k_thermal_register(struct ath12k_base *ab) +static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i) { struct ath12k *ar; - int i, j, ret; + int ret; - if (!IS_REACHABLE(CONFIG_HWMON)) + ar = ab->pdevs[i].ar; + if (!ar) return 0; - for (i = 0; i < ab->num_radios; i++) { - ar = ab->pdevs[i].ar; - if (!ar) - continue; - - ar->thermal.hwmon_dev = - hwmon_device_register_with_groups(&ar->ah->hw->wiphy->dev, - "ath12k_hwmon", ar, - ath12k_hwmon_groups); - if (IS_ERR(ar->thermal.hwmon_dev)) { - ret = PTR_ERR(ar->thermal.hwmon_dev); - ar->thermal.hwmon_dev = NULL; - ath12k_err(ar->ab, "failed to register hwmon device: %d\n", - ret); - for (j = i - 1; j >= 0; j--) { - ar = ab->pdevs[j].ar; - if (!ar) - continue; - - hwmon_device_unregister(ar->thermal.hwmon_dev); - ar->thermal.hwmon_dev = NULL; - } - return ret; - } + ar->thermal.hwmon_dev = + hwmon_device_register_with_groups(&ar->ah->hw->wiphy->dev, + "ath12k_hwmon", ar, + ath12k_hwmon_groups); + if (IS_ERR(ar->thermal.hwmon_dev)) { + ret = PTR_ERR(ar->thermal.hwmon_dev); + ar->thermal.hwmon_dev = NULL; + ath12k_err(ar->ab, "failed to register hwmon device: %d\n", + ret); + return ret; } return 0; } -void ath12k_thermal_unregister(struct ath12k_base *ab) +static void ath12k_thermal_cleanup_radio(struct ath12k_base *ab, int i) { struct ath12k *ar; + + ar = ab->pdevs[i].ar; + if (!ar) + return; + + hwmon_device_unregister(ar->thermal.hwmon_dev); + ar->thermal.hwmon_dev = NULL; +} + +int ath12k_thermal_register(struct ath12k_base *ab) +{ + int i, ret; + + if (!IS_REACHABLE(CONFIG_HWMON)) + return 0; + + for (i = 0; i < ab->num_radios; i++) { + ret = ath12k_thermal_setup_radio(ab, i); + if (ret) + goto out; + } + + return 0; +out: + for (i--; i >= 0; i--) + ath12k_thermal_cleanup_radio(ab, i); + + return ret; +} + +void ath12k_thermal_unregister(struct ath12k_base *ab) +{ int i; if (!IS_REACHABLE(CONFIG_HWMON)) return; - for (i = 0; i < ab->num_radios; i++) { - ar = ab->pdevs[i].ar; - if (!ar) - continue; - - if (ar->thermal.hwmon_dev) { - hwmon_device_unregister(ar->thermal.hwmon_dev); - ar->thermal.hwmon_dev = NULL; - } - } + for (i = 0; i < ab->num_radios; i++) + ath12k_thermal_cleanup_radio(ab, i); } From 0ab4dc09b7819ddea698fe5a0cc711a64186f12d Mon Sep 17 00:00:00 2001 From: Maharaja Kennadyrajan Date: Mon, 13 Apr 2026 21:08:39 +0530 Subject: [PATCH 015/143] wifi: ath12k: reorder group start/stop for safe thermal sysfs cleanup A later change adds thermal cooling device sysfs under the wiphy device kobject. With the current teardown order, MAC/wiphy are unregistered before per-device cleanup, so any subsequent thermal sysfs removal would run after the wiphy kobject is gone. That ordering is asymmetric with setup and would risk kernfs issues during removal. This change also adjusts the position of ath12k_mac_mlo_teardown(). Previously it ran before per-device cleanup/MAC unregister. MLO teardown issues WMI to teardown multi-link state and is part of the MAC teardown sequence. Placing it alongside MAC unregister (after per-device cleanup) preserves setup/teardown symmetry and avoids racing with remaining netdev/wiphy state. Reorder hw_group_stop() so per-device cleanup (including thermal/hwmon sysfs removal) runs while the wiphy still exists. After per-device cleanup completes, unregister the MAC (dropping wiphys), run ath12k_mac_mlo_teardown(), and finally destroy the MAC. This mirrors the setup sequence and keeps sysfs cleanup safe when introduced in a later patch. To keep start/stop symmetry, add ath12k_core_device_setup() to encapsulate the per-device bring-up steps (pdev create, IRQ enable, rfkill config) that were previously open-coded in hw_group_start(). Use this helper in hw_group_start() to match the existing per-device cleanup helper used by hw_group_stop(). Note that set_bit(ATH12K_FLAG_REGISTERED, &ab->dev_flags) is now executed outside the ab->core_lock critical section. The core_lock has not provided protection for the REGISTERED flag, readers do not rely on core_lock for this bit, and the flag is only toggled in the serialized group start/stop path using atomic bitops. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Maharaja Kennadyrajan Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260413153840.1969931-5-maharaja.kennadyrajan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 50 +++++++++++++++----------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index e8137144fd1f..1a9866061f82 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1006,6 +1006,27 @@ static void ath12k_core_device_cleanup(struct ath12k_base *ab) mutex_unlock(&ab->core_lock); } +static int ath12k_core_device_setup(struct ath12k_base *ab) +{ + int ret; + + guard(mutex)(&ab->core_lock); + + ret = ath12k_core_pdev_create(ab); + if (ret) { + ath12k_err(ab, "failed to create pdev core %d\n", ret); + return ret; + } + + ath12k_hif_irq_enable(ab); + + ret = ath12k_core_rfkill_config(ab); + if (ret && ret != -EOPNOTSUPP) + return ret; + + return 0; +} + static void ath12k_core_hw_group_stop(struct ath12k_hw_group *ag) { struct ath12k_base *ab; @@ -1015,10 +1036,6 @@ static void ath12k_core_hw_group_stop(struct ath12k_hw_group *ag) clear_bit(ATH12K_GROUP_FLAG_REGISTERED, &ag->flags); - ath12k_mac_unregister(ag); - - ath12k_mac_mlo_teardown(ag); - for (i = ag->num_devices - 1; i >= 0; i--) { ab = ag->ab[i]; if (!ab) @@ -1029,6 +1046,12 @@ static void ath12k_core_hw_group_stop(struct ath12k_hw_group *ag) ath12k_core_device_cleanup(ab); } + /* Unregister MAC (drops wiphys) only after per-device cleanup */ + ath12k_mac_unregister(ag); + + /* Teardown MLO state after MAC unregister for symmetry */ + ath12k_mac_mlo_teardown(ag); + ath12k_mac_destroy(ag); } @@ -1165,26 +1188,11 @@ static int ath12k_core_hw_group_start(struct ath12k_hw_group *ag) if (!ab) continue; - mutex_lock(&ab->core_lock); - set_bit(ATH12K_FLAG_REGISTERED, &ab->dev_flags); - ret = ath12k_core_pdev_create(ab); - if (ret) { - ath12k_err(ab, "failed to create pdev core %d\n", ret); - mutex_unlock(&ab->core_lock); + ret = ath12k_core_device_setup(ab); + if (ret) goto err; - } - - ath12k_hif_irq_enable(ab); - - ret = ath12k_core_rfkill_config(ab); - if (ret && ret != -EOPNOTSUPP) { - mutex_unlock(&ab->core_lock); - goto err; - } - - mutex_unlock(&ab->core_lock); } return 0; From e12d2d3983acb150fd987d19ec6a2a530da110df Mon Sep 17 00:00:00 2001 From: Maharaja Kennadyrajan Date: Mon, 13 Apr 2026 21:08:40 +0530 Subject: [PATCH 016/143] wifi: ath12k: add thermal cooling device support Add thermal cooling device support to control the temperature by throttling data transmission. Throttling is performed by suspending data TX queues according to a configured duty-cycle off percentage. The thermal cooling device allows users to configure the duty-cycle off percentage and operate the device with the selected value. User configuration updates a single duty-cycle off percentage, which is applied uniformly by the host and treated as only one temperature level. This value remains in effect until updated again by the user. All other thermal throttling parameters continue to use their default firmware provided values. Reject invalid duty-cycle off percentage values that fall outside the supported range. Register a cooling device to allow the thermal framework to query and set the current throttle state, report the maximum supported state, and keep the host state in sync with successful firmware updates. A throttle state of zero restores the default firmware thermal configuration. Command to set the duty-cycle off percent: echo 40 > /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device0/cur_state Command to read duty-cycle off percent: cat /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device0/cur_state Command to read the maximum duty-cycle off percent: cat /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device0/max_state Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Maharaja Kennadyrajan Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260413153840.1969931-6-maharaja.kennadyrajan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 1 + drivers/net/wireless/ath/ath12k/thermal.c | 107 +++++++++++++++++++++- drivers/net/wireless/ath/ath12k/thermal.h | 14 +++ 3 files changed, 121 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 99e6cc6d6d7f..9ce759626f18 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -14806,6 +14806,7 @@ static void ath12k_mac_setup(struct ath12k *ar) init_completion(&ar->completed_11d_scan); init_completion(&ar->regd_update_completed); init_completion(&ar->thermal.wmi_sync); + mutex_init(&ar->thermal.lock); ar->thermal.temperature = 0; ar->thermal.hwmon_dev = NULL; diff --git a/drivers/net/wireless/ath/ath12k/thermal.c b/drivers/net/wireless/ath/ath12k/thermal.c index 6f70c11c1098..97fc49c40ac1 100644 --- a/drivers/net/wireless/ath/ath12k/thermal.c +++ b/drivers/net/wireless/ath/ath12k/thermal.c @@ -76,6 +76,73 @@ void ath12k_thermal_init_configs(struct ath12k *ar) ar->thermal.tt_level_configs = &tt_level_configs[cfg_idx][0]; } +static int +ath12k_thermal_get_max_throttle_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = ATH12K_THERMAL_THROTTLE_MAX; + + return 0; +} + +static int +ath12k_thermal_get_cur_throttle_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct ath12k *ar = cdev->devdata; + + mutex_lock(&ar->thermal.lock); + *state = ar->thermal.throttle_state; + mutex_unlock(&ar->thermal.lock); + + return 0; +} + +int ath12k_thermal_set_throttling(struct ath12k *ar, u32 throttle_state) +{ + struct ath12k_wmi_thermal_mitigation_arg param = {}; + struct ath12k_wmi_tt_level_config_param cfg = {}; + int ret; + + param.num_levels = 1; + cfg.dcoffpercent = throttle_state; + param.levelconf = &cfg; + + ret = ath12k_wmi_send_thermal_mitigation_cmd(ar, ¶m); + if (ret) + ath12k_warn(ar->ab, "failed to send thermal mitigation cmd: %d\n", + ret); + + return ret; +} + +static int +ath12k_thermal_set_cur_throttle_state(struct thermal_cooling_device *cdev, + unsigned long throttle_state) +{ + struct ath12k *ar = cdev->devdata; + + if (throttle_state > ATH12K_THERMAL_THROTTLE_MAX) + return -EINVAL; + + scoped_guard(mutex, &ar->thermal.lock) { + if (ar->thermal.throttle_state == throttle_state) + return 0; + ar->thermal.throttle_state = throttle_state; + } + + if (throttle_state == 0) + return ath12k_thermal_throttling_config_default(ar); + + return ath12k_thermal_set_throttling(ar, throttle_state); +} + +static const struct thermal_cooling_device_ops ath12k_thermal_ops = { + .get_max_state = ath12k_thermal_get_max_throttle_state, + .get_cur_state = ath12k_thermal_get_cur_throttle_state, + .set_cur_state = ath12k_thermal_set_cur_throttle_state, +}; + static ssize_t ath12k_thermal_temp_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -132,6 +199,7 @@ ATTRIBUTE_GROUPS(ath12k_hwmon); static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i) { + char pdev_name[20]; struct ath12k *ar; int ret; @@ -139,6 +207,28 @@ static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i) if (!ar) return 0; + ar->thermal.cdev = + thermal_cooling_device_register("ath12k_thermal", ar, + &ath12k_thermal_ops); + if (IS_ERR(ar->thermal.cdev)) { + ret = PTR_ERR(ar->thermal.cdev); + ar->thermal.cdev = NULL; + ath12k_err(ar->ab, "failed to register cooling device: %d\n", + ret); + return ret; + } + + scnprintf(pdev_name, sizeof(pdev_name), "cooling_device%u", + ar->hw_link_id); + + ret = sysfs_create_link(&ar->ah->hw->wiphy->dev.kobj, + &ar->thermal.cdev->device.kobj, pdev_name); + if (ret) { + ath12k_err(ab, "failed to create cooling device symlink: %d\n", + ret); + goto unregister_cdev; + } + ar->thermal.hwmon_dev = hwmon_device_register_with_groups(&ar->ah->hw->wiphy->dev, "ath12k_hwmon", ar, @@ -148,14 +238,22 @@ static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i) ar->thermal.hwmon_dev = NULL; ath12k_err(ar->ab, "failed to register hwmon device: %d\n", ret); - return ret; + goto remove_sysfs; } return 0; + +remove_sysfs: + sysfs_remove_link(&ar->ah->hw->wiphy->dev.kobj, pdev_name); +unregister_cdev: + thermal_cooling_device_unregister(ar->thermal.cdev); + ar->thermal.cdev = NULL; + return ret; } static void ath12k_thermal_cleanup_radio(struct ath12k_base *ab, int i) { + char pdev_name[20]; struct ath12k *ar; ar = ab->pdevs[i].ar; @@ -164,6 +262,13 @@ static void ath12k_thermal_cleanup_radio(struct ath12k_base *ab, int i) hwmon_device_unregister(ar->thermal.hwmon_dev); ar->thermal.hwmon_dev = NULL; + + scnprintf(pdev_name, sizeof(pdev_name), "cooling_device%u", + ar->hw_link_id); + sysfs_remove_link(&ar->ah->hw->wiphy->dev.kobj, pdev_name); + + thermal_cooling_device_unregister(ar->thermal.cdev); + ar->thermal.cdev = NULL; } int ath12k_thermal_register(struct ath12k_base *ab) diff --git a/drivers/net/wireless/ath/ath12k/thermal.h b/drivers/net/wireless/ath/ath12k/thermal.h index 33231bb3683c..30e7b0880e05 100644 --- a/drivers/net/wireless/ath/ath12k/thermal.h +++ b/drivers/net/wireless/ath/ath12k/thermal.h @@ -7,9 +7,12 @@ #ifndef _ATH12K_THERMAL_ #define _ATH12K_THERMAL_ +#include + #define ATH12K_THERMAL_SYNC_TIMEOUT_HZ (5 * HZ) #define ATH12K_THERMAL_DEFAULT_DUTY_CYCLE 100 +#define ATH12K_THERMAL_THROTTLE_MAX 100 enum ath12k_thermal_cfg_idx { /* Internal Power Amplifier Device */ @@ -26,6 +29,10 @@ struct ath12k_thermal { int temperature; struct device *hwmon_dev; const struct ath12k_wmi_tt_level_config_param *tt_level_configs; + struct thermal_cooling_device *cdev; + /* Serialize thermal operations and hwmon reads */ + struct mutex lock; + u32 throttle_state; }; #if IS_REACHABLE(CONFIG_THERMAL) @@ -34,6 +41,7 @@ void ath12k_thermal_unregister(struct ath12k_base *ab); void ath12k_thermal_event_temperature(struct ath12k *ar, int temperature); int ath12k_thermal_throttling_config_default(struct ath12k *ar); void ath12k_thermal_init_configs(struct ath12k *ar); +int ath12k_thermal_set_throttling(struct ath12k *ar, u32 throttle_state); #else static inline int ath12k_thermal_register(struct ath12k_base *ab) { @@ -57,5 +65,11 @@ static inline int ath12k_thermal_throttling_config_default(struct ath12k *ar) static inline void ath12k_thermal_init_configs(struct ath12k *ar) { } + +static inline int ath12k_thermal_set_throttling(struct ath12k *ar, + u32 throttle_state) +{ + return 0; +} #endif #endif /* _ATH12K_THERMAL_ */ From 1e8ab2e2e9622d7fa31810a4d7031614e2945f45 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Wed, 15 Apr 2026 12:08:57 +0530 Subject: [PATCH 017/143] wifi: ath12k: add channel 177 to the 5 GHz channel list Add support for 5 GHz channel 177 with a center frequency of 5885 MHz and Operating Class 125 per IEEE Std 802.11-2024 Table E-4. Channels 169, 173, and 177 are in the 5.9 GHz band and must be disabled when 5.9 GHz service bit is not supported. The 5.9 GHz band is only permitted for WLAN operation under FCC regulations. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Yingying Tang Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260415063857.2462256-1-yintang@qti.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.h | 4 ++-- drivers/net/wireless/ath/ath12k/dp_rx.c | 11 +++++++++-- drivers/net/wireless/ath/ath12k/mac.c | 26 +++++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/wmi.h | 1 + 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 8be435535a4e..4edc8f4e0cb5 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -542,8 +542,8 @@ struct ath12k_sta { #define ATH12K_MAX_5GHZ_FREQ (ATH12K_5GHZ_MAX_CENTER + ATH12K_HALF_20MHZ_BW) #define ATH12K_MIN_6GHZ_FREQ (ATH12K_6GHZ_MIN_CENTER - ATH12K_HALF_20MHZ_BW) #define ATH12K_MAX_6GHZ_FREQ (ATH12K_6GHZ_MAX_CENTER + ATH12K_HALF_20MHZ_BW) -#define ATH12K_NUM_CHANS 101 -#define ATH12K_MAX_5GHZ_CHAN 173 +#define ATH12K_NUM_CHANS 102 +#define ATH12K_MAX_5GHZ_CHAN 177 static inline bool ath12k_is_2ghz_channel_freq(u32 freq) { diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index cae00e0539df..a4fec1dc55c6 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -17,6 +17,11 @@ #include "dp_mon.h" #include "debugfs_htt_stats.h" +#define ATH12K_2GHZ_MIN_CHAN_NUM 1 +#define ATH12K_2GHZ_MAX_CHAN_NUM 14 +#define ATH12K_5GHZ_MIN_CHAN_NUM 36 +#define ATH12K_5GHZ_MAX_CHAN_NUM 177 + static int ath12k_dp_rx_tid_delete_handler(struct ath12k_base *ab, struct ath12k_dp_rx_tid_rxq *rx_tid); @@ -1314,9 +1319,11 @@ void ath12k_dp_rx_h_ppdu(struct ath12k_pdev_dp *dp_pdev, center_freq <= ATH12K_MAX_6GHZ_FREQ) { rx_status->band = NL80211_BAND_6GHZ; rx_status->freq = center_freq; - } else if (channel_num >= 1 && channel_num <= 14) { + } else if (channel_num >= ATH12K_2GHZ_MIN_CHAN_NUM && + channel_num <= ATH12K_2GHZ_MAX_CHAN_NUM) { rx_status->band = NL80211_BAND_2GHZ; - } else if (channel_num >= 36 && channel_num <= 173) { + } else if (channel_num >= ATH12K_5GHZ_MIN_CHAN_NUM && + channel_num <= ATH12K_5GHZ_MAX_CHAN_NUM) { rx_status->band = NL80211_BAND_5GHZ; } diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 9ce759626f18..c6879f21a615 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -51,6 +51,9 @@ .max_power = 30, \ } +#define ATH12K_5_9_GHZ_MIN_FREQ 5845 +#define ATH12K_5_9_GHZ_MAX_FREQ 5885 + static const struct ieee80211_channel ath12k_2ghz_channels[] = { CHAN2G(1, 2412, 0), CHAN2G(2, 2417, 0), @@ -96,6 +99,7 @@ static const struct ieee80211_channel ath12k_5ghz_channels[] = { CHAN5G(165, 5825, 0), CHAN5G(169, 5845, 0), CHAN5G(173, 5865, 0), + CHAN5G(177, 5885, 0), }; static const struct ieee80211_channel ath12k_6ghz_channels[] = { @@ -13891,6 +13895,26 @@ static int ath12k_mac_update_band(struct ath12k *ar, return 0; } +static void ath12k_mac_update_5_9_ghz_ch_list(struct ath12k *ar, + struct ieee80211_supported_band *band) +{ + int i; + + if (test_bit(WMI_TLV_SERVICE_5_9GHZ_SUPPORT, + ar->ab->wmi_ab.svc_map)) + return; + + guard(spinlock_bh)(&ar->ab->base_lock); + if (ar->ab->dfs_region != ATH12K_DFS_REG_FCC) + return; + + for (i = 0; i < band->n_channels; i++) { + if (band->channels[i].center_freq >= ATH12K_5_9_GHZ_MIN_FREQ && + band->channels[i].center_freq <= ATH12K_5_9_GHZ_MAX_FREQ) + band->channels[i].flags |= IEEE80211_CHAN_DISABLED; + } +} + static int ath12k_mac_setup_channels_rates(struct ath12k *ar, u32 supported_bands, struct ieee80211_supported_band *bands[]) @@ -14024,6 +14048,8 @@ static int ath12k_mac_setup_channels_rates(struct ath12k *ar, band->n_bitrates = ath12k_a_rates_size; band->bitrates = ath12k_a_rates; + ath12k_mac_update_5_9_ghz_ch_list(ar, band); + if (ab->hw_params->single_pdev_only) { phy_id = ath12k_get_phy_id(ar, WMI_HOST_WLAN_5GHZ_CAP); reg_cap = &ab->hal_reg_cap[phy_id]; diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index c644604c1426..65ff41396c08 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2260,6 +2260,7 @@ enum wmi_tlv_service { WMI_TLV_SERVICE_FREQINFO_IN_METADATA = 219, WMI_TLV_SERVICE_EXT2_MSG = 220, WMI_TLV_SERVICE_BEACON_PROTECTION_SUPPORT = 244, + WMI_TLV_SERVICE_5_9GHZ_SUPPORT = 247, WMI_TLV_SERVICE_SRG_SRP_SPATIAL_REUSE_SUPPORT = 249, WMI_TLV_SERVICE_MBSS_PARAM_IN_VDEV_START_SUPPORT = 253, From f8b7c2f1a45bb70eac754b2bd438168bbeafb499 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 28 Apr 2026 18:29:49 +0200 Subject: [PATCH 018/143] wifi: ath10k: drop gpio_led reference The driver uses a 'struct gpio_led' internally, but does not actually interact with the gpio_led driver, in particular it does not actually use gpiolib here. Signed-off-by: Arnd Bergmann Reviewed-by: Loic Poulain Link: https://patch.msgid.link/20260428162955.614739-1-arnd@kernel.org Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/core.h | 1 - drivers/net/wireless/ath/ath10k/leds.c | 8 +------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h index 73a9db302245..dfee432615eb 100644 --- a/drivers/net/wireless/ath/ath10k/core.h +++ b/drivers/net/wireless/ath/ath10k/core.h @@ -1269,7 +1269,6 @@ struct ath10k { } testmode; struct { - struct gpio_led wifi_led; struct led_classdev cdev; char label[48]; u32 gpio_state_pin; diff --git a/drivers/net/wireless/ath/ath10k/leds.c b/drivers/net/wireless/ath/ath10k/leds.c index 3a6c8111e7c6..a3961e7760a5 100644 --- a/drivers/net/wireless/ath/ath10k/leds.c +++ b/drivers/net/wireless/ath/ath10k/leds.c @@ -19,15 +19,13 @@ static int ath10k_leds_set_brightness_blocking(struct led_classdev *led_cdev, { struct ath10k *ar = container_of(led_cdev, struct ath10k, leds.cdev); - struct gpio_led *led = &ar->leds.wifi_led; mutex_lock(&ar->conf_mutex); if (ar->state != ATH10K_STATE_ON) goto out; - ar->leds.gpio_state_pin = (brightness != LED_OFF) ^ led->active_low; - ath10k_wmi_gpio_output(ar, ar->hw_params.led_pin, ar->leds.gpio_state_pin); + ath10k_wmi_gpio_output(ar, ar->hw_params.led_pin, brightness == LED_OFF); out: mutex_unlock(&ar->conf_mutex); @@ -63,13 +61,9 @@ int ath10k_leds_register(struct ath10k *ar) snprintf(ar->leds.label, sizeof(ar->leds.label), "ath10k-%s", wiphy_name(ar->hw->wiphy)); - ar->leds.wifi_led.active_low = 1; - ar->leds.wifi_led.name = ar->leds.label; - ar->leds.wifi_led.default_state = LEDS_GPIO_DEFSTATE_KEEP; ar->leds.cdev.name = ar->leds.label; ar->leds.cdev.brightness_set_blocking = ath10k_leds_set_brightness_blocking; - ar->leds.cdev.default_trigger = ar->leds.wifi_led.default_trigger; ret = led_classdev_register(wiphy_dev(ar->hw->wiphy), &ar->leds.cdev); if (ret) From db17e958184be9fe2b6242b7c5b7b261c0ce7110 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 17 Mar 2026 19:08:32 +0100 Subject: [PATCH 019/143] wifi: ath: Fix the license marking The Copyright for Qualcomm changed its format and replaces the old Qualcomm Innovative Center by Qualcomm Technology Inc. Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260317180833.3061582-1-daniel.lezcano@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/qmi.c | 2 +- drivers/net/wireless/ath/ath11k/qmi.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/qmi.c b/drivers/net/wireless/ath/ath10k/qmi.c index af58f88c27e1..0d74548a5f34 100644 --- a/drivers/net/wireless/ath/ath10k/qmi.c +++ b/drivers/net/wireless/ath/ath10k/qmi.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: ISC /* * Copyright (c) 2018 The Linux Foundation. All rights reserved. - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include diff --git a/drivers/net/wireless/ath/ath11k/qmi.c b/drivers/net/wireless/ath/ath11k/qmi.c index 1397756d6251..410a7ee076a0 100644 --- a/drivers/net/wireless/ath/ath11k/qmi.c +++ b/drivers/net/wireless/ath/ath11k/qmi.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ From e7f9be2c7bfff07b5aba1e6eac3452cd729ad214 Mon Sep 17 00:00:00 2001 From: Aishwarya R Date: Mon, 11 May 2026 09:32:41 +0530 Subject: [PATCH 020/143] wifi: ath12k: Add support for handling incumbent signal interference in 6 GHz When incumbent signal interference is detected by an AP/mesh interface operating in the 6 GHz band, as mandated by the FCC, it is expected to vacate the affected channels. The firmware indicates the interference to the host using the WMI_DCS_INTERFERENCE_EVENT. To handle the new WMI event, first parse it to retrieve the interference information. Next, validate the interference-detected channel and the interference bitmap. The interference bitmap received from the firmware uses a mapping where bit 0 corresponds to the primary 20 MHz segment, regardless of its position within the operating bandwidth. Bit 1 represents the next adjacent 20 MHz segment, bit 2 the lower 20 MHz segment of the adjacent 40 MHz segment, and so on, progressing sequentially across the bandwidth. However, for userspace consumption via mac80211, this bitmap must be transformed into a standardized format such that each bit position directly maps to the corresponding sub-channel index within the operating bandwidth. Finally, indicate the transformed interference bitmap to mac80211, which then notifies userspace of the interference. Once the incumbent signal interference is detected, firmware suspends TX internally on the affected operating channel while userspace decides the mitigation action. Userspace is expected to trigger a channel switch or bandwidth reduction to mitigate the interference. Also, add a flag handling_in_progress to indicate that handling of interference is in progress. Set it to true after indicating to mac80211 about the interference. Reset the flag to false after the operating channel is switched by userspace. This prevents processing any further interference events when there is already a previous event being handled. Hence, further events are processed only after a channel switch request is received from userspace for the previous event. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.5-01651-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aishwarya R Co-developed-by: Hari Chandrakanthan Signed-off-by: Hari Chandrakanthan Signed-off-by: Amith A Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260511040242.1351792-2-amith.a@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.h | 8 + drivers/net/wireless/ath/ath12k/mac.c | 46 +++ drivers/net/wireless/ath/ath12k/wmi.c | 389 +++++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/wmi.h | 58 +++- 4 files changed, 500 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 4edc8f4e0cb5..f6d8ec9ef7b0 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -763,6 +763,14 @@ struct ath12k { struct ath12k_pdev_rssi_offsets rssi_info; struct ath12k_thermal thermal; + + /* Protected by ar->data_lock */ + struct ath12k_incumbent_signal_interference { + u32 center_freq; + enum nl80211_chan_width width; + u32 chan_bw_interference_bitmap; + bool handling_in_progress; + } incumbent_signal_interference; }; struct ath12k_hw { diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index c6879f21a615..54081beb9a5f 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -9641,6 +9641,10 @@ static int ath12k_mac_start(struct ath12k *ar) ar->allocated_vdev_map = 0; ar->chan_tx_pwr = ATH12K_PDEV_TX_POWER_INVALID; + spin_lock_bh(&ar->data_lock); + ar->incumbent_signal_interference.handling_in_progress = false; + spin_unlock_bh(&ar->data_lock); + /* Configure monitor status ring with default rx_filter to get rx status * such as rssi, rx_duration. */ @@ -9854,6 +9858,10 @@ static void ath12k_mac_stop(struct ath12k *ar) synchronize_rcu(); atomic_set(&ar->num_pending_mgmt_tx, 0); + + spin_lock_bh(&ar->data_lock); + ar->incumbent_signal_interference.handling_in_progress = false; + spin_unlock_bh(&ar->data_lock); } void ath12k_mac_op_stop(struct ieee80211_hw *hw, bool suspend) @@ -11440,8 +11448,10 @@ ath12k_mac_update_vif_chan(struct ath12k *ar, struct ieee80211_vif_chanctx_switch *vifs, int n_vifs) { + struct ath12k_incumbent_signal_interference *incumbent; struct ath12k_wmi_vdev_up_params params = {}; struct ieee80211_bss_conf *link_conf; + struct cfg80211_chan_def *chandef; struct ath12k_base *ab = ar->ab; struct ath12k_link_vif *arvif; struct ieee80211_vif *vif; @@ -11553,6 +11563,42 @@ ath12k_mac_update_vif_chan(struct ath12k *ar, if (!ath12k_mac_monitor_stop(ar)) ath12k_mac_monitor_start(ar); } + + incumbent = &ar->incumbent_signal_interference; + spin_lock_bh(&ar->data_lock); + if (incumbent->handling_in_progress) { + chandef = &vifs[0].new_ctx->def; + if (incumbent->chan_bw_interference_bitmap & + ATH12K_WMI_DCS_SEG_PRI20) { + if (incumbent->center_freq != + chandef->chan->center_freq) { + incumbent->chan_bw_interference_bitmap = 0; + incumbent->handling_in_progress = false; + ath12k_dbg(ab, ATH12K_DBG_MAC, + "incumbent signal interference chan switch completed\n"); + } else { + ath12k_warn(ab, + "incumbent signal interference chan switch not done, freq %u\n", + incumbent->center_freq); + } + } else { + if (incumbent->center_freq != + chandef->chan->center_freq || + incumbent->width != chandef->width) { + incumbent->chan_bw_interference_bitmap = 0; + incumbent->handling_in_progress = false; + ath12k_dbg(ab, ATH12K_DBG_MAC, + "Bandwidth/channel change due to incumbent signal interference completed\n"); + } else { + ath12k_warn(ab, "Bandwidth/channel change due to incumbent sig intf not done intf_freq %u chan_freq %u intf_width %u chan_width %u\n", + incumbent->center_freq, + chandef->chan->center_freq, + incumbent->width, + chandef->width); + } + } + } + spin_unlock_bh(&ar->data_lock); } static void diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index c7559938564c..8609c612aa2c 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -234,6 +234,68 @@ static const int ath12k_hw_mode_pri_map[] = { PRIMAP(WMI_HOST_HW_MODE_MAX), }; +/* + * Interference bitmap transform maps used by + * ath12k_wmi_transform_interference_bitmap(). + * + * Firmware reports bitmap bits in a primary-based order where: + * - bit 0 is always the primary 20 MHz segment, + * - bit 1 is the adjacent 20 MHz in the same 40 MHz block, + * - bit 2 is the lower 20 MHz segment of the adjacent 40 MHz segment + * - bit 3 is the higher 20 MHz segment of the adjacent 40 MHz segment + * - remaining bits continue outward in 80/160/320 MHz groups. + * + * cfg80211 userspace notification expects absolute frequency order where: + * - bit 0 is the lowest-frequency 20 MHz segment in the current chandef, + * - bit N increases monotonically toward higher frequency. + * + * For each bandwidth-specific map: + * - row index = primary 20 MHz index in absolute (low->high) order, + * - column index = source bit position from firmware bitmap, + * - value = destination bit position in absolute order bitmap. + * + * Example for 80 MHz: if primary index is 2 (third 20 MHz chunk from low + * frequency), row intf_map_80[2] = { 2, 3, 0, 1 } means firmware bits {0,1,2,3} + * are remapped to destination bits {2,3,0,1} before notifying cfg80211. + */ + +static const int intf_map_80[4][4] = { + { 0, 1, 2, 3 }, + { 1, 0, 2, 3 }, + { 2, 3, 0, 1 }, + { 3, 2, 0, 1 } +}; + +static const int intf_map_160[8][8] = { + { 0, 1, 2, 3, 4, 5, 6, 7 }, + { 1, 0, 2, 3, 4, 5, 6, 7 }, + { 2, 3, 0, 1, 4, 5, 6, 7 }, + { 3, 2, 0, 1, 4, 5, 6, 7 }, + { 4, 5, 6, 7, 0, 1, 2, 3 }, + { 5, 4, 6, 7, 0, 1, 2, 3 }, + { 6, 7, 4, 5, 0, 1, 2, 3 }, + { 7, 6, 4, 5, 0, 1, 2, 3 } +}; + +static const int intf_map_320[16][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 2, 3, 0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 3, 2, 0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 4, 5, 6, 7, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 5, 4, 6, 7, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 6, 7, 4, 5, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 7, 6, 4, 5, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 }, + { 9, 8, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 }, + { 10, 11, 8, 9, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 }, + { 11, 10, 8, 9, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 }, + { 12, 13, 14, 15, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 }, + { 13, 12, 14, 15, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 }, + { 14, 15, 12, 13, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 }, + { 15, 14, 12, 13, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 } +}; + static int ath12k_wmi_tlv_iter(struct ath12k_base *ab, const void *ptr, size_t len, int (*iter)(struct ath12k_base *ab, u16 tag, u16 len, @@ -8597,6 +8659,330 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab, ev->ctl_failsafe_status); } +static int +ath12k_wmi_incumbent_signal_interference_subtlv_parser(struct ath12k_base *ab, + u16 tag, u16 len, + const void *ptr, + void *data) +{ + const struct ath12k_wmi_incumbent_signal_interference_params *info; + struct ath12k_wmi_incumbent_signal_interference_arg *arg = data; + + switch (tag) { + case WMI_TAG_DCS_INCUMBENT_SIGNAL_INTERFERENCE_TYPE: + if (len < sizeof(*info)) { + ath12k_warn(ab, + "DCS incumbent signal interference subtlv 0x%x invalid len %u\n", + tag, len); + return -EINVAL; + } + + info = ptr; + + arg->chan_width = le32_to_cpu(info->chan_width); + arg->chan_freq = le32_to_cpu(info->chan_freq); + arg->center_freq0 = le32_to_cpu(info->center_freq0); + arg->center_freq1 = le32_to_cpu(info->center_freq1); + arg->chan_bw_interference_bitmap = + le32_to_cpu(info->chan_bw_interference_bitmap); + + ath12k_dbg(ab, ATH12K_DBG_WMI, + "incumbent signal interference chan width %u freq %u center_freq0 %u center_freq1 %u bitmap 0x%x\n", + arg->chan_width, arg->chan_freq, + arg->center_freq0, arg->center_freq1, + arg->chan_bw_interference_bitmap); + break; + default: + ath12k_warn(ab, "Received invalid tag 0x%x for WMI DCS interference in subtlvs\n", + tag); + return -EINVAL; + } + + return 0; +} + +static int ath12k_wmi_dcs_interference_event_parser(struct ath12k_base *ab, + u16 tag, u16 len, + const void *ptr, void *data) +{ + int ret = 0; + + switch (tag) { + case WMI_TAG_DCS_INTERFERENCE_EVENT: + /* Fixed param should already be processed */ + break; + case WMI_TAG_ARRAY_STRUCT: + ret = ath12k_wmi_tlv_iter(ab, ptr, len, + ath12k_wmi_incumbent_signal_interference_subtlv_parser, + data); + break; + default: + ath12k_warn(ab, "Received invalid tag 0x%x for WMI DCS interference event\n", + tag); + ret = -EINVAL; + break; + } + + return ret; +} + +static bool +ath12k_wmi_validate_interference_info(struct ath12k *ar, + struct ath12k_wmi_incumbent_signal_interference_arg *info) +{ + switch (info->chan_width) { + case WMI_CHAN_WIDTH_20: + if (info->chan_bw_interference_bitmap > ATH12K_WMI_DCS_SEG_PRI20) { + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, + "DCS interference event received with wrong chan width bmap 0x%x for 20 MHz", + info->chan_bw_interference_bitmap); + return false; + } + break; + case WMI_CHAN_WIDTH_40: + if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 | + ATH12K_WMI_DCS_SEG_SEC20)) { + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, + "DCS interference event received with wrong chan width bmap 0x%x for 40 MHz", + info->chan_bw_interference_bitmap); + return false; + } + break; + case WMI_CHAN_WIDTH_80: + if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 | + ATH12K_WMI_DCS_SEG_SEC20 | + ATH12K_WMI_DCS_SEG_SEC40)) { + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, + "DCS interference event received with wrong chan width bmap 0x%x for 80 MHz", + info->chan_bw_interference_bitmap); + return false; + } + break; + case WMI_CHAN_WIDTH_160: + if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 | + ATH12K_WMI_DCS_SEG_SEC20 | + ATH12K_WMI_DCS_SEG_SEC40 | + ATH12K_WMI_DCS_SEG_SEC80)) { + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, + "DCS interference event received with wrong chan width bmap 0x%x for 160 MHz", + info->chan_bw_interference_bitmap); + return false; + } + break; + case WMI_CHAN_WIDTH_320: + if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 | + ATH12K_WMI_DCS_SEG_SEC20 | + ATH12K_WMI_DCS_SEG_SEC40 | + ATH12K_WMI_DCS_SEG_SEC80 | + ATH12K_WMI_DCS_SEG_SEC160)) { + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, + "DCS interference event received with wrong chan width bmap 0x%x for 320 MHz", + info->chan_bw_interference_bitmap); + return false; + } + break; + default: + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, + "DCS interference event received with unknown channel width %u", + info->chan_width); + return false; + } + return true; +} + +static u32 +ath12k_wmi_transform_interference_bitmap(int input_bitmap, + struct cfg80211_chan_def *chandef) +{ + u16 output_bits[ATH12K_MAX_20MHZ_SEGMENTS] = {}; + u16 input_bits[ATH12K_MAX_20MHZ_SEGMENTS] = {}; + u32 start_freq, segment_freq; + int primary_index = -1; + u32 output_bitmap = 0; + u16 num_sub_chans; + int bandwidth; + + bandwidth = nl80211_chan_width_to_mhz(chandef->width); + if (bandwidth < 0) + return 0; + + /* + * Firmware reports bit 0 as primary 20 MHz irrespective of absolute + * frequency position. Convert to standardized lowest-to-highest 20 MHz + * ordering expected by cfg80211/mac80211 userspace consumers. + */ + num_sub_chans = bandwidth / 20; + start_freq = (chandef->center_freq1 - bandwidth / 2) + 10; + + for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; i++) { + segment_freq = start_freq + (i * 20); + if (segment_freq == chandef->chan->center_freq) { + primary_index = i; + break; + } + } + if (primary_index == -1) + return 0; + + for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; ++i) + input_bits[i] = BIT(i) & input_bitmap; + + for (int i = 0; i < num_sub_chans; ++i) { + int src = i, dst = i; + + switch (bandwidth) { + case 40: + if (primary_index == 1) + dst = 1 - i; + break; + case 80: + dst = intf_map_80[primary_index][i]; + break; + case 160: + dst = intf_map_160[primary_index][i]; + break; + case 320: + dst = intf_map_320[primary_index][i]; + break; + } + output_bits[dst] = input_bits[src]; + } + + for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; ++i) + output_bitmap |= output_bits[i] ? BIT(i) : 0; + + return output_bitmap; +} + +static void +ath12k_wmi_process_incumbent_signal_interference_evt(struct ath12k_base *ab, + struct sk_buff *skb, + const struct ath12k_wmi_intf_arg *intf_arg) +{ + struct ath12k_wmi_incumbent_signal_interference_arg info = {}; + struct ath12k_incumbent_signal_interference *incumbent; + struct ath12k_mac_get_any_chanctx_conf_arg arg; + u32 transformed_intf_bitmap; + struct ieee80211_hw *hw; + struct ath12k *ar; + int ret; + + guard(rcu)(); + + ar = ath12k_mac_get_ar_by_pdev_id(ab, intf_arg->pdev_id); + if (!ar) { + ath12k_warn(ab, "incumbent signal interference detected on invalid pdev %d\n", + intf_arg->pdev_id); + return; + } + if (!ar->supports_6ghz) { + ath12k_warn(ab, "pdev does not support 6 GHz, dropping DCS interference event\n"); + return; + } + + incumbent = &ar->incumbent_signal_interference; + spin_lock_bh(&ar->data_lock); + if (incumbent->handling_in_progress) { + spin_unlock_bh(&ar->data_lock); + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, + "incumbent signal interference handling ongoing, dropping DCS interference event"); + return; + } + spin_unlock_bh(&ar->data_lock); + + ret = ath12k_wmi_tlv_iter(ab, skb->data, skb->len, + ath12k_wmi_dcs_interference_event_parser, + &info); + if (ret) { + ath12k_warn(ab, + "failed to parse incumbent signal interference TLV. Error %d\n", + ret); + return; + } + + if (!ath12k_wmi_validate_interference_info(ar, &info)) { + ath12k_warn(ab, "invalid DCS incumbent signal interference TLV - Skipping event"); + return; + } + + arg.ar = ar; + arg.chanctx_conf = NULL; + hw = ath12k_ar_to_hw(ar); + ieee80211_iter_chan_contexts_atomic(hw, + ath12k_mac_get_any_chanctx_conf_iter, + &arg); + if (!arg.chanctx_conf) { + ath12k_warn(ab, "failed to find valid chanctx_conf in incumbent signal intf detected event\n"); + return; + } + + if (info.chan_freq != arg.chanctx_conf->def.chan->center_freq) { + ath12k_dbg(ab, ATH12K_DBG_WMI, + "dcs interference event received with wrong channel %d (ctx freq %d)", + info.chan_freq, arg.chanctx_conf->def.chan->center_freq); + return; + } + + spin_lock_bh(&ar->data_lock); + incumbent->center_freq = arg.chanctx_conf->def.chan->center_freq; + incumbent->width = arg.chanctx_conf->def.width; + incumbent->chan_bw_interference_bitmap = info.chan_bw_interference_bitmap; + incumbent->handling_in_progress = true; + spin_unlock_bh(&ar->data_lock); + transformed_intf_bitmap = + ath12k_wmi_transform_interference_bitmap(info.chan_bw_interference_bitmap, + &arg.chanctx_conf->def); + ath12k_dbg(ab, ATH12K_DBG_WMI, + "incumbent signal interference bitmap 0x%x (transformed 0x%x)\n", + info.chan_bw_interference_bitmap, transformed_intf_bitmap); + cfg80211_incumbent_signal_notify(hw->wiphy, + &arg.chanctx_conf->def, + transformed_intf_bitmap, + GFP_ATOMIC); +} + +static void +ath12k_wmi_dcs_interference_event(struct ath12k_base *ab, + struct sk_buff *skb) +{ + const struct ath12k_wmi_dcs_interference_ev_fixed_params *dcs_intf_ev; + struct ath12k_wmi_intf_arg dcs_intf_arg; + const struct wmi_tlv *tlv; + u16 tlv_tag; + u8 *ptr; + + if (skb->len < (sizeof(*dcs_intf_ev) + TLV_HDR_SIZE)) { + ath12k_warn(ab, "DCS interference event is of incorrect length\n"); + return; + } + + ptr = skb->data; + tlv = (struct wmi_tlv *)ptr; + tlv_tag = le32_get_bits(tlv->header, WMI_TLV_TAG); + ptr += sizeof(*tlv); + + if (tlv_tag != WMI_TAG_DCS_INTERFERENCE_EVENT) { + ath12k_warn(ab, "DCS interference event received with wrong tag\n"); + return; + } + + dcs_intf_ev = (struct ath12k_wmi_dcs_interference_ev_fixed_params *)ptr; + + dcs_intf_arg.interference_type = + le32_to_cpu(dcs_intf_ev->interference_type); + dcs_intf_arg.pdev_id = le32_to_cpu(dcs_intf_ev->pdev_id); + + if (dcs_intf_arg.interference_type == + ATH12K_WMI_DCS_INCUMBENT_SIGNAL_INTERFERENCE) { + ath12k_dbg(ab, ATH12K_DBG_WMI, + "incumbent signal interference (Type %u) detected on pdev %u.", + dcs_intf_arg.interference_type, + dcs_intf_arg.pdev_id); + ath12k_wmi_process_incumbent_signal_interference_evt(ab, skb, + &dcs_intf_arg); + } +} + static void ath12k_wmi_process_csa_switch_count_event(struct ath12k_base *ab, const struct ath12k_wmi_pdev_csa_event *ev, @@ -9961,6 +10347,9 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb) case WMI_OBSS_COLOR_COLLISION_DETECTION_EVENTID: ath12k_wmi_obss_color_collision_event(ab, skb); break; + case WMI_DCS_INTERFERENCE_EVENTID: + ath12k_wmi_dcs_interference_event(ab, skb); + break; /* add Unsupported events (rare) here */ case WMI_TBTTOFFSET_EXT_UPDATE_EVENTID: case WMI_PEER_OPER_MODE_CHANGE_EVENTID: diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 65ff41396c08..e3102d7cca98 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2015,7 +2015,7 @@ enum wmi_tlv_tag { WMI_TAG_VDEV_CH_POWER_INFO, WMI_TAG_MLO_LINK_SET_ACTIVE_CMD = 0x3BE, WMI_TAG_EHT_RATE_SET = 0x3C4, - WMI_TAG_DCS_AWGN_INT_TYPE = 0x3C5, + WMI_TAG_DCS_INCUMBENT_SIGNAL_INTERFERENCE_TYPE = 0x3C5, WMI_TAG_MLO_TX_SEND_PARAMS, WMI_TAG_MLO_PARTNER_LINK_PARAMS, WMI_TAG_MLO_PARTNER_LINK_PARAMS_PEER_ASSOC, @@ -4536,6 +4536,62 @@ struct ath12k_wmi_pdev_radar_event { a_sle32 sidx; } __packed; +#define ATH12K_WMI_DCS_INCUMBENT_SIGNAL_INTERFERENCE 0x04 + +struct ath12k_wmi_dcs_interference_ev_fixed_params { + __le32 interference_type; + __le32 pdev_id; +} __packed; + +struct ath12k_wmi_incumbent_signal_interference_params { + __le32 chan_width; + __le32 chan_freq; + __le32 center_freq0; + __le32 center_freq1; + __le32 chan_bw_interference_bitmap; +} __packed; + +struct ath12k_wmi_incumbent_signal_interference_arg { + u32 chan_width; + u32 chan_freq; + u32 center_freq0; + u32 center_freq1; + u32 chan_bw_interference_bitmap; +}; + +struct ath12k_wmi_intf_arg { + u32 interference_type; + u32 pdev_id; +}; + +enum ath12k_wmi_dcs_interference_chan_segment { + /* + * Firmware reports interference bitmap in primary-based order. + * Bit 0 is the primary 20 MHz, bit 1 is the adjacent 20 MHz within + * the primary 40 MHz. Bits 2-3 cover the secondary 40 MHz, bits 4-7 + * cover the secondary 80 MHz, and bits 8-15 cover the secondary 160 MHz. + */ + ATH12K_WMI_DCS_SEG_PRI20 = 0x1, + ATH12K_WMI_DCS_SEG_SEC20 = 0x2, + ATH12K_WMI_DCS_SEG_SEC40_LOW = 0x4, + ATH12K_WMI_DCS_SEG_SEC40_UP = 0x8, + ATH12K_WMI_DCS_SEG_SEC40 = 0xC, + ATH12K_WMI_DCS_SEG_SEC80_LOW = 0x10, + ATH12K_WMI_DCS_SEG_SEC80_LOW_UP = 0x20, + ATH12K_WMI_DCS_SEG_SEC80_UP_LOW = 0x40, + ATH12K_WMI_DCS_SEG_SEC80_UP = 0x80, + ATH12K_WMI_DCS_SEG_SEC80 = 0xF0, + ATH12K_WMI_DCS_SEG_SEC160_LOW = 0x0100, + ATH12K_WMI_DCS_SEG_SEC160_LOW_UP = 0x0200, + ATH12K_WMI_DCS_SEG_SEC160_LOW_UP_UP = 0x0400, + ATH12K_WMI_DCS_SEG_SEC160_LOW_UP_UP_UP = 0x0800, + ATH12K_WMI_DCS_SEG_SEC160_UP_LOW_LOW_LOW = 0x1000, + ATH12K_WMI_DCS_SEG_SEC160_UP_LOW_LOW = 0x2000, + ATH12K_WMI_DCS_SEG_SEC160_UP_LOW = 0x4000, + ATH12K_WMI_DCS_SEG_SEC160_UP = 0x8000, + ATH12K_WMI_DCS_SEG_SEC160 = 0xFF00, +}; + struct wmi_pdev_temperature_event { /* temperature value in Celsius degree */ a_sle32 temp; From afa1bd86eddd9f395bfa3d5cb3c7b5158e1383e0 Mon Sep 17 00:00:00 2001 From: Aishwarya R Date: Mon, 11 May 2026 09:32:42 +0530 Subject: [PATCH 021/143] wifi: ath12k: Add debugfs support to simulate incumbent signal interference Add debugfs support to simulate incumbent signal interference from the host for testing purposes. The debugfs entry is created only for 6 GHz radio when firmware advertises the support through WMI_TLV_SERVICE_DCS_INCUMBENT_SIGNAL_INTERFERENCE_SUPPORT flag. Debugfs command: echo > /sys/kernel/debug/ath12k/pci-000X/macX/simulate_incumbent_signal_interference Each bit in the interference_bitmap represents a 20 MHz segment. Bit 0 corresponds to the primary 20 MHz segment, regardless of its position within the operating bandwidth. Bit 1 represents the next adjacent 20 MHz segment, bit 2 the lower 20 MHz segment of the adjacent 40 MHz segment, and so on-progressing sequentially across the bandwidth.. Example: echo 0xF0 > /sys/kernel/debug/ath12k/pci-0002:01:00.0/mac0/simulate_incumbent_signal_interference This indicates that all the subchannels in the secondary 80 MHz segment were affected. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.5-01651-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aishwarya R Signed-off-by: Amith A Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260511040242.1351792-3-amith.a@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/debugfs.c | 46 +++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/wmi.c | 36 ++++++++++++++++++ drivers/net/wireless/ath/ath12k/wmi.h | 14 +++++++ 3 files changed, 96 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/debugfs.c b/drivers/net/wireless/ath/ath12k/debugfs.c index 8c81a1c22449..d17d4a8f1cb7 100644 --- a/drivers/net/wireless/ath/ath12k/debugfs.c +++ b/drivers/net/wireless/ath/ath12k/debugfs.c @@ -1450,6 +1450,44 @@ static const struct file_operations fops_pdev_stats = { .llseek = default_llseek, }; +static ssize_t +ath12k_write_simulate_incumbent_signal_interference(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath12k *ar = file->private_data; + struct ath12k_hw *ah = ath12k_ar_to_ah(ar); + struct wiphy *wiphy = ath12k_ar_to_hw(ar)->wiphy; + u32 chan_bw_interference_bitmap; + int ret; + + if (ah->state != ATH12K_HW_STATE_ON) + return -ENETDOWN; + + /* + * Bitmap uses the firmware primary-based ordering documented in + * ath12k_wmi_transform_interference_bitmap() & intf_map_80. + */ + if (kstrtou32_from_user(user_buf, count, 0, &chan_bw_interference_bitmap)) + return -EINVAL; + + wiphy_lock(wiphy); + ret = ath12k_wmi_simulate_incumbent_signal_interference(ar, chan_bw_interference_bitmap); + if (ret) + goto exit; + + ret = count; + +exit: + wiphy_unlock(wiphy); + return ret; +} + +static const struct file_operations fops_simulate_incumbent_signal_interference = { + .write = ath12k_write_simulate_incumbent_signal_interference, + .open = simple_open, +}; + static void ath12k_debugfs_fw_stats_register(struct ath12k *ar) { @@ -1515,6 +1553,14 @@ void ath12k_debugfs_register(struct ath12k *ar) ar, &fops_tpc_stats_type); init_completion(&ar->debug.tpc_complete); + if (ar->mac.sbands[NL80211_BAND_6GHZ].channels && + test_bit(WMI_TLV_SERVICE_DCS_INCUMBENT_SIGNAL_INTERFERENCE_SUPPORT, + ar->ab->wmi_ab.svc_map)) { + debugfs_create_file("simulate_incumbent_signal_interference", 0200, + ar->debug.debugfs_pdev, ar, + &fops_simulate_incumbent_signal_interference); + } + ath12k_debugfs_htt_stats_register(ar); ath12k_debugfs_fw_stats_register(ar); diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 8609c612aa2c..25e61cc7e5ac 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -10568,6 +10568,42 @@ int ath12k_wmi_send_tpc_stats_request(struct ath12k *ar, return ret; } +int ath12k_wmi_simulate_incumbent_signal_interference(struct ath12k *ar, + u32 chan_bw_interference_bitmap) +{ + struct wmi_unit_test_arg wmi_ut = {}; + struct ath12k_link_vif *arvif; + struct ath12k_vif *ahvif; + bool arvif_found = false; + + list_for_each_entry(arvif, &ar->arvifs, list) { + ahvif = arvif->ahvif; + if (arvif->is_started && ahvif->vdev_type == WMI_VDEV_TYPE_AP) { + arvif_found = true; + break; + } + } + + if (!arvif_found) + return -EINVAL; + + wmi_ut.args[ATH12K_WMI_INCUMBENT_SIGNAL_TEST_INTF] = + ATH12K_WMI_UNIT_TEST_INCUMBENT_SIGNAL_INTF_TYPE; + wmi_ut.args[ATH12K_WMI_INCUMBENT_SIGNAL_TEST_BITMAP] = + chan_bw_interference_bitmap; + + wmi_ut.vdev_id = arvif->vdev_id; + wmi_ut.module_id = ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_MODULE; + wmi_ut.num_args = ATH12K_WMI_INCUMBENT_SIGNAL_MAX_TEST_ARGS; + wmi_ut.diag_token = ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_TOKEN; + + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, + "Triggering incumbent signal interference simulation, interference bitmap: 0x%x\n", + chan_bw_interference_bitmap); + + return ath12k_wmi_send_unit_test_cmd(ar, &wmi_ut); +} + int ath12k_wmi_connect(struct ath12k_base *ab) { u32 i; diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index e3102d7cca98..14b8dcdf881d 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2270,6 +2270,8 @@ enum wmi_tlv_service { WMI_TLV_SERVICE_REG_CC_EXT_EVENT_SUPPORT = 281, + WMI_TLV_SERVICE_DCS_INCUMBENT_SIGNAL_INTERFERENCE_SUPPORT = 286, + WMI_TLV_SERVICE_11BE = 289, WMI_TLV_SERVICE_WMSK_COMPACTION_RX_TLVS = 361, @@ -4252,6 +4254,16 @@ enum dfs_test_args_idx { DFS_MAX_TEST_ARGS, }; +#define ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_MODULE 0x18 +#define ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_TOKEN 0 +#define ATH12K_WMI_UNIT_TEST_INCUMBENT_SIGNAL_INTF_TYPE 1 + +enum ath12k_wmi_incumbent_signal_test_args_idx { + ATH12K_WMI_INCUMBENT_SIGNAL_TEST_INTF, + ATH12K_WMI_INCUMBENT_SIGNAL_TEST_BITMAP, + ATH12K_WMI_INCUMBENT_SIGNAL_MAX_TEST_ARGS, +}; + /* update if another test command requires more */ #define WMI_UNIT_TEST_ARGS_MAX DFS_MAX_TEST_ARGS @@ -6683,6 +6695,8 @@ int ath12k_wmi_send_vdev_set_tpc_power(struct ath12k *ar, struct ath12k_reg_tpc_power_info *param); int ath12k_wmi_send_mlo_link_set_active_cmd(struct ath12k_base *ab, struct wmi_mlo_link_set_active_arg *param); +int ath12k_wmi_simulate_incumbent_signal_interference(struct ath12k *ar, + u32 chan_bw_interference_bitmap); int ath12k_wmi_alloc(void); void ath12k_wmi_free(void); From 8b7a26b6681922a38cd5a7829ace61f8e54df9b7 Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Mon, 20 Apr 2026 13:01:29 +0200 Subject: [PATCH 022/143] wifi: ath11k: fix warning when unbinding If there is an error during some initialization related to firmware, the buffers dp->tx_ring[i].tx_status are released. However this is released again when the device is unbinded (ath11k_pci), and we get: WARNING: CPU: 0 PID: 6231 at mm/slub.c:4368 free_large_kmalloc+0x57/0x90 Call Trace: free_large_kmalloc ath11k_dp_free ath11k_core_deinit ath11k_pci_remove ... The issue is always reproducible from a VM because the MSI addressing initialization is failing. In order to fix the issue, just set the buffers to NULL after releasing in order to avoid the double free. Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Cc: stable@vger.kernel.org Signed-off-by: Jose Ignacio Tornos Martinez Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260420110130.509670-1-jtornosm@redhat.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/dp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/ath/ath11k/dp.c b/drivers/net/wireless/ath/ath11k/dp.c index bbb86f165141..5a50b623bd07 100644 --- a/drivers/net/wireless/ath/ath11k/dp.c +++ b/drivers/net/wireless/ath/ath11k/dp.c @@ -1040,6 +1040,7 @@ void ath11k_dp_free(struct ath11k_base *ab) idr_destroy(&dp->tx_ring[i].txbuf_idr); spin_unlock_bh(&dp->tx_ring[i].tx_idr_lock); kfree(dp->tx_ring[i].tx_status); + dp->tx_ring[i].tx_status = NULL; } /* Deinit any SOC level resource */ From 30d516006fa1f72f957c18c6171f5680dcdebfb0 Mon Sep 17 00:00:00 2001 From: Kexin Sun Date: Sat, 21 Mar 2026 19:00:11 +0800 Subject: [PATCH 023/143] wifi: ath10k: update outdated comment for renamed ieee80211_tx_status() The function ieee80211_tx_status() was renamed to ieee80211_tx_status_skb() by commit 2703bc851399 ("wifi: mac80211: rename ieee80211_tx_status() to ieee80211_tx_status_skb()"). Update the stale reference in ath10k_htt_tx_hl(). Assisted-by: unnamed:deepseek-v3.2 coccinelle Signed-off-by: Kexin Sun Link: https://patch.msgid.link/20260321110011.8556-1-kexinsun@smail.nju.edu.cn Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/htt_tx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath10k/htt_tx.c b/drivers/net/wireless/ath/ath10k/htt_tx.c index d6f1d85ba871..29e99fbf36fd 100644 --- a/drivers/net/wireless/ath/ath10k/htt_tx.c +++ b/drivers/net/wireless/ath/ath10k/htt_tx.c @@ -1353,7 +1353,7 @@ static int ath10k_htt_tx_hl(struct ath10k_htt *htt, enum ath10k_hw_txrx_mode txm msdu_id = res; } - /* As msdu is freed by mac80211 (in ieee80211_tx_status()) and by + /* As msdu is freed by mac80211 (in ieee80211_tx_status_skb()) and by * ath10k (in ath10k_htt_htc_tx_complete()) we have to increase * reference by one to avoid a use-after-free case and a double * free. From e1fc08598aa34b28359831e768076f56632720c1 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Thu, 12 Feb 2026 20:50:35 +0800 Subject: [PATCH 024/143] wifi: iwlwifi: mld: fix race condition in PTP removal iwl_mld_ptp_remove() calls cancel_delayed_work_sync() only after ptp_clock_unregister() and clearing ptp_data state (ptp_clock, last_gp2, wrap_counter). This creates a race where the delayed work iwl_mld_ptp_work() can execute between ptp_clock_unregister() and cancel_delayed_work_sync(), observing partially cleared PTP state. Move cancel_delayed_work_sync() before ptp_clock_unregister() to ensure the delayed work is fully stopped before any PTP cleanup begins. Cc: stable@vger.kernel.org Reviewed-by: Simon Horman Reviewed-by: Vadim Fedorenko Signed-off-by: Junjie Cao Link: https://patch.msgid.link/20260212125035.1345718-2-junjie.cao@intel.com Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/ptp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c index c65f4b56a327..20ae338e5696 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c @@ -321,10 +321,10 @@ void iwl_mld_ptp_remove(struct iwl_mld *mld) mld->ptp_data.ptp_clock_info.name, ptp_clock_index(mld->ptp_data.ptp_clock)); + cancel_delayed_work_sync(&mld->ptp_data.dwork); ptp_clock_unregister(mld->ptp_data.ptp_clock); mld->ptp_data.ptp_clock = NULL; mld->ptp_data.last_gp2 = 0; mld->ptp_data.wrap_counter = 0; - cancel_delayed_work_sync(&mld->ptp_data.dwork); } } From 65150c9cc3e06ab54bc4e8134a47f6f5d095a4e3 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Thu, 12 Feb 2026 20:50:34 +0800 Subject: [PATCH 025/143] wifi: iwlwifi: mvm: fix race condition in PTP removal iwl_mvm_ptp_remove() calls cancel_delayed_work_sync() only after ptp_clock_unregister() and clearing ptp_data state (ptp_clock, ptp_clock_info, last_gp2). This creates a race where the delayed work iwl_mvm_ptp_work() can execute between ptp_clock_unregister() and cancel_delayed_work_sync(), observing partially cleared PTP state. Move cancel_delayed_work_sync() before ptp_clock_unregister() to ensure the delayed work is fully stopped before any PTP cleanup begins. Cc: stable@vger.kernel.org Reviewed-by: Simon Horman Reviewed-by: Vadim Fedorenko Signed-off-by: Junjie Cao Link: https://patch.msgid.link/20260212125035.1345718-1-junjie.cao@intel.com Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/ptp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c index f7b620136c85..e05e531b09f0 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c @@ -325,11 +325,11 @@ void iwl_mvm_ptp_remove(struct iwl_mvm *mvm) mvm->ptp_data.ptp_clock_info.name, ptp_clock_index(mvm->ptp_data.ptp_clock)); + cancel_delayed_work_sync(&mvm->ptp_data.dwork); ptp_clock_unregister(mvm->ptp_data.ptp_clock); mvm->ptp_data.ptp_clock = NULL; memset(&mvm->ptp_data.ptp_clock_info, 0, sizeof(mvm->ptp_data.ptp_clock_info)); mvm->ptp_data.last_gp2 = 0; - cancel_delayed_work_sync(&mvm->ptp_data.dwork); } } From f54a741c278a5ccd7ff3fc657c8c1cd694448944 Mon Sep 17 00:00:00 2001 From: Jay Ng Date: Tue, 7 Apr 2026 20:42:36 -0700 Subject: [PATCH 026/143] wifi: iwlwifi: remove unused header inclusions Remove header files that are included but provide no symbols, types, or macros used by the including translation unit. In iwl-trans.c, fw/api/tx.h defines TX command structures (iwl_tx_cmd, iwl_tx_resp, TX_CMD_* flags) used by the PCIe TX path, not by the transport core itself. Similarly, iwl-fh.h defines Flow Handler register addresses and DMA-related constants (FH_*, RFH_*, TFD_*) that are consumed by PCIe-specific code, none of which are referenced in iwl-trans.c. In iwl-nvm-parse.c, fw/acpi.h defines ACPI/SAR/GEO/PPAG interfaces (iwl_acpi_*, iwl_sar_*, iwl_geo_*). No references to any of these interfaces exist in this file. Signed-off-by: Jay Ng Link: https://patch.msgid.link/20260408034413.1875915-1-jayng9663@gmail.com Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c | 1 - drivers/net/wireless/intel/iwlwifi/iwl-trans.c | 2 -- 2 files changed, 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 8f3f651451bb..2aa47c5fa7d4 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -18,7 +18,6 @@ #include "iwl-prph.h" #include "iwl-io.h" #include "iwl-csr.h" -#include "fw/acpi.h" #include "fw/api/nvm-reg.h" #include "fw/api/commands.h" #include "fw/api/cmdhdr.h" diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c index 16b2c313e72b..db1db0a42928 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c @@ -8,10 +8,8 @@ #include #include -#include "fw/api/tx.h" #include "iwl-trans.h" #include "iwl-drv.h" -#include "iwl-fh.h" #include #include "fw/api/commands.h" #include "pcie/gen1_2/internal.h" From f056fc2b927448d37eca6b6cacc3d1b0f67b20d2 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Thu, 2 Apr 2026 14:48:07 +0800 Subject: [PATCH 027/143] wifi: iwlwifi: mld: validate sta_mask before ffs() in BA session handlers Three BA session handlers use ffs(ba_data->sta_mask) - 1 to derive a station ID without checking that sta_mask is non-zero. When sta_mask is zero, ffs() returns 0 and the subtraction wraps to 0xFFFFFFFF, causing an out-of-bounds access on fw_id_to_link_sta[]. Add WARN_ON_ONCE(!ba_data->sta_mask) guards before each ffs() call, consistent with the existing check in iwl_mld_ampdu_rx_start(). Reported-by: Yuhao Jiang Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Link: https://patch.msgid.link/SYBPR01MB788115C6CE873271A9A15A25AF51A@SYBPR01MB7881.ausprd01.prod.outlook.com Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/agg.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/agg.c index 3bf36f8f6874..e3627ad0321c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/agg.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.c @@ -64,6 +64,9 @@ static void iwl_mld_release_frames_from_notif(struct iwl_mld *mld, } /* pick any STA ID to find the pointer */ + if (WARN_ON_ONCE(!ba_data->sta_mask)) + goto out_unlock; + sta_id = ffs(ba_data->sta_mask) - 1; link_sta = rcu_dereference(mld->fw_id_to_link_sta[sta_id]); if (WARN_ON_ONCE(IS_ERR_OR_NULL(link_sta) || !link_sta->sta)) @@ -166,6 +169,9 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue, goto out_unlock; /* pick any STA ID to find the pointer */ + if (WARN_ON_ONCE(!ba_data->sta_mask)) + goto out_unlock; + sta_id = ffs(ba_data->sta_mask) - 1; link_sta = rcu_dereference(mld->fw_id_to_link_sta[sta_id]); if (WARN_ON_ONCE(IS_ERR_OR_NULL(link_sta) || !link_sta->sta)) @@ -347,6 +353,9 @@ static void iwl_mld_rx_agg_session_expired(struct timer_list *t) } /* timer expired, pick any STA ID to find the pointer */ + if (WARN_ON_ONCE(!ba_data->sta_mask)) + goto unlock; + sta_id = ffs(ba_data->sta_mask) - 1; link_sta = rcu_dereference(ba_data->mld->fw_id_to_link_sta[sta_id]); From df087ac00d278f03d75d053fe78d38a83ffd2814 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Sun, 10 May 2026 23:48:26 +0300 Subject: [PATCH 028/143] wifi: iwlwifi: mld: set NAN phy capabilities 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 Link: https://patch.msgid.link/20260510234534.3a5e5251dbaf.I566e1749cd17baa672bdd43712114e434f810a69@changeid Signed-off-by: Miri Korenblit --- .../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 @@ -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 */ From d715e737ba7100f629a457d9cbdd60b037b31aca Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 10 May 2026 23:48:27 +0300 Subject: [PATCH 029/143] wifi: iwlwifi: mld: tlc: separate from link STA 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 Link: https://patch.msgid.link/20260510234534.2c6589aab9db.I01729ec8be4687884831fc8761bf3fd8bba83056@changeid Signed-off-by: Miri Korenblit --- 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 @@ -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(!link_conf->chanreq.oper.chan)) + if (WARN_ON_ONCE(!mld_link)) return; + 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, From ac25c511108c3068b170244f8d833bcc1f721bc2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 10 May 2026 23:48:28 +0300 Subject: [PATCH 030/143] wifi: iwlwifi: mld: disable queue hang detection for NAN data 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 Link: https://patch.msgid.link/20260510234534.9886987db700.Ifd879478bc30af25de0eada6143dbc3e6a548068@changeid Signed-off-by: Miri Korenblit --- 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 @@ -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) From 6e373b916a60848508dbbc1af79ba7ca5b893373 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 10 May 2026 23:48:29 +0300 Subject: [PATCH 031/143] wifi: iwlwifi: mld: support NAN and NAN_DATA interfaces 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 Co-developed-by: Miri Korenblit Link: https://patch.msgid.link/20260510234534.dd12944c140b.I3578198660a533faf9f6a94432ef2114f4a9dfae@changeid Signed-off-by: Miri Korenblit --- .../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 @@ -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; + } + + if (!iwl_mld_vif_fw_id_valid(mld_vif)) 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))) - return; - 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__ */ From ef9d9c1c1609c425d78bba8457fc710e0f66eedd Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 10 May 2026 23:48:30 +0300 Subject: [PATCH 032/143] wifi: iwlwifi: mld: add NAN link management 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 Co-developed-by: Miri Korenblit Link: https://patch.msgid.link/20260510234534.39ee3533ef30.I81ff6dc02e912396040f922e21888fd94b3c26e6@changeid Signed-off-by: Miri Korenblit --- .../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 #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 #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 #include +/** + * 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) From cfc84d1710f331e47e09f337b07e0207a5ae9a14 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 10 May 2026 23:48:31 +0300 Subject: [PATCH 033/143] wifi: iwlwifi: add NAN schedule command support Add the NAN schedule command API definition and implementation of the schedule updates. Co-developed-by: Miri Korenblit Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260510234534.95fabd44a598.I7cbe877f3b13a44554d95e56b10d930dde4704c9@changeid Signed-off-by: Miri Korenblit --- .../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 #include @@ -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); } } From 03779b6966f444e9ccbb9c4abe1e6bb6769d099d Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 10 May 2026 23:48:32 +0300 Subject: [PATCH 034/143] wifi: iwlwifi: mld: implement NAN peer station management Implement peer station management for NAN, i.e. support for adding, removing, and updating NMI and NDI stations. Signed-off-by: Johannes Berg Co-developed-by: Miri Korenblit Link: https://patch.msgid.link/20260510234534.cbf6bac4744f.I3bd01266e47f24fb7f1240db62ac3bd47c479127@changeid Signed-off-by: Miri Korenblit --- .../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 | 98 +++++++++++++++---- drivers/net/wireless/intel/iwlwifi/mld/sta.h | 4 +- 4 files changed, 110 insertions(+), 29 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 @@ -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); - - mld_link = iwl_mld_link_from_mac80211(link); - - if (WARN_ON(!link || !mld_link) || fw_id < 0) + 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; + + is_6ghz = false; + uora_exists = false; + + 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); From ddb509acccb70dd3a60f6353db95e52296f06c63 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 10 May 2026 23:48:33 +0300 Subject: [PATCH 035/143] wifi: iwlwifi: mld: add peer schedule support Add support for NAN peer schedule configuration and update. Signed-off-by: Johannes Berg Co-developed-by: Miri Korenblit Link: https://patch.msgid.link/20260510234534.ede42401c3ee.I8e483edd1e917dfa59901b520db595cea28906e3@changeid Signed-off-by: Miri Korenblit --- .../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__ */ From 09e2f2ea5e3576ad0ad36155e24efa7f48051e64 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Sun, 10 May 2026 23:48:34 +0300 Subject: [PATCH 036/143] wifi: iwlwifi: mld: use host rate for NAN management frames 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 Link: https://patch.msgid.link/20260510234534.fb7272ddbb45.I843dba36f52363bc7e9c81f937547ead12147539@changeid Signed-off-by: Miri Korenblit --- 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, From 0359d3fa3ccb2f48dfd7ba54c18e1378254a3054 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Sun, 10 May 2026 23:48:35 +0300 Subject: [PATCH 037/143] wifi: iwlwifi: mld: extract NAN capabilities setting to a function There are now quite a lot of capabilities to set, so move it to a dedicated function, for better clarity. Link: https://patch.msgid.link/20260510234534.da1f380ced3b.I02013e9d5946a1b3372d2fcd570057fdb4416b65@changeid Signed-off-by: Miri Korenblit --- .../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; From 356f2646dfc2ccc0cb7456ab84ead89a52b4add2 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Sun, 10 May 2026 23:48:36 +0300 Subject: [PATCH 038/143] wifi: iwlwifi: mld: Fix number of antennas in NAN capabilities Instead of hardcoding the number of supported antennas for Tx/Rx, set them according to hardware capabilities. Signed-off-by: Ilan Peer Link: https://patch.msgid.link/20260510234534.26f8653d44a3.Ib8b9769e7a598b01f6285bd0782aa3bf13f144a2@changeid Signed-off-by: Miri Korenblit --- 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; From 175d51f1d283765a5aed9f9a625961a93bd48e69 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Sun, 10 May 2026 23:48:37 +0300 Subject: [PATCH 039/143] wifi: iwlwifi: mld: Do not declare support for NDPE 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 Link: https://patch.msgid.link/20260510234534.a76eb7235116.I160f1232e51711d5e2c063f0e1539ef71db50e1e@changeid Signed-off-by: Miri Korenblit --- 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; From db2770feda66029ed8601ead6b888954f232c7f1 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Sun, 10 May 2026 23:48:38 +0300 Subject: [PATCH 040/143] wifi: iwlwifi: mld: Do not declare NAN support for Extended Key ID 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 Link: https://patch.msgid.link/20260510234534.42f0e92d0ee1.Ic3e47a926539a1d8f20103c40ebe5cfeaf671a96@changeid Signed-off-by: Miri Korenblit --- 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; From 28db4b2625174a47b3f6b79d996b0804aaae8ab2 Mon Sep 17 00:00:00 2001 From: Israel Kozitz Date: Sun, 10 May 2026 23:48:39 +0300 Subject: [PATCH 041/143] wifi: iwlwifi: mld: fix NAN max channel switch time unit 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 Reviewed-by: Ilan Peer Link: https://patch.msgid.link/20260510234534.cb3c05e5d334.I89d9f336aaf388c6e48769de5fe1e5db19295057@changeid Signed-off-by: Miri Korenblit --- 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; From 8a9c1cdfac1ce0cd9a324fc202624d24ac3551a3 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Sun, 10 May 2026 23:48:40 +0300 Subject: [PATCH 042/143] wifi: iwlwifi: mld: don't allow softAP with NAN SoftAP in concurrency with NAN is not supported. Update the interface combinations accordingly. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260510234534.fafa2298ffc5.I3cd256f3075d6e93b1fd9afc905af0a6a633ed4b@changeid Signed-off-by: Miri Korenblit --- .../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); From b4dfc8f8ce29713e276ff7cbacb3bcb98c754a8b Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Mon, 11 May 2026 20:36:17 +0300 Subject: [PATCH 043/143] wifi: iwlwifi: add RF name handling for PE chip type for debugfs Implement RF name handling for PE chip type in debugfs, including special case handling to show PETC when the silicon is in Z step. Signed-off-by: Pagadala Yesu Anjaneyulu Link: https://patch.msgid.link/20260511203428.f49fbbe61925.I7e001558e66eb2c6c5081be7c21defe6a81aa265@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/iwl-csr.h | 3 ++- .../net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-csr.h b/drivers/net/wireless/intel/iwlwifi/iwl-csr.h index f3fa37fee2e4..d2fa80a3dd04 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-csr.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-csr.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2005-2014, 2018-2025 Intel Corporation + * Copyright (C) 2005-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2016 Intel Deutschland GmbH */ @@ -366,6 +366,7 @@ enum { #define CSR_HW_RF_ID_TYPE_GF4 (0x0010E000) #define CSR_HW_RF_ID_TYPE_FM (0x00112000) #define CSR_HW_RF_ID_TYPE_WP (0x00113000) +#define CSR_HW_RF_ID_TYPE_PE (0x00114000) /* HW_RF CHIP STEP */ #define CSR_HW_RF_STEP(_val) (((_val) >> 8) & 0xF) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c index a50e845cea42..87e5f79ccb3c 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include "iwl-trans.h" #include "iwl-prph.h" @@ -327,6 +327,13 @@ static void iwl_pcie_get_rf_name(struct iwl_trans *trans) else pos = scnprintf(buf, buflen, "WH"); break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_PE): + if (SILICON_Z_STEP == + CSR_HW_RFID_STEP(trans->info.hw_rf_id)) + pos = scnprintf(buf, buflen, "PETC"); + else + pos = scnprintf(buf, buflen, "PE"); + break; default: return; } From 605f563c585b09cdc5771cf16486272e1894be68 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Mon, 11 May 2026 20:36:18 +0300 Subject: [PATCH 044/143] wifi: iwlwifi: bump core version for BZ/SC/DR to 103 Start supporting Core 103 FW on these devices. Link: https://patch.msgid.link/20260511203428.663b92bb5637.Iae934894dcdfc2e75e305584c57bcb8642804614@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/cfg/bz.c | 2 +- drivers/net/wireless/intel/iwlwifi/cfg/dr.c | 2 +- drivers/net/wireless/intel/iwlwifi/cfg/sc.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index 3653ddbf3ce9..8b3f15899396 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -10,7 +10,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_BZ_UCODE_CORE_MAX 102 +#define IWL_BZ_UCODE_CORE_MAX 103 /* Lowest firmware API version supported */ #define IWL_BZ_UCODE_API_MIN 100 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c index 83d893b10f8e..a8f2ac688d31 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c @@ -9,7 +9,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_DR_UCODE_CORE_MAX 102 +#define IWL_DR_UCODE_CORE_MAX 103 /* Lowest firmware API version supported */ #define IWL_DR_UCODE_API_MIN 100 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index 749d46dc0236..8e6efde3c64b 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -10,7 +10,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_SC_UCODE_CORE_MAX 102 +#define IWL_SC_UCODE_CORE_MAX 103 /* Lowest firmware API version supported */ #define IWL_SC_UCODE_API_MIN 100 From c73869c1df2a27dc3c928e94b42660124318f727 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Mon, 11 May 2026 20:36:19 +0300 Subject: [PATCH 045/143] wifi: iwlwifi: fix the access to CNVR TOP registers For Dr and Sc, we need to wait until the TOP is ready to gets its registers read by the host. Signed-off-by: Emmanuel Grumbach Reviewed-by: Daniel Gabay Link: https://patch.msgid.link/20260511203428.09778340db51.I28d678ad404bbf8068f139666d38e29be5aa063b@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/iwl-io.c | 18 ++++++++++++- drivers/net/wireless/intel/iwlwifi/iwl-io.h | 4 ++- drivers/net/wireless/intel/iwlwifi/iwl-prph.h | 7 ++++- .../intel/iwlwifi/pcie/gen1_2/trans.c | 26 ++++++++++++++++++- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-io.c b/drivers/net/wireless/intel/iwlwifi/iwl-io.c index b1944584c693..c4ccfffdf6af 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-io.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-io.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2003-2014, 2018-2022, 2024-2025 Intel Corporation + * Copyright (C) 2003-2014, 2018-2022, 2024-2026 Intel Corporation * Copyright (C) 2015-2016 Intel Deutschland GmbH */ #include @@ -168,6 +168,22 @@ int iwl_poll_prph_bit(struct iwl_trans *trans, u32 addr, return -ETIMEDOUT; } +int iwl_poll_umac_prph_bits_no_grab(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout) +{ + int t = 0; + + do { + if ((iwl_read_umac_prph_no_grab(trans, addr) & mask) == + (bits & mask)) + return 0; + udelay(IWL_POLL_INTERVAL); + t += IWL_POLL_INTERVAL; + } while (t < timeout); + + return -ETIMEDOUT; +} + void iwl_set_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask) { if (iwl_trans_grab_nic_access(trans)) { diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-io.h b/drivers/net/wireless/intel/iwlwifi/iwl-io.h index 5bcec239ffc4..d920a32fc173 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-io.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-io.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2018-2021, 2025 Intel Corporation + * Copyright (C) 2018-2021, 2025-2026 Intel Corporation */ #ifndef __iwl_io_h__ #define __iwl_io_h__ @@ -51,6 +51,8 @@ static inline void iwl_write_prph(struct iwl_trans *trans, u32 ofs, u32 val) int iwl_poll_prph_bit(struct iwl_trans *trans, u32 addr, u32 bits, u32 mask, int timeout); +int iwl_poll_umac_prph_bits_no_grab(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout); void iwl_set_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask); void iwl_set_bits_mask_prph(struct iwl_trans *trans, u32 ofs, u32 bits, u32 mask); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-prph.h b/drivers/net/wireless/intel/iwlwifi/iwl-prph.h index a7214ddcfaf5..6ca1f51b69a1 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-prph.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-prph.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2005-2014, 2018-2025 Intel Corporation + * Copyright (C) 2005-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016 Intel Deutschland GmbH */ @@ -411,6 +411,11 @@ enum { #define HPM_SECONDARY_DEVICE_STATE 0xa03404 #define WFPM_MAC_OTP_CFG7_ADDR 0xa03338 #define WFPM_MAC_OTP_CFG7_DATA 0xa0333c +#define WFPM_RSRCS_4PHS_REQ_STTS 0xa033f8 +#define WFPM_RSRCS_4PHS_ACK_STTS 0xa033fc + +#define RSRC_REQ_CNVR_TOP BIT(6) +#define RSRC_ACK_CNVR_TOP BIT(6) /* For UMAG_GEN_HW_STATUS reg check */ diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c index a05f60f9224b..377b2e30b540 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2007-2015, 2018-2024 Intel Corporation + * Copyright (C) 2007-2015, 2018-2024, 2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -4000,6 +4000,30 @@ static void get_crf_id(struct iwl_trans *iwl_trans, else sd_reg_ver_addr = SD_REG_VER; + /* wait until the device is ready to access the prph registers */ + if (iwl_trans->mac_cfg->device_family == IWL_DEVICE_FAMILY_DR || + iwl_trans->mac_cfg->device_family == IWL_DEVICE_FAMILY_SC) { + u32 req = iwl_read_umac_prph_no_grab(iwl_trans, + WFPM_RSRCS_4PHS_REQ_STTS); + int ret; + + if (!(req & RSRC_REQ_CNVR_TOP)) { + IWL_ERR(iwl_trans, + "WFPM_RSRCS_4PHS_REQ_STTS bit 6 is clear 0x%x\n", + req); + return; + } + + ret = iwl_poll_umac_prph_bits_no_grab(iwl_trans, + WFPM_RSRCS_4PHS_ACK_STTS, + RSRC_ACK_CNVR_TOP, + RSRC_ACK_CNVR_TOP, + 50 * 1000); + if (ret < 0) + IWL_ERR(iwl_trans, + "WFPM_RSRCS_4PHS_ACK_STTS bit 6 is clear\n"); + } + /* Enable access to peripheral registers */ val = iwl_read_umac_prph_no_grab(iwl_trans, WFPM_CTRL_REG); val |= WFPM_AUX_CTL_AUX_IF_MAC_OWNER_MSK; From a342c99cb70dde344723f6c802efd24e614f4fda Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Mon, 11 May 2026 20:36:20 +0300 Subject: [PATCH 046/143] wifi: iwlwifi: mld: honor BSS_CHANGED_BEACON_ENABLED We should stick to mac80211's flow to start / stop beaconing. This allows to stop beaconing before we remove the BIGTK. Note that the start and stop beaconing flows are not exactly symmetric. When we start beaconing, we just update the beacon template. We assume that mac80211 won't update the beacons, if we're not supposed to be sending it. Also note that we now send the beacon template after the broadcast station was added to the firmware: the broadcast station is added in the start_ap() flow, while the beacon template is now added in the link_changed() flow which happens later. This is not what we did before this patch, but this sequence is supported by the firmware as well. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260511203428.cf10e5754171.I8022517c6c5aedb4b56fba30a5545de8f62dddbe@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/ap.c | 25 +++++++++++++++---- drivers/net/wireless/intel/iwlwifi/mld/ap.h | 6 ++++- .../net/wireless/intel/iwlwifi/mld/mac80211.c | 8 +++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ap.c b/drivers/net/wireless/intel/iwlwifi/mld/ap.c index 5c59acc8c4c5..c29e4a77be05 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ap.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ap.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2024 Intel Corporation + * Copyright (C) 2024, 2026 Intel Corporation */ #include @@ -239,6 +239,25 @@ int iwl_mld_store_ap_early_key(struct iwl_mld *mld, return -ENOSPC; } +void iwl_mld_stop_beacon(struct iwl_mld *mld, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link) +{ + struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); + struct iwl_mac_beacon_cmd cmd = {}; + int cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, BEACON_TEMPLATE_CMD, 14); + + if (WARN_ON(!mld_link)) + return; + + if (cmd_ver < 15) + return; + + /* leave byte_cnt 0 */ + cmd.link_id = cpu_to_le32(mld_link->fw_id); + + iwl_mld_send_cmd_pdu(mld, BEACON_TEMPLATE_CMD, &cmd); +} + static int iwl_mld_send_ap_early_keys(struct iwl_mld *mld, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link) @@ -276,10 +295,6 @@ int iwl_mld_start_ap_ibss(struct ieee80211_hw *hw, if (vif->type == NL80211_IFTYPE_AP) iwl_mld_send_ap_tx_power_constraint_cmd(mld, vif, link); - ret = iwl_mld_update_beacon_template(mld, vif, link); - if (ret) - return ret; - /* the link should be already activated when assigning chan context, * and LINK_CONTEXT_MODIFY_EHT_PARAMS is deprecated */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ap.h b/drivers/net/wireless/intel/iwlwifi/mld/ap.h index 4a6f52b9552d..a3b6bed814ad 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ap.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/ap.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2024 Intel Corporation + * Copyright (C) 2024, 2026 Intel Corporation */ #ifndef __iwl_ap_h__ #define __iwl_ap_h__ @@ -14,6 +14,10 @@ int iwl_mld_update_beacon_template(struct iwl_mld *mld, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf); +void iwl_mld_stop_beacon(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf); + int iwl_mld_start_ap_ibss(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 1106ad651cfe..403bc38ac2bc 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -1258,8 +1258,14 @@ iwl_mld_link_info_changed_ap_ibss(struct iwl_mld *mld, if (link_changes) iwl_mld_change_link_in_fw(mld, link, link_changes); - if (changes & BSS_CHANGED_BEACON) + if (changes & BSS_CHANGED_BEACON) { + WARN_ON(!link->enable_beacon); iwl_mld_update_beacon_template(mld, vif, link); + } + + /* Enabling beacons was already covered above */ + if ((changes & BSS_CHANGED_BEACON_ENABLED) && !link->enable_beacon) + iwl_mld_stop_beacon(mld, vif, link); } static From 848ae606eb5e8fa3908a7483f5c4e05ef491443c Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Mon, 11 May 2026 20:36:21 +0300 Subject: [PATCH 047/143] wifi: iwlwifi: mld: move iwl_mld_link_info_changed_ap_ibss to ap.c This function is ap mode related, move it to ap.c. Also, don't call iwl_mld_ftm_responder_clear from stop_ap() since mac80211 does it now before stopping the AP. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260511203428.83ea430fbd74.I5ac85373c250b684cb46978d9e6bd42ba0e88171@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/ap.c | 37 ++++++++++++++++++- drivers/net/wireless/intel/iwlwifi/mld/ap.h | 8 ++-- .../net/wireless/intel/iwlwifi/mld/mac80211.c | 33 ----------------- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ap.c b/drivers/net/wireless/intel/iwlwifi/mld/ap.c index c29e4a77be05..bc426b911ce5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ap.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ap.c @@ -239,8 +239,8 @@ int iwl_mld_store_ap_early_key(struct iwl_mld *mld, return -ENOSPC; } -void iwl_mld_stop_beacon(struct iwl_mld *mld, struct ieee80211_vif *vif, - struct ieee80211_bss_conf *link) +static void iwl_mld_stop_beacon(struct iwl_mld *mld, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link) { struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); struct iwl_mac_beacon_cmd cmd = {}; @@ -258,6 +258,39 @@ void iwl_mld_stop_beacon(struct iwl_mld *mld, struct ieee80211_vif *vif, iwl_mld_send_cmd_pdu(mld, BEACON_TEMPLATE_CMD, &cmd); } +void +iwl_mld_link_info_changed_ap_ibss(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link, + u64 changes) +{ + u32 link_changes = 0; + + if (changes & BSS_CHANGED_ERP_SLOT) + link_changes |= LINK_CONTEXT_MODIFY_RATES_INFO; + + if (changes & (BSS_CHANGED_ERP_CTS_PROT | BSS_CHANGED_HT)) + link_changes |= LINK_CONTEXT_MODIFY_PROTECT_FLAGS; + + if (changes & (BSS_CHANGED_QOS | BSS_CHANGED_BANDWIDTH)) + link_changes |= LINK_CONTEXT_MODIFY_QOS_PARAMS; + + if (changes & BSS_CHANGED_HE_BSS_COLOR) + link_changes |= LINK_CONTEXT_MODIFY_HE_PARAMS; + + if (link_changes) + iwl_mld_change_link_in_fw(mld, link, link_changes); + + if (changes & BSS_CHANGED_BEACON) { + WARN_ON(!link->enable_beacon); + iwl_mld_update_beacon_template(mld, vif, link); + } + + /* Enabling beacons was already covered above */ + if ((changes & BSS_CHANGED_BEACON_ENABLED) && !link->enable_beacon) + iwl_mld_stop_beacon(mld, vif, link); +} + static int iwl_mld_send_ap_early_keys(struct iwl_mld *mld, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ap.h b/drivers/net/wireless/intel/iwlwifi/mld/ap.h index a3b6bed814ad..f10e9c9a38ff 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ap.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/ap.h @@ -14,9 +14,11 @@ int iwl_mld_update_beacon_template(struct iwl_mld *mld, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf); -void iwl_mld_stop_beacon(struct iwl_mld *mld, - struct ieee80211_vif *vif, - struct ieee80211_bss_conf *link_conf); +void +iwl_mld_link_info_changed_ap_ibss(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link, + u64 changes); int iwl_mld_start_ap_ibss(struct ieee80211_hw *hw, 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 403bc38ac2bc..c02994e63b14 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -1235,39 +1235,6 @@ int iwl_mld_mac80211_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, return 0; } -static void -iwl_mld_link_info_changed_ap_ibss(struct iwl_mld *mld, - struct ieee80211_vif *vif, - struct ieee80211_bss_conf *link, - u64 changes) -{ - u32 link_changes = 0; - - if (changes & BSS_CHANGED_ERP_SLOT) - link_changes |= LINK_CONTEXT_MODIFY_RATES_INFO; - - if (changes & (BSS_CHANGED_ERP_CTS_PROT | BSS_CHANGED_HT)) - link_changes |= LINK_CONTEXT_MODIFY_PROTECT_FLAGS; - - if (changes & (BSS_CHANGED_QOS | BSS_CHANGED_BANDWIDTH)) - link_changes |= LINK_CONTEXT_MODIFY_QOS_PARAMS; - - if (changes & BSS_CHANGED_HE_BSS_COLOR) - link_changes |= LINK_CONTEXT_MODIFY_HE_PARAMS; - - if (link_changes) - iwl_mld_change_link_in_fw(mld, link, link_changes); - - if (changes & BSS_CHANGED_BEACON) { - WARN_ON(!link->enable_beacon); - iwl_mld_update_beacon_template(mld, vif, link); - } - - /* Enabling beacons was already covered above */ - if ((changes & BSS_CHANGED_BEACON_ENABLED) && !link->enable_beacon) - iwl_mld_stop_beacon(mld, vif, link); -} - static u32 iwl_mld_link_changed_mapping(struct iwl_mld *mld, struct ieee80211_vif *vif, From 71027eac55feba2fbda081abb575cb746832fe31 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Mon, 11 May 2026 20:36:22 +0300 Subject: [PATCH 048/143] wifi: iwlwifi: add XIAOMI to PPAG approved list Add XIAOMI to the list of the OEMs that are allowed to use the PPAG feature Signed-off-by: Pagadala Yesu Anjaneyulu Link: https://patch.msgid.link/20260511203428.a6f01de83902.I9d5b5122d71ba872974f9e506e033dcb457d80a2@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/regulatory.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c index 55128caac7ed..8d9ff36e30f5 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2023, 2025 Intel Corporation + * Copyright (C) 2023, 2025-2026 Intel Corporation */ #include #include "iwl-drv.h" @@ -112,6 +112,11 @@ static const struct dmi_system_id dmi_ppag_approved_list[] = { DMI_MATCH(DMI_SYS_VENDOR, "WIKO"), }, }, + { .ident = "XIAOMI", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "XIAOMI"), + }, + }, {} }; From 2b7a3019b935d6070a06cb5d16d2e6b8f3342907 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 11 May 2026 20:36:23 +0300 Subject: [PATCH 049/143] wifi: iwlwifi: mld: clean up station handling in key APIs The internal key APIs, when called with group keys where mac80211 doesn't pass the (AP) station pointer, are still sometimes called with the AP station pointer on internal calls. This is confusing. Clean it up and always call them with the AP STA when it exists, even when coming in from mac80211, by looking it up immediately. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260511203428.0efcbac3e96e.I5b78af51df283d452fc4f1366b04b4769f60f24e@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/key.c | 12 ++---------- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 11 +++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/key.c b/drivers/net/wireless/intel/iwlwifi/mld/key.c index 04192c5f07ff..2cb312ebd7a7 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/key.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/key.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2024 Intel Corporation + * Copyright (C) 2024, 2026 Intel Corporation */ #include "key.h" #include "iface.h" @@ -12,7 +12,6 @@ static u32 iwl_mld_get_key_flags(struct iwl_mld *mld, struct ieee80211_sta *sta, struct ieee80211_key_conf *key) { - struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); bool pairwise = key->flags & IEEE80211_KEY_FLAG_PAIRWISE; bool igtk = key->keyidx == 4 || key->keyidx == 5; u32 flags = 0; @@ -38,9 +37,6 @@ static u32 iwl_mld_get_key_flags(struct iwl_mld *mld, break; } - if (!sta && vif->type == NL80211_IFTYPE_STATION) - sta = mld_vif->ap_sta; - /* If we are installing an iGTK (in AP or STA mode), we need to tell * the firmware this key will en/decrypt MGMT frames. * Same goes if we are installing a pairwise key for an MFP station. @@ -92,11 +88,7 @@ static u32 iwl_mld_get_key_sta_mask(struct iwl_mld *mld, return BIT(link->mcast_sta.sta_id); } - /* for client mode use the AP STA also for group keys */ - if (!sta && vif->type == NL80211_IFTYPE_STATION) - sta = mld_vif->ap_sta; - - /* STA should be non-NULL now */ + /* STA should be non-NULL */ if (WARN_ON(!sta)) return 0; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index c02994e63b14..4b5f50ff2b05 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -2282,9 +2282,20 @@ static int iwl_mld_mac80211_set_key(struct ieee80211_hw *hw, struct ieee80211_sta *sta, struct ieee80211_key_conf *key) { + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); int ret; + /* + * FW always needs the AP STA for client mode. + * Note that during removal this could already + * be NULL (mac80211 removes keys after STAs) + * but then we'll already have removed the key + * and set hw_key_idx = STA_KEY_IDX_INVALID. + */ + if (!sta && vif->type == NL80211_IFTYPE_STATION) + sta = mld_vif->ap_sta; + switch (cmd) { case SET_KEY: ret = iwl_mld_set_key_add(mld, vif, sta, key); From bc2f28ead224b422e11a062808a6b5e924f342b8 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 11 May 2026 20:36:24 +0300 Subject: [PATCH 050/143] wifi: iwlwifi: mld: add TLC support for NAN stations In order to support NAN, TLC now has a station bitmap. Use this and update TLC for the NAN stations accordingly whenever links (and thus PHYs) change, and whenever else mac80211 might update the rate scale information. Signed-off-by: Johannes Berg Co-developed-by: Miri Korenblit Link: https://patch.msgid.link/20260511203428.ea4112d28331.Ie11049c12a4765a97f3e6dfd1ea4afb265c93817@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 29 ++- drivers/net/wireless/intel/iwlwifi/mld/tlc.c | 189 +++++++++++++++---- 2 files changed, 182 insertions(+), 36 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index ceea66c01205..59e85818b613 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -7,6 +7,7 @@ #include "iface.h" #include "link.h" #include "mlo.h" +#include "tlc.h" #include "fw/api/mac-cfg.h" #include "fw/api/mac.h" #include "fw/api/rs.h" @@ -437,15 +438,26 @@ iwl_mld_nan_link_add(struct iwl_mld *mld, } static int iwl_mld_nan_link_set_active(struct iwl_mld *mld, + struct ieee80211_vif *vif, struct iwl_mld_nan_link *nan_link, bool active) { struct iwl_link_config_cmd cmd; + struct ieee80211_sta *sta; int ret; if (nan_link->active == active) return 0; + if (active) { + 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_NDI) + iwl_mld_config_tlc(mld, mld_sta->vif, sta); + } + } + nan_link->active = active; iwl_mld_nan_link_prep_cmd(mld, nan_link, &cmd, @@ -457,8 +469,10 @@ static int iwl_mld_nan_link_set_active(struct iwl_mld *mld, return ret; } - if (!active) + if (!active) { nan_link->chanctx = NULL; + /* TODO: when FW is ready, Update phy in TLC to invalid after */ + } return 0; } @@ -590,7 +604,7 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, if (links[i]) { added_links = true; link_used[links[i]->fw_id] = true; - iwl_mld_nan_link_set_active(mld, links[i], true); + iwl_mld_nan_link_set_active(mld, vif, links[i], true); } } @@ -630,7 +644,7 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, /* 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_set_active(mld, vif, nan_link, false); remove_link_ids |= BIT(nan_link->fw_id); /* mark unused for STA updates */ nan_link->fw_id = FW_CTXT_ID_INVALID; @@ -694,6 +708,15 @@ int iwl_mld_mac802111_nan_peer_sched_changed(struct ieee80211_hw *hw, .len[1] = sched->ulw_size, .dataflags[1] = IWL_HCMD_DFL_DUP, }; + struct ieee80211_sta *iter; + + /* Update TLC in case peer channels were added/removed/updated */ + for_each_station(iter, mld->hw) { + struct iwl_mld_sta *tmp = iwl_mld_sta_from_mac80211(iter); + + if (tmp->sta_type == STATION_TYPE_NAN_PEER_NDI) + iwl_mld_config_tlc(mld, tmp->vif, iter); + } for (int i = 0; i < ARRAY_SIZE(sched->maps); i++) { if (sched->maps[i].map_id == CFG80211_NAN_INVALID_MAP_ID) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c index b1722982db9c..7db56eefc7df 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c @@ -529,7 +529,7 @@ 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 iwl_mld_sta *mld_sta, - int fw_sta_id, int phy_id, + int fw_sta_mask, int phy_id, struct iwl_mld_tlc_sta_capa *capa) { struct iwl_tlc_config_cmd cmd = { @@ -550,10 +550,7 @@ static void iwl_mld_send_tlc_cmd(struct iwl_mld *mld, u8 cmd_size; int ret; - if (fw_sta_id < 0) - return; - - cmd.sta_mask = cpu_to_le32(BIT(fw_sta_id)); + cmd.sta_mask = cpu_to_le32(fw_sta_mask); cmd.phy_id = cpu_to_le32(phy_id); iwl_mld_fill_supp_rates(mld, vif, capa, &cmd); @@ -617,25 +614,27 @@ int iwl_mld_send_tlc_dhc(struct iwl_mld *mld, u8 sta_id, u32 type, u32 data) return ret; } -void iwl_mld_config_tlc_link(struct iwl_mld *mld, - struct ieee80211_vif *vif, - struct ieee80211_bss_conf *link_conf, - struct ieee80211_link_sta *link_sta) +static void _iwl_mld_config_tlc_link(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *chan_ctx, + u32 fw_sta_mask, + struct ieee80211_link_sta *link_sta, + 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_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; + int i; - if (WARN_ON_ONCE(!mld_link)) - return; + /* + * Note: Due to NAN, the chan_ctx here might not be the same as the + * vif->links[link_sta->link_id] one (which is NULL in NAN), so some + * care is needed in this function (and the 'capa' exists to make it + * less error-prone, the other functions need not worry about it.) + */ - chan_ctx = rcu_dereference_wiphy(mld->wiphy, mld_link->chan_ctx); - if (WARN_ON_ONCE(!chan_ctx)) + if (WARN_ON(!chan_ctx)) return; band = chan_ctx->def.chan->band; @@ -653,25 +652,18 @@ void iwl_mld_config_tlc_link(struct iwl_mld *mld, /* 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->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); + 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; - 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_send_tlc_cmd(mld, vif, mld_sta, fw_sta_mask, iwl_mld_phy_from_mac80211(chan_ctx)->fw_id, - &capa); + capa); } void iwl_mld_tlc_update_phy(struct iwl_mld *mld, struct ieee80211_vif *vif, @@ -717,6 +709,136 @@ void iwl_mld_tlc_update_phy(struct iwl_mld *mld, struct ieee80211_vif *vif, } } +static void iwl_mld_config_tlc_nan(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct ieee80211_link_sta *link_sta, + int fw_sta_id) +{ + struct ieee80211_nan_peer_sched *sched; + struct iwl_mld_nan_link *nan_link; + struct ieee80211_sta *nmi, *iter; + struct iwl_mld_vif *nan_mld_vif; + u32 fw_sta_mask; + + nmi = wiphy_dereference(mld->wiphy, link_sta->sta->nmi); + if (WARN_ON(!nmi)) + return; + + sched = nmi->nan_sched; + + /* This sta might not be in the list yet as it is just getting added */ + fw_sta_mask = BIT(fw_sta_id); + for_each_station(iter, mld->hw) { + struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(iter); + struct ieee80211_sta *iter_nmi; + + iter_nmi = wiphy_dereference(mld->wiphy, iter->nmi); + + if (iter_nmi == nmi) + fw_sta_mask |= BIT(mld_sta->deflink.fw_id); + } + + if (WARN_ON(!mld->nan_device_vif)) + return; + + nan_mld_vif = iwl_mld_vif_from_mac80211(mld->nan_device_vif); + + for_each_mld_nan_valid_link(nan_mld_vif, nan_link) { + struct ieee80211_chanctx_conf *chan_ctx; + struct iwl_mld_tlc_sta_capa capa = {}; + struct cfg80211_chan_def *chandef; + bool common_channel = false; + + chan_ctx = nan_link->chanctx; + if (!chan_ctx) + continue; + + chandef = iwl_mld_get_chandef_from_chanctx(mld, chan_ctx); + + capa.smps_mode = IEEE80211_SMPS_OFF; /* always off */ + + capa.rx_nss = 2; /* maximum we support */ + capa.bandwidth = ieee80211_chan_width_to_rx_bw(chandef->width); + + for (int j = 0; j < (sched ? sched->n_channels : 0); j++) { + enum ieee80211_sta_rx_bandwidth rx_bw; + enum nl80211_chan_width width; + int chains; + + if (sched->channels[j].chanctx_conf != chan_ctx) + continue; + + common_channel = true; + + width = sched->channels[j].chanreq.oper.width; + rx_bw = ieee80211_chan_width_to_rx_bw(width); + capa.bandwidth = min(capa.bandwidth, rx_bw); + + chains = sched->channels[j].needed_rx_chains; + capa.rx_nss = min(capa.rx_nss, chains); + } + + /* Apply minimum parameters if there is no common channel */ + if (!common_channel) { + capa.bandwidth = IEEE80211_STA_RX_BW_20; + capa.rx_nss = 1; + } + + capa.own_he_cap = &mld->wiphy->nan_capa.phy.he; + /* no EHT/UHR for NAN */ + + _iwl_mld_config_tlc_link(mld, vif, chan_ctx, fw_sta_mask, + link_sta, &capa); + } +} + +void iwl_mld_config_tlc_link(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf, + struct ieee80211_link_sta *link_sta) +{ + struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link_conf); + int fw_sta_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta); + struct ieee80211_chanctx_conf *chan_ctx; + struct ieee80211_supported_band *sband; + struct iwl_mld_tlc_sta_capa capa = {}; + struct cfg80211_chan_def *chandef; + enum nl80211_band band; + + if (fw_sta_id < 0) + return; + + if (vif->type == NL80211_IFTYPE_NAN) + return; + + if (vif->type == NL80211_IFTYPE_NAN_DATA) { + iwl_mld_config_tlc_nan(mld, vif, link_sta, fw_sta_id); + return; + } + + if (WARN_ON_ONCE(!mld_link)) + return; + + chan_ctx = rcu_dereference_wiphy(mld->wiphy, mld_link->chan_ctx); + if (WARN_ON_ONCE(!chan_ctx)) + return; + + chandef = &iwl_mld_phy_from_mac80211(chan_ctx)->chandef; + band = chandef->chan->band; + sband = mld->hw->wiphy->bands[band]; + + capa.smps_mode = link_sta->smps_mode; + capa.rx_nss = link_sta->rx_nss; + capa.bandwidth = ieee80211_chan_width_to_rx_bw(chandef->width); + capa.bandwidth = min(capa.bandwidth, link_sta->bandwidth); + 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); + + _iwl_mld_config_tlc_link(mld, vif, chan_ctx, BIT(fw_sta_id), + link_sta, &capa); +} + void iwl_mld_config_tlc(struct iwl_mld *mld, struct ieee80211_vif *vif, struct ieee80211_sta *sta) { @@ -725,6 +847,7 @@ void iwl_mld_config_tlc(struct iwl_mld *mld, struct ieee80211_vif *vif, lockdep_assert_wiphy(mld->wiphy); + /* Note: for NAN this is only the mac80211-level deflink */ for_each_vif_active_link(vif, link, link_id) { struct ieee80211_link_sta *link_sta = link_sta_dereference_check(sta, link_id); From b0bb915f7577a6432066b052cb764d0040405b11 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Mon, 11 May 2026 20:36:25 +0300 Subject: [PATCH 051/143] wifi: iwlwifi: mld: allow NAN data Allow NAN_DATA interfaces when FW supports it. Link: https://patch.msgid.link/20260511203428.c47453e9329a.I4a250a0df0ca0d184eb6ac84a4a9fcc3491e9e66@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 13 +++++++++---- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 12 ++++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 4b5f50ff2b05..8856f96a9760 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -62,13 +62,17 @@ static const struct ieee80211_iface_limit iwl_mld_limits_ap[] = { static const struct ieee80211_iface_limit iwl_mld_limits_nan[] = { { - .max = 2, + .max = 1, .types = BIT(NL80211_IFTYPE_STATION), }, { .max = 1, .types = BIT(NL80211_IFTYPE_NAN), }, + { + .max = 2, + .types = BIT(NL80211_IFTYPE_NAN_DATA), + }, }; static const struct ieee80211_iface_combination @@ -87,8 +91,8 @@ iwl_mld_iface_combinations[] = { }, /* NAN combination follow, this excludes P2P and AP */ { - .num_different_channels = 2, - .max_interfaces = 3, + .num_different_channels = 3, + .max_interfaces = 4, .limits = iwl_mld_limits_nan, .n_limits = ARRAY_SIZE(iwl_mld_limits_nan), }, @@ -266,6 +270,7 @@ 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->interface_modes |= BIT(NL80211_IFTYPE_NAN_DATA); hw->wiphy->nan_supported_bands = BIT(NL80211_BAND_2GHZ); if (mld->nvm_data->bands[NL80211_BAND_5GHZ].n_channels) @@ -363,7 +368,7 @@ static void iwl_mac_hw_set_wiphy(struct iwl_mld *mld) ARRAY_SIZE(iwl_mld_iface_combinations); iwl_mld_hw_set_nan(mld); } else { - /* Do not include NAN combinations */ + /* Do not include NAN combination */ wiphy->n_iface_combinations = ARRAY_SIZE(iwl_mld_iface_combinations) - 1; } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index 59e85818b613..cb7a3e01adb4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -18,8 +18,16 @@ bool iwl_mld_nan_supported(struct iwl_mld *mld) { - return fw_has_capa(&mld->fw->ucode_capa, - IWL_UCODE_TLV_CAPA_NAN_SYNC_SUPPORT); + const struct iwl_fw *fw = mld->fw; + + if (fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_NAN_SYNC_SUPPORT) && + iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, NAN_SCHEDULE_CMD), 0) >= 1 && + iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, NAN_PEER_CMD), 0) >= 1 && + iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, STA_CONFIG_CMD), 0) >= 3 && + iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, MAC_CONFIG_CMD), 0) >= 4 && + iwl_fw_lookup_cmd_ver(fw, WIDE_ID(DATA_PATH_GROUP, TLC_MNG_CONFIG_CMD), 0) >= 6) + return true; + return false; } static int iwl_mld_nan_send_config_cmd(struct iwl_mld *mld, From 646381b37872f2367aa1d0a909c45f3c61ea9d6c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 11 May 2026 20:36:26 +0300 Subject: [PATCH 052/143] wifi: iwlwifi: mld: track TX/RX IGTKs separately Due to FW/HW limitations and the MME being at the end of the frame, the devices only support a single IGTK for RX. For TX multiple aren't needed, only the latest will be used, but in the device there are space restrictions, so we can also only install one. For NAN, however, we will have one for RX for each peer, and one for ourselves to transmit with. Separate out the tracking of IGTK: instead of being per link make the TX ones per link and the RX ones per (link) station. Note that we currently hardcode that the FW can only have two (IWL_MAX_NUM_IGTKS) IGTKs, which won't be sufficient for NAN with security, concurrently with BSS. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260511203428.8fa6859b185d.I584e8b9d238f0b9ef6b65f95114c255209d1c2a3@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/d3.c | 20 ++-- .../net/wireless/intel/iwlwifi/mld/iface.h | 15 +++ drivers/net/wireless/intel/iwlwifi/mld/key.c | 98 ++++++++++++------- drivers/net/wireless/intel/iwlwifi/mld/link.h | 9 +- drivers/net/wireless/intel/iwlwifi/mld/sta.h | 4 + 5 files changed, 97 insertions(+), 49 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index ef98efc8fb1b..c44f02f225ce 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.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 "mld.h" @@ -951,19 +951,25 @@ iwl_mld_add_mcast_rekey(struct ieee80211_vif *vif, iwl_mld_update_mcast_rx_seq(key_config, key_data); - /* The FW holds only one igtk so we keep track of the valid one */ + /* The FW holds only one IGTK so we keep track of the valid one */ if (key_config->keyidx == 4 || key_config->keyidx == 5) { - struct iwl_mld_link *mld_link = - iwl_mld_link_from_mac80211(link_conf); + struct iwl_mld_link_sta *mld_ap_link_sta; + + mld_ap_link_sta = iwl_mld_get_ap_link_sta(vif, + link_conf->link_id); + if (WARN_ON(!mld_ap_link_sta)) + return; /* If we had more than one rekey, mac80211 will tell us to * remove the old and add the new so we will update the IGTK in * drv_set_key */ - if (mld_link->igtk && mld_link->igtk != key_config) { + if (mld_ap_link_sta->rx_igtk && + mld_ap_link_sta->rx_igtk != key_config) { /* mark the old IGTK as not in FW */ - mld_link->igtk->hw_key_idx = STA_KEY_IDX_INVALID; - mld_link->igtk = key_config; + mld_ap_link_sta->rx_igtk->hw_key_idx = + STA_KEY_IDX_INVALID; + mld_ap_link_sta->rx_igtk = key_config; } } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.h b/drivers/net/wireless/intel/iwlwifi/mld/iface.h index ce4f8ca885cf..147ff6787ed9 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h @@ -289,4 +289,19 @@ static inline bool iwl_mld_vif_low_latency(const struct iwl_mld_vif *mld_vif) struct ieee80211_vif *iwl_mld_get_bss_vif(struct iwl_mld *mld); +static inline struct iwl_mld_link_sta * +iwl_mld_get_ap_link_sta(struct ieee80211_vif *vif, int link_id) +{ + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + struct ieee80211_sta *ap_sta = mld_vif->ap_sta; + struct iwl_mld_sta *mld_ap_sta; + + if (!ap_sta) + return NULL; + + mld_ap_sta = iwl_mld_sta_from_mac80211(ap_sta); + + return iwl_mld_link_sta_dereference_check(mld_ap_sta, link_id); +} + #endif /* __iwl_mld_iface_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/key.c b/drivers/net/wireless/intel/iwlwifi/mld/key.c index 2cb312ebd7a7..944d5487c110 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/key.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/key.c @@ -170,34 +170,69 @@ static void iwl_mld_remove_key_from_fw(struct iwl_mld *mld, u32 sta_mask, iwl_mld_send_cmd_pdu(mld, WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD), &cmd); } +static struct ieee80211_key_conf ** +iwl_mld_get_igtk_ptr(struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + /* key's link ID is set to -1 for non-MLO */ + int link_id = key->link_id < 0 ? 0 : key->link_id; + struct iwl_mld_link_sta *mld_ap_link_sta; + struct iwl_mld_link *mld_link; + struct iwl_mld_sta *mld_sta; + + if (key->keyidx != 4 && key->keyidx != 5) + return NULL; + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + if (WARN_ON(!sta)) + return NULL; + + mld_sta = iwl_mld_sta_from_mac80211(sta); + mld_ap_link_sta = iwl_mld_link_sta_dereference_check(mld_sta, + link_id); + if (WARN_ON(!mld_ap_link_sta)) + return NULL; + + return &mld_ap_link_sta->rx_igtk; + case NL80211_IFTYPE_NAN: + if (sta) { + mld_sta = iwl_mld_sta_from_mac80211(sta); + + return &mld_sta->deflink.rx_igtk; + } + + return &mld_vif->deflink.tx_igtk; + case NL80211_IFTYPE_AP: + mld_link = iwl_mld_link_dereference_check(mld_vif, link_id); + if (WARN_ON(!mld_link)) + return NULL; + + return &mld_link->tx_igtk; + default: + WARN_ONCE(1, "invalid iftype %d for IGTK\n", vif->type); + return NULL; + } +} + void iwl_mld_remove_key(struct iwl_mld *mld, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *key) { u32 sta_mask = iwl_mld_get_key_sta_mask(mld, vif, sta, key); u32 key_flags = iwl_mld_get_key_flags(mld, vif, sta, key); - struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + struct ieee80211_key_conf **igtk_ptr; lockdep_assert_wiphy(mld->wiphy); if (!sta_mask) return; - if (key->keyidx == 4 || key->keyidx == 5) { - struct iwl_mld_link *mld_link; - unsigned int link_id = 0; - - /* set to -1 for non-MLO right now */ - if (key->link_id >= 0) - link_id = key->link_id; - - mld_link = iwl_mld_link_dereference_check(mld_vif, link_id); - if (WARN_ON(!mld_link)) - return; - - if (mld_link->igtk == key) - mld_link->igtk = NULL; - + igtk_ptr = iwl_mld_get_igtk_ptr(vif, sta, key); + if (igtk_ptr && *igtk_ptr == key) { + *igtk_ptr = NULL; mld->num_igtks--; } @@ -214,9 +249,7 @@ int iwl_mld_add_key(struct iwl_mld *mld, { u32 sta_mask = iwl_mld_get_key_sta_mask(mld, vif, sta, key); u32 key_flags = iwl_mld_get_key_flags(mld, vif, sta, key); - struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); - struct iwl_mld_link *mld_link = NULL; - bool igtk = key->keyidx == 4 || key->keyidx == 5; + struct ieee80211_key_conf **igtk_ptr; int ret; lockdep_assert_wiphy(mld->wiphy); @@ -224,36 +257,25 @@ int iwl_mld_add_key(struct iwl_mld *mld, if (!sta_mask) return -EINVAL; - if (igtk) { + igtk_ptr = iwl_mld_get_igtk_ptr(vif, sta, key); + if (igtk_ptr) { if (mld->num_igtks == IWL_MAX_NUM_IGTKS) return -EOPNOTSUPP; - u8 link_id = 0; - - /* set to -1 for non-MLO right now */ - if (key->link_id >= 0) - link_id = key->link_id; - - mld_link = iwl_mld_link_dereference_check(mld_vif, link_id); - - if (WARN_ON(!mld_link)) - return -EINVAL; - - if (mld_link->igtk) { + if (*igtk_ptr) { IWL_DEBUG_MAC80211(mld, "remove old IGTK %d\n", - mld_link->igtk->keyidx); - iwl_mld_remove_key(mld, vif, sta, mld_link->igtk); + (*igtk_ptr)->keyidx); + iwl_mld_remove_key(mld, vif, sta, *igtk_ptr); } - - WARN_ON(mld_link->igtk); } ret = iwl_mld_add_key_to_fw(mld, sta_mask, key_flags, key); if (ret) return ret; - if (mld_link) { - mld_link->igtk = key; + if (igtk_ptr) { + WARN_ON(*igtk_ptr); + *igtk_ptr = key; mld->num_igtks++; } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.h b/drivers/net/wireless/intel/iwlwifi/mld/link.h index 84d9a24134a8..2b3e6b55367f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.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_link_h__ #define __iwl_mld_link_h__ @@ -34,8 +34,9 @@ struct iwl_probe_resp_data { * @chan_ctx: pointer to the channel context assigned to the link. If a link * has an assigned channel context it means that it is active. * @he_ru_2mhz_block: 26-tone RU OFDMA transmissions should be blocked. - * @igtk: fw can only have one IGTK at a time, whereas mac80211 can have two. - * This tracks the one IGTK that currently exists in FW. + * @tx_igtk: FW can only have one IGTK per MAC at a time, whereas mac80211 can + * have two. This tracks the one IGTK that currently exists in FW, for TX + * purposes. The RX IGTKs are tracked per station. * @bigtks: BIGTKs of the AP. Only valid for STA mode. * @bcast_sta: station used for broadcast packets. Used in AP, GO and IBSS. * @mcast_sta: station used for multicast packets. Used in AP, GO and IBSS. @@ -60,7 +61,7 @@ struct iwl_mld_link { struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS]; struct ieee80211_chanctx_conf __rcu *chan_ctx; bool he_ru_2mhz_block; - struct ieee80211_key_conf *igtk; + struct ieee80211_key_conf *tx_igtk; struct ieee80211_key_conf __rcu *bigtks[2]; ); /* And here fields that survive a fw restart */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.h b/drivers/net/wireless/intel/iwlwifi/mld/sta.h index 13644ffd185d..dff14ff0a5af 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.h @@ -28,6 +28,9 @@ struct iwl_mld_rxq_dup_data { * This represents the link-level sta - the driver level equivalent to the * ieee80211_link_sta * + * @rx_igtk: FW can only have one IGTK for RX at a time, whereas mac80211 will + * have two. This tracks the one IGTK that currently exists in FW, to + * remove it there when a new one is installed. * @last_rate_n_flags: rate_n_flags from the last &iwl_tlc_update_notif * @signal_avg: the signal average coming from the firmware * @in_fw: whether the link STA is uploaded to the FW (false during restart) @@ -37,6 +40,7 @@ struct iwl_mld_rxq_dup_data { struct iwl_mld_link_sta { /* Add here fields that need clean up on restart */ struct_group(zeroed_on_hw_restart, + struct ieee80211_key_conf *rx_igtk; u32 last_rate_n_flags; bool in_fw; s8 signal_avg; From 117694a9633f9ae627cf642b067f2d92571fa2f6 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 11 May 2026 20:36:27 +0300 Subject: [PATCH 053/143] wifi: iwlwifi: mld: don't report bad STA ID in EHT TB sniffer The field being reported here is part of the EHT union, and not valid in EHT TB. Don't report it there. We could probably report the station ID we're following, but for now just don't, since it appears nobody really cared. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260511203428.b23984f12a46.Ia25014392129d5815facaacec78ea8ba7f35d9a0@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/rx.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c index b270cf87824d..a9f08f984633 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c @@ -1206,6 +1206,13 @@ static void iwl_mld_decode_eht_non_tb(struct iwl_mld_rx_phy_data *phy_data, iwl_mld_eht_set_ru_alloc(rx_status, le32_get_bits(phy_data->ntfy->sigs.eht.b2, OFDM_RX_FRAME_EHT_STA_RU)); + + if (phy_data->with_data) + eht->user_info[0] |= + cpu_to_le32(IEEE80211_RADIOTAP_EHT_USER_INFO_STA_ID_KNOWN) | + LE32_DEC_ENC(phy_data->ntfy->sigs.eht.user_id, + OFDM_RX_FRAME_EHT_USER_FIELD_ID, + IEEE80211_RADIOTAP_EHT_USER_INFO_STA_ID); } static void iwl_mld_decode_eht_phy_data(struct iwl_mld_rx_phy_data *phy_data, @@ -1314,14 +1321,6 @@ static void iwl_mld_rx_eht(struct iwl_mld *mld, struct sk_buff *skb, if (likely(!phy_data->ntfy)) return; - if (phy_data->with_data) { - eht->user_info[0] |= - cpu_to_le32(IEEE80211_RADIOTAP_EHT_USER_INFO_STA_ID_KNOWN) | - LE32_DEC_ENC(phy_data->ntfy->sigs.eht.user_id, - OFDM_RX_FRAME_EHT_USER_FIELD_ID, - IEEE80211_RADIOTAP_EHT_USER_INFO_STA_ID); - } - iwl_mld_decode_eht_usig(phy_data, skb); iwl_mld_decode_eht_phy_data(phy_data, rx_status, eht); } From b73bc926989b8c80c21e96a4c5ccf46a59beb5a4 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 11 May 2026 20:36:28 +0300 Subject: [PATCH 054/143] wifi: iwlwifi: api: RX: define UHR RX PHY flags Define a couple of UHR PHY flags in the metadata that carry information about 2x LDPC, UEQM and NPCA. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260511203428.cfca1a7d40cf.I32b51dc6eae98787533c64ccf2a73789409dfcb9@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/api/rx.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h index ac6c1ef2cbcd..8ef764787ba4 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h @@ -263,6 +263,9 @@ enum iwl_rx_mpdu_reorder_data { enum iwl_rx_mpdu_phy_info { IWL_RX_MPDU_PHY_EOF_INDICATION = BIT(0), + IWL_RX_MPDU_PHY_UEQM = BIT(1) | BIT(2), + IWL_RX_MPDU_PHY_2X_LDPC = BIT(3), + IWL_RX_MPDU_PHY_NPCA = BIT(4), IWL_RX_MPDU_PHY_AMPDU = BIT(5), IWL_RX_MPDU_PHY_AMPDU_TOGGLE = BIT(6), IWL_RX_MPDU_PHY_SHORT_PREAMBLE = BIT(7), From 7acb29519300aae5a48353ebbbd7234d112c1e1c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 11 May 2026 20:36:29 +0300 Subject: [PATCH 055/143] wifi: iwlwifi: fw: api: fix UHR U-SIG whitespace Somehow I put tabs there where it should be spaces, fix that. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260511203428.f960f56f6734.I1f925625e606fbc96e8401ac3c5667671065bbe1@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/api/rx.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h index 8ef764787ba4..e46e2fe46eaa 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h @@ -1278,15 +1278,15 @@ struct iwl_uhr_sigs { __le32 usig_a1; #define OFDM_RX_FRAME_UHR_BSS_COLOR2 0x0000003f __le32 usig_a1_uhr; -#define OFDM_RX_FRAME_UHR_PPDU_TYPE 0x00000003 -#define OFDM_RX_FRAME_UHR_COBF_CSR_DISABLE 0x00000004 -#define OFDM_RX_FRAME_UHR_PUNC_CHANNEL 0x000000f8 -#define OFDM_RX_FRAME_UHR_USIG2_VALIDATE_B8 0x00000100 -#define OFDM_RX_FRAME_UHR_SIG_MCS 0x00000600 -#define OFDM_RX_FRAME_UHR_SIG_SYM_NUM 0x0000f800 -#define OFDM_RX_FRAME_UHR_TRIG_SPATIAL_REUSE_1 0x000f0000 -#define OFDM_RX_FRAME_UHR_TRIG_SPATIAL_REUSE_2 0x00f00000 -#define OFDM_RX_FRAME_UHR_TRIG_USIG2_DISREGARD 0x1f000000 +#define OFDM_RX_FRAME_UHR_PPDU_TYPE 0x00000003 +#define OFDM_RX_FRAME_UHR_COBF_CSR_DISABLE 0x00000004 +#define OFDM_RX_FRAME_UHR_PUNC_CHANNEL 0x000000f8 +#define OFDM_RX_FRAME_UHR_USIG2_VALIDATE_B8 0x00000100 +#define OFDM_RX_FRAME_UHR_SIG_MCS 0x00000600 +#define OFDM_RX_FRAME_UHR_SIG_SYM_NUM 0x0000f800 +#define OFDM_RX_FRAME_UHR_TRIG_SPATIAL_REUSE_1 0x000f0000 +#define OFDM_RX_FRAME_UHR_TRIG_SPATIAL_REUSE_2 0x00f00000 +#define OFDM_RX_FRAME_UHR_TRIG_USIG2_DISREGARD 0x1f000000 __le32 usig_a2_uhr; #define OFDM_RX_FRAME_UHR_SPATIAL_REUSE 0x0000000f #define OFDM_RX_FRAME_UHR_GI_LTF_TYPE 0x00000030 From 06bc6f66ecab27a8717f2b2cd0d68ad800faf0c3 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 11 May 2026 20:36:30 +0300 Subject: [PATCH 056/143] wifi: iwlwifi: fw: api: add/fix some UHR sniffer definitions Add some remaining definitions for the UHR sniffer API, and fix the ELR where instead of usig_a2_uhr we'll get usig_a2_uhr_elr. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260511203428.458cd2ff6623.Ia76e0371adac431b6a5fb93004640278d8572f09@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/api/rx.h | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h index e46e2fe46eaa..aa4e5e9d4d4e 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h @@ -1172,8 +1172,14 @@ struct iwl_he_tb_sigs { #define OFDM_UCODE_TRIG_BASE_RX_BANDWIDTH 0x00007000 #define OFDM_UCODE_TRIG_BASE_PS160 0x00008000 #define OFDM_UCODE_EHT_TRIG_CONTROL_CHANNEL 0x000f0000 +/* for UHR, since PE RF: */ +#define OFDM_UCODE_TRIG_BASE_RX_MCS_UHR_AND_LATER 0x01f00000 +#define OFDM_UCODE_RX_IS_DRU 0x02000000 +#define OFDM_UCODE_RX_IS_2XLDPC_ENABLED 0x04000000 +#define OFDM_UCODE_RX_DRU_DBW 0x60000000 __le32 tb_rx0; -#define OFDM_UCODE_TRIG_BASE_RX_MCS 0x0000000f +/* prior to PE RF, cannot have 5 bits needed for UHR: */ +#define OFDM_UCODE_TRIG_BASE_RX_MCS_PRE_UHR 0x0000000f #define OFDM_UCODE_TRIG_BASE_RX_DCM 0x00000010 #define OFDM_UCODE_TRIG_BASE_RX_GI_LTF_TYPE 0x00000060 #define OFDM_UCODE_TRIG_BASE_RX_NSTS 0x00000380 @@ -1287,6 +1293,7 @@ struct iwl_uhr_sigs { #define OFDM_RX_FRAME_UHR_TRIG_SPATIAL_REUSE_1 0x000f0000 #define OFDM_RX_FRAME_UHR_TRIG_SPATIAL_REUSE_2 0x00f00000 #define OFDM_RX_FRAME_UHR_TRIG_USIG2_DISREGARD 0x1f000000 +#define OFDM_RX_FRAME_UHR_USIG_CRC_OK 0x40000000 __le32 usig_a2_uhr; #define OFDM_RX_FRAME_UHR_SPATIAL_REUSE 0x0000000f #define OFDM_RX_FRAME_UHR_GI_LTF_TYPE 0x00000030 @@ -1308,6 +1315,7 @@ struct iwl_uhr_sigs { #define OFDM_RX_FRAME_UHR_CODING 0x00000200 #define OFDM_RX_FRAME_UHR_SPATIAL_CONFIG 0x00003c00 #define OFDM_RX_FRAME_UHR_STA_RU 0x003fc000 +#define OFDM_RX_FRAME_UHR_STA_RU_P80 0x00004000 #define OFDM_RX_FRAME_UHR_STA_RU_PS160 0x00400000 #define OFDM_RX_FRAME_UHR_UEQM 0x00800000 #define OFDM_RX_FRAME_UHR_2XLDPC 0x01000000 @@ -1333,7 +1341,12 @@ struct iwl_uhr_tb_sigs { struct iwl_uhr_elr_sigs { /* same as UHR above */ - __le32 usig_a1, usig_a2_uhr; + __le32 usig_a1; +#define OFDM_RX_VECTOR_UHR_ELR_PPDU_TYPE 0x00000003 +#define OFDM_RX_VECTOR_UHR_ELR_USIG_STA_ID 0x00001ffc +#define OFDM_RX_VECTOR_UHR_ELR_VALIDATE 0x0000e000 +#define OFDM_RX_VECTOR_UHR_ELR_CRC_OK 0x00010000 + __le32 usig_a2_uhr_elr; #define OFDM_RX_VECTOR_UHR_ELR_VER_ID 0x00000007 #define OFDM_RX_VECTOR_UHR_ELR_UPLINK_FLAG 0x00000008 #define OFDM_RX_VECTOR_UHR_ELR_MCS 0x00000010 From 45e1a52f65b0c1553b6649f48cac37d453233cef Mon Sep 17 00:00:00 2001 From: Avinash Bhatt Date: Mon, 11 May 2026 20:36:31 +0300 Subject: [PATCH 057/143] wifi: iwlwifi: fix buffer overflow when firmware reports no channels On parsing NVM in setting country code, if firmware reports 0 channels, buffer is allocated for 0 rules but a dummy rule is added for cfg80211 compatibility, causing kmemdup() to read 128 bytes from a 32-byte buffer. Allocate regd buffer for one rule addition when reported channels are 0. Signed-off-by: Avinash Bhatt Link: https://patch.msgid.link/20260511203428.e03cd831bc96.I8260d881eebe3e83d3208959b525c51af26414e6@changeid Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/iwl-nvm-parse.c | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index 0736c8c00d4e..455d6e8c7028 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -1724,8 +1724,16 @@ iwl_parse_nvm_mcc_info(struct iwl_trans *trans, IWL_DEBUG_DEV(dev, IWL_DL_LAR, "building regdom for %d channels\n", num_of_ch); - /* build a regdomain rule for every valid channel */ - regd = kzalloc_flex(*regd, reg_rules, num_of_ch); + /* build a regdomain rule for every valid channel. + * Certain firmware versions might report no valid channels + * if booted in RF-kill, i.e. not all calibrations etc. are + * running. We'll get out of this situation later when the + * rfkill is removed and we update the regdomain again, but + * since cfg80211 doesn't accept an empty regdomain, we need + * to allocate space for at least one rule to add a dummy + * (unusable) rule in this case so we can init. + */ + regd = kzalloc_flex(*regd, reg_rules, num_of_ch ?: 1); if (!regd) return ERR_PTR(-ENOMEM); @@ -1799,14 +1807,7 @@ iwl_parse_nvm_mcc_info(struct iwl_trans *trans, reg_query_regdb_wmm(regd->alpha2, center_freq, rule); } - /* - * Certain firmware versions might report no valid channels - * if booted in RF-kill, i.e. not all calibrations etc. are - * running. We'll get out of this situation later when the - * rfkill is removed and we update the regdomain again, but - * since cfg80211 doesn't accept an empty regdomain, add a - * dummy (unusable) rule here in this case so we can init. - */ + /* If no valid rules were found, add a dummy rule */ if (!valid_rules) { valid_rules = 1; rule = ®d->reg_rules[valid_rules - 1]; From 7b2b86fd7030abe8330f57ed1faf54ea214eb872 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 12 May 2026 08:22:53 +0300 Subject: [PATCH 058/143] wifi: iwlwifi: pcie: fix ACPI DSM check The acpi_check_dsm() function expects a bitmap of function IDs to check for, not a single value. Evidently, on many platforms function 1 exists so checking for 2 succeeded, but it's wrong, we need to check correctly for function 2. Fix that. Reported-by: Jeremy Compostella Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260512082114.81c4022ae602.Id3f575944ee6b87634cfb976d7334eac227d18a6@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c index 377b2e30b540..9f4c99dca195 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c @@ -2044,7 +2044,7 @@ iwl_trans_pcie_call_prod_reset_dsm(struct pci_dev *pdev, u16 cmd, u16 value) 0xDD, 0x26, 0xB5, 0xFD); if (!acpi_check_dsm(ACPI_HANDLE(&pdev->dev), &dsm_guid, ACPI_DSM_REV, - DSM_INTERNAL_FUNC_PRODUCT_RESET)) + BIT(DSM_INTERNAL_FUNC_PRODUCT_RESET))) return ERR_PTR(-ENODEV); return iwl_acpi_get_dsm_object(&pdev->dev, ACPI_DSM_REV, From de04c81d4811496bd9c375bec44c2e984218be9a Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 12 May 2026 08:22:54 +0300 Subject: [PATCH 059/143] wifi: iwlwifi: rename iwl_system_statistics_notif_oper rename iwl_system_statistics_notif_oper to iwl_system_statistics_notif_oper_v3 since v4 is on the way. Same for iwl_stats_ntfy_per_phy, since v2 is on the way. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260512082114.2c3b55b1cae7.Ic982845bfe08c7c9ea16c267570e3e3856de84b8@changeid Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/commands.h | 4 ++-- .../net/wireless/intel/iwlwifi/fw/api/stats.h | 18 +++++++++--------- drivers/net/wireless/intel/iwlwifi/mld/notif.c | 4 ++-- drivers/net/wireless/intel/iwlwifi/mld/stats.c | 10 +++++----- drivers/net/wireless/intel/iwlwifi/mvm/ops.c | 4 ++-- drivers/net/wireless/intel/iwlwifi/mvm/rx.c | 9 +++++---- 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h b/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h index 36159a769916..dcac0ba8cccf 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2022, 2024-2025 Intel Corporation + * Copyright (C) 2018-2022, 2024-2026 Intel Corporation */ #ifndef __iwl_fw_api_commands_h__ #define __iwl_fw_api_commands_h__ @@ -653,7 +653,7 @@ enum iwl_system_subcmd_ids { enum iwl_statistics_subcmd_ids { /** * @STATISTICS_OPER_NOTIF: Notification about operational - * statistics &struct iwl_system_statistics_notif_oper + * statistics &struct iwl_system_statistics_notif_oper_v3 */ STATISTICS_OPER_NOTIF = 0x0, diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h b/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h index 68983f6a0026..8d734512f4d5 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2012-2014, 2018, 2020-2021, 2023-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018, 2020-2021, 2023-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -557,7 +557,7 @@ struct iwl_stats_ntfy_per_mac { #define IWL_STATS_MAX_BW_INDEX 5 /** - * struct iwl_stats_ntfy_per_phy - per PHY statistics + * struct iwl_stats_ntfy_per_phy_v1 - per PHY statistics * @channel_load: channel load * @channel_load_by_us: device contribution to MCLM * @channel_load_not_by_us: other devices' contribution to MCLM @@ -572,7 +572,7 @@ struct iwl_stats_ntfy_per_mac { * per channel BW. note BACK counted as 1 * @last_tx_ch_width_indx: last txed frame channel width index */ -struct iwl_stats_ntfy_per_phy { +struct iwl_stats_ntfy_per_phy_v1 { __le32 channel_load; __le32 channel_load_by_us; __le32 channel_load_not_by_us; @@ -600,17 +600,17 @@ struct iwl_stats_ntfy_per_sta { #define IWL_STATS_MAX_PHY_OPERATIONAL 3 /** - * struct iwl_system_statistics_notif_oper - statistics notification + * struct iwl_system_statistics_notif_oper_v3 - statistics notification * * @time_stamp: time when the notification is sent from firmware * @per_link: per link statistics, &struct iwl_stats_ntfy_per_link - * @per_phy: per phy statistics, &struct iwl_stats_ntfy_per_phy + * @per_phy: per phy statistics, &struct iwl_stats_ntfy_per_phy_v1 * @per_sta: per sta statistics, &struct iwl_stats_ntfy_per_sta */ -struct iwl_system_statistics_notif_oper { +struct iwl_system_statistics_notif_oper_v3 { __le32 time_stamp; struct iwl_stats_ntfy_per_link per_link[IWL_FW_MAX_LINKS]; - struct iwl_stats_ntfy_per_phy per_phy[IWL_STATS_MAX_PHY_OPERATIONAL]; + struct iwl_stats_ntfy_per_phy_v1 per_phy[IWL_STATS_MAX_PHY_OPERATIONAL]; struct iwl_stats_ntfy_per_sta per_sta[IWL_STATION_COUNT_MAX]; } __packed; /* STATISTICS_FW_NTFY_OPERATIONAL_API_S_VER_3 */ @@ -642,7 +642,7 @@ struct iwl_system_statistics_end_notif { * @hdr: general statistics header * @flags: bitmap of possible notification structures * @per_mac: per mac statistics, &struct iwl_stats_ntfy_per_mac - * @per_phy: per phy statistics, &struct iwl_stats_ntfy_per_phy + * @per_phy: per phy statistics, &struct iwl_stats_ntfy_per_phy_v1 * @per_sta: per sta statistics, &struct iwl_stats_ntfy_per_sta * @rx_time: rx time * @tx_time: usec the radio is transmitting. @@ -653,7 +653,7 @@ struct iwl_statistics_operational_ntfy { struct iwl_statistics_ntfy_hdr hdr; __le32 flags; struct iwl_stats_ntfy_per_mac per_mac[MAC_INDEX_AUX]; - struct iwl_stats_ntfy_per_phy per_phy[IWL_STATS_MAX_PHY_OPERATIONAL]; + struct iwl_stats_ntfy_per_phy_v1 per_phy[IWL_STATS_MAX_PHY_OPERATIONAL]; struct iwl_stats_ntfy_per_sta per_sta[IWL_STATION_COUNT_MAX]; __le64 rx_time; __le64 tx_time; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index 9c88a8579a75..f7aeff61d785 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.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 "mld.h" @@ -330,7 +330,7 @@ CMD_VERSIONS(probe_resp_data_notif, CMD_VERSIONS(datapath_monitor_notif, CMD_VER_ENTRY(1, iwl_datapath_monitor_notif)) CMD_VERSIONS(stats_oper_notif, - CMD_VER_ENTRY(3, iwl_system_statistics_notif_oper)) + CMD_VER_ENTRY(3, iwl_system_statistics_notif_oper_v3)) CMD_VERSIONS(stats_oper_part1_notif, CMD_VER_ENTRY(4, iwl_system_statistics_part1_notif_oper)) CMD_VERSIONS(bt_coex_notif, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/stats.c b/drivers/net/wireless/intel/iwlwifi/mld/stats.c index 54eb0ead78ee..714d66324e9f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/stats.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/stats.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 "mld.h" @@ -40,7 +40,7 @@ iwl_mld_fill_stats_from_oper_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt, u8 fw_sta_id, struct station_info *sinfo) { - const struct iwl_system_statistics_notif_oper *notif = + const struct iwl_system_statistics_notif_oper_v3 *notif = (void *)&pkt->data; const struct iwl_stats_ntfy_per_sta *per_sta = ¬if->per_sta[fw_sta_id]; @@ -483,7 +483,7 @@ static void iwl_mld_fill_chanctx_stats(struct ieee80211_hw *hw, void *data) { struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx); - const struct iwl_stats_ntfy_per_phy *per_phy = data; + const struct iwl_stats_ntfy_per_phy_v1 *per_phy = data; u32 new_load, old_load; if (WARN_ON(phy->fw_id >= IWL_STATS_MAX_PHY_OPERATIONAL)) @@ -512,7 +512,7 @@ static void iwl_mld_fill_chanctx_stats(struct ieee80211_hw *hw, static void iwl_mld_process_per_phy_stats(struct iwl_mld *mld, - const struct iwl_stats_ntfy_per_phy *per_phy) + const struct iwl_stats_ntfy_per_phy_v1 *per_phy) { ieee80211_iter_chan_contexts_mtx(mld->hw, iwl_mld_fill_chanctx_stats, @@ -523,7 +523,7 @@ iwl_mld_process_per_phy_stats(struct iwl_mld *mld, void iwl_mld_handle_stats_oper_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt) { - const struct iwl_system_statistics_notif_oper *stats = + const struct iwl_system_statistics_notif_oper_v3 *stats = (void *)&pkt->data; u32 curr_ts_usec = le32_to_cpu(stats->time_stamp); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c index ae177477b201..726477336bb4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -335,7 +335,7 @@ static const struct iwl_rx_handlers iwl_mvm_rx_handlers[] = { RX_HANDLER_GRP(STATISTICS_GROUP, STATISTICS_OPER_NOTIF, iwl_mvm_handle_rx_system_oper_stats, RX_HANDLER_ASYNC_LOCKED_WIPHY, - struct iwl_system_statistics_notif_oper), + struct iwl_system_statistics_notif_oper_v3), RX_HANDLER_GRP(STATISTICS_GROUP, STATISTICS_OPER_PART1_NOTIF, iwl_mvm_handle_rx_system_oper_part1_stats, RX_HANDLER_ASYNC_LOCKED, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c index d0c0faae0122..269c4b45de80 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -757,8 +757,9 @@ iwl_mvm_update_tcm_from_stats(struct iwl_mvm *mvm, __le32 *air_time_le, spin_unlock(&mvm->tcm.lock); } -static void iwl_mvm_handle_per_phy_stats(struct iwl_mvm *mvm, - struct iwl_stats_ntfy_per_phy *per_phy) +static void +iwl_mvm_handle_per_phy_stats(struct iwl_mvm *mvm, + struct iwl_stats_ntfy_per_phy_v1 *per_phy) { int i; @@ -937,7 +938,7 @@ void iwl_mvm_handle_rx_system_oper_stats(struct iwl_mvm *mvm, { u8 average_energy[IWL_STATION_COUNT_MAX]; struct iwl_rx_packet *pkt = rxb_addr(rxb); - struct iwl_system_statistics_notif_oper *stats; + struct iwl_system_statistics_notif_oper_v3 *stats; int i; u32 notif_ver = iwl_fw_lookup_notif_ver(mvm->fw, STATISTICS_GROUP, STATISTICS_OPER_NOTIF, 0); From 2db4f7712f8c6432332094a7e0113b4df82dc012 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 12 May 2026 08:22:55 +0300 Subject: [PATCH 060/143] wifi: iwlwifi: introduce iwl_system_statistics_notif_oper version 4 This new version includes Coex related statistics Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260512082114.705fbeeb28d1.I4804d029fa76cfb8267f8c6bcac1ed237a8b3497@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/fw/api/stats.h | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h b/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h index 8d734512f4d5..8d16782a129c 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h @@ -585,6 +585,67 @@ struct iwl_stats_ntfy_per_phy_v1 { __le32 last_tx_ch_width_indx; } __packed; /* STATISTICS_NTFY_PER_PHY_API_S_VER_1 */ +/** + * struct iwl_stats_ntfy_coex - coex statistics + * + * @wifi_kill_cnt: count of wifi frames killed by BT + * @wifi_tx_cts_kill_cnt: count of wifi Tx CTS frames killed by BT + * @ttc2_ppdu_error_count: Count PPDU errors on TTC2 - BT Tx indication rises + * within wifi Tx packet on non-shared antenna and wifi is NOT killed by + * PTA/TCL. + * @trc2_ppdu_error_count: count PPDU errors on TRC2 - BT Rx indication rises + * within wifi Tx packet on non-shared antenna and wifi is NOT killed by + * PTA/TCL + * @rrc1_collision_count: count RRC1 - BT Rx indication rises within wifi Rx + * packet on shared antenna + * @rrc2_collision_count: count RRC2 - BT Rx indication rises within wifi Rx + * packet on non-shared antenna + * @rtc2_collision_count: count RTC2 - BT Tx indication rises within wifi Rx + * packet on non-shared antenna + * @reserved: reserved + */ +struct iwl_stats_ntfy_coex { + __le16 wifi_kill_cnt; + __le16 wifi_tx_cts_kill_cnt; + __le16 ttc2_ppdu_error_count; + __le16 trc2_ppdu_error_count; + __le16 rrc1_collision_count; + __le16 rrc2_collision_count; + __le16 rtc2_collision_count; + __le16 reserved; +} __packed; /* STATISTICS_FW_NTFY_COEX_TELEMETRY_API_S_VER_1 */ + +/** + * struct iwl_stats_ntfy_per_phy - per PHY statistics + * @channel_load: channel load + * @channel_load_by_us: device contribution to MCLM + * @channel_load_not_by_us: other devices' contribution to MCLM + * @clt: CLT HW timer (TIM_CH_LOAD2) + * @act: active accumulator SW + * @elp: elapsed time accumulator SW + * @rx_detected_per_ch_width: number of deferred TX per channel width, + * 0 - 20, 1/2/3 - 40/80/160 + * @success_per_ch_width: number of frames that got ACK/BACK/CTS + * per channel BW. note, BACK counted as 1 + * @fail_per_ch_width: number of frames that didn't get ACK/BACK/CTS + * per channel BW. note BACK counted as 1 + * @last_tx_ch_width_indx: last txed frame channel width index + * @coex: coex related data + */ +struct iwl_stats_ntfy_per_phy { + __le32 channel_load; + __le32 channel_load_by_us; + __le32 channel_load_not_by_us; + __le32 clt; + __le32 act; + __le32 elp; + __le32 rx_detected_per_ch_width[IWL_STATS_MAX_BW_INDEX]; + __le32 success_per_ch_width[IWL_STATS_MAX_BW_INDEX]; + __le32 fail_per_ch_width[IWL_STATS_MAX_BW_INDEX]; + __le32 last_tx_ch_width_indx; + struct iwl_stats_ntfy_coex coex; +} __packed; /* STATISTICS_NTFY_PER_PHY_API_S_VER_2 */ + /* unknown channel load (due to not being active on channel) */ #define IWL_STATS_UNKNOWN_CHANNEL_LOAD 0xffffffff @@ -614,6 +675,21 @@ struct iwl_system_statistics_notif_oper_v3 { struct iwl_stats_ntfy_per_sta per_sta[IWL_STATION_COUNT_MAX]; } __packed; /* STATISTICS_FW_NTFY_OPERATIONAL_API_S_VER_3 */ +/** + * struct iwl_system_statistics_notif_oper - statistics notification + * + * @time_stamp: time when the notification is sent from firmware + * @per_link: per link statistics, &struct iwl_stats_ntfy_per_link + * @per_phy: per phy statistics, &struct iwl_stats_ntfy_per_phy_v1 + * @per_sta: per sta statistics, &struct iwl_stats_ntfy_per_sta + */ +struct iwl_system_statistics_notif_oper { + __le32 time_stamp; + struct iwl_stats_ntfy_per_link per_link[IWL_FW_MAX_LINKS]; + struct iwl_stats_ntfy_per_phy per_phy[IWL_STATS_MAX_PHY_OPERATIONAL]; + struct iwl_stats_ntfy_per_sta per_sta[IWL_STATION_COUNT_MAX]; +} __packed; /* STATISTICS_FW_NTFY_OPERATIONAL_API_S_VER_4 */ + /** * struct iwl_system_statistics_part1_notif_oper - part1 stats notification * From 41572ee0746921c84ad98d8fbdf842f21a1c422a Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 12 May 2026 08:22:56 +0300 Subject: [PATCH 061/143] wifi: iwlwifi: mld: support the new statistics APIs This new API adds information about Coexistence. It changes the layout of the notification, adapt the code accordingly. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260512082114.04ccf0f3f2c6.Iaff5ac57a53fe62ea7c1313bc57a81b23b1e2fdf@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/mld/notif.c | 3 +- .../net/wireless/intel/iwlwifi/mld/stats.c | 59 +++++++++++++++---- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index f7aeff61d785..692251257116 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -330,7 +330,8 @@ CMD_VERSIONS(probe_resp_data_notif, CMD_VERSIONS(datapath_monitor_notif, CMD_VER_ENTRY(1, iwl_datapath_monitor_notif)) CMD_VERSIONS(stats_oper_notif, - CMD_VER_ENTRY(3, iwl_system_statistics_notif_oper_v3)) + CMD_VER_ENTRY(3, iwl_system_statistics_notif_oper_v3) + CMD_VER_ENTRY(4, iwl_system_statistics_notif_oper)) CMD_VERSIONS(stats_oper_part1_notif, CMD_VER_ENTRY(4, iwl_system_statistics_part1_notif_oper)) CMD_VERSIONS(bt_coex_notif, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/stats.c b/drivers/net/wireless/intel/iwlwifi/mld/stats.c index 714d66324e9f..6e826797f637 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/stats.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/stats.c @@ -40,13 +40,21 @@ iwl_mld_fill_stats_from_oper_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt, u8 fw_sta_id, struct station_info *sinfo) { - const struct iwl_system_statistics_notif_oper_v3 *notif = - (void *)&pkt->data; - const struct iwl_stats_ntfy_per_sta *per_sta = - ¬if->per_sta[fw_sta_id]; + const struct iwl_stats_ntfy_per_sta *per_sta; struct ieee80211_link_sta *link_sta; struct iwl_mld_link_sta *mld_link_sta; + if (iwl_fw_lookup_notif_ver(mld->fw, STATISTICS_GROUP, + STATISTICS_OPER_NOTIF, 3) >= 4) { + const struct iwl_system_statistics_notif_oper *notif = + (void *)&pkt->data; + per_sta = ¬if->per_sta[fw_sta_id]; + } else { + const struct iwl_system_statistics_notif_oper_v3 *notif = + (void *)&pkt->data; + per_sta = ¬if->per_sta[fw_sta_id]; + } + /* 0 isn't a valid value, but FW might send 0. * In that case, set the latest non-zero value we stored */ @@ -483,7 +491,7 @@ static void iwl_mld_fill_chanctx_stats(struct ieee80211_hw *hw, void *data) { struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx); - const struct iwl_stats_ntfy_per_phy_v1 *per_phy = data; + const struct iwl_stats_ntfy_per_phy *per_phy = data; u32 new_load, old_load; if (WARN_ON(phy->fw_id >= IWL_STATS_MAX_PHY_OPERATIONAL)) @@ -512,7 +520,7 @@ static void iwl_mld_fill_chanctx_stats(struct ieee80211_hw *hw, static void iwl_mld_process_per_phy_stats(struct iwl_mld *mld, - const struct iwl_stats_ntfy_per_phy_v1 *per_phy) + const struct iwl_stats_ntfy_per_phy *per_phy) { ieee80211_iter_chan_contexts_mtx(mld->hw, iwl_mld_fill_chanctx_stats, @@ -523,17 +531,42 @@ iwl_mld_process_per_phy_stats(struct iwl_mld *mld, void iwl_mld_handle_stats_oper_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt) { - const struct iwl_system_statistics_notif_oper_v3 *stats = + struct iwl_system_statistics_notif_oper *_notif __free(kfree) = NULL; + const struct iwl_system_statistics_notif_oper *notif = (void *)&pkt->data; - u32 curr_ts_usec = le32_to_cpu(stats->time_stamp); - BUILD_BUG_ON(ARRAY_SIZE(stats->per_sta) != IWL_STATION_COUNT_MAX); - BUILD_BUG_ON(ARRAY_SIZE(stats->per_link) < + BUILD_BUG_ON(ARRAY_SIZE(notif->per_sta) != IWL_STATION_COUNT_MAX); + BUILD_BUG_ON(ARRAY_SIZE(notif->per_link) < ARRAY_SIZE(mld->fw_id_to_bss_conf)); - iwl_mld_process_per_link_stats(mld, stats->per_link, curr_ts_usec); - iwl_mld_process_per_sta_stats(mld, stats->per_sta); - iwl_mld_process_per_phy_stats(mld, stats->per_phy); + if (iwl_fw_lookup_notif_ver(mld->fw, STATISTICS_GROUP, + STATISTICS_OPER_NOTIF, 3) == 3) { + const struct iwl_system_statistics_notif_oper_v3 *stats = + (void *)&pkt->data; + _notif = kzalloc_obj(*_notif); + + if (!_notif) + return; + + _notif->time_stamp = stats->time_stamp; + for (int i = 0; i < ARRAY_SIZE(_notif->per_link); i++) + _notif->per_link[i] = stats->per_link[i]; + + BUILD_BUG_ON(sizeof(_notif->per_phy[0]) < + sizeof(stats->per_phy[0])); + for (int i = 0; i < ARRAY_SIZE(_notif->per_phy); i++) + memcpy(&_notif->per_phy[i], &stats->per_phy[i], + sizeof(stats->per_phy[i])); + for (int i = 0; i < ARRAY_SIZE(_notif->per_sta); i++) + _notif->per_sta[i] = stats->per_sta[i]; + + notif = _notif; + } + + iwl_mld_process_per_link_stats(mld, notif->per_link, + le32_to_cpu(notif->time_stamp)); + iwl_mld_process_per_sta_stats(mld, notif->per_sta); + iwl_mld_process_per_phy_stats(mld, notif->per_phy); } void iwl_mld_handle_stats_oper_part1_notif(struct iwl_mld *mld, From 857fab1bbe8985f4231aa90aaa34033bb1a5e073 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 12 May 2026 08:22:58 +0300 Subject: [PATCH 062/143] wifi: iwlwifi: advertise UHR capabilities for such devices Advertise UHR capabilities for devices supporting UHR (i.e. devices with some, but not all, PE RFs.) For correct support it's also needed to plumb through the regulatory flags the firmware might have/enforce. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260512082114.ef9ebeb41889.I85b67f4973752bcf62416dabb943b9ddf1e63f16@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/cfg/rf-pe.c | 23 +++++- .../net/wireless/intel/iwlwifi/iwl-config.h | 7 +- .../wireless/intel/iwlwifi/iwl-nvm-parse.c | 77 ++++++++++++------- .../wireless/intel/iwlwifi/iwl-nvm-parse.h | 2 + .../wireless/intel/iwlwifi/iwl-nvm-utils.h | 1 + drivers/net/wireless/intel/iwlwifi/pcie/drv.c | 2 +- 6 files changed, 80 insertions(+), 32 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c index 2c29054ce7b8..c02478b73057 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c @@ -4,7 +4,28 @@ */ #include "iwl-config.h" -/* currently iwl_rf_wh/iwl_rf_wh_160mhz are just defines for the FM ones */ +#define IWL_PE_NVM_VERSION 0x0a1d + +#define IWL_DEVICE_PE \ + .ht_params = { \ + .stbc = true, \ + .ldpc = true, \ + .ht40_bands = BIT(NL80211_BAND_2GHZ) | \ + BIT(NL80211_BAND_5GHZ), \ + }, \ + .led_mode = IWL_LED_RF_STATE, \ + .non_shared_ant = ANT_B, \ + .vht_mu_mimo_supported = true, \ + .uhb_supported = true, \ + .eht_supported = true, \ + .uhr_supported = true, \ + .num_rbds = IWL_NUM_RBDS_EHT, \ + .nvm_ver = IWL_PE_NVM_VERSION, \ + .nvm_type = IWL_NVM_EXT + +const struct iwl_rf_cfg iwl_rf_pe = { + IWL_DEVICE_PE, +}; const char iwl_killer_bn1850w2_name[] = "Killer(R) Wi-Fi 8 BN1850w2 320MHz Wireless Network Adapter (BN201.D2W)"; diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-config.h b/drivers/net/wireless/intel/iwlwifi/iwl-config.h index 5f40cd15e27f..da6d3686e7dd 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-config.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-config.h @@ -416,6 +416,7 @@ struct iwl_mac_cfg { * @nvm_type: see &enum iwl_nvm_type * @uhb_supported: ultra high band channels supported * @eht_supported: EHT supported + * @uhr_supported: UHR supported * @num_rbds: number of receive buffer descriptors to use * (only used for multi-queue capable devices) * @@ -449,7 +450,8 @@ struct iwl_rf_cfg { lp_xtal_workaround:1, vht_mu_mimo_supported:1, uhb_supported:1, - eht_supported:1; + eht_supported:1, + uhr_supported:1; u8 valid_tx_ant; u8 valid_rx_ant; u8 non_shared_ant; @@ -744,7 +746,8 @@ extern const struct iwl_rf_cfg iwl_rf_fm_160mhz; #define iwl_rf_wh iwl_rf_fm #define iwl_rf_wh_160mhz iwl_rf_fm_160mhz extern const struct iwl_rf_cfg iwl_rf_wh_non_eht; -#define iwl_rf_pe iwl_rf_fm +extern const struct iwl_rf_cfg iwl_rf_pe; +#define iwl_rf_pe_no_uhr iwl_rf_fm #endif /* CONFIG_IWLMLD */ #endif /* __IWL_CONFIG_H__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index 455d6e8c7028..38ff6f944add 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -205,28 +205,30 @@ enum iwl_reg_capa_flags_v2 { }; /* GEO_CHANNEL_CAPABILITIES_API_S_VER_2 */ /** - * enum iwl_reg_capa_flags_v4 - global flags applied for the whole regulatory + * enum iwl_reg_capa_flags_v5 - global flags applied for the whole regulatory * domain. - * @REG_CAPA_V4_160MHZ_ALLOWED: 11ac channel with a width of 160Mhz is allowed + * @REG_CAPA_V5_160MHZ_ALLOWED: 11ac channel with a width of 160Mhz is allowed * for this regulatory domain (valid only in 5Ghz). - * @REG_CAPA_V4_80MHZ_ALLOWED: 11ac channel with a width of 80Mhz is allowed + * @REG_CAPA_V5_80MHZ_ALLOWED: 11ac channel with a width of 80Mhz is allowed * for this regulatory domain (valid only in 5Ghz). - * @REG_CAPA_V4_MCS_12_ALLOWED: 11ac with MCS 12 is allowed. - * @REG_CAPA_V4_MCS_13_ALLOWED: 11ac with MCS 13 is allowed. - * @REG_CAPA_V4_11BE_DISABLED: 11be is forbidden for this regulatory domain. - * @REG_CAPA_V4_11AX_DISABLED: 11ax is forbidden for this regulatory domain. - * @REG_CAPA_V4_320MHZ_ALLOWED: 11be channel with a width of 320Mhz is allowed + * @REG_CAPA_V5_MCS_12_ALLOWED: 11ac with MCS 12 is allowed. + * @REG_CAPA_V5_MCS_13_ALLOWED: 11ac with MCS 13 is allowed. + * @REG_CAPA_V5_11BE_DISABLED: 11be is forbidden for this regulatory domain. + * @REG_CAPA_V5_11AX_DISABLED: 11ax is forbidden for this regulatory domain. + * @REG_CAPA_V5_320MHZ_ALLOWED: 11be channel with a width of 320Mhz is allowed * for this regulatory domain (valid only in 5GHz). + * @REG_CAPA_V5_11BN_DISABLED: UHR is not allowed for this regulatory domain */ -enum iwl_reg_capa_flags_v4 { - REG_CAPA_V4_160MHZ_ALLOWED = BIT(3), - REG_CAPA_V4_80MHZ_ALLOWED = BIT(4), - REG_CAPA_V4_MCS_12_ALLOWED = BIT(5), - REG_CAPA_V4_MCS_13_ALLOWED = BIT(6), - REG_CAPA_V4_11BE_DISABLED = BIT(8), - REG_CAPA_V4_11AX_DISABLED = BIT(13), - REG_CAPA_V4_320MHZ_ALLOWED = BIT(16), -}; /* GEO_CHANNEL_CAPABILITIES_API_S_VER_4 */ +enum iwl_reg_capa_flags_v5 { + REG_CAPA_V5_160MHZ_ALLOWED = BIT(3), + REG_CAPA_V5_80MHZ_ALLOWED = BIT(4), + REG_CAPA_V5_MCS_12_ALLOWED = BIT(5), + REG_CAPA_V5_MCS_13_ALLOWED = BIT(6), + REG_CAPA_V5_11BE_DISABLED = BIT(8), + REG_CAPA_V5_11AX_DISABLED = BIT(13), + REG_CAPA_V5_320MHZ_ALLOWED = BIT(16), + REG_CAPA_V5_11BN_DISABLED = BIT(17), +}; /* GEO_CHANNEL_CAPABILITIES_API_S_VER_4, 5 */ /* * API v2 for reg_capa_flags is relevant from version 6 and onwards of the @@ -545,7 +547,7 @@ static const u8 iwl_vendor_caps[] = { 0x00 }; -static const struct ieee80211_sband_iftype_data iwl_he_eht_capa[] = { +static const struct ieee80211_sband_iftype_data iwl_iftype_cap[] = { { .types_mask = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_P2P_CLIENT), @@ -689,6 +691,11 @@ static const struct ieee80211_sband_iftype_data iwl_he_eht_capa[] = { */ .eht_ppe_thres = {0xc1, 0x0e, 0xe0 } }, + .uhr_cap = { + .has_uhr = true, + .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | + IEEE80211_UHR_PHY_CAP_ELR_TX, + }, }, { .types_mask = BIT(NL80211_IFTYPE_AP) | @@ -787,6 +794,11 @@ static const struct ieee80211_sband_iftype_data iwl_he_eht_capa[] = { */ .eht_ppe_thres = {0xc1, 0x0e, 0xe0 } }, + .uhr_cap = { + .has_uhr = true, + .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | + IEEE80211_UHR_PHY_CAP_ELR_TX, + }, }, }; @@ -854,6 +866,9 @@ iwl_nvm_fixup_sband_iftd(struct iwl_trans *trans, fips_enabled) iftype_data->eht_cap.has_eht = false; + if (!data->sku_cap_11bn_enable || !iftype_data->eht_cap.has_eht) + iftype_data->uhr_cap.has_uhr = false; + /* Advertise an A-MPDU exponent extension based on * operating band */ @@ -1022,9 +1037,9 @@ static void iwl_init_he_hw_capab(struct iwl_trans *trans, struct ieee80211_sband_iftype_data *iftype_data; int i; - BUILD_BUG_ON(sizeof(data->iftd.low) != sizeof(iwl_he_eht_capa)); - BUILD_BUG_ON(sizeof(data->iftd.high) != sizeof(iwl_he_eht_capa)); - BUILD_BUG_ON(sizeof(data->iftd.uhb) != sizeof(iwl_he_eht_capa)); + BUILD_BUG_ON(sizeof(data->iftd.low) != sizeof(iwl_iftype_cap)); + BUILD_BUG_ON(sizeof(data->iftd.high) != sizeof(iwl_iftype_cap)); + BUILD_BUG_ON(sizeof(data->iftd.uhb) != sizeof(iwl_iftype_cap)); switch (sband->band) { case NL80211_BAND_2GHZ: @@ -1041,10 +1056,10 @@ static void iwl_init_he_hw_capab(struct iwl_trans *trans, return; } - memcpy(iftype_data, iwl_he_eht_capa, sizeof(iwl_he_eht_capa)); + memcpy(iftype_data, iwl_iftype_cap, sizeof(iwl_iftype_cap)); _ieee80211_set_sband_iftype_data(sband, iftype_data, - ARRAY_SIZE(iwl_he_eht_capa)); + ARRAY_SIZE(iwl_iftype_cap)); for (i = 0; i < sband->n_iftype_data; i++) iwl_nvm_fixup_sband_iftd(trans, data, sband, &iftype_data[i], @@ -1652,6 +1667,9 @@ u32 iwl_nvm_get_regdom_bw_flags(const u16 *nvm_chan, if (reg_capa.disable_11be) flags |= NL80211_RRF_NO_EHT; + if (reg_capa.disable_11bn) + flags |= NL80211_RRF_NO_UHR; + return flags; } EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_nvm_get_regdom_bw_flags); @@ -1662,11 +1680,13 @@ static struct iwl_reg_capa iwl_get_reg_capa(u32 flags, u8 resp_ver) if (resp_ver >= REG_CAPA_V4_RESP_VER) { reg_capa.allow_40mhz = true; - reg_capa.allow_80mhz = flags & REG_CAPA_V4_80MHZ_ALLOWED; - reg_capa.allow_160mhz = flags & REG_CAPA_V4_160MHZ_ALLOWED; - reg_capa.allow_320mhz = flags & REG_CAPA_V4_320MHZ_ALLOWED; - reg_capa.disable_11ax = flags & REG_CAPA_V4_11AX_DISABLED; - reg_capa.disable_11be = flags & REG_CAPA_V4_11BE_DISABLED; + reg_capa.allow_80mhz = flags & REG_CAPA_V5_80MHZ_ALLOWED; + reg_capa.allow_160mhz = flags & REG_CAPA_V5_160MHZ_ALLOWED; + reg_capa.allow_320mhz = flags & REG_CAPA_V5_320MHZ_ALLOWED; + reg_capa.disable_11ax = flags & REG_CAPA_V5_11AX_DISABLED; + reg_capa.disable_11be = flags & REG_CAPA_V5_11BE_DISABLED; + /* can check: was reserved and irrelevant for pre-UHR devices */ + reg_capa.disable_11bn = flags & REG_CAPA_V5_11BN_DISABLED; } else if (resp_ver >= REG_CAPA_V2_RESP_VER) { reg_capa.allow_40mhz = flags & REG_CAPA_V2_40MHZ_ALLOWED; reg_capa.allow_80mhz = flags & REG_CAPA_V2_80MHZ_ALLOWED; @@ -2121,6 +2141,7 @@ struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans, !!(mac_flags & NVM_MAC_SKU_FLAGS_MIMO_DISABLED); if (trans->cfg->eht_supported) nvm->sku_cap_11be_enable = true; + nvm->sku_cap_11bn_enable = trans->cfg->uhr_supported; /* Initialize PHY sku data */ nvm->valid_tx_ant = (u8)le32_to_cpu(rsp->phy_sku.tx_chains); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h index 12f28bb0e859..e676d7c2d6cc 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h @@ -35,6 +35,7 @@ enum iwl_nvm_sbands_flags { * for this regulatory domain (valid only in 6 Ghz). * @disable_11ax: 11ax is forbidden for this regulatory domain. * @disable_11be: 11be is forbidden for this regulatory domain. + * @disable_11bn: UHR/11bn is not allowed for this regulatory domain */ struct iwl_reg_capa { bool allow_40mhz; @@ -43,6 +44,7 @@ struct iwl_reg_capa { bool allow_320mhz; bool disable_11ax; bool disable_11be; + bool disable_11bn; }; /** diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h index e0c31f328a6c..52d35b73ed74 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h @@ -32,6 +32,7 @@ struct iwl_nvm_data { bool sku_cap_ipan_enable; bool sku_cap_mimo_disabled; bool sku_cap_11be_enable; + bool sku_cap_11bn_enable; u16 radio_cfg_type; u8 radio_cfg_step; diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c index dc99e7ac4726..78482db732c1 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -1069,7 +1069,7 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct iwl_dev_info iwl_dev_info_table[] = { /* PE RF */ IWL_DEV_INFO(iwl_rf_pe, iwl_bn201_name, RF_TYPE(PE)), - IWL_DEV_INFO(iwl_rf_pe, iwl_be223_name, RF_TYPE(PE), + IWL_DEV_INFO(iwl_rf_pe_no_uhr, iwl_be223_name, RF_TYPE(PE), SUBDEV_MASKED(0x0524, 0xFFF)), IWL_DEV_INFO(iwl_rf_pe, iwl_bn203_name, RF_TYPE(PE), SUBDEV_MASKED(0x0324, 0xFFF)), From fd2484732db163889d7b7a1732eb83c60afd934b Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 12 May 2026 08:22:59 +0300 Subject: [PATCH 063/143] wifi: iwlwifi: remove nvm_ver for devices that don't need it This was needed only to check the NVM for devices that had a specific firmware image to run the initial calibrations. Remove this field from newer devices that no longer have a specific image for those. Signed-off-by: Emmanuel Grumbach Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260512082114.261b04a1b8f2.I5834a3efd0ae7e601a02cc0582287ced405a0aef@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c | 6 +----- drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c | 4 ---- drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c | 6 +----- drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c | 5 +---- drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c | 6 +----- drivers/net/wireless/intel/iwlwifi/cfg/sc.c | 3 --- 6 files changed, 4 insertions(+), 26 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c index ad2536f53084..f482536e6b5e 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include "iwl-config.h" @@ -11,9 +11,6 @@ #define IWL_GL_B_FM_B_FW_PRE "iwlwifi-gl-b0-fm-b0" #define IWL_GL_C_FM_C_FW_PRE "iwlwifi-gl-c0-fm-c0" -/* NVM versions */ -#define IWL_FM_NVM_VERSION 0x0a1d - #define IWL_DEVICE_FM \ .ht_params = { \ .stbc = true, \ @@ -27,7 +24,6 @@ .uhb_supported = true, \ .eht_supported = true, \ .num_rbds = IWL_NUM_RBDS_EHT, \ - .nvm_ver = IWL_FM_NVM_VERSION, \ .nvm_type = IWL_NVM_EXT const struct iwl_rf_cfg iwl_rf_fm = { diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c index c16cda087a7c..0c20fe619f93 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c @@ -35,9 +35,6 @@ #define IWL_SC_A_GF4_A_MODULE_FIRMWARE(api) \ IWL_SC_A_GF4_A_FW_PRE "-" __stringify(api) ".ucode" -/* NVM versions */ -#define IWL_GF_NVM_VERSION 0x0a1d - const struct iwl_rf_cfg iwl_rf_gf = { .uhb_supported = true, .led_mode = IWL_LED_RF_STATE, @@ -49,7 +46,6 @@ const struct iwl_rf_cfg iwl_rf_gf = { .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ), }, - .nvm_ver = IWL_GF_NVM_VERSION, .nvm_type = IWL_NVM_EXT, .num_rbds = IWL_NUM_RBDS_HE, .ucode_api_min = IWL_GF_UCODE_API_MIN, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c index 6cf187d92dbf..3c3a8d5702d8 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include "iwl-config.h" @@ -40,9 +40,6 @@ #define IWL_SC_A_HR_B_FW_MODULE_FIRMWARE(api) \ IWL_SC_A_HR_B_FW_PRE "-" __stringify(api) ".ucode" -/* NVM versions */ -#define IWL_HR_NVM_VERSION 0x0a1d - #define IWL_DEVICE_HR \ .led_mode = IWL_LED_RF_STATE, \ .non_shared_ant = ANT_B, \ @@ -54,7 +51,6 @@ BIT(NL80211_BAND_5GHZ), \ }, \ .num_rbds = IWL_NUM_RBDS_HE, \ - .nvm_ver = IWL_HR_NVM_VERSION, \ .nvm_type = IWL_NVM_EXT, \ .ucode_api_min = IWL_HR_UCODE_API_MIN, \ .ucode_api_max = IWL_HR_UCODE_API_MAX diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c index c02478b73057..7a04cb120b1b 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c @@ -1,11 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2025 Intel Corporation + * Copyright (C) 2025-2026 Intel Corporation */ #include "iwl-config.h" -#define IWL_PE_NVM_VERSION 0x0a1d - #define IWL_DEVICE_PE \ .ht_params = { \ .stbc = true, \ @@ -20,7 +18,6 @@ .eht_supported = true, \ .uhr_supported = true, \ .num_rbds = IWL_NUM_RBDS_EHT, \ - .nvm_ver = IWL_PE_NVM_VERSION, \ .nvm_type = IWL_NVM_EXT const struct iwl_rf_cfg iwl_rf_pe = { diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c index b5803ea1eb78..fa63a9a01264 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c @@ -1,12 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2025 Intel Corporation + * Copyright (C) 2025-2026 Intel Corporation */ #include "iwl-config.h" -/* NVM versions */ -#define IWL_WH_NVM_VERSION 0x0a1d - #define IWL_DEVICE_WH \ .ht_params = { \ .stbc = true, \ @@ -19,7 +16,6 @@ .vht_mu_mimo_supported = true, \ .uhb_supported = true, \ .num_rbds = IWL_NUM_RBDS_EHT, \ - .nvm_ver = IWL_WH_NVM_VERSION, \ .nvm_type = IWL_NVM_EXT /* currently iwl_rf_wh/iwl_rf_wh_160mhz are just defines for the FM ones */ diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index 8e6efde3c64b..4532d31d5f8b 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -15,9 +15,6 @@ /* Lowest firmware API version supported */ #define IWL_SC_UCODE_API_MIN 100 -/* NVM versions */ -#define IWL_SC_NVM_VERSION 0x0a1d - /* Memory offsets and lengths */ #define IWL_SC_SMEM_OFFSET 0x400000 #define IWL_SC_SMEM_LEN 0xD0000 From 3aaafd34c652c038c8ed2a637f301173ca48f18b Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 12 May 2026 08:23:00 +0300 Subject: [PATCH 064/143] wifi: iwlwifi: print FSEQ sha1 in addition to version The version isn't all that useful, however the sha1 will identify the exact source. Print that as well. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260512082114.7239e0b90611.I2a833b0354c5512189e74d3a296c55546444a360@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c index d5ded4d3a30b..83a63be90e46 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2005-2014, 2018-2025 Intel Corporation + * Copyright (C) 2005-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -1294,8 +1294,9 @@ static int iwl_parse_tlv_firmware(struct iwl_drv *drv, if (tlv_len != sizeof(*fseq_ver)) goto invalid_tlv_len; - IWL_DEBUG_INFO(drv, "TLV_FW_FSEQ_VERSION: %.32s\n", - fseq_ver->version); + IWL_DEBUG_INFO(drv, + "TLV_FW_FSEQ_VERSION: %.32s (sha1: %.20s)\n", + fseq_ver->version, fseq_ver->sha1); } break; case IWL_UCODE_TLV_FW_NUM_STATIONS: From 613880b1e3a5048562b0882017913862a6ca4e95 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Tue, 12 May 2026 08:23:01 +0300 Subject: [PATCH 065/143] wifi: iwlwifi: support a TLV indicating num of mgmt mcast keys FW has a limitation of how many multicast management keys it supports. Until today we just assumed this limitation. But now as it is changing, due to NAN, we need a clear indication from the FW so we know how many we can install. Read and store this indication from the FW's TLV. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260512082114.f171962abd2e.Ic678616c7d574de257e5923d56258043a5261674@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/api/sta.h | 3 +-- drivers/net/wireless/intel/iwlwifi/fw/file.h | 3 ++- drivers/net/wireless/intel/iwlwifi/fw/img.h | 3 ++- drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 7 +++++++ drivers/net/wireless/intel/iwlwifi/mld/key.c | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/sta.h b/drivers/net/wireless/intel/iwlwifi/fw/api/sta.h index e6f9abdfa546..3e62a458b131 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/sta.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/sta.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2012-2014, 2018-2021, 2023, 2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2021, 2023, 2025-2026 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -191,7 +191,6 @@ enum iwl_sta_sleep_flag { #define STA_KEY_IDX_INVALID (0xff) #define STA_KEY_MAX_DATA_KEY_NUM (4) #define IWL_MAX_GLOBAL_KEYS (4) -#define IWL_MAX_NUM_IGTKS 2 #define STA_KEY_LEN_WEP40 (5) #define STA_KEY_LEN_WEP104 (13) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/file.h b/drivers/net/wireless/intel/iwlwifi/fw/file.h index f7a6f21267e9..68ddd99a9f7d 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/file.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/file.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2008-2014, 2018-2024 Intel Corporation + * Copyright (C) 2008-2014, 2018-2024, 2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -111,6 +111,7 @@ enum iwl_ucode_tlv_type { IWL_UCODE_TLV_FW_NUM_STATIONS = IWL_UCODE_TLV_CONST_BASE + 0, IWL_UCODE_TLV_FW_NUM_LINKS = IWL_UCODE_TLV_CONST_BASE + 1, IWL_UCODE_TLV_FW_NUM_BEACONS = IWL_UCODE_TLV_CONST_BASE + 2, + IWL_UCODE_TLV_FW_NUM_MCAST_KEY_ENTRIES = IWL_UCODE_TLV_CONST_BASE + 3, IWL_UCODE_TLV_TYPE_DEBUG_INFO = IWL_UCODE_TLV_DEBUG_BASE + 0, IWL_UCODE_TLV_TYPE_BUFFER_ALLOCATION = IWL_UCODE_TLV_DEBUG_BASE + 1, diff --git a/drivers/net/wireless/intel/iwlwifi/fw/img.h b/drivers/net/wireless/intel/iwlwifi/fw/img.h index 94113d1db8e1..75b1344f6cbe 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/img.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/img.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2005-2014, 2018-2024 Intel Corporation + * Copyright (C) 2005-2014, 2018-2024, 2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016 Intel Deutschland GmbH */ @@ -53,6 +53,7 @@ struct iwl_ucode_capabilities { u32 num_stations; u32 num_links; u32 num_beacons; + u32 num_mcast_key_entries; DECLARE_BITMAP(_api, NUM_IWL_UCODE_TLV_API); DECLARE_BITMAP(_capa, NUM_IWL_UCODE_TLV_CAPA); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c index 83a63be90e46..488524529538 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c @@ -1331,6 +1331,12 @@ static int iwl_parse_tlv_firmware(struct iwl_drv *drv, capa->num_beacons = le32_to_cpup((const __le32 *)tlv_data); break; + case IWL_UCODE_TLV_FW_NUM_MCAST_KEY_ENTRIES: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + capa->num_mcast_key_entries = + le32_to_cpup((const __le32 *)tlv_data); + break; case IWL_UCODE_TLV_UMAC_DEBUG_ADDRS: { const struct iwl_umac_debug_addrs *dbg_ptrs = (const void *)tlv_data; @@ -1641,6 +1647,7 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context) fw->ucode_capa.n_scan_channels = IWL_DEFAULT_SCAN_CHANNELS; fw->ucode_capa.num_stations = IWL_STATION_COUNT_MAX; fw->ucode_capa.num_beacons = 1; + fw->ucode_capa.num_mcast_key_entries = 2; /* dump all fw memory areas by default */ fw->dbg.dump_mask = 0xffffffff; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/key.c b/drivers/net/wireless/intel/iwlwifi/mld/key.c index 944d5487c110..151e35b851f7 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/key.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/key.c @@ -259,7 +259,7 @@ int iwl_mld_add_key(struct iwl_mld *mld, igtk_ptr = iwl_mld_get_igtk_ptr(vif, sta, key); if (igtk_ptr) { - if (mld->num_igtks == IWL_MAX_NUM_IGTKS) + if (mld->num_igtks == mld->fw->ucode_capa.num_mcast_key_entries) return -EOPNOTSUPP; if (*igtk_ptr) { From 9a007d2d9fe48cdd2f5381ff2e82fa93f1ae7692 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Tue, 12 May 2026 08:23:02 +0300 Subject: [PATCH 066/143] wifi: iwlwifi: mark that we support iwl_rx_mpdu_desc version 7 and 8 Mark the driver struct as matching RX_MPDU_RES_START_API_S_VER_7 and RX_MPDU_RES_START_API_S_VER_8 Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260512082114.3604e2111b27.I1f44475b56e084ee4aaeaed11b8a4fe2d27bed0c@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/api/rx.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h index aa4e5e9d4d4e..699343cf0279 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h @@ -735,6 +735,8 @@ struct iwl_rx_mpdu_desc { * RX_MPDU_RES_START_API_S_VER_4, * RX_MPDU_RES_START_API_S_VER_5, * RX_MPDU_RES_START_API_S_VER_6 + * RX_MPDU_RES_START_API_S_VER_7 + * RX_MPDU_RES_START_API_S_VER_8 */ #define IWL_RX_DESC_SIZE_V1 offsetofend(struct iwl_rx_mpdu_desc, v1) From 982b8a36c04a04829c0997641e3d501c7df12902 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Tue, 12 May 2026 08:23:03 +0300 Subject: [PATCH 067/143] wifi: iwlwifi: stop supporting cores 97 to 100 Remove support from cores 97 to 100, including. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260512082114.3dc9251c69ef.Icc027dc533f06ac007abfa0356774948ffdfaf3d@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/cfg/bz.c | 6 +++--- drivers/net/wireless/intel/iwlwifi/cfg/dr.c | 6 +++--- drivers/net/wireless/intel/iwlwifi/cfg/sc.c | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index 8b3f15899396..c060b12cc952 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -12,8 +12,8 @@ /* Highest firmware core release supported */ #define IWL_BZ_UCODE_CORE_MAX 103 -/* Lowest firmware API version supported */ -#define IWL_BZ_UCODE_API_MIN 100 +/* Lowest firmware core release supported */ +#define IWL_BZ_UCODE_CORE_MIN 101 /* Memory offsets and lengths */ #define IWL_BZ_SMEM_OFFSET 0x400000 @@ -69,7 +69,7 @@ static const struct iwl_family_base_params iwl_bz_base = { }, .features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM, .ucode_api_max = ENCODE_CORE_AS_API(IWL_BZ_UCODE_CORE_MAX), - .ucode_api_min = IWL_BZ_UCODE_API_MIN, + .ucode_api_min = ENCODE_CORE_AS_API(IWL_BZ_UCODE_CORE_MIN), }; const struct iwl_mac_cfg iwl_bz_mac_cfg = { diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c index a8f2ac688d31..3ccbb21276c1 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c @@ -11,8 +11,8 @@ /* Highest firmware core release supported */ #define IWL_DR_UCODE_CORE_MAX 103 -/* Lowest firmware API version supported */ -#define IWL_DR_UCODE_API_MIN 100 +/* Lowest firmware core release supported */ +#define IWL_DR_UCODE_CORE_MIN 101 /* Memory offsets and lengths */ #define IWL_DR_SMEM_OFFSET 0x400000 @@ -70,7 +70,7 @@ static const struct iwl_family_base_params iwl_dr_base = { }, .features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM, .ucode_api_max = ENCODE_CORE_AS_API(IWL_DR_UCODE_CORE_MAX), - .ucode_api_min = IWL_DR_UCODE_API_MIN, + .ucode_api_min = ENCODE_CORE_AS_API(IWL_DR_UCODE_CORE_MIN), }; const struct iwl_mac_cfg iwl_dr_mac_cfg = { diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index 4532d31d5f8b..04c726e8c283 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -12,8 +12,8 @@ /* Highest firmware core release supported */ #define IWL_SC_UCODE_CORE_MAX 103 -/* Lowest firmware API version supported */ -#define IWL_SC_UCODE_API_MIN 100 +/* Lowest firmware core release supported */ +#define IWL_SC_UCODE_CORE_MIN 101 /* Memory offsets and lengths */ #define IWL_SC_SMEM_OFFSET 0x400000 @@ -75,7 +75,7 @@ static const struct iwl_family_base_params iwl_sc_base = { }, .features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM, .ucode_api_max = ENCODE_CORE_AS_API(IWL_SC_UCODE_CORE_MAX), - .ucode_api_min = IWL_SC_UCODE_API_MIN, + .ucode_api_min = ENCODE_CORE_AS_API(IWL_SC_UCODE_CORE_MIN), }; const struct iwl_mac_cfg iwl_sc_mac_cfg = { From 27f4469f78344e2db30e58b388ca0c399455c4da Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Tue, 12 May 2026 08:23:04 +0300 Subject: [PATCH 068/143] wifi: iwlwifi: mld: stop supporting iwl_compressed_ba_notif version 5 and 6 The oldest core that devices that load iwlmld op mode are supporting is core 101. Core 101 has version 7 of iwl_compressed_ba_notif, so earlier versions are no longer needed. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260512082114.59e41f7e1e43.I13572fed4393935c31fbe5c565be9f385bc0da30@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/notif.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index 692251257116..fc6550bea155 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -307,8 +307,6 @@ CMD_VERSIONS(tx_resp_notif, CMD_VER_ENTRY(8, iwl_tx_resp) CMD_VER_ENTRY(9, iwl_tx_resp)) CMD_VERSIONS(compressed_ba_notif, - CMD_VER_ENTRY(5, iwl_compressed_ba_notif) - CMD_VER_ENTRY(6, iwl_compressed_ba_notif) CMD_VER_ENTRY(7, iwl_compressed_ba_notif)) CMD_VERSIONS(tlc_notif, CMD_VER_ENTRY(3, iwl_tlc_update_notif) From e11f9d97957c316919c6da402033d0da200194c4 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Tue, 12 May 2026 08:23:05 +0300 Subject: [PATCH 069/143] wifi: iwlwifi: mld: stop supporting MAC_PM_POWER_TABLE version 1 The oldest core that iwmld loads is now 101, which supports version 2, so remove version 1. This allows to remove the smps workaround. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260512082114.e42cff396ea8.I9175bf4aaba0c846fbea54c9bede4d18ad1c252b@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/mld/iface.h | 2 -- .../net/wireless/intel/iwlwifi/mld/mac80211.c | 28 +--------------- .../net/wireless/intel/iwlwifi/mld/power.c | 33 ++----------------- 3 files changed, 4 insertions(+), 59 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.h b/drivers/net/wireless/intel/iwlwifi/mld/iface.h index 147ff6787ed9..71c794204475 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h @@ -138,7 +138,6 @@ struct iwl_mld_emlsr { * @beacon_inject_active: indicates an active debugfs beacon ie injection * @low_latency_causes: bit flags, indicating the causes for low-latency, * see @iwl_mld_low_latency_cause. - * @ps_disabled: indicates that PS is disabled for this interface * @last_link_activation_time: last time a link was activated, for * deferring MLO scans (to make them more reliable) * @mld: pointer to the mld structure. @@ -171,7 +170,6 @@ struct iwl_mld_vif { bool beacon_inject_active; #endif u8 low_latency_causes; - bool ps_disabled; time64_t last_link_activation_time; ); /* And here fields that survive a fw restart */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 8856f96a9760..f37a365a87a4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -1368,28 +1368,6 @@ iwl_mld_mac80211_link_info_changed(struct ieee80211_hw *hw, iwl_mld_set_tx_power(mld, link_conf, link_conf->txpower); } -static void -iwl_mld_smps_workaround(struct iwl_mld *mld, struct ieee80211_vif *vif, bool enable) -{ - struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); - bool workaround_required = - iwl_fw_lookup_cmd_ver(mld->fw, MAC_PM_POWER_TABLE, 0) < 2; - - if (!workaround_required) - return; - - /* Send the device-level power commands since the - * firmware checks the POWER_TABLE_CMD's POWER_SAVE_EN bit to - * determine SMPS mode. - */ - if (mld_vif->ps_disabled == !enable) - return; - - mld_vif->ps_disabled = !enable; - - iwl_mld_update_device_power(mld, false); -} - static void iwl_mld_mac80211_vif_cfg_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, @@ -1427,10 +1405,8 @@ void iwl_mld_mac80211_vif_cfg_changed(struct ieee80211_hw *hw, } } - if (changes & BSS_CHANGED_PS) { - iwl_mld_smps_workaround(mld, vif, vif->cfg.ps); + if (changes & BSS_CHANGED_PS) iwl_mld_update_mac_power(mld, vif, false); - } /* TODO: task=MLO BSS_CHANGED_MLD_VALID_LINKS/CHANGED_MLD_TTLM */ } @@ -1837,7 +1813,6 @@ static int iwl_mld_move_sta_state_up(struct iwl_mld *mld, FW_CTXT_ACTION_MODIFY); if (ret) return ret; - iwl_mld_smps_workaround(mld, vif, vif->cfg.ps); } /* MFP is set by default before the station is authorized. @@ -1882,7 +1857,6 @@ static int iwl_mld_move_sta_state_down(struct iwl_mld *mld, &mld_vif->mlo_scan_start_wk); iwl_mld_reset_cca_40mhz_workaround(mld, vif); - iwl_mld_smps_workaround(mld, vif, true); } /* once we move into assoc state, need to update the FW to diff --git a/drivers/net/wireless/intel/iwlwifi/mld/power.c b/drivers/net/wireless/intel/iwlwifi/mld/power.c index 49b0d9f8f865..fe71da8b9c89 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/power.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/power.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 @@ -11,36 +11,11 @@ #include "link.h" #include "constants.h" -static void iwl_mld_vif_ps_iterator(void *data, u8 *mac, - struct ieee80211_vif *vif) -{ - bool *ps_enable = (bool *)data; - struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); - - if (vif->type != NL80211_IFTYPE_STATION) - return; - - *ps_enable &= !mld_vif->ps_disabled; -} - int iwl_mld_update_device_power(struct iwl_mld *mld, bool d3) { struct iwl_device_power_cmd cmd = {}; - bool enable_ps = false; - if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) { - enable_ps = true; - - /* Disable power save if any STA interface has - * power save turned off - */ - ieee80211_iterate_active_interfaces_mtx(mld->hw, - IEEE80211_IFACE_ITER_NORMAL, - iwl_mld_vif_ps_iterator, - &enable_ps); - } - - if (enable_ps) + if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) cmd.flags |= cpu_to_le16(DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK); @@ -252,9 +227,7 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld, return; cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); - - if (iwl_fw_lookup_cmd_ver(mld->fw, MAC_PM_POWER_TABLE, 0) >= 2) - cmd->flags |= cpu_to_le16(POWER_FLAGS_ENABLE_SMPS_MSK); + cmd->flags |= cpu_to_le16(POWER_FLAGS_ENABLE_SMPS_MSK); /* firmware supports LPRX for beacons at rate 1 Mbps or 6 Mbps only */ if (link_conf->beacon_rate && From 288c5106f387b1ea865db99dc8fd41649051979a Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Tue, 12 May 2026 08:23:06 +0300 Subject: [PATCH 070/143] wifi: iwlwifi: mld: stop supporting TLC_MNG_UPDATE_NTFY_API_S_VER_3 The oldest core iwlmld support is 101, which has version 4. Remove version 3 Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260512082114.36dac3f3589c.I8aee18f323c5f3bbcc02dfc2e0c08be6b8943986@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/notif.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index fc6550bea155..4e9f3768c381 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -309,7 +309,6 @@ CMD_VERSIONS(tx_resp_notif, CMD_VERSIONS(compressed_ba_notif, CMD_VER_ENTRY(7, iwl_compressed_ba_notif)) CMD_VERSIONS(tlc_notif, - CMD_VER_ENTRY(3, iwl_tlc_update_notif) CMD_VER_ENTRY(4, iwl_tlc_update_notif)) CMD_VERSIONS(mu_mimo_grp_notif, CMD_VER_ENTRY(1, iwl_mu_group_mgmt_notif)) From b4049b093a6f99d06f97c07782b11848b429f1d4 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Tue, 12 May 2026 08:23:07 +0300 Subject: [PATCH 071/143] wifi: iwlwifi: mld: stop supporting rate_n_flags version 2 Now as the oldest fw we support is core 101, and it supports only version 3, remove version 2. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260512082114.39e460bc8235.I4cd02612a68a3f183d51f428569448670bd19d9b@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/debugfs.c | 3 +-- .../net/wireless/intel/iwlwifi/mld/mac80211.c | 16 ---------------- drivers/net/wireless/intel/iwlwifi/mld/mld.c | 3 --- drivers/net/wireless/intel/iwlwifi/mld/mld.h | 5 +---- drivers/net/wireless/intel/iwlwifi/mld/rx.c | 3 +-- drivers/net/wireless/intel/iwlwifi/mld/tlc.c | 4 +--- drivers/net/wireless/intel/iwlwifi/mld/tx.c | 10 ++++------ 7 files changed, 8 insertions(+), 36 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c b/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c index b05b58eb1281..9ec8ddce0c38 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/debugfs.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 "mld.h" @@ -1035,7 +1035,6 @@ static ssize_t _iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf, /* input is in FW format (v2 or v3) so convert to v3 */ rate = iwl_v3_rate_from_v2_v3(cpu_to_le32(rate), v3); - rate = le32_to_cpu(iwl_v3_rate_to_v2_v3(rate, mld->fw_rates_ver_3)); ret = iwl_mld_send_tlc_dhc(mld, fw_sta_id, partial ? IWL_TLC_DEBUG_PARTIAL_FIXED_RATE : diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index f37a365a87a4..4315b1b2b36b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -455,22 +455,6 @@ static void iwl_mac_hw_set_misc(struct iwl_mld *mld) static int iwl_mld_hw_verify_preconditions(struct iwl_mld *mld) { - int ratecheck; - - /* check for rates version 3 */ - ratecheck = - (iwl_fw_lookup_cmd_ver(mld->fw, TX_CMD, 0) >= 11) + - (iwl_fw_lookup_notif_ver(mld->fw, DATA_PATH_GROUP, - TLC_MNG_UPDATE_NOTIF, 0) >= 4) + - (iwl_fw_lookup_notif_ver(mld->fw, LEGACY_GROUP, - REPLY_RX_MPDU_CMD, 0) >= 6) + - (iwl_fw_lookup_notif_ver(mld->fw, LONG_GROUP, TX_CMD, 0) >= 9); - - if (ratecheck != 0 && ratecheck != 4) { - IWL_ERR(mld, "Firmware has inconsistent rates\n"); - return -EINVAL; - } - /* 11ax is expected to be enabled for all supported devices */ if (WARN_ON(!mld->nvm_data->sku_cap_11ax_enable)) return -EINVAL; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index dfd4798c103a..9a3b768fce7a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -413,9 +413,6 @@ iwl_op_mode_mld_start(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg, iwl_construct_mld(mld, trans, cfg, fw, hw, dbgfs_dir); - /* we'll verify later it matches between commands */ - mld->fw_rates_ver_3 = iwl_fw_lookup_cmd_ver(mld->fw, TX_CMD, 0) >= 11; - iwl_mld_construct_fw_runtime(mld, trans, fw, dbgfs_dir); iwl_mld_get_bios_tables(mld); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.h b/drivers/net/wireless/intel/iwlwifi/mld/mld.h index 69da3c346394..1a59b3e4014d 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.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_h__ #define __iwl_mld_h__ @@ -189,7 +189,6 @@ * TX rate_n_flags for non-STA mgmt frames (toggles on every TX failure). * @set_tx_ant: stores the last TX antenna bitmask set by user space (if any) * @set_rx_ant: stores the last RX antenna bitmask set by user space (if any) - * @fw_rates_ver_3: FW rates are in version 3 * @low_latency: low-latency manager. * @tzone: thermal zone device's data * @cooling_dev: cooling device's related data @@ -299,8 +298,6 @@ struct iwl_mld { u8 set_tx_ant; u8 set_rx_ant; - bool fw_rates_ver_3; - struct iwl_mld_low_latency low_latency; bool ibss_manager; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c index a9f08f984633..01603dc07f0a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c @@ -39,8 +39,7 @@ iwl_mld_fill_phy_data_from_mpdu(struct iwl_mld *mld, } phy_data->phy_info = le16_to_cpu(desc->phy_info); - phy_data->rate_n_flags = iwl_v3_rate_from_v2_v3(desc->v3.rate_n_flags, - mld->fw_rates_ver_3); + phy_data->rate_n_flags = le32_to_cpu(desc->v3.rate_n_flags); phy_data->gp2_on_air_rise = le32_to_cpu(desc->v3.gp2_on_air_rise); phy_data->energy_a = desc->v3.energy_a; phy_data->energy_b = desc->v3.energy_b; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c index 7db56eefc7df..fb68d8810e14 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c @@ -956,9 +956,7 @@ void iwl_mld_handle_tlc_notif(struct iwl_mld *mld, if (WARN_ON(!mld_link_sta)) return; - mld_link_sta->last_rate_n_flags = - iwl_v3_rate_from_v2_v3(notif->rate, - mld->fw_rates_ver_3); + mld_link_sta->last_rate_n_flags = le32_to_cpu(notif->rate); rs_pretty_print_rate(pretty_rate, sizeof(pretty_rate), mld_link_sta->last_rate_n_flags); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c index dec8ecd6b805..636d24633e57 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c @@ -494,7 +494,7 @@ static __le32 iwl_mld_get_tx_rate_n_flags(struct iwl_mld *mld, rate = iwl_mld_mac80211_rate_idx_to_fw(mld, info, -1) | iwl_mld_get_tx_ant(mld, info, sta, fc); - return iwl_v3_rate_to_v2_v3(rate, mld->fw_rates_ver_3); + return cpu_to_le32(rate); } static void @@ -1012,14 +1012,11 @@ void iwl_mld_tx_from_txq(struct iwl_mld *mld, struct ieee80211_txq *txq) rcu_read_unlock(); } -static void iwl_mld_hwrate_to_tx_rate(struct iwl_mld *mld, - __le32 rate_n_flags_fw, +static void iwl_mld_hwrate_to_tx_rate(u32 rate_n_flags, struct ieee80211_tx_info *info) { enum nl80211_band band = info->band; struct ieee80211_tx_rate *tx_rate = &info->status.rates[0]; - u32 rate_n_flags = iwl_v3_rate_from_v2_v3(rate_n_flags_fw, - mld->fw_rates_ver_3); u32 sgi = rate_n_flags & RATE_MCS_SGI_MSK; u32 chan_width = rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK; u32 format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; @@ -1150,7 +1147,8 @@ void iwl_mld_handle_tx_resp_notif(struct iwl_mld *mld, iwl_dbg_tlv_time_point(&mld->fwrt, tp, NULL); } - iwl_mld_hwrate_to_tx_rate(mld, tx_resp->initial_rate, info); + iwl_mld_hwrate_to_tx_rate(le32_to_cpu(tx_resp->initial_rate), + info); if (likely(!iwl_mld_time_sync_frame(mld, skb, hdr->addr1))) ieee80211_tx_status_skb(mld->hw, skb); From e2323929a68af099481cf13dd37038fa12647bd4 Mon Sep 17 00:00:00 2001 From: Aaron Katzin Date: Tue, 12 May 2026 22:34:27 +0300 Subject: [PATCH 072/143] wifi: iwlwifi: pcie: add debug print for resume flow if powered off Log when the resume flow identifies based on the scratch register that the device was powered off. Signed-off-by: Aaron Katzin Reviewed-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260512222731.42623e43fde7.Ibcd656ca845828ce6f2358f9ef80e1ddf03f8f59@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/drv.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c index 78482db732c1..fdafbad506a7 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -1239,8 +1239,12 @@ static int _iwl_pci_resume(struct device *device, bool restore) u32 scratch = iwl_read32(trans, CSR_FUNC_SCRATCH); if (!(scratch & CSR_FUNC_SCRATCH_POWER_OFF_MASK) || - scratch == ~0U) + scratch == ~0U) { device_was_powered_off = true; + IWL_DEBUG_WOWLAN(trans, + "Scratch 0x%08x indicates device was powered off\n", + scratch); + } } else { /* * bh are re-enabled by iwl_trans_pcie_release_nic_access, From 764a3a1b421f8d4c53c8269bab0decf511acd4f0 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 12 May 2026 22:34:28 +0300 Subject: [PATCH 073/143] wifi: iwlwifi: tighten flags in debugfs command sending The only flags that could reasonably be used here are CMD_WANT_SKB and CMD_ASYNC, CMD_SEND_IN_RFKILL doesn't really make sense and CMD_BLOCK_TXQS just triggers a warning, as does CMD_WANT_SKB | CMD_ASYNC. Clear CMD_WANT_SKB since the response SKB isn't used anyway, and refuse flags other than CMD_ASYNC to avoid the warnings or other issues. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260512222731.71b8cda9c4e8.I0cccfd67675989b48d003833f5813c8fbded45a6@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/debugfs.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c b/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c index ddee7c2deb36..f06978d5b5ee 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2024 Intel Corporation + * Copyright (C) 2012-2014, 2018-2024, 2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -275,16 +275,19 @@ static ssize_t iwl_dbgfs_send_hcmd_write(struct iwl_fw_runtime *fwrt, char *buf, goto out; } + /* ignore this flag, we cannot use the response */ + hcmd.flags &= ~CMD_WANT_SKB; + /* reject flags other than async, they cannot be used this way */ + if (hcmd.flags & ~CMD_ASYNC) { + ret = -EINVAL; + goto out; + } + if (fwrt->ops && fwrt->ops->send_hcmd) ret = fwrt->ops->send_hcmd(fwrt->ops_ctx, &hcmd); else ret = -EPERM; - if (ret < 0) - goto out; - - if (hcmd.flags & CMD_WANT_SKB) - iwl_free_resp(&hcmd); out: kfree(data); return ret ?: count; From bc863c23d8b57d7ccd687e820f0527bda993ba0c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 12 May 2026 22:34:29 +0300 Subject: [PATCH 074/143] wifi: iwlwifi: define new FSEQ TLV with MAC ID Some firmware files can be used for different MACs, for example for sc2/sc2f, yet might have different FSEQ versions. The files will then contain multiple bigger FSEQ TLVs indicating the MAC ID in addition to the version. For now, since we don't parse this, define only the new format. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260512222731.f31ffbff36b0.I227e5c94d4da79a32058d71539b190384caba03e@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/file.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/file.h b/drivers/net/wireless/intel/iwlwifi/fw/file.h index 68ddd99a9f7d..197c88c25f72 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/file.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/file.h @@ -1064,10 +1064,15 @@ struct iwl_fw_dump_exclude { __le32 addr, size; }; -struct iwl_fw_fseq_bin_version { +struct iwl_fw_fseq_bin_version_v1 { __le32 major, minor; }; /* FW_TLV_FSEQ_BIN_VERSION_S */ +struct iwl_fw_fseq_bin_version { + /* rf_id is currently unused and always zero */ + __le32 mac_id, rf_id, major, minor; +}; /* FW_TLV_FSEQ_BIN_VERSION_S */ + static inline size_t _iwl_tlv_array_len(const struct iwl_ucode_tlv *tlv, size_t fixed_size, size_t var_size) { From 82aaa2d8651f6c9d842c947f40bfdd987b1e0744 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 12 May 2026 22:34:30 +0300 Subject: [PATCH 075/143] wifi: iwlwifi: set state to NO_FW on reset On debugfs reset, set the transport FW state to NO_FW so that the restart won't attempt to send commands, which of course fails because the FW was killed during the error dump. Use iwl_trans_fw_error() now since that's effectively the same as the old code plus setting the state. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260512222731.cdebf264f5e1.Ia31783c74df0d4b66816a1b34d3771b9a717048b@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c index 9f4c99dca195..e3603571bdd9 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c @@ -3201,8 +3201,7 @@ static ssize_t iwl_dbgfs_reset_write(struct file *file, return -EINVAL; trans->request_top_reset = 1; } - iwl_op_mode_nic_error(trans->op_mode, IWL_ERR_TYPE_DEBUGFS); - iwl_trans_schedule_reset(trans, IWL_ERR_TYPE_DEBUGFS); + iwl_trans_fw_error(trans, IWL_ERR_TYPE_DEBUGFS); return count; } From c35c44434329c6b3bd2c6c1e5add5ae10d933a4d Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Tue, 12 May 2026 22:34:31 +0300 Subject: [PATCH 076/143] wifi: iwlwifi: mld: call iwl_mld_free_ap_early_key() for AP only The driver supports intalling IGTK on a NAN device interface. When the IGTK is removed, iwl_mld_free_ap_early_key() is called which results in a warning since no links are attached to this interface. The iwl_mld_free_ap_early_key() function should be called for AP or IBSS interfaces only. Check the interface type before calling it. Signed-off-by: Avraham Stern Link: https://patch.msgid.link/20260512222731.d67d658b421d.Ife2d1f3b17119ce696582ce6a2a69026d368d8af@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 4315b1b2b36b..41bc47e4e00c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -2227,7 +2227,9 @@ static void iwl_mld_set_key_remove(struct iwl_mld *mld, } /* if this key was stored to be added later to the FW - free it here */ - if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE) && + (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC)) iwl_mld_free_ap_early_key(mld, key, mld_vif); /* We already removed it */ From fc58bb9ecef04aa46f201fc421a16e670b5dc01f Mon Sep 17 00:00:00 2001 From: Avinash Bhatt Date: Tue, 12 May 2026 22:34:32 +0300 Subject: [PATCH 077/143] wifi: iwlwifi: Transition to basic uAPSD with MAC_PM_POWER_TABLE API VER_3 uAPSD is transitioning to a certification-only feature. The new firmware API version 3 removes advanced uAPSD fields, keeping only basic parameters needed for certification testing. Support the new VER_3 API in the MLD driver while maintaining backward compatibility with VER_1/2. The MVM driver continues using VER_2 only. Remove the obsolete PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION notification from the MLD driver Signed-off-by: Avinash Bhatt Link: https://patch.msgid.link/20260512222731.96b6f53c8708.I4f01b97b25d91ebb1561845d7925103e274574fa@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/fw/api/power.h | 48 ++++- .../net/wireless/intel/iwlwifi/mld/iface.c | 18 -- .../net/wireless/intel/iwlwifi/mld/iface.h | 3 - drivers/net/wireless/intel/iwlwifi/mld/mld.c | 1 - .../net/wireless/intel/iwlwifi/mld/notif.c | 6 - .../net/wireless/intel/iwlwifi/mld/power.c | 177 ++++++++++++++++-- drivers/net/wireless/intel/iwlwifi/mvm/mvm.h | 4 +- .../net/wireless/intel/iwlwifi/mvm/power.c | 14 +- 8 files changed, 214 insertions(+), 57 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h index a3f916630df2..115e65ba19f8 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH */ @@ -85,12 +85,13 @@ struct iwl_ltr_config_cmd { * '1' PM could sleep over DTIM till listen Interval. * @POWER_FLAGS_SNOOZE_ENA_MSK: Enable snoozing only if uAPSD is enabled and all * access categories are both delivery and trigger enabled. + * (Not supported since version 3) * @POWER_FLAGS_BT_SCO_ENA: Enable BT SCO coex only if uAPSD and * PBW Snoozing enabled * @POWER_FLAGS_ADVANCE_PM_ENA_MSK: Advanced PM (uAPSD) enable mask * @POWER_FLAGS_LPRX_ENA_MSK: Low Power RX enable. * @POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK: AP/GO's uAPSD misbehaving - * detection enablement + * detection enablement (Not supported since version 3) * @POWER_FLAGS_ENABLE_SMPS_MSK: SMPS is allowed for this vif */ enum iwl_power_flags { @@ -175,9 +176,9 @@ struct iwl_device_power_cmd { } __packed; /** - * struct iwl_mac_power_cmd - New power command containing uAPSD support + * struct iwl_mac_power_cmd_v2 - power command V2 containing uAPSD support * MAC_PM_POWER_TABLE = 0xA9 (command, has simple generic response) - * @id_and_color: MAC contex identifier, &enum iwl_ctxt_id_and_color + * @id_and_color: MAC context identifier, &enum iwl_ctxt_id_and_color * @flags: Power table command flags from POWER_FLAGS_* * @keep_alive_seconds: Keep alive period in seconds. Default - 25 sec. * Minimum allowed:- 3 * DTIM. Keep alive period must be @@ -216,7 +217,7 @@ struct iwl_device_power_cmd { * @limited_ps_threshold: (unused) * @reserved: reserved (padding) */ -struct iwl_mac_power_cmd { +struct iwl_mac_power_cmd_v2 { /* CONTEXT_DESC_API_T_VER_1 */ __le32 id_and_color; @@ -242,6 +243,43 @@ struct iwl_mac_power_cmd { u8 reserved; } __packed; /* CLIENT_PM_POWER_TABLE_S_VER_1, VER_2 */ +/** + * struct iwl_mac_power_cmd - power command + * MAC_PM_POWER_TABLE = 0xA9 (command, has simple generic response) + * @id_and_color: MAC context identifier, &enum iwl_ctxt_id_and_color + * @flags: Power table command flags from POWER_FLAGS_* + * @keep_alive_seconds: Keep alive period in seconds. Default - 25 sec. + * Minimum allowed:- 3 * DTIM. Keep alive period must be + * set regardless of power scheme or current power state. + * FW use this value also when PM is disabled. + * @rx_data_timeout: Minimum time (usec) from last Rx packet for AM to + * PSM transition - legacy PM + * @tx_data_timeout: Minimum time (usec) from last Tx packet for AM to + * PSM transition - legacy PM + * @lprx_rssi_threshold: Signal strength up to which LP RX can be enabled. + * Default: 80dbm + * @skip_dtim_periods: Number of DTIM periods to skip if Skip over DTIM flag + * is set. For example, if it is required to skip over + * one DTIM, this value need to be set to 2 (DTIM periods). + * @qndp_tid: TID client shall use for uAPSD QNDP triggers + * @uapsd_ac_flags: Set trigger-enabled and delivery-enabled indication for + * each corresponding AC. + * Use IEEE80211_WMM_IE_STA_QOSINFO_AC* for correct values. + */ +struct iwl_mac_power_cmd { + /* CONTEXT_DESC_API_T_VER_1 */ + __le32 id_and_color; + + __le16 flags; + __le16 keep_alive_seconds; + __le32 rx_data_timeout; + __le32 tx_data_timeout; + u8 lprx_rssi_threshold; + u8 skip_dtim_periods; + u8 qndp_tid; + u8 uapsd_ac_flags; +} __packed; /* CLIENT_PM_POWER_TABLE_S_VER_3 */ + /* * struct iwl_uapsd_misbehaving_ap_notif - FW sends this notification when * associated AP is identified as improperly implementing uAPSD protocol. diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c index 5fc3f6729455..4fe57d79daa6 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c @@ -739,24 +739,6 @@ void iwl_mld_handle_probe_resp_data_notif(struct iwl_mld *mld, kfree_rcu(old_data, rcu_head); } -void iwl_mld_handle_uapsd_misbehaving_ap_notif(struct iwl_mld *mld, - struct iwl_rx_packet *pkt) -{ - struct iwl_uapsd_misbehaving_ap_notif *notif = (void *)pkt->data; - struct ieee80211_vif *vif; - - if (IWL_FW_CHECK(mld, notif->mac_id >= ARRAY_SIZE(mld->fw_id_to_vif), - "mac id is invalid: %d\n", notif->mac_id)) - return; - - vif = wiphy_dereference(mld->wiphy, mld->fw_id_to_vif[notif->mac_id]); - - if (WARN_ON(!vif) || ieee80211_vif_is_mld(vif)) - return; - - IWL_WARN(mld, "uapsd misbehaving AP: %pM\n", vif->bss_conf.bssid); -} - void iwl_mld_handle_datapath_monitor_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt) { diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.h b/drivers/net/wireless/intel/iwlwifi/mld/iface.h index 71c794204475..d6fb58e785e5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h @@ -274,9 +274,6 @@ void iwl_mld_handle_probe_resp_data_notif(struct iwl_mld *mld, void iwl_mld_handle_datapath_monitor_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt); -void iwl_mld_handle_uapsd_misbehaving_ap_notif(struct iwl_mld *mld, - struct iwl_rx_packet *pkt); - void iwl_mld_reset_cca_40mhz_workaround(struct iwl_mld *mld, struct ieee80211_vif *vif); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 9a3b768fce7a..25bab6ab6375 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -162,7 +162,6 @@ static const struct iwl_hcmd_names iwl_mld_legacy_names[] = { HCMD_NAME(PHY_CONFIGURATION_CMD), HCMD_NAME(SCAN_OFFLOAD_UPDATE_PROFILES_CMD), HCMD_NAME(POWER_TABLE_CMD), - HCMD_NAME(PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION), HCMD_NAME(BEACON_NOTIFICATION), HCMD_NAME(BEACON_TEMPLATE_CMD), HCMD_NAME(TX_ANT_CONFIGURATION_CMD), diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index 4e9f3768c381..1c81152042ab 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -339,8 +339,6 @@ CMD_VERSIONS(emlsr_mode_notif, CMD_VER_ENTRY(2, iwl_esr_mode_notif)) CMD_VERSIONS(emlsr_trans_fail_notif, CMD_VER_ENTRY(1, iwl_esr_trans_fail_notif)) -CMD_VERSIONS(uapsd_misbehaving_ap_notif, - CMD_VER_ENTRY(1, iwl_uapsd_misbehaving_ap_notif)) CMD_VERSIONS(time_msmt_notif, CMD_VER_ENTRY(1, iwl_time_msmt_notify)) CMD_VERSIONS(time_sync_confirm_notif, @@ -363,8 +361,6 @@ DEFINE_SIMPLE_CANCELLATION(scan_complete, iwl_umac_scan_complete, uid) DEFINE_SIMPLE_CANCELLATION(scan_start, iwl_umac_scan_start, uid) DEFINE_SIMPLE_CANCELLATION(probe_resp_data, iwl_probe_resp_data_notif, mac_id) -DEFINE_SIMPLE_CANCELLATION(uapsd_misbehaving_ap, iwl_uapsd_misbehaving_ap_notif, - mac_id) DEFINE_SIMPLE_CANCELLATION(ftm_resp, iwl_tof_range_rsp_ntfy, request_id) DEFINE_SIMPLE_CANCELLATION(beacon_filter, iwl_beacon_filter_notif, link_id) @@ -455,8 +451,6 @@ const struct iwl_rx_handler iwl_mld_rx_handlers[] = { emlsr_mode_notif, RX_HANDLER_ASYNC) RX_HANDLER_NO_OBJECT(MAC_CONF_GROUP, EMLSR_TRANS_FAIL_NOTIF, emlsr_trans_fail_notif, RX_HANDLER_ASYNC) - RX_HANDLER_OF_VIF(LEGACY_GROUP, PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION, - uapsd_misbehaving_ap_notif) RX_HANDLER_NO_OBJECT(LEGACY_GROUP, WNM_80211V_TIMING_MEASUREMENT_NOTIFICATION, time_msmt_notif, RX_HANDLER_SYNC) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/power.c b/drivers/net/wireless/intel/iwlwifi/mld/power.c index fe71da8b9c89..da065a446f81 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/power.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/power.c @@ -88,10 +88,10 @@ static bool iwl_mld_power_is_radar(struct iwl_mld *mld, return chanctx_conf->def.chan->flags & IEEE80211_CHAN_RADAR; } -static void iwl_mld_power_configure_uapsd(struct iwl_mld *mld, - struct iwl_mld_link *link, - struct iwl_mac_power_cmd *cmd, - bool ps_poll) +static void iwl_mld_power_configure_uapsd_v2(struct iwl_mld *mld, + struct iwl_mld_link *link, + struct iwl_mac_power_cmd_v2 *cmd, + bool ps_poll) { bool tid_found = false; @@ -150,10 +150,54 @@ static void iwl_mld_power_configure_uapsd(struct iwl_mld *mld, cmd->uapsd_max_sp = mld->hw->uapsd_max_sp_len; } +static void iwl_mld_power_configure_uapsd(struct iwl_mld *mld, + struct iwl_mld_link *link, + struct iwl_mac_power_cmd *cmd, + bool ps_poll) +{ + bool tid_found = false; + + /* set advanced pm flag with no uapsd ACs to enable ps-poll */ + if (ps_poll) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK); + return; + } + + for (enum ieee80211_ac_numbers ac = IEEE80211_AC_VO; + ac <= IEEE80211_AC_BK; + ac++) { + if (!link->queue_params[ac].uapsd) + continue; + + cmd->flags |= + cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK); + cmd->uapsd_ac_flags |= BIT(ac); + + /* QNDP TID - the highest TID with no admission control */ + if (!tid_found && !link->queue_params[ac].acm) { + tid_found = true; + switch (ac) { + case IEEE80211_AC_VO: + cmd->qndp_tid = 6; + break; + case IEEE80211_AC_VI: + cmd->qndp_tid = 5; + break; + case IEEE80211_AC_BE: + cmd->qndp_tid = 0; + break; + case IEEE80211_AC_BK: + cmd->qndp_tid = 1; + break; + } + } + } +} + static void iwl_mld_power_config_skip_dtim(struct iwl_mld *mld, const struct ieee80211_bss_conf *link_conf, - struct iwl_mac_power_cmd *cmd) + u8 *skip_dtim_periods, __le16 *flags) { unsigned int dtimper_tu; unsigned int dtimper; @@ -171,15 +215,15 @@ iwl_mld_power_config_skip_dtim(struct iwl_mld *mld, /* configure skip over dtim up to 900 TU DTIM interval */ skip = max_t(int, 1, 900 / dtimper_tu); - cmd->skip_dtim_periods = skip; - cmd->flags |= cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); + *skip_dtim_periods = skip; + *flags |= cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); } #define POWER_KEEP_ALIVE_PERIOD_SEC 25 -static void iwl_mld_power_build_cmd(struct iwl_mld *mld, - struct ieee80211_vif *vif, - struct iwl_mac_power_cmd *cmd, - bool d3) +static void iwl_mld_power_build_cmd_v2(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct iwl_mac_power_cmd_v2 *cmd, + bool d3) { int dtimper, bi; int keep_alive; @@ -238,7 +282,9 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld, } if (d3) { - iwl_mld_power_config_skip_dtim(mld, link_conf, cmd); + iwl_mld_power_config_skip_dtim(mld, link_conf, + &cmd->skip_dtim_periods, + &cmd->flags); cmd->rx_data_timeout = cpu_to_le32(IWL_MLD_WOWLAN_PS_RX_DATA_TIMEOUT); cmd->tx_data_timeout = @@ -259,6 +305,95 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld, * mac80211 will allow uAPSD. Always call iwl_mld_power_configure_uapsd * which will look at what mac80211 is saying. */ +#ifdef CONFIG_IWLWIFI_DEBUGFS + ps_poll = mld_vif->use_ps_poll; +#endif + iwl_mld_power_configure_uapsd_v2(mld, link, cmd, ps_poll); +} + +static void iwl_mld_power_build_cmd(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct iwl_mac_power_cmd *cmd, + bool d3) +{ + int dtimper, bi; + int keep_alive; + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + struct ieee80211_bss_conf *link_conf = &vif->bss_conf; + struct iwl_mld_link *link = &mld_vif->deflink; + bool ps_poll = false; + __le32 fw_id = cpu_to_le32(mld_vif->fw_id); + + if (ieee80211_vif_is_mld(vif)) { + int link_id; + + if (WARN_ON(!vif->active_links)) + return; + + /* The firmware consumes one single configuration for the vif + * and can't differentiate between links, just pick the lowest + * link_id's configuration and use that. + */ + link_id = __ffs(vif->active_links); + link_conf = link_conf_dereference_check(vif, link_id); + link = iwl_mld_link_dereference_check(mld_vif, link_id); + + if (WARN_ON(!link_conf || !link)) + return; + } + dtimper = link_conf->dtim_period; + bi = link_conf->beacon_int; + + /* Regardless of power management state the driver must set + * keep alive period. FW will use it for sending keep alive NDPs + * immediately after association. Check that keep alive period + * is at least 3 * DTIM + */ + keep_alive = DIV_ROUND_UP(ieee80211_tu_to_usec(3 * dtimper * bi), + USEC_PER_SEC); + keep_alive = max(keep_alive, POWER_KEEP_ALIVE_PERIOD_SEC); + + cmd->id_and_color = fw_id; + cmd->keep_alive_seconds = cpu_to_le16(keep_alive); + + if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) + cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_SAVE_ENA_MSK); + + if (vif->cfg.ps && iwl_mld_tdls_sta_count(mld) == 0) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); + cmd->flags |= cpu_to_le16(POWER_FLAGS_ENABLE_SMPS_MSK); + + /* firmware supports LPRX for beacons at rate 1 Mbps or + * 6 Mbps only + */ + if (link_conf->beacon_rate && + (link_conf->beacon_rate->bitrate == 10 || + link_conf->beacon_rate->bitrate == 60)) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK); + cmd->lprx_rssi_threshold = POWER_LPRX_RSSI_THRESHOLD; + } + } + + if (d3) { + iwl_mld_power_config_skip_dtim(mld, link_conf, + &cmd->skip_dtim_periods, + &cmd->flags); + cmd->rx_data_timeout = + cpu_to_le32(IWL_MLD_WOWLAN_PS_RX_DATA_TIMEOUT); + cmd->tx_data_timeout = + cpu_to_le32(IWL_MLD_WOWLAN_PS_TX_DATA_TIMEOUT); + } else if (iwl_mld_vif_low_latency(mld_vif) && vif->p2p) { + cmd->tx_data_timeout = + cpu_to_le32(IWL_MLD_SHORT_PS_TX_DATA_TIMEOUT); + cmd->rx_data_timeout = + cpu_to_le32(IWL_MLD_SHORT_PS_RX_DATA_TIMEOUT); + } else { + cmd->rx_data_timeout = + cpu_to_le32(IWL_MLD_DEFAULT_PS_RX_DATA_TIMEOUT); + cmd->tx_data_timeout = + cpu_to_le32(IWL_MLD_DEFAULT_PS_TX_DATA_TIMEOUT); + } + #ifdef CONFIG_IWLWIFI_DEBUGFS ps_poll = mld_vif->use_ps_poll; #endif @@ -268,11 +403,23 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld, int iwl_mld_update_mac_power(struct iwl_mld *mld, struct ieee80211_vif *vif, bool d3) { - struct iwl_mac_power_cmd cmd = {}; + int cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, MAC_PM_POWER_TABLE, 0); - iwl_mld_power_build_cmd(mld, vif, &cmd, d3); + if (cmd_ver >= 3) { + struct iwl_mac_power_cmd cmd = {}; - return iwl_mld_send_cmd_pdu(mld, MAC_PM_POWER_TABLE, &cmd); + iwl_mld_power_build_cmd(mld, vif, &cmd, d3); + return iwl_mld_send_cmd_with_flags_pdu(mld, + MAC_PM_POWER_TABLE, 0, + &cmd, sizeof(cmd)); + } else { + struct iwl_mac_power_cmd_v2 cmd = {}; + + iwl_mld_power_build_cmd_v2(mld, vif, &cmd, d3); + return iwl_mld_send_cmd_with_flags_pdu(mld, + MAC_PM_POWER_TABLE, 0, + &cmd, sizeof(cmd)); + } } static void diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index 402ba5dee8b2..be89b84204fb 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -469,7 +469,7 @@ struct iwl_mvm_vif { struct dentry *dbgfs_slink; struct iwl_dbgfs_pm dbgfs_pm; struct iwl_dbgfs_bf dbgfs_bf; - struct iwl_mac_power_cmd mac_pwr_cmd; + struct iwl_mac_power_cmd_v2 mac_pwr_cmd; int dbgfs_quota_min; bool ftm_unprotected; #endif diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/power.c b/drivers/net/wireless/intel/iwlwifi/mvm/power.c index 610de29b7be0..46792c508753 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/power.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/power.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2019, 2021-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2019, 2021-2026 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH */ @@ -83,7 +83,7 @@ void iwl_mvm_beacon_filter_set_cqm_params(struct iwl_mvm *mvm, } static void iwl_mvm_power_log(struct iwl_mvm *mvm, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { IWL_DEBUG_POWER(mvm, "Sending power table command on mac id 0x%X for power level %d, flags = 0x%X\n", @@ -121,7 +121,7 @@ static void iwl_mvm_power_log(struct iwl_mvm *mvm, static void iwl_mvm_power_configure_uapsd(struct iwl_mvm *mvm, struct ieee80211_vif *vif, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); enum ieee80211_ac_numbers ac; @@ -296,7 +296,7 @@ static bool iwl_mvm_power_is_radar(struct ieee80211_bss_conf *link_conf) static void iwl_mvm_power_config_skip_dtim(struct iwl_mvm *mvm, struct ieee80211_vif *vif, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { struct ieee80211_bss_conf *link_conf; unsigned int min_link_skip = ~0; @@ -344,7 +344,7 @@ static void iwl_mvm_power_config_skip_dtim(struct iwl_mvm *mvm, static void iwl_mvm_power_build_cmd(struct iwl_mvm *mvm, struct ieee80211_vif *vif, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { int dtimper, bi; int keep_alive; @@ -466,7 +466,7 @@ static void iwl_mvm_power_build_cmd(struct iwl_mvm *mvm, static int iwl_mvm_power_send_cmd(struct iwl_mvm *mvm, struct ieee80211_vif *vif) { - struct iwl_mac_power_cmd cmd = {}; + struct iwl_mac_power_cmd_v2 cmd = {}; iwl_mvm_power_build_cmd(mvm, vif, &cmd); iwl_mvm_power_log(mvm, &cmd); @@ -717,7 +717,7 @@ int iwl_mvm_power_mac_dbgfs_read(struct iwl_mvm *mvm, int bufsz) { struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); - struct iwl_mac_power_cmd cmd = {}; + struct iwl_mac_power_cmd_v2 cmd = {}; int pos = 0; mutex_lock(&mvm->mutex); From ab29379db7e0987a16fd122c9fde9218dcb575c7 Mon Sep 17 00:00:00 2001 From: Daniel Gabay Date: Tue, 12 May 2026 22:34:33 +0300 Subject: [PATCH 078/143] wifi: iwlwifi: mld: fix NAN DW end notification handler Use IWL_DEBUG_INFO instead of IWL_INFO for logging the DW end notification, as this is a recurring event during NAN operation and should not spam the kernel log. Also fix a coding style issue - missing space after 'if'. Signed-off-by: Daniel Gabay Link: https://patch.msgid.link/20260512222731.0fd880fb80f9.Iacd459d0e2df28444bb6ccf8730e2f50440e6e32@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index cb7a3e01adb4..264ea7a9a896 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -269,7 +269,7 @@ void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld, struct wireless_dev *wdev; struct ieee80211_channel *chan; - IWL_INFO(mld, "NAN: DW end: band=%u\n", notif->band); + IWL_DEBUG_INFO(mld, "NAN: DW end: band=%u\n", notif->band); if (IWL_FW_CHECK(mld, !mld_vif, "NAN: DW end without mld_vif\n")) return; From dbaece4ef786119efe702f6d8e0f45565bb25c39 Mon Sep 17 00:00:00 2001 From: Daniel Gabay Date: Tue, 12 May 2026 22:34:34 +0300 Subject: [PATCH 079/143] wifi: iwlwifi: mld: add NULL check for channel in DW end handler ieee80211_get_channel() can return NULL if the frequency is not registered in the wiphy (e.g. due to regulatory domain restrictions). The returned channel pointer is passed directly to cfg80211_next_nan_dw_notif() which dereferences it unconditionally in both the tracepoint and the netlink message, causing a NULL pointer dereference. Add a NULL check before using the channel pointer. Signed-off-by: Daniel Gabay Reviewed-by: Ilan Peer Link: https://patch.msgid.link/20260512222731.a250203cd1c6.I1d807aab415da30a55dd89a974c3226adc547ebb@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index 264ea7a9a896..deb72e401e3c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -305,6 +305,9 @@ void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld, return; } + if (WARN_ON_ONCE(!chan)) + return; + wdev = ieee80211_vif_to_wdev(mld->nan_device_vif); cfg80211_next_nan_dw_notif(wdev, chan, GFP_KERNEL); } From ee61fa6d6c194e9393e1cb89fcb507ecaf60f1d9 Mon Sep 17 00:00:00 2001 From: Daniel Gabay Date: Tue, 12 May 2026 22:34:35 +0300 Subject: [PATCH 080/143] wifi: iwlwifi: mld: validate aux sta before flush in stop_nan iwl_mld_stop_nan() calls iwl_mld_flush_link_sta_txqs() without checking that aux_sta.sta_id is valid. The DW end handler correctly guards this with a WARN_ON check. Add the same defensive check to stop_nan for consistency and to avoid sending a flush command with an invalid sta_id. Signed-off-by: Daniel Gabay Reviewed-by: Ilan Peer Link: https://patch.msgid.link/20260512222731.76b3db062a5e.I0567cfeb915c38c517eb6e1829d78c9fa8653c1f@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index deb72e401e3c..913f0acf172a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -203,7 +203,8 @@ int iwl_mld_stop_nan(struct ieee80211_hw *hw, /* assume that higher layer guarantees that no additional frames are * added before calling this callback */ - iwl_mld_flush_link_sta_txqs(mld, mld_vif->aux_sta.sta_id); + if (!WARN_ON(mld_vif->aux_sta.sta_id == IWL_INVALID_STA)) + iwl_mld_flush_link_sta_txqs(mld, mld_vif->aux_sta.sta_id); iwl_mld_remove_aux_sta(mld, vif); /* cancel based on object type being NAN, as the NAN objects do From 5bea479c9c5bbcb18c468ee1a88074726ef52334 Mon Sep 17 00:00:00 2001 From: Avinash Bhatt Date: Tue, 12 May 2026 22:34:36 +0300 Subject: [PATCH 081/143] wifi: iwlwifi: mld: add chan-load hysteresis for MLO scan triggers Introduce a three-level, table-driven hysteresis mechanism for deciding when to trigger internal MLO scans based on MCLM Chan Load. Prevents repeated triggers under fluctuating load conditions. Signed-off-by: Avinash Bhatt Link: https://patch.msgid.link/20260512222731.2db48151d32d.I8660f0660a95358bda067af806d28bc2fa6f7f76@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.c | 61 +++++++++++++++++++ drivers/net/wireless/intel/iwlwifi/mld/link.h | 15 +++++ drivers/net/wireless/intel/iwlwifi/mld/mlo.c | 29 ++++++++- .../net/wireless/intel/iwlwifi/mld/stats.c | 4 ++ 4 files changed, 106 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index 9e40b334ee1f..a8d146edc4bd 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -16,6 +16,23 @@ #include "fw/api/context.h" #include "fw/dbg.h" +/** + * struct iwl_mld_link_chan_load_threshold - channel load thresholds + * @high_lim: level up transition thresholds, in percentage + * @low_lim: level down transition thresholds, in percentage + */ +struct iwl_mld_link_chan_load_threshold { + u8 high_lim; + u8 low_lim; +}; + +static const struct iwl_mld_link_chan_load_threshold +link_chan_load_thresh_tbl[] = { + [LINK_CHAN_LOAD_LVL1] = { .high_lim = 45, .low_lim = 40 }, + [LINK_CHAN_LOAD_LVL2] = { .high_lim = 70, .low_lim = 65 }, + [LINK_CHAN_LOAD_LVL3] = { .high_lim = 85, .low_lim = 80 }, +}; + int iwl_mld_send_link_cmd(struct iwl_mld *mld, struct iwl_link_config_cmd *cmd, enum iwl_ctxt_action action) @@ -792,6 +809,50 @@ int iwl_mld_get_chan_load_by_others(struct iwl_mld *mld, return chan_load; } +/* Returns whether internal MLO Scan needs to be triggered */ +bool iwl_mld_chan_load_requires_scan(struct iwl_mld *mld, + struct ieee80211_bss_conf *link_conf, + u32 new_chan_load) +{ + struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link_conf); + enum iwl_mld_link_chan_load_level new_lvl; + bool scan_trig = false; + + if (WARN_ON(!mld_link)) + return false; + + /* For each Level, + * First check if high limit threshold crosses + * If not then, check if low limit threshold crosses + * Set new level based on low limit thresh only if old level + * is not lower than level threshold + */ + for (new_lvl = LINK_CHAN_LOAD_LVL_MAX; + new_lvl > LINK_CHAN_LOAD_LVL_NONE; new_lvl--) { + if (new_chan_load >= + link_chan_load_thresh_tbl[new_lvl].high_lim) + break; + if (new_chan_load >= + link_chan_load_thresh_tbl[new_lvl].low_lim && + mld_link->chan_load_lvl >= new_lvl) + break; + } + + /* Trigger scan only for Level Up Transition */ + if (new_lvl > mld_link->chan_load_lvl) + scan_trig = true; + + IWL_DEBUG_EHT(mld, + "Link %d: chan_load=%d%%, old_lvl=%d, new_lvl=%d, scan_trig=%d\n", + link_conf->link_id, new_chan_load, + mld_link->chan_load_lvl, new_lvl, scan_trig); + + /* Update computed new level */ + mld_link->chan_load_lvl = new_lvl; + + return scan_trig; +} + static unsigned int iwl_mld_get_default_chan_load(struct ieee80211_bss_conf *link_conf) { diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.h b/drivers/net/wireless/intel/iwlwifi/mld/link.h index 2b3e6b55367f..4527f054ce92 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.h @@ -10,6 +10,14 @@ #include "mld.h" #include "sta.h" +enum iwl_mld_link_chan_load_level { + LINK_CHAN_LOAD_LVL_NONE, + LINK_CHAN_LOAD_LVL1, + LINK_CHAN_LOAD_LVL2, + LINK_CHAN_LOAD_LVL3, + LINK_CHAN_LOAD_LVL_MAX = LINK_CHAN_LOAD_LVL3 +}; + /** * struct iwl_probe_resp_data - data for NoA/CSA updates * @rcu_head: used for freeing the data on update @@ -50,6 +58,8 @@ struct iwl_probe_resp_data { * @silent_deactivation: next deactivation needs to be silent. * @probe_resp_data: data from FW notification to store NOA related data to be * inserted into probe response. + * @chan_load_lvl: current channel load level for a link, computed based on + * channel load by others on a link. */ struct iwl_mld_link { struct rcu_head rcu_head; @@ -63,6 +73,7 @@ struct iwl_mld_link { bool he_ru_2mhz_block; struct ieee80211_key_conf *tx_igtk; struct ieee80211_key_conf __rcu *bigtks[2]; + enum iwl_mld_link_chan_load_level chan_load_lvl; ); /* And here fields that survive a fw restart */ struct iwl_mld_int_sta bcast_sta; @@ -135,6 +146,10 @@ int iwl_mld_get_chan_load_by_others(struct iwl_mld *mld, struct ieee80211_bss_conf *link_conf, bool expect_active_link); +bool iwl_mld_chan_load_requires_scan(struct iwl_mld *mld, + struct ieee80211_bss_conf *link_conf, + u32 new_chan_load); + void iwl_mld_handle_beacon_filter_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c index 8227ccb31d60..2a3b2c883fc4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mlo.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 "mlo.h" #include "phy.h" @@ -1081,8 +1081,13 @@ static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac, container_of((const void *)phy, struct ieee80211_chanctx_conf, drv_priv); struct iwl_mld *mld = iwl_mld_vif_from_mac80211(vif)->mld; - struct ieee80211_bss_conf *prim_link; + u32 new_chan_load = phy->avg_channel_load_not_by_us; + struct ieee80211_bss_conf *prim_link, *link_conf; unsigned int prim_link_id; + int link_id; + + if (!ieee80211_vif_is_mld(vif) || hweight16(vif->valid_links) <= 1) + return; prim_link_id = iwl_mld_get_primary_link(vif); prim_link = link_conf_dereference_protected(vif, prim_link_id); @@ -1090,6 +1095,25 @@ static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac, if (WARN_ON(!prim_link)) return; + /* Evaluate MLO Internal Scan for high chan load beyond thresholds */ + for_each_vif_active_link(vif, link_conf, link_id) { + if (rcu_access_pointer(link_conf->chanctx_conf) != chanctx) + continue; + + if (iwl_mld_chan_load_requires_scan(mld, + link_conf, + new_chan_load)) { + /* When EMLSR is active, only trigger scan based on + * primary link + */ + if (iwl_mld_emlsr_active(vif) && link_conf != prim_link) + continue; + + iwl_mld_int_mlo_scan(mld, vif); + return; + } + } + if (chanctx != rcu_access_pointer(prim_link->chanctx_conf)) return; @@ -1107,7 +1131,6 @@ static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac, prim_link_id); } else { u32 old_chan_load = data->prev_chan_load_not_by_us; - u32 new_chan_load = phy->avg_channel_load_not_by_us; u32 min_thresh = iwl_mld_get_min_chan_load_thresh(chanctx); #define THRESHOLD_CROSSED(threshold) \ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/stats.c b/drivers/net/wireless/intel/iwlwifi/mld/stats.c index 6e826797f637..b93e0f8ab5fb 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/stats.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/stats.c @@ -515,6 +515,10 @@ static void iwl_mld_fill_chanctx_stats(struct ieee80211_hw *hw, (old_load >> 1); } + IWL_DEBUG_EHT(phy->mld, + "PHY %d: load_by_us=%u%% load_not_by_us=%u%%\n", + phy->fw_id, phy->channel_load_by_us, new_load); + iwl_mld_emlsr_check_chan_load(hw, phy, old_load); } From 300073ea3d699df32a15efadefeda39efe11a0fb Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 12 May 2026 22:34:37 +0300 Subject: [PATCH 082/143] wifi: iwlwifi: implement the new RSC notification Add support for a new RSC notification that arrives on DATA queues. The same RSC handling previously done in the WOWLAN flow is now done through this notification, with backward compatibility maintained. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260512222731.2c0cd8b43e67.I7cfee4b57e7f84b0d38667290f45ed5be4cdd270@changeid Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/datapath.h | 9 +- drivers/net/wireless/intel/iwlwifi/mld/d3.c | 146 ++++++++++++++++-- drivers/net/wireless/intel/iwlwifi/mld/d3.h | 6 +- .../net/wireless/intel/iwlwifi/mld/iface.c | 4 +- drivers/net/wireless/intel/iwlwifi/mld/mld.c | 1 + .../net/wireless/intel/iwlwifi/mld/notif.c | 10 ++ drivers/net/wireless/intel/iwlwifi/mld/rx.c | 24 +++ drivers/net/wireless/intel/iwlwifi/mld/rx.h | 7 +- 8 files changed, 190 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h b/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h index 06370c161fe4..e494e5b18d22 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.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 * Copyright (C) 2012-2014, 2018-2022 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH @@ -91,6 +91,13 @@ enum iwl_data_path_subcmd_ids { */ SEC_KEY_CMD = 0x18, + /** + * @RSC_NOTIF: notification to update each Rx queue with the RSC. This + * notification is sent after resume and uses + * &struct iwl_wowlan_all_rsc_tsc_v5. + */ + RSC_NOTIF = 0xF1, + /** * @ESR_MODE_NOTIF: notification to recommend/force a wanted esr mode, * uses &struct iwl_esr_mode_notif or &struct iwl_esr_mode_notif_v1 diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index c44f02f225ce..ca4222a9a6ff 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -43,6 +43,12 @@ struct iwl_mld_resume_key_iter_data { struct iwl_mld_wowlan_status *wowlan_status; }; +struct iwl_mld_rsc_resume_iter_data { + struct iwl_mld *mld; + const struct iwl_wowlan_all_rsc_tsc_v5 *notif; + int queue; +}; + struct iwl_mld_suspend_key_iter_data { struct iwl_wowlan_rsc_tsc_params_cmd *rsc; bool have_rsc; @@ -282,7 +288,8 @@ static void iwl_mld_convert_gtk_resume_data(struct iwl_mld *mld, struct iwl_mld_wowlan_status *wowlan_status, const struct iwl_wowlan_gtk_status *gtk_data, - const struct iwl_wowlan_all_rsc_tsc_v5 *sc) + const struct iwl_wowlan_all_rsc_tsc_v5 *sc, + int rsc_notif_ver) { int status_idx = 0; @@ -305,14 +312,18 @@ iwl_mld_convert_gtk_resume_data(struct iwl_mld *mld, wowlan_status->gtk[status_idx].id = wowlan_status->gtk[status_idx].flags & IWL_WOWLAN_GTK_IDX_MASK; - /* The rsc for both gtk keys are stored in gtk[0]->sc->mcast_rsc - * The gtk ids can be any two numbers between 0 and 3, - * the id_map maps between the key id and the index in sc->mcast - */ - rsc_idx = - sc->mcast_key_id_map[wowlan_status->gtk[status_idx].id]; - iwl_mld_convert_gtk_resume_seq(&wowlan_status->gtk[status_idx], - sc, rsc_idx); + /* If RSC_NOTIF is not supported */ + if (rsc_notif_ver == IWL_FW_CMD_VER_UNKNOWN) { + /* The rsc for both gtk keys are stored in + * gtk[0]->sc->mcast_rsc. The gtk ids can be any two + * numbers between 0 and 3, the id_map maps between the + * key id and the index in sc->mcast + */ + rsc_idx = + sc->mcast_key_id_map[wowlan_status->gtk[status_idx].id]; + iwl_mld_convert_gtk_resume_seq(&wowlan_status->gtk[status_idx], + sc, rsc_idx); + } if (key_status == IWL_WOWLAN_STATUS_NEW_KEY) { memcpy(wowlan_status->gtk[status_idx].key, @@ -598,6 +609,10 @@ iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld, PROT_OFFLOAD_GROUP, WOWLAN_INFO_NOTIFICATION, IWL_FW_CMD_VER_UNKNOWN); + int rsc_notif_ver = iwl_fw_lookup_notif_ver(mld->fw, + DATA_PATH_GROUP, + RSC_NOTIF, + IWL_FW_CMD_VER_UNKNOWN); if (wowlan_info_ver == 5) { /* v5 format - validate before conversion */ @@ -642,8 +657,10 @@ iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld, return true; iwl_mld_convert_gtk_resume_data(mld, wowlan_status, notif->gtk, - ¬if->gtk[0].sc); - iwl_mld_convert_ptk_resume_seq(mld, wowlan_status, ¬if->gtk[0].sc); + ¬if->gtk[0].sc, rsc_notif_ver); + if (rsc_notif_ver == IWL_FW_CMD_VER_UNKNOWN) + iwl_mld_convert_ptk_resume_seq(mld, wowlan_status, + ¬if->gtk[0].sc); /* only one igtk is passed by FW */ iwl_mld_convert_igtk_resume_data(wowlan_status, ¬if->igtk[0]); iwl_mld_convert_bigtk_resume_data(wowlan_status, notif->bigtk); @@ -902,8 +919,14 @@ iwl_mld_resume_keys_iter(struct ieee80211_hw *hw, struct iwl_mld_resume_key_iter_data *data = _data; struct iwl_mld_wowlan_status *wowlan_status = data->wowlan_status; u8 status_idx; + int rsc_notif_ver = iwl_fw_lookup_notif_ver(data->mld->fw, + DATA_PATH_GROUP, + RSC_NOTIF, + IWL_FW_CMD_VER_UNKNOWN); - if (key->keyidx >= 0 && key->keyidx <= 3) { + /* If RSC_NOTIF is not supported */ + if (rsc_notif_ver == IWL_FW_CMD_VER_UNKNOWN && + key->keyidx >= 0 && key->keyidx <= 3) { /* PTK */ if (sta) { iwl_mld_update_ptk_rx_seq(data->mld, wowlan_status, @@ -932,6 +955,105 @@ iwl_mld_resume_keys_iter(struct ieee80211_hw *hw, } } +static void +iwl_mld_rsc_update_key_iter(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *_data) +{ + struct iwl_mld_rsc_resume_iter_data *data = _data; + struct ieee80211_key_seq seq; + + if (key->keyidx > 3) + return; + + if (sta) { + /* PTK */ + BUILD_BUG_ON(ARRAY_SIZE(data->notif->ucast_rsc) != + IWL_MAX_TID_COUNT); + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) { + /* TKIP: just update key sequences */ + for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { + iwl_mld_le64_to_tkip_seq(data->notif->ucast_rsc[tid], + &seq); + ieee80211_set_key_rx_seq(key, tid, &seq); + } + } else { + struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); + struct iwl_mld_ptk_pn *mld_ptk_pn = + rcu_dereference_wiphy(data->mld->wiphy, + mld_sta->ptk_pn[key->keyidx]); + + if (WARN_ON(!mld_ptk_pn)) + return; + + if (WARN_ON(data->queue >= + data->mld->trans->info.num_rxqs)) + return; + + for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { + iwl_mld_le64_to_aes_seq(data->notif->ucast_rsc[tid], + &seq); + ieee80211_set_key_rx_seq(key, tid, &seq); + memcpy(mld_ptk_pn->q[data->queue].pn[tid], + seq.ccmp.pn, + IEEE80211_CCMP_PN_LEN); + } + } + + IWL_DEBUG_WOWLAN(data->mld, + "Updated PTK RSC for key %d on queue %d\n", + key->keyidx, data->queue); + } else { + /* GTK */ + int rsc_idx = data->notif->mcast_key_id_map[key->keyidx]; + + if (rsc_idx == IWL_MCAST_KEY_MAP_INVALID) + return; + + if (IWL_FW_CHECK(data->mld, + rsc_idx >= ARRAY_SIZE(data->notif->mcast_rsc), + "Invalid mcast key mapping: %d for key %d\n", + rsc_idx, key->keyidx)) + return; + + for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { + __le64 rsc = + data->notif->mcast_rsc[rsc_idx][tid]; + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) + iwl_mld_le64_to_tkip_seq(rsc, &seq); + else + iwl_mld_le64_to_aes_seq(rsc, &seq); + ieee80211_set_key_rx_seq(key, tid, &seq); + } + + IWL_DEBUG_WOWLAN(data->mld, + "Updated GTK %d RSC (rsc_idx %d) on queue %d\n", + key->keyidx, rsc_idx, data->queue); + } +} + +void +iwl_mld_process_rsc_notification(struct iwl_mld *mld, + struct ieee80211_vif *vif, + const struct iwl_wowlan_all_rsc_tsc_v5 *notif, + int queue) +{ + struct iwl_mld_rsc_resume_iter_data iter_data = { + .mld = mld, + .notif = notif, + .queue = queue, + }; + + /* Iterate through all active keys and update RSC */ + ieee80211_iter_keys_rcu(mld->hw, vif, + iwl_mld_rsc_update_key_iter, + &iter_data); +} + static void iwl_mld_add_mcast_rekey(struct ieee80211_vif *vif, struct iwl_mld *mld, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.h b/drivers/net/wireless/intel/iwlwifi/mld/d3.h index 618d6fb3c796..c2e8ba877042 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2024 Intel Corporation + * Copyright (C) 2024, 2026 Intel Corporation */ #ifndef __iwl_mld_d3_h__ #define __iwl_mld_d3_h__ @@ -42,6 +42,10 @@ int iwl_mld_wowlan_resume(struct iwl_mld *mld); void iwl_mld_set_rekey_data(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct cfg80211_gtk_rekey_data *data); +void iwl_mld_process_rsc_notification(struct iwl_mld *mld, + struct ieee80211_vif *vif, + const struct iwl_wowlan_all_rsc_tsc_v5 *notif, + int queue); #if IS_ENABLED(CONFIG_IPV6) void iwl_mld_ipv6_addr_change(struct ieee80211_hw *hw, struct ieee80211_vif *vif, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c index 4fe57d79daa6..6f2590f9a69b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c @@ -853,6 +853,6 @@ struct ieee80211_vif *iwl_mld_get_bss_vif(struct iwl_mld *mld) fw_id = __ffs(fw_id_bitmap); - return wiphy_dereference(mld->wiphy, - mld->fw_id_to_vif[fw_id]); + return rcu_dereference_wiphy(mld->wiphy, + mld->fw_id_to_vif[fw_id]); } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 25bab6ab6375..3caa76b9b2cb 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -260,6 +260,7 @@ static const struct iwl_hcmd_names iwl_mld_data_path_names[] = { HCMD_NAME(RX_BAID_ALLOCATION_CONFIG_CMD), HCMD_NAME(SCD_QUEUE_CONFIG_CMD), HCMD_NAME(SEC_KEY_CMD), + HCMD_NAME(RSC_NOTIF), HCMD_NAME(ESR_MODE_NOTIF), HCMD_NAME(MONITOR_NOTIF), HCMD_NAME(TLC_MNG_UPDATE_NOTIF), diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index 1c81152042ab..6170953982b7 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -599,6 +599,11 @@ void iwl_mld_rx(struct iwl_op_mode *op_mode, struct napi_struct *napi, else if (unlikely(cmd_id == WIDE_ID(DATA_PATH_GROUP, RX_QUEUES_NOTIFICATION))) iwl_mld_handle_rx_queues_sync_notif(mld, napi, pkt, 0); +#ifdef CONFIG_PM_SLEEP + else if (unlikely(cmd_id == WIDE_ID(DATA_PATH_GROUP, + RSC_NOTIF))) + iwl_mld_handle_rsc_notif(mld, pkt, 0); +#endif else if (cmd_id == WIDE_ID(DATA_PATH_GROUP, PHY_AIR_SNIFFER_NOTIF)) iwl_mld_handle_phy_air_sniffer_notif(mld, napi, pkt); else @@ -622,6 +627,11 @@ void iwl_mld_rx_rss(struct iwl_op_mode *op_mode, struct napi_struct *napi, iwl_mld_handle_rx_queues_sync_notif(mld, napi, pkt, queue); else if (unlikely(cmd_id == WIDE_ID(LEGACY_GROUP, FRAME_RELEASE))) iwl_mld_handle_frame_release_notif(mld, napi, pkt, queue); +#ifdef CONFIG_PM_SLEEP + else if (unlikely(cmd_id == WIDE_ID(DATA_PATH_GROUP, + RSC_NOTIF))) + iwl_mld_handle_rsc_notif(mld, pkt, queue); +#endif } void iwl_mld_delete_handlers(struct iwl_mld *mld, const u16 *cmds, int n_cmds) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c index 01603dc07f0a..269439d789f4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c @@ -2260,6 +2260,30 @@ void iwl_mld_handle_rx_queues_sync_notif(struct iwl_mld *mld, wake_up(&mld->rxq_sync.waitq); } +#ifdef CONFIG_PM_SLEEP +void iwl_mld_handle_rsc_notif(struct iwl_mld *mld, + struct iwl_rx_packet *pkt, int queue) +{ + const struct iwl_wowlan_all_rsc_tsc_v5 *notif = (void *)pkt->data; + u32 len = iwl_rx_packet_payload_len(pkt); + struct ieee80211_vif *bss_vif; + + if (IWL_FW_CHECK(mld, len != sizeof(*notif), + "invalid notification size %u (%zu)\n", + len, sizeof(*notif))) + return; + + /* for the bss lookup and updating the keys' pn */ + guard(rcu)(); + + bss_vif = iwl_mld_get_bss_vif(mld); + if (WARN_ON(!bss_vif)) + return; + + iwl_mld_process_rsc_notification(mld, bss_vif, notif, queue); +} +#endif /* CONFIG_PM_SLEEP */ + static void iwl_mld_no_data_rx(struct iwl_mld *mld, struct napi_struct *napi, struct iwl_rx_phy_air_sniffer_ntfy *ntfy) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.h b/drivers/net/wireless/intel/iwlwifi/mld/rx.h index 09dddbd40f55..573b89c3c9c6 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/rx.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.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_rx_h__ #define __iwl_mld_rx_h__ @@ -61,6 +61,11 @@ void iwl_mld_handle_rx_queues_sync_notif(struct iwl_mld *mld, struct napi_struct *napi, struct iwl_rx_packet *pkt, int queue); +#ifdef CONFIG_PM_SLEEP +void iwl_mld_handle_rsc_notif(struct iwl_mld *mld, + struct iwl_rx_packet *pkt, int queue); +#endif + void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld, struct napi_struct *napi, struct sk_buff *skb, int queue, From 050b99cb04e878bde10ffec447b6eb414bcec37f Mon Sep 17 00:00:00 2001 From: Avinash Bhatt Date: Tue, 12 May 2026 22:34:38 +0300 Subject: [PATCH 083/143] wifi: iwlwifi: mld: add duplicated beacon RSSI adjustment For 6 GHz duplicated beacons, the RSSI is measured only on the 20 MHz primary channel while the actual beacon energy spans the full operational bandwidth. This leads to underestimated link quality. Detect duplicated beacons and apply bandwidth-based RSSI adjustments. Signed-off-by: Avinash Bhatt Link: https://patch.msgid.link/20260512222731.a4c873e63054.Iea8e8a5f3b384622308dd8d03c5881ff75f1908c@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.c | 67 ++++++++++++++++++- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index a8d146edc4bd..c07a0d6b0bd4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -894,6 +894,60 @@ iwl_mld_get_avail_chan_load(struct iwl_mld *mld, return MAX_CHAN_LOAD - iwl_mld_get_chan_load(mld, link_conf); } +static s8 +iwl_mld_get_dup_beacon_rssi_adjust(struct iwl_mld *mld, + struct ieee80211_bss_conf *link_conf) +{ + const struct ieee80211_he_6ghz_oper *he_6ghz_oper; + const struct cfg80211_bss_ies *beacon_ies; + const struct element *elem; + + /* Duplicated beacon feature is only specific to 6 GHz */ + if (WARN_ONCE(link_conf->chanreq.oper.chan->band != NL80211_BAND_6GHZ, + "Unexpected band %d\n", + link_conf->chanreq.oper.chan->band)) + return 0; + + lockdep_assert_wiphy(mld->wiphy); + + beacon_ies = wiphy_dereference(mld->wiphy, link_conf->bss->beacon_ies); + if (!beacon_ies) + return 0; + + elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, + beacon_ies->data, beacon_ies->len); + if (!elem || + elem->datalen < sizeof(struct ieee80211_he_operation) + 1 || + elem->datalen < ieee80211_he_oper_size(&elem->data[1])) + return 0; + + he_6ghz_oper = ieee80211_he_6ghz_oper((const void *)&elem->data[1]); + if (!he_6ghz_oper) + return 0; + + if (!(he_6ghz_oper->control & IEEE80211_HE_6GHZ_OPER_CTRL_DUP_BEACON)) + return 0; + + /* Apply adjustment based on operational bandwidth */ + switch (link_conf->chanreq.oper.width) { + case NL80211_CHAN_WIDTH_20: + case NL80211_CHAN_WIDTH_20_NOHT: + return 0; + case NL80211_CHAN_WIDTH_40: + return 3; + case NL80211_CHAN_WIDTH_80: + return 6; + case NL80211_CHAN_WIDTH_160: + return 9; + case NL80211_CHAN_WIDTH_320: + return 12; + default: + WARN_ONCE(1, "Unexpected channel width: %d\n", + link_conf->chanreq.oper.width); + return 0; + } +} + /* This function calculates the grade of a link. Returns 0 in error case */ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, struct ieee80211_bss_conf *link_conf) @@ -903,7 +957,7 @@ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, s32 link_rssi; unsigned int grade = MAX_GRADE; - if (WARN_ON_ONCE(!link_conf)) + if (WARN_ON_ONCE(!link_conf || !link_conf->bss)) return 0; band = link_conf->chanreq.oper.chan->band; @@ -918,8 +972,15 @@ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, * For 6 GHz the RSSI of the beacons is lower than * the RSSI of the data. */ - if (band == NL80211_BAND_6GHZ && link_rssi) - link_rssi += 4; + if (band == NL80211_BAND_6GHZ && link_rssi) { + s8 rssi_adj_6g = + iwl_mld_get_dup_beacon_rssi_adjust(mld, link_conf); + + if (!rssi_adj_6g) + rssi_adj_6g = 4; + + link_rssi += rssi_adj_6g; + } rssi_idx = band == NL80211_BAND_2GHZ ? 0 : 1; From 9ac94f8404e8a20282dd571da814afd7f85c6944 Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Tue, 12 May 2026 22:34:39 +0300 Subject: [PATCH 084/143] wifi: iwlwifi: mld: add support for nan schedule config command version 2 Version 2 of the NAN schedule config command adds support for deferred schedule update and passing the availability attribute blob to firmware. Add support for the new command version. Currently the new functionality is not supported. Signed-off-by: Avraham Stern Link: https://patch.msgid.link/20260512222731.422e4bc47f84.I04b7dec6f3e48cfd950e7ff6f13484d0881d832e@changeid Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/mac-cfg.h | 42 ++++++++++++++++++- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 22 ++++++++-- 2 files changed, 58 insertions(+), 6 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 75b477319096..dd850aeebdec 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -1254,7 +1254,7 @@ struct iwl_nan_config_cmd { } __packed; /* NAN_CONFIG_CMD_API_S_VER_1 */ /** - * struct iwl_nan_schedule_cmd - NAN schedule command + * struct iwl_nan_schedule_cmd_v1 - 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 @@ -1263,7 +1263,7 @@ struct iwl_nan_config_cmd { * @channels.link_id: FW link ID, or %0xFF for unset * @channels.reserved: (reserved) */ -struct iwl_nan_schedule_cmd { +struct iwl_nan_schedule_cmd_v1 { struct { __le32 availability_map; u8 channel_entry[6]; @@ -1272,6 +1272,44 @@ struct iwl_nan_schedule_cmd { } __packed channels[NUM_PHY_CTX]; } __packed; /* NAN_SCHEDULE_CMD_API_S_VER_1 */ +#define IWL_MAX_AVAILABILITY_ATTR_LEN 54 + +/** + * 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) + * @avail_attr: NAN availability attribute information + * @avail_attr.attr_len: length of the availability attribute + * @avail_attr.reserved: reserved + * @avail_attr.attr: the availability attribute including the attribute header + * @deferred: true if the firmware should defer applying the schedule until + * notifying all peers. For a deferred schedule update, the firmware should + * send a notification to the driver after the new schedule is applied. + * @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]; + + struct { + u8 attr_len; + u8 reserved; + u8 attr[IWL_MAX_AVAILABILITY_ATTR_LEN]; + } __packed avail_attr; + + u8 deferred; + u8 reserved[3]; +} __packed; /* NAN_SCHEDULE_CMD_API_S_VER_2 */ + /** * struct iwl_nan_peer_cmd - NAN peer command * @nmi_sta_id: NAN management station ID diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index 913f0acf172a..53a0c3b9bb33 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -546,11 +546,27 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, bool added_links = false; bool empty_schedule = true; int ret, i; + u16 cmd_size; + u32 cmd_id = WIDE_ID(MAC_CONF_GROUP, NAN_SCHEDULE_CMD); + u8 version = iwl_fw_lookup_cmd_ver(mld->fw, cmd_id, 0); if (!(changes & BSS_CHANGED_NAN_LOCAL_SCHED)) return; - for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) { + switch (version) { + case 1: + cmd_size = sizeof(struct iwl_nan_schedule_cmd_v1); + break; + case 2: + cmd_size = sizeof(struct iwl_nan_schedule_cmd); + break; + default: + IWL_ERR(mld, "NAN: unsupported NAN schedule cmd version %d\n", + version); + return; + } + + for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) { if (!sched_cfg->channels[i].chanreq.oper.chan) continue; empty_schedule = false; @@ -647,9 +663,7 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, 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); + ret = iwl_mld_send_cmd_pdu(mld, cmd_id, &cmd, cmd_size); if (ret) IWL_ERR(mld, "NAN: failed to update schedule (%d)\n", ret); From 59a26553f1a03242332092c2c5af95e66df8c556 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Tue, 12 May 2026 22:34:40 +0300 Subject: [PATCH 085/143] wifi: iwlwifi: mld: Add support for multiple NAN Management stations The following limitations arise from the current FW support for NAN: - While NAN synchronization and discovery beacons are sent internally by the firmware, the BIGTK is configured to the auxiliary station associated with the NAN operation. Thus, the beacons are transmitted unprotected. - The auxiliary station cannot be configured with support for management frame protection as this is not supported by the firmware. Thus, there is no way to protect the SDFs and the NAFs. To overcome the above limitations the firmware introduced the following new station types: - NAN broadcast station: Used for NAN synchronization and discovery. i.e., used for beacon transmissions. A BIGTK can be configured to this station and thus beacons can be transmitted with protection. - NAN management station: Used for sending SDFs and NAFs. This station can be configured with support for management frame protection etc. Modify the iwlmld logic to support the older and the newer firmware designs. As no Tx queue is needed for the NAN broadcast station, modify the internal station support to allow adding/removing a station without a queue. In addition, since no links are associated with these stations, modify the internal station support to allow adding a station without a link mask. Signed-off-by: Ilan Peer Link: https://patch.msgid.link/20260512222731.692bf627811d.I7170dbaa28a74519c012e2d7818e2999819dc478@changeid Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/mac-cfg.h | 15 ++- .../net/wireless/intel/iwlwifi/mld/iface.c | 8 ++ .../net/wireless/intel/iwlwifi/mld/iface.h | 6 + drivers/net/wireless/intel/iwlwifi/mld/nan.c | 123 +++++++++++++++--- drivers/net/wireless/intel/iwlwifi/mld/nan.h | 2 + drivers/net/wireless/intel/iwlwifi/mld/sta.c | 69 +++++++--- drivers/net/wireless/intel/iwlwifi/mld/sta.h | 13 ++ drivers/net/wireless/intel/iwlwifi/mld/tx.c | 5 +- 8 files changed, 200 insertions(+), 41 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 dd850aeebdec..1df81df5830e 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -735,6 +735,10 @@ struct iwl_link_config_cmd { * @STATION_TYPE_NAN_PEER_NDI: NAN data peer station type. A station * of this type can have any number of links (even none) set in the * link_mask. (Supported since version 3.) + * @STATION_TYPE_NAN_BCAST: NAN station used for synchronization and + * discovery. No queue is associated with this station. + * @STATION_TYPE_NAN_MGMT: NAN station used for NAN management frames, e.g., + * SDFs and NAFs. * @STATION_TYPE_MAX: maximum number of FW station types * @STATION_TYPE_AUX: aux sta. In the FW there is no need for a special type * for the aux sta, so this type is only for driver - internal use. @@ -745,6 +749,8 @@ enum iwl_fw_sta_type { STATION_TYPE_MCAST, STATION_TYPE_NAN_PEER_NMI, STATION_TYPE_NAN_PEER_NDI, + STATION_TYPE_NAN_BCAST, + STATION_TYPE_NAN_MGMT, STATION_TYPE_MAX, STATION_TYPE_AUX = STATION_TYPE_MAX /* this doesn't exist in FW */ }; /* STATION_TYPE_E_VER_1, _VER_2 */ @@ -882,7 +888,9 @@ struct iwl_sta_cfg_cmd_v2 { * ( STA_CONFIG_CMD = 0xA ) * * @sta_id: index of station in uCode's station table - * @link_mask: bitmap of link FW IDs used with this STA + * @link_mask: bitmap of link FW IDs used with this STA. Should be set to 0 + * for STATION_TYPE_NAN_BCAST and STATION_TYPE_NAN_MGMT as they are not + * associated with any link added by the driver. * @peer_mld_address: the peers mld address * @reserved_for_peer_mld_address: reserved * @peer_link_address: the address of the link that is used to communicate @@ -1213,7 +1221,8 @@ enum iwl_nan_flags { * @discovery_beacon_interval: discovery beacon interval in TUs * @cluster_id: lower last two bytes of the cluster ID, in case the local * device starts a cluster - * @sta_id: station ID of the NAN station + * @sta_id: station ID of the NAN station. Used only in version 1, in version 2 + * it is reserved. * @hb_channel: channel for 5 GHz if the device supports operation on 5 GHz. * Valid values are 44 and 149, which correspond to the 5 GHz channel, and * 0 which means that NAN operation on the 5 GHz band is disabled. @@ -1251,7 +1260,7 @@ struct iwl_nan_config_cmd { __le32 nan_attr_len; __le32 nan_vendor_elems_len; u8 beacon_data[]; -} __packed; /* NAN_CONFIG_CMD_API_S_VER_1 */ +} __packed; /* NAN_CONFIG_CMD_API_S_VER_1, NAN_CONFIG_CMD_API_S_VER_2 */ /** * struct iwl_nan_schedule_cmd_v1 - NAN schedule command diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c index 6f2590f9a69b..2a270d689de8 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c @@ -61,6 +61,11 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif) /* 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]); + + if (mld_vif->nan.bcast_sta.sta_id != IWL_INVALID_STA) + iwl_mld_free_internal_sta(mld, &mld_vif->nan.bcast_sta); + if (mld_vif->nan.mgmt_sta.sta_id != IWL_INVALID_STA) + iwl_mld_free_internal_sta(mld, &mld_vif->nan.mgmt_sta); } CLEANUP_STRUCT(mld_vif); @@ -526,6 +531,9 @@ iwl_mld_init_vif(struct iwl_mld *mld, struct ieee80211_vif *vif) 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->nan.bcast_sta); + iwl_mld_init_internal_sta(&mld_vif->nan.mgmt_sta); } 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 d6fb58e785e5..75b6727503d3 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h @@ -155,6 +155,10 @@ struct iwl_mld_emlsr { * @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 + * @nan.bcast_sta: internal station used for NAN synchronization and discovery + * activities. No queue is associated with it. + * @nan.mgmt_sta: internal station used for NAN management frames, e.g., SDFs + * and NAFs. */ struct iwl_mld_vif { /* Add here fields that need clean up on restart */ @@ -181,6 +185,8 @@ struct iwl_mld_vif { /* use only with wiphy protection */ struct iwl_mld_nan_link links[IWL_FW_MAX_LINKS]; bool mac_added; + struct iwl_mld_int_sta bcast_sta; + struct iwl_mld_int_sta mgmt_sta; } nan; struct iwl_mld_emlsr emlsr; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index 53a0c3b9bb33..351c726be51f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -50,6 +50,98 @@ static int iwl_mld_nan_send_config_cmd(struct iwl_mld *mld, return iwl_mld_send_cmd(mld, &hcmd); } +static bool iwl_mld_nan_use_nan_stations(struct iwl_mld *mld) +{ + /* + * If the FW supports version 1 of the NAN config command, it means that + * it needs to receive the station ID of the auxiliary station in the + * NAN configuration command. Otherwise, use the NAN dedicated station + * types. + */ + return iwl_fw_lookup_cmd_ver(mld->fw, + WIDE_ID(MAC_CONF_GROUP, + NAN_CFG_CMD), 1) != 1; +} + +static const struct iwl_mld_int_sta * +iwl_mld_nan_get_mgmt_sta(struct iwl_mld *mld, struct ieee80211_vif *vif) +{ + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + const struct iwl_mld_int_sta *sta; + + if (iwl_mld_nan_use_nan_stations(mld)) + sta = &mld_vif->nan.mgmt_sta; + else + sta = &mld_vif->aux_sta; + + if (WARN_ON(sta->sta_id == IWL_INVALID_STA)) + return NULL; + + return sta; +} + +int iwl_mld_nan_get_mgmt_queue(struct iwl_mld *mld, struct ieee80211_vif *vif) +{ + const struct iwl_mld_int_sta *sta = iwl_mld_nan_get_mgmt_sta(mld, vif); + + if (!sta) + return IWL_MLD_INVALID_QUEUE; + + return sta->queue_id; +} + +static void iwl_mld_nan_flush(struct iwl_mld *mld, struct ieee80211_vif *vif) +{ + const struct iwl_mld_int_sta *sta = iwl_mld_nan_get_mgmt_sta(mld, vif); + + if (!sta) + return; + + if (WARN_ON(sta->queue_id == IWL_MLD_INVALID_QUEUE)) + return; + + IWL_DEBUG_INFO(mld, "NAN: flush queues for sta=%u\n", + sta->sta_id); + + iwl_mld_flush_link_sta_txqs(mld, sta->sta_id); +} + +static void iwl_mld_nan_remove_stations(struct iwl_mld *mld, + struct ieee80211_vif *vif) +{ + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + + iwl_mld_nan_flush(mld, vif); + + if (!iwl_mld_nan_use_nan_stations(mld)) { + iwl_mld_remove_aux_sta(mld, vif); + return; + } + + iwl_mld_remove_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta); + iwl_mld_remove_nan_mgmt_sta(mld, &mld_vif->nan.mgmt_sta); +} + +static int iwl_mld_nan_add_stations(struct iwl_mld *mld, + struct ieee80211_vif *vif) +{ + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + int ret; + + if (!iwl_mld_nan_use_nan_stations(mld)) + return iwl_mld_add_aux_sta(mld, &mld_vif->aux_sta); + + ret = iwl_mld_add_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta); + if (ret) + return ret; + + ret = iwl_mld_add_nan_mgmt_sta(mld, &mld_vif->nan.mgmt_sta); + if (ret) + iwl_mld_remove_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta); + + return ret; +} + static int iwl_mld_nan_config(struct iwl_mld *mld, struct ieee80211_vif *vif, struct cfg80211_nan_conf *conf, @@ -126,7 +218,12 @@ static int iwl_mld_nan_config(struct iwl_mld *mld, conf->vendor_elems_len); } - cmd.sta_id = mld_vif->aux_sta.sta_id; + /* FW needs to know about the station ID only with version 1 of the + * NAN configuration command + */ + if (!iwl_mld_nan_use_nan_stations(mld)) + cmd.sta_id = mld_vif->aux_sta.sta_id; + return iwl_mld_nan_send_config_cmd(mld, &cmd, data, conf->extra_nan_attrs_len + conf->vendor_elems_len); @@ -136,8 +233,6 @@ int iwl_mld_start_nan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct cfg80211_nan_conf *conf) { struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); - struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); - struct iwl_mld_int_sta *aux_sta = &mld_vif->aux_sta; int ret; IWL_DEBUG_MAC80211(mld, "NAN: start: bands=0x%x\n", conf->bands); @@ -146,19 +241,20 @@ int iwl_mld_start_nan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (ret) return ret; - ret = iwl_mld_add_aux_sta(mld, aux_sta); + ret = iwl_mld_nan_add_stations(mld, vif); if (ret) goto unblock_emlsr; ret = iwl_mld_nan_config(mld, vif, conf, FW_CTXT_ACTION_ADD); if (ret) { IWL_ERR(mld, "Failed to start NAN. ret=%d\n", ret); - goto remove_aux; + goto remove_stas; } + return 0; -remove_aux: - iwl_mld_remove_aux_sta(mld, vif); +remove_stas: + iwl_mld_nan_remove_stations(mld, vif); unblock_emlsr: iwl_mld_update_emlsr_block(mld, false, IWL_MLD_EMLSR_BLOCKED_NAN); @@ -186,7 +282,6 @@ int iwl_mld_stop_nan(struct ieee80211_hw *hw, struct ieee80211_vif *vif) { struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); - struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); struct iwl_nan_config_cmd cmd = { .action = cpu_to_le32(FW_CTXT_ACTION_REMOVE), }; @@ -203,9 +298,7 @@ int iwl_mld_stop_nan(struct ieee80211_hw *hw, /* assume that higher layer guarantees that no additional frames are * added before calling this callback */ - if (!WARN_ON(mld_vif->aux_sta.sta_id == IWL_INVALID_STA)) - iwl_mld_flush_link_sta_txqs(mld, mld_vif->aux_sta.sta_id); - iwl_mld_remove_aux_sta(mld, vif); + iwl_mld_nan_remove_stations(mld, vif); /* cancel based on object type being NAN, as the NAN objects do * not have a unique identifier associated with them @@ -279,13 +372,7 @@ void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld, "NAN: DW end without NAN started\n")) return; - if (WARN_ON(mld_vif->aux_sta.sta_id == IWL_INVALID_STA)) - return; - - IWL_DEBUG_INFO(mld, "NAN: flush queues for aux sta=%u\n", - mld_vif->aux_sta.sta_id); - - iwl_mld_flush_link_sta_txqs(mld, mld_vif->aux_sta.sta_id); + iwl_mld_nan_flush(mld, mld->nan_device_vif); /* TODO: currently the notification specified the band on which the DW * ended. Need to change that to the actual channel on which the next DW diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.h b/drivers/net/wireless/intel/iwlwifi/mld/nan.h index 80e18c4ddb33..caa98dbb4a75 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.h @@ -53,4 +53,6 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, int iwl_mld_mac802111_nan_peer_sched_changed(struct ieee80211_hw *hw, struct ieee80211_sta *sta); +int iwl_mld_nan_get_mgmt_queue(struct iwl_mld *mld, struct ieee80211_vif *vif); + #endif /* __iwl_mld_nan_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.c b/drivers/net/wireless/intel/iwlwifi/mld/sta.c index f794f80b0fdd..1fae5a6ba8d4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.c @@ -430,6 +430,8 @@ static int iwl_mld_send_sta_cmd(struct iwl_mld *mld, cmd_v2->link_id = cpu_to_le32(__ffs(le32_to_cpu(cmd->link_mask))); } else if (WARN_ON(cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_PEER_NMI) && cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_PEER_NDI) && + cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_BCAST) && + cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_MGMT) && hweight32(le32_to_cpu(cmd->link_mask)) != 1)) { return -EINVAL; } @@ -1063,8 +1065,7 @@ static int iwl_mld_send_aux_sta_cmd(struct iwl_mld *mld, static int iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld, const struct iwl_mld_int_sta *internal_sta, - u8 fw_link_id, - const u8 *addr) + u32 link_mask, const u8 *addr) { struct iwl_sta_cfg_cmd cmd = {}; @@ -1072,7 +1073,7 @@ iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld, return iwl_mld_send_aux_sta_cmd(mld, internal_sta); cmd.sta_id = cpu_to_le32((u8)internal_sta->sta_id); - cmd.link_mask = cpu_to_le32(BIT(fw_link_id)); + cmd.link_mask = cpu_to_le32(link_mask); cmd.station_type = cpu_to_le32(internal_sta->sta_type); /* FW doesn't allow to add a IGTK/BIGTK if the sta isn't marked as MFP. @@ -1094,7 +1095,8 @@ iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld, static int iwl_mld_add_internal_sta(struct iwl_mld *mld, struct iwl_mld_int_sta *internal_sta, enum iwl_fw_sta_type sta_type, - u8 fw_link_id, const u8 *addr, u8 tid) + u32 link_mask, const u8 *addr, + u8 tid, bool add_txq) { int ret, queue_id; @@ -1106,11 +1108,14 @@ static int iwl_mld_add_internal_sta(struct iwl_mld *mld, internal_sta->sta_type = sta_type; - ret = iwl_mld_add_internal_sta_to_fw(mld, internal_sta, fw_link_id, + ret = iwl_mld_add_internal_sta_to_fw(mld, internal_sta, link_mask, addr); if (ret) goto err; + if (!add_txq) + return 0; + queue_id = iwl_mld_allocate_internal_txq(mld, internal_sta, tid); if (queue_id < 0) { iwl_mld_rm_sta_from_fw(mld, internal_sta->sta_id); @@ -1145,8 +1150,8 @@ int iwl_mld_add_bcast_sta(struct iwl_mld *mld, return iwl_mld_add_internal_sta(mld, &mld_link->bcast_sta, STATION_TYPE_BCAST_MGMT, - mld_link->fw_id, addr, - IWL_MGMT_TID); + BIT(mld_link->fw_id), addr, + IWL_MGMT_TID, true); } int iwl_mld_add_mcast_sta(struct iwl_mld *mld, @@ -1165,14 +1170,16 @@ int iwl_mld_add_mcast_sta(struct iwl_mld *mld, return iwl_mld_add_internal_sta(mld, &mld_link->mcast_sta, STATION_TYPE_MCAST, - mld_link->fw_id, mcast_addr, 0); + BIT(mld_link->fw_id), mcast_addr, + 0, true); } int iwl_mld_add_aux_sta(struct iwl_mld *mld, struct iwl_mld_int_sta *internal_sta) { return iwl_mld_add_internal_sta(mld, internal_sta, STATION_TYPE_AUX, - 0, NULL, IWL_MAX_TID_COUNT); + 0, NULL, IWL_MAX_TID_COUNT, + true); } int iwl_mld_add_mon_sta(struct iwl_mld *mld, @@ -1189,23 +1196,25 @@ int iwl_mld_add_mon_sta(struct iwl_mld *mld, return iwl_mld_add_internal_sta(mld, &mld_link->mon_sta, STATION_TYPE_BCAST_MGMT, - mld_link->fw_id, NULL, - IWL_MAX_TID_COUNT); + BIT(mld_link->fw_id), NULL, + IWL_MAX_TID_COUNT, + true); } static void iwl_mld_remove_internal_sta(struct iwl_mld *mld, struct iwl_mld_int_sta *internal_sta, bool flush, u8 tid) { - if (WARN_ON_ONCE(internal_sta->sta_id == IWL_INVALID_STA || - internal_sta->queue_id == IWL_MLD_INVALID_QUEUE)) + if (WARN_ON_ONCE(internal_sta->sta_id == IWL_INVALID_STA)) return; - if (flush) + if (flush && !WARN_ON_ONCE(internal_sta->queue_id == + IWL_MLD_INVALID_QUEUE)) iwl_mld_flush_link_sta_txqs(mld, internal_sta->sta_id); - iwl_mld_free_txq(mld, BIT(internal_sta->sta_id), - tid, internal_sta->queue_id); + if (internal_sta->queue_id != IWL_MLD_INVALID_QUEUE) + iwl_mld_free_txq(mld, BIT(internal_sta->sta_id), + tid, internal_sta->queue_id); iwl_mld_rm_sta_from_fw(mld, internal_sta->sta_id); @@ -1406,3 +1415,31 @@ int iwl_mld_update_link_stas(struct iwl_mld *mld, return ret; } + +int iwl_mld_add_nan_bcast_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta) +{ + const u8 bcast_addr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + return iwl_mld_add_internal_sta(mld, sta, STATION_TYPE_NAN_BCAST, + 0, bcast_addr, 0, false); +} + +int iwl_mld_add_nan_mgmt_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta) +{ + return iwl_mld_add_internal_sta(mld, sta, STATION_TYPE_NAN_MGMT, + 0, NULL, IWL_MAX_TID_COUNT, true); +} + +void iwl_mld_remove_nan_bcast_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta) +{ + iwl_mld_remove_internal_sta(mld, sta, false, 0); +} + +void iwl_mld_remove_nan_mgmt_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta) +{ + iwl_mld_remove_internal_sta(mld, sta, true, IWL_MAX_TID_COUNT); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.h b/drivers/net/wireless/intel/iwlwifi/mld/sta.h index dff14ff0a5af..df859a9e5230 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.h @@ -276,4 +276,17 @@ int iwl_mld_update_link_stas(struct iwl_mld *mld, struct ieee80211_vif *vif, struct ieee80211_sta *sta, u16 old_links, u16 new_links); + +int iwl_mld_add_nan_bcast_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta); + +int iwl_mld_add_nan_mgmt_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta); + +void iwl_mld_remove_nan_bcast_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta); + +void iwl_mld_remove_nan_mgmt_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta); + #endif /* __iwl_mld_sta_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c index 636d24633e57..39e1d959e42c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c @@ -680,11 +680,8 @@ iwl_mld_get_tx_queue_id(struct iwl_mld *mld, struct ieee80211_txq *txq, WARN_ON(!ieee80211_is_mgmt(fc)); return mld_vif->aux_sta.queue_id; case NL80211_IFTYPE_NAN: - mld_vif = iwl_mld_vif_from_mac80211(info->control.vif); - WARN_ON(!ieee80211_is_mgmt(fc)); - - return mld_vif->aux_sta.queue_id; + return iwl_mld_nan_get_mgmt_queue(mld, info->control.vif); default: WARN_ONCE(1, "Unsupported vif type\n"); break; From ace3199321869dc2c69cf6a36478aecc097effd6 Mon Sep 17 00:00:00 2001 From: Avinash Bhatt Date: Tue, 12 May 2026 22:34:41 +0300 Subject: [PATCH 086/143] wifi: iwlwifi: mld: Add KUnit tests for channel-load thresholds Add a KUnit test suite for iwl_mld_chan_load_requires_scan, covering level-up, level-down, and no-change transitions. The test directly sets channel-load values, validating scan-trigger decisions and updated load levels Signed-off-by: Avinash Bhatt Link: https://patch.msgid.link/20260512222731.4dd3ccaffc46.I9c1f210e5ef25248097a226f4b3a2af5fbcf3c87@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.c | 1 + .../wireless/intel/iwlwifi/mld/tests/Makefile | 1 + .../iwlwifi/mld/tests/chan_load_thresh.c | 139 ++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index c07a0d6b0bd4..805f2e2eac38 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -852,6 +852,7 @@ bool iwl_mld_chan_load_requires_scan(struct iwl_mld *mld, return scan_trig; } +EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_chan_load_requires_scan); static unsigned int iwl_mld_get_default_chan_load(struct ieee80211_bss_conf *link_conf) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile b/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile index 36317feb923b..efa61638b8ee 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause iwlmld-tests-y += module.o hcmd.o utils.o link.o rx.o agg.o link-selection.o +iwlmld-tests-y += chan_load_thresh.o ccflags-y += -I$(src)/../ obj-$(CONFIG_IWLWIFI_KUNIT_TESTS) += iwlmld-tests.o diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c new file mode 100644 index 000000000000..87e29e09949b --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * KUnit tests for channel helper functions + * + * Copyright (C) 2026 Intel Corporation + */ +#include +#include "mld.h" +#include "link.h" +#include "iface.h" +#include "utils.h" + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +struct test_chan_load_case { + const char *desc; + u32 load; + enum iwl_mld_link_chan_load_level old_lvl; + enum iwl_mld_link_chan_load_level expected_lvl; + bool expected_scan_trig; +}; + +static const struct test_chan_load_case test_chan_load_thresh_cases[] = { + /* Level-up transitions */ + { + .desc = "Transition NONE->NONE", + .load = 20, + .old_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_scan_trig = false, + }, + { + .desc = "Transition NONE->LVL1", + .load = 50, + .old_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_lvl = LINK_CHAN_LOAD_LVL1, + .expected_scan_trig = true, + }, + { + .desc = "Transition LVL1->LVL2", + .load = 75, + .old_lvl = LINK_CHAN_LOAD_LVL1, + .expected_lvl = LINK_CHAN_LOAD_LVL2, + .expected_scan_trig = true, + }, + { + .desc = "Transition LVL2->LVL3", + .load = 90, + .old_lvl = LINK_CHAN_LOAD_LVL2, + .expected_lvl = LINK_CHAN_LOAD_LVL3, + .expected_scan_trig = true, + }, + + /* Level-down transitions */ + { + .desc = "Transition LVL1->NONE", + .load = 30, + .old_lvl = LINK_CHAN_LOAD_LVL1, + .expected_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_scan_trig = false, + }, + { + .desc = "Transition LVL2->LVL1", + .load = 60, + .old_lvl = LINK_CHAN_LOAD_LVL2, + .expected_lvl = LINK_CHAN_LOAD_LVL1, + .expected_scan_trig = false, + }, + { + .desc = "Transition LVL3->LVL2", + .load = 70, + .old_lvl = LINK_CHAN_LOAD_LVL3, + .expected_lvl = LINK_CHAN_LOAD_LVL2, + .expected_scan_trig = false, + }, + + /* No change */ + { + .desc = "Transition LVL2->LVL2", + .load = 72, + .old_lvl = LINK_CHAN_LOAD_LVL2, + .expected_lvl = LINK_CHAN_LOAD_LVL2, + .expected_scan_trig = false, + }, +}; + +KUNIT_ARRAY_PARAM_DESC(test_chan_load_thresh_cases, + test_chan_load_thresh_cases, desc); + +static void test_chan_load_thresholds(struct kunit *test) +{ + const struct test_chan_load_case *tc = test->param_value; + struct iwl_mld *mld = test->priv; + struct ieee80211_vif *vif; + struct iwl_mld_vif *mld_vif; + struct ieee80211_bss_conf *link_conf; + struct iwl_mld_link *mld_link; + struct iwl_mld_kunit_link assoc_link = { + .id = 0, + .chandef = &chandef_6ghz_160mhz, + }; + bool scan_trig; + u32 chan_load; + + /* Setup associated non-MLO station */ + vif = iwlmld_kunit_setup_non_mlo_assoc(&assoc_link); + mld_vif = iwl_mld_vif_from_mac80211(vif); + + link_conf = &vif->bss_conf; + mld_link = &mld_vif->deflink; + + chan_load = tc->load; + mld_link->chan_load_lvl = tc->old_lvl; + + /* Execute function under test */ + wiphy_lock(mld->wiphy); + scan_trig = iwl_mld_chan_load_requires_scan(mld, link_conf, chan_load); + wiphy_unlock(mld->wiphy); + + /* Check return value */ + KUNIT_EXPECT_EQ(test, tc->expected_scan_trig, scan_trig); + + /* Check updated channel-load level */ + KUNIT_EXPECT_EQ(test, tc->expected_lvl, mld_link->chan_load_lvl); +} + +static struct kunit_case chan_load_thresh_test_cases[] = { + KUNIT_CASE_PARAM(test_chan_load_thresholds, + test_chan_load_thresh_cases_gen_params), + {} +}; + +static struct kunit_suite chan_load_thresh_test_suite = { + .name = "iwl_mld_chan_load_threshold_tests", + .init = iwlmld_kunit_test_init, + .test_cases = chan_load_thresh_test_cases, +}; + +kunit_test_suite(chan_load_thresh_test_suite); From dfc22f0dea00da50abd2ab36ba3c7dfebe402bf2 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 13 May 2026 08:43:48 +0300 Subject: [PATCH 087/143] wifi: iwlwifi: led_compensation is needed for iwldvm only Keep that setting only for devices that use iwldvm. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260513084215.a618af54e3ad.I635c3f49c317960dc8751ddeb5a4a3114d52f406@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/cfg/22000.c | 3 +-- drivers/net/wireless/intel/iwlwifi/cfg/7000.c | 3 +-- drivers/net/wireless/intel/iwlwifi/cfg/8000.c | 3 +-- drivers/net/wireless/intel/iwlwifi/cfg/9000.c | 3 +-- drivers/net/wireless/intel/iwlwifi/cfg/ax210.c | 3 +-- drivers/net/wireless/intel/iwlwifi/cfg/bz.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/dr.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/sc.c | 1 - 8 files changed, 5 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c index f0453f3f6ba6..5b2a5e254d6e 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include #include @@ -28,7 +28,6 @@ static const struct iwl_family_base_params iwl_22000_base = { .num_of_queues = 512, .max_tfd_queue_size = 256, .shadow_ram_support = true, - .led_compensation = 57, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/7000.c b/drivers/net/wireless/intel/iwlwifi/cfg/7000.c index f987ad3192c1..2936ccf1dbb2 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/7000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/7000.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2020, 2023, 2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2020, 2023, 2025-2026 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2015 Intel Deutschland GmbH */ @@ -54,7 +54,6 @@ static const struct iwl_family_base_params iwl7000_base = { .num_of_queues = 31, .max_tfd_queue_size = 256, .shadow_ram_support = true, - .led_compensation = 57, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/8000.c b/drivers/net/wireless/intel/iwlwifi/cfg/8000.c index 3c844cd419e8..3e936ba7f871 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/8000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/8000.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2014, 2018-2020, 2023, 2025 Intel Corporation + * Copyright (C) 2014, 2018-2020, 2023, 2025-2026 Intel Corporation * Copyright (C) 2014-2015 Intel Mobile Communications GmbH * Copyright (C) 2016 Intel Deutschland GmbH */ @@ -40,7 +40,6 @@ static const struct iwl_family_base_params iwl8000_base = { .num_of_queues = 31, .max_tfd_queue_size = 256, .shadow_ram_support = true, - .led_compensation = 57, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/9000.c b/drivers/net/wireless/intel/iwlwifi/cfg/9000.c index 5872fc9b8caf..7194f681cec2 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/9000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/9000.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2021, 2023, 2025 Intel Corporation + * Copyright (C) 2018-2021, 2023, 2025-2026 Intel Corporation */ #include #include @@ -31,7 +31,6 @@ static const struct iwl_family_base_params iwl9000_base = { .num_of_queues = 31, .max_tfd_queue_size = 256, .shadow_ram_support = true, - .led_compensation = 57, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c index 582f61661062..1f862748e33d 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include #include @@ -23,7 +23,6 @@ static const struct iwl_family_base_params iwl_ax210_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, .shadow_ram_support = true, - .led_compensation = 57, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index c060b12cc952..7b3837b2a8e5 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -23,7 +23,6 @@ static const struct iwl_family_base_params iwl_bz_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, .shadow_ram_support = true, - .led_compensation = 57, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c index 3ccbb21276c1..12feac3bc0a2 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c @@ -24,7 +24,6 @@ static const struct iwl_family_base_params iwl_dr_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, .shadow_ram_support = true, - .led_compensation = 57, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index 04c726e8c283..f82ba1d64b63 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -29,7 +29,6 @@ static const struct iwl_family_base_params iwl_sc_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, .shadow_ram_support = true, - .led_compensation = 57, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, From bbdc473327de4dbc6ad410cf668a2b0728fb1ec1 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 13 May 2026 08:43:49 +0300 Subject: [PATCH 088/143] wifi: iwlwifi: shadow_ram_support is needed for iwldvm only. Keep that setting only for devices that use iwldvm. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260513084215.365f3984c554.I29138e178dea6fc371a3907013bb3825c4e82475@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/cfg/22000.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/7000.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/8000.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/9000.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/ax210.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/bz.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/dr.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/sc.c | 1 - 8 files changed, 8 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c index 5b2a5e254d6e..0ce8d6229bab 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c @@ -27,7 +27,6 @@ static const struct iwl_family_base_params iwl_22000_base = { .num_of_queues = 512, .max_tfd_queue_size = 256, - .shadow_ram_support = true, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/7000.c b/drivers/net/wireless/intel/iwlwifi/cfg/7000.c index 2936ccf1dbb2..c55995fdf1c7 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/7000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/7000.c @@ -53,7 +53,6 @@ static const struct iwl_family_base_params iwl7000_base = { .eeprom_size = OTP_LOW_IMAGE_SIZE_16K, .num_of_queues = 31, .max_tfd_queue_size = 256, - .shadow_ram_support = true, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/8000.c b/drivers/net/wireless/intel/iwlwifi/cfg/8000.c index 3e936ba7f871..182a8bf5bcf0 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/8000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/8000.c @@ -39,7 +39,6 @@ static const struct iwl_family_base_params iwl8000_base = { .eeprom_size = OTP_LOW_IMAGE_SIZE_32K, .num_of_queues = 31, .max_tfd_queue_size = 256, - .shadow_ram_support = true, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/9000.c b/drivers/net/wireless/intel/iwlwifi/cfg/9000.c index 7194f681cec2..55db225a02fc 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/9000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/9000.c @@ -30,7 +30,6 @@ static const struct iwl_family_base_params iwl9000_base = { .eeprom_size = OTP_LOW_IMAGE_SIZE_32K, .num_of_queues = 31, .max_tfd_queue_size = 256, - .shadow_ram_support = true, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c index 1f862748e33d..b355384ee210 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c @@ -22,7 +22,6 @@ static const struct iwl_family_base_params iwl_ax210_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, - .shadow_ram_support = true, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index 7b3837b2a8e5..589bc88727a5 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -22,7 +22,6 @@ static const struct iwl_family_base_params iwl_bz_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, - .shadow_ram_support = true, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c index 12feac3bc0a2..ecd48aac4e2a 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c @@ -23,7 +23,6 @@ static const struct iwl_family_base_params iwl_dr_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, - .shadow_ram_support = true, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index f82ba1d64b63..75a9a1c56dd3 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -28,7 +28,6 @@ static const struct iwl_family_base_params iwl_sc_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, - .shadow_ram_support = true, .wd_timeout = IWL_LONG_WD_TIMEOUT, .max_event_log_size = 512, .shadow_reg_enable = true, From ca6806f199b876cf0427250d95f3ff0254e2c343 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 13 May 2026 08:43:50 +0300 Subject: [PATCH 089/143] wifi: iwlwifi: max_event_log_size is needed for iwldvm only This is used to set init_evtlog_size and inst_evtlog_size in case those are not advertised by the firmware's TLVs. Those two fields are used only in iwldvm. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260513084215.a83033e43988.I8a55f8ff0e7bc5f42a49b15facd8558708b7d880@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/cfg/22000.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/7000.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/8000.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/9000.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/ax210.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/bz.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/dr.c | 1 - drivers/net/wireless/intel/iwlwifi/cfg/sc.c | 1 - 8 files changed, 8 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c index 0ce8d6229bab..563c596240b2 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c @@ -28,7 +28,6 @@ static const struct iwl_family_base_params iwl_22000_base = { .num_of_queues = 512, .max_tfd_queue_size = 256, .wd_timeout = IWL_LONG_WD_TIMEOUT, - .max_event_log_size = 512, .shadow_reg_enable = true, .pcie_l1_allowed = true, .smem_offset = IWL_22000_SMEM_OFFSET, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/7000.c b/drivers/net/wireless/intel/iwlwifi/cfg/7000.c index c55995fdf1c7..1be72d71fccf 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/7000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/7000.c @@ -54,7 +54,6 @@ static const struct iwl_family_base_params iwl7000_base = { .num_of_queues = 31, .max_tfd_queue_size = 256, .wd_timeout = IWL_LONG_WD_TIMEOUT, - .max_event_log_size = 512, .shadow_reg_enable = true, .pcie_l1_allowed = true, .apmg_wake_up_wa = true, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/8000.c b/drivers/net/wireless/intel/iwlwifi/cfg/8000.c index 182a8bf5bcf0..834aa520ab0c 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/8000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/8000.c @@ -40,7 +40,6 @@ static const struct iwl_family_base_params iwl8000_base = { .num_of_queues = 31, .max_tfd_queue_size = 256, .wd_timeout = IWL_LONG_WD_TIMEOUT, - .max_event_log_size = 512, .shadow_reg_enable = true, .pcie_l1_allowed = true, .nvm_hw_section_num = 10, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/9000.c b/drivers/net/wireless/intel/iwlwifi/cfg/9000.c index 55db225a02fc..2954434ce851 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/9000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/9000.c @@ -31,7 +31,6 @@ static const struct iwl_family_base_params iwl9000_base = { .num_of_queues = 31, .max_tfd_queue_size = 256, .wd_timeout = IWL_LONG_WD_TIMEOUT, - .max_event_log_size = 512, .shadow_reg_enable = true, .pcie_l1_allowed = true, .smem_offset = IWL9000_SMEM_OFFSET, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c index b355384ee210..ee24ded67069 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c @@ -23,7 +23,6 @@ static const struct iwl_family_base_params iwl_ax210_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, .wd_timeout = IWL_LONG_WD_TIMEOUT, - .max_event_log_size = 512, .shadow_reg_enable = true, .pcie_l1_allowed = true, .smem_offset = IWL_AX210_SMEM_OFFSET, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index 589bc88727a5..1b14ba5a09f9 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -23,7 +23,6 @@ static const struct iwl_family_base_params iwl_bz_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, .wd_timeout = IWL_LONG_WD_TIMEOUT, - .max_event_log_size = 512, .shadow_reg_enable = true, .pcie_l1_allowed = true, .smem_offset = IWL_BZ_SMEM_OFFSET, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c index ecd48aac4e2a..afde84ad0493 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c @@ -24,7 +24,6 @@ static const struct iwl_family_base_params iwl_dr_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, .wd_timeout = IWL_LONG_WD_TIMEOUT, - .max_event_log_size = 512, .shadow_reg_enable = true, .pcie_l1_allowed = true, .smem_offset = IWL_DR_SMEM_OFFSET, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index 75a9a1c56dd3..52a08105d0c4 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -29,7 +29,6 @@ static const struct iwl_family_base_params iwl_sc_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, .wd_timeout = IWL_LONG_WD_TIMEOUT, - .max_event_log_size = 512, .shadow_reg_enable = true, .pcie_l1_allowed = true, .smem_offset = IWL_SC_SMEM_OFFSET, From 54d48636dfd5d16f7b2f9f2225f04c7a51f2ab55 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 13 May 2026 08:43:51 +0300 Subject: [PATCH 090/143] wifi: iwlwifi: smem_offset smem_len are not needed from 22000 and up This was used for the old (pre-ini) debug mechanism which is now obsolete. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260513084215.18095aac398a.I0bdc5120b9b4226692dcffb1c5971f617d99e632@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/cfg/22000.c | 6 ------ drivers/net/wireless/intel/iwlwifi/cfg/ax210.c | 6 ------ drivers/net/wireless/intel/iwlwifi/cfg/bz.c | 6 ------ drivers/net/wireless/intel/iwlwifi/cfg/dr.c | 6 ------ drivers/net/wireless/intel/iwlwifi/cfg/sc.c | 6 ------ 5 files changed, 30 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c index 563c596240b2..e929a08e7585 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c @@ -15,10 +15,6 @@ /* Lowest firmware API version supported */ #define IWL_22000_UCODE_API_MIN 77 -/* Memory offsets and lengths */ -#define IWL_22000_SMEM_OFFSET 0x400000 -#define IWL_22000_SMEM_LEN 0xD0000 - #define IWL_CC_A_FW_PRE "iwlwifi-cc-a0" #define IWL_CC_A_MODULE_FIRMWARE(api) \ @@ -30,8 +26,6 @@ static const struct iwl_family_base_params iwl_22000_base = { .wd_timeout = IWL_LONG_WD_TIMEOUT, .shadow_reg_enable = true, .pcie_l1_allowed = true, - .smem_offset = IWL_22000_SMEM_OFFSET, - .smem_len = IWL_22000_SMEM_LEN, .features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM, .apmg_not_supported = true, .mac_addr_from_csr = 0x380, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c index ee24ded67069..ec1951a94e23 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c @@ -15,18 +15,12 @@ /* Lowest firmware API version supported */ #define IWL_AX210_UCODE_API_MIN 89 -/* Memory offsets and lengths */ -#define IWL_AX210_SMEM_OFFSET 0x400000 -#define IWL_AX210_SMEM_LEN 0xD0000 - static const struct iwl_family_base_params iwl_ax210_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, .wd_timeout = IWL_LONG_WD_TIMEOUT, .shadow_reg_enable = true, .pcie_l1_allowed = true, - .smem_offset = IWL_AX210_SMEM_OFFSET, - .smem_len = IWL_AX210_SMEM_LEN, .features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM, .apmg_not_supported = true, .mac_addr_from_csr = 0x380, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index 1b14ba5a09f9..a70b356d9576 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -15,18 +15,12 @@ /* Lowest firmware core release supported */ #define IWL_BZ_UCODE_CORE_MIN 101 -/* Memory offsets and lengths */ -#define IWL_BZ_SMEM_OFFSET 0x400000 -#define IWL_BZ_SMEM_LEN 0xD0000 - static const struct iwl_family_base_params iwl_bz_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, .wd_timeout = IWL_LONG_WD_TIMEOUT, .shadow_reg_enable = true, .pcie_l1_allowed = true, - .smem_offset = IWL_BZ_SMEM_OFFSET, - .smem_len = IWL_BZ_SMEM_LEN, .apmg_not_supported = true, .mac_addr_from_csr = 0x30, .d3_debug_data_base_addr = 0x401000, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c index afde84ad0493..e95112fdc664 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c @@ -14,10 +14,6 @@ /* Lowest firmware core release supported */ #define IWL_DR_UCODE_CORE_MIN 101 -/* Memory offsets and lengths */ -#define IWL_DR_SMEM_OFFSET 0x400000 -#define IWL_DR_SMEM_LEN 0xD0000 - #define IWL_DR_A_PE_A_FW_PRE "iwlwifi-dr-a0-pe-a0" static const struct iwl_family_base_params iwl_dr_base = { @@ -26,8 +22,6 @@ static const struct iwl_family_base_params iwl_dr_base = { .wd_timeout = IWL_LONG_WD_TIMEOUT, .shadow_reg_enable = true, .pcie_l1_allowed = true, - .smem_offset = IWL_DR_SMEM_OFFSET, - .smem_len = IWL_DR_SMEM_LEN, .apmg_not_supported = true, .mac_addr_from_csr = 0x30, .d3_debug_data_base_addr = 0x401000, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index 52a08105d0c4..d1e5cef42fff 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -15,10 +15,6 @@ /* Lowest firmware core release supported */ #define IWL_SC_UCODE_CORE_MIN 101 -/* Memory offsets and lengths */ -#define IWL_SC_SMEM_OFFSET 0x400000 -#define IWL_SC_SMEM_LEN 0xD0000 - #define IWL_SC_A_FM_B_FW_PRE "iwlwifi-sc-a0-fm-b0" #define IWL_SC_A_FM_C_FW_PRE "iwlwifi-sc-a0-fm-c0" #define IWL_SC_A_WH_A_FW_PRE "iwlwifi-sc-a0-wh-a0" @@ -31,8 +27,6 @@ static const struct iwl_family_base_params iwl_sc_base = { .wd_timeout = IWL_LONG_WD_TIMEOUT, .shadow_reg_enable = true, .pcie_l1_allowed = true, - .smem_offset = IWL_SC_SMEM_OFFSET, - .smem_len = IWL_SC_SMEM_LEN, .apmg_not_supported = true, .mac_addr_from_csr = 0x30, .d3_debug_data_base_addr = 0x401000, From ba3b38d38a33367882b8e436ff6af117d0f112da Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Wed, 13 May 2026 08:43:52 +0300 Subject: [PATCH 091/143] wifi: iwlwifi: mld: add handler for NAN ULW attribute notification When a notification about a new ULW attribute arrives, send it to user space so the ULW attribute can be added to the relevant frames (e.g. SDF). Signed-off-by: Avraham Stern Link: https://patch.msgid.link/20260513084215.76d980e195a7.Ide4aaf4553a3980e6990485cd37204a922c36913@changeid Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/mac-cfg.h | 20 +++++++++++ drivers/net/wireless/intel/iwlwifi/mld/mld.c | 1 + drivers/net/wireless/intel/iwlwifi/mld/nan.c | 33 +++++++++++++++++++ drivers/net/wireless/intel/iwlwifi/mld/nan.h | 5 +++ .../net/wireless/intel/iwlwifi/mld/notif.c | 3 ++ 5 files changed, 62 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 1df81df5830e..dde6cfd9d286 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -80,6 +80,10 @@ enum iwl_mac_conf_subcmd_ids { * @NAN_PEER_CMD: &struct iwl_nan_peer_cmd */ NAN_PEER_CMD = 0x14, + /** + * @NAN_ULW_ATTR_NOTIF: &struct iwl_nan_ulw_attr_notif + */ + NAN_ULW_ATTR_NOTIF = 0xf2, /** * @NAN_DW_END_NOTIF: &struct iwl_nan_dw_end_notif */ @@ -1393,4 +1397,20 @@ struct iwl_nan_dw_end_notif { u8 reserved[3]; } __packed; /* NAN_DW_END_NTF_API_S_VER_1 */ +#define IWL_NAN_MAX_ENDLESS_ULW_ATTR_LEN 48 + +/** + * struct iwl_nan_ulw_attr_notif - sent to notify the host of a change in the + * ULW attribute + * + * @attr_len: length of the ULW attribute in bytes + * @reserved: reserved + * @attr: the ULW attribute including the attribute header + */ +struct iwl_nan_ulw_attr_notif { + u8 attr_len; + u8 reserved[3]; + u8 attr[IWL_NAN_MAX_ENDLESS_ULW_ATTR_LEN]; +} __packed; /* NAN_ULW_ATTR_NOTIF_API_S_VER_1 */ + #endif /* __iwl_fw_api_mac_cfg_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 3caa76b9b2cb..054bb1134425 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(NAN_CFG_CMD), HCMD_NAME(NAN_SCHEDULE_CMD), HCMD_NAME(NAN_PEER_CMD), + HCMD_NAME(NAN_ULW_ATTR_NOTIF), 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 351c726be51f..8f35687c58b1 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -353,6 +353,39 @@ bool iwl_mld_cancel_nan_dw_end_notif(struct iwl_mld *mld, return true; } +bool iwl_mld_cancel_nan_ulw_attr_notif(struct iwl_mld *mld, + struct iwl_rx_packet *pkt, + u32 obj_id) +{ + return true; +} + +void iwl_mld_handle_nan_ulw_attr_notif(struct iwl_mld *mld, + struct iwl_rx_packet *pkt) +{ + struct iwl_nan_ulw_attr_notif *notif = (void *)pkt->data; + struct wireless_dev *wdev; + + IWL_DEBUG_INFO(mld, "NAN: ULW attr update: len=%u\n", notif->attr_len); + + if (IWL_FW_CHECK(mld, !mld->nan_device_vif, + "NAN: ULW attr update without NAN vif\n")) + return; + + if (IWL_FW_CHECK(mld, !ieee80211_vif_nan_started(mld->nan_device_vif), + "NAN: ULW attr update without NAN started\n")) + return; + + if (IWL_FW_CHECK(mld, + notif->attr_len > IWL_NAN_MAX_ENDLESS_ULW_ATTR_LEN, + "NAN: ULW attr update invalid len %u\n", + notif->attr_len)) + return; + + wdev = ieee80211_vif_to_wdev(mld->nan_device_vif); + cfg80211_nan_ulw_update(wdev, notif->attr, notif->attr_len, GFP_KERNEL); +} + void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt) { diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.h b/drivers/net/wireless/intel/iwlwifi/mld/nan.h index caa98dbb4a75..40152dc7d906 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.h @@ -38,11 +38,16 @@ int iwl_mld_stop_nan(struct ieee80211_hw *hw, struct ieee80211_vif *vif); void iwl_mld_handle_nan_cluster_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt); +void iwl_mld_handle_nan_ulw_attr_notif(struct iwl_mld *mld, + struct iwl_rx_packet *pkt); void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt); bool iwl_mld_cancel_nan_cluster_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt, u32 obj_id); +bool iwl_mld_cancel_nan_ulw_attr_notif(struct iwl_mld *mld, + struct iwl_rx_packet *pkt, + u32 obj_id); bool iwl_mld_cancel_nan_dw_end_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt, u32 obj_id); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index 6170953982b7..a5074bbc7e24 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -346,6 +346,7 @@ CMD_VERSIONS(time_sync_confirm_notif, CMD_VERSIONS(ftm_resp_notif, CMD_VER_ENTRY(10, iwl_tof_range_rsp_ntfy)) CMD_VERSIONS(beacon_filter_notif, CMD_VER_ENTRY(2, iwl_beacon_filter_notif)) CMD_VERSIONS(nan_cluster_notif, CMD_VER_ENTRY(1, iwl_nan_cluster_notif)) +CMD_VERSIONS(nan_ulw_attr_notif, CMD_VER_ENTRY(1, iwl_nan_ulw_attr_notif)) CMD_VERSIONS(nan_dw_end_notif, CMD_VER_ENTRY(1, iwl_nan_dw_end_notif)) DEFINE_SIMPLE_CANCELLATION(session_prot, iwl_session_prot_notif, mac_link_id) @@ -463,6 +464,8 @@ const struct iwl_rx_handler iwl_mld_rx_handlers[] = { ftm_resp_notif) RX_HANDLER_OF_NAN(MAC_CONF_GROUP, NAN_JOINED_CLUSTER_NOTIF, nan_cluster_notif) + RX_HANDLER_OF_NAN(MAC_CONF_GROUP, NAN_ULW_ATTR_NOTIF, + nan_ulw_attr_notif) RX_HANDLER_OF_NAN(MAC_CONF_GROUP, NAN_DW_END_NOTIF, nan_dw_end_notif) }; From 7f7a450ed83a2a700b10b23272ea9a419d27f9bb Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 13 May 2026 08:43:53 +0300 Subject: [PATCH 092/143] wifi: iwlwifi: mld: support NPCA capability for UHR devices UHR devices have NPCA capability, so implement the necessary configuration and set the capability bit. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260513084215.8c88bf84af91.If59ceaa49c2431e08941bcba3b2e75bb93aaad63@changeid Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/mac-cfg.h | 4 +- .../wireless/intel/iwlwifi/iwl-nvm-parse.c | 3 ++ drivers/net/wireless/intel/iwlwifi/mld/link.c | 15 +++++++ .../net/wireless/intel/iwlwifi/mld/mac80211.c | 39 ++++++++++++++++++- drivers/net/wireless/intel/iwlwifi/mld/phy.c | 24 ++++++++++-- 5 files changed, 80 insertions(+), 5 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 dde6cfd9d286..fa2fc6a92e60 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -549,12 +549,14 @@ enum iwl_link_ctx_protection_flags { * radar pulses). * @LINK_FLG_NDP_FEEDBACK_ENABLED: mark support for NDP feedback and change * of threshold + * @LINK_FLG_NPCA: NPCA enabled */ enum iwl_link_ctx_flags { LINK_FLG_BSS_COLOR_DIS = BIT(0), LINK_FLG_MU_EDCA_CW = BIT(1), LINK_FLG_RU_2MHZ_BLOCK = BIT(2), LINK_FLG_NDP_FEEDBACK_ENABLED = BIT(3), + LINK_FLG_NPCA = BIT(4), }; /* LINK_CONTEXT_FLAG_E_VER_1 */ /** @@ -591,7 +593,7 @@ enum iwl_npca_flags { * @initial_qsrc: Indicates the value that is used to initialize the * EDCAF QSRC[AC] variables * @min_dur_threshold: minimum PPDU time to switch to the non-primary - * NPCA channel (usec) + * NPCA channel (spec representation) * @flags: NPCA flags, see &enum iwl_npca_flags * @reserved: reserved for alignment purposes */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index 38ff6f944add..4e8222ffe732 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -695,6 +695,9 @@ static const struct ieee80211_sband_iftype_data iwl_iftype_cap[] = { .has_uhr = true, .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | IEEE80211_UHR_PHY_CAP_ELR_TX, + .mac.mac_cap = { + [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP, + }, }, }, { diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index 805f2e2eac38..9ef39b03e67f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -374,6 +374,21 @@ iwl_mld_change_link_in_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link, if (WARN_ON(changes & LINK_CONTEXT_MODIFY_EHT_PARAMS)) changes &= ~LINK_CONTEXT_MODIFY_EHT_PARAMS; + if (link->uhr_support && link->npca.enabled) { + flags |= LINK_FLG_NPCA; + if (link->npca.moplen) + cmd.npca_params.flags |= IWL_NPCA_FLAG_MAC_HDR_BASED; + cmd.npca_params.dis_subch_bmap = + cpu_to_le16(link->chanreq.oper.npca_punctured); + cmd.npca_params.initial_qsrc = link->npca.init_qsrc; + cmd.npca_params.min_dur_threshold = link->npca.min_dur_thresh; + /* spec/mac80211 have these in units of 4 usec */ + cmd.npca_params.switch_delay = + 4 * link->npca.switch_delay; + cmd.npca_params.switch_back_delay = + 4 * link->npca.switch_back_delay; + } + send_cmd: cmd.modify_mask = cpu_to_le32(changes); cmd.flags = cpu_to_le32(flags); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 41bc47e4e00c..1f2843af39c1 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -981,6 +981,30 @@ void iwl_mld_remove_chanctx(struct ieee80211_hw *hw, mld->used_phy_ids &= ~BIT(phy->fw_id); } +static void +iwl_mld_update_link_npca_puncturing(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); + struct ieee80211_vif *vif; + + for_each_active_interface(vif, hw) { + struct ieee80211_bss_conf *link; + int link_id; + + for_each_vif_active_link(vif, link, link_id) { + if (rcu_access_pointer(link->chanctx_conf) != ctx) + continue; + + if (!link->npca.enabled) + continue; + + iwl_mld_change_link_in_fw(mld, link, + LINK_CONTEXT_MODIFY_UHR_PARAMS); + } + } +} + static void iwl_mld_change_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *ctx, u32 changed) @@ -996,9 +1020,19 @@ void iwl_mld_change_chanctx(struct ieee80211_hw *hw, IEEE80211_CHANCTX_CHANGE_CHANNEL))) return; + /* NPCA puncturing is in link API for FW */ + if (changed & IEEE80211_CHANCTX_CHANGE_NPCA_PUNCT) { + iwl_mld_update_link_npca_puncturing(hw, ctx); + changed &= ~IEEE80211_CHANCTX_CHANGE_NPCA_PUNCT; + } + /* Check if a FW update is required */ - if (changed & IEEE80211_CHANCTX_CHANGE_AP) + if (!changed) + return; + + if (changed & IEEE80211_CHANCTX_CHANGE_AP || + changed & IEEE80211_CHANCTX_CHANGE_NPCA) goto update; if (chandef->chan == phy->chandef.chan && @@ -1255,6 +1289,9 @@ u32 iwl_mld_link_changed_mapping(struct iwl_mld *mld, link_changes |= LINK_CONTEXT_MODIFY_HE_PARAMS; } + if (changes & BSS_CHANGED_NPCA) + link_changes |= LINK_CONTEXT_MODIFY_UHR_PARAMS; + return link_changes; } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/phy.c b/drivers/net/wireless/intel/iwlwifi/mld/phy.c index 1d93fb9e4dbf..59bf088ead84 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/phy.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/phy.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 @@ -99,9 +99,9 @@ iwl_mld_nl80211_width_to_fw(enum nl80211_chan_width width) /* Maps the driver specific control channel position (relative to the center * freq) definitions to the fw values */ -u8 iwl_mld_get_fw_ctrl_pos(const struct cfg80211_chan_def *chandef) +static u8 _iwl_mld_get_fw_ctrl_pos(u32 control, u32 cf1) { - int offs = chandef->chan->center_freq - chandef->center_freq1; + int offs = control - cf1; int abs_offs = abs(offs); u8 ret; @@ -127,6 +127,12 @@ u8 iwl_mld_get_fw_ctrl_pos(const struct cfg80211_chan_def *chandef) return ret; } +u8 iwl_mld_get_fw_ctrl_pos(const struct cfg80211_chan_def *chandef) +{ + return _iwl_mld_get_fw_ctrl_pos(chandef->chan->center_freq, + chandef->center_freq1); +} + int iwl_mld_phy_fw_action(struct iwl_mld *mld, struct ieee80211_chanctx_conf *ctx, u32 action) { @@ -150,6 +156,18 @@ int iwl_mld_phy_fw_action(struct iwl_mld *mld, cmd.sbb_ctrl_channel_loc = iwl_mld_get_fw_ctrl_pos(&ctx->ap); } + /* + * Set NPCA channel if NPCA is used; if not used, just set it to an + * arbitrary channel on the other side to help firmware. + */ + if (chandef->npca_chan) + cmd.secondary_ctrl_chnl_loc = + _iwl_mld_get_fw_ctrl_pos(chandef->npca_chan->center_freq, + chandef->center_freq1); + else + cmd.secondary_ctrl_chnl_loc = + cmd.ci.ctrl_pos ^ IWL_PHY_CTRL_POS_ABOVE; + ret = iwl_mld_send_cmd_pdu(mld, PHY_CONTEXT_CMD, &cmd); if (ret) IWL_ERR(mld, "Failed to send PHY_CONTEXT_CMD ret = %d\n", ret); From f5d199b14c06828d71775504fcc8461369a770fc Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 13 May 2026 08:43:54 +0300 Subject: [PATCH 093/143] wifi: iwlwifi: mld: implement UHR DPS For UHR, implement dynamic power save (DPS). Since the firmware handles most of it, simply advertise DPS capability and set the corresponding link flag when the AP has advertised that it is a DPS assisting AP. DPS assisting is currently not supported. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260513084215.69c5657f2b0c.I0e0dfcd2e2641a5656a14f01ce696443ae10bc4a@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h | 2 ++ drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c | 3 ++- drivers/net/wireless/intel/iwlwifi/mld/link.c | 10 +++++++++- 3 files changed, 13 insertions(+), 2 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 fa2fc6a92e60..604281eb29c7 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -550,6 +550,7 @@ enum iwl_link_ctx_protection_flags { * @LINK_FLG_NDP_FEEDBACK_ENABLED: mark support for NDP feedback and change * of threshold * @LINK_FLG_NPCA: NPCA enabled + * @LINK_FLG_DPS: AP is a DPS assisting AP */ enum iwl_link_ctx_flags { LINK_FLG_BSS_COLOR_DIS = BIT(0), @@ -557,6 +558,7 @@ enum iwl_link_ctx_flags { LINK_FLG_RU_2MHZ_BLOCK = BIT(2), LINK_FLG_NDP_FEEDBACK_ENABLED = BIT(3), LINK_FLG_NPCA = BIT(4), + LINK_FLG_DPS = BIT(6), }; /* LINK_CONTEXT_FLAG_E_VER_1 */ /** diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index 4e8222ffe732..f29de029c709 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -696,7 +696,8 @@ static const struct ieee80211_sband_iftype_data iwl_iftype_cap[] = { .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | IEEE80211_UHR_PHY_CAP_ELR_TX, .mac.mac_cap = { - [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP, + [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP | + IEEE80211_UHR_MAC_CAP0_DPS_SUPP, }, }, }, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index 9ef39b03e67f..785459937fbe 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -335,10 +335,18 @@ iwl_mld_change_link_in_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link, link_sta_dereference_check(mld_vif->ap_sta, link->link_id); - if (!WARN_ON(!link_sta) && link_sta->he_cap.has_he && + if (WARN_ON(!link_sta)) + return -EINVAL; + + if (link_sta->he_cap.has_he && link_sta->he_cap.he_cap_elem.mac_cap_info[5] & IEEE80211_HE_MAC_CAP5_OM_CTRL_UL_MU_DATA_DIS_RX) cmd.ul_mu_data_disable = 1; + + if (link_sta->uhr_cap.has_uhr && + link_sta->uhr_cap.mac.mac_cap[0] & + IEEE80211_UHR_MAC_CAP0_DPS_ASSIST_SUPP) + flags |= LINK_FLG_DPS; } cmd.htc_trig_based_pkt_ext = link->htc_trig_based_pkt_ext; From 0b7ae44f666818174e97fd398021ea17c68069d0 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 13 May 2026 08:43:55 +0300 Subject: [PATCH 094/143] wifi: iwlwifi: mld: give link STA debugfs files a namespace The generated data structures and function names used are just the debugfs filename, which is a shared namespace and could lead to confusion. Prefix these with "link_sta_" to disambiguate. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260513084215.4421f114bb33.I0de78b6b184935bc2a3d77fa649888da31b5a0fb@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/mld/debugfs.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c b/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c index 9ec8ddce0c38..ba5a47519aa1 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c @@ -1049,20 +1049,22 @@ static ssize_t _iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf, return ret ? : count; } -static ssize_t iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf, - size_t count, void *data) +static ssize_t +iwl_dbgfs_link_sta_fixed_rate_write(struct iwl_mld *mld, char *buf, + size_t count, void *data) { return _iwl_dbgfs_fixed_rate_write(mld, buf, count, data, false); } -static ssize_t iwl_dbgfs_fixed_rate_v3_write(struct iwl_mld *mld, char *buf, - size_t count, void *data) +static ssize_t +iwl_dbgfs_link_sta_fixed_rate_v3_write(struct iwl_mld *mld, char *buf, + size_t count, void *data) { return _iwl_dbgfs_fixed_rate_write(mld, buf, count, data, true); } -static ssize_t iwl_dbgfs_tlc_dhc_write(struct iwl_mld *mld, char *buf, - size_t count, void *data) +static ssize_t iwl_dbgfs_link_sta_tlc_dhc_write(struct iwl_mld *mld, char *buf, + size_t count, void *data) { struct ieee80211_link_sta *link_sta = data; struct iwl_mld_link_sta *mld_link_sta; @@ -1091,12 +1093,12 @@ static ssize_t iwl_dbgfs_tlc_dhc_write(struct iwl_mld *mld, char *buf, #define LINK_STA_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) \ debugfs_create_file(alias, mode, parent, link_sta, \ - &iwl_dbgfs_##name##_ops) + &iwl_dbgfs_link_sta_##name##_ops) #define LINK_STA_DEBUGFS_ADD_FILE(name, parent, mode) \ LINK_STA_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode) #define LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(name, bufsz) \ - WIPHY_DEBUGFS_WRITE_FILE_OPS(name, bufsz, link_sta) + WIPHY_DEBUGFS_WRITE_FILE_OPS(link_sta_##name, bufsz, link_sta) LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(tlc_dhc, 64); LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(fixed_rate, 64); From 2773c46c6ae8b3209d6d0caa3c2835dab3417162 Mon Sep 17 00:00:00 2001 From: Avinash Bhatt Date: Wed, 13 May 2026 08:43:56 +0300 Subject: [PATCH 095/143] wifi: iwlwifi: mld: implement PSD/EIRP RSSI adjustment APs operating in PSD mode increase transmit power with bandwidth, requiring RSSI compensation for accurate link grading. Parse TPE elements to determine power mode and apply RSSI adjustment based on operational bandwidth and power constraints. Signed-off-by: Avinash Bhatt Link: https://patch.msgid.link/20260513084215.ff1c5ba7491f.I2403678e34625809a7edf02f18760eda16a2c906@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.c | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index 785459937fbe..70965627e2dd 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -972,6 +972,145 @@ iwl_mld_get_dup_beacon_rssi_adjust(struct iwl_mld *mld, } } +static s8 iwl_mld_get_primary_psd(const struct ieee80211_parsed_tpe_psd *psd, + const struct cfg80211_chan_def *chandef, + int bw_mhz) +{ + int start_freq, primary_idx; + + if (!psd->valid) + return S8_MAX; + + start_freq = chandef->center_freq1 - (bw_mhz / 2); + primary_idx = (chandef->chan->center_freq - start_freq - 10) / 20; + + if (primary_idx < 0 || primary_idx >= psd->count) + return S8_MAX; + + /* TPE element stores PSD limit as value * 2 */ + return psd->power[primary_idx] / 2; +} + +static s8 iwl_mld_get_psd_eirp_rssi_adjust(struct ieee80211_bss_conf *link_conf) +{ + const struct ieee80211_parsed_tpe *tpe = &link_conf->tpe; + s8 psd_20mhz, psd_oper, psd_local, psd_reg, psd_boost; + s8 min_20mhz, min_oper, adjustment, ap_power_limit; + s8 psd_avg_local = S8_MAX, psd_avg_reg = S8_MAX; + s8 eirp_20mhz, eirp_oper, eirp_local, eirp_reg; + int bw_mhz, num_subchans; + u8 bw_index; + + /* PSD/EIRP adjustment is only specific to 6 GHz */ + if (WARN_ONCE(link_conf->chanreq.oper.chan->band != NL80211_BAND_6GHZ, + "PSD/EIRP adjustment called for non-6 GHz band %d\n", + link_conf->chanreq.oper.chan->band)) + return 0; + + bw_mhz = nl80211_chan_width_to_mhz(link_conf->chanreq.oper.width); + + switch (bw_mhz) { + case 20: + bw_index = 0; + break; + case 40: + bw_index = 1; + break; + case 80: + bw_index = 2; + break; + case 160: + bw_index = 3; + break; + case 320: + bw_index = 4; + break; + default: + WARN_ONCE(1, "Unexpected bandwidth: %d MHz\n", bw_mhz); + return 0; + } + + if (link_conf->power_type == IEEE80211_REG_VLP_AP) + ap_power_limit = 14; + else + ap_power_limit = 23; + + /* Primary 20 MHz PSD */ + psd_local = iwl_mld_get_primary_psd(&tpe->psd_local[0], + &link_conf->chanreq.oper, + bw_mhz); + psd_reg = iwl_mld_get_primary_psd(&tpe->psd_reg_client[0], + &link_conf->chanreq.oper, + bw_mhz); + psd_20mhz = min(psd_local, psd_reg); + + /* TPE element stores EIRP limit as value * 2 */ + eirp_local = (tpe->max_local[0].valid && tpe->max_local[0].count > 0) ? + tpe->max_local[0].power[0] / 2 : S8_MAX; + eirp_reg = (tpe->max_reg_client[0].valid && + tpe->max_reg_client[0].count > 0) ? + tpe->max_reg_client[0].power[0] / 2 : S8_MAX; + eirp_20mhz = min(eirp_local, eirp_reg); + + num_subchans = bw_mhz / 20; + + if (tpe->psd_local[0].valid) { + int sum_local = 0, valid_local = 0; + int count_local = min(num_subchans, tpe->psd_local[0].count); + + for (int i = 0; i < count_local; i++) { + if (tpe->psd_local[0].power[i] != S8_MIN) { + sum_local += tpe->psd_local[0].power[i]; + valid_local++; + } + } + /* TPE element stores PSD limit as value * 2 */ + if (valid_local > 0) + psd_avg_local = sum_local / valid_local / 2; + } + + if (tpe->psd_reg_client[0].valid) { + int sum_reg = 0, valid_reg = 0; + int count_reg = min(num_subchans, tpe->psd_reg_client[0].count); + + for (int i = 0; i < count_reg; i++) { + if (tpe->psd_reg_client[0].power[i] != S8_MIN) { + sum_reg += + tpe->psd_reg_client[0].power[i]; + valid_reg++; + } + } + /* TPE element stores PSD limit as value * 2 */ + if (valid_reg > 0) + psd_avg_reg = sum_reg / valid_reg / 2; + } + + psd_oper = min(psd_avg_local, psd_avg_reg); + + /* TPE element stores EIRP limit as value * 2 */ + eirp_local = (tpe->max_local[0].valid && + tpe->max_local[0].count > bw_index) ? + tpe->max_local[0].power[bw_index] / 2 : S8_MAX; + eirp_reg = (tpe->max_reg_client[0].valid && + tpe->max_reg_client[0].count > bw_index) ? + tpe->max_reg_client[0].power[bw_index] / 2 : S8_MAX; + eirp_oper = min(eirp_local, eirp_reg); + + min_20mhz = min(ap_power_limit, min(eirp_20mhz, psd_20mhz)); + + /* PSD boost: 10*log10(BW/20) approximated as 3*ilog2(BW/20) */ + psd_boost = 3 * ilog2(bw_mhz / 20); + + /* Use int for psd_oper + psd_boost to prevent s8 overflow */ + min_oper = min(ap_power_limit, + min(eirp_oper, + (s8)min_t(int, psd_oper + psd_boost, S8_MAX))); + + adjustment = max(min_oper - min_20mhz, 0); + + return adjustment; +} + /* This function calculates the grade of a link. Returns 0 in error case */ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, struct ieee80211_bss_conf *link_conf) @@ -1000,6 +1139,10 @@ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, s8 rssi_adj_6g = iwl_mld_get_dup_beacon_rssi_adjust(mld, link_conf); + if (!rssi_adj_6g) + rssi_adj_6g = + iwl_mld_get_psd_eirp_rssi_adjust(link_conf); + if (!rssi_adj_6g) rssi_adj_6g = 4; From f9a6f64cf6000fdf5b1992f48734c8fba66f7ac5 Mon Sep 17 00:00:00 2001 From: Avinash Bhatt Date: Wed, 13 May 2026 08:43:57 +0300 Subject: [PATCH 096/143] wifi: iwlwifi: mld: update link grading tables per bandwidth Replace single RSSI-to-grade table with separate tables for each operational bandwidth. Grade selection now depends on both RSSI and the link's operational bandwidth. Improve puncturing penalty calculation to apply proportional reduction based on unusable spectrum percentage rather than absolute channel count. Update existing KUnit tests with RSSI values for new grading tables. Signed-off-by: Avinash Bhatt Tested-by: Miriam Rachel Korenblit Link: https://patch.msgid.link/20260513084215.9f848b0da851.Id239216fda62e25d343495696cc60742e57d56ea@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.c | 228 ++++++++++++++---- .../intel/iwlwifi/mld/tests/link-selection.c | 6 +- 2 files changed, 183 insertions(+), 51 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index 70965627e2dd..3c1825ba2b86 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -696,35 +696,131 @@ struct iwl_mld_rssi_to_grade { u16 grade; }; -#define RSSI_TO_GRADE_LINE(_lb, _hb_uhb, _grade) \ +#define RSSI_TO_GRADE_LINE_WITH_LB(_lb, _hb_uhb, _grade) \ { \ .rssi = {_lb, _hb_uhb}, \ .grade = _grade \ } -/* - * This array must be sorted by increasing RSSI for proper functionality. - * The grades are actually estimated throughput, represented as fixed-point - * with a scale factor of 1/10. - */ -static const struct iwl_mld_rssi_to_grade rssi_to_grade_map[] = { - RSSI_TO_GRADE_LINE(-85, -89, 172), - RSSI_TO_GRADE_LINE(-83, -86, 344), - RSSI_TO_GRADE_LINE(-82, -85, 516), - RSSI_TO_GRADE_LINE(-80, -83, 688), - RSSI_TO_GRADE_LINE(-77, -79, 1032), - RSSI_TO_GRADE_LINE(-73, -76, 1376), - RSSI_TO_GRADE_LINE(-70, -74, 1548), - RSSI_TO_GRADE_LINE(-69, -72, 1720), - RSSI_TO_GRADE_LINE(-65, -68, 2064), - RSSI_TO_GRADE_LINE(-61, -66, 2294), - RSSI_TO_GRADE_LINE(-58, -61, 2580), - RSSI_TO_GRADE_LINE(-55, -58, 2868), - RSSI_TO_GRADE_LINE(-46, -55, 3098), - RSSI_TO_GRADE_LINE(-43, -54, 3442) +#define RSSI_TO_GRADE_LINE(_rssi, _grade) \ + RSSI_TO_GRADE_LINE_WITH_LB(_rssi, _rssi, _grade) + +/* Tables must be sorted by increasing RSSI */ + +/* 20 MHz Operational BW Grading Table */ +static const struct iwl_mld_rssi_to_grade rssi_to_grade_20mhz[] = { + RSSI_TO_GRADE_LINE_WITH_LB(-94, -95, 9), + RSSI_TO_GRADE_LINE_WITH_LB(-92, -93, 17), + RSSI_TO_GRADE_LINE_WITH_LB(-90, -90, 34), + RSSI_TO_GRADE_LINE_WITH_LB(-87, -87, 52), + RSSI_TO_GRADE_LINE_WITH_LB(-83, -84, 69), + RSSI_TO_GRADE_LINE_WITH_LB(-79, -80, 103), + RSSI_TO_GRADE_LINE_WITH_LB(-75, -75, 137), + RSSI_TO_GRADE_LINE_WITH_LB(-72, -73, 155), + RSSI_TO_GRADE_LINE_WITH_LB(-70, -71, 172), + RSSI_TO_GRADE_LINE_WITH_LB(-67, -68, 206), + RSSI_TO_GRADE_LINE_WITH_LB(-64, -65, 230), + RSSI_TO_GRADE_LINE_WITH_LB(-59, -60, 258), + RSSI_TO_GRADE_LINE_WITH_LB(-57, -58, 287), + RSSI_TO_GRADE_LINE_WITH_LB(-52, -53, 310), + RSSI_TO_GRADE_LINE_WITH_LB(-50, -50, 345), }; -#define MAX_GRADE (rssi_to_grade_map[ARRAY_SIZE(rssi_to_grade_map) - 1].grade) +/* 40 MHz Operational BW Grading Table */ +static const struct iwl_mld_rssi_to_grade rssi_to_grade_40mhz[] = { + RSSI_TO_GRADE_LINE_WITH_LB(-95, -95, 9), + RSSI_TO_GRADE_LINE_WITH_LB(-93, -93, 17), + RSSI_TO_GRADE_LINE_WITH_LB(-91, -92, 18), + RSSI_TO_GRADE_LINE_WITH_LB(-89, -90, 34), + RSSI_TO_GRADE_LINE_WITH_LB(-87, -87, 68), + RSSI_TO_GRADE_LINE_WITH_LB(-84, -84, 104), + RSSI_TO_GRADE_LINE_WITH_LB(-80, -81, 138), + RSSI_TO_GRADE_LINE_WITH_LB(-77, -77, 206), + RSSI_TO_GRADE_LINE_WITH_LB(-72, -72, 274), + RSSI_TO_GRADE_LINE_WITH_LB(-69, -70, 310), + RSSI_TO_GRADE_LINE_WITH_LB(-67, -68, 344), + RSSI_TO_GRADE_LINE_WITH_LB(-64, -65, 412), + RSSI_TO_GRADE_LINE_WITH_LB(-61, -62, 460), + RSSI_TO_GRADE_LINE_WITH_LB(-56, -57, 516), + RSSI_TO_GRADE_LINE_WITH_LB(-54, -55, 574), + RSSI_TO_GRADE_LINE_WITH_LB(-49, -50, 620), + RSSI_TO_GRADE_LINE_WITH_LB(-46, -47, 690), +}; + +/* 80 MHz Operational BW Grading Table */ +static const struct iwl_mld_rssi_to_grade rssi_to_grade_80mhz[] = { + RSSI_TO_GRADE_LINE(-95, 9), + RSSI_TO_GRADE_LINE(-93, 17), + RSSI_TO_GRADE_LINE(-92, 18), + RSSI_TO_GRADE_LINE(-90, 34), + RSSI_TO_GRADE_LINE(-89, 36), + RSSI_TO_GRADE_LINE(-87, 68), + RSSI_TO_GRADE_LINE(-83, 136), + RSSI_TO_GRADE_LINE(-80, 208), + RSSI_TO_GRADE_LINE(-77, 276), + RSSI_TO_GRADE_LINE(-74, 412), + RSSI_TO_GRADE_LINE(-69, 548), + RSSI_TO_GRADE_LINE(-67, 620), + RSSI_TO_GRADE_LINE(-66, 688), + RSSI_TO_GRADE_LINE(-61, 824), + RSSI_TO_GRADE_LINE(-59, 920), + RSSI_TO_GRADE_LINE(-54, 1032), + RSSI_TO_GRADE_LINE(-52, 1148), + RSSI_TO_GRADE_LINE(-47, 1240), + RSSI_TO_GRADE_LINE(-44, 1380), +}; + +/* 160 MHz Operational BW Grading Table */ +static const struct iwl_mld_rssi_to_grade rssi_to_grade_160mhz[] = { + RSSI_TO_GRADE_LINE(-95, 9), + RSSI_TO_GRADE_LINE(-93, 17), + RSSI_TO_GRADE_LINE(-92, 18), + RSSI_TO_GRADE_LINE(-90, 34), + RSSI_TO_GRADE_LINE(-89, 36), + RSSI_TO_GRADE_LINE(-87, 68), + RSSI_TO_GRADE_LINE(-86, 72), + RSSI_TO_GRADE_LINE(-84, 136), + RSSI_TO_GRADE_LINE(-81, 272), + RSSI_TO_GRADE_LINE(-78, 416), + RSSI_TO_GRADE_LINE(-75, 552), + RSSI_TO_GRADE_LINE(-71, 824), + RSSI_TO_GRADE_LINE(-67, 1096), + RSSI_TO_GRADE_LINE(-65, 1240), + RSSI_TO_GRADE_LINE(-63, 1376), + RSSI_TO_GRADE_LINE(-59, 1648), + RSSI_TO_GRADE_LINE(-57, 1840), + RSSI_TO_GRADE_LINE(-52, 2064), + RSSI_TO_GRADE_LINE(-50, 2296), + RSSI_TO_GRADE_LINE(-46, 2480), + RSSI_TO_GRADE_LINE(-42, 2760), +}; + +/* 320 MHz Operational BW Grading Table */ +static const struct iwl_mld_rssi_to_grade rssi_to_grade_320mhz[] = { + RSSI_TO_GRADE_LINE(-95, 9), + RSSI_TO_GRADE_LINE(-93, 17), + RSSI_TO_GRADE_LINE(-92, 18), + RSSI_TO_GRADE_LINE(-90, 34), + RSSI_TO_GRADE_LINE(-89, 36), + RSSI_TO_GRADE_LINE(-87, 68), + RSSI_TO_GRADE_LINE(-86, 72), + RSSI_TO_GRADE_LINE(-84, 136), + RSSI_TO_GRADE_LINE(-83, 144), + RSSI_TO_GRADE_LINE(-81, 272), + RSSI_TO_GRADE_LINE(-78, 544), + RSSI_TO_GRADE_LINE(-75, 832), + RSSI_TO_GRADE_LINE(-72, 1104), + RSSI_TO_GRADE_LINE(-69, 1648), + RSSI_TO_GRADE_LINE(-64, 2192), + RSSI_TO_GRADE_LINE(-62, 2480), + RSSI_TO_GRADE_LINE(-61, 2752), + RSSI_TO_GRADE_LINE(-57, 3296), + RSSI_TO_GRADE_LINE(-55, 3680), + RSSI_TO_GRADE_LINE(-50, 4128), + RSSI_TO_GRADE_LINE(-47, 4592), + RSSI_TO_GRADE_LINE(-43, 4960), + RSSI_TO_GRADE_LINE(-40, 5520), +}; #define DEFAULT_CHAN_LOAD_2GHZ 30 #define DEFAULT_CHAN_LOAD_5GHZ 15 @@ -734,26 +830,23 @@ static const struct iwl_mld_rssi_to_grade rssi_to_grade_map[] = { #define SCALE_FACTOR 256 #define MAX_CHAN_LOAD 256 -static unsigned int -iwl_mld_get_n_subchannels(const struct ieee80211_bss_conf *link_conf) +static void +iwl_mld_apply_puncturing_penalty(const struct ieee80211_bss_conf *link_conf, + unsigned int *grade, int bw_mhz) { - enum nl80211_chan_width chan_width = - link_conf->chanreq.oper.width; - int mhz = nl80211_chan_width_to_mhz(chan_width); - unsigned int n_subchannels; + unsigned int n_punctured, n_subchannels; - if (WARN_ONCE(mhz < 20 || mhz > 320, - "Invalid channel width : (%d)\n", mhz)) - return 1; + /* Puncturing only applicable for BW >= 80 MHz */ + if (bw_mhz < 80) + return; - /* total number of subchannels */ - n_subchannels = mhz / 20; + n_punctured = hweight16(link_conf->chanreq.oper.punctured); + if (n_punctured == 0) + return; - /* No puncturing if less than 80 MHz */ - if (mhz >= 80) - n_subchannels -= hweight16(link_conf->chanreq.oper.punctured); + n_subchannels = bw_mhz / 20; - return n_subchannels; + *grade = *grade * (n_subchannels - n_punctured) / n_subchannels; } static int @@ -1115,14 +1208,19 @@ static s8 iwl_mld_get_psd_eirp_rssi_adjust(struct ieee80211_bss_conf *link_conf) unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, struct ieee80211_bss_conf *link_conf) { + const struct iwl_mld_rssi_to_grade *grade_table; enum nl80211_band band; - int rssi_idx; + int rssi_idx, table_size, bw_mhz; s32 link_rssi; - unsigned int grade = MAX_GRADE; + unsigned int grade; if (WARN_ON_ONCE(!link_conf || !link_conf->bss)) return 0; + bw_mhz = nl80211_chan_width_to_mhz(link_conf->chanreq.oper.width); + if (bw_mhz < 0) + return 0; + band = link_conf->chanreq.oper.chan->band; if (WARN_ONCE(band != NL80211_BAND_2GHZ && band != NL80211_BAND_5GHZ && @@ -1149,22 +1247,50 @@ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, link_rssi += rssi_adj_6g; } + /* Select grading table based on operational bandwidth */ + switch (bw_mhz) { + case 20: + grade_table = rssi_to_grade_20mhz; + table_size = ARRAY_SIZE(rssi_to_grade_20mhz); + break; + case 40: + grade_table = rssi_to_grade_40mhz; + table_size = ARRAY_SIZE(rssi_to_grade_40mhz); + break; + case 80: + grade_table = rssi_to_grade_80mhz; + table_size = ARRAY_SIZE(rssi_to_grade_80mhz); + break; + case 160: + grade_table = rssi_to_grade_160mhz; + table_size = ARRAY_SIZE(rssi_to_grade_160mhz); + break; + case 320: + grade_table = rssi_to_grade_320mhz; + table_size = ARRAY_SIZE(rssi_to_grade_320mhz); + break; + default: + WARN_ONCE(1, "Invalid bandwidth: %d MHz\n", bw_mhz); + return 0; + } + + /* Initialize grade to maximum value from selected table */ + grade = grade_table[table_size - 1].grade; + rssi_idx = band == NL80211_BAND_2GHZ ? 0 : 1; - /* No valid RSSI - take the lowest grade */ + /* No valid RSSI - take the lowest grade from selected table */ if (!link_rssi) - link_rssi = rssi_to_grade_map[0].rssi[rssi_idx]; + link_rssi = grade_table[0].rssi[rssi_idx]; IWL_DEBUG_EHT(mld, - "Calculating grade of link %d: band = %d, bandwidth = %d, punctured subchannels =0x%x RSSI = %d\n", - link_conf->link_id, band, - link_conf->chanreq.oper.width, + "Calculating grade of link %d: band = %d, BW = %d, punct subchannels = 0x%x RSSI = %d\n", + link_conf->link_id, band, bw_mhz, link_conf->chanreq.oper.punctured, link_rssi); - /* Get grade based on RSSI */ - for (int i = 0; i < ARRAY_SIZE(rssi_to_grade_map); i++) { - const struct iwl_mld_rssi_to_grade *line = - &rssi_to_grade_map[i]; + /* Get grade based on RSSI from the bandwidth-specific table */ + for (int i = 0; i < table_size; i++) { + const struct iwl_mld_rssi_to_grade *line = &grade_table[i]; if (link_rssi > line->rssi[rssi_idx]) continue; @@ -1173,8 +1299,10 @@ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, } /* Apply the channel load and puncturing factors */ - grade = grade * iwl_mld_get_avail_chan_load(mld, link_conf) / SCALE_FACTOR; - grade = grade * iwl_mld_get_n_subchannels(link_conf); + grade = grade * iwl_mld_get_avail_chan_load(mld, link_conf) / + SCALE_FACTOR; + + iwl_mld_apply_puncturing_penalty(link_conf, &grade, bw_mhz); IWL_DEBUG_EHT(mld, "Link %d's grade: %d\n", link_conf->link_id, grade); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c index 766c24db3613..69d222a8194c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c @@ -2,7 +2,7 @@ /* * KUnit tests for link selection functions * - * Copyright (C) 2025 Intel Corporation + * Copyright (C) 2025-2026 Intel Corporation */ #include @@ -34,6 +34,7 @@ static const struct link_grading_test_case { .link_id = 0, .chandef = &chandef_2ghz_20mhz, .active = false, + .signal = -70, .has_chan_util_elem = true, .chan_util = 128, }, @@ -45,6 +46,7 @@ static const struct link_grading_test_case { .link_id = 0, .chandef = &chandef_2ghz_20mhz, .active = false, + .signal = -70, .has_chan_util_elem = true, .chan_util = 180, }, @@ -55,6 +57,7 @@ static const struct link_grading_test_case { .input.link = { .link_id = 0, .chandef = &chandef_2ghz_20mhz, + .signal = -70, .has_chan_util_elem = true, .chan_util = 180, .active = true, @@ -67,6 +70,7 @@ static const struct link_grading_test_case { .input.link = { .link_id = 0, .chandef = &chandef_2ghz_20mhz, + .signal = -70, .active = true, }, .expected_grade = 120, From f3157a5c048c8908b11ec301d0e4c54607c399e4 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 13 May 2026 08:43:58 +0300 Subject: [PATCH 097/143] wifi: iwlwifi: bump core version for BZ/SC/DR to 104 Start supporting Core 104 FW on these devices. Link: https://patch.msgid.link/20260513084215.9d584ef19b3c.I2b5b08ff63f8fafda6bc888b597615ad7d34f537@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/cfg/bz.c | 4 ++-- drivers/net/wireless/intel/iwlwifi/cfg/dr.c | 4 ++-- drivers/net/wireless/intel/iwlwifi/cfg/sc.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index a70b356d9576..51d7c599e998 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include #include @@ -10,7 +10,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_BZ_UCODE_CORE_MAX 103 +#define IWL_BZ_UCODE_CORE_MAX 104 /* Lowest firmware core release supported */ #define IWL_BZ_UCODE_CORE_MIN 101 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c index e95112fdc664..76fbc120a092 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/dr.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 #include @@ -9,7 +9,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_DR_UCODE_CORE_MAX 103 +#define IWL_DR_UCODE_CORE_MAX 104 /* Lowest firmware core release supported */ #define IWL_DR_UCODE_CORE_MIN 101 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index d1e5cef42fff..db7858b2cb74 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include #include @@ -10,7 +10,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_SC_UCODE_CORE_MAX 103 +#define IWL_SC_UCODE_CORE_MAX 104 /* Lowest firmware core release supported */ #define IWL_SC_UCODE_CORE_MIN 101 From 90cec2c95428f2f926e1754caae846dac0d088d1 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 13 May 2026 08:43:59 +0300 Subject: [PATCH 098/143] wifi: iwlwifi: define MODULE_FIRMWARE with the correct API A firmware API of a mix and match (MAC + RF) is the lower one of the two. But the MODULE_FIRMWARE of QU/QUZ/SO/MA/TY with GF/HR, was defined using the max API of GF/HR, which is 100, instead of the max API of QU/QUZ/SO/MA/TY, which are API 77/89. Therefore, the wrong firmware files were published in the modinfo, leading to missing firmware files. Fix this by using the right API when declaring MODULE_FIRMWARE. While at it, add a IWL_FW_AND_PNVM entry for SO and GF4, that was missing. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260513084215.c8dcacbeb616.I3d51a9daeda6fb7f52b3014101db79fdc98295d5@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/cfg/22000.c | 12 ++++++++ .../net/wireless/intel/iwlwifi/cfg/ax210.c | 29 +++++++++++++++++++ .../net/wireless/intel/iwlwifi/cfg/rf-gf.c | 13 --------- .../net/wireless/intel/iwlwifi/cfg/rf-hr.c | 24 --------------- 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c index e929a08e7585..01ca65eb5acd 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c @@ -16,9 +16,18 @@ #define IWL_22000_UCODE_API_MIN 77 #define IWL_CC_A_FW_PRE "iwlwifi-cc-a0" +#define IWL_QU_B_HR_B_FW_PRE "iwlwifi-Qu-b0-hr-b0" +#define IWL_QU_C_HR_B_FW_PRE "iwlwifi-Qu-c0-hr-b0" +#define IWL_QUZ_A_HR_B_FW_PRE "iwlwifi-QuZ-a0-hr-b0" #define IWL_CC_A_MODULE_FIRMWARE(api) \ IWL_CC_A_FW_PRE "-" __stringify(api) ".ucode" +#define IWL_QU_B_HR_B_MODULE_FIRMWARE(api) \ + IWL_QU_B_HR_B_FW_PRE "-" __stringify(api) ".ucode" +#define IWL_QUZ_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_QUZ_A_HR_B_FW_PRE "-" __stringify(api) ".ucode" +#define IWL_QU_C_HR_B_MODULE_FIRMWARE(api) \ + IWL_QU_C_HR_B_FW_PRE "-" __stringify(api) ".ucode" static const struct iwl_family_base_params iwl_22000_base = { .num_of_queues = 512, @@ -104,4 +113,7 @@ const char iwl_ax201_killer_1650s_name[] = const char iwl_ax201_killer_1650i_name[] = "Killer(R) Wi-Fi 6 AX1650i 160MHz Wireless Network Adapter (201NGW)"; +MODULE_FIRMWARE(IWL_QU_B_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QU_C_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QUZ_A_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); MODULE_FIRMWARE(IWL_CC_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c index ec1951a94e23..2519f577669e 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c @@ -15,6 +15,24 @@ /* Lowest firmware API version supported */ #define IWL_AX210_UCODE_API_MIN 89 +#define IWL_SO_A_GF_A_FW_PRE "iwlwifi-so-a0-gf-a0" +#define IWL_TY_A_GF_A_FW_PRE "iwlwifi-ty-a0-gf-a0" +#define IWL_MA_A_GF_A_FW_PRE "iwlwifi-ma-a0-gf-a0" +#define IWL_MA_B_GF_A_FW_PRE "iwlwifi-ma-b0-gf-a0" +#define IWL_SO_A_GF4_A_FW_PRE "iwlwifi-so-a0-gf4-a0" +#define IWL_MA_A_GF4_A_FW_PRE "iwlwifi-ma-a0-gf4-a0" +#define IWL_MA_B_GF4_A_FW_PRE "iwlwifi-ma-b0-gf4-a0" +#define IWL_SO_A_HR_B_FW_PRE "iwlwifi-so-a0-hr-b0" +#define IWL_MA_A_HR_B_FW_PRE "iwlwifi-ma-a0-hr-b0" +#define IWL_MA_B_HR_B_FW_PRE "iwlwifi-ma-b0-hr-b0" + +#define IWL_SO_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_SO_A_HR_B_FW_PRE "-" __stringify(api) ".ucode" +#define IWL_MA_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_MA_A_HR_B_FW_PRE "-" __stringify(api) ".ucode" +#define IWL_MA_B_HR_B_MODULE_FIRMWARE(api) \ + IWL_MA_B_HR_B_FW_PRE "-" __stringify(api) ".ucode" + static const struct iwl_family_base_params iwl_ax210_base = { .num_of_queues = 512, .max_tfd_queue_size = 65536, @@ -112,3 +130,14 @@ const struct iwl_mac_cfg iwl_ma_mac_cfg = { .integrated = true, .umac_prph_offset = 0x300000 }; + +IWL_FW_AND_PNVM(IWL_SO_A_GF_A_FW_PRE, IWL_AX210_UCODE_API_MAX); +IWL_FW_AND_PNVM(IWL_TY_A_GF_A_FW_PRE, IWL_AX210_UCODE_API_MAX); +IWL_FW_AND_PNVM(IWL_MA_A_GF_A_FW_PRE, IWL_AX210_UCODE_API_MAX); +IWL_FW_AND_PNVM(IWL_MA_B_GF_A_FW_PRE, IWL_AX210_UCODE_API_MAX); +IWL_FW_AND_PNVM(IWL_SO_A_GF4_A_FW_PRE, IWL_AX210_UCODE_API_MAX); +IWL_FW_AND_PNVM(IWL_MA_A_GF4_A_FW_PRE, IWL_AX210_UCODE_API_MAX); +IWL_FW_AND_PNVM(IWL_MA_B_GF4_A_FW_PRE, IWL_AX210_UCODE_API_MAX); +MODULE_FIRMWARE(IWL_SO_A_HR_B_MODULE_FIRMWARE(IWL_AX210_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_A_HR_B_MODULE_FIRMWARE(IWL_AX210_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_B_HR_B_MODULE_FIRMWARE(IWL_AX210_UCODE_API_MAX)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c index 0c20fe619f93..99a5110924cd 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c @@ -11,13 +11,6 @@ /* Lowest firmware API version supported */ #define IWL_GF_UCODE_API_MIN 100 -#define IWL_SO_A_GF_A_FW_PRE "iwlwifi-so-a0-gf-a0" -#define IWL_TY_A_GF_A_FW_PRE "iwlwifi-ty-a0-gf-a0" -#define IWL_MA_A_GF_A_FW_PRE "iwlwifi-ma-a0-gf-a0" -#define IWL_MA_B_GF_A_FW_PRE "iwlwifi-ma-b0-gf-a0" -#define IWL_SO_A_GF4_A_FW_PRE "iwlwifi-so-a0-gf4-a0" -#define IWL_MA_A_GF4_A_FW_PRE "iwlwifi-ma-a0-gf4-a0" -#define IWL_MA_B_GF4_A_FW_PRE "iwlwifi-ma-b0-gf4-a0" #define IWL_BZ_A_GF_A_FW_PRE "iwlwifi-bz-a0-gf-a0" #define IWL_BZ_A_GF4_A_FW_PRE "iwlwifi-bz-a0-gf4-a0" #define IWL_SC_A_GF_A_FW_PRE "iwlwifi-sc-a0-gf-a0" @@ -69,12 +62,6 @@ const char iwl_ax210_name[] = "Intel(R) Wi-Fi 6E AX210 160MHz"; const char iwl_ax211_name[] = "Intel(R) Wi-Fi 6E AX211 160MHz"; const char iwl_ax411_name[] = "Intel(R) Wi-Fi 6E AX411 160MHz"; -IWL_FW_AND_PNVM(IWL_SO_A_GF_A_FW_PRE, IWL_GF_UCODE_API_MAX); -IWL_FW_AND_PNVM(IWL_TY_A_GF_A_FW_PRE, IWL_GF_UCODE_API_MAX); -IWL_FW_AND_PNVM(IWL_MA_A_GF_A_FW_PRE, IWL_GF_UCODE_API_MAX); -IWL_FW_AND_PNVM(IWL_MA_B_GF_A_FW_PRE, IWL_GF_UCODE_API_MAX); -IWL_FW_AND_PNVM(IWL_MA_A_GF4_A_FW_PRE, IWL_GF_UCODE_API_MAX); -IWL_FW_AND_PNVM(IWL_MA_B_GF4_A_FW_PRE, IWL_GF_UCODE_API_MAX); MODULE_FIRMWARE(IWL_BZ_A_GF_A_MODULE_FIRMWARE(IWL_GF_UCODE_API_MAX)); MODULE_FIRMWARE(IWL_BZ_A_GF4_A_MODULE_FIRMWARE(IWL_GF_UCODE_API_MAX)); MODULE_FIRMWARE(IWL_SC_A_GF_A_MODULE_FIRMWARE(IWL_GF_UCODE_API_MAX)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c index 3c3a8d5702d8..16b9075acdd8 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c @@ -11,28 +11,10 @@ /* Lowest firmware API version supported */ #define IWL_HR_UCODE_API_MIN 100 -#define IWL_QU_B_HR_B_FW_PRE "iwlwifi-Qu-b0-hr-b0" -#define IWL_QU_C_HR_B_FW_PRE "iwlwifi-Qu-c0-hr-b0" -#define IWL_QUZ_A_HR_B_FW_PRE "iwlwifi-QuZ-a0-hr-b0" -#define IWL_SO_A_HR_B_FW_PRE "iwlwifi-so-a0-hr-b0" -#define IWL_MA_A_HR_B_FW_PRE "iwlwifi-ma-a0-hr-b0" -#define IWL_MA_B_HR_B_FW_PRE "iwlwifi-ma-b0-hr-b0" #define IWL_BZ_A_HR_B_FW_PRE "iwlwifi-bz-a0-hr-b0" #define IWL_SC_A_HR_A_FW_PRE "iwlwifi-sc-a0-hr-b0" #define IWL_SC_A_HR_B_FW_PRE "iwlwifi-sc-a0-hr-b0" -#define IWL_QU_B_HR_B_MODULE_FIRMWARE(api) \ - IWL_QU_B_HR_B_FW_PRE "-" __stringify(api) ".ucode" -#define IWL_QUZ_A_HR_B_MODULE_FIRMWARE(api) \ - IWL_QUZ_A_HR_B_FW_PRE "-" __stringify(api) ".ucode" -#define IWL_QU_C_HR_B_MODULE_FIRMWARE(api) \ - IWL_QU_C_HR_B_FW_PRE "-" __stringify(api) ".ucode" -#define IWL_SO_A_HR_B_MODULE_FIRMWARE(api) \ - IWL_SO_A_HR_B_FW_PRE "-" __stringify(api) ".ucode" -#define IWL_MA_A_HR_B_FW_MODULE_FIRMWARE(api) \ - IWL_MA_A_HR_B_FW_PRE "-" __stringify(api) ".ucode" -#define IWL_MA_B_HR_B_FW_MODULE_FIRMWARE(api) \ - IWL_MA_B_HR_B_FW_PRE "-" __stringify(api) ".ucode" #define IWL_BZ_A_HR_B_MODULE_FIRMWARE(api) \ IWL_BZ_A_HR_B_FW_PRE "-" __stringify(api) ".ucode" #define IWL_SC_A_HR_A_FW_MODULE_FIRMWARE(api) \ @@ -74,12 +56,6 @@ const char iwl_ax200_name[] = "Intel(R) Wi-Fi 6 AX200 160MHz"; const char iwl_ax201_name[] = "Intel(R) Wi-Fi 6 AX201 160MHz"; const char iwl_ax203_name[] = "Intel(R) Wi-Fi 6 AX203"; -MODULE_FIRMWARE(IWL_QU_B_HR_B_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX)); -MODULE_FIRMWARE(IWL_QU_C_HR_B_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX)); -MODULE_FIRMWARE(IWL_QUZ_A_HR_B_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX)); -MODULE_FIRMWARE(IWL_SO_A_HR_B_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX)); -MODULE_FIRMWARE(IWL_MA_A_HR_B_FW_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX)); -MODULE_FIRMWARE(IWL_MA_B_HR_B_FW_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX)); MODULE_FIRMWARE(IWL_BZ_A_HR_B_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX)); MODULE_FIRMWARE(IWL_SC_A_HR_A_FW_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX)); MODULE_FIRMWARE(IWL_SC_A_HR_B_FW_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX)); From b5d6175c112889a4c837f45a15aefea4ae1aba13 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Wed, 13 May 2026 08:44:00 +0300 Subject: [PATCH 099/143] wifi: iwlwifi: mld: Replace static declarations of IWL_MLD_ALLOC_FN Replace static declarations of IWL_MLD_ALLOC_FN with a new macro IWL_MLD_ALLOC_FN_STATIC that declares the static. This is needed to resolve issues with spatch, which fails to handle functions after "static IWL_MLD_ALLOC_FN". Signed-off-by: Ilan Peer Link: https://patch.msgid.link/20260513084215.861e4554157d.Id07d4037c75f3ebc8a57ac95b14286369fb3467b@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/iface.c | 2 +- drivers/net/wireless/intel/iwlwifi/mld/mld.h | 3 +++ drivers/net/wireless/intel/iwlwifi/mld/sta.c | 2 +- drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c | 6 +++--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c index 2a270d689de8..f7a0a8efe374 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c @@ -499,7 +499,7 @@ static void iwl_mld_mlo_scan_start_wk(struct wiphy *wiphy, iwl_mld_int_mlo_scan(mld, iwl_mld_vif_to_mac80211(mld_vif)); } -static IWL_MLD_ALLOC_FN(vif, vif) +IWL_MLD_ALLOC_FN_STATIC(vif, vif) /* Constructor function for struct iwl_mld_vif */ static void diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.h b/drivers/net/wireless/intel/iwlwifi/mld/mld.h index 1a59b3e4014d..922aa3dbff54 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.h @@ -552,6 +552,9 @@ iwl_mld_allocate_##_type##_fw_id(struct iwl_mld *mld, \ return -ENOSPC; \ } +#define IWL_MLD_ALLOC_FN_STATIC(_type, _mac80211_type) \ +static IWL_MLD_ALLOC_FN(_type, _mac80211_type) + static inline struct ieee80211_bss_conf * iwl_mld_fw_id_to_link_conf(struct iwl_mld *mld, u8 fw_link_id) { diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.c b/drivers/net/wireless/intel/iwlwifi/mld/sta.c index 1fae5a6ba8d4..4c168ad53b0f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.c @@ -577,7 +577,7 @@ int iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld, return iwl_mld_send_sta_cmd(mld, &cmd); } -static IWL_MLD_ALLOC_FN(link_sta, link_sta) +IWL_MLD_ALLOC_FN_STATIC(link_sta, link_sta) static int iwl_mld_add_link_sta(struct iwl_mld *mld, struct ieee80211_link_sta *link_sta) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c index 0cdbbb86dbd9..cb1968b07452 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c @@ -2,7 +2,7 @@ /* * KUnit tests for channel helper functions * - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #include #include @@ -92,7 +92,7 @@ static void iwlmld_kunit_init_link(struct ieee80211_vif *vif, rcu_assign_pointer(vif->link_conf[link_id], link); } -static IWL_MLD_ALLOC_FN(vif, vif) +IWL_MLD_ALLOC_FN_STATIC(vif, vif) /* Helper function to add and initialize a VIF for KUnit tests */ struct ieee80211_vif *iwlmld_kunit_add_vif(bool mlo, enum nl80211_iftype type) @@ -197,7 +197,7 @@ void iwlmld_kunit_assign_chanctx_to_link(struct ieee80211_vif *vif, vif->active_links |= BIT(link->link_id); } -static IWL_MLD_ALLOC_FN(link_sta, link_sta) +IWL_MLD_ALLOC_FN_STATIC(link_sta, link_sta) static void iwlmld_kunit_add_link_sta(struct ieee80211_sta *sta, struct ieee80211_link_sta *link_sta, From b506efbde89255bd2246b7a6bcf58f7cc1d4ccd1 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Wed, 13 May 2026 08:44:01 +0300 Subject: [PATCH 100/143] wifi: iwlwifi: mld: Add support for NAN multicast data The FW added a new station type to allow NAN multicast data to be transmitted. The queue of the station is opened by the firmware when all the NAN Peer NDI stations associated with the NDI are scheduled at the same time. Add the corresponding support in iwlmld: - When a NAN data interface is added, add a NAN multicast data station and allocate a queue to it. - When a NAN data interface is removed, remove the NAN multicast station. - On the Tx path, when a multicast data frame is received on a NAN data interface, direct it to the NAN multicast data queue. Signed-off-by: Ilan Peer Link: https://patch.msgid.link/20260513084215.1d8e3463717d.I57deaea42bce9afee63f284a57eb8755485e7ea8@changeid Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/mac-cfg.h | 6 +- .../net/wireless/intel/iwlwifi/mld/iface.c | 6 ++ .../net/wireless/intel/iwlwifi/mld/iface.h | 2 + .../net/wireless/intel/iwlwifi/mld/mac80211.c | 32 ++++++- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 29 +++++- drivers/net/wireless/intel/iwlwifi/mld/nan.h | 2 + drivers/net/wireless/intel/iwlwifi/mld/sta.c | 94 ++++++++++++++++--- drivers/net/wireless/intel/iwlwifi/mld/sta.h | 11 +++ drivers/net/wireless/intel/iwlwifi/mld/tx.c | 11 ++- 9 files changed, 172 insertions(+), 21 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 604281eb29c7..d03a2bda4e68 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -747,6 +747,8 @@ struct iwl_link_config_cmd { * discovery. No queue is associated with this station. * @STATION_TYPE_NAN_MGMT: NAN station used for NAN management frames, e.g., * SDFs and NAFs. + * @STATION_TYPE_NAN_MCAST_DATA: NAN station used for multicast NAN data + * frames. * @STATION_TYPE_MAX: maximum number of FW station types * @STATION_TYPE_AUX: aux sta. In the FW there is no need for a special type * for the aux sta, so this type is only for driver - internal use. @@ -759,6 +761,7 @@ enum iwl_fw_sta_type { STATION_TYPE_NAN_PEER_NDI, STATION_TYPE_NAN_BCAST, STATION_TYPE_NAN_MGMT, + STATION_TYPE_NAN_MCAST_DATA, STATION_TYPE_MAX, STATION_TYPE_AUX = STATION_TYPE_MAX /* this doesn't exist in FW */ }; /* STATION_TYPE_E_VER_1, _VER_2 */ @@ -933,7 +936,8 @@ struct iwl_sta_cfg_cmd_v2 { * @mic_prep_pad_delay: MIC prep time padding * @mic_compute_pad_delay: MIC compute time padding * @nmi_sta_id: for an NDI peer STA, the NMI peer STA ID it relates to - * @ndi_local_addr: for an NDI peer STA, the local NDI interface MAC address + * @ndi_local_addr: for an NDI peer STA or NAN multicast data station, + * the local NDI interface MAC address * @reserved: Reserved for alignment */ struct iwl_sta_cfg_cmd { diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c index f7a0a8efe374..399efeb469f6 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c @@ -68,6 +68,10 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif) iwl_mld_free_internal_sta(mld, &mld_vif->nan.mgmt_sta); } + if (vif->type == NL80211_IFTYPE_NAN_DATA && + mld_vif->nan.mcast_data_sta.sta_id != IWL_INVALID_STA) + iwl_mld_free_internal_sta(mld, &mld_vif->nan.mcast_data_sta); + CLEANUP_STRUCT(mld_vif); } @@ -534,6 +538,8 @@ iwl_mld_init_vif(struct iwl_mld *mld, struct ieee80211_vif *vif) iwl_mld_init_internal_sta(&mld_vif->nan.bcast_sta); iwl_mld_init_internal_sta(&mld_vif->nan.mgmt_sta); + } else if (vif->type == NL80211_IFTYPE_NAN_DATA) { + iwl_mld_init_internal_sta(&mld_vif->nan.mcast_data_sta); } 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 75b6727503d3..c6a7588df1fa 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h @@ -159,6 +159,7 @@ struct iwl_mld_emlsr { * activities. No queue is associated with it. * @nan.mgmt_sta: internal station used for NAN management frames, e.g., SDFs * and NAFs. + * @nan.mcast_data_sta: internal station used for multicast NAN Data frames. */ struct iwl_mld_vif { /* Add here fields that need clean up on restart */ @@ -187,6 +188,7 @@ struct iwl_mld_vif { bool mac_added; struct iwl_mld_int_sta bcast_sta; struct iwl_mld_int_sta mgmt_sta; + struct iwl_mld_int_sta mcast_data_sta; } nan; struct iwl_mld_emlsr emlsr; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 1f2843af39c1..b6df09812dae 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -663,8 +663,27 @@ int iwl_mld_mac80211_add_interface(struct ieee80211_hw *hw, if (ret) return ret; - if (vif->type == NL80211_IFTYPE_NAN_DATA) + if (vif->type == NL80211_IFTYPE_NAN_DATA) { + if (WARN_ON(!mld->nan_device_vif)) { + ret = -EINVAL; + goto err; + } + + if (iwl_mld_nan_use_nan_stations(mld)) { + struct iwl_mld_vif *mld_vif = + iwl_mld_vif_from_mac80211(vif); + struct iwl_mld_int_sta *sta = + &mld_vif->nan.mcast_data_sta; + + ret = iwl_mld_add_nan_mcast_data_sta(mld, + vif->addr, + sta); + if (ret) + goto err; + } + return 0; + } /* * Add the default link, but not if this is an MLD vif as that implies @@ -732,10 +751,17 @@ void iwl_mld_mac80211_remove_interface(struct ieee80211_hw *hw, if (vif->type == NL80211_IFTYPE_P2P_DEVICE) mld->p2p_device_vif = NULL; - if (vif->type == NL80211_IFTYPE_NAN) + if (vif->type == NL80211_IFTYPE_NAN) { mld->nan_device_vif = NULL; - else if (vif->type != NL80211_IFTYPE_NAN_DATA) + } else if (vif->type != NL80211_IFTYPE_NAN_DATA) { iwl_mld_remove_link(mld, &vif->bss_conf); + } else if (iwl_mld_nan_use_nan_stations(mld)) { + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + struct iwl_mld_int_sta *sta = &mld_vif->nan.mcast_data_sta; + + if (sta->sta_id != IWL_INVALID_STA) + iwl_mld_remove_nan_mcast_data_sta(mld, sta); + } #ifdef CONFIG_IWLWIFI_DEBUGFS debugfs_remove(iwl_mld_vif_from_mac80211(vif)->dbgfs_slink); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index 8f35687c58b1..530ba263c5f0 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -50,7 +50,7 @@ static int iwl_mld_nan_send_config_cmd(struct iwl_mld *mld, return iwl_mld_send_cmd(mld, &hcmd); } -static bool iwl_mld_nan_use_nan_stations(struct iwl_mld *mld) +bool iwl_mld_nan_use_nan_stations(struct iwl_mld *mld) { /* * If the FW supports version 1 of the NAN config command, it means that @@ -650,6 +650,20 @@ iwl_mld_nan_find_link(struct iwl_mld_vif *mld_vif, return NULL; } +static void iwl_mld_nan_set_mcast_data_links(struct ieee80211_vif *vif) +{ + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + + if (vif->type != NL80211_IFTYPE_NAN_DATA) + return; + + /* Note that all errors are handled internally so nothing to do + * with the return value (used only to silence compilation warnings) + */ + iwl_mld_update_nan_mcast_data_sta(mld_vif->mld, vif->addr, + &mld_vif->nan.mcast_data_sta); +} + void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, struct ieee80211_vif *vif, u64 changes) @@ -807,6 +821,19 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI) iwl_mld_add_modify_sta_cmd(mld, &sta->deflink); } + + /* + * Iterate over all the NAN Data interfaces and update the links + * for the internal multicast data station. + * In recovery - the station will be added later in + * drv_add_interface + */ + if (iwl_mld_nan_use_nan_stations(mld) && !mld->fw_status.in_hw_restart) { + struct ieee80211_vif *iter; + + for_each_active_interface(iter, mld->hw) + iwl_mld_nan_set_mcast_data_links(iter); + } } /* delete unused links */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.h b/drivers/net/wireless/intel/iwlwifi/mld/nan.h index 40152dc7d906..f4e4cd4b4e8d 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.h @@ -60,4 +60,6 @@ int iwl_mld_mac802111_nan_peer_sched_changed(struct ieee80211_hw *hw, int iwl_mld_nan_get_mgmt_queue(struct iwl_mld *mld, struct ieee80211_vif *vif); +bool iwl_mld_nan_use_nan_stations(struct iwl_mld *mld); + #endif /* __iwl_mld_nan_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.c b/drivers/net/wireless/intel/iwlwifi/mld/sta.c index 4c168ad53b0f..cd13238ed613 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.c @@ -428,6 +428,10 @@ static int iwl_mld_send_sta_cmd(struct iwl_mld *mld, len = sizeof(struct iwl_sta_cfg_cmd_v2); cmd_v2->link_id = cpu_to_le32(__ffs(le32_to_cpu(cmd->link_mask))); + } else if (cmd->station_type == + cpu_to_le32(STATION_TYPE_NAN_MCAST_DATA)) { + if (WARN_ON(!hweight32(le32_to_cpu(cmd->link_mask)))) + return -EINVAL; } else if (WARN_ON(cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_PEER_NMI) && cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_PEER_NDI) && cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_BCAST) && @@ -442,6 +446,19 @@ static int iwl_mld_send_sta_cmd(struct iwl_mld *mld, return ret; } +static u32 iwl_mld_get_nan_link_mask(struct iwl_mld *mld) +{ + struct iwl_mld_vif *nan_dev = + iwl_mld_vif_from_mac80211(mld->nan_device_vif); + struct iwl_mld_nan_link *nan_link; + u32 link_mask = 0; + + for_each_mld_nan_valid_link(nan_dev, nan_link) + link_mask |= BIT(nan_link->fw_id); + + return link_mask; +} + int iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld, struct ieee80211_link_sta *link_sta) { @@ -459,21 +476,13 @@ int iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld, 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; + if (WARN_ON(!mld->nan_device_vif)) + return -EINVAL; is_6ghz = false; uora_exists = false; - 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); + link_mask = iwl_mld_get_nan_link_mask(mld); } else { struct ieee80211_bss_conf *link; struct iwl_mld_link *mld_link; @@ -1063,7 +1072,7 @@ static int iwl_mld_send_aux_sta_cmd(struct iwl_mld *mld, } static int -iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld, +iwl_mld_set_internal_sta_to_fw(struct iwl_mld *mld, const struct iwl_mld_int_sta *internal_sta, u32 link_mask, const u8 *addr) { @@ -1085,8 +1094,12 @@ iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld, cmd.mfp = cpu_to_le32(1); if (addr) { - memcpy(cmd.peer_mld_address, addr, ETH_ALEN); - memcpy(cmd.peer_link_address, addr, ETH_ALEN); + if (internal_sta->sta_type == STATION_TYPE_NAN_MCAST_DATA) { + ether_addr_copy(cmd.ndi_local_addr, addr); + } else { + memcpy(cmd.peer_mld_address, addr, ETH_ALEN); + memcpy(cmd.peer_link_address, addr, ETH_ALEN); + } } return iwl_mld_send_sta_cmd(mld, &cmd); @@ -1108,7 +1121,7 @@ static int iwl_mld_add_internal_sta(struct iwl_mld *mld, internal_sta->sta_type = sta_type; - ret = iwl_mld_add_internal_sta_to_fw(mld, internal_sta, link_mask, + ret = iwl_mld_set_internal_sta_to_fw(mld, internal_sta, link_mask, addr); if (ret) goto err; @@ -1432,6 +1445,51 @@ int iwl_mld_add_nan_mgmt_sta(struct iwl_mld *mld, 0, NULL, IWL_MAX_TID_COUNT, true); } +int iwl_mld_add_nan_mcast_data_sta(struct iwl_mld *mld, + const u8 *ndi_addr, + struct iwl_mld_int_sta *sta) +{ + u32 link_mask = iwl_mld_get_nan_link_mask(mld); + + /* In case that there are no NAN links, nothing to do */ + if (!link_mask) + return 0; + + return iwl_mld_add_internal_sta(mld, sta, + STATION_TYPE_NAN_MCAST_DATA, + link_mask, ndi_addr, + 0, true); +} + +int iwl_mld_update_nan_mcast_data_sta(struct iwl_mld *mld, + const u8 *ndi_addr, + struct iwl_mld_int_sta *sta) +{ + u32 link_mask; + + if (WARN_ON(!mld->nan_device_vif)) + return -EINVAL; + + /* If the sta doesn't exist, add it */ + if (sta->sta_id == IWL_INVALID_STA) + return iwl_mld_add_nan_mcast_data_sta(mld, ndi_addr, sta); + + /* The station was already added */ + if (WARN_ON(sta->sta_type != STATION_TYPE_NAN_MCAST_DATA)) + return -EINVAL; + + link_mask = iwl_mld_get_nan_link_mask(mld); + if (link_mask) + return iwl_mld_set_internal_sta_to_fw(mld, + sta, link_mask, + ndi_addr); + + /* If no links are associated with NAN, remove the station */ + iwl_mld_remove_nan_mcast_data_sta(mld, sta); + + return 0; +} + void iwl_mld_remove_nan_bcast_sta(struct iwl_mld *mld, struct iwl_mld_int_sta *sta) { @@ -1443,3 +1501,9 @@ void iwl_mld_remove_nan_mgmt_sta(struct iwl_mld *mld, { iwl_mld_remove_internal_sta(mld, sta, true, IWL_MAX_TID_COUNT); } + +void iwl_mld_remove_nan_mcast_data_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta) +{ + iwl_mld_remove_internal_sta(mld, sta, true, 0); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.h b/drivers/net/wireless/intel/iwlwifi/mld/sta.h index df859a9e5230..98d693235c66 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.h @@ -283,10 +283,21 @@ int iwl_mld_add_nan_bcast_sta(struct iwl_mld *mld, int iwl_mld_add_nan_mgmt_sta(struct iwl_mld *mld, struct iwl_mld_int_sta *sta); +int iwl_mld_add_nan_mcast_data_sta(struct iwl_mld *mld, + const u8 *ndi_addr, + struct iwl_mld_int_sta *sta); + +int iwl_mld_update_nan_mcast_data_sta(struct iwl_mld *mld, + const u8 *ndi_addr, + struct iwl_mld_int_sta *sta); + void iwl_mld_remove_nan_bcast_sta(struct iwl_mld *mld, struct iwl_mld_int_sta *sta); void iwl_mld_remove_nan_mgmt_sta(struct iwl_mld *mld, struct iwl_mld_int_sta *sta); +void iwl_mld_remove_nan_mcast_data_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta); + #endif /* __iwl_mld_sta_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c index 39e1d959e42c..1100f7f1e76f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c @@ -351,7 +351,8 @@ u8 iwl_mld_get_lowest_rate(struct iwl_mld *mld, iwl_mld_get_basic_rates_and_band(mld, vif, info, &basic_rates, &band); if (band >= NUM_NL80211_BANDS) { - WARN_ON(vif->type != NL80211_IFTYPE_NAN); + WARN_ON(vif->type != NL80211_IFTYPE_NAN && + vif->type != NL80211_IFTYPE_NAN_DATA); return IWL_FIRST_OFDM_RATE; } @@ -682,6 +683,14 @@ iwl_mld_get_tx_queue_id(struct iwl_mld *mld, struct ieee80211_txq *txq, case NL80211_IFTYPE_NAN: WARN_ON(!ieee80211_is_mgmt(fc)); return iwl_mld_nan_get_mgmt_queue(mld, info->control.vif); + case NL80211_IFTYPE_NAN_DATA: + WARN_ON(!ieee80211_is_data(fc)); + + if (!iwl_mld_nan_use_nan_stations(mld)) + break; + + mld_vif = iwl_mld_vif_from_mac80211(info->control.vif); + return mld_vif->nan.mcast_data_sta.queue_id; default: WARN_ONCE(1, "Unsupported vif type\n"); break; From 927c7e847389745682d501ab5928f74131bd8f1b Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 13 May 2026 08:44:02 +0300 Subject: [PATCH 101/143] wifi: iwlwifi: mld: set correct key mask for NAN For NAN data, the supplicant may derive and install GTKs to transmit with. As multicast data encryption is supported only with firmware versions that support adding NAN multicast data station, allow GTK installation only with such FW versions. Otherwise, do not install the GTK to the device. For NAN management, there are no GTKs, but there are IGTKs and BIGTKs, which need to be installed for the NAN broadcast and NAN management stations. This is supported only with firmware versions that allow adding NAN broadcast station and NAN management station. Handle both of these cases by adjusting the station mask appropriately, returning a zero station mask for the GTK and allowing that to not be an error/warning message. Signed-off-by: Johannes Berg Co-developed-by: Ilan Peer Signed-off-by: Ilan Peer Link: https://patch.msgid.link/20260513084215.7e18e0718d22.Icbf4204a9f1190eb4102016ad5f1c5bdab49635b@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/mld/iface.c | 2 + .../net/wireless/intel/iwlwifi/mld/iface.h | 3 ++ drivers/net/wireless/intel/iwlwifi/mld/key.c | 50 +++++++++++++++++-- .../net/wireless/intel/iwlwifi/mld/mac80211.c | 1 - drivers/net/wireless/intel/iwlwifi/mld/sta.c | 8 ++- 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c index 399efeb469f6..2b837c6fa5fe 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c @@ -66,6 +66,8 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif) iwl_mld_free_internal_sta(mld, &mld_vif->nan.bcast_sta); if (mld_vif->nan.mgmt_sta.sta_id != IWL_INVALID_STA) iwl_mld_free_internal_sta(mld, &mld_vif->nan.mgmt_sta); + + mld_vif->nan.tx_igtk = NULL; } if (vif->type == NL80211_IFTYPE_NAN_DATA && diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.h b/drivers/net/wireless/intel/iwlwifi/mld/iface.h index c6a7588df1fa..bc6f45ff76f8 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h @@ -160,6 +160,8 @@ struct iwl_mld_emlsr { * @nan.mgmt_sta: internal station used for NAN management frames, e.g., SDFs * and NAFs. * @nan.mcast_data_sta: internal station used for multicast NAN Data frames. + * @nan.tx_igtk: TX IGTK key for NAN, tracked separately since NAN does not + * use the vif links. */ struct iwl_mld_vif { /* Add here fields that need clean up on restart */ @@ -189,6 +191,7 @@ struct iwl_mld_vif { struct iwl_mld_int_sta bcast_sta; struct iwl_mld_int_sta mgmt_sta; struct iwl_mld_int_sta mcast_data_sta; + struct ieee80211_key_conf *tx_igtk; } nan; struct iwl_mld_emlsr emlsr; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/key.c b/drivers/net/wireless/intel/iwlwifi/mld/key.c index 151e35b851f7..a6454d90d0e3 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/key.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/key.c @@ -63,6 +63,40 @@ static u32 iwl_mld_get_key_sta_mask(struct iwl_mld *mld, lockdep_assert_wiphy(mld->wiphy); + if (vif->type == NL80211_IFTYPE_NAN_DATA && !sta) { + /* Older firmware versions do not support transmission of + * multicast data frames. + */ + if (!iwl_mld_nan_use_nan_stations(mld)) + return 0; + + if (WARN_ON(mld_vif->nan.mcast_data_sta.sta_id == + IWL_INVALID_STA)) + return 0; + + return BIT(mld_vif->nan.mcast_data_sta.sta_id); + } + + if (vif->type == NL80211_IFTYPE_NAN && !sta) { + /* Older firmware versions do not support installation of + * IGTK/BIGTK keys. + */ + if (!iwl_mld_nan_use_nan_stations(mld)) + return 0; + + if (WARN_ON(mld_vif->nan.bcast_sta.sta_id == IWL_INVALID_STA || + mld_vif->nan.mgmt_sta.sta_id == IWL_INVALID_STA)) + return 0; + + if (key->keyidx >= 4 && key->keyidx <= 5) + return BIT(mld_vif->nan.mgmt_sta.sta_id); + + if (key->keyidx >= 6 && key->keyidx <= 7) + return BIT(mld_vif->nan.bcast_sta.sta_id); + + return 0; + } + /* AP group keys are per link and should be on the mcast/bcast STA */ if (vif->type == NL80211_IFTYPE_AP && !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { @@ -204,7 +238,7 @@ iwl_mld_get_igtk_ptr(struct ieee80211_vif *vif, return &mld_sta->deflink.rx_igtk; } - return &mld_vif->deflink.tx_igtk; + return &mld_vif->nan.tx_igtk; case NL80211_IFTYPE_AP: mld_link = iwl_mld_link_dereference_check(mld_vif, link_id); if (WARN_ON(!mld_link)) @@ -254,8 +288,16 @@ int iwl_mld_add_key(struct iwl_mld *mld, lockdep_assert_wiphy(mld->wiphy); - if (!sta_mask) + if (!sta_mask) { + /* for NAN (GTK) indicate SW-only, it's not used at all */ + if (vif->type == NL80211_IFTYPE_NAN_DATA && !sta && + !iwl_mld_nan_use_nan_stations(mld)) + return 1; + + /* otherwise that's not valid */ + IWL_WARN(mld, "empty STA mask for key %d\n", key->keyidx); return -EINVAL; + } igtk_ptr = iwl_mld_get_igtk_ptr(vif, sta, key); if (igtk_ptr) { @@ -270,8 +312,10 @@ int iwl_mld_add_key(struct iwl_mld *mld, } ret = iwl_mld_add_key_to_fw(mld, sta_mask, key_flags, key); - if (ret) + if (ret) { + IWL_WARN(mld, "failed to add key to FW (%d)\n", ret); return ret; + } if (igtk_ptr) { WARN_ON(*igtk_ptr); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index b6df09812dae..9736b43155d6 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -2248,7 +2248,6 @@ static int iwl_mld_set_key_add(struct iwl_mld *mld, ret = iwl_mld_add_key(mld, vif, sta, key); if (ret) { - IWL_WARN(mld, "set key failed (%d)\n", ret); if (ptk_pn) { RCU_INIT_POINTER(mld_sta->ptk_pn[keyidx], NULL); kfree(ptk_pn); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.c b/drivers/net/wireless/intel/iwlwifi/mld/sta.c index cd13238ed613..77eeeed66116 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.c @@ -1089,8 +1089,14 @@ iwl_mld_set_internal_sta_to_fw(struct iwl_mld *mld, * On the other hand, FW will never check this flag during RX since * an AP/GO doesn't receive protected broadcast management frames. * So, we can set it unconditionally. + * + * For NAN stations associated with a NAN Device, the MFP bit must be + * set to 1, as otherwise the FW will assert when a key associated with + * these stations would be added. */ - if (internal_sta->sta_type == STATION_TYPE_BCAST_MGMT) + if (internal_sta->sta_type == STATION_TYPE_BCAST_MGMT || + internal_sta->sta_type == STATION_TYPE_NAN_BCAST || + internal_sta->sta_type == STATION_TYPE_NAN_MGMT) cmd.mfp = cpu_to_le32(1); if (addr) { From 6d75bcc06ae468dafeac123e7150fb7fd3ed3872 Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Fri, 15 May 2026 15:09:34 +0300 Subject: [PATCH 102/143] wifi: iwlwifi: mld: nan: add availability attribute to schedule config Add the availability attribute to the schedule config command. The firmware needs to add this attribute to schedule update frames (e.g. when ULW changed or the local schedule changed). Signed-off-by: Avraham Stern Link: https://patch.msgid.link/20260515150751.97809376508e.Ie7f00f97992016c6bb2f4a5c9fc201ac58eed8ce@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index 530ba263c5f0..d13b2374b642 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -693,6 +693,14 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, break; case 2: cmd_size = sizeof(struct iwl_nan_schedule_cmd); + + if (sched_cfg->avail_blob_len && + !WARN_ON(sched_cfg->avail_blob_len > + sizeof(cmd.avail_attr.attr))) { + cmd.avail_attr.attr_len = sched_cfg->avail_blob_len; + memcpy(cmd.avail_attr.attr, sched_cfg->avail_blob, + sched_cfg->avail_blob_len); + } break; default: IWL_ERR(mld, "NAN: unsupported NAN schedule cmd version %d\n", @@ -727,6 +735,15 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, BUILD_BUG_ON(ARRAY_SIZE(sched_cfg->channels) != ARRAY_SIZE(cmd.channels)); + /* + * mac80211 removes unused channels before adding new ones, so it may + * update an empty schedule with an availability attribute because it + * is going to add channels later. Since the firmware does not expect + * an availability attribute without channels, ignore it in that case. + */ + if (empty_schedule) + cmd.avail_attr.attr_len = 0; + /* find links we can keep (same chanctx/PHY) */ for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) { struct ieee80211_chanctx_conf *chanctx; From 6f55c4f3ca2bd1621cd1a7bb1ec65de4c595a819 Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Fri, 15 May 2026 15:09:35 +0300 Subject: [PATCH 103/143] wifi: iwlwifi: mld: add support for deferred nan schedule config Add support for deferred schedule update. Notify mac80211 that the schedule update is done when the firmware notifies that the schedule is applied. Signed-off-by: Avraham Stern Link: https://patch.msgid.link/20260515150751.a69730d26890.I2ae54cbed8b507e6398a55c19795b27d5ea03aba@changeid Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/mac-cfg.h | 29 +++++++++++++ drivers/net/wireless/intel/iwlwifi/mld/mld.c | 1 + drivers/net/wireless/intel/iwlwifi/mld/nan.c | 43 +++++++++++++++++++ drivers/net/wireless/intel/iwlwifi/mld/nan.h | 5 +++ .../net/wireless/intel/iwlwifi/mld/notif.c | 4 ++ 5 files changed, 82 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 d03a2bda4e68..09f869b5be0c 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -84,6 +84,11 @@ enum iwl_mac_conf_subcmd_ids { * @NAN_ULW_ATTR_NOTIF: &struct iwl_nan_ulw_attr_notif */ NAN_ULW_ATTR_NOTIF = 0xf2, + /** + * @NAN_SCHED_UPDATE_COMPLETED_NOTIF: + * &struct iwl_nan_sched_update_completed_notif + */ + NAN_SCHED_UPDATE_COMPLETED_NOTIF = 0xf3, /** * @NAN_DW_END_NOTIF: &struct iwl_nan_dw_end_notif */ @@ -1421,4 +1426,28 @@ struct iwl_nan_ulw_attr_notif { u8 attr[IWL_NAN_MAX_ENDLESS_ULW_ATTR_LEN]; } __packed; /* NAN_ULW_ATTR_NOTIF_API_S_VER_1 */ +/** + * enum iwl_nan_sched_update_status - NAN schedule update status + * + * @IWL_NAN_SCHED_UPDATE_SUCCESS: schedule update completed successfully + * @IWL_NAN_SCHED_UPDATE_FAILURE: schedule update failed. Currently not expected + * to happen, but reserved for future use. + */ +enum iwl_nan_sched_update_status { + IWL_NAN_SCHED_UPDATE_SUCCESS = 0, + IWL_NAN_SCHED_UPDATE_FAILURE = 1, +}; + +/** + * struct iwl_nan_sched_update_completed_notif - NAN schedule update completed + * + * @status: status of the schedule update operation. See + * &enum iwl_nan_sched_update_status + * @reserved: reserved + */ +struct iwl_nan_sched_update_completed_notif { + u8 status; + u8 reserved[3]; +} __packed; /* NAN_SCHED_UPDATE_COMPLETED_NTF_API_S_VER_1 */ + #endif /* __iwl_fw_api_mac_cfg_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 054bb1134425..0ef7c24831d8 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -238,6 +238,7 @@ static const struct iwl_hcmd_names iwl_mld_mac_conf_names[] = { HCMD_NAME(NAN_SCHEDULE_CMD), HCMD_NAME(NAN_PEER_CMD), HCMD_NAME(NAN_ULW_ATTR_NOTIF), + HCMD_NAME(NAN_SCHED_UPDATE_COMPLETED_NOTIF), 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 d13b2374b642..d34a9a2cbeae 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -689,11 +689,20 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, switch (version) { case 1: + if (sched_cfg->deferred) { + IWL_ERR(mld, + "NAN: deferred schedule not supported by FW\n"); + return; + } + cmd_size = sizeof(struct iwl_nan_schedule_cmd_v1); break; case 2: cmd_size = sizeof(struct iwl_nan_schedule_cmd); + if (sched_cfg->deferred) + cmd.deferred = 1; + if (sched_cfg->avail_blob_len && !WARN_ON(sched_cfg->avail_blob_len > sizeof(cmd.avail_attr.attr))) { @@ -869,6 +878,40 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, } } +bool iwl_mld_cancel_nan_sched_update_completed_notif(struct iwl_mld *mld, + struct iwl_rx_packet *pkt, + u32 obj_id) +{ + return true; +} + +void iwl_mld_handle_nan_sched_update_completed_notif(struct iwl_mld *mld, + struct iwl_rx_packet *pkt) +{ + struct iwl_nan_sched_update_completed_notif *notif = (void *)pkt->data; + struct ieee80211_vif *vif = mld->nan_device_vif; + + if (IWL_FW_CHECK(mld, !vif, + "NAN: schedule update completed without NAN vif\n")) + return; + + if (IWL_FW_CHECK(mld, !ieee80211_vif_nan_started(vif), + "NAN: schedule update completed without NAN started\n")) + return; + + /* + * Deferred schedule update should not fail in firmware since all + * channels and links were added. + */ + IWL_FW_CHECK(mld, notif->status != IWL_NAN_SCHED_UPDATE_SUCCESS, + "NAN: deferred schedule update failed\n"); + + if (WARN_ON(!vif->cfg.nan_sched.deferred)) + return; + + ieee80211_nan_sched_update_done(vif); +} + int iwl_mld_mac802111_nan_peer_sched_changed(struct ieee80211_hw *hw, struct ieee80211_sta *sta) { diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.h b/drivers/net/wireless/intel/iwlwifi/mld/nan.h index f4e4cd4b4e8d..5411bca52cde 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.h @@ -42,6 +42,8 @@ void iwl_mld_handle_nan_ulw_attr_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt); void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt); +void iwl_mld_handle_nan_sched_update_completed_notif(struct iwl_mld *mld, + struct iwl_rx_packet *pkt); bool iwl_mld_cancel_nan_cluster_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt, u32 obj_id); @@ -51,6 +53,9 @@ bool iwl_mld_cancel_nan_ulw_attr_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); +bool iwl_mld_cancel_nan_sched_update_completed_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); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index a5074bbc7e24..d7383022a0ed 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -348,6 +348,8 @@ CMD_VERSIONS(beacon_filter_notif, CMD_VER_ENTRY(2, iwl_beacon_filter_notif)) CMD_VERSIONS(nan_cluster_notif, CMD_VER_ENTRY(1, iwl_nan_cluster_notif)) CMD_VERSIONS(nan_ulw_attr_notif, CMD_VER_ENTRY(1, iwl_nan_ulw_attr_notif)) CMD_VERSIONS(nan_dw_end_notif, CMD_VER_ENTRY(1, iwl_nan_dw_end_notif)) +CMD_VERSIONS(nan_sched_update_completed_notif, + CMD_VER_ENTRY(1, iwl_nan_sched_update_completed_notif)) DEFINE_SIMPLE_CANCELLATION(session_prot, iwl_session_prot_notif, mac_link_id) DEFINE_SIMPLE_CANCELLATION(tlc, iwl_tlc_update_notif, sta_id) @@ -468,6 +470,8 @@ const struct iwl_rx_handler iwl_mld_rx_handlers[] = { nan_ulw_attr_notif) RX_HANDLER_OF_NAN(MAC_CONF_GROUP, NAN_DW_END_NOTIF, nan_dw_end_notif) + RX_HANDLER_OF_NAN(MAC_CONF_GROUP, NAN_SCHED_UPDATE_COMPLETED_NOTIF, + nan_sched_update_completed_notif) }; EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_rx_handlers); From e9308657a4693849bb1fb6bd1f8fc1de7cac327f Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 15 May 2026 15:09:36 +0300 Subject: [PATCH 104/143] wifi: iwlwifi: mld: add UHR DUO support The firmware needs to know whether or not DUO (assisting) is supported by the AP, and we should claim DUO support ourselves. Implement this. Signed-off-by: Johannes Berg Reviewed-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260515150751.c006eda0139a.I2b1b540c256d29b3b6af21f7eacbe1f549734e9f@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h | 2 ++ drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c | 1 + drivers/net/wireless/intel/iwlwifi/mld/link.c | 5 +++++ 3 files changed, 8 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 09f869b5be0c..dd69687a4e48 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -556,6 +556,7 @@ enum iwl_link_ctx_protection_flags { * of threshold * @LINK_FLG_NPCA: NPCA enabled * @LINK_FLG_DPS: AP is a DPS assisting AP + * @LINK_FLG_DUO: AP supports UHR DUO */ enum iwl_link_ctx_flags { LINK_FLG_BSS_COLOR_DIS = BIT(0), @@ -564,6 +565,7 @@ enum iwl_link_ctx_flags { LINK_FLG_NDP_FEEDBACK_ENABLED = BIT(3), LINK_FLG_NPCA = BIT(4), LINK_FLG_DPS = BIT(6), + LINK_FLG_DUO = BIT(8), }; /* LINK_CONTEXT_FLAG_E_VER_1 */ /** diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index f29de029c709..7027bca249a0 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -698,6 +698,7 @@ static const struct ieee80211_sband_iftype_data iwl_iftype_cap[] = { .mac.mac_cap = { [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP | IEEE80211_UHR_MAC_CAP0_DPS_SUPP, + [1] = IEEE80211_UHR_MAC_CAP1_DUO_SUPP, }, }, }, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index 3c1825ba2b86..4ac2740dd8a1 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -347,6 +347,11 @@ iwl_mld_change_link_in_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link, link_sta->uhr_cap.mac.mac_cap[0] & IEEE80211_UHR_MAC_CAP0_DPS_ASSIST_SUPP) flags |= LINK_FLG_DPS; + + if (link_sta->uhr_cap.has_uhr && + link_sta->uhr_cap.mac.mac_cap[1] & + IEEE80211_UHR_MAC_CAP1_DUO_SUPP) + flags |= LINK_FLG_DUO; } cmd.htc_trig_based_pkt_ext = link->htc_trig_based_pkt_ext; From c0240187445936792903d878f5eb0adf51e19de1 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 15 May 2026 15:09:37 +0300 Subject: [PATCH 105/143] wifi: iwlwifi: mld: implement UHR multi-link PM For multi-link power management, the driver really only needs to advertise the capability and tell the firmware that the AP has it. Implement this. Signed-off-by: Johannes Berg Reviewed-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260515150751.a28557532e2b.Id35c7f7c319c36daf8ef35fec28940d4db6fc253@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h | 2 ++ drivers/net/wireless/intel/iwlwifi/mld/link.c | 5 +++++ drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 2 ++ 3 files changed, 9 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 dd69687a4e48..09b2cddc4ad2 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -556,6 +556,7 @@ enum iwl_link_ctx_protection_flags { * of threshold * @LINK_FLG_NPCA: NPCA enabled * @LINK_FLG_DPS: AP is a DPS assisting AP + * @LINK_FLG_MLPM: AP supports UHR multi-link PM * @LINK_FLG_DUO: AP supports UHR DUO */ enum iwl_link_ctx_flags { @@ -565,6 +566,7 @@ enum iwl_link_ctx_flags { LINK_FLG_NDP_FEEDBACK_ENABLED = BIT(3), LINK_FLG_NPCA = BIT(4), LINK_FLG_DPS = BIT(6), + LINK_FLG_MLPM = BIT(7), LINK_FLG_DUO = BIT(8), }; /* LINK_CONTEXT_FLAG_E_VER_1 */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index 4ac2740dd8a1..e5e9b7089c14 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -352,6 +352,11 @@ iwl_mld_change_link_in_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link, link_sta->uhr_cap.mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DUO_SUPP) flags |= LINK_FLG_DUO; + + if (link_sta->uhr_cap.has_uhr && + mld_vif->ap_sta->ext_mld_capa_ops & + IEEE80211_UHR_ML_EXT_MLD_CAPA_ML_PM) + flags |= LINK_FLG_MLPM; } cmd.htc_trig_based_pkt_ext = link->htc_trig_based_pkt_ext; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 9736b43155d6..9dc540720ecd 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -420,6 +420,8 @@ static void iwl_mac_hw_set_wiphy(struct iwl_mld *mld) mld->ext_capab[0].eml_capabilities = IWL_MLD_EMLSR_CAPA; mld->ext_capab[0].mld_capa_and_ops = IWL_MLD_CAPA_OPS; + mld->ext_capab[0].ext_mld_capa_and_ops = + IEEE80211_UHR_ML_EXT_MLD_CAPA_ML_PM; } From 5aee72298cb969bac5358d40bb94ac878503cd2d Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Fri, 15 May 2026 15:09:38 +0300 Subject: [PATCH 106/143] wifi: iwlwifi: mld: evacuate NAN channels on link switch The FW API doesn't allow a station (of certain types, including STATION_TYPE_PEER) to not be correlated to any link. Therefore, when switching links, mac80211 first adds the new link and then removes the old one. In case we have a NAN interface that operates on 3 links and a BSS interface that operates on the 4th link, there won't be any room to perform a link switch that temporarily needs two links for the BSS interface, but the firmware only has four total. To mitigate this, try to evacuate a NAN channel in this scenario: First try to evacuate a NAN channel using the same phy as the BSS link being deactivated, since we expect NAN to follow the BSS channel configuration. If that doesn't work, try to evacuate any NAN channel. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260515150751.a193c0f41b1f.I1d56c8d8d61d110422271971843b71a93f5ec354@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/mld/mac80211.c | 95 ++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 9dc540720ecd..92858b8f7395 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -2585,15 +2585,108 @@ iwl_mld_mac80211_mgd_protect_tdls_discover(struct ieee80211_hw *hw, ret); } +static int iwl_mld_count_free_link_ids(struct iwl_mld *mld) +{ + int free_count = 0; + + for (int i = 0; i < mld->fw->ucode_capa.num_links; i++) { + if (!rcu_access_pointer(mld->fw_id_to_bss_conf[i])) + free_count++; + } + + return free_count; +} + +static bool +iwl_mld_chanctx_used_by_other_vif(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *chanctx_conf) +{ + struct ieee80211_bss_conf *iter_link; + struct ieee80211_vif *iter_vif; + int link_id; + + for_each_active_interface(iter_vif, hw) { + if (vif == iter_vif) + continue; + + /* NAN doesn't have active links, so we don't count NAN users */ + for_each_vif_active_link(iter_vif, iter_link, link_id) { + if (rcu_access_pointer(iter_link->chanctx_conf) == + chanctx_conf) + return true; + } + } + + return false; +} + static bool iwl_mld_can_activate_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 desired_links) { struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); int n_links = hweight16(desired_links); + int n_add = hweight16(desired_links & ~vif->active_links); + unsigned long to_deactivate = vif->active_links & ~desired_links; + int free_link_ids; + int i; /* Check if HW supports the wanted number of links */ - return n_links <= iwl_mld_max_active_links(mld, vif); + if (n_links > iwl_mld_max_active_links(mld, vif)) + return false; + + /* + * During link switch, mac80211 first adds the new links, then removes + * the old ones. This means we temporarily need extra link objects + * during the transition. Check if we have enough free link IDs. + */ + + free_link_ids = iwl_mld_count_free_link_ids(mld); + + if (free_link_ids >= n_add) + return true; + + if (!mld->nan_device_vif) + return false; + + /* + * Not enough free link IDs. First try to evacuate NAN from the + * channel context of a link that is going to be deactivated. + */ + for_each_set_bit(i, &to_deactivate, IEEE80211_MLD_MAX_NUM_LINKS) { + struct ieee80211_bss_conf *link_conf; + struct ieee80211_chanctx_conf *chanctx_conf; + + link_conf = link_conf_dereference_protected(vif, i); + if (!link_conf) + continue; + + chanctx_conf = wiphy_dereference(mld->wiphy, link_conf->chanctx_conf); + if (!chanctx_conf) + continue; + + if (iwl_mld_chanctx_used_by_other_vif(hw, vif, chanctx_conf)) + continue; + + if (ieee80211_nan_try_evacuate(hw, chanctx_conf)) { + free_link_ids = iwl_mld_count_free_link_ids(mld); + /* + * Evacuation of one channel should do the job. If not, + * something bad is happening. Don't try to evacuate more + */ + return free_link_ids >= n_add; + } + } + + /* Couldn't find/evacuate any channel going to go unused, try any */ + if (ieee80211_nan_try_evacuate(hw, NULL)) { + free_link_ids = iwl_mld_count_free_link_ids(mld); + if (free_link_ids >= n_add) + return true; + } + + return false; } static int From e5d5a3d7749898d8c94b548c667b41ffca4459a9 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Fri, 15 May 2026 15:09:39 +0300 Subject: [PATCH 107/143] wifi: iwlwifi: reduce the log level of firmware debug buffer size mismatch This is not really an error and we can safely reduce the log level to INFO. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260515150751.36a772e925aa.I0f8db3099bd07e72ee007b322c0f85102f0550f9@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c index e3603571bdd9..50342604491d 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c @@ -141,10 +141,10 @@ static void iwl_pcie_alloc_fw_monitor_block(struct iwl_trans *trans, return; if (power != max_power) - IWL_ERR(trans, - "Sorry - debug buffer is only %luK while you requested %luK\n", - (unsigned long)BIT(power - 10), - (unsigned long)BIT(max_power - 10)); + IWL_INFO(trans, + "Sorry - debug buffer is only %luK while you requested %luK\n", + (unsigned long)BIT(power - 10), + (unsigned long)BIT(max_power - 10)); fw_mon->block = block; fw_mon->physical = physical; From f26185a4da67a40e9d805206abf77f83c541a983 Mon Sep 17 00:00:00 2001 From: Daniel Gabay Date: Fri, 15 May 2026 15:09:40 +0300 Subject: [PATCH 108/143] wifi: iwlwifi: print UHR rate type Log "UHR" in rs_pretty_print_rate() instead of "Unknown". Signed-off-by: Daniel Gabay Link: https://patch.msgid.link/20260515150751.768874507c43.I3cffe81612cd0f2fc218ab26ae2aa3e9ba541a15@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/rs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/rs.c b/drivers/net/wireless/intel/iwlwifi/fw/rs.c index 746f2acffb8f..2aa300b26158 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/rs.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/rs.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2021-2022, 2025 Intel Corporation + * Copyright (C) 2021-2022, 2025-2026 Intel Corporation */ #include @@ -124,6 +124,9 @@ int rs_pretty_print_rate(char *buf, int bufsz, const u32 rate) case RATE_MCS_MOD_TYPE_EHT: type = "EHT"; break; + case RATE_MCS_MOD_TYPE_UHR: + type = "UHR"; + break; default: type = "Unknown"; /* shouldn't happen */ } From 7b32710111214bcbb2fd0da0fd73b5c806c2d96c Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Fri, 15 May 2026 15:09:41 +0300 Subject: [PATCH 109/143] wifi: iwlwifi: mld: Disallow using a per-STA GTK for Tx When a GTK is configured for a station, it can only be used for Rx and never for Tx. In such a case, set the IWL_SEC_KEY_FLAG_NO_TX flag when the key is installed, so the FW will not use it for Tx. Specifically, this is needed for per-station GTK installed on NAN NDI stations associated with NAN Data interfaces. Signed-off-by: Ilan Peer Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260515150751.b004744087cb.I25fb83f9e3dc563d122a160da150d793155513fa@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/key.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/key.c b/drivers/net/wireless/intel/iwlwifi/mld/key.c index a6454d90d0e3..bf80b4770b5a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/key.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/key.c @@ -49,6 +49,12 @@ static u32 iwl_mld_get_key_flags(struct iwl_mld *mld, if (key->flags & IEEE80211_KEY_FLAG_SPP_AMSDU) flags |= IWL_SEC_KEY_FLAG_SPP_AMSDU; + /* When a GTK is configured for a station, it can only be + * used for Rx and never for Tx. Thus, set the NO TX flag. + */ + if (!pairwise && sta) + flags |= IWL_SEC_KEY_FLAG_NO_TX; + return flags; } From 0cff2f246db56c03f1e00261239fa6a9f1ec43bf Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 15 May 2026 15:09:42 +0300 Subject: [PATCH 110/143] wifi: iwlwifi: mld: rename LINK_DEBUGFS_WRITE_FILE_OPS These are with wiphy mutex held, so rename the macro to LINK_DEBUGFS_WIPHY_WRITE_FILE_OPS indicating that. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260515150751.5bff36ea7dd0.I62b01f83b622f281257fb842d9cc00b28dc2f5e1@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/debugfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c b/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c index ba5a47519aa1..afe972834cb8 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c @@ -979,7 +979,8 @@ void iwl_mld_add_vif_debugfs(struct ieee80211_hw *hw, VIF_DEBUGFS_ADD_FILE(twt_operation, mld_vif_dbgfs, 0200); VIF_DEBUGFS_ADD_FILE(int_mlo_scan, mld_vif_dbgfs, 0200); } -#define LINK_DEBUGFS_WRITE_FILE_OPS(name, bufsz) \ + +#define LINK_DEBUGFS_WIPHY_WRITE_FILE_OPS(name, bufsz) \ WIPHY_DEBUGFS_WRITE_FILE_OPS(link_##name, bufsz, bss_conf) #define LINK_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) \ From 308decca2de5fbc4d5022fe9f846fec87d18fda4 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 15 May 2026 15:09:43 +0300 Subject: [PATCH 111/143] wifi: iwlwifi: mld: add link and link station FW IDs to debugfs Add the link and link station FW IDs to debugfs to aid debug and testing, since assignments can't be known ahead of time, especially with ID randomisation turned on. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260515150751.7224fab5fe8d.Ic2fd82f5f20945aa070ac9e38882fcff2172a4d8@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/mld/debugfs.c | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c b/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c index afe972834cb8..351a4f177e92 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c @@ -983,12 +983,35 @@ void iwl_mld_add_vif_debugfs(struct ieee80211_hw *hw, #define LINK_DEBUGFS_WIPHY_WRITE_FILE_OPS(name, bufsz) \ WIPHY_DEBUGFS_WRITE_FILE_OPS(link_##name, bufsz, bss_conf) +/* + * Note: no locking is provided, so the function must have its own, + * but it cannot acquire the wiphy mutex. + */ +#define LINK_DEBUGFS_READ_FILE_OPS(name, bufsz) \ + _MLD_DEBUGFS_READ_FILE_OPS(link_##name, bufsz, struct ieee80211_bss_conf) + #define LINK_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) \ debugfs_create_file(alias, mode, parent, link_conf, \ &iwl_dbgfs_link_##name##_ops) #define LINK_DEBUGFS_ADD_FILE(name, parent, mode) \ LINK_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode) +static ssize_t iwl_dbgfs_link_fw_id_read(struct ieee80211_bss_conf *link_conf, + size_t buflen, void *buf) +{ + struct iwl_mld_link *mld_link; + + guard(rcu)(); + + mld_link = iwl_mld_link_from_mac80211(link_conf); + if (!mld_link) + return -EINVAL; + + return scnprintf(buf, buflen, "%d\n", mld_link->fw_id); +} + +LINK_DEBUGFS_READ_FILE_OPS(fw_id, 64); + void iwl_mld_add_link_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf, @@ -1009,6 +1032,8 @@ void iwl_mld_add_link_debugfs(struct ieee80211_hw *hw, /* Release the reference from debugfs_lookup */ dput(mld_link_dir); } + + LINK_DEBUGFS_ADD_FILE(fw_id, mld_link_dir, 0400); } static ssize_t _iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf, @@ -1092,6 +1117,21 @@ static ssize_t iwl_dbgfs_link_sta_tlc_dhc_write(struct iwl_mld *mld, char *buf, return ret ? : count; } +static ssize_t +iwl_dbgfs_link_sta_fw_id_read(struct ieee80211_link_sta *link_sta, + size_t buflen, void *buf) +{ + struct iwl_mld_link_sta *mld_link_sta; + + guard(rcu)(); + + mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta); + if (!mld_link_sta) + return -EINVAL; + + return scnprintf(buf, buflen, "%u\n", mld_link_sta->fw_id); +} + #define LINK_STA_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) \ debugfs_create_file(alias, mode, parent, link_sta, \ &iwl_dbgfs_link_sta_##name##_ops) @@ -1101,9 +1141,18 @@ static ssize_t iwl_dbgfs_link_sta_tlc_dhc_write(struct iwl_mld *mld, char *buf, #define LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(name, bufsz) \ WIPHY_DEBUGFS_WRITE_FILE_OPS(link_sta_##name, bufsz, link_sta) +/* + * Note: no locking is provided, so the function must have its own, + * but it cannot acquire the wiphy mutex. + */ +#define LINK_STA_DEBUGFS_READ_OPS(name, bufsz) \ + _MLD_DEBUGFS_READ_FILE_OPS(link_sta_##name, bufsz, \ + struct ieee80211_link_sta) + LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(tlc_dhc, 64); LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(fixed_rate, 64); LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(fixed_rate_v3, 64); +LINK_STA_DEBUGFS_READ_OPS(fw_id, 64); void iwl_mld_add_link_sta_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif, @@ -1113,4 +1162,5 @@ void iwl_mld_add_link_sta_debugfs(struct ieee80211_hw *hw, LINK_STA_DEBUGFS_ADD_FILE(fixed_rate, dir, 0200); LINK_STA_DEBUGFS_ADD_FILE(fixed_rate_v3, dir, 0200); LINK_STA_DEBUGFS_ADD_FILE(tlc_dhc, dir, 0200); + LINK_STA_DEBUGFS_ADD_FILE(fw_id, dir, 0400); } From 06101cc80003a37acf843822760a842693f2882c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 15 May 2026 15:09:44 +0300 Subject: [PATCH 112/143] wifi: iwlwifi: api: remove NAN_GROUP This is no longer used by the driver nor supported by firmware, and the kernel-doc reference to enum iwl_nan_subcmd_ids is long dead. Remove it. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260515150751.1427beb76b0a.I6faacff99020984b14a76d9387f3aaa6281f3552@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/api/commands.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h b/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h index dcac0ba8cccf..24bac3f00310 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h @@ -22,7 +22,6 @@ * &enum iwl_data_path_subcmd_ids * @SCAN_GROUP: scan group, uses command IDs from * &enum iwl_scan_subcmd_ids - * @NAN_GROUP: NAN group, uses command IDs from &enum iwl_nan_subcmd_ids * @LOCATION_GROUP: location group, uses command IDs from * &enum iwl_location_subcmd_ids * @BT_COEX_GROUP: bt coex group, uses command IDs from @@ -43,7 +42,6 @@ enum iwl_mvm_command_groups { PHY_OPS_GROUP = 0x4, DATA_PATH_GROUP = 0x5, SCAN_GROUP = 0x6, - NAN_GROUP = 0x7, LOCATION_GROUP = 0x8, BT_COEX_GROUP = 0x9, PROT_OFFLOAD_GROUP = 0xb, From 804efb7c5f86059b50f2b090e63d62ed1d0f7bfa Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 15 May 2026 15:09:45 +0300 Subject: [PATCH 113/143] wifi: iwlwifi: api: clean up/fix some kernel-doc references Some of these structs just don't exist (any more), or other versions should be referenced, clean that up. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260515150751.1e65dc357cbf.I454805593324e51ff71ec5e6bac83aa6dace5383@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/api/commands.h | 7 +++---- drivers/net/wireless/intel/iwlwifi/fw/api/location.h | 7 ++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h b/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h index 24bac3f00310..abd259350589 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h @@ -57,8 +57,7 @@ enum iwl_legacy_cmds { /** * @UCODE_ALIVE_NTFY: * Alive data from the firmware, as described in - * &struct iwl_alive_ntf_v3 or &struct iwl_alive_ntf_v4 or - * &struct iwl_alive_ntf_v5 or &struct iwl_alive_ntf_v7. + * &struct iwl_alive_ntf_v3 or &struct iwl_alive_ntf_v7. */ UCODE_ALIVE_NTFY = 0x1, @@ -384,7 +383,7 @@ enum iwl_legacy_cmds { * @STATISTICS_NOTIFICATION: * one of &struct iwl_notif_statistics_v10, * &struct iwl_notif_statistics_v11, - * &struct iwl_notif_statistic, + * &struct iwl_notif_statistics, * &struct iwl_statistics_operational_ntfy_ver_14 * &struct iwl_statistics_operational_ntfy */ @@ -558,7 +557,7 @@ enum iwl_legacy_cmds { WOWLAN_CONFIGURATION = 0xe1, /** - * @WOWLAN_TSC_RSC_PARAM: &struct iwl_wowlan_rsc_tsc_params_cmd_v4, + * @WOWLAN_TSC_RSC_PARAM: &struct iwl_wowlan_rsc_tsc_params_cmd_ver_2, * &struct iwl_wowlan_rsc_tsc_params_cmd */ WOWLAN_TSC_RSC_PARAM = 0xe2, diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/location.h b/drivers/net/wireless/intel/iwlwifi/fw/api/location.h index 2ee3a48aa5df..d3f774ffacde 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/location.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/location.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2015-2017 Intel Deutschland GmbH * Copyright (C) 2018-2022 Intel Corporation - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #ifndef __iwl_fw_api_location_h__ #define __iwl_fw_api_location_h__ @@ -81,8 +81,9 @@ enum iwl_location_subcmd_ids { * @TOF_RANGE_RESPONSE_NOTIF: ranging response, using one of * &struct iwl_tof_range_rsp_ntfy_v5, * &struct iwl_tof_range_rsp_ntfy_v6, - * &struct iwl_tof_range_rsp_ntfy_v7 or - * &struct iwl_tof_range_rsp_ntfy_v8 + * &struct iwl_tof_range_rsp_ntfy_v7, + * &struct iwl_tof_range_rsp_ntfy_v9 or + * &struct iwl_tof_range_rsp_ntfy */ TOF_RANGE_RESPONSE_NOTIF = 0xFF, }; From 5f88b045d959f8a51757e0fde29d5d204da1176f Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 15 May 2026 15:09:46 +0300 Subject: [PATCH 114/143] wifi: iwlwifi: pcie: add two LNL PCI IDs Add two PCI IDs for two WiFi-7 BE1735x Killer devices (these are CRFs) so they work when put into the LNL platform. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260515150751.d2e3c380227a.I791eef3dedc11a8b246ce3130a34018886e63d3f@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c index fdafbad506a7..b0c59181907e 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -537,6 +537,8 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct pci_device_id iwl_hw_card_ids[] = { {IWL_PCI_DEVICE(0xA840, 0x4314, iwl_bz_mac_cfg)}, {IWL_PCI_DEVICE(0xA840, 0x1775, iwl_bz_mac_cfg)}, {IWL_PCI_DEVICE(0xA840, 0x1776, iwl_bz_mac_cfg)}, + {IWL_PCI_DEVICE(0xA840, 0x1735, iwl_bz_mac_cfg)}, + {IWL_PCI_DEVICE(0xA840, 0x1736, iwl_bz_mac_cfg)}, {IWL_PCI_DEVICE(0x7740, PCI_ANY_ID, iwl_bz_mac_cfg)}, {IWL_PCI_DEVICE(0x4D40, PCI_ANY_ID, iwl_bz_mac_cfg)}, From 5864b87863dbd061dc3a2f779fa6137dbee4a2cb Mon Sep 17 00:00:00 2001 From: Shahar Tzarfati Date: Fri, 15 May 2026 15:09:47 +0300 Subject: [PATCH 115/143] wifi: iwlwifi: mld: expose beacon avg signal Store beacon_average_energy from per-link FW statistics and expose it via station_info as rx_beacon_signal_avg in sta statistics. This fixes missing beacon average signal reporting to upper layers. Signed-off-by: Shahar Tzarfati Link: https://patch.msgid.link/20260515150751.a74a22d90890.I74d596359c5b69364fb977fdf31396eb57ca0927@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.h | 3 ++ .../net/wireless/intel/iwlwifi/mld/stats.c | 47 ++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.h b/drivers/net/wireless/intel/iwlwifi/mld/link.h index 4527f054ce92..0b3974d86531 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.h @@ -36,6 +36,8 @@ struct iwl_probe_resp_data { * @rcu_head: RCU head for freeing this data. * @fw_id: the fw id of the link. * @active: if the link is active or not. + * @avg_signal: The current average signal of beacons [dBm] retrieved from + * firmware per-link periodic stats (STATISTICS_OPER_NOTIF). * @queue_params: QoS data from mac80211. This is updated with a call to * drv_conf_tx per each AC, and then notified once with BSS_CHANGED_QOS. * So we store it here and then send one link cmd for all the ACs. @@ -68,6 +70,7 @@ struct iwl_mld_link { struct_group(zeroed_on_hw_restart, u8 fw_id; bool active; + s8 avg_signal; struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS]; struct ieee80211_chanctx_conf __rcu *chan_ctx; bool he_ru_2mhz_block; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/stats.c b/drivers/net/wireless/intel/iwlwifi/mld/stats.c index b93e0f8ab5fb..e7b283cbe199 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/stats.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/stats.c @@ -311,6 +311,40 @@ static void iwl_mld_sta_stats_fill_txrate(struct iwl_mld_sta *mld_sta, } } +static void iwl_mld_sta_stats_fill_beacon_signal_avg(struct ieee80211_vif *vif, + struct station_info *sinfo) +{ + struct ieee80211_bss_conf *link_conf; + struct iwl_mld_link *link; + u8 link_id; + + if (iwl_mld_emlsr_active(vif)) + return; + + /* TODO: support statistics for NAN */ + if (vif->type == NL80211_IFTYPE_NAN || + vif->type == NL80211_IFTYPE_NAN_DATA) + return; + + link_id = iwl_mld_get_primary_link(vif); + link_conf = link_conf_dereference_protected(vif, link_id); + + if (WARN_ONCE(!link_conf, + "link_conf is NULL for link_id=%u\n", link_id)) + return; + + link = iwl_mld_link_from_mac80211(link_conf); + if (WARN_ONCE(!link, + "iwl_mld_link is NULL for link_id=%u\n", link_id)) + return; + + if (!link->avg_signal) + return; + + sinfo->rx_beacon_signal_avg = link->avg_signal; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_SIGNAL_AVG); +} + void iwl_mld_mac80211_sta_statistics(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, @@ -329,9 +363,9 @@ void iwl_mld_mac80211_sta_statistics(struct ieee80211_hw *hw, iwl_mld_sta_stats_fill_txrate(mld_sta, sinfo); - /* TODO: NL80211_STA_INFO_BEACON_RX */ + iwl_mld_sta_stats_fill_beacon_signal_avg(vif, sinfo); - /* TODO: NL80211_STA_INFO_BEACON_SIGNAL_AVG */ + /* TODO: NL80211_STA_INFO_BEACON_RX */ } #define IWL_MLD_TRAFFIC_LOAD_MEDIUM_THRESH 10 /* percentage */ @@ -443,6 +477,8 @@ iwl_mld_process_per_link_stats(struct iwl_mld *mld, fw_id++) { const struct iwl_stats_ntfy_per_link *link_stats; struct ieee80211_bss_conf *bss_conf; + struct iwl_mld_link *link; + u32 avg_raw; int sig; bss_conf = iwl_mld_fw_id_to_link_conf(mld, fw_id); @@ -456,6 +492,13 @@ iwl_mld_process_per_link_stats(struct iwl_mld *mld, sig = -le32_to_cpu(link_stats->beacon_filter_average_energy); iwl_mld_update_link_sig(bss_conf->vif, sig, bss_conf); + link = iwl_mld_link_from_mac80211(bss_conf); + if (WARN_ON_ONCE(!link)) + continue; + + avg_raw = le32_to_cpu(link_stats->beacon_average_energy); + link->avg_signal = clamp_t(int, -(int)avg_raw, S8_MIN, 0); + /* TODO: parse more fields here (task=statistics)*/ } From ce2edf7c3910cb3222d51c0a7457b7a71703a5b1 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Fri, 15 May 2026 15:09:48 +0300 Subject: [PATCH 116/143] wifi: iwlwifi: mld: disallow puncturing in US/CA for WH FM continues to follow the BIOS/MCC policy, while WH sets DISALLOW_PUNCTURING for US/CA and clears it for other MCC values. Update the MCC handling accordingly. Signed-off-by: Pagadala Yesu Anjaneyulu Reviewed-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260515150751.df1f1fdd141f.I900c9e2e3dd722619db12ba10d0879a56a2a55f2@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/mcc.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c b/drivers/net/wireless/intel/iwlwifi/mld/mcc.c index 16bb1b4904f9..8502129abe49 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mcc.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 @@ -129,7 +129,7 @@ iwl_mld_get_regdomain(struct iwl_mld *mld, mld->mcc_src = resp->source_id; - /* FM is the earliest supported and later always do puncturing */ + /* FM follows BIOS/MCC policy, WH disallows puncturing only in US/CA. */ if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == IWL_CFG_RF_TYPE_FM) { if (!iwl_puncturing_is_allowed_in_bios(mld->bios_enable_puncturing, le16_to_cpu(resp->mcc))) @@ -137,6 +137,15 @@ iwl_mld_get_regdomain(struct iwl_mld *mld, else __clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING, mld->hw->flags); + } else if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == + IWL_CFG_RF_TYPE_WH) { + u16 mcc = le16_to_cpu(resp->mcc); + + if (mcc == IWL_MCC_US || mcc == IWL_MCC_CANADA) + ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING); + else + __clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING, + mld->hw->flags); } out: From 59a5876e474ac62ccd199faba5baf92709b1f431 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Sun, 17 May 2026 10:59:45 +0300 Subject: [PATCH 117/143] wifi: iwlwifi: mld: don't flush async_handlers_wk when canceling notifications iwl_mld_cancel_async_notifications does 2 things: it purges the list of notifications and flushes the async_handlers_wk. We call iwl_mld_cancel_async_notifications after we stopped or suspended the fw. So in that stage we don't expect any new notification coming, and if erroneously there are new notifications coming, the work will be queued again anyway, so the flush is pretty much pointless. iwl_mld_cancel_async_notifications will need to be called in a context w/o the wiphy lock held, and the only reason why this function requires the lock being held is for flushing the work. Remove the flush. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260517100550.70dddca96191.I06d3c6433ec22f81f2fb3fb2ee43881e662c5212@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/notif.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index d7383022a0ed..7574689e4088 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -694,10 +694,6 @@ void iwl_mld_cancel_async_notifications(struct iwl_mld *mld) { struct iwl_async_handler_entry *entry, *tmp; - lockdep_assert_wiphy(mld->wiphy); - - wiphy_work_cancel(mld->wiphy, &mld->async_handlers_wk); - spin_lock_bh(&mld->async_handlers_lock); list_for_each_entry_safe(entry, tmp, &mld->async_handlers_list, list) { iwl_mld_log_async_handler_op(mld, "Purged", &entry->rxb); From a40ad60a47f7c904b75a9ff83b39edebf3961c85 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Sun, 17 May 2026 10:59:46 +0300 Subject: [PATCH 118/143] wifi: iwlwifi: mld: purge async notifications upon nic error This fixes a kernel panic in reconfig failure: 1. we have a BSS connection 2. we have a NAN connection 3. FW error occurs 4. reconfig restores the BSS connection 5. however, restoring the NAN connection fails due to a FW error. 6. erroneously, ieee80211_handle_reconfig_failure is called and marks all interfaces as not-in-driver (will be fixed in a different patch). 7. mac80211 frees the links of the BSS connection but doesn't tell the driver about that, as it thinks that this vif is not in the driver. 8. in ieee80211_stop_device, *ALL* wiphy works are getting flushed (erroneously?) 9. Therefore, async_handlers_wk is being executed, processing the statistics notification that was received after we restored the BSS connection. 10. the notification handler dereferences fw_id_to_bss_conf[id], which is now a dangling pointer, as mac80211 already freed this link in (7). 11. On the first access to one of the links fields, we panic. While this can and should be fixed by removing the call to ieee80211_handle_reconfig_failure in (6), it is also not a good idea to carry and maybe handle notifications from a dead FW. We do purge the notifications when we stop the FW, but in reconfig failure we stop the FW too late, after the notifications are processed. In addition, async_handlers_wk can always be scheduled before the reconfig work. Purge the notifications immediately when transport notifies about a nic error. Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260517100550.4414228bf1d1.I1926a2b2e7827eaac22882699880ec04a3cb95f0@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/mld.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 0ef7c24831d8..78c78cf891cd 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -676,6 +676,15 @@ iwl_mld_nic_error(struct iwl_op_mode *op_mode, if (type != IWL_ERR_TYPE_RESET_HS_TIMEOUT && mld->fw_status.running) mld->fw_status.in_hw_restart = true; + + /* FW is dead. We don't want to process its notifications. + * Right, we cancel them also in iwl_mld_stop_fw, but + * iwl_mld_async_handlers_wk might be executed before + * ieee80211_restart_work. + * In addition, in case of an error during recovery, + * iwl_mld_stop_fw might be too late. + */ + iwl_mld_cancel_async_notifications(mld); } static void iwl_mld_dump_error(struct iwl_op_mode *op_mode, From d157c79643d98f6d42bc19395dba19b5fc8581b6 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Sun, 17 May 2026 10:59:47 +0300 Subject: [PATCH 119/143] wifi: iwlwifi: bump maximum core version for BZ/SC/DR to 105 Start supporting Core 105 FW on these devices. Link: https://patch.msgid.link/20260517100550.8674feeafcad.I3d3ae3a7acb976a947cd7e65a8d7fb8199d2e1ab@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/cfg/bz.c | 2 +- drivers/net/wireless/intel/iwlwifi/cfg/dr.c | 2 +- drivers/net/wireless/intel/iwlwifi/cfg/sc.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index 51d7c599e998..ecb4f81a99f5 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -10,7 +10,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_BZ_UCODE_CORE_MAX 104 +#define IWL_BZ_UCODE_CORE_MAX 105 /* Lowest firmware core release supported */ #define IWL_BZ_UCODE_CORE_MIN 101 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c index 76fbc120a092..e8968b3051d3 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c @@ -9,7 +9,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_DR_UCODE_CORE_MAX 104 +#define IWL_DR_UCODE_CORE_MAX 105 /* Lowest firmware core release supported */ #define IWL_DR_UCODE_CORE_MIN 101 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index db7858b2cb74..6aaa49aeec99 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -10,7 +10,7 @@ #include "fw/api/txq.h" /* Highest firmware core release supported */ -#define IWL_SC_UCODE_CORE_MAX 104 +#define IWL_SC_UCODE_CORE_MAX 105 /* Lowest firmware core release supported */ #define IWL_SC_UCODE_CORE_MIN 101 From 2a5094c088121ad6b7854e8e719ca75a61075ad7 Mon Sep 17 00:00:00 2001 From: Avinash Bhatt Date: Sun, 17 May 2026 10:59:48 +0300 Subject: [PATCH 120/143] wifi: iwlwifi: mld: skip MLO scan trigger when AP has no QBSS Load IE When FW PHY statistics report high channel load (MCLM), the driver triggers an internal MLO scan to find a better link. However, link grade computation uses the QBSS Load IE from AP beacons, not MCLM data. If the AP does not broadcast a QBSS Load IE, the scan produces no benefit as the grade falls back to static band defaults regardless, and the same bad link will be selected anyway as the active link. Skip the MLO scan trigger when the AP does not advertise a QBSS Load IE. Signed-off-by: Avinash Bhatt Link: https://patch.msgid.link/20260517100550.621538e20244.I7fdccb759508f32991cc06774cc7621725a58bd3@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.c | 5 ++--- drivers/net/wireless/intel/iwlwifi/mld/link.h | 3 +++ drivers/net/wireless/intel/iwlwifi/mld/mlo.c | 7 +++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index e5e9b7089c14..549f1c1ea22f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -859,9 +859,8 @@ iwl_mld_apply_puncturing_penalty(const struct ieee80211_bss_conf *link_conf, *grade = *grade * (n_subchannels - n_punctured) / n_subchannels; } -static int -iwl_mld_get_chan_load_from_element(struct iwl_mld *mld, - struct ieee80211_bss_conf *link_conf) +int iwl_mld_get_chan_load_from_element(struct iwl_mld *mld, + struct ieee80211_bss_conf *link_conf) { const struct cfg80211_bss_ies *ies; const struct element *bss_load_elem = NULL; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.h b/drivers/net/wireless/intel/iwlwifi/mld/link.h index 0b3974d86531..f1997e280058 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.h @@ -145,6 +145,9 @@ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, unsigned int iwl_mld_get_chan_load(struct iwl_mld *mld, struct ieee80211_bss_conf *link_conf); +int iwl_mld_get_chan_load_from_element(struct iwl_mld *mld, + struct ieee80211_bss_conf *link_conf); + int iwl_mld_get_chan_load_by_others(struct iwl_mld *mld, struct ieee80211_bss_conf *link_conf, bool expect_active_link); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c index 2a3b2c883fc4..a2f8a6957535 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c @@ -1100,6 +1100,13 @@ static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac, if (rcu_access_pointer(link_conf->chanctx_conf) != chanctx) continue; + /* No QBSS IE - links will be selected based on default channel + * load values, so the same link will be selected again. + * No point in scan. + */ + if (iwl_mld_get_chan_load_from_element(mld, link_conf) < 0) + continue; + if (iwl_mld_chan_load_requires_scan(mld, link_conf, new_chan_load)) { From 4b6d725ff01cb92becda6e805143e331fdf1d73e Mon Sep 17 00:00:00 2001 From: Avinash Bhatt Date: Sun, 17 May 2026 10:59:49 +0300 Subject: [PATCH 121/143] wifi: iwlwifi: mld: keep healthy link on EMLSR missed beacon exit When EMLSR exits due to beacon loss on the current link, the driver should keep the link that is still receiving beacons. The previous code always called get_primary_link(), keeping the primary link regardless of which link is actually losing beacons. If the primary link is the one losing beacons, the driver exits EMLSR onto the degraded link and the connection is lost eventually. When both links lose beacons, keep the primary link. When only the current link loses beacons -- whether due to signal loss or a BSS parameter change -- keep the other link. Signed-off-by: Avinash Bhatt Link: https://patch.msgid.link/20260517100550.b2d700f7775e.I8e9189ce6cf4388878beab14e56341becd5f427c@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index 549f1c1ea22f..1bdfba6279e2 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -663,15 +663,20 @@ void iwl_mld_handle_missed_beacon_notif(struct iwl_mld *mld, * OR more than IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED * on current link and the link's bss_param_ch_count has changed on * the other link's beacon. + * + * When both links lose beacons, keep the primary (symmetric failure). + * When only the current link is sick, keep the other link. */ - if ((missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS && - scnd_lnk_bcn_lost >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS) || - missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH || - (bss_param_ch_cnt_link_id != link_id && - missed_bcon >= - IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED)) { + if (missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS && + scnd_lnk_bcn_lost >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS) { iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_MISSED_BEACON, iwl_mld_get_primary_link(vif)); + } else if (missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH || + (bss_param_ch_cnt_link_id != link_id && + missed_bcon >= + IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED)) { + iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_MISSED_BEACON, + iwl_mld_get_other_link(vif, link_id)); } } EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_handle_missed_beacon_notif); From 6021651b2bf96cd61e45af91c4138479d402bb5f Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Sun, 17 May 2026 10:59:50 +0300 Subject: [PATCH 122/143] wifi: iwlwifi: move pcie content to pcie internal transport The iwl_txq, iwl_pcie_first_tb_buf and iwl_pcie_txq_entry don't need to be exported to the op_mode in iwl-trans.h. Declare those in the transport's internal header file to avoid pollution. iwl_trans_pcie_send_hcmd can also be moved to the internal header file. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260517100550.208921548b4b.I76b1ac8499275e6d231880861e3843278f278c34@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/iwl-trans.h | 105 +----------------- .../intel/iwlwifi/pcie/gen1_2/internal.h | 105 +++++++++++++++++- 2 files changed, 105 insertions(+), 105 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h index 61e4f4776dcb..c661807c6e6b 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2005-2014, 2018-2025 Intel Corporation + * Copyright (C) 2005-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -700,106 +700,6 @@ struct iwl_cmd_meta { u32 tbs; }; -/* - * The FH will write back to the first TB only, so we need to copy some data - * into the buffer regardless of whether it should be mapped or not. - * This indicates how big the first TB must be to include the scratch buffer - * and the assigned PN. - * Since PN location is 8 bytes at offset 12, it's 20 now. - * If we make it bigger then allocations will be bigger and copy slower, so - * that's probably not useful. - */ -#define IWL_FIRST_TB_SIZE 20 -#define IWL_FIRST_TB_SIZE_ALIGN ALIGN(IWL_FIRST_TB_SIZE, 64) - -struct iwl_pcie_txq_entry { - void *cmd; - struct sk_buff *skb; - /* buffer to free after command completes */ - const void *free_buf; - struct iwl_cmd_meta meta; -}; - -struct iwl_pcie_first_tb_buf { - u8 buf[IWL_FIRST_TB_SIZE_ALIGN]; -}; - -/** - * struct iwl_txq - Tx Queue for DMA - * @tfds: transmit frame descriptors (DMA memory) - * @first_tb_bufs: start of command headers, including scratch buffers, for - * the writeback -- this is DMA memory and an array holding one buffer - * for each command on the queue - * @first_tb_dma: DMA address for the first_tb_bufs start - * @entries: transmit entries (driver state) - * @lock: queue lock - * @reclaim_lock: reclaim lock - * @stuck_timer: timer that fires if queue gets stuck - * @trans: pointer back to transport (for timer) - * @need_update: indicates need to update read/write index - * @ampdu: true if this queue is an ampdu queue for an specific RA/TID - * @wd_timeout: queue watchdog timeout (jiffies) - per queue - * @frozen: tx stuck queue timer is frozen - * @frozen_expiry_remainder: remember how long until the timer fires - * @block: queue is blocked - * @bc_tbl: byte count table of the queue (relevant only for gen2 transport) - * @write_ptr: 1-st empty entry (index) host_w - * @read_ptr: last used entry (index) host_r - * @dma_addr: physical addr for BD's - * @n_window: safe queue window - * @id: queue id - * @low_mark: low watermark, resume queue if free space more than this - * @high_mark: high watermark, stop queue if free space less than this - * @overflow_q: overflow queue for handling frames that didn't fit on HW queue - * @overflow_tx: need to transmit from overflow - * - * A Tx queue consists of circular buffer of BDs (a.k.a. TFDs, transmit frame - * descriptors) and required locking structures. - * - * Note the difference between TFD_QUEUE_SIZE_MAX and n_window: the hardware - * always assumes 256 descriptors, so TFD_QUEUE_SIZE_MAX is always 256 (unless - * there might be HW changes in the future). For the normal TX - * queues, n_window, which is the size of the software queue data - * is also 256; however, for the command queue, n_window is only - * 32 since we don't need so many commands pending. Since the HW - * still uses 256 BDs for DMA though, TFD_QUEUE_SIZE_MAX stays 256. - * This means that we end up with the following: - * HW entries: | 0 | ... | N * 32 | ... | N * 32 + 31 | ... | 255 | - * SW entries: | 0 | ... | 31 | - * where N is a number between 0 and 7. This means that the SW - * data is a window overlayed over the HW queue. - */ -struct iwl_txq { - void *tfds; - struct iwl_pcie_first_tb_buf *first_tb_bufs; - dma_addr_t first_tb_dma; - struct iwl_pcie_txq_entry *entries; - /* lock for syncing changes on the queue */ - spinlock_t lock; - /* lock to prevent concurrent reclaim */ - spinlock_t reclaim_lock; - unsigned long frozen_expiry_remainder; - struct timer_list stuck_timer; - struct iwl_trans *trans; - bool need_update; - bool frozen; - bool ampdu; - int block; - unsigned long wd_timeout; - struct sk_buff_head overflow_q; - struct iwl_dma_ptr bc_tbl; - - int write_ptr; - int read_ptr; - dma_addr_t dma_addr; - int n_window; - u32 id; - int low_mark; - int high_mark; - - bool overflow_tx; -}; - /** * struct iwl_trans_info - transport info for outside use * @name: the device name @@ -1236,9 +1136,6 @@ enum iwl_reset_mode { void iwl_trans_pcie_reset(struct iwl_trans *trans, enum iwl_reset_mode mode); void iwl_trans_pcie_fw_reset_handshake(struct iwl_trans *trans); -int iwl_trans_pcie_send_hcmd(struct iwl_trans *trans, - struct iwl_host_cmd *cmd); - /* Internal helper */ static inline void iwl_trans_set_info(struct iwl_trans *trans, struct iwl_trans_info *info) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h index 7b7b35e442f9..24f8714eae9f 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2003-2015, 2018-2025 Intel Corporation + * Copyright (C) 2003-2015, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -287,6 +287,106 @@ enum iwl_pcie_imr_status { IMR_D2S_ERROR, }; +/* + * The FH will write back to the first TB only, so we need to copy some data + * into the buffer regardless of whether it should be mapped or not. + * This indicates how big the first TB must be to include the scratch buffer + * and the assigned PN. + * Since PN location is 8 bytes at offset 12, it's 20 now. + * If we make it bigger then allocations will be bigger and copy slower, so + * that's probably not useful. + */ +#define IWL_FIRST_TB_SIZE 20 +#define IWL_FIRST_TB_SIZE_ALIGN ALIGN(IWL_FIRST_TB_SIZE, 64) + +struct iwl_pcie_txq_entry { + void *cmd; + struct sk_buff *skb; + /* buffer to free after command completes */ + const void *free_buf; + struct iwl_cmd_meta meta; +}; + +struct iwl_pcie_first_tb_buf { + u8 buf[IWL_FIRST_TB_SIZE_ALIGN]; +}; + +/** + * struct iwl_txq - Tx Queue for DMA + * @tfds: transmit frame descriptors (DMA memory) + * @first_tb_bufs: start of command headers, including scratch buffers, for + * the writeback -- this is DMA memory and an array holding one buffer + * for each command on the queue + * @first_tb_dma: DMA address for the first_tb_bufs start + * @entries: transmit entries (driver state) + * @lock: queue lock + * @reclaim_lock: reclaim lock + * @stuck_timer: timer that fires if queue gets stuck + * @trans: pointer back to transport (for timer) + * @need_update: indicates need to update read/write index + * @ampdu: true if this queue is an ampdu queue for a specific RA/TID + * @wd_timeout: queue watchdog timeout (jiffies) - per queue + * @frozen: tx stuck queue timer is frozen + * @frozen_expiry_remainder: remember how long until the timer fires + * @block: queue is blocked + * @bc_tbl: byte count table of the queue (relevant only for gen2 transport) + * @write_ptr: 1-st empty entry (index) host_w + * @read_ptr: last used entry (index) host_r + * @dma_addr: physical addr for BDs + * @n_window: safe queue window + * @id: queue id + * @low_mark: low watermark, resume queue if free space more than this + * @high_mark: high watermark, stop queue if free space less than this + * @overflow_q: overflow queue for handling frames that didn't fit on HW queue + * @overflow_tx: need to transmit from overflow + * + * A Tx queue consists of circular buffer of BDs (a.k.a. TFDs, transmit frame + * descriptors) and required locking structures. + * + * Note the difference between TFD_QUEUE_SIZE_MAX and n_window: the hardware + * always assumes 256 descriptors, so TFD_QUEUE_SIZE_MAX is always 256 (unless + * there might be HW changes in the future). For the normal TX + * queues, n_window, which is the size of the software queue data + * is also 256; however, for the command queue, n_window is only + * 32 since we don't need so many commands pending. Since the HW + * still uses 256 BDs for DMA though, TFD_QUEUE_SIZE_MAX stays 256. + * This means that we end up with the following: + * HW entries: | 0 | ... | N * 32 | ... | N * 32 + 31 | ... | 255 | + * SW entries: | 0 | ... | 31 | + * where N is a number between 0 and 7. This means that the SW + * data is a window overlaid over the HW queue. + */ +struct iwl_txq { + void *tfds; + struct iwl_pcie_first_tb_buf *first_tb_bufs; + dma_addr_t first_tb_dma; + struct iwl_pcie_txq_entry *entries; + /* lock for syncing changes on the queue */ + spinlock_t lock; + /* lock to prevent concurrent reclaim */ + spinlock_t reclaim_lock; + unsigned long frozen_expiry_remainder; + struct timer_list stuck_timer; + struct iwl_trans *trans; + bool need_update; + bool frozen; + bool ampdu; + int block; + unsigned long wd_timeout; + struct sk_buff_head overflow_q; + struct iwl_dma_ptr bc_tbl; + + int write_ptr; + int read_ptr; + dma_addr_t dma_addr; + int n_window; + u32 id; + int low_mark; + int high_mark; + + bool overflow_tx; +}; + /** * struct iwl_pcie_txqs - TX queues data * @@ -1153,6 +1253,9 @@ int iwl_trans_pcie_copy_imr(struct iwl_trans *trans, int iwl_trans_pcie_rxq_dma_data(struct iwl_trans *trans, int queue, struct iwl_trans_rxq_dma_data *data); +int iwl_trans_pcie_send_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd); + static inline bool iwl_pcie_gen1_is_pm_supported(struct iwl_trans *trans) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); From e4fdfbbb1f1d85811e5eac60115e4afacf2f6c19 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Sun, 17 May 2026 10:59:51 +0300 Subject: [PATCH 123/143] wifi: iwlwifi: move iwl_trans_activate_nic to iwl-trans.c This function reaches the transport eventually so move it to iwl-trans.c. Now we can remove the include to the pcie transport's internal header from iwl-io.c Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260517100550.0d433fb04d51.I50c48e3f4abe23236d3735236dac250588780f6a@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/iwl-io.c | 7 ------- drivers/net/wireless/intel/iwlwifi/iwl-io.h | 2 -- drivers/net/wireless/intel/iwlwifi/iwl-trans.c | 9 ++++++++- drivers/net/wireless/intel/iwlwifi/iwl-trans.h | 2 ++ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-io.c b/drivers/net/wireless/intel/iwlwifi/iwl-io.c index c4ccfffdf6af..bb746112ddad 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-io.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-io.c @@ -12,7 +12,6 @@ #include "iwl-debug.h" #include "iwl-prph.h" #include "iwl-fh.h" -#include "pcie/gen1_2/internal.h" void iwl_write8(struct iwl_trans *trans, u32 ofs, u8 val) { @@ -412,12 +411,6 @@ int iwl_dump_fh(struct iwl_trans *trans, char **buf) return 0; } -int iwl_trans_activate_nic(struct iwl_trans *trans) -{ - return iwl_pcie_gen1_2_activate_nic(trans); -} -IWL_EXPORT_SYMBOL(iwl_trans_activate_nic); - void iwl_trans_sync_nmi_with_addr(struct iwl_trans *trans, u32 inta_addr, u32 sw_err_bit) { diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-io.h b/drivers/net/wireless/intel/iwlwifi/iwl-io.h index d920a32fc173..6dce2e5267a6 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-io.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-io.h @@ -59,8 +59,6 @@ void iwl_set_bits_mask_prph(struct iwl_trans *trans, u32 ofs, void iwl_clear_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask); void iwl_force_nmi(struct iwl_trans *trans); -int iwl_trans_activate_nic(struct iwl_trans *trans); - /* Error handling */ int iwl_dump_fh(struct iwl_trans *trans, char **buf); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c index db1db0a42928..5b44e15fe64d 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c @@ -2,7 +2,7 @@ /* * Copyright (C) 2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH - * Copyright (C) 2019-2021, 2023-2025 Intel Corporation + * Copyright (C) 2019-2021, 2023-2026 Intel Corporation */ #include #include @@ -820,3 +820,10 @@ bool iwl_trans_is_ltr_enabled(struct iwl_trans *trans) return iwl_pcie_gen1_2_is_ltr_enabled(trans); } IWL_EXPORT_SYMBOL(iwl_trans_is_ltr_enabled); + +int iwl_trans_activate_nic(struct iwl_trans *trans) +{ + return iwl_pcie_gen1_2_activate_nic(trans); +} +IWL_EXPORT_SYMBOL(iwl_trans_activate_nic); + diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h index c661807c6e6b..1ed6bcb7882c 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -1053,6 +1053,8 @@ static inline bool iwl_trans_dbg_ini_valid(struct iwl_trans *trans) void iwl_trans_interrupts(struct iwl_trans *trans, bool enable); +int iwl_trans_activate_nic(struct iwl_trans *trans); + static inline void iwl_trans_finish_sw_reset(struct iwl_trans *trans) { clear_bit(STATUS_IN_SW_RESET, &trans->status); From 8f788274adde93f6f5f39d6aa910defef82b9aee Mon Sep 17 00:00:00 2001 From: Shahar Tzarfati Date: Sun, 17 May 2026 10:59:52 +0300 Subject: [PATCH 124/143] wifi: iwlwifi: Add names for Killer BE1735x and BE1730x The devices were supported but had no name in the driver. Add the correct names for these devices. Signed-off-by: Shahar Tzarfati Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260517100550.359f865f0920.Ie73648dd75f9c7d9e9a707311bd4d724d83b8763@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c | 2 ++ drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c | 2 ++ drivers/net/wireless/intel/iwlwifi/iwl-config.h | 2 ++ drivers/net/wireless/intel/iwlwifi/pcie/drv.c | 4 ++++ 4 files changed, 10 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c index f482536e6b5e..294cf25ae2a6 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c @@ -47,6 +47,8 @@ const char iwl_killer_be1790s_name[] = "Killer(R) Wi-Fi 7 BE1790s 320MHz Wireless Network Adapter (BE401D2W)"; const char iwl_killer_be1790i_name[] = "Killer(R) Wi-Fi 7 BE1790i 320MHz Wireless Network Adapter (BE401NGW)"; +const char iwl_killer_be1730x_name[] = + "Killer(TM) Wi-Fi 7 BE1730x 160MHz Wireless Network Adapter (BE202)"; const char iwl_be201_name[] = "Intel(R) Wi-Fi 7 BE201 320MHz"; const char iwl_be200_name[] = "Intel(R) Wi-Fi 7 BE200 320MHz"; diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c index fa63a9a01264..c432aa1a0af6 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c @@ -29,6 +29,8 @@ const char iwl_killer_be1775s_name[] = "Killer(R) Wi-Fi 7 BE1775s 320MHz Wireless Network Adapter (BE211D2W)"; const char iwl_killer_be1775i_name[] = "Killer(R) Wi-Fi 7 BE1775i 320MHz Wireless Network Adapter (BE211NGW)"; +const char iwl_killer_be1735x_name[] = + "Killer(TM) Wi-Fi 7 BE1735x 160MHz Wireless Network Adapter (BE213)"; const char iwl_be211_name[] = "Intel(R) Wi-Fi 7 BE211 320MHz"; const char iwl_be213_name[] = "Intel(R) Wi-Fi 7 BE213 160MHz"; diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-config.h b/drivers/net/wireless/intel/iwlwifi/iwl-config.h index da6d3686e7dd..30d5ec31b9c3 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-config.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-config.h @@ -676,6 +676,7 @@ extern const char iwl_killer_be1750w_name[]; extern const char iwl_killer_be1750x_name[]; extern const char iwl_killer_be1790s_name[]; extern const char iwl_killer_be1790i_name[]; +extern const char iwl_killer_be1730x_name[]; extern const char iwl_be201_name[]; extern const char iwl_be200_name[]; extern const char iwl_be202_name[]; @@ -683,6 +684,7 @@ extern const char iwl_be401_name[]; extern const char iwl_be213_name[]; extern const char iwl_killer_be1775s_name[]; extern const char iwl_killer_be1775i_name[]; +extern const char iwl_killer_be1735x_name[]; extern const char iwl_be211_name[]; extern const char iwl_killer_bn1850w2_name[]; extern const char iwl_killer_bn1850i_name[]; diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c index b0c59181907e..608100bc6b11 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -1054,6 +1054,8 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct iwl_dev_info iwl_dev_info_table[] = { IWL_DEV_INFO(iwl_rf_fm, iwl_killer_be1750i_name, SUBDEV(0x1772)), IWL_DEV_INFO(iwl_rf_fm, iwl_killer_be1790s_name, SUBDEV(0x1791)), IWL_DEV_INFO(iwl_rf_fm, iwl_killer_be1790i_name, SUBDEV(0x1792)), + IWL_DEV_INFO(iwl_rf_fm_160mhz, iwl_killer_be1730x_name, SUBDEV(0x1730)), + IWL_DEV_INFO(iwl_rf_fm_160mhz, iwl_killer_be1730x_name, SUBDEV(0x1731)), /* Killer discrete */ IWL_DEV_INFO(iwl_rf_fm, iwl_killer_be1750w_name, @@ -1079,6 +1081,8 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct iwl_dev_info iwl_dev_info_table[] = { /* Killer */ IWL_DEV_INFO(iwl_rf_wh, iwl_killer_be1775s_name, SUBDEV(0x1776)), IWL_DEV_INFO(iwl_rf_wh, iwl_killer_be1775i_name, SUBDEV(0x1775)), + IWL_DEV_INFO(iwl_rf_wh_160mhz, iwl_killer_be1735x_name, SUBDEV(0x1735)), + IWL_DEV_INFO(iwl_rf_wh_160mhz, iwl_killer_be1735x_name, SUBDEV(0x1736)), IWL_DEV_INFO(iwl_rf_pe, iwl_killer_bn1850w2_name, SUBDEV(0x1851)), IWL_DEV_INFO(iwl_rf_pe, iwl_killer_bn1850i_name, SUBDEV(0x1852)), From 33f6b403ec08149a1555fdc1b30b998aac214853 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 17 May 2026 10:59:53 +0300 Subject: [PATCH 125/143] wifi: iwlwifi: clean up location format/BW encoding This is all fairly manual, with the shifts etc., and the documentation has to call out the specific bits (which also isn't usable for our tracing data). Add an enum that directly declares the bit masks for it and then use the more modern u8_encode_bits(). Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260517100550.bc09f4f6fd29.I315f15856eb36f0490b8f5008537d3e0ffc215f2@changeid Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/location.h | 100 +++++++++--------- .../intel/iwlwifi/mld/ftm-initiator.c | 30 ++++-- .../intel/iwlwifi/mvm/ftm-initiator.c | 30 ++++-- .../intel/iwlwifi/mvm/ftm-responder.c | 32 ++++-- 4 files changed, 111 insertions(+), 81 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/location.h b/drivers/net/wireless/intel/iwlwifi/fw/api/location.h index d3f774ffacde..421ea94ace01 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/location.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/location.h @@ -88,6 +88,46 @@ enum iwl_location_subcmd_ids { TOF_RANGE_RESPONSE_NOTIF = 0xFF, }; +/** + * enum iwl_location_frame_format - location frame formats + * @IWL_LOCATION_FRAME_FORMAT_LEGACY: legacy + * @IWL_LOCATION_FRAME_FORMAT_HT: HT + * @IWL_LOCATION_FRAME_FORMAT_VHT: VHT + * @IWL_LOCATION_FRAME_FORMAT_HE: HE + */ +enum iwl_location_frame_format { + IWL_LOCATION_FRAME_FORMAT_LEGACY, + IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FRAME_FORMAT_VHT, + IWL_LOCATION_FRAME_FORMAT_HE, +}; + +/** + * enum iwl_location_bw - location bandwidth selection + * @IWL_LOCATION_BW_20MHZ: 20 MHz + * @IWL_LOCATION_BW_40MHZ: 40 MHz + * @IWL_LOCATION_BW_80MHZ: 80 MHz + * @IWL_LOCATION_BW_160MHZ: 160 MHz + * @IWL_LOCATION_BW_320MHZ: 320 MHz + */ +enum iwl_location_bw { + IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_BW_40MHZ, + IWL_LOCATION_BW_80MHZ, + IWL_LOCATION_BW_160MHZ, + IWL_LOCATION_BW_320MHZ, +}; + +/** + * enum iwl_location_format_bw - format/BW encoding + * @IWL_LOCATION_FMT_BW_FORMAT: &enum iwl_location_frame_format + * @IWL_LOCATION_FMT_BW_BANDWIDTH: &enum iwl_location_bw + */ +enum iwl_location_format_bw { + IWL_LOCATION_FMT_BW_FORMAT = 0x0f, + IWL_LOCATION_FMT_BW_BANDWIDTH = 0xf0, +}; + /** * struct iwl_tof_config_cmd - ToF configuration * @tof_disabled: indicates if ToF is disabled (or not) @@ -264,8 +304,7 @@ struct iwl_tof_responder_config_cmd_v6 { * struct iwl_tof_responder_config_cmd_v7 - ToF AP mode (for debug) * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field * @responder_cfg_flags: &iwl_tof_responder_cfg_flags - * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. - * bits 4 - 7: &enum iwl_location_bw. + * @format_bw: &enum iwl_location_format_bw * @rate: current AP rate * @channel_num: current AP Channel * @ctrl_ch_position: coding of the control channel position relative to @@ -303,8 +342,7 @@ struct iwl_tof_responder_config_cmd_v7 { * struct iwl_tof_responder_config_cmd_v8 - ToF AP mode (for debug) * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field * @responder_cfg_flags: &iwl_tof_responder_cfg_flags - * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. - * bits 4 - 7: &enum iwl_location_bw. + * @format_bw: &enum iwl_location_format_bw * @rate: current AP rate * @channel_num: current AP Channel * @ctrl_ch_position: coding of the control channel position relative to @@ -349,8 +387,7 @@ struct iwl_tof_responder_config_cmd_v8 { * struct iwl_tof_responder_config_cmd_v9 - ToF AP mode (for debug) * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field * @responder_cfg_flags: &iwl_tof_responder_cfg_flags - * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. - * bits 4 - 7: &enum iwl_location_bw. + * @format_bw: &enum iwl_location_format_bw * @bss_color: current AP bss_color * @channel_num: current AP Channel * @ctrl_ch_position: coding of the control channel position relative to @@ -401,8 +438,7 @@ struct iwl_tof_responder_config_cmd_v9 { * struct iwl_tof_responder_config_cmd - ToF AP mode * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field * @responder_cfg_flags: &iwl_tof_responder_cfg_flags - * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. - * bits 4 - 7: &enum iwl_location_bw. + * @format_bw: &enum iwl_location_format_bw * @bss_color: current AP bss_color * @channel_num: current AP Channel * @ctrl_ch_position: coding of the control channel position relative to @@ -677,44 +713,13 @@ struct iwl_tof_range_req_ap_entry_v3 { __le32 tsf_delta; } __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_3 */ -/** - * enum iwl_location_frame_format - location frame formats - * @IWL_LOCATION_FRAME_FORMAT_LEGACY: legacy - * @IWL_LOCATION_FRAME_FORMAT_HT: HT - * @IWL_LOCATION_FRAME_FORMAT_VHT: VHT - * @IWL_LOCATION_FRAME_FORMAT_HE: HE - */ -enum iwl_location_frame_format { - IWL_LOCATION_FRAME_FORMAT_LEGACY, - IWL_LOCATION_FRAME_FORMAT_HT, - IWL_LOCATION_FRAME_FORMAT_VHT, - IWL_LOCATION_FRAME_FORMAT_HE, -}; - -/** - * enum iwl_location_bw - location bandwidth selection - * @IWL_LOCATION_BW_20MHZ: 20MHz - * @IWL_LOCATION_BW_40MHZ: 40MHz - * @IWL_LOCATION_BW_80MHZ: 80MHz - * @IWL_LOCATION_BW_160MHZ: 160MHz - */ -enum iwl_location_bw { - IWL_LOCATION_BW_20MHZ, - IWL_LOCATION_BW_40MHZ, - IWL_LOCATION_BW_80MHZ, - IWL_LOCATION_BW_160MHZ, -}; - #define TK_11AZ_LEN 32 -#define LOCATION_BW_POS 4 - /** * struct iwl_tof_range_req_ap_entry_v4 - AP configuration parameters * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. * @channel_num: AP Channel number - * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. - * bits 4 - 7: &enum iwl_location_bw. + * @format_bw: &enum iwl_location_format_bw * @ctrl_ch_position: Coding of the control channel position relative to the * center frequency, see iwl_mvm_get_ctrl_pos(). * @ftmr_max_retries: Max number of retries to send the FTMR in case of no @@ -764,8 +769,7 @@ enum iwl_location_cipher { * struct iwl_tof_range_req_ap_entry_v6 - AP configuration parameters * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. * @channel_num: AP Channel number - * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. - * bits 4 - 7: &enum iwl_location_bw. + * @format_bw: &enum iwl_location_format_bw * @ctrl_ch_position: Coding of the control channel position relative to the * center frequency, see iwl_mvm_get_ctrl_pos(). * @ftmr_max_retries: Max number of retries to send the FTMR in case of no @@ -811,8 +815,7 @@ struct iwl_tof_range_req_ap_entry_v6 { * struct iwl_tof_range_req_ap_entry_v7 - AP configuration parameters * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. * @channel_num: AP Channel number - * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. - * bits 4 - 7: &enum iwl_location_bw. + * @format_bw: &enum iwl_location_format_bw * @ctrl_ch_position: Coding of the control channel position relative to the * center frequency, see iwl_mvm_get_ctrl_pos(). * @ftmr_max_retries: Max number of retries to send the FTMR in case of no @@ -869,8 +872,7 @@ struct iwl_tof_range_req_ap_entry_v7 { * struct iwl_tof_range_req_ap_entry_v8 - AP configuration parameters * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. * @channel_num: AP Channel number - * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. - * bits 4 - 7: &enum iwl_location_bw. + * @format_bw: &enum iwl_location_format_bw * @ctrl_ch_position: Coding of the control channel position relative to the * center frequency, see iwl_mvm_get_ctrl_pos(). * @ftmr_max_retries: Max number of retries to send the FTMR in case of no @@ -940,8 +942,7 @@ struct iwl_tof_range_req_ap_entry_v8 { * struct iwl_tof_range_req_ap_entry_v9 - AP configuration parameters * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. * @channel_num: AP Channel number - * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. - * bits 4 - 7: &enum iwl_location_bw. + * @format_bw: &enum iwl_location_format_bw * @ctrl_ch_position: Coding of the control channel position relative to the * center frequency, see iwl_mvm_get_ctrl_pos(). * @ftmr_max_retries: Max number of retries to send the FTMR in case of no @@ -1025,8 +1026,7 @@ struct iwl_tof_range_req_ap_entry_v9 { * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. * @band: 0 for 5.2 GHz, 1 for 2.4 GHz, 2 for 6GHz * @channel_num: AP Channel number - * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. - * bits 4 - 7: &enum iwl_location_bw. + * @format_bw: &enum iwl_location_format_bw * @ctrl_ch_position: Coding of the control channel position relative to the * center frequency, see iwl_mvm_get_ctrl_pos(). * @bssid: AP's BSSID diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c index 3464b3268712..81df3fdfcbf5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c @@ -71,24 +71,34 @@ iwl_mld_ftm_set_target_chandef(struct iwl_mld *mld, switch (peer->chandef.width) { case NL80211_CHAN_WIDTH_20_NOHT: - target->format_bw = IWL_LOCATION_FRAME_FORMAT_LEGACY; - target->format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + target->format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_LEGACY, + IWL_LOCATION_FMT_BW_FORMAT); + target->format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_20: - target->format_bw = IWL_LOCATION_FRAME_FORMAT_HT; - target->format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + target->format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FMT_BW_FORMAT); + target->format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_40: - target->format_bw = IWL_LOCATION_FRAME_FORMAT_HT; - target->format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS; + target->format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FMT_BW_FORMAT); + target->format_bw |= u8_encode_bits(IWL_LOCATION_BW_40MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_80: - target->format_bw = IWL_LOCATION_FRAME_FORMAT_VHT; - target->format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS; + target->format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_VHT, + IWL_LOCATION_FMT_BW_FORMAT); + target->format_bw |= u8_encode_bits(IWL_LOCATION_BW_80MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_160: - target->format_bw = IWL_LOCATION_FRAME_FORMAT_HE; - target->format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS; + target->format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HE, + IWL_LOCATION_FMT_BW_FORMAT); + target->format_bw |= u8_encode_bits(IWL_LOCATION_BW_160MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; default: IWL_ERR(mld, "Unsupported BW in FTM request (%d)\n", diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c index 1b67836b1fac..3a14ca5e512a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c @@ -261,20 +261,28 @@ iwl_mvm_ftm_target_chandef_v2(struct iwl_mvm *mvm, switch (peer->chandef.width) { case NL80211_CHAN_WIDTH_20_NOHT: - *format_bw = IWL_LOCATION_FRAME_FORMAT_LEGACY; - *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_LEGACY, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_20: - *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; - *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_40: - *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; - *format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_40MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_80: - *format_bw = IWL_LOCATION_FRAME_FORMAT_VHT; - *format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_VHT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_80MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_160: cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, @@ -282,8 +290,10 @@ iwl_mvm_ftm_target_chandef_v2(struct iwl_mvm *mvm, IWL_FW_CMD_VER_UNKNOWN); if (cmd_ver >= 13) { - *format_bw = IWL_LOCATION_FRAME_FORMAT_HE; - *format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HE, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_160MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; } fallthrough; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c index 83f6e508a094..ae7a163c81c9 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2024 Intel Corporation + * Copyright (C) 2018-2024, 2026 Intel Corporation */ #include #include @@ -54,27 +54,37 @@ static int iwl_mvm_ftm_responder_set_bw_v2(struct cfg80211_chan_def *chandef, { switch (chandef->width) { case NL80211_CHAN_WIDTH_20_NOHT: - *format_bw = IWL_LOCATION_FRAME_FORMAT_LEGACY; - *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_LEGACY, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_20: - *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; - *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_40: - *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; - *format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_40MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); break; case NL80211_CHAN_WIDTH_80: - *format_bw = IWL_LOCATION_FRAME_FORMAT_VHT; - *format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_VHT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_80MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); break; case NL80211_CHAN_WIDTH_160: if (cmd_ver >= 9) { - *format_bw = IWL_LOCATION_FRAME_FORMAT_HE; - *format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HE, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_160MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); break; } From c82e208ab8958660134adec0aa2090abdc2e7ea0 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 17 May 2026 10:59:54 +0300 Subject: [PATCH 126/143] wifi: iwlwifi: fw: move struct iwl_fw_ini_dump_entry to dbg.c This is only used/needed in this file, so move it to clarify that it's not part of any external API. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260517100550.72b02319e812.I5cf95f64e3c3c688871bfabbe4fd7393b63a7dc8@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/dbg.c | 14 +++++++++++++- drivers/net/wireless/intel/iwlwifi/fw/error-dump.h | 14 +------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c index 0cffa5493704..4c41f042d6a0 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2005-2014, 2018-2025 Intel Corporation + * Copyright (C) 2005-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH */ @@ -2239,6 +2239,18 @@ struct iwl_dump_ini_mem_ops { void *range, u32 range_len, int idx); }; +/** + * struct iwl_fw_ini_dump_entry - dump entry descriptor + * @list: list of dump entries + * @size: size of the data + * @data: entry data + */ +struct iwl_fw_ini_dump_entry { + struct list_head list; + u32 size; + u8 data[]; +} __packed; + /** * iwl_dump_ini_mem - dump memory region * diff --git a/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h b/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h index 525a82030daa..07f1240df866 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2014, 2018-2025 Intel Corporation + * Copyright (C) 2014, 2018-2026 Intel Corporation * Copyright (C) 2014-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -265,18 +265,6 @@ struct iwl_fw_ini_error_dump_data { __u8 data[]; } __packed; -/** - * struct iwl_fw_ini_dump_entry - dump entry descriptor - * @list: list of dump entries - * @size: size of the data - * @data: entry data - */ -struct iwl_fw_ini_dump_entry { - struct list_head list; - u32 size; - u8 data[]; -} __packed; - /** * struct iwl_fw_ini_dump_file_hdr - header of dump file * @barker: must be %IWL_FW_INI_ERROR_DUMP_BARKER From 7551a3bef6b6d51febb4b586c678e4760627a9d0 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 17 May 2026 10:59:55 +0300 Subject: [PATCH 127/143] wifi: iwlwifi: fw: separate ini dump allocation Separate out the ini dump allocation to happen before the actual dumping in preparation for better device MAC access. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260517100550.01a65fd9e6de.If9b648a5565671801c15be898f2b89afdb878256@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/dbg.c | 182 +++++++++++--------- 1 file changed, 99 insertions(+), 83 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c index 4c41f042d6a0..886c8e6ef48e 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c @@ -2239,33 +2239,19 @@ struct iwl_dump_ini_mem_ops { void *range, u32 range_len, int idx); }; -/** - * struct iwl_fw_ini_dump_entry - dump entry descriptor - * @list: list of dump entries - * @size: size of the data - * @data: entry data - */ struct iwl_fw_ini_dump_entry { + const struct iwl_dump_ini_mem_ops *ops; + struct iwl_dump_ini_region_data reg_data; struct list_head list; + u32 region_dump_policy; u32 size; u8 data[]; } __packed; -/** - * iwl_dump_ini_mem - dump memory region - * - * @fwrt: fw runtime struct - * @list: list to add the dump tlv to - * @reg_data: memory region - * @ops: memory dump operations - * - * Creates a dump tlv and copy a memory region into it. - * - * Returns: the size of the current dump tlv or 0 if failed - */ -static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, - struct iwl_dump_ini_region_data *reg_data, - const struct iwl_dump_ini_mem_ops *ops) +static void iwl_dump_ini_mem_prep(struct iwl_fw_runtime *fwrt, + struct list_head *list, + struct iwl_dump_ini_region_data *reg_data, + const struct iwl_dump_ini_mem_ops *ops) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_dump_entry *entry; @@ -2273,58 +2259,59 @@ static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, struct iwl_fw_ini_error_dump_header *header; u32 type = reg->type; u32 id = le32_get_bits(reg->id, IWL_FW_INI_REGION_ID_MASK); - u32 num_of_ranges, i, size; - u8 *range; - u32 free_size; - u64 header_size; + u32 num_of_ranges, size; u32 dump_policy = IWL_FW_INI_DUMP_VERBOSE; + u32 dp; IWL_DEBUG_FW(fwrt, "WRT: Collecting region: dump type=%d, id=%d, type=%d\n", dump_policy, id, type); if (le32_to_cpu(reg->hdr.version) >= 2) { - u32 dp = le32_get_bits(reg->id, - IWL_FW_INI_REGION_DUMP_POLICY_MASK); + dp = le32_get_bits(reg->id, IWL_FW_INI_REGION_DUMP_POLICY_MASK); if (dump_policy == IWL_FW_INI_DUMP_VERBOSE && !(dp & IWL_FW_INI_DEBUG_DUMP_POLICY_NO_LIMIT)) { IWL_DEBUG_FW(fwrt, "WRT: no dump - type %d and policy mismatch=%d\n", dump_policy, dp); - return 0; + return; } else if (dump_policy == IWL_FW_INI_DUMP_MEDIUM && !(dp & IWL_FW_IWL_DEBUG_DUMP_POLICY_MAX_LIMIT_5MB)) { IWL_DEBUG_FW(fwrt, "WRT: no dump - type %d and policy mismatch=%d\n", dump_policy, dp); - return 0; + return; } else if (dump_policy == IWL_FW_INI_DUMP_BRIEF && !(dp & IWL_FW_INI_DEBUG_DUMP_POLICY_MAX_LIMIT_600KB)) { IWL_DEBUG_FW(fwrt, "WRT: no dump - type %d and policy mismatch=%d\n", dump_policy, dp); - return 0; + return; } + } else { + dp = 0; } if (!ops->get_num_of_ranges || !ops->get_size || !ops->fill_mem_hdr || !ops->fill_range) { IWL_DEBUG_FW(fwrt, "WRT: no ops for collecting data\n"); - return 0; + return; } size = ops->get_size(fwrt, reg_data); if (size < sizeof(*header)) { IWL_DEBUG_FW(fwrt, "WRT: size didn't include space for header\n"); - return 0; + return; } entry = vzalloc(sizeof(*entry) + sizeof(*tlv) + size); if (!entry) - return 0; + return; entry->size = sizeof(*tlv) + size; + entry->reg_data = *reg_data; + entry->region_dump_policy = dp; tlv = (void *)entry->data; tlv->type = reg->type; @@ -2341,7 +2328,29 @@ static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, header->name_len = cpu_to_le32(IWL_FW_INI_MAX_NAME); memcpy(header->name, reg->name, IWL_FW_INI_MAX_NAME); - free_size = size; + entry->ops = ops; + list_add_tail(&entry->list, list); +} + +static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, + struct iwl_fw_ini_dump_entry *entry) +{ + struct iwl_dump_ini_region_data *reg_data = &entry->reg_data; + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + const struct iwl_dump_ini_mem_ops *ops = entry->ops; + struct iwl_fw_ini_error_dump_data *tlv; + struct iwl_fw_ini_error_dump_header *header; + u32 type = reg->type; + u32 id = le32_get_bits(reg->id, IWL_FW_INI_REGION_ID_MASK); + u32 i; + u8 *range; + u32 free_size; + u64 header_size; + + tlv = (void *)entry->data; + header = (void *)tlv->data; + + free_size = entry->size - sizeof(*tlv); range = ops->fill_mem_hdr(fwrt, reg_data, header, free_size); if (!range) { IWL_ERR(fwrt, @@ -2362,7 +2371,7 @@ static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, free_size -= header_size; - for (i = 0; i < num_of_ranges; i++) { + for (i = 0; i < le32_to_cpu(header->num_of_ranges); i++) { int range_size = ops->fill_range(fwrt, reg_data, range, free_size, i); @@ -2384,11 +2393,10 @@ static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, range = range + range_size; } - list_add_tail(&entry->list, list); - return entry->size; out_err: + list_del(&entry->list); vfree(entry); return 0; @@ -2617,22 +2625,19 @@ static bool iwl_dump_due_to_error(enum iwl_fw_ini_time_point tp_id) tp_id == IWL_FW_INI_TIME_POINT_FW_HW_ERROR; } -static u32 -iwl_dump_ini_dump_regions(struct iwl_fw_runtime *fwrt, - struct iwl_fwrt_dump_data *dump_data, - struct list_head *list, - enum iwl_fw_ini_time_point tp_id, - u64 regions_mask, - struct iwl_dump_ini_region_data *imr_reg_data, - enum iwl_dump_ini_region_selector which) +static void +iwl_dump_ini_dump_regions_prep(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + struct list_head *list, + enum iwl_fw_ini_time_point tp_id, + u64 regions_mask, + struct iwl_ucode_tlv **imr_tlv) { - u32 size = 0; - for (int i = 0; i < ARRAY_SIZE(fwrt->trans->dbg.active_regions); i++) { struct iwl_dump_ini_region_data reg_data = { .dump_data = dump_data, }; - u32 reg_type, dp; + u32 reg_type; struct iwl_fw_ini_region_tlv *reg; if (!(BIT_ULL(i) & regions_mask)) @@ -2650,8 +2655,6 @@ iwl_dump_ini_dump_regions(struct iwl_fw_runtime *fwrt, if (reg_type >= ARRAY_SIZE(iwl_dump_ini_region_ops)) continue; - dp = le32_get_bits(reg->id, IWL_FW_INI_REGION_DUMP_POLICY_MASK); - if ((reg_type == IWL_FW_INI_REGION_PERIPHERY_PHY || reg_type == IWL_FW_INI_REGION_PERIPHERY_PHY_RANGE || reg_type == IWL_FW_INI_REGION_PERIPHERY_SNPS_DPHYIP) && @@ -2662,6 +2665,39 @@ iwl_dump_ini_dump_regions(struct iwl_fw_runtime *fwrt, continue; } + /* + * DRAM_IMR can be collected only for FW/HW error timepoint + * when fw is not alive. In addition, it must be collected + * lastly as it overwrites SRAM that can possibly contain + * debug data which also need to be collected. + */ + if (reg_type == IWL_FW_INI_REGION_DRAM_IMR) { + if (iwl_dump_due_to_error(tp_id)) + *imr_tlv = fwrt->trans->dbg.active_regions[i]; + else + IWL_INFO(fwrt, + "WRT: trying to collect DRAM_IMR at time point: %d, skipping\n", + tp_id); + /* continue to next region */ + continue; + } + + iwl_dump_ini_mem_prep(fwrt, list, ®_data, + &iwl_dump_ini_region_ops[reg_type]); + } +} + +static u32 +iwl_dump_ini_dump_entries(struct iwl_fw_runtime *fwrt, + struct list_head *list, + enum iwl_dump_ini_region_selector which) +{ + struct iwl_fw_ini_dump_entry *entry, *tmp; + u32 size = 0; + + list_for_each_entry_safe(entry, tmp, list, list) { + u32 dp = entry->region_dump_policy; + switch (which) { case IWL_INI_DUMP_ALL_REGIONS: break; @@ -2675,27 +2711,7 @@ iwl_dump_ini_dump_regions(struct iwl_fw_runtime *fwrt, break; } - /* - * DRAM_IMR can be collected only for FW/HW error timepoint - * when fw is not alive. In addition, it must be collected - * lastly as it overwrites SRAM that can possibly contain - * debug data which also need to be collected. - */ - if (reg_type == IWL_FW_INI_REGION_DRAM_IMR) { - if (iwl_dump_due_to_error(tp_id)) - imr_reg_data->reg_tlv = - fwrt->trans->dbg.active_regions[i]; - else - IWL_INFO(fwrt, - "WRT: trying to collect DRAM_IMR at time point: %d, skipping\n", - tp_id); - /* continue to next region */ - continue; - } - - - size += iwl_dump_ini_mem(fwrt, list, ®_data, - &iwl_dump_ini_region_ops[reg_type]); + size += iwl_dump_ini_mem(fwrt, entry); } return size; @@ -2718,32 +2734,32 @@ static u32 iwl_dump_ini_trigger(struct iwl_fw_runtime *fwrt, BUILD_BUG_ON((sizeof(trigger->regions_mask) * BITS_PER_BYTE) < ARRAY_SIZE(fwrt->trans->dbg.active_regions)); + iwl_dump_ini_dump_regions_prep(fwrt, dump_data, list, tp_id, + regions_mask, &imr_reg_data.reg_tlv); + + /* append DRAM_IMR region to be collected last */ + if (imr_reg_data.reg_tlv) + iwl_dump_ini_mem_prep(fwrt, list, &imr_reg_data, + &iwl_dump_ini_region_ops[IWL_FW_INI_REGION_DRAM_IMR]); + if (trigger->apply_policy & cpu_to_le32(IWL_FW_INI_APPLY_POLICY_SPLIT_DUMP_RESET)) { - size += iwl_dump_ini_dump_regions(fwrt, dump_data, list, tp_id, - regions_mask, &imr_reg_data, + size += iwl_dump_ini_dump_entries(fwrt, list, IWL_INI_DUMP_EARLY_REGIONS); iwl_trans_pcie_fw_reset_handshake(fwrt->trans); - size += iwl_dump_ini_dump_regions(fwrt, dump_data, list, tp_id, - regions_mask, &imr_reg_data, + size += iwl_dump_ini_dump_entries(fwrt, list, IWL_INI_DUMP_LATE_REGIONS); } else { if (fw_has_capa(&fwrt->fw->ucode_capa, IWL_UCODE_TLV_CAPA_RESET_DURING_ASSERT) && iwl_dump_due_to_error(tp_id)) iwl_trans_pcie_fw_reset_handshake(fwrt->trans); - size += iwl_dump_ini_dump_regions(fwrt, dump_data, list, tp_id, - regions_mask, &imr_reg_data, + size += iwl_dump_ini_dump_entries(fwrt, list, IWL_INI_DUMP_ALL_REGIONS); } - /* collect DRAM_IMR region in the last */ - if (imr_reg_data.reg_tlv) - size += iwl_dump_ini_mem(fwrt, list, &imr_reg_data, - &iwl_dump_ini_region_ops[IWL_FW_INI_REGION_DRAM_IMR]); - if (size) { + if (size) size += iwl_dump_ini_info(fwrt, trigger, list); - } return size; } From a2eb1e75ef6b1e471d33e53918f7060f4e1f453b Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 17 May 2026 10:59:56 +0300 Subject: [PATCH 128/143] wifi: iwlwifi: fw: dbg: always use non-tracing PRPH access The iwl_{read,write}_prph_no_grab() functions will trace each access, but in debug dump a lot of accesses already use the transport versions of these functions directly. Since the data (register addresses and their content) is going into the dump file, tracing isn't really needed. Use the transport functions in all places. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260517100550.bbc7623a0348.I827729916da8f264befbcb90ac6509c359ee97a3@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/dbg.c | 63 +++++++++++---------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c index 886c8e6ef48e..18667de4915f 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c @@ -46,8 +46,8 @@ static void iwl_read_radio_regs(struct iwl_fw_runtime *fwrt, u32 rd_cmd = RADIO_RSP_RD_CMD; rd_cmd |= i << RADIO_RSP_ADDR_POS; - iwl_write_prph_no_grab(fwrt->trans, RSP_RADIO_CMD, rd_cmd); - *pos = (u8)iwl_read_prph_no_grab(fwrt->trans, RSP_RADIO_RDDAT); + iwl_trans_write_prph(fwrt->trans, RSP_RADIO_CMD, rd_cmd); + *pos = (u8)iwl_trans_read_prph(fwrt->trans, RSP_RADIO_RDDAT); pos++; } @@ -513,7 +513,7 @@ static void iwl_read_prph_block(struct iwl_trans *trans, u32 start, u32 i; for (i = 0; i < len_bytes; i += 4) - *data++ = cpu_to_le32(iwl_read_prph_no_grab(trans, start + i)); + *data++ = cpu_to_le32(iwl_trans_read_prph(trans, start + i)); } static void iwl_dump_prph(struct iwl_fw_runtime *fwrt, @@ -1132,16 +1132,16 @@ static int iwl_dump_ini_prph_phy_iter_common(struct iwl_fw_runtime *fwrt, continue; } - iwl_write_prph_no_grab(fwrt->trans, indirect_wr_addr, - WMAL_INDRCT_CMD(addr + i)); + iwl_trans_write_prph(fwrt->trans, indirect_wr_addr, + WMAL_INDRCT_CMD(addr + i)); if (fwrt->trans->info.hw_rf_id != IWL_CFG_RF_TYPE_JF1 && fwrt->trans->info.hw_rf_id != IWL_CFG_RF_TYPE_JF2 && fwrt->trans->info.hw_rf_id != IWL_CFG_RF_TYPE_HR1 && fwrt->trans->info.hw_rf_id != IWL_CFG_RF_TYPE_HR2) { udelay(2); - prph_stts = iwl_read_prph_no_grab(fwrt->trans, - WMAL_MRSPF_STTS); + prph_stts = iwl_trans_read_prph(fwrt->trans, + WMAL_MRSPF_STTS); /* Abort dump if status is 0xA5A5A5A2 or FIFO1 empty */ if (prph_stts == WMAL_TIMEOUT_VAL || @@ -1149,8 +1149,8 @@ static int iwl_dump_ini_prph_phy_iter_common(struct iwl_fw_runtime *fwrt, break; } - prph_val = iwl_read_prph_no_grab(fwrt->trans, - indirect_rd_addr); + prph_val = iwl_trans_read_prph(fwrt->trans, + indirect_rd_addr); *val++ = cpu_to_le32(prph_val); } @@ -1410,7 +1410,7 @@ static int iwl_dump_ini_txf_iter(struct iwl_fw_runtime *fwrt, range->fifo_hdr.num_of_registers = cpu_to_le32(registers_num); range->range_data_size = cpu_to_le32(iter->fifo_size + registers_size); - iwl_write_prph_no_grab(fwrt->trans, TXF_LARC_NUM + offs, iter->fifo); + iwl_trans_write_prph(fwrt->trans, TXF_LARC_NUM + offs, iter->fifo); /* * read txf registers. for each register, write to the dump the @@ -1420,8 +1420,8 @@ static int iwl_dump_ini_txf_iter(struct iwl_fw_runtime *fwrt, addr = le32_to_cpu(reg->addrs[i]) + offs; reg_dump->addr = cpu_to_le32(addr); - reg_dump->data = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, - addr)); + reg_dump->data = cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + addr)); reg_dump++; } @@ -1432,17 +1432,17 @@ static int iwl_dump_ini_txf_iter(struct iwl_fw_runtime *fwrt, } /* Set the TXF_READ_MODIFY_ADDR to TXF_WR_PTR */ - iwl_write_prph_no_grab(fwrt->trans, TXF_READ_MODIFY_ADDR + offs, - TXF_WR_PTR + offs); + iwl_trans_write_prph(fwrt->trans, TXF_READ_MODIFY_ADDR + offs, + TXF_WR_PTR + offs); /* Dummy-read to advance the read pointer to the head */ - iwl_read_prph_no_grab(fwrt->trans, TXF_READ_MODIFY_DATA + offs); + iwl_trans_read_prph(fwrt->trans, TXF_READ_MODIFY_DATA + offs); /* Read FIFO */ addr = TXF_READ_MODIFY_DATA + offs; data = (void *)reg_dump; for (i = 0; i < iter->fifo_size; i += sizeof(*data)) - *data++ = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, addr)); + *data++ = cpu_to_le32(iwl_trans_read_prph(fwrt->trans, addr)); if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, @@ -1487,12 +1487,12 @@ iwl_dump_ini_prph_snps_dphyip_iter(struct iwl_fw_runtime *fwrt, continue; } - iwl_write_prph_no_grab(fwrt->trans, indirect_rd_wr_addr, - addr + i); + iwl_trans_write_prph(fwrt->trans, indirect_rd_wr_addr, + addr + i); /* wait a bit for value to be ready in register */ udelay(1); - prph_val = iwl_read_prph_no_grab(fwrt->trans, - indirect_rd_wr_addr); + prph_val = iwl_trans_read_prph(fwrt->trans, + indirect_rd_wr_addr); *val++ = cpu_to_le32((prph_val & DPHYIP_INDIRECT_RD_MSK) >> DPHYIP_INDIRECT_RD_SHIFT); } @@ -1601,8 +1601,8 @@ static int iwl_dump_ini_rxf_iter(struct iwl_fw_runtime *fwrt, addr = le32_to_cpu(reg->addrs[i]) + offs; reg_dump->addr = cpu_to_le32(addr); - reg_dump->data = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, - addr)); + reg_dump->data = cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + addr)); reg_dump++; } @@ -1615,18 +1615,17 @@ static int iwl_dump_ini_rxf_iter(struct iwl_fw_runtime *fwrt, offs = rxf_data.offset; /* Lock fence */ - iwl_write_prph_no_grab(fwrt->trans, RXF_SET_FENCE_MODE + offs, 0x1); + iwl_trans_write_prph(fwrt->trans, RXF_SET_FENCE_MODE + offs, 0x1); /* Set fence pointer to the same place like WR pointer */ - iwl_write_prph_no_grab(fwrt->trans, RXF_LD_WR2FENCE + offs, 0x1); + iwl_trans_write_prph(fwrt->trans, RXF_LD_WR2FENCE + offs, 0x1); /* Set fence offset */ - iwl_write_prph_no_grab(fwrt->trans, RXF_LD_FENCE_OFFSET_ADDR + offs, - 0x0); + iwl_trans_write_prph(fwrt->trans, RXF_LD_FENCE_OFFSET_ADDR + offs, 0x0); /* Read FIFO */ addr = RXF_FIFO_RD_FENCE_INC + offs; data = (void *)reg_dump; for (i = 0; i < rxf_data.size; i += sizeof(*data)) - *data++ = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, addr)); + *data++ = cpu_to_le32(iwl_trans_read_prph(fwrt->trans, addr)); out: iwl_trans_release_nic_access(fwrt->trans); @@ -1690,9 +1689,11 @@ iwl_dump_ini_dbgi_sram_iter(struct iwl_fw_runtime *fwrt, range->range_data_size = reg->dev_addr.size; for (i = 0; i < (le32_to_cpu(reg->dev_addr.size) / 4); i++) { - prph_data = iwl_read_prph_no_grab(fwrt->trans, (i % 2) ? - DBGI_SRAM_TARGET_ACCESS_RDATA_MSB : - DBGI_SRAM_TARGET_ACCESS_RDATA_LSB); + prph_data = + iwl_trans_read_prph(fwrt->trans, + (i % 2) ? + DBGI_SRAM_TARGET_ACCESS_RDATA_MSB : + DBGI_SRAM_TARGET_ACCESS_RDATA_LSB); if (iwl_trans_is_hw_error_value(prph_data)) { iwl_trans_release_nic_access(fwrt->trans); return -EBUSY; @@ -1791,7 +1792,7 @@ static __le32 iwl_get_mon_reg(struct iwl_fw_runtime *fwrt, u32 alloc_id, if (!reg_info || !reg_info->addr || !reg_info->mask) return 0; - val = iwl_read_prph_no_grab(fwrt->trans, reg_info->addr + offs); + val = iwl_trans_read_prph(fwrt->trans, reg_info->addr + offs); return cpu_to_le32(mask_apply_and_normalize(val, reg_info->mask)); } From 2c3ea36a59f3350f0cea41b8bd1ea74425165e11 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 17 May 2026 10:59:57 +0300 Subject: [PATCH 129/143] wifi: iwlwifi: fw: separate out old-style dump code There are two dump paths: new "ini" style, and the old, more driver-centric, way. Separate out the code for the old way into a separate dbg-old.c file, to simplify. Fix a typo ('trasport') along the way. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260517100550.1de0f1110d5a.I4faebe15192a1f27cb4b7270fb52154f06eb2a10@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/Makefile | 2 +- .../net/wireless/intel/iwlwifi/fw/dbg-old.c | 1022 +++++++++++++++++ drivers/net/wireless/intel/iwlwifi/fw/dbg.c | 1012 +--------------- drivers/net/wireless/intel/iwlwifi/fw/dbg.h | 7 +- 4 files changed, 1032 insertions(+), 1011 deletions(-) create mode 100644 drivers/net/wireless/intel/iwlwifi/fw/dbg-old.c diff --git a/drivers/net/wireless/intel/iwlwifi/Makefile b/drivers/net/wireless/intel/iwlwifi/Makefile index 941257b811b4..2a74efe09cf0 100644 --- a/drivers/net/wireless/intel/iwlwifi/Makefile +++ b/drivers/net/wireless/intel/iwlwifi/Makefile @@ -32,7 +32,7 @@ iwlwifi-objs += iwl-dbg-tlv.o iwlwifi-objs += iwl-trans.o iwlwifi-objs += fw/img.o fw/notif-wait.o fw/rs.o -iwlwifi-objs += fw/dbg.o fw/pnvm.o fw/dump.o +iwlwifi-objs += fw/dbg.o fw/dbg-old.o fw/pnvm.o fw/dump.o iwlwifi-objs += fw/regulatory.o iwlwifi-$(CONFIG_IWLMVM) += fw/paging.o fw/smem.o fw/init.o iwlwifi-$(CONFIG_IWLMLD) += fw/smem.o fw/init.o diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg-old.c b/drivers/net/wireless/intel/iwlwifi/fw/dbg-old.c new file mode 100644 index 000000000000..19b1bfd0abee --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg-old.c @@ -0,0 +1,1022 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2026 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include +#include "iwl-drv.h" +#include "runtime.h" +#include "dbg.h" +#include "debugfs.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-csr.h" +#include "iwl-fh.h" + +/** + * struct iwl_fw_dump_ptrs - set of pointers needed for the fw-error-dump + * + * @fwrt_ptr: pointer to the buffer coming from fwrt + * @trans_ptr: pointer to struct %iwl_trans_dump_data which contains the + * transport's data. + * @fwrt_len: length of the valid data in fwrt_ptr + */ +struct iwl_fw_dump_ptrs { + struct iwl_trans_dump_data *trans_ptr; + void *fwrt_ptr; + u32 fwrt_len; +}; + +#define RADIO_REG_MAX_READ 0x2ad +static void iwl_read_radio_regs(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + u8 *pos = (void *)(*dump_data)->data; + int i; + + IWL_DEBUG_INFO(fwrt, "WRT radio registers dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RADIO_REG); + (*dump_data)->len = cpu_to_le32(RADIO_REG_MAX_READ); + + for (i = 0; i < RADIO_REG_MAX_READ; i++) { + u32 rd_cmd = RADIO_RSP_RD_CMD; + + rd_cmd |= i << RADIO_RSP_ADDR_POS; + iwl_trans_write_prph(fwrt->trans, RSP_RADIO_CMD, rd_cmd); + *pos = (u8)iwl_trans_read_prph(fwrt->trans, RSP_RADIO_RDDAT); + + pos++; + } + + *dump_data = iwl_fw_error_next_data(*dump_data); + + iwl_trans_release_nic_access(fwrt->trans); +} + +static void iwl_fwrt_dump_rxf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + int size, u32 offset, int fifo_num) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + u32 *fifo_data; + u32 fifo_len; + int i; + + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = size; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + return; + + /* Add a TLV for the RXF */ + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RXF); + (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(fifo_num); + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_D_SPACE + offset)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_WR_PTR + offset)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_RD_PTR + offset)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_FENCE_PTR + offset)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_SET_FENCE_MODE + offset)); + + /* Lock fence */ + iwl_trans_write_prph(fwrt->trans, RXF_SET_FENCE_MODE + offset, 0x1); + /* Set fence pointer to the same place like WR pointer */ + iwl_trans_write_prph(fwrt->trans, RXF_LD_WR2FENCE + offset, 0x1); + /* Set fence offset */ + iwl_trans_write_prph(fwrt->trans, + RXF_LD_FENCE_OFFSET_ADDR + offset, 0x0); + + /* Read FIFO */ + fifo_len /= sizeof(u32); /* Size in DWORDS */ + for (i = 0; i < fifo_len; i++) + fifo_data[i] = iwl_trans_read_prph(fwrt->trans, + RXF_FIFO_RD_FENCE_INC + + offset); + *dump_data = iwl_fw_error_next_data(*dump_data); +} + +static void iwl_fwrt_dump_txf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + int size, u32 offset, int fifo_num) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + u32 *fifo_data; + u32 fifo_len; + int i; + + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = size; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + return; + + /* Add a TLV for the FIFO */ + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_TXF); + (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(fifo_num); + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_FIFO_ITEM_CNT + offset)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_WR_PTR + offset)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_RD_PTR + offset)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_FENCE_PTR + offset)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_LOCK_FENCE + offset)); + + /* Set the TXF_READ_MODIFY_ADDR to TXF_WR_PTR */ + iwl_trans_write_prph(fwrt->trans, TXF_READ_MODIFY_ADDR + offset, + TXF_WR_PTR + offset); + + /* Dummy-read to advance the read pointer to the head */ + iwl_trans_read_prph(fwrt->trans, TXF_READ_MODIFY_DATA + offset); + + /* Read FIFO */ + for (i = 0; i < fifo_len / sizeof(u32); i++) + fifo_data[i] = iwl_trans_read_prph(fwrt->trans, + TXF_READ_MODIFY_DATA + + offset); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) + fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, + fifo_data, fifo_len); + + *dump_data = iwl_fw_error_next_data(*dump_data); +} + +static void iwl_fw_dump_rxf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; + + IWL_DEBUG_INFO(fwrt, "WRT RX FIFO dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) { + /* Pull RXF1 */ + iwl_fwrt_dump_rxf(fwrt, dump_data, + cfg->lmac[0].rxfifo1_size, 0, 0); + /* Pull RXF2 */ + iwl_fwrt_dump_rxf(fwrt, dump_data, cfg->rxfifo2_size, + RXF_DIFF_FROM_PREV + + fwrt->trans->mac_cfg->umac_prph_offset, 1); + /* Pull LMAC2 RXF1 */ + if (fwrt->smem_cfg.num_lmacs > 1) + iwl_fwrt_dump_rxf(fwrt, dump_data, + cfg->lmac[1].rxfifo1_size, + LMAC2_PRPH_OFFSET, 2); + } + + iwl_trans_release_nic_access(fwrt->trans); +} + +static void iwl_fw_dump_txf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; + u32 *fifo_data; + u32 fifo_len; + int i, j; + + IWL_DEBUG_INFO(fwrt, "WRT TX FIFO dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) { + /* Pull TXF data from LMAC1 */ + for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; i++) { + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, TXF_LARC_NUM, i); + iwl_fwrt_dump_txf(fwrt, dump_data, + cfg->lmac[0].txfifo_size[i], 0, i); + } + + /* Pull TXF data from LMAC2 */ + if (fwrt->smem_cfg.num_lmacs > 1) { + for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; + i++) { + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, + TXF_LARC_NUM + + LMAC2_PRPH_OFFSET, i); + iwl_fwrt_dump_txf(fwrt, dump_data, + cfg->lmac[1].txfifo_size[i], + LMAC2_PRPH_OFFSET, + i + cfg->num_txfifo_entries); + } + } + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && + fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) { + /* Pull UMAC internal TXF data from all TXFs */ + for (i = 0; + i < ARRAY_SIZE(fwrt->smem_cfg.internal_txfifo_size); + i++) { + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = fwrt->smem_cfg.internal_txfifo_size[i]; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + continue; + + /* Add a TLV for the internal FIFOs */ + (*dump_data)->type = + cpu_to_le32(IWL_FW_ERROR_DUMP_INTERNAL_TXF); + (*dump_data)->len = + cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(i); + + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, TXF_CPU2_NUM, i + + fwrt->smem_cfg.num_txfifo_entries); + + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_FIFO_ITEM_CNT)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_WR_PTR)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_RD_PTR)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_FENCE_PTR)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_LOCK_FENCE)); + + /* Set TXF_CPU2_READ_MODIFY_ADDR to TXF_CPU2_WR_PTR */ + iwl_trans_write_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_ADDR, + TXF_CPU2_WR_PTR); + + /* Dummy-read to advance the read pointer to head */ + iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_DATA); + + /* Read FIFO */ + fifo_len /= sizeof(u32); /* Size in DWORDS */ + for (j = 0; j < fifo_len; j++) + fifo_data[j] = + iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_DATA); + *dump_data = iwl_fw_error_next_data(*dump_data); + } + } + + iwl_trans_release_nic_access(fwrt->trans); +} + +struct iwl_prph_range { + u32 start, end; +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_comm[] = { + { .start = 0x00a00000, .end = 0x00a00000 }, + { .start = 0x00a0000c, .end = 0x00a00024 }, + { .start = 0x00a0002c, .end = 0x00a0003c }, + { .start = 0x00a00410, .end = 0x00a00418 }, + { .start = 0x00a00420, .end = 0x00a00420 }, + { .start = 0x00a00428, .end = 0x00a00428 }, + { .start = 0x00a00430, .end = 0x00a0043c }, + { .start = 0x00a00444, .end = 0x00a00444 }, + { .start = 0x00a004c0, .end = 0x00a004cc }, + { .start = 0x00a004d8, .end = 0x00a004d8 }, + { .start = 0x00a004e0, .end = 0x00a004f0 }, + { .start = 0x00a00840, .end = 0x00a00840 }, + { .start = 0x00a00850, .end = 0x00a00858 }, + { .start = 0x00a01004, .end = 0x00a01008 }, + { .start = 0x00a01010, .end = 0x00a01010 }, + { .start = 0x00a01018, .end = 0x00a01018 }, + { .start = 0x00a01024, .end = 0x00a01024 }, + { .start = 0x00a0102c, .end = 0x00a01034 }, + { .start = 0x00a0103c, .end = 0x00a01040 }, + { .start = 0x00a01048, .end = 0x00a01094 }, + { .start = 0x00a01c00, .end = 0x00a01c20 }, + { .start = 0x00a01c58, .end = 0x00a01c58 }, + { .start = 0x00a01c7c, .end = 0x00a01c7c }, + { .start = 0x00a01c28, .end = 0x00a01c54 }, + { .start = 0x00a01c5c, .end = 0x00a01c5c }, + { .start = 0x00a01c60, .end = 0x00a01cdc }, + { .start = 0x00a01ce0, .end = 0x00a01d0c }, + { .start = 0x00a01d18, .end = 0x00a01d20 }, + { .start = 0x00a01d2c, .end = 0x00a01d30 }, + { .start = 0x00a01d40, .end = 0x00a01d5c }, + { .start = 0x00a01d80, .end = 0x00a01d80 }, + { .start = 0x00a01d98, .end = 0x00a01d9c }, + { .start = 0x00a01da8, .end = 0x00a01da8 }, + { .start = 0x00a01db8, .end = 0x00a01df4 }, + { .start = 0x00a01dc0, .end = 0x00a01dfc }, + { .start = 0x00a01e00, .end = 0x00a01e2c }, + { .start = 0x00a01e40, .end = 0x00a01e60 }, + { .start = 0x00a01e68, .end = 0x00a01e6c }, + { .start = 0x00a01e74, .end = 0x00a01e74 }, + { .start = 0x00a01e84, .end = 0x00a01e90 }, + { .start = 0x00a01e9c, .end = 0x00a01ec4 }, + { .start = 0x00a01ed0, .end = 0x00a01ee0 }, + { .start = 0x00a01f00, .end = 0x00a01f1c }, + { .start = 0x00a01f44, .end = 0x00a01ffc }, + { .start = 0x00a02000, .end = 0x00a02048 }, + { .start = 0x00a02068, .end = 0x00a020f0 }, + { .start = 0x00a02100, .end = 0x00a02118 }, + { .start = 0x00a02140, .end = 0x00a0214c }, + { .start = 0x00a02168, .end = 0x00a0218c }, + { .start = 0x00a021c0, .end = 0x00a021c0 }, + { .start = 0x00a02400, .end = 0x00a02410 }, + { .start = 0x00a02418, .end = 0x00a02420 }, + { .start = 0x00a02428, .end = 0x00a0242c }, + { .start = 0x00a02434, .end = 0x00a02434 }, + { .start = 0x00a02440, .end = 0x00a02460 }, + { .start = 0x00a02468, .end = 0x00a024b0 }, + { .start = 0x00a024c8, .end = 0x00a024cc }, + { .start = 0x00a02500, .end = 0x00a02504 }, + { .start = 0x00a0250c, .end = 0x00a02510 }, + { .start = 0x00a02540, .end = 0x00a02554 }, + { .start = 0x00a02580, .end = 0x00a025f4 }, + { .start = 0x00a02600, .end = 0x00a0260c }, + { .start = 0x00a02648, .end = 0x00a02650 }, + { .start = 0x00a02680, .end = 0x00a02680 }, + { .start = 0x00a026c0, .end = 0x00a026d0 }, + { .start = 0x00a02700, .end = 0x00a0270c }, + { .start = 0x00a02804, .end = 0x00a02804 }, + { .start = 0x00a02818, .end = 0x00a0281c }, + { .start = 0x00a02c00, .end = 0x00a02db4 }, + { .start = 0x00a02df4, .end = 0x00a02fb0 }, + { .start = 0x00a03000, .end = 0x00a03014 }, + { .start = 0x00a0301c, .end = 0x00a0302c }, + { .start = 0x00a03034, .end = 0x00a03038 }, + { .start = 0x00a03040, .end = 0x00a03048 }, + { .start = 0x00a03060, .end = 0x00a03068 }, + { .start = 0x00a03070, .end = 0x00a03074 }, + { .start = 0x00a0307c, .end = 0x00a0307c }, + { .start = 0x00a03080, .end = 0x00a03084 }, + { .start = 0x00a0308c, .end = 0x00a03090 }, + { .start = 0x00a03098, .end = 0x00a03098 }, + { .start = 0x00a030a0, .end = 0x00a030a0 }, + { .start = 0x00a030a8, .end = 0x00a030b4 }, + { .start = 0x00a030bc, .end = 0x00a030bc }, + { .start = 0x00a030c0, .end = 0x00a0312c }, + { .start = 0x00a03c00, .end = 0x00a03c5c }, + { .start = 0x00a04400, .end = 0x00a04454 }, + { .start = 0x00a04460, .end = 0x00a04474 }, + { .start = 0x00a044c0, .end = 0x00a044ec }, + { .start = 0x00a04500, .end = 0x00a04504 }, + { .start = 0x00a04510, .end = 0x00a04538 }, + { .start = 0x00a04540, .end = 0x00a04548 }, + { .start = 0x00a04560, .end = 0x00a0457c }, + { .start = 0x00a04590, .end = 0x00a04598 }, + { .start = 0x00a045c0, .end = 0x00a045f4 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_9000[] = { + { .start = 0x00a05c00, .end = 0x00a05c18 }, + { .start = 0x00a05400, .end = 0x00a056e8 }, + { .start = 0x00a08000, .end = 0x00a098bc }, + { .start = 0x00a02400, .end = 0x00a02758 }, + { .start = 0x00a04764, .end = 0x00a0476c }, + { .start = 0x00a04770, .end = 0x00a04774 }, + { .start = 0x00a04620, .end = 0x00a04624 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_22000[] = { + { .start = 0x00a00000, .end = 0x00a00000 }, + { .start = 0x00a0000c, .end = 0x00a00024 }, + { .start = 0x00a0002c, .end = 0x00a00034 }, + { .start = 0x00a0003c, .end = 0x00a0003c }, + { .start = 0x00a00410, .end = 0x00a00418 }, + { .start = 0x00a00420, .end = 0x00a00420 }, + { .start = 0x00a00428, .end = 0x00a00428 }, + { .start = 0x00a00430, .end = 0x00a0043c }, + { .start = 0x00a00444, .end = 0x00a00444 }, + { .start = 0x00a00840, .end = 0x00a00840 }, + { .start = 0x00a00850, .end = 0x00a00858 }, + { .start = 0x00a01004, .end = 0x00a01008 }, + { .start = 0x00a01010, .end = 0x00a01010 }, + { .start = 0x00a01018, .end = 0x00a01018 }, + { .start = 0x00a01024, .end = 0x00a01024 }, + { .start = 0x00a0102c, .end = 0x00a01034 }, + { .start = 0x00a0103c, .end = 0x00a01040 }, + { .start = 0x00a01048, .end = 0x00a01050 }, + { .start = 0x00a01058, .end = 0x00a01058 }, + { .start = 0x00a01060, .end = 0x00a01070 }, + { .start = 0x00a0108c, .end = 0x00a0108c }, + { .start = 0x00a01c20, .end = 0x00a01c28 }, + { .start = 0x00a01d10, .end = 0x00a01d10 }, + { .start = 0x00a01e28, .end = 0x00a01e2c }, + { .start = 0x00a01e60, .end = 0x00a01e60 }, + { .start = 0x00a01e80, .end = 0x00a01e80 }, + { .start = 0x00a01ea0, .end = 0x00a01ea0 }, + { .start = 0x00a02000, .end = 0x00a0201c }, + { .start = 0x00a02024, .end = 0x00a02024 }, + { .start = 0x00a02040, .end = 0x00a02048 }, + { .start = 0x00a020c0, .end = 0x00a020e0 }, + { .start = 0x00a02400, .end = 0x00a02404 }, + { .start = 0x00a0240c, .end = 0x00a02414 }, + { .start = 0x00a0241c, .end = 0x00a0243c }, + { .start = 0x00a02448, .end = 0x00a024bc }, + { .start = 0x00a024c4, .end = 0x00a024cc }, + { .start = 0x00a02508, .end = 0x00a02508 }, + { .start = 0x00a02510, .end = 0x00a02514 }, + { .start = 0x00a0251c, .end = 0x00a0251c }, + { .start = 0x00a0252c, .end = 0x00a0255c }, + { .start = 0x00a02564, .end = 0x00a025a0 }, + { .start = 0x00a025a8, .end = 0x00a025b4 }, + { .start = 0x00a025c0, .end = 0x00a025c0 }, + { .start = 0x00a025e8, .end = 0x00a025f4 }, + { .start = 0x00a02c08, .end = 0x00a02c18 }, + { .start = 0x00a02c2c, .end = 0x00a02c38 }, + { .start = 0x00a02c68, .end = 0x00a02c78 }, + { .start = 0x00a03000, .end = 0x00a03000 }, + { .start = 0x00a03010, .end = 0x00a03014 }, + { .start = 0x00a0301c, .end = 0x00a0302c }, + { .start = 0x00a03034, .end = 0x00a03038 }, + { .start = 0x00a03040, .end = 0x00a03044 }, + { .start = 0x00a03060, .end = 0x00a03068 }, + { .start = 0x00a03070, .end = 0x00a03070 }, + { .start = 0x00a0307c, .end = 0x00a03084 }, + { .start = 0x00a0308c, .end = 0x00a03090 }, + { .start = 0x00a03098, .end = 0x00a03098 }, + { .start = 0x00a030a0, .end = 0x00a030a0 }, + { .start = 0x00a030a8, .end = 0x00a030b4 }, + { .start = 0x00a030bc, .end = 0x00a030c0 }, + { .start = 0x00a030c8, .end = 0x00a030f4 }, + { .start = 0x00a03100, .end = 0x00a0312c }, + { .start = 0x00a03c00, .end = 0x00a03c5c }, + { .start = 0x00a04400, .end = 0x00a04454 }, + { .start = 0x00a04460, .end = 0x00a04474 }, + { .start = 0x00a044c0, .end = 0x00a044ec }, + { .start = 0x00a04500, .end = 0x00a04504 }, + { .start = 0x00a04510, .end = 0x00a04538 }, + { .start = 0x00a04540, .end = 0x00a04548 }, + { .start = 0x00a04560, .end = 0x00a04560 }, + { .start = 0x00a04570, .end = 0x00a0457c }, + { .start = 0x00a04590, .end = 0x00a04590 }, + { .start = 0x00a04598, .end = 0x00a04598 }, + { .start = 0x00a045c0, .end = 0x00a045f4 }, + { .start = 0x00a05c18, .end = 0x00a05c1c }, + { .start = 0x00a0c000, .end = 0x00a0c018 }, + { .start = 0x00a0c020, .end = 0x00a0c028 }, + { .start = 0x00a0c038, .end = 0x00a0c094 }, + { .start = 0x00a0c0c0, .end = 0x00a0c104 }, + { .start = 0x00a0c10c, .end = 0x00a0c118 }, + { .start = 0x00a0c150, .end = 0x00a0c174 }, + { .start = 0x00a0c17c, .end = 0x00a0c188 }, + { .start = 0x00a0c190, .end = 0x00a0c198 }, + { .start = 0x00a0c1a0, .end = 0x00a0c1a8 }, + { .start = 0x00a0c1b0, .end = 0x00a0c1b8 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_ax210[] = { + { .start = 0x00d03c00, .end = 0x00d03c64 }, + { .start = 0x00d05c18, .end = 0x00d05c1c }, + { .start = 0x00d0c000, .end = 0x00d0c174 }, +}; + +static void iwl_read_prph_block(struct iwl_trans *trans, u32 start, + u32 len_bytes, __le32 *data) +{ + u32 i; + + for (i = 0; i < len_bytes; i += 4) + *data++ = cpu_to_le32(iwl_trans_read_prph(trans, start + i)); +} + +static void iwl_dump_prph(struct iwl_fw_runtime *fwrt, + const struct iwl_prph_range *iwl_prph_dump_addr, + u32 range_len, void *ptr) +{ + struct iwl_fw_error_dump_prph *prph; + struct iwl_trans *trans = fwrt->trans; + struct iwl_fw_error_dump_data **data = + (struct iwl_fw_error_dump_data **)ptr; + u32 i; + + if (!data) + return; + + IWL_DEBUG_INFO(trans, "WRT PRPH dump\n"); + + if (!iwl_trans_grab_nic_access(trans)) + return; + + for (i = 0; i < range_len; i++) { + /* The range includes both boundaries */ + int num_bytes_in_chunk = iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PRPH); + (*data)->len = cpu_to_le32(sizeof(*prph) + + num_bytes_in_chunk); + prph = (void *)(*data)->data; + prph->prph_start = cpu_to_le32(iwl_prph_dump_addr[i].start); + + iwl_read_prph_block(trans, iwl_prph_dump_addr[i].start, + /* our range is inclusive, hence + 4 */ + iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4, + (void *)prph->data); + + *data = iwl_fw_error_next_data(*data); + } + + iwl_trans_release_nic_access(trans); +} + +static void iwl_fw_get_prph_len(struct iwl_fw_runtime *fwrt, + const struct iwl_prph_range *iwl_prph_dump_addr, + u32 range_len, void *ptr) +{ + u32 *prph_len = (u32 *)ptr; + int i, num_bytes_in_chunk; + + if (!prph_len) + return; + + for (i = 0; i < range_len; i++) { + /* The range includes both boundaries */ + num_bytes_in_chunk = + iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4; + + *prph_len += sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_prph) + + num_bytes_in_chunk; + } +} + +static void iwl_fw_prph_handler(struct iwl_fw_runtime *fwrt, void *ptr, + void (*handler)(struct iwl_fw_runtime *, + const struct iwl_prph_range *, + u32, void *)) +{ + u32 range_len; + + if (fwrt->trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_ax210); + handler(fwrt, iwl_prph_dump_addr_ax210, range_len, ptr); + } else if (fwrt->trans->mac_cfg->device_family >= + IWL_DEVICE_FAMILY_22000) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_22000); + handler(fwrt, iwl_prph_dump_addr_22000, range_len, ptr); + } else { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_comm); + handler(fwrt, iwl_prph_dump_addr_comm, range_len, ptr); + + if (fwrt->trans->mac_cfg->mq_rx_supported) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_9000); + handler(fwrt, iwl_prph_dump_addr_9000, range_len, ptr); + } + } +} + +static void iwl_fw_dump_mem(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + u32 len, u32 ofs, u32 type) +{ + struct iwl_fw_error_dump_mem *dump_mem; + + if (!len) + return; + + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM); + (*dump_data)->len = cpu_to_le32(len + sizeof(*dump_mem)); + dump_mem = (void *)(*dump_data)->data; + dump_mem->type = cpu_to_le32(type); + dump_mem->offset = cpu_to_le32(ofs); + iwl_trans_read_mem_bytes(fwrt->trans, ofs, dump_mem->data, len); + *dump_data = iwl_fw_error_next_data(*dump_data); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, ofs, + dump_mem->data, len); + + IWL_DEBUG_INFO(fwrt, "WRT memory dump. Type=%u\n", dump_mem->type); +} + +#define ADD_LEN(len, item_len, const_len) \ + do {size_t item = item_len; len += (!!item) * const_len + item; } \ + while (0) + +static int iwl_fw_rxf_len(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_shared_mem_cfg *mem_cfg) +{ + size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fifo); + u32 fifo_len = 0; + int i; + + if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) + return 0; + + /* Count RXF2 size */ + ADD_LEN(fifo_len, mem_cfg->rxfifo2_size, hdr_len); + + /* Count RXF1 sizes */ + if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) + mem_cfg->num_lmacs = MAX_NUM_LMAC; + + for (i = 0; i < mem_cfg->num_lmacs; i++) + ADD_LEN(fifo_len, mem_cfg->lmac[i].rxfifo1_size, hdr_len); + + return fifo_len; +} + +static int iwl_fw_txf_len(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_shared_mem_cfg *mem_cfg) +{ + size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fifo); + u32 fifo_len = 0; + int i; + + if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) + goto dump_internal_txf; + + /* Count TXF sizes */ + if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) + mem_cfg->num_lmacs = MAX_NUM_LMAC; + + for (i = 0; i < mem_cfg->num_lmacs; i++) { + int j; + + for (j = 0; j < mem_cfg->num_txfifo_entries; j++) + ADD_LEN(fifo_len, mem_cfg->lmac[i].txfifo_size[j], + hdr_len); + } + +dump_internal_txf: + if (!(iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && + fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG))) + goto out; + + for (i = 0; i < ARRAY_SIZE(mem_cfg->internal_txfifo_size); i++) + ADD_LEN(fifo_len, mem_cfg->internal_txfifo_size[i], hdr_len); + +out: + return fifo_len; +} + +static void iwl_dump_paging(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **data) +{ + int i; + + IWL_DEBUG_INFO(fwrt, "WRT paging dump\n"); + for (i = 1; i < fwrt->num_of_paging_blk + 1; i++) { + struct iwl_fw_error_dump_paging *paging; + struct page *pages = + fwrt->fw_paging_db[i].fw_paging_block; + dma_addr_t addr = fwrt->fw_paging_db[i].fw_paging_phys; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PAGING); + (*data)->len = cpu_to_le32(sizeof(*paging) + + PAGING_BLOCK_SIZE); + paging = (void *)(*data)->data; + paging->index = cpu_to_le32(i); + dma_sync_single_for_cpu(fwrt->trans->dev, addr, + PAGING_BLOCK_SIZE, + DMA_BIDIRECTIONAL); + memcpy(paging->data, page_address(pages), + PAGING_BLOCK_SIZE); + dma_sync_single_for_device(fwrt->trans->dev, addr, + PAGING_BLOCK_SIZE, + DMA_BIDIRECTIONAL); + (*data) = iwl_fw_error_next_data(*data); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, + fwrt->fw_paging_db[i].fw_offs, + paging->data, + PAGING_BLOCK_SIZE); + } +} + +static struct iwl_fw_error_dump_file * +iwl_fw_error_dump_file(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dump_ptrs *fw_error_dump, + struct iwl_fwrt_dump_data *data) +{ + struct iwl_fw_error_dump_file *dump_file; + struct iwl_fw_error_dump_data *dump_data; + struct iwl_fw_error_dump_info *dump_info; + struct iwl_fw_error_dump_smem_cfg *dump_smem_cfg; + struct iwl_fw_error_dump_trigger_desc *dump_trig; + u32 sram_len, sram_ofs; + const struct iwl_fw_dbg_mem_seg_tlv *fw_mem = fwrt->fw->dbg.mem_tlv; + struct iwl_fwrt_shared_mem_cfg *mem_cfg = &fwrt->smem_cfg; + u32 file_len, fifo_len = 0, prph_len = 0, radio_len = 0; + u32 smem_len = fwrt->fw->dbg.n_mem_tlv ? 0 : fwrt->trans->mac_cfg->base->smem_len; + u32 sram2_len = fwrt->fw->dbg.n_mem_tlv ? + 0 : fwrt->trans->cfg->dccm2_len; + int i; + + /* SRAM - include stack CCM if driver knows the values for it */ + if (!fwrt->trans->cfg->dccm_offset || + !fwrt->trans->cfg->dccm_len) { + const struct fw_img *img; + + if (fwrt->cur_fw_img >= IWL_UCODE_TYPE_MAX) + return NULL; + img = &fwrt->fw->img[fwrt->cur_fw_img]; + sram_ofs = img->sec[IWL_UCODE_SECTION_DATA].offset; + sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; + } else { + sram_ofs = fwrt->trans->cfg->dccm_offset; + sram_len = fwrt->trans->cfg->dccm_len; + } + + /* reading RXF/TXF sizes */ + if (iwl_trans_is_fw_error(fwrt->trans)) { + fifo_len = iwl_fw_rxf_len(fwrt, mem_cfg); + fifo_len += iwl_fw_txf_len(fwrt, mem_cfg); + + /* Make room for PRPH registers */ + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_PRPH)) + iwl_fw_prph_handler(fwrt, &prph_len, + iwl_fw_get_prph_len); + + if (fwrt->trans->mac_cfg->device_family == + IWL_DEVICE_FAMILY_7000 && + iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RADIO_REG)) + radio_len = sizeof(*dump_data) + RADIO_REG_MAX_READ; + } + + file_len = sizeof(*dump_file) + fifo_len + prph_len + radio_len; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) + file_len += sizeof(*dump_data) + sizeof(*dump_info); + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) + file_len += sizeof(*dump_data) + sizeof(*dump_smem_cfg); + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { + size_t hdr_len = sizeof(*dump_data) + + sizeof(struct iwl_fw_error_dump_mem); + + /* Dump SRAM only if no mem_tlvs */ + if (!fwrt->fw->dbg.n_mem_tlv) + ADD_LEN(file_len, sram_len, hdr_len); + + /* Make room for all mem types that exist */ + ADD_LEN(file_len, smem_len, hdr_len); + ADD_LEN(file_len, sram2_len, hdr_len); + + for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) + ADD_LEN(file_len, le32_to_cpu(fw_mem[i].len), hdr_len); + } + + /* Make room for fw's virtual image pages, if it exists */ + if (iwl_fw_dbg_is_paging_enabled(fwrt)) + file_len += fwrt->num_of_paging_blk * + (sizeof(*dump_data) + + sizeof(struct iwl_fw_error_dump_paging) + + PAGING_BLOCK_SIZE); + + if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { + file_len += sizeof(*dump_data) + + fwrt->trans->mac_cfg->base->d3_debug_data_length * 2; + } + + /* If we only want a monitor dump, reset the file length */ + if (data->monitor_only) { + file_len = sizeof(*dump_file) + sizeof(*dump_data) * 2 + + sizeof(*dump_info) + sizeof(*dump_smem_cfg); + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && + data->desc) + file_len += sizeof(*dump_data) + sizeof(*dump_trig) + + data->desc->len; + + dump_file = vzalloc(file_len); + if (!dump_file) + return NULL; + + fw_error_dump->fwrt_ptr = dump_file; + + dump_file->barker = cpu_to_le32(IWL_FW_ERROR_DUMP_BARKER); + dump_data = (void *)dump_file->data; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) { + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_DEV_FW_INFO); + dump_data->len = cpu_to_le32(sizeof(*dump_info)); + dump_info = (void *)dump_data->data; + dump_info->hw_type = + cpu_to_le32(CSR_HW_REV_TYPE(fwrt->trans->info.hw_rev)); + dump_info->hw_step = + cpu_to_le32(fwrt->trans->info.hw_rev_step); + memcpy(dump_info->fw_human_readable, fwrt->fw->human_readable, + sizeof(dump_info->fw_human_readable)); + strscpy_pad(dump_info->dev_human_readable, + fwrt->trans->info.name, + sizeof(dump_info->dev_human_readable)); + strscpy_pad(dump_info->bus_human_readable, fwrt->dev->bus->name, + sizeof(dump_info->bus_human_readable)); + dump_info->num_of_lmacs = fwrt->smem_cfg.num_lmacs; + dump_info->lmac_err_id[0] = + cpu_to_le32(fwrt->dump.lmac_err_id[0]); + if (fwrt->smem_cfg.num_lmacs > 1) + dump_info->lmac_err_id[1] = + cpu_to_le32(fwrt->dump.lmac_err_id[1]); + dump_info->umac_err_id = cpu_to_le32(fwrt->dump.umac_err_id); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) { + /* Dump shared memory configuration */ + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM_CFG); + dump_data->len = cpu_to_le32(sizeof(*dump_smem_cfg)); + dump_smem_cfg = (void *)dump_data->data; + dump_smem_cfg->num_lmacs = cpu_to_le32(mem_cfg->num_lmacs); + dump_smem_cfg->num_txfifo_entries = + cpu_to_le32(mem_cfg->num_txfifo_entries); + for (i = 0; i < MAX_NUM_LMAC; i++) { + int j; + u32 *txf_size = mem_cfg->lmac[i].txfifo_size; + + for (j = 0; j < TX_FIFO_MAX_NUM; j++) + dump_smem_cfg->lmac[i].txfifo_size[j] = + cpu_to_le32(txf_size[j]); + dump_smem_cfg->lmac[i].rxfifo1_size = + cpu_to_le32(mem_cfg->lmac[i].rxfifo1_size); + } + dump_smem_cfg->rxfifo2_size = + cpu_to_le32(mem_cfg->rxfifo2_size); + dump_smem_cfg->internal_txfifo_addr = + cpu_to_le32(mem_cfg->internal_txfifo_addr); + for (i = 0; i < TX_FIFO_INTERNAL_MAX_NUM; i++) { + dump_smem_cfg->internal_txfifo_size[i] = + cpu_to_le32(mem_cfg->internal_txfifo_size[i]); + } + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* We only dump the FIFOs if the FW is in error state */ + if (fifo_len) { + iwl_fw_dump_rxf(fwrt, &dump_data); + iwl_fw_dump_txf(fwrt, &dump_data); + } + + if (radio_len) + iwl_read_radio_regs(fwrt, &dump_data); + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && + data->desc) { + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_ERROR_INFO); + dump_data->len = cpu_to_le32(sizeof(*dump_trig) + + data->desc->len); + dump_trig = (void *)dump_data->data; + memcpy(dump_trig, &data->desc->trig_desc, + sizeof(*dump_trig) + data->desc->len); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* In case we only want monitor dump, skip to dump transport data */ + if (data->monitor_only) + goto out; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { + const struct iwl_fw_dbg_mem_seg_tlv *fw_dbg_mem = + fwrt->fw->dbg.mem_tlv; + + if (!fwrt->fw->dbg.n_mem_tlv) + iwl_fw_dump_mem(fwrt, &dump_data, sram_len, sram_ofs, + IWL_FW_ERROR_DUMP_MEM_SRAM); + + for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) { + u32 len = le32_to_cpu(fw_dbg_mem[i].len); + u32 ofs = le32_to_cpu(fw_dbg_mem[i].ofs); + + iwl_fw_dump_mem(fwrt, &dump_data, len, ofs, + le32_to_cpu(fw_dbg_mem[i].data_type)); + } + + iwl_fw_dump_mem(fwrt, &dump_data, smem_len, + fwrt->trans->mac_cfg->base->smem_offset, + IWL_FW_ERROR_DUMP_MEM_SMEM); + + iwl_fw_dump_mem(fwrt, &dump_data, sram2_len, + fwrt->trans->cfg->dccm2_offset, + IWL_FW_ERROR_DUMP_MEM_SRAM); + } + + if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { + u32 addr = fwrt->trans->mac_cfg->base->d3_debug_data_base_addr; + size_t data_size = fwrt->trans->mac_cfg->base->d3_debug_data_length; + + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_D3_DEBUG_DATA); + dump_data->len = cpu_to_le32(data_size * 2); + + memcpy(dump_data->data, fwrt->dump.d3_debug_data, data_size); + + kfree(fwrt->dump.d3_debug_data); + fwrt->dump.d3_debug_data = NULL; + + iwl_trans_read_mem_bytes(fwrt->trans, addr, + dump_data->data + data_size, + data_size); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, addr, + dump_data->data + data_size, + data_size); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* Dump fw's virtual image */ + if (iwl_fw_dbg_is_paging_enabled(fwrt)) + iwl_dump_paging(fwrt, &dump_data); + + if (prph_len) + iwl_fw_prph_handler(fwrt, &dump_data, iwl_dump_prph); + +out: + dump_file->file_len = cpu_to_le32(file_len); + return dump_file; +} + +void iwl_fw_error_dump(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data) +{ + struct iwl_fw_dump_ptrs fw_error_dump = {}; + struct iwl_fw_error_dump_file *dump_file; + struct scatterlist *sg_dump_data; + u32 file_len; + u32 dump_mask = fwrt->fw->dbg.dump_mask; + + dump_file = iwl_fw_error_dump_file(fwrt, &fw_error_dump, dump_data); + if (!dump_file) + return; + + if (dump_data->monitor_only) + dump_mask &= BIT(IWL_FW_ERROR_DUMP_FW_MONITOR); + + fw_error_dump.trans_ptr = iwl_trans_dump_data(fwrt->trans, dump_mask, + fwrt->sanitize_ops, + fwrt->sanitize_ctx); + file_len = le32_to_cpu(dump_file->file_len); + fw_error_dump.fwrt_len = file_len; + + if (fw_error_dump.trans_ptr) { + file_len += fw_error_dump.trans_ptr->len; + dump_file->file_len = cpu_to_le32(file_len); + } + + sg_dump_data = iwl_fw_dbg_alloc_sgtable(file_len); + if (sg_dump_data) { + sg_pcopy_from_buffer(sg_dump_data, + sg_nents(sg_dump_data), + fw_error_dump.fwrt_ptr, + fw_error_dump.fwrt_len, 0); + if (fw_error_dump.trans_ptr) + sg_pcopy_from_buffer(sg_dump_data, + sg_nents(sg_dump_data), + fw_error_dump.trans_ptr->data, + fw_error_dump.trans_ptr->len, + fw_error_dump.fwrt_len); + dev_coredumpsg(fwrt->trans->dev, sg_dump_data, file_len, + GFP_KERNEL); + } + vfree(fw_error_dump.fwrt_ptr); + vfree(fw_error_dump.trans_ptr); +} diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c index 18667de4915f..64ead8ecd52c 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c @@ -13,556 +13,13 @@ #include "iwl-prph.h" #include "iwl-csr.h" #include "iwl-fh.h" -/** - * struct iwl_fw_dump_ptrs - set of pointers needed for the fw-error-dump - * - * @fwrt_ptr: pointer to the buffer coming from fwrt - * @trans_ptr: pointer to struct %iwl_trans_dump_data which contains the - * transport's data. - * @fwrt_len: length of the valid data in fwrt_ptr - */ -struct iwl_fw_dump_ptrs { - struct iwl_trans_dump_data *trans_ptr; - void *fwrt_ptr; - u32 fwrt_len; -}; - -#define RADIO_REG_MAX_READ 0x2ad -static void iwl_read_radio_regs(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data) -{ - u8 *pos = (void *)(*dump_data)->data; - int i; - - IWL_DEBUG_INFO(fwrt, "WRT radio registers dump\n"); - - if (!iwl_trans_grab_nic_access(fwrt->trans)) - return; - - (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RADIO_REG); - (*dump_data)->len = cpu_to_le32(RADIO_REG_MAX_READ); - - for (i = 0; i < RADIO_REG_MAX_READ; i++) { - u32 rd_cmd = RADIO_RSP_RD_CMD; - - rd_cmd |= i << RADIO_RSP_ADDR_POS; - iwl_trans_write_prph(fwrt->trans, RSP_RADIO_CMD, rd_cmd); - *pos = (u8)iwl_trans_read_prph(fwrt->trans, RSP_RADIO_RDDAT); - - pos++; - } - - *dump_data = iwl_fw_error_next_data(*dump_data); - - iwl_trans_release_nic_access(fwrt->trans); -} - -static void iwl_fwrt_dump_rxf(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data, - int size, u32 offset, int fifo_num) -{ - struct iwl_fw_error_dump_fifo *fifo_hdr; - u32 *fifo_data; - u32 fifo_len; - int i; - - fifo_hdr = (void *)(*dump_data)->data; - fifo_data = (void *)fifo_hdr->data; - fifo_len = size; - - /* No need to try to read the data if the length is 0 */ - if (fifo_len == 0) - return; - - /* Add a TLV for the RXF */ - (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RXF); - (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); - - fifo_hdr->fifo_num = cpu_to_le32(fifo_num); - fifo_hdr->available_bytes = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - RXF_RD_D_SPACE + offset)); - fifo_hdr->wr_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - RXF_RD_WR_PTR + offset)); - fifo_hdr->rd_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - RXF_RD_RD_PTR + offset)); - fifo_hdr->fence_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - RXF_RD_FENCE_PTR + offset)); - fifo_hdr->fence_mode = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - RXF_SET_FENCE_MODE + offset)); - - /* Lock fence */ - iwl_trans_write_prph(fwrt->trans, RXF_SET_FENCE_MODE + offset, 0x1); - /* Set fence pointer to the same place like WR pointer */ - iwl_trans_write_prph(fwrt->trans, RXF_LD_WR2FENCE + offset, 0x1); - /* Set fence offset */ - iwl_trans_write_prph(fwrt->trans, - RXF_LD_FENCE_OFFSET_ADDR + offset, 0x0); - - /* Read FIFO */ - fifo_len /= sizeof(u32); /* Size in DWORDS */ - for (i = 0; i < fifo_len; i++) - fifo_data[i] = iwl_trans_read_prph(fwrt->trans, - RXF_FIFO_RD_FENCE_INC + - offset); - *dump_data = iwl_fw_error_next_data(*dump_data); -} - -static void iwl_fwrt_dump_txf(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data, - int size, u32 offset, int fifo_num) -{ - struct iwl_fw_error_dump_fifo *fifo_hdr; - u32 *fifo_data; - u32 fifo_len; - int i; - - fifo_hdr = (void *)(*dump_data)->data; - fifo_data = (void *)fifo_hdr->data; - fifo_len = size; - - /* No need to try to read the data if the length is 0 */ - if (fifo_len == 0) - return; - - /* Add a TLV for the FIFO */ - (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_TXF); - (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); - - fifo_hdr->fifo_num = cpu_to_le32(fifo_num); - fifo_hdr->available_bytes = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_FIFO_ITEM_CNT + offset)); - fifo_hdr->wr_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_WR_PTR + offset)); - fifo_hdr->rd_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_RD_PTR + offset)); - fifo_hdr->fence_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_FENCE_PTR + offset)); - fifo_hdr->fence_mode = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_LOCK_FENCE + offset)); - - /* Set the TXF_READ_MODIFY_ADDR to TXF_WR_PTR */ - iwl_trans_write_prph(fwrt->trans, TXF_READ_MODIFY_ADDR + offset, - TXF_WR_PTR + offset); - - /* Dummy-read to advance the read pointer to the head */ - iwl_trans_read_prph(fwrt->trans, TXF_READ_MODIFY_DATA + offset); - - /* Read FIFO */ - for (i = 0; i < fifo_len / sizeof(u32); i++) - fifo_data[i] = iwl_trans_read_prph(fwrt->trans, - TXF_READ_MODIFY_DATA + - offset); - - if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) - fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, - fifo_data, fifo_len); - - *dump_data = iwl_fw_error_next_data(*dump_data); -} - -static void iwl_fw_dump_rxf(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data) -{ - struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; - - IWL_DEBUG_INFO(fwrt, "WRT RX FIFO dump\n"); - - if (!iwl_trans_grab_nic_access(fwrt->trans)) - return; - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) { - /* Pull RXF1 */ - iwl_fwrt_dump_rxf(fwrt, dump_data, - cfg->lmac[0].rxfifo1_size, 0, 0); - /* Pull RXF2 */ - iwl_fwrt_dump_rxf(fwrt, dump_data, cfg->rxfifo2_size, - RXF_DIFF_FROM_PREV + - fwrt->trans->mac_cfg->umac_prph_offset, 1); - /* Pull LMAC2 RXF1 */ - if (fwrt->smem_cfg.num_lmacs > 1) - iwl_fwrt_dump_rxf(fwrt, dump_data, - cfg->lmac[1].rxfifo1_size, - LMAC2_PRPH_OFFSET, 2); - } - - iwl_trans_release_nic_access(fwrt->trans); -} - -static void iwl_fw_dump_txf(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data) -{ - struct iwl_fw_error_dump_fifo *fifo_hdr; - struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; - u32 *fifo_data; - u32 fifo_len; - int i, j; - - IWL_DEBUG_INFO(fwrt, "WRT TX FIFO dump\n"); - - if (!iwl_trans_grab_nic_access(fwrt->trans)) - return; - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) { - /* Pull TXF data from LMAC1 */ - for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; i++) { - /* Mark the number of TXF we're pulling now */ - iwl_trans_write_prph(fwrt->trans, TXF_LARC_NUM, i); - iwl_fwrt_dump_txf(fwrt, dump_data, - cfg->lmac[0].txfifo_size[i], 0, i); - } - - /* Pull TXF data from LMAC2 */ - if (fwrt->smem_cfg.num_lmacs > 1) { - for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; - i++) { - /* Mark the number of TXF we're pulling now */ - iwl_trans_write_prph(fwrt->trans, - TXF_LARC_NUM + - LMAC2_PRPH_OFFSET, i); - iwl_fwrt_dump_txf(fwrt, dump_data, - cfg->lmac[1].txfifo_size[i], - LMAC2_PRPH_OFFSET, - i + cfg->num_txfifo_entries); - } - } - } - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && - fw_has_capa(&fwrt->fw->ucode_capa, - IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) { - /* Pull UMAC internal TXF data from all TXFs */ - for (i = 0; - i < ARRAY_SIZE(fwrt->smem_cfg.internal_txfifo_size); - i++) { - fifo_hdr = (void *)(*dump_data)->data; - fifo_data = (void *)fifo_hdr->data; - fifo_len = fwrt->smem_cfg.internal_txfifo_size[i]; - - /* No need to try to read the data if the length is 0 */ - if (fifo_len == 0) - continue; - - /* Add a TLV for the internal FIFOs */ - (*dump_data)->type = - cpu_to_le32(IWL_FW_ERROR_DUMP_INTERNAL_TXF); - (*dump_data)->len = - cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); - - fifo_hdr->fifo_num = cpu_to_le32(i); - - /* Mark the number of TXF we're pulling now */ - iwl_trans_write_prph(fwrt->trans, TXF_CPU2_NUM, i + - fwrt->smem_cfg.num_txfifo_entries); - - fifo_hdr->available_bytes = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_FIFO_ITEM_CNT)); - fifo_hdr->wr_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_WR_PTR)); - fifo_hdr->rd_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_RD_PTR)); - fifo_hdr->fence_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_FENCE_PTR)); - fifo_hdr->fence_mode = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_LOCK_FENCE)); - - /* Set TXF_CPU2_READ_MODIFY_ADDR to TXF_CPU2_WR_PTR */ - iwl_trans_write_prph(fwrt->trans, - TXF_CPU2_READ_MODIFY_ADDR, - TXF_CPU2_WR_PTR); - - /* Dummy-read to advance the read pointer to head */ - iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_READ_MODIFY_DATA); - - /* Read FIFO */ - fifo_len /= sizeof(u32); /* Size in DWORDS */ - for (j = 0; j < fifo_len; j++) - fifo_data[j] = - iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_READ_MODIFY_DATA); - *dump_data = iwl_fw_error_next_data(*dump_data); - } - } - - iwl_trans_release_nic_access(fwrt->trans); -} - -struct iwl_prph_range { - u32 start, end; -}; - -static const struct iwl_prph_range iwl_prph_dump_addr_comm[] = { - { .start = 0x00a00000, .end = 0x00a00000 }, - { .start = 0x00a0000c, .end = 0x00a00024 }, - { .start = 0x00a0002c, .end = 0x00a0003c }, - { .start = 0x00a00410, .end = 0x00a00418 }, - { .start = 0x00a00420, .end = 0x00a00420 }, - { .start = 0x00a00428, .end = 0x00a00428 }, - { .start = 0x00a00430, .end = 0x00a0043c }, - { .start = 0x00a00444, .end = 0x00a00444 }, - { .start = 0x00a004c0, .end = 0x00a004cc }, - { .start = 0x00a004d8, .end = 0x00a004d8 }, - { .start = 0x00a004e0, .end = 0x00a004f0 }, - { .start = 0x00a00840, .end = 0x00a00840 }, - { .start = 0x00a00850, .end = 0x00a00858 }, - { .start = 0x00a01004, .end = 0x00a01008 }, - { .start = 0x00a01010, .end = 0x00a01010 }, - { .start = 0x00a01018, .end = 0x00a01018 }, - { .start = 0x00a01024, .end = 0x00a01024 }, - { .start = 0x00a0102c, .end = 0x00a01034 }, - { .start = 0x00a0103c, .end = 0x00a01040 }, - { .start = 0x00a01048, .end = 0x00a01094 }, - { .start = 0x00a01c00, .end = 0x00a01c20 }, - { .start = 0x00a01c58, .end = 0x00a01c58 }, - { .start = 0x00a01c7c, .end = 0x00a01c7c }, - { .start = 0x00a01c28, .end = 0x00a01c54 }, - { .start = 0x00a01c5c, .end = 0x00a01c5c }, - { .start = 0x00a01c60, .end = 0x00a01cdc }, - { .start = 0x00a01ce0, .end = 0x00a01d0c }, - { .start = 0x00a01d18, .end = 0x00a01d20 }, - { .start = 0x00a01d2c, .end = 0x00a01d30 }, - { .start = 0x00a01d40, .end = 0x00a01d5c }, - { .start = 0x00a01d80, .end = 0x00a01d80 }, - { .start = 0x00a01d98, .end = 0x00a01d9c }, - { .start = 0x00a01da8, .end = 0x00a01da8 }, - { .start = 0x00a01db8, .end = 0x00a01df4 }, - { .start = 0x00a01dc0, .end = 0x00a01dfc }, - { .start = 0x00a01e00, .end = 0x00a01e2c }, - { .start = 0x00a01e40, .end = 0x00a01e60 }, - { .start = 0x00a01e68, .end = 0x00a01e6c }, - { .start = 0x00a01e74, .end = 0x00a01e74 }, - { .start = 0x00a01e84, .end = 0x00a01e90 }, - { .start = 0x00a01e9c, .end = 0x00a01ec4 }, - { .start = 0x00a01ed0, .end = 0x00a01ee0 }, - { .start = 0x00a01f00, .end = 0x00a01f1c }, - { .start = 0x00a01f44, .end = 0x00a01ffc }, - { .start = 0x00a02000, .end = 0x00a02048 }, - { .start = 0x00a02068, .end = 0x00a020f0 }, - { .start = 0x00a02100, .end = 0x00a02118 }, - { .start = 0x00a02140, .end = 0x00a0214c }, - { .start = 0x00a02168, .end = 0x00a0218c }, - { .start = 0x00a021c0, .end = 0x00a021c0 }, - { .start = 0x00a02400, .end = 0x00a02410 }, - { .start = 0x00a02418, .end = 0x00a02420 }, - { .start = 0x00a02428, .end = 0x00a0242c }, - { .start = 0x00a02434, .end = 0x00a02434 }, - { .start = 0x00a02440, .end = 0x00a02460 }, - { .start = 0x00a02468, .end = 0x00a024b0 }, - { .start = 0x00a024c8, .end = 0x00a024cc }, - { .start = 0x00a02500, .end = 0x00a02504 }, - { .start = 0x00a0250c, .end = 0x00a02510 }, - { .start = 0x00a02540, .end = 0x00a02554 }, - { .start = 0x00a02580, .end = 0x00a025f4 }, - { .start = 0x00a02600, .end = 0x00a0260c }, - { .start = 0x00a02648, .end = 0x00a02650 }, - { .start = 0x00a02680, .end = 0x00a02680 }, - { .start = 0x00a026c0, .end = 0x00a026d0 }, - { .start = 0x00a02700, .end = 0x00a0270c }, - { .start = 0x00a02804, .end = 0x00a02804 }, - { .start = 0x00a02818, .end = 0x00a0281c }, - { .start = 0x00a02c00, .end = 0x00a02db4 }, - { .start = 0x00a02df4, .end = 0x00a02fb0 }, - { .start = 0x00a03000, .end = 0x00a03014 }, - { .start = 0x00a0301c, .end = 0x00a0302c }, - { .start = 0x00a03034, .end = 0x00a03038 }, - { .start = 0x00a03040, .end = 0x00a03048 }, - { .start = 0x00a03060, .end = 0x00a03068 }, - { .start = 0x00a03070, .end = 0x00a03074 }, - { .start = 0x00a0307c, .end = 0x00a0307c }, - { .start = 0x00a03080, .end = 0x00a03084 }, - { .start = 0x00a0308c, .end = 0x00a03090 }, - { .start = 0x00a03098, .end = 0x00a03098 }, - { .start = 0x00a030a0, .end = 0x00a030a0 }, - { .start = 0x00a030a8, .end = 0x00a030b4 }, - { .start = 0x00a030bc, .end = 0x00a030bc }, - { .start = 0x00a030c0, .end = 0x00a0312c }, - { .start = 0x00a03c00, .end = 0x00a03c5c }, - { .start = 0x00a04400, .end = 0x00a04454 }, - { .start = 0x00a04460, .end = 0x00a04474 }, - { .start = 0x00a044c0, .end = 0x00a044ec }, - { .start = 0x00a04500, .end = 0x00a04504 }, - { .start = 0x00a04510, .end = 0x00a04538 }, - { .start = 0x00a04540, .end = 0x00a04548 }, - { .start = 0x00a04560, .end = 0x00a0457c }, - { .start = 0x00a04590, .end = 0x00a04598 }, - { .start = 0x00a045c0, .end = 0x00a045f4 }, -}; - -static const struct iwl_prph_range iwl_prph_dump_addr_9000[] = { - { .start = 0x00a05c00, .end = 0x00a05c18 }, - { .start = 0x00a05400, .end = 0x00a056e8 }, - { .start = 0x00a08000, .end = 0x00a098bc }, - { .start = 0x00a02400, .end = 0x00a02758 }, - { .start = 0x00a04764, .end = 0x00a0476c }, - { .start = 0x00a04770, .end = 0x00a04774 }, - { .start = 0x00a04620, .end = 0x00a04624 }, -}; - -static const struct iwl_prph_range iwl_prph_dump_addr_22000[] = { - { .start = 0x00a00000, .end = 0x00a00000 }, - { .start = 0x00a0000c, .end = 0x00a00024 }, - { .start = 0x00a0002c, .end = 0x00a00034 }, - { .start = 0x00a0003c, .end = 0x00a0003c }, - { .start = 0x00a00410, .end = 0x00a00418 }, - { .start = 0x00a00420, .end = 0x00a00420 }, - { .start = 0x00a00428, .end = 0x00a00428 }, - { .start = 0x00a00430, .end = 0x00a0043c }, - { .start = 0x00a00444, .end = 0x00a00444 }, - { .start = 0x00a00840, .end = 0x00a00840 }, - { .start = 0x00a00850, .end = 0x00a00858 }, - { .start = 0x00a01004, .end = 0x00a01008 }, - { .start = 0x00a01010, .end = 0x00a01010 }, - { .start = 0x00a01018, .end = 0x00a01018 }, - { .start = 0x00a01024, .end = 0x00a01024 }, - { .start = 0x00a0102c, .end = 0x00a01034 }, - { .start = 0x00a0103c, .end = 0x00a01040 }, - { .start = 0x00a01048, .end = 0x00a01050 }, - { .start = 0x00a01058, .end = 0x00a01058 }, - { .start = 0x00a01060, .end = 0x00a01070 }, - { .start = 0x00a0108c, .end = 0x00a0108c }, - { .start = 0x00a01c20, .end = 0x00a01c28 }, - { .start = 0x00a01d10, .end = 0x00a01d10 }, - { .start = 0x00a01e28, .end = 0x00a01e2c }, - { .start = 0x00a01e60, .end = 0x00a01e60 }, - { .start = 0x00a01e80, .end = 0x00a01e80 }, - { .start = 0x00a01ea0, .end = 0x00a01ea0 }, - { .start = 0x00a02000, .end = 0x00a0201c }, - { .start = 0x00a02024, .end = 0x00a02024 }, - { .start = 0x00a02040, .end = 0x00a02048 }, - { .start = 0x00a020c0, .end = 0x00a020e0 }, - { .start = 0x00a02400, .end = 0x00a02404 }, - { .start = 0x00a0240c, .end = 0x00a02414 }, - { .start = 0x00a0241c, .end = 0x00a0243c }, - { .start = 0x00a02448, .end = 0x00a024bc }, - { .start = 0x00a024c4, .end = 0x00a024cc }, - { .start = 0x00a02508, .end = 0x00a02508 }, - { .start = 0x00a02510, .end = 0x00a02514 }, - { .start = 0x00a0251c, .end = 0x00a0251c }, - { .start = 0x00a0252c, .end = 0x00a0255c }, - { .start = 0x00a02564, .end = 0x00a025a0 }, - { .start = 0x00a025a8, .end = 0x00a025b4 }, - { .start = 0x00a025c0, .end = 0x00a025c0 }, - { .start = 0x00a025e8, .end = 0x00a025f4 }, - { .start = 0x00a02c08, .end = 0x00a02c18 }, - { .start = 0x00a02c2c, .end = 0x00a02c38 }, - { .start = 0x00a02c68, .end = 0x00a02c78 }, - { .start = 0x00a03000, .end = 0x00a03000 }, - { .start = 0x00a03010, .end = 0x00a03014 }, - { .start = 0x00a0301c, .end = 0x00a0302c }, - { .start = 0x00a03034, .end = 0x00a03038 }, - { .start = 0x00a03040, .end = 0x00a03044 }, - { .start = 0x00a03060, .end = 0x00a03068 }, - { .start = 0x00a03070, .end = 0x00a03070 }, - { .start = 0x00a0307c, .end = 0x00a03084 }, - { .start = 0x00a0308c, .end = 0x00a03090 }, - { .start = 0x00a03098, .end = 0x00a03098 }, - { .start = 0x00a030a0, .end = 0x00a030a0 }, - { .start = 0x00a030a8, .end = 0x00a030b4 }, - { .start = 0x00a030bc, .end = 0x00a030c0 }, - { .start = 0x00a030c8, .end = 0x00a030f4 }, - { .start = 0x00a03100, .end = 0x00a0312c }, - { .start = 0x00a03c00, .end = 0x00a03c5c }, - { .start = 0x00a04400, .end = 0x00a04454 }, - { .start = 0x00a04460, .end = 0x00a04474 }, - { .start = 0x00a044c0, .end = 0x00a044ec }, - { .start = 0x00a04500, .end = 0x00a04504 }, - { .start = 0x00a04510, .end = 0x00a04538 }, - { .start = 0x00a04540, .end = 0x00a04548 }, - { .start = 0x00a04560, .end = 0x00a04560 }, - { .start = 0x00a04570, .end = 0x00a0457c }, - { .start = 0x00a04590, .end = 0x00a04590 }, - { .start = 0x00a04598, .end = 0x00a04598 }, - { .start = 0x00a045c0, .end = 0x00a045f4 }, - { .start = 0x00a05c18, .end = 0x00a05c1c }, - { .start = 0x00a0c000, .end = 0x00a0c018 }, - { .start = 0x00a0c020, .end = 0x00a0c028 }, - { .start = 0x00a0c038, .end = 0x00a0c094 }, - { .start = 0x00a0c0c0, .end = 0x00a0c104 }, - { .start = 0x00a0c10c, .end = 0x00a0c118 }, - { .start = 0x00a0c150, .end = 0x00a0c174 }, - { .start = 0x00a0c17c, .end = 0x00a0c188 }, - { .start = 0x00a0c190, .end = 0x00a0c198 }, - { .start = 0x00a0c1a0, .end = 0x00a0c1a8 }, - { .start = 0x00a0c1b0, .end = 0x00a0c1b8 }, -}; - -static const struct iwl_prph_range iwl_prph_dump_addr_ax210[] = { - { .start = 0x00d03c00, .end = 0x00d03c64 }, - { .start = 0x00d05c18, .end = 0x00d05c1c }, - { .start = 0x00d0c000, .end = 0x00d0c174 }, -}; - -static void iwl_read_prph_block(struct iwl_trans *trans, u32 start, - u32 len_bytes, __le32 *data) -{ - u32 i; - - for (i = 0; i < len_bytes; i += 4) - *data++ = cpu_to_le32(iwl_trans_read_prph(trans, start + i)); -} - -static void iwl_dump_prph(struct iwl_fw_runtime *fwrt, - const struct iwl_prph_range *iwl_prph_dump_addr, - u32 range_len, void *ptr) -{ - struct iwl_fw_error_dump_prph *prph; - struct iwl_trans *trans = fwrt->trans; - struct iwl_fw_error_dump_data **data = - (struct iwl_fw_error_dump_data **)ptr; - u32 i; - - if (!data) - return; - - IWL_DEBUG_INFO(trans, "WRT PRPH dump\n"); - - if (!iwl_trans_grab_nic_access(trans)) - return; - - for (i = 0; i < range_len; i++) { - /* The range includes both boundaries */ - int num_bytes_in_chunk = iwl_prph_dump_addr[i].end - - iwl_prph_dump_addr[i].start + 4; - - (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PRPH); - (*data)->len = cpu_to_le32(sizeof(*prph) + - num_bytes_in_chunk); - prph = (void *)(*data)->data; - prph->prph_start = cpu_to_le32(iwl_prph_dump_addr[i].start); - - iwl_read_prph_block(trans, iwl_prph_dump_addr[i].start, - /* our range is inclusive, hence + 4 */ - iwl_prph_dump_addr[i].end - - iwl_prph_dump_addr[i].start + 4, - (void *)prph->data); - - *data = iwl_fw_error_next_data(*data); - } - - iwl_trans_release_nic_access(trans); -} /* - * alloc_sgtable - allocates (chained) scatterlist in the given size, + * iwl_fw_dbg_alloc_sgtable - allocates (chained) scatterlist in the given size, * fills it with pages and returns it * @size: the size (in bytes) of the table */ -static struct scatterlist *alloc_sgtable(ssize_t size) +struct scatterlist *iwl_fw_dbg_alloc_sgtable(ssize_t size) { struct scatterlist *result = NULL, *prev; int nents, i, n_prev; @@ -625,423 +82,6 @@ static struct scatterlist *alloc_sgtable(ssize_t size) return result; } -static void iwl_fw_get_prph_len(struct iwl_fw_runtime *fwrt, - const struct iwl_prph_range *iwl_prph_dump_addr, - u32 range_len, void *ptr) -{ - u32 *prph_len = (u32 *)ptr; - int i, num_bytes_in_chunk; - - if (!prph_len) - return; - - for (i = 0; i < range_len; i++) { - /* The range includes both boundaries */ - num_bytes_in_chunk = - iwl_prph_dump_addr[i].end - - iwl_prph_dump_addr[i].start + 4; - - *prph_len += sizeof(struct iwl_fw_error_dump_data) + - sizeof(struct iwl_fw_error_dump_prph) + - num_bytes_in_chunk; - } -} - -static void iwl_fw_prph_handler(struct iwl_fw_runtime *fwrt, void *ptr, - void (*handler)(struct iwl_fw_runtime *, - const struct iwl_prph_range *, - u32, void *)) -{ - u32 range_len; - - if (fwrt->trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { - range_len = ARRAY_SIZE(iwl_prph_dump_addr_ax210); - handler(fwrt, iwl_prph_dump_addr_ax210, range_len, ptr); - } else if (fwrt->trans->mac_cfg->device_family >= - IWL_DEVICE_FAMILY_22000) { - range_len = ARRAY_SIZE(iwl_prph_dump_addr_22000); - handler(fwrt, iwl_prph_dump_addr_22000, range_len, ptr); - } else { - range_len = ARRAY_SIZE(iwl_prph_dump_addr_comm); - handler(fwrt, iwl_prph_dump_addr_comm, range_len, ptr); - - if (fwrt->trans->mac_cfg->mq_rx_supported) { - range_len = ARRAY_SIZE(iwl_prph_dump_addr_9000); - handler(fwrt, iwl_prph_dump_addr_9000, range_len, ptr); - } - } -} - -static void iwl_fw_dump_mem(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data, - u32 len, u32 ofs, u32 type) -{ - struct iwl_fw_error_dump_mem *dump_mem; - - if (!len) - return; - - (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM); - (*dump_data)->len = cpu_to_le32(len + sizeof(*dump_mem)); - dump_mem = (void *)(*dump_data)->data; - dump_mem->type = cpu_to_le32(type); - dump_mem->offset = cpu_to_le32(ofs); - iwl_trans_read_mem_bytes(fwrt->trans, ofs, dump_mem->data, len); - *dump_data = iwl_fw_error_next_data(*dump_data); - - if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) - fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, ofs, - dump_mem->data, len); - - IWL_DEBUG_INFO(fwrt, "WRT memory dump. Type=%u\n", dump_mem->type); -} - -#define ADD_LEN(len, item_len, const_len) \ - do {size_t item = item_len; len += (!!item) * const_len + item; } \ - while (0) - -static int iwl_fw_rxf_len(struct iwl_fw_runtime *fwrt, - struct iwl_fwrt_shared_mem_cfg *mem_cfg) -{ - size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + - sizeof(struct iwl_fw_error_dump_fifo); - u32 fifo_len = 0; - int i; - - if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) - return 0; - - /* Count RXF2 size */ - ADD_LEN(fifo_len, mem_cfg->rxfifo2_size, hdr_len); - - /* Count RXF1 sizes */ - if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) - mem_cfg->num_lmacs = MAX_NUM_LMAC; - - for (i = 0; i < mem_cfg->num_lmacs; i++) - ADD_LEN(fifo_len, mem_cfg->lmac[i].rxfifo1_size, hdr_len); - - return fifo_len; -} - -static int iwl_fw_txf_len(struct iwl_fw_runtime *fwrt, - struct iwl_fwrt_shared_mem_cfg *mem_cfg) -{ - size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + - sizeof(struct iwl_fw_error_dump_fifo); - u32 fifo_len = 0; - int i; - - if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) - goto dump_internal_txf; - - /* Count TXF sizes */ - if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) - mem_cfg->num_lmacs = MAX_NUM_LMAC; - - for (i = 0; i < mem_cfg->num_lmacs; i++) { - int j; - - for (j = 0; j < mem_cfg->num_txfifo_entries; j++) - ADD_LEN(fifo_len, mem_cfg->lmac[i].txfifo_size[j], - hdr_len); - } - -dump_internal_txf: - if (!(iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && - fw_has_capa(&fwrt->fw->ucode_capa, - IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG))) - goto out; - - for (i = 0; i < ARRAY_SIZE(mem_cfg->internal_txfifo_size); i++) - ADD_LEN(fifo_len, mem_cfg->internal_txfifo_size[i], hdr_len); - -out: - return fifo_len; -} - -static void iwl_dump_paging(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **data) -{ - int i; - - IWL_DEBUG_INFO(fwrt, "WRT paging dump\n"); - for (i = 1; i < fwrt->num_of_paging_blk + 1; i++) { - struct iwl_fw_error_dump_paging *paging; - struct page *pages = - fwrt->fw_paging_db[i].fw_paging_block; - dma_addr_t addr = fwrt->fw_paging_db[i].fw_paging_phys; - - (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PAGING); - (*data)->len = cpu_to_le32(sizeof(*paging) + - PAGING_BLOCK_SIZE); - paging = (void *)(*data)->data; - paging->index = cpu_to_le32(i); - dma_sync_single_for_cpu(fwrt->trans->dev, addr, - PAGING_BLOCK_SIZE, - DMA_BIDIRECTIONAL); - memcpy(paging->data, page_address(pages), - PAGING_BLOCK_SIZE); - dma_sync_single_for_device(fwrt->trans->dev, addr, - PAGING_BLOCK_SIZE, - DMA_BIDIRECTIONAL); - (*data) = iwl_fw_error_next_data(*data); - - if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) - fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, - fwrt->fw_paging_db[i].fw_offs, - paging->data, - PAGING_BLOCK_SIZE); - } -} - -static struct iwl_fw_error_dump_file * -iwl_fw_error_dump_file(struct iwl_fw_runtime *fwrt, - struct iwl_fw_dump_ptrs *fw_error_dump, - struct iwl_fwrt_dump_data *data) -{ - struct iwl_fw_error_dump_file *dump_file; - struct iwl_fw_error_dump_data *dump_data; - struct iwl_fw_error_dump_info *dump_info; - struct iwl_fw_error_dump_smem_cfg *dump_smem_cfg; - struct iwl_fw_error_dump_trigger_desc *dump_trig; - u32 sram_len, sram_ofs; - const struct iwl_fw_dbg_mem_seg_tlv *fw_mem = fwrt->fw->dbg.mem_tlv; - struct iwl_fwrt_shared_mem_cfg *mem_cfg = &fwrt->smem_cfg; - u32 file_len, fifo_len = 0, prph_len = 0, radio_len = 0; - u32 smem_len = fwrt->fw->dbg.n_mem_tlv ? 0 : fwrt->trans->mac_cfg->base->smem_len; - u32 sram2_len = fwrt->fw->dbg.n_mem_tlv ? - 0 : fwrt->trans->cfg->dccm2_len; - int i; - - /* SRAM - include stack CCM if driver knows the values for it */ - if (!fwrt->trans->cfg->dccm_offset || - !fwrt->trans->cfg->dccm_len) { - const struct fw_img *img; - - if (fwrt->cur_fw_img >= IWL_UCODE_TYPE_MAX) - return NULL; - img = &fwrt->fw->img[fwrt->cur_fw_img]; - sram_ofs = img->sec[IWL_UCODE_SECTION_DATA].offset; - sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; - } else { - sram_ofs = fwrt->trans->cfg->dccm_offset; - sram_len = fwrt->trans->cfg->dccm_len; - } - - /* reading RXF/TXF sizes */ - if (iwl_trans_is_fw_error(fwrt->trans)) { - fifo_len = iwl_fw_rxf_len(fwrt, mem_cfg); - fifo_len += iwl_fw_txf_len(fwrt, mem_cfg); - - /* Make room for PRPH registers */ - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_PRPH)) - iwl_fw_prph_handler(fwrt, &prph_len, - iwl_fw_get_prph_len); - - if (fwrt->trans->mac_cfg->device_family == - IWL_DEVICE_FAMILY_7000 && - iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RADIO_REG)) - radio_len = sizeof(*dump_data) + RADIO_REG_MAX_READ; - } - - file_len = sizeof(*dump_file) + fifo_len + prph_len + radio_len; - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) - file_len += sizeof(*dump_data) + sizeof(*dump_info); - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) - file_len += sizeof(*dump_data) + sizeof(*dump_smem_cfg); - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { - size_t hdr_len = sizeof(*dump_data) + - sizeof(struct iwl_fw_error_dump_mem); - - /* Dump SRAM only if no mem_tlvs */ - if (!fwrt->fw->dbg.n_mem_tlv) - ADD_LEN(file_len, sram_len, hdr_len); - - /* Make room for all mem types that exist */ - ADD_LEN(file_len, smem_len, hdr_len); - ADD_LEN(file_len, sram2_len, hdr_len); - - for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) - ADD_LEN(file_len, le32_to_cpu(fw_mem[i].len), hdr_len); - } - - /* Make room for fw's virtual image pages, if it exists */ - if (iwl_fw_dbg_is_paging_enabled(fwrt)) - file_len += fwrt->num_of_paging_blk * - (sizeof(*dump_data) + - sizeof(struct iwl_fw_error_dump_paging) + - PAGING_BLOCK_SIZE); - - if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { - file_len += sizeof(*dump_data) + - fwrt->trans->mac_cfg->base->d3_debug_data_length * 2; - } - - /* If we only want a monitor dump, reset the file length */ - if (data->monitor_only) { - file_len = sizeof(*dump_file) + sizeof(*dump_data) * 2 + - sizeof(*dump_info) + sizeof(*dump_smem_cfg); - } - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && - data->desc) - file_len += sizeof(*dump_data) + sizeof(*dump_trig) + - data->desc->len; - - dump_file = vzalloc(file_len); - if (!dump_file) - return NULL; - - fw_error_dump->fwrt_ptr = dump_file; - - dump_file->barker = cpu_to_le32(IWL_FW_ERROR_DUMP_BARKER); - dump_data = (void *)dump_file->data; - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) { - dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_DEV_FW_INFO); - dump_data->len = cpu_to_le32(sizeof(*dump_info)); - dump_info = (void *)dump_data->data; - dump_info->hw_type = - cpu_to_le32(CSR_HW_REV_TYPE(fwrt->trans->info.hw_rev)); - dump_info->hw_step = - cpu_to_le32(fwrt->trans->info.hw_rev_step); - memcpy(dump_info->fw_human_readable, fwrt->fw->human_readable, - sizeof(dump_info->fw_human_readable)); - strscpy_pad(dump_info->dev_human_readable, - fwrt->trans->info.name, - sizeof(dump_info->dev_human_readable)); - strscpy_pad(dump_info->bus_human_readable, fwrt->dev->bus->name, - sizeof(dump_info->bus_human_readable)); - dump_info->num_of_lmacs = fwrt->smem_cfg.num_lmacs; - dump_info->lmac_err_id[0] = - cpu_to_le32(fwrt->dump.lmac_err_id[0]); - if (fwrt->smem_cfg.num_lmacs > 1) - dump_info->lmac_err_id[1] = - cpu_to_le32(fwrt->dump.lmac_err_id[1]); - dump_info->umac_err_id = cpu_to_le32(fwrt->dump.umac_err_id); - - dump_data = iwl_fw_error_next_data(dump_data); - } - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) { - /* Dump shared memory configuration */ - dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM_CFG); - dump_data->len = cpu_to_le32(sizeof(*dump_smem_cfg)); - dump_smem_cfg = (void *)dump_data->data; - dump_smem_cfg->num_lmacs = cpu_to_le32(mem_cfg->num_lmacs); - dump_smem_cfg->num_txfifo_entries = - cpu_to_le32(mem_cfg->num_txfifo_entries); - for (i = 0; i < MAX_NUM_LMAC; i++) { - int j; - u32 *txf_size = mem_cfg->lmac[i].txfifo_size; - - for (j = 0; j < TX_FIFO_MAX_NUM; j++) - dump_smem_cfg->lmac[i].txfifo_size[j] = - cpu_to_le32(txf_size[j]); - dump_smem_cfg->lmac[i].rxfifo1_size = - cpu_to_le32(mem_cfg->lmac[i].rxfifo1_size); - } - dump_smem_cfg->rxfifo2_size = - cpu_to_le32(mem_cfg->rxfifo2_size); - dump_smem_cfg->internal_txfifo_addr = - cpu_to_le32(mem_cfg->internal_txfifo_addr); - for (i = 0; i < TX_FIFO_INTERNAL_MAX_NUM; i++) { - dump_smem_cfg->internal_txfifo_size[i] = - cpu_to_le32(mem_cfg->internal_txfifo_size[i]); - } - - dump_data = iwl_fw_error_next_data(dump_data); - } - - /* We only dump the FIFOs if the FW is in error state */ - if (fifo_len) { - iwl_fw_dump_rxf(fwrt, &dump_data); - iwl_fw_dump_txf(fwrt, &dump_data); - } - - if (radio_len) - iwl_read_radio_regs(fwrt, &dump_data); - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && - data->desc) { - dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_ERROR_INFO); - dump_data->len = cpu_to_le32(sizeof(*dump_trig) + - data->desc->len); - dump_trig = (void *)dump_data->data; - memcpy(dump_trig, &data->desc->trig_desc, - sizeof(*dump_trig) + data->desc->len); - - dump_data = iwl_fw_error_next_data(dump_data); - } - - /* In case we only want monitor dump, skip to dump trasport data */ - if (data->monitor_only) - goto out; - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { - const struct iwl_fw_dbg_mem_seg_tlv *fw_dbg_mem = - fwrt->fw->dbg.mem_tlv; - - if (!fwrt->fw->dbg.n_mem_tlv) - iwl_fw_dump_mem(fwrt, &dump_data, sram_len, sram_ofs, - IWL_FW_ERROR_DUMP_MEM_SRAM); - - for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) { - u32 len = le32_to_cpu(fw_dbg_mem[i].len); - u32 ofs = le32_to_cpu(fw_dbg_mem[i].ofs); - - iwl_fw_dump_mem(fwrt, &dump_data, len, ofs, - le32_to_cpu(fw_dbg_mem[i].data_type)); - } - - iwl_fw_dump_mem(fwrt, &dump_data, smem_len, - fwrt->trans->mac_cfg->base->smem_offset, - IWL_FW_ERROR_DUMP_MEM_SMEM); - - iwl_fw_dump_mem(fwrt, &dump_data, sram2_len, - fwrt->trans->cfg->dccm2_offset, - IWL_FW_ERROR_DUMP_MEM_SRAM); - } - - if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { - u32 addr = fwrt->trans->mac_cfg->base->d3_debug_data_base_addr; - size_t data_size = fwrt->trans->mac_cfg->base->d3_debug_data_length; - - dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_D3_DEBUG_DATA); - dump_data->len = cpu_to_le32(data_size * 2); - - memcpy(dump_data->data, fwrt->dump.d3_debug_data, data_size); - - kfree(fwrt->dump.d3_debug_data); - fwrt->dump.d3_debug_data = NULL; - - iwl_trans_read_mem_bytes(fwrt->trans, addr, - dump_data->data + data_size, - data_size); - - if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) - fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, addr, - dump_data->data + data_size, - data_size); - - dump_data = iwl_fw_error_next_data(dump_data); - } - - /* Dump fw's virtual image */ - if (iwl_fw_dbg_is_paging_enabled(fwrt)) - iwl_dump_paging(fwrt, &dump_data); - - if (prph_len) - iwl_fw_prph_handler(fwrt, &dump_data, iwl_dump_prph); - -out: - dump_file->file_len = cpu_to_le32(file_len); - return dump_file; -} - /** * struct iwl_dump_ini_region_data - region data * @reg_tlv: region TLV @@ -2826,52 +1866,6 @@ static inline void iwl_fw_free_dump_desc(struct iwl_fw_runtime *fwrt, fwrt->dump.umac_err_id = 0; } -static void iwl_fw_error_dump(struct iwl_fw_runtime *fwrt, - struct iwl_fwrt_dump_data *dump_data) -{ - struct iwl_fw_dump_ptrs fw_error_dump = {}; - struct iwl_fw_error_dump_file *dump_file; - struct scatterlist *sg_dump_data; - u32 file_len; - u32 dump_mask = fwrt->fw->dbg.dump_mask; - - dump_file = iwl_fw_error_dump_file(fwrt, &fw_error_dump, dump_data); - if (!dump_file) - return; - - if (dump_data->monitor_only) - dump_mask &= BIT(IWL_FW_ERROR_DUMP_FW_MONITOR); - - fw_error_dump.trans_ptr = iwl_trans_dump_data(fwrt->trans, dump_mask, - fwrt->sanitize_ops, - fwrt->sanitize_ctx); - file_len = le32_to_cpu(dump_file->file_len); - fw_error_dump.fwrt_len = file_len; - - if (fw_error_dump.trans_ptr) { - file_len += fw_error_dump.trans_ptr->len; - dump_file->file_len = cpu_to_le32(file_len); - } - - sg_dump_data = alloc_sgtable(file_len); - if (sg_dump_data) { - sg_pcopy_from_buffer(sg_dump_data, - sg_nents(sg_dump_data), - fw_error_dump.fwrt_ptr, - fw_error_dump.fwrt_len, 0); - if (fw_error_dump.trans_ptr) - sg_pcopy_from_buffer(sg_dump_data, - sg_nents(sg_dump_data), - fw_error_dump.trans_ptr->data, - fw_error_dump.trans_ptr->len, - fw_error_dump.fwrt_len); - dev_coredumpsg(fwrt->trans->dev, sg_dump_data, file_len, - GFP_KERNEL); - } - vfree(fw_error_dump.fwrt_ptr); - vfree(fw_error_dump.trans_ptr); -} - static void iwl_dump_ini_list_free(struct list_head *list) { while (!list_empty(list)) { @@ -2900,7 +1894,7 @@ static void iwl_fw_error_ini_dump(struct iwl_fw_runtime *fwrt, if (!file_len) return; - sg_dump_data = alloc_sgtable(file_len); + sg_dump_data = iwl_fw_dbg_alloc_sgtable(file_len); if (sg_dump_data) { struct iwl_fw_ini_dump_entry *entry; int sg_entries = sg_nents(sg_dump_data); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg.h b/drivers/net/wireless/intel/iwlwifi/fw/dbg.h index 8034c9ecba69..fc962a320583 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/dbg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2005-2014, 2018-2019, 2021-2025 Intel Corporation + * Copyright (C) 2005-2014, 2018-2019, 2021-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH */ @@ -38,6 +38,11 @@ struct iwl_fw_dbg_params { u32 out_ctrl; }; +/* old-style dump entry point */ +void iwl_fw_error_dump(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data); +struct scatterlist *iwl_fw_dbg_alloc_sgtable(ssize_t size); + extern const struct iwl_fw_dump_desc iwl_dump_desc_assert; int iwl_fw_dbg_collect_desc(struct iwl_fw_runtime *fwrt, From 90b2f286bc7d4a5b7ed5f9892ec2d2a821c356fd Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 17 May 2026 10:59:58 +0300 Subject: [PATCH 130/143] wifi: iwlwifi: dbg: remove unused 'range_len' arg from dump None of the functions use this, and it's not really passed the length of the specific range anyway, but rather the entire remaining size. Remove the unused argument. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260517100550.ea3502f97c2a.I4ce08f0ea7ea3bacd4928b427c0710b77259d002@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/dbg.c | 45 ++++++++++----------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c index 64ead8ecd52c..069c3bad6f29 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c @@ -111,7 +111,7 @@ static int iwl_dump_ini_prph_mac_iter_common(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_prph_mac_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; u32 addr = le32_to_cpu(reg->addrs[idx]) + @@ -124,7 +124,7 @@ iwl_dump_ini_prph_mac_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_prph_mac_block_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_addr_size *pairs = (void *)reg->addrs; @@ -201,7 +201,7 @@ static int iwl_dump_ini_prph_phy_iter_common(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_prph_phy_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; u32 addr = le32_to_cpu(reg->addrs[idx]); @@ -214,7 +214,7 @@ iwl_dump_ini_prph_phy_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_prph_phy_block_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_addr_size *pairs = (void *)reg->addrs; @@ -227,7 +227,7 @@ iwl_dump_ini_prph_phy_block_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_csr_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -246,7 +246,7 @@ static int iwl_dump_ini_csr_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_config_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_trans *trans = fwrt->trans; struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; @@ -274,7 +274,7 @@ static int iwl_dump_ini_config_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_dev_mem_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -296,7 +296,7 @@ static int iwl_dump_ini_dev_mem_iter(struct iwl_fw_runtime *fwrt, } static int _iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct page *page = fwrt->fw_paging_db[idx].fw_paging_block; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -316,7 +316,7 @@ static int _iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_error_dump_range *range; u32 page_size; @@ -325,7 +325,7 @@ static int iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, idx++; if (!fwrt->trans->mac_cfg->gen2) - return _iwl_dump_ini_paging_iter(fwrt, range_ptr, range_len, idx); + return _iwl_dump_ini_paging_iter(fwrt, range_ptr, idx); range = range_ptr; page_size = fwrt->trans->init_dram.paging[idx].size; @@ -341,7 +341,7 @@ static int iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_mon_dram_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -360,7 +360,7 @@ iwl_dump_ini_mon_dram_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_mon_smem_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -428,7 +428,7 @@ static bool iwl_ini_txf_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_txf_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -497,7 +497,7 @@ static int iwl_dump_ini_txf_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_prph_snps_dphyip_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -610,7 +610,7 @@ static void iwl_ini_get_rxf_data(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_rxf_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -676,7 +676,7 @@ static int iwl_dump_ini_rxf_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_err_table_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_region_err_table *err_table = ®->err_table; @@ -695,7 +695,7 @@ iwl_dump_ini_err_table_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_special_mem_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_region_special_device_memory *special_mem = @@ -716,7 +716,7 @@ iwl_dump_ini_special_mem_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_dbgi_sram_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -746,7 +746,7 @@ iwl_dump_ini_dbgi_sram_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_fw_pkt_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_error_dump_range *range = range_ptr; struct iwl_rx_packet *pkt = reg_data->dump_data->fw_pkt; @@ -767,7 +767,7 @@ static int iwl_dump_ini_fw_pkt_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_imr_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { /* read the IMR memory and DMA it to SRAM */ struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -1277,7 +1277,7 @@ struct iwl_dump_ini_mem_ops { void *data, u32 data_len); int (*fill_range)(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range, u32 range_len, int idx); + void *range, int idx); }; struct iwl_fw_ini_dump_entry { @@ -1413,8 +1413,7 @@ static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, free_size -= header_size; for (i = 0; i < le32_to_cpu(header->num_of_ranges); i++) { - int range_size = ops->fill_range(fwrt, reg_data, range, - free_size, i); + int range_size = ops->fill_range(fwrt, reg_data, range, i); if (range_size < 0) { IWL_ERR(fwrt, From 4f479cbee68509ef56e4be310da93ec7883feb65 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 17 May 2026 10:59:59 +0300 Subject: [PATCH 131/143] wifi: iwlwifi: transport: add memory read under NIC access Add functions to be able to do memory read under NIC access, in order to use them later during firmware dump. These may drop and re-acquire the spinlock, but will not acquire and release the NIC access. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260517100550.7bb1ea51c347.I91420a24fb0c481c75a2600d60e1365c15c1c5a9@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/iwl-trans.c | 6 +++ .../net/wireless/intel/iwlwifi/iwl-trans.h | 16 ++++++ .../intel/iwlwifi/pcie/gen1_2/internal.h | 2 + .../intel/iwlwifi/pcie/gen1_2/trans.c | 54 +++++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c index 5b44e15fe64d..0009488ca51b 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c @@ -459,6 +459,12 @@ int iwl_trans_read_mem(struct iwl_trans *trans, u32 addr, } IWL_EXPORT_SYMBOL(iwl_trans_read_mem); +int iwl_trans_read_mem_no_grab(struct iwl_trans *trans, u32 addr, + void *buf, u32 dwords) +{ + return iwl_trans_pcie_read_mem_no_grab(trans, addr, buf, dwords); +} + int iwl_trans_write_mem(struct iwl_trans *trans, u32 addr, const void *buf, int dwords) { diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h index 1ed6bcb7882c..3ae840e546e8 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -919,6 +919,14 @@ void iwl_trans_write_prph(struct iwl_trans *trans, u32 ofs, u32 val); int iwl_trans_read_mem(struct iwl_trans *trans, u32 addr, void *buf, int dwords); +/* + * Note the special calling convention - it's allowed to drop the + * internal transport lock and re-enable BHs temporarily, but will + * not release NIC access. + */ +int iwl_trans_read_mem_no_grab(struct iwl_trans *trans, u32 addr, + void *buf, u32 dwords); + int iwl_trans_read_config32(struct iwl_trans *trans, u32 ofs, u32 *val); @@ -934,6 +942,14 @@ void iwl_trans_debugfs_cleanup(struct iwl_trans *trans); (bufsize) / sizeof(u32)); \ }) +static inline int +iwl_trans_read_mem_bytes_no_grab(struct iwl_trans *trans, + u32 addr, void *buf, u32 bufsize) +{ + return iwl_trans_read_mem_no_grab(trans, addr, buf, + bufsize / sizeof(u32)); +} + int iwl_trans_write_imr_mem(struct iwl_trans *trans, u32 dst_addr, u64 src_addr, u32 byte_cnt); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h index 24f8714eae9f..abc0c831d1ca 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h @@ -1186,6 +1186,8 @@ u32 iwl_trans_pcie_read_prph(struct iwl_trans *trans, u32 reg); void iwl_trans_pcie_write_prph(struct iwl_trans *trans, u32 addr, u32 val); int iwl_trans_pcie_read_mem(struct iwl_trans *trans, u32 addr, void *buf, int dwords); +int iwl_trans_pcie_read_mem_no_grab(struct iwl_trans *trans, u32 addr, + void *buf, u32 dwords); int iwl_trans_pcie_sw_reset(struct iwl_trans *trans, bool retake_ownership); struct iwl_trans_dump_data * iwl_trans_pcie_dump_data(struct iwl_trans *trans, u32 dump_mask, diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c index 50342604491d..1c4ee76d8387 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c @@ -2424,6 +2424,15 @@ bool iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans) return false; } +static void iwl_trans_pcie_resched_with_nic_access(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_unlock_bh(&trans_pcie->reg_lock); + cond_resched(); + spin_lock_bh(&trans_pcie->reg_lock); +} + void __releases(nic_access_nobh) iwl_trans_pcie_release_nic_access(struct iwl_trans *trans) { @@ -2506,6 +2515,51 @@ int iwl_trans_pcie_read_mem(struct iwl_trans *trans, u32 addr, return 0; } +int iwl_trans_pcie_read_mem_no_grab(struct iwl_trans *trans, u32 addr, + void *buf, u32 dwords) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); +#define IWL_MAX_HW_ERRS 5 + unsigned int num_consec_hw_errors = 0; + u32 offs = 0; + u32 *vals = buf; + + lockdep_assert_held(&trans_pcie->reg_lock); + + while (offs < dwords) { + /* limit the time we spin here under lock to 1/2s */ + unsigned long end = jiffies + HZ / 2; + bool resched = false; + + iwl_write32(trans, HBUS_TARG_MEM_RADDR, + addr + 4 * offs); + + while (offs < dwords) { + vals[offs] = iwl_read32(trans, HBUS_TARG_MEM_RDAT); + + if (iwl_trans_is_hw_error_value(vals[offs])) + num_consec_hw_errors++; + else + num_consec_hw_errors = 0; + + if (num_consec_hw_errors >= IWL_MAX_HW_ERRS) + return -EIO; + + offs++; + + if (time_after(jiffies, end)) { + resched = true; + break; + } + } + + if (resched) + iwl_trans_pcie_resched_with_nic_access(trans); + } + + return 0; +} + int iwl_trans_pcie_read_config32(struct iwl_trans *trans, u32 ofs, u32 *val) { From 455fac900cf93d03a020b82f2cf2849cd2c74fd8 Mon Sep 17 00:00:00 2001 From: Dongyang Jin Date: Tue, 19 May 2026 14:02:59 +0800 Subject: [PATCH 132/143] wifi: iwlwifi: mld: fix indentation in iwl_mld_fill_supp_rates() Fix the following inconsistent indentation warnings reported by smatch: smatch warnings: drivers/net/wireless/intel/iwlwifi/mld/tlc.c:454 iwl_mld_fill_supp_rates() warn: inconsistent indenting There's an extra tab, remove it. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202605170928.yPTN7kOn-lkp@intel.com/ Signed-off-by: Dongyang Jin Link: https://patch.msgid.link/20260519060259.2327712-1-jindongyang@kylinos.cn Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/tlc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c index fb68d8810e14..a03834d3ac65 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c @@ -451,7 +451,7 @@ iwl_mld_fill_supp_rates(struct iwl_mld *mld, 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(capa, cmd); + 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] = From d5c336161088c588f85da64f48ba6deead194afd Mon Sep 17 00:00:00 2001 From: Ripan Deuri Date: Wed, 20 May 2026 00:58:15 +0530 Subject: [PATCH 133/143] wifi: ath12k: fix error unwind on arch_init() failure in PCI probe When arch_init() fails in ath12k_pci_probe(), the code jumps to err_pci_msi_free, leaking resources in teardown. Redirect the failure path to err_free_irq so teardown matches the setup order. Compile-tested only. Fixes: 614c23e24ee8 ("wifi: ath12k: Support arch-specific DP device allocation") Signed-off-by: Ripan Deuri Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260519192815.3911324-1-ripan.deuri@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 375277ca2b89..d9a22d6afbb0 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -1639,7 +1639,7 @@ static int ath12k_pci_probe(struct pci_dev *pdev, ret = ab_pci->device_family_ops->arch_init(ab); if (ret) { ath12k_err(ab, "PCI arch_init failed %d\n", ret); - goto err_pci_msi_free; + goto err_free_irq; } ret = ath12k_core_init(ab); From f31ce7e3154487ec1957b10b620b396b25760b82 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Fri, 15 May 2026 11:17:19 -0400 Subject: [PATCH 134/143] wifi: mac80211: add KUnit coverage for negotiated TTLM parser Add KUnit coverage for ieee80211_parse_neg_ttlm() to lock the sparse link_map_presence layout against future regressions. The sparse_presence_no_oob_read case crafts a negotiated TTLM element with link_map_presence = BIT(0) | BIT(7) and bm_size = 2 in a buffer sized exactly to the validated element length. Without the parser fix this would read 14 bytes past the buffer when processing TID 7; under KASAN that is a slab-out-of-bounds report. The dense_presence_baseline case crafts a fully populated link_map_presence = 0xff element to confirm that the cursor-advance fix does not regress the path that was already correct. Export ieee80211_parse_neg_ttlm via VISIBLE_IF_MAC80211_KUNIT so the test can call it directly. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260515151719.1317659-3-michael.bommarito@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 4 + net/mac80211/mlme.c | 3 +- net/mac80211/tests/.kunitconfig | 4 + net/mac80211/tests/Makefile | 2 +- net/mac80211/tests/ttlm.c | 175 ++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 net/mac80211/tests/.kunitconfig create mode 100644 net/mac80211/tests/ttlm.c diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index fc4424b125c1..1360522649c2 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2926,6 +2926,10 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata, struct ieee80211_chan_req *chanreq, struct cfg80211_chan_def *ap_chandef, unsigned long *userspace_selectors); +int ieee80211_parse_neg_ttlm(struct ieee80211_sub_if_data *sdata, + const struct ieee80211_ttlm_elem *ttlm, + struct ieee80211_neg_ttlm *neg_ttlm, + u8 *direction); #else #define EXPORT_SYMBOL_IF_MAC80211_KUNIT(sym) #define VISIBLE_IF_MAC80211_KUNIT static diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index f60f59d1b37d..e8d6f6a95c0a 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -8189,7 +8189,7 @@ ieee80211_send_neg_ttlm_res(struct ieee80211_sub_if_data *sdata, ieee80211_tx_skb(sdata, skb); } -static int +VISIBLE_IF_MAC80211_KUNIT int ieee80211_parse_neg_ttlm(struct ieee80211_sub_if_data *sdata, const struct ieee80211_ttlm_elem *ttlm, struct ieee80211_neg_ttlm *neg_ttlm, @@ -8270,6 +8270,7 @@ ieee80211_parse_neg_ttlm(struct ieee80211_sub_if_data *sdata, } return 0; } +EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_parse_neg_ttlm); static void ieee80211_process_neg_ttlm_req(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, diff --git a/net/mac80211/tests/.kunitconfig b/net/mac80211/tests/.kunitconfig new file mode 100644 index 000000000000..ab2cc5cfc1f5 --- /dev/null +++ b/net/mac80211/tests/.kunitconfig @@ -0,0 +1,4 @@ +CONFIG_KUNIT=y +CONFIG_CFG80211=y +CONFIG_MAC80211=y +CONFIG_MAC80211_KUNIT_TEST=y diff --git a/net/mac80211/tests/Makefile b/net/mac80211/tests/Makefile index 3c7f874e5c41..2e9ade90f7b6 100644 --- a/net/mac80211/tests/Makefile +++ b/net/mac80211/tests/Makefile @@ -1,3 +1,3 @@ -mac80211-tests-y += module.o util.o elems.o mfp.o tpe.o chan-mode.o s1g_tim.o +mac80211-tests-y += module.o util.o elems.o mfp.o tpe.o chan-mode.o s1g_tim.o ttlm.o obj-$(CONFIG_MAC80211_KUNIT_TEST) += mac80211-tests.o diff --git a/net/mac80211/tests/ttlm.c b/net/mac80211/tests/ttlm.c new file mode 100644 index 000000000000..18d0592b13d9 --- /dev/null +++ b/net/mac80211/tests/ttlm.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * KUnit tests for negotiated TTLM (TID-To-Link Mapping) parsing + * + * Copyright (C) 2026 Michael Bommarito + */ +#include +#include +#include "../ieee80211_i.h" + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +/* + * Build a negotiated TTLM element in caller-supplied buffer. + * + * @buf: destination buffer (must be at least elem_size bytes) + * @elem_size: sizeof(ttlm_elem) + 1 (presence byte) + npresent * bm_size + * @presence: link_map_presence bitmask; each set bit => one map follows + * @bm_size: bytes per map (1 or 2); 2 => LINK_MAP_SIZE bit clear + * @maps: array of npresent u16 maps, one per set bit in presence + * + * Control field encodes direction=BOTH; no switch-time, no expected-dur, + * no DEF_LINK_MAP. LINK_MAP_SIZE bit is set iff bm_size==1. + * + * Returns pointer to the ieee80211_ttlm_elem at buf. + */ +static const struct ieee80211_ttlm_elem * +build_neg_ttlm_elem(u8 *buf, size_t elem_size, + u8 presence, u8 bm_size, const u16 *maps) +{ + struct ieee80211_ttlm_elem *t = (void *)buf; + u8 control; + u8 *pos; + int i, tid; + + memset(buf, 0, elem_size); + + control = IEEE80211_TTLM_DIRECTION_BOTH; /* bits [1:0] = 2 */ + if (bm_size == 1) + control |= IEEE80211_TTLM_CONTROL_LINK_MAP_SIZE; + + t->control = control; + + pos = (u8 *)t->optional; + *pos++ = presence; + + i = 0; + for (tid = 0; tid < IEEE80211_TTLM_NUM_TIDS; tid++) { + if (!(presence & BIT(tid))) + continue; + if (bm_size == 1) + *pos = (u8)maps[i]; + else + put_unaligned_le16(maps[i], pos); + pos += bm_size; + i++; + } + + return t; +} + +/* + * sparse_presence_no_oob_read - BIT(0)|BIT(7) presence, bm_size=2 + * + * Only TID 0 and TID 7 have maps; TIDs 1-6 are absent. Element length + * is exactly 6 bytes (1 control + 1 presence + 2 * 2-byte maps). + * + * Pre-fix the parser advanced pos by bm_size AFTER the switch() block + * (i.e. unconditionally for every TID), so when processing TID 7 it + * had already advanced 6 * bm_size = 12 bytes past the presence byte + * for the absent TIDs before reading the TID-7 map - 14 bytes past the + * end of the 2-byte TID-7 map. Under KASAN that is a slab-out-of-bounds. + * + * After the fix pos is advanced only inside the presence-bit branch so + * the cursor lands exactly at end-of-element after processing TID 7. + */ +static void sparse_presence_no_oob_read(struct kunit *test) +{ + /* + * presence = BIT(0)|BIT(7): 2 maps present. + * elem_size = sizeof(ttlm_elem) + 1 (presence) + 2*2 (maps) = 6. + */ + const u8 presence = BIT(0) | BIT(7); + const u8 bm_size = 2; + const int npresent = 2; + const size_t elem_size = sizeof(struct ieee80211_ttlm_elem) + + 1 + npresent * bm_size; + /* + * Allocate exact-size buffer so a pre-fix OOB read walks into the + * KASAN red zone immediately after the allocation. + */ + u8 *buf = kunit_kzalloc(test, elem_size, GFP_KERNEL); + const struct ieee80211_ttlm_elem *ttlm; + struct ieee80211_neg_ttlm neg_ttlm = {}; + /* Non-zero maps so the parser does not reject with -EINVAL. */ + const u16 maps[2] = { 0x0001, 0x0001 }; + u8 direction = 0; + int ret; + + KUNIT_ASSERT_NOT_NULL(test, buf); + + ttlm = build_neg_ttlm_elem(buf, elem_size, presence, bm_size, maps); + + /* + * Pass NULL for sdata: the only sdata dereference in this code path + * is inside mlme_dbg() on error returns, which are guarded by + * MAC80211_MLME_DEBUG == 0 in non-debug builds and by the dead-code + * eliminator in KUnit builds. The success path does not touch sdata. + */ + ret = ieee80211_parse_neg_ttlm(NULL, ttlm, &neg_ttlm, &direction); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)direction, IEEE80211_TTLM_DIRECTION_BOTH); + /* TID 0: map present */ + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.downlink[0], 0x0001); + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.uplink[0], 0x0001); + /* TID 3: absent => map should be 0 */ + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.downlink[3], 0); + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.uplink[3], 0); + /* TID 7: map present */ + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.downlink[7], 0x0001); + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.uplink[7], 0x0001); +} + +/* + * dense_presence_baseline - presence=0xff (all 8 TIDs), bm_size=2 + * + * Every TID has a map; this is the dense layout the parser handled + * correctly even before the fix. Confirms the cursor-advance fix + * does not regress the already-correct path. + */ +static void dense_presence_baseline(struct kunit *test) +{ + const u8 presence = 0xff; + const u8 bm_size = 2; + const int npresent = 8; + const size_t elem_size = sizeof(struct ieee80211_ttlm_elem) + + 1 + npresent * bm_size; + u8 *buf = kunit_kzalloc(test, elem_size, GFP_KERNEL); + const struct ieee80211_ttlm_elem *ttlm; + struct ieee80211_neg_ttlm neg_ttlm = {}; + const u16 maps[8] = { + 0x0003, 0x0003, 0x0003, 0x0003, + 0x0003, 0x0003, 0x0003, 0x0003, + }; + u8 direction = 0; + int ret; + + KUNIT_ASSERT_NOT_NULL(test, buf); + + ttlm = build_neg_ttlm_elem(buf, elem_size, presence, bm_size, maps); + + ret = ieee80211_parse_neg_ttlm(NULL, ttlm, &neg_ttlm, &direction); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)direction, IEEE80211_TTLM_DIRECTION_BOTH); + /* All TIDs present: every downlink/uplink entry must be 0x0003. */ + for (int tid = 0; tid < IEEE80211_TTLM_NUM_TIDS; tid++) { + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.downlink[tid], 0x0003); + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.uplink[tid], 0x0003); + } +} + +static struct kunit_case mac80211_ttlm_test_cases[] = { + KUNIT_CASE(sparse_presence_no_oob_read), + KUNIT_CASE(dense_presence_baseline), + {} +}; + +static struct kunit_suite mac80211_ttlm = { + .name = "mac80211-ttlm", + .test_cases = mac80211_ttlm_test_cases, +}; + +kunit_test_suite(mac80211_ttlm); From a1d4a1bc09f134156fe04db84c70258752790962 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 25 May 2026 09:56:36 +0300 Subject: [PATCH 135/143] wifi: mwifiex: remove an unnecessary check We know that "start_raw" is true so remove this check and pull the code in an tab. Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/ahPypOdz-KXh5KAJ@stanley.mountain Signed-off-by: Johannes Berg --- .../net/wireless/marvell/mwifiex/sta_cmd.c | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c index 70ce31d7c76e..623ddde8c8e5 100644 --- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c +++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c @@ -1568,28 +1568,23 @@ int mwifiex_send_rgpower_table(struct mwifiex_private *priv, const u8 *data, return -EINVAL; } - if (start_raw) { - while ((*pos != '\r' && *pos != '\n') && - (token = strsep((char **)&pos, " "))) { - if (ptr - hostcmd->cmd >= - MWIFIEX_SIZE_OF_CMD_BUFFER) { - mwifiex_dbg( - adapter, ERROR, - "%s: hostcmd is larger than %d, aborting\n", - __func__, MWIFIEX_SIZE_OF_CMD_BUFFER); - return -ENOMEM; - } - - ret = kstrtou8(token, 16, ptr); - if (ret < 0) { - mwifiex_dbg( - adapter, ERROR, - "%s: failed to parse hostcmd %d token: %s\n", - __func__, ret, token); - return ret; - } - ptr++; + while ((*pos != '\r' && *pos != '\n') && + (token = strsep((char **)&pos, " "))) { + if (ptr - hostcmd->cmd >= MWIFIEX_SIZE_OF_CMD_BUFFER) { + mwifiex_dbg(adapter, ERROR, + "%s: hostcmd is larger than %d, aborting\n", + __func__, MWIFIEX_SIZE_OF_CMD_BUFFER); + return -ENOMEM; } + + ret = kstrtou8(token, 16, ptr); + if (ret < 0) { + mwifiex_dbg(adapter, ERROR, + "%s: failed to parse hostcmd %d token: %s\n", + __func__, ret, token); + return ret; + } + ptr++; } } From c6e9b87746352ea7b1fc7929e1b8dd6ad5b05dee Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Wed, 27 May 2026 13:38:27 +1000 Subject: [PATCH 136/143] wifi: mac80211_hwsim: don't run RC update on new STA on S1G vif mac80211_hwsim_sta_rc_update() is unable to handle S1G widths so when a new STA is added under a S1G vif the WARN is hit preventing hwsim use for S1G. For now, skip calling rc_update() for S1G interfaces. This is required such that the soon-to-be S1G hwsim tests can successfully run. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260527033828.183821-2-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/mac80211_hwsim_main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 67a33e5e7d54..0241b38beddc 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -2816,7 +2816,10 @@ static int mac80211_hwsim_sta_add(struct ieee80211_hw *hw, hwsim_check_magic(vif); hwsim_set_sta_magic(sta); - mac80211_hwsim_sta_rc_update(hw, vif, &sta->deflink, 0); + + /* For now, don't run RC update on STAs on an S1G interface */ + if (!vif->cfg.s1g) + mac80211_hwsim_sta_rc_update(hw, vif, &sta->deflink, 0); if (sta->valid_links) { WARN(hweight16(sta->valid_links) > 1, From db215112182e3eddf2a551e25a7dfd23aca3a7fc Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Wed, 27 May 2026 13:38:28 +1000 Subject: [PATCH 137/143] wifi: mac80211_hwsim: modernise S1G channel list The current S1G channel list in mac80211_hwsim does not represent what S1G drivers would advertise that being 1MHz primaries. Also, include the NO_PRIMARY flag on the edgeband 1MHz primaries to emulate US operation such that it can also be tested. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260527033828.183821-3-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- .../wireless/virtual/mac80211_hwsim_main.c | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 0241b38beddc..1512e160e41a 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -356,6 +356,14 @@ static struct net_device *hwsim_mon; /* global monitor netdev */ .hw_value = (_freq), \ } +#define CHANS1G(_freq, _offset, _flags) { \ + .band = NL80211_BAND_S1GHZ, \ + .center_freq = (_freq), \ + .freq_offset = (_offset), \ + .hw_value = (_freq), \ + .flags = (_flags), \ +} + static const struct ieee80211_channel hwsim_channels_2ghz[] = { CHAN2G(2412), /* Channel 1 */ CHAN2G(2417), /* Channel 2 */ @@ -490,7 +498,38 @@ static const struct ieee80211_channel hwsim_channels_6ghz[] = { static_assert(HWSIM_NUM_CHANNELS_6GHZ == ARRAY_SIZE(hwsim_channels_6ghz), "Inconsistent 6 GHz channel count"); -static struct ieee80211_channel hwsim_channels_s1g[HWSIM_NUM_S1G_CHANNELS_US]; +/* + * US 2024 channels (op class 1). Additionally to emulate real world + * US operation, the edgeband 1MHz channels (1, 51) are marked as NO_PRIMARY. + */ +static const struct ieee80211_channel hwsim_channels_s1g[] = { + CHANS1G(902, 500, IEEE80211_CHAN_S1G_NO_PRIMARY), /* Channel 1 */ + CHANS1G(903, 500, 0), /* Channel 3 */ + CHANS1G(904, 500, 0), /* Channel 5 */ + CHANS1G(905, 500, 0), /* Channel 7 */ + CHANS1G(906, 500, 0), /* Channel 9 */ + CHANS1G(907, 500, 0), /* Channel 11 */ + CHANS1G(908, 500, 0), /* Channel 13 */ + CHANS1G(909, 500, 0), /* Channel 15 */ + CHANS1G(910, 500, 0), /* Channel 17 */ + CHANS1G(911, 500, 0), /* Channel 19 */ + CHANS1G(912, 500, 0), /* Channel 21 */ + CHANS1G(913, 500, 0), /* Channel 23 */ + CHANS1G(914, 500, 0), /* Channel 25 */ + CHANS1G(915, 500, 0), /* Channel 27 */ + CHANS1G(916, 500, 0), /* Channel 29 */ + CHANS1G(917, 500, 0), /* Channel 31 */ + CHANS1G(918, 500, 0), /* Channel 33 */ + CHANS1G(919, 500, 0), /* Channel 35 */ + CHANS1G(920, 500, 0), /* Channel 37 */ + CHANS1G(921, 500, 0), /* Channel 39 */ + CHANS1G(922, 500, 0), /* Channel 41 */ + CHANS1G(923, 500, 0), /* Channel 43 */ + CHANS1G(924, 500, 0), /* Channel 45 */ + CHANS1G(925, 500, 0), /* Channel 47 */ + CHANS1G(926, 500, 0), /* Channel 49 */ + CHANS1G(927, 500, IEEE80211_CHAN_S1G_NO_PRIMARY), /* Channel 51 */ +}; static const struct ieee80211_sta_s1g_cap hwsim_s1g_cap = { .s1g = true, @@ -519,19 +558,6 @@ static const struct ieee80211_sta_s1g_cap hwsim_s1g_cap = { 0 }, }; -static void hwsim_init_s1g_channels(struct ieee80211_channel *chans) -{ - int ch, freq; - - for (ch = 0; ch < ARRAY_SIZE(hwsim_channels_s1g); ch++) { - freq = 902000 + (ch + 1) * 500; - chans[ch].band = NL80211_BAND_S1GHZ; - chans[ch].center_freq = KHZ_TO_MHZ(freq); - chans[ch].freq_offset = freq % 1000; - chans[ch].hw_value = ch + 1; - } -} - static const struct ieee80211_rate hwsim_rates[] = { { .bitrate = 10 }, { .bitrate = 20, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, @@ -7443,8 +7469,6 @@ static int __init init_mac80211_hwsim(void) if (err) goto out_exit_virtio; - hwsim_init_s1g_channels(hwsim_channels_s1g); - for (i = 0; i < radios; i++) { struct hwsim_new_radio_params param = { 0 }; From f681502c79173a79c3de16be274eca83e8fd8d3f Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Wed, 27 May 2026 21:33:58 +0800 Subject: [PATCH 138/143] wifi: nl80211: re-check wiphy netns in testmode and vendor dump continuations Commit 79240f3f6d76 ("wifi: nl80211: re-check wiphy netns in nl80211_prepare_wdev_dump() continuation") fixed one dumpit path that looked the wiphy up by index on a later call without confirming it was still in the caller's netns. Two more dumpit paths have the same gap. nl80211_testmode_dump() and nl80211_prepare_vendor_dump() both keep the wiphy index in cb->args[] and look it up again on later calls, through cfg80211_rdev_by_wiphy_idx() and wiphy_idx_to_wiphy(). The first call binds to the caller's netns. A later call does not check it again. In between, the wiphy can move to another netns via NL80211_CMD_SET_WIPHY_NETNS. Add the same net_eq() check to both. On a mismatch, return -ENODEV and the dump ends. No mainline driver registers .testmode_dump or wiphy_vendor_command.dumpit, so these paths are not reachable today. Drivers outside the tree can register either. Signed-off-by: Maoyi Xie Link: https://patch.msgid.link/20260527133358.2853238-1-maoyixie.tju@gmail.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index c272a2fbad03..cdb5e9b77143 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -13730,6 +13730,16 @@ static int nl80211_testmode_dump(struct sk_buff *skb, err = -ENOENT; goto out_err; } + + /* + * The wiphy may have moved netns between dumpit + * invocations (via NL80211_CMD_SET_WIPHY_NETNS), so + * re-check that it still matches the caller's netns. + */ + if (!net_eq(wiphy_net(&rdev->wiphy), sock_net(skb->sk))) { + err = -ENODEV; + goto out_err; + } } else { attrbuf = kzalloc_objs(*attrbuf, NUM_NL80211_ATTR); if (!attrbuf) { @@ -17771,6 +17781,15 @@ static int nl80211_prepare_vendor_dump(struct sk_buff *skb, if (!wiphy) return -ENODEV; + + /* + * The wiphy may have moved netns between dumpit + * invocations (via NL80211_CMD_SET_WIPHY_NETNS), so + * re-check that it still matches the caller's netns. + */ + if (!net_eq(wiphy_net(wiphy), sock_net(skb->sk))) + return -ENODEV; + *rdev = wiphy_to_rdev(wiphy); *wdev = NULL; From 847f7ead7bdcd6ef4aef12fb1efbd4bf276d0a13 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 27 May 2026 22:42:40 +0300 Subject: [PATCH 139/143] wifi: mac80211_hwsim: add debug messages for link changes It's useful to be able to see the link addresses when the interface links change, so add some prints to hwsim for the vif link change callback. Signed-off-by: Johannes Berg Reviewed-by: Benjamin Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260527224151.b22a048abc7c.Ief2132c5c4bb67fa56e90eb8e7d59dfd9f2b9378@changeid Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/mac80211_hwsim_main.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 1512e160e41a..6ea082157307 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -3534,8 +3534,13 @@ static int mac80211_hwsim_change_vif_links(struct ieee80211_hw *hw, if (!new_links) add |= BIT(0); - for_each_set_bit(i, &rem, IEEE80211_MLD_MAX_NUM_LINKS) + wiphy_dbg(hw->wiphy, "%s:\n", __func__); + + for_each_set_bit(i, &rem, IEEE80211_MLD_MAX_NUM_LINKS) { mac80211_hwsim_config_mac_nl(hw, old[i]->addr, false); + wiphy_dbg(hw->wiphy, + " link [%d/%pM] removed\n", i, old[i]->addr); + } for_each_set_bit(i, &add, IEEE80211_MLD_MAX_NUM_LINKS) { struct ieee80211_bss_conf *link_conf; @@ -3545,6 +3550,8 @@ static int mac80211_hwsim_change_vif_links(struct ieee80211_hw *hw, continue; mac80211_hwsim_config_mac_nl(hw, link_conf->addr, true); + wiphy_dbg(hw->wiphy, + " link [%d/%pM] added\n", i, link_conf->addr); } return 0; From 8facc23dc26eb185146a6c0ae68ae6b9f130eee2 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 27 May 2026 22:51:43 +0300 Subject: [PATCH 140/143] wifi: mac80211: add an option to filter out a channel in combinations check Sometimes, ieee80211_check_combinations fails, but it is hard to know why exactly. We will have to return an array of reasons, one per combination. In cases where we want to check if it failed because there are not enough chanctxs (and maybe remove one if needed), we can just not fill in that chanctx(s) in iface_combination_params::num_different_channels in ieee80211_fill_ifcomb_params, so that chanctx(s) won't be taken into account. To allow that, add an option to pass a callback to ieee80211_fill_ifcomb_params. This callback will be called for each chanctx we consider to count in num_different_channels and will return whether or not this chanctx should be skipped and not counted. This mechanism will be used later. Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260527224745.0042232f996e.I5d323125e34f3b253510e85ceb7a770d59f689fb@changeid Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 34 ++++++++++++++++++++++++++++++---- net/mac80211/util.c | 34 +++++++++++++++++++++------------- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 1360522649c2..20b27c8f7d4e 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2818,10 +2818,36 @@ int ieee80211_send_action_csa(struct ieee80211_sub_if_data *sdata, struct cfg80211_csa_settings *csa_settings); void ieee80211_recalc_sb_count(struct ieee80211_sub_if_data *sdata, u64 tsf); void ieee80211_recalc_dtim(struct ieee80211_sub_if_data *sdata, u64 tsf); -int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, - const struct cfg80211_chan_def *chandef, - enum ieee80211_chanctx_mode chanmode, - u8 radar_detect, int radio_idx); + +struct ieee80211_check_combinations_data { + const struct cfg80211_chan_def *chandef; + enum ieee80211_chanctx_mode chanmode; + u8 radar_detect; + int radio_idx; + bool (*chanctx_filter)(struct ieee80211_chanctx *ctx, + void *filter_data); + void *filter_data; +}; + +int ieee80211_check_combinations_ext(struct ieee80211_sub_if_data *sdata, + struct ieee80211_check_combinations_data *data); + +static inline int +ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, + const struct cfg80211_chan_def *chandef, + enum ieee80211_chanctx_mode chanmode, + u8 radar_detect, int radio_idx) +{ + struct ieee80211_check_combinations_data data = { + .chandef = chandef, + .chanmode = chanmode, + .radar_detect = radar_detect, + .radio_idx = radio_idx, + }; + + return ieee80211_check_combinations_ext(sdata, &data); +} + int ieee80211_max_num_channels(struct ieee80211_local *local, int radio_idx); u32 ieee80211_get_radio_mask(struct wiphy *wiphy, struct net_device *dev); void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local, diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 255905f971c8..2a7ab269687a 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -4260,7 +4260,10 @@ static int ieee80211_fill_ifcomb_params(struct ieee80211_local *local, struct iface_combination_params *params, const struct cfg80211_chan_def *chandef, - struct ieee80211_sub_if_data *sdata) + struct ieee80211_sub_if_data *sdata, + bool (*chanctx_filter)(struct ieee80211_chanctx *ctx, + void *filter_data), + void *filter_data) { struct ieee80211_sub_if_data *sdata_iter; struct ieee80211_chanctx *ctx; @@ -4281,6 +4284,10 @@ ieee80211_fill_ifcomb_params(struct ieee80211_local *local, cfg80211_chandef_compatible(chandef, &ctx->conf.def)) continue; + if (chanctx_filter && + chanctx_filter(ctx, filter_data)) + continue; + params->num_different_channels++; } @@ -4305,26 +4312,25 @@ ieee80211_fill_ifcomb_params(struct ieee80211_local *local, return total; } -int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, - const struct cfg80211_chan_def *chandef, - enum ieee80211_chanctx_mode chanmode, - u8 radar_detect, int radio_idx) +int ieee80211_check_combinations_ext(struct ieee80211_sub_if_data *sdata, + struct ieee80211_check_combinations_data *data) { - bool shared = chanmode == IEEE80211_CHANCTX_SHARED; + const struct cfg80211_chan_def *chandef = data->chandef; + bool shared = data->chanmode == IEEE80211_CHANCTX_SHARED; struct ieee80211_local *local = sdata->local; enum nl80211_iftype iftype = sdata->wdev.iftype; struct iface_combination_params params = { - .radar_detect = radar_detect, - .radio_idx = radio_idx, + .radar_detect = data->radar_detect, + .radio_idx = data->radio_idx, }; int total; lockdep_assert_wiphy(local->hw.wiphy); - if (WARN_ON(hweight32(radar_detect) > 1)) + if (WARN_ON(hweight32(data->radar_detect) > 1)) return -EINVAL; - if (WARN_ON(chandef && chanmode == IEEE80211_CHANCTX_SHARED && + if (WARN_ON(chandef && data->chanmode == IEEE80211_CHANCTX_SHARED && !chandef->chan)) return -EINVAL; @@ -4343,7 +4349,7 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, /* Always allow software iftypes */ if (cfg80211_iftype_allowed(local->hw.wiphy, iftype, 0, 1)) { - if (radar_detect) + if (data->radar_detect) return -EINVAL; return 0; } @@ -4356,7 +4362,9 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, total = ieee80211_fill_ifcomb_params(local, ¶ms, shared ? chandef : NULL, - sdata); + sdata, + data->chanctx_filter, + data->filter_data); if (total == 1 && !params.radar_detect) return 0; @@ -4383,7 +4391,7 @@ int ieee80211_max_num_channels(struct ieee80211_local *local, int radio_idx) lockdep_assert_wiphy(local->hw.wiphy); - ieee80211_fill_ifcomb_params(local, ¶ms, NULL, NULL); + ieee80211_fill_ifcomb_params(local, ¶ms, NULL, NULL, NULL, NULL); err = cfg80211_iter_combinations(local->hw.wiphy, ¶ms, ieee80211_iter_max_chans, From a5b9ebd5c3f409ddfe1c15bdba361f904c16d719 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 27 May 2026 22:51:44 +0300 Subject: [PATCH 141/143] wifi: mac80211: refactor ieee80211_nan_try_evacuate Extract the logic that finds a NAN channel which makes a good evacuation candidate into a separate function. It will be used in a later patch. Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260527224745.589503562912.I9e266da48ceaf85bd0fe1b0487c3fdbeaaf9baaa@changeid Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 27 +++++++++++++++---- net/mac80211/nan.c | 53 +++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 20b27c8f7d4e..fcdb6fdd4d75 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1946,6 +1946,11 @@ ieee80211_vif_get_num_mcast_if(struct ieee80211_sub_if_data *sdata) return -1; } +static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata) +{ + return test_bit(SDATA_STATE_RUNNING, &sdata->state); +} + int ieee80211_hw_config(struct ieee80211_local *local, int radio_idx, u32 changed); int ieee80211_hw_conf_chan(struct ieee80211_local *local); @@ -2055,6 +2060,23 @@ int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata, void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched); void ieee80211_nan_update_ndi_carrier(struct ieee80211_sub_if_data *ndi_sdata); +static inline struct ieee80211_sub_if_data * +ieee80211_find_nan_sdata(struct ieee80211_local *local) +{ + struct ieee80211_sub_if_data *sdata; + + lockdep_assert_wiphy(local->hw.wiphy); + + /* Find the NAN interface - there can only be one */ + list_for_each_entry(sdata, &local->interfaces, list) { + if (ieee80211_sdata_running(sdata) && + sdata->vif.type == NL80211_IFTYPE_NAN) + return sdata; + } + + return NULL; +} + /* scan/BSS handling */ void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work); int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata, @@ -2155,11 +2177,6 @@ void ieee80211_recalc_txpower(struct ieee80211_link_data *link, bool update_bss); void ieee80211_recalc_offload(struct ieee80211_local *local); -static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata) -{ - return test_bit(SDATA_STATE_RUNNING, &sdata->state); -} - /* link handling */ void ieee80211_link_setup(struct ieee80211_link_data *link); void ieee80211_link_init(struct ieee80211_sub_if_data *sdata, diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c index cea620aaee6a..44b6e298d94d 100644 --- a/net/mac80211/nan.c +++ b/net/mac80211/nan.c @@ -754,33 +754,20 @@ ieee80211_nan_evacuate_channel(struct ieee80211_sub_if_data *sdata, ieee80211_free_chanctx(sdata->local, ctx, false); } -bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw, - struct ieee80211_chanctx_conf *conf) +static struct ieee80211_nan_channel * +ieee80211_nan_find_evac_chan(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_chanctx *ctx) { - struct ieee80211_sub_if_data *sdata = NULL, *tmp; - struct ieee80211_local *local = hw_to_local(hw); - struct ieee80211_nan_channel *evac_chan = NULL; struct ieee80211_nan_sched_cfg *sched_cfg; - struct ieee80211_chanctx *ctx = NULL; + struct ieee80211_nan_channel *evac_chan = NULL; int min_slot_count = INT_MAX; int usable_channels = 0; lockdep_assert_wiphy(local->hw.wiphy); - if (conf) - ctx = container_of(conf, struct ieee80211_chanctx, conf); - - /* Find the NAN interface - there can only be one */ - list_for_each_entry(tmp, &local->interfaces, list) { - if (ieee80211_sdata_running(tmp) && - tmp->vif.type == NL80211_IFTYPE_NAN) { - sdata = tmp; - break; - } - } - - if (!sdata) - return false; + if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_NAN)) + return NULL; sched_cfg = &sdata->vif.cfg.nan_sched; @@ -823,10 +810,34 @@ bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw, /* No suitable NAN channel found */ if (!evac_chan) - return false; + return NULL; /* NAN needs at least one remaining usable channel after evacuation */ if (usable_channels < 2) + return NULL; + + return evac_chan; +} + +bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *conf) +{ + struct ieee80211_local *local = hw_to_local(hw); + struct ieee80211_sub_if_data *sdata = + ieee80211_find_nan_sdata(local); + struct ieee80211_nan_channel *evac_chan; + struct ieee80211_chanctx *ctx = NULL; + + lockdep_assert_wiphy(local->hw.wiphy); + + if (!sdata) + return false; + + if (conf) + ctx = container_of(conf, struct ieee80211_chanctx, conf); + + evac_chan = ieee80211_nan_find_evac_chan(local, sdata, ctx); + if (!evac_chan) return false; ieee80211_nan_evacuate_channel(sdata, evac_chan); From f1a389fcc3f13a00c1202b0f63e073a2054184cc Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 27 May 2026 22:51:45 +0300 Subject: [PATCH 142/143] wifi: mac80211: fix channel evacuation logic When we try to assign a chanctx to a link, if ieee80211_find_or_create_chanctx() failed, we try to evacuate a NAN channel and call it again. This logic is broken: In case there are not enough chanctxs we will fail earlier, when we check ieee80211_check_combinations(). To fix this, do the following in case ieee80211_check_combinations() failed: - check if there is a NAN channel that can be evacuated - make ieee80211_check_combinations() not consider the chanctx of that NAN channel, so we pretend that it was already evacuated - If now ieee80211_check_combinations() is successful, we know that it helped, and we can remove that NAN channel for real. Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260527224745.5f7b10505145.I1712bc64f9eec9c99f05994208ad124624a29f1c@changeid Signed-off-by: Johannes Berg --- net/mac80211/chan.c | 78 ++++++++++++++++++++++++++++++++------ net/mac80211/ieee80211_i.h | 6 +++ net/mac80211/nan.c | 4 +- 3 files changed, 75 insertions(+), 13 deletions(-) diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index c64a99131954..23d46cd57137 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -2216,6 +2216,64 @@ ieee80211_find_or_create_chanctx(struct ieee80211_sub_if_data *sdata, assign_on_failure, radio_idx); } +static bool +ieee80211_nan_evac_chanctx_filter(struct ieee80211_chanctx *ctx, + void *filter_data) +{ + return ctx == filter_data; +} + +static int +ieee80211_try_nan_chan_evacuation(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + const struct cfg80211_chan_def *chandef, + enum ieee80211_chanctx_mode mode, + u8 radar_detect_width) +{ + struct ieee80211_sub_if_data *nan_sdata = ieee80211_find_nan_sdata(local); + struct ieee80211_check_combinations_data comb_data = { + .chandef = chandef, + .chanmode = mode, + .radar_detect = radar_detect_width, + .radio_idx = -1, + .chanctx_filter = ieee80211_nan_evac_chanctx_filter, + }; + struct ieee80211_nan_channel *evac_chan; + struct ieee80211_chanctx *evac_ctx; + int ret; + + if (!nan_sdata) + return -ENOENT; + + /* Find an evacuation candidate... */ + evac_chan = ieee80211_nan_find_evac_chan(local, nan_sdata, NULL); + if (!evac_chan || WARN_ON(!evac_chan->chanctx_conf)) + return -ENOENT; + + evac_ctx = container_of(evac_chan->chanctx_conf, + struct ieee80211_chanctx, conf); + + /* + * ... check combinations assuming to-be-evacuated ctx is already + * released + */ + comb_data.filter_data = evac_ctx; + ret = ieee80211_check_combinations_ext(sdata, &comb_data); + if (ret < 0) + return ret; + + /* That helped! Let's evacuate the channel */ + ieee80211_nan_evacuate_channel(nan_sdata, evac_chan); + + /* Re-check, just to be on the safe-side */ + ret = ieee80211_check_combinations(sdata, chandef, mode, + radar_detect_width, -1); + + /* That shouldn't happen, we checked before! */ + WARN_ON(ret); + return ret; +} + int _ieee80211_link_use_channel(struct ieee80211_link_data *link, const struct ieee80211_chan_req *chanreq, enum ieee80211_chanctx_mode mode, @@ -2247,23 +2305,21 @@ int _ieee80211_link_use_channel(struct ieee80211_link_data *link, ret = ieee80211_check_combinations(sdata, &chanreq->oper, mode, radar_detect_width, -1); - if (ret < 0) - goto out; + if (ret < 0) { + /* Let's check if evacuating a NAN channel will help */ + ret = ieee80211_try_nan_chan_evacuation(local, sdata, + &chanreq->oper, + mode, + radar_detect_width); + if (ret < 0) + goto out; + } if (!local->in_reconfig) __ieee80211_link_release_channel(link, false); ctx = ieee80211_find_or_create_chanctx(sdata, chanreq, mode, assign_on_failure, &reused_ctx); - if (IS_ERR(ctx)) { - /* Try to evacuate a NAN channel to free up a chanctx */ - if (ieee80211_nan_try_evacuate(&local->hw, NULL)) - ctx = ieee80211_find_or_create_chanctx(sdata, chanreq, - mode, - assign_on_failure, - &reused_ctx); - } - if (IS_ERR(ctx)) { ret = PTR_ERR(ctx); goto out; diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index fcdb6fdd4d75..18a101710432 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2059,6 +2059,12 @@ int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata, struct cfg80211_nan_peer_sched *sched); void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched); void ieee80211_nan_update_ndi_carrier(struct ieee80211_sub_if_data *ndi_sdata); +struct ieee80211_nan_channel * +ieee80211_nan_find_evac_chan(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_chanctx *ctx); +void ieee80211_nan_evacuate_channel(struct ieee80211_sub_if_data *sdata, + struct ieee80211_nan_channel *nan_channel); static inline struct ieee80211_sub_if_data * ieee80211_find_nan_sdata(struct ieee80211_local *local) diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c index 44b6e298d94d..1800bb96dd29 100644 --- a/net/mac80211/nan.c +++ b/net/mac80211/nan.c @@ -712,7 +712,7 @@ int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata, return ret; } -static void +void ieee80211_nan_evacuate_channel(struct ieee80211_sub_if_data *sdata, struct ieee80211_nan_channel *nan_channel) { @@ -754,7 +754,7 @@ ieee80211_nan_evacuate_channel(struct ieee80211_sub_if_data *sdata, ieee80211_free_chanctx(sdata->local, ctx, false); } -static struct ieee80211_nan_channel * +struct ieee80211_nan_channel * ieee80211_nan_find_evac_chan(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, struct ieee80211_chanctx *ctx) From 23add15a3f66ace513e3b8b3f434d135d33b2e7b Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 28 May 2026 02:10:50 +0200 Subject: [PATCH 143/143] wifi: cfg80211: use strscpy in cfg80211_wext_giwname strcpy() has been deprecated [1] because it performs no bounds checking on the destination buffer, which can lead to buffer overflows. While the current code works correctly, replace strcpy() with the safer strscpy() to follow secure coding best practices. [1] https://www.kernel.org/doc/html/latest/process/deprecated.html#strcpy Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260528001049.1394078-2-thorsten.blum@linux.dev Signed-off-by: Johannes Berg --- net/wireless/wext-compat.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c index c3aa56977243..5dbf3ef4b257 100644 --- a/net/wireless/wext-compat.c +++ b/net/wireless/wext-compat.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -27,7 +28,7 @@ int cfg80211_wext_giwname(struct net_device *dev, struct iw_request_info *info, union iwreq_data *wrqu, char *extra) { - strcpy(wrqu->name, "IEEE 802.11"); + strscpy(wrqu->name, "IEEE 802.11"); return 0; }