summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-05-29 03:05:23 +0300
committerJakub Kicinski <kuba@kernel.org>2026-05-29 03:05:23 +0300
commite7d6bd24e883bf7c328d73c99bf6bcde19bf5e61 (patch)
treeb0ef04b56501883d2b56defe4096911cac66bf41
parent9a82e387e27a4422a0d2d9d644180b7bd913e85a (diff)
parent6f5dc19f46f4bd0e104c9a4da2f0a912cdf3bd86 (diff)
downloadlinux-e7d6bd24e883bf7c328d73c99bf6bcde19bf5e61.tar.xz
Merge tag 'wireless-next-2026-05-28' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next
Johannes Berg says: ==================== Mostly driver updates: - iwlwifi - more UHR support - NAN (multicast, schedule improvements, multi-station) - cleanups, etc. - ath12k - thermal throttling/cooling device support - 6 GHz incumbent interference detection - channel 177 in 5 GHz - hwsim: S1G fixes - mac80211: NAN channel handling improvements * tag 'wireless-next-2026-05-28' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next: (143 commits) wifi: cfg80211: use strscpy in cfg80211_wext_giwname wifi: mac80211: fix channel evacuation logic wifi: mac80211: refactor ieee80211_nan_try_evacuate wifi: mac80211: add an option to filter out a channel in combinations check wifi: mac80211_hwsim: add debug messages for link changes wifi: nl80211: re-check wiphy netns in testmode and vendor dump continuations wifi: mac80211_hwsim: modernise S1G channel list wifi: mac80211_hwsim: don't run RC update on new STA on S1G vif wifi: mwifiex: remove an unnecessary check wifi: mac80211: add KUnit coverage for negotiated TTLM parser wifi: ath12k: fix error unwind on arch_init() failure in PCI probe wifi: iwlwifi: mld: fix indentation in iwl_mld_fill_supp_rates() wifi: iwlwifi: transport: add memory read under NIC access wifi: iwlwifi: dbg: remove unused 'range_len' arg from dump wifi: iwlwifi: fw: separate out old-style dump code wifi: iwlwifi: fw: dbg: always use non-tracing PRPH access wifi: iwlwifi: fw: separate ini dump allocation wifi: iwlwifi: fw: move struct iwl_fw_ini_dump_entry to dbg.c wifi: iwlwifi: clean up location format/BW encoding wifi: iwlwifi: Add names for Killer BE1735x and BE1730x ... ==================== Link: https://patch.msgid.link/20260528123911.284536-26-johannes@sipsolutions.net Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--drivers/net/wireless/ath/ath10k/core.h1
-rw-r--r--drivers/net/wireless/ath/ath10k/htt_tx.c2
-rw-r--r--drivers/net/wireless/ath/ath10k/leds.c8
-rw-r--r--drivers/net/wireless/ath/ath10k/qmi.c4
-rw-r--r--drivers/net/wireless/ath/ath10k/qmi_wlfw_v01.h1
-rw-r--r--drivers/net/wireless/ath/ath11k/Kconfig2
-rw-r--r--drivers/net/wireless/ath/ath11k/dp.c1
-rw-r--r--drivers/net/wireless/ath/ath11k/mhi.c4
-rw-r--r--drivers/net/wireless/ath/ath11k/pci.c8
-rw-r--r--drivers/net/wireless/ath/ath11k/qmi.c3
-rw-r--r--drivers/net/wireless/ath/ath11k/qmi.h1
-rw-r--r--drivers/net/wireless/ath/ath12k/Kconfig6
-rw-r--r--drivers/net/wireless/ath/ath12k/core.c50
-rw-r--r--drivers/net/wireless/ath/ath12k/core.h12
-rw-r--r--drivers/net/wireless/ath/ath12k/debugfs.c46
-rw-r--r--drivers/net/wireless/ath/ath12k/dp_rx.c68
-rw-r--r--drivers/net/wireless/ath/ath12k/mac.c110
-rw-r--r--drivers/net/wireless/ath/ath12k/pci.c2
-rw-r--r--drivers/net/wireless/ath/ath12k/qmi.c2
-rw-r--r--drivers/net/wireless/ath/ath12k/qmi.h1
-rw-r--r--drivers/net/wireless/ath/ath12k/thermal.c252
-rw-r--r--drivers/net/wireless/ath/ath12k/thermal.h35
-rw-r--r--drivers/net/wireless/ath/ath12k/wmi.c565
-rw-r--r--drivers/net/wireless/ath/ath12k/wmi.h125
-rw-r--r--drivers/net/wireless/ath/ath9k/ar9002_hw.c6
-rw-r--r--drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c31
-rw-r--r--drivers/net/wireless/ath/ath9k/common-init.c8
-rw-r--r--drivers/net/wireless/ath/ath9k/init.c11
-rw-r--r--drivers/net/wireless/ath/ath9k/recv.c4
-rw-r--r--drivers/net/wireless/ath/ath9k/xmit.c7
-rw-r--r--drivers/net/wireless/intel/iwlwifi/Makefile2
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/22000.c23
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/7000.c5
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/8000.c5
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/9000.c5
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/ax210.c38
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/bz.c19
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/dr.c19
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c8
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c17
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c30
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c22
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c8
-rw-r--r--drivers/net/wireless/intel/iwlwifi/cfg/sc.c22
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/api/commands.h13
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h9
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/api/location.h107
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h184
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/api/power.h48
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/api/rx.h40
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/api/sta.h3
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/api/stats.h88
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/dbg-old.c1022
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/dbg.c1292
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/dbg.h7
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/debugfs.c15
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/error-dump.h14
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/file.h10
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/img.h3
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/regulatory.c7
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/rs.c5
-rw-r--r--drivers/net/wireless/intel/iwlwifi/iwl-config.h9
-rw-r--r--drivers/net/wireless/intel/iwlwifi/iwl-csr.h3
-rw-r--r--drivers/net/wireless/intel/iwlwifi/iwl-drv.c14
-rw-r--r--drivers/net/wireless/intel/iwlwifi/iwl-io.c25
-rw-r--r--drivers/net/wireless/intel/iwlwifi/iwl-io.h6
-rw-r--r--drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c150
-rw-r--r--drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h2
-rw-r--r--drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h9
-rw-r--r--drivers/net/wireless/intel/iwlwifi/iwl-prph.h7
-rw-r--r--drivers/net/wireless/intel/iwlwifi/iwl-trans.c17
-rw-r--r--drivers/net/wireless/intel/iwlwifi/iwl-trans.h123
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/agg.c9
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/ap.c58
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/ap.h8
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/d3.c168
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/d3.h6
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/debugfs.c74
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c30
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/iface.c187
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/iface.h62
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/key.c166
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/link.c569
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/link.h37
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/mac80211.c358
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/mcc.c13
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/mld.c20
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/mld.h16
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/mlo.c36
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/nan.c748
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/nan.h41
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/notif.c35
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/phy.c24
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/power.c210
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/ptp.c2
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/rx.c44
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/rx.h7
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/sta.c245
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/sta.h32
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/stats.c108
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile1
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c139
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c6
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c8
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/tlc.c410
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/tx.c51
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c30
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c32
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mvm/mvm.h4
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mvm/ops.c4
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mvm/power.c14
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mvm/ptp.c2
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mvm/rx.c9
-rw-r--r--drivers/net/wireless/intel/iwlwifi/pcie/drv.c14
-rw-r--r--drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h107
-rw-r--r--drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c9
-rw-r--r--drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c93
-rw-r--r--drivers/net/wireless/marvell/mwifiex/sta_cmd.c35
-rw-r--r--drivers/net/wireless/virtual/mac80211_hwsim_main.c70
-rw-r--r--net/mac80211/chan.c78
-rw-r--r--net/mac80211/ieee80211_i.h71
-rw-r--r--net/mac80211/mlme.c3
-rw-r--r--net/mac80211/nan.c55
-rw-r--r--net/mac80211/tests/.kunitconfig4
-rw-r--r--net/mac80211/tests/Makefile2
-rw-r--r--net/mac80211/tests/ttlm.c175
-rw-r--r--net/mac80211/util.c34
-rw-r--r--net/wireless/nl80211.c19
-rw-r--r--net/wireless/wext-compat.c3
129 files changed, 7075 insertions, 2551 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/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.
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)
diff --git a/drivers/net/wireless/ath/ath10k/qmi.c b/drivers/net/wireless/ath/ath10k/qmi.c
index e7f90fd9e9b8..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 <linux/completion.h>
@@ -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/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/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 */
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 <linux/msi.h>
@@ -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);
}
diff --git a/drivers/net/wireless/ath/ath11k/qmi.c b/drivers/net/wireless/ath/ath11k/qmi.c
index feebbc30f3df..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.
*/
@@ -3337,7 +3336,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/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
diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
index 3d7da2ea65f7..742d4fd1b598 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 @@ core_pdev_create:
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);
- goto err;
- }
-
- ath12k_hif_irq_enable(ab);
-
- ret = ath12k_core_rfkill_config(ab);
- if (ret && ret != -EOPNOTSUPP) {
- mutex_unlock(&ab->core_lock);
+ ret = ath12k_core_device_setup(ab);
+ if (ret)
goto err;
- }
-
- mutex_unlock(&ab->core_lock);
}
return 0;
diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index 8be435535a4e..f6d8ec9ef7b0 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)
{
@@ -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/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/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c
index b108ccd0f637..a68b28aa1f4b 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);
@@ -1095,7 +1100,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;
@@ -1103,12 +1109,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);
@@ -1126,9 +1144,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;
@@ -1142,19 +1161,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);
@@ -1289,9 +1322,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;
}
@@ -1336,9 +1371,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, rxcb->peer_id);
@@ -1383,15 +1416,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);
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 2cff9485c95a..87b27f7cff5d 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[] = {
@@ -5615,12 +5619,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;
@@ -5642,18 +5648,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) {
@@ -5678,7 +5674,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);
}
@@ -9650,6 +9645,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.
*/
@@ -9677,6 +9676,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]);
@@ -9857,6 +9862,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)
@@ -11443,8 +11452,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;
@@ -11556,6 +11567,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
@@ -13739,19 +13786,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;
@@ -13904,6 +13945,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[])
@@ -14037,6 +14098,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];
@@ -14465,6 +14528,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;
}
@@ -14817,6 +14882,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/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);
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
diff --git a/drivers/net/wireless/ath/ath12k/thermal.c b/drivers/net/wireless/ath/ath12k/thermal.c
index a764d2112a3c..97fc49c40ac1 100644
--- a/drivers/net/wireless/ath/ath12k/thermal.c
+++ b/drivers/net/wireless/ath/ath12k/thermal.c
@@ -12,6 +12,137 @@
#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, &param);
+ 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 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, &param);
+ 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)
@@ -66,59 +197,108 @@ 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)
{
+ char pdev_name[20];
struct ath12k *ar;
- int i, j, ret;
+ int ret;
+
+ ar = ab->pdevs[i].ar;
+ 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,
+ 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);
+ 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;
+ if (!ar)
+ return;
+
+ 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)
+{
+ int i, ret;
if (!IS_REACHABLE(CONFIG_HWMON))
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;
- }
+ 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)
{
- struct ath12k *ar;
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);
}
diff --git a/drivers/net/wireless/ath/ath12k/thermal.h b/drivers/net/wireless/ath/ath12k/thermal.h
index 9d84056188e1..30e7b0880e05 100644
--- a/drivers/net/wireless/ath/ath12k/thermal.h
+++ b/drivers/net/wireless/ath/ath12k/thermal.h
@@ -7,20 +7,41 @@
#ifndef _ATH12K_THERMAL_
#define _ATH12K_THERMAL_
+#include <linux/mutex.h>
+
#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 */
+ 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;
+ struct thermal_cooling_device *cdev;
+ /* Serialize thermal operations and hwmon reads */
+ struct mutex lock;
+ u32 throttle_state;
};
#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);
+int ath12k_thermal_set_throttling(struct ath12k *ar, u32 throttle_state);
#else
static inline int ath12k_thermal_register(struct ath12k_base *ab)
{
@@ -36,5 +57,19 @@ 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)
+{
+}
+
+static inline int ath12k_thermal_set_throttling(struct ath12k *ar,
+ u32 throttle_state)
+{
+ return 0;
+}
#endif
#endif /* _ATH12K_THERMAL_ */
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 9b8b370ab5ec..18f91051199c 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,
@@ -3380,6 +3442,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)
{
@@ -4199,12 +4330,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 +4463,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 +4497,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 +4540,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;
}
@@ -8540,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,
@@ -8762,6 +9205,42 @@ exit:
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)
{
@@ -9811,6 +10290,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;
@@ -9865,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:
@@ -10083,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;
@@ -10874,10 +11395,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)
@@ -10902,10 +11422,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)
@@ -10931,10 +11450,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)
@@ -10997,10 +11515,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
diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
index 4a34b2ca99ea..14b8dcdf881d 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,
@@ -2014,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,
@@ -2259,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,
@@ -2268,11 +2270,17 @@ 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,
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,
@@ -3586,7 +3594,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 +3602,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 {
@@ -4120,6 +4128,49 @@ 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;
+
+#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;
@@ -4203,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
@@ -4487,6 +4548,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;
@@ -6514,6 +6631,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)
@@ -6576,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);
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/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);
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 ee951493e993..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;
@@ -576,7 +574,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 +591,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);
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;
diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c
index 0ac9212e42f7..89d8b3178784 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);
}
@@ -2746,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);
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/cfg/22000.c b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c
index f0453f3f6ba6..01ca65eb5acd 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 <linux/module.h>
#include <linux/stringify.h>
@@ -15,26 +15,26 @@
/* 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_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,
.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,
.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,
@@ -113,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/7000.c b/drivers/net/wireless/intel/iwlwifi/cfg/7000.c
index f987ad3192c1..1be72d71fccf 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
*/
@@ -53,10 +53,7 @@ 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,
- .led_compensation = 57,
.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 3c844cd419e8..834aa520ab0c 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
*/
@@ -39,10 +39,7 @@ 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,
- .led_compensation = 57,
.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 5872fc9b8caf..2954434ce851 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 <linux/module.h>
#include <linux/stringify.h>
@@ -30,10 +30,7 @@ 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,
- .led_compensation = 57,
.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 582f61661062..2519f577669e 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 <linux/module.h>
#include <linux/stringify.h>
@@ -15,21 +15,30 @@
/* 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
+#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,
- .shadow_ram_support = true,
- .led_compensation = 57,
.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,
- .smem_len = IWL_AX210_SMEM_LEN,
.features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM,
.apmg_not_supported = true,
.mac_addr_from_csr = 0x380,
@@ -121,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/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c
index 3653ddbf3ce9..ecb4f81a99f5 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 <linux/module.h>
#include <linux/stringify.h>
@@ -10,26 +10,17 @@
#include "fw/api/txq.h"
/* Highest firmware core release supported */
-#define IWL_BZ_UCODE_CORE_MAX 102
+#define IWL_BZ_UCODE_CORE_MAX 105
-/* Lowest firmware API version supported */
-#define IWL_BZ_UCODE_API_MIN 100
-
-/* Memory offsets and lengths */
-#define IWL_BZ_SMEM_OFFSET 0x400000
-#define IWL_BZ_SMEM_LEN 0xD0000
+/* Lowest firmware core release supported */
+#define IWL_BZ_UCODE_CORE_MIN 101
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,
.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,
@@ -69,7 +60,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 83d893b10f8e..e8968b3051d3 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 <linux/module.h>
#include <linux/stringify.h>
@@ -9,28 +9,19 @@
#include "fw/api/txq.h"
/* Highest firmware core release supported */
-#define IWL_DR_UCODE_CORE_MAX 102
+#define IWL_DR_UCODE_CORE_MAX 105
-/* Lowest firmware API version supported */
-#define IWL_DR_UCODE_API_MIN 100
-
-/* Memory offsets and lengths */
-#define IWL_DR_SMEM_OFFSET 0x400000
-#define IWL_DR_SMEM_LEN 0xD0000
+/* Lowest firmware core release supported */
+#define IWL_DR_UCODE_CORE_MIN 101
#define IWL_DR_A_PE_A_FW_PRE "iwlwifi-dr-a0-pe-a0"
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,
.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,
@@ -70,7 +61,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/rf-fm.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c
index ad2536f53084..294cf25ae2a6 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 = {
@@ -51,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-gf.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c
index c16cda087a7c..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"
@@ -35,9 +28,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 +39,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,
@@ -73,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 6cf187d92dbf..16b9075acdd8 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"
@@ -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) \
@@ -40,9 +22,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 +33,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
@@ -78,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));
diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c
index 2c29054ce7b8..7a04cb120b1b 100644
--- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c
+++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c
@@ -1,10 +1,28 @@
// 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"
-/* currently iwl_rf_wh/iwl_rf_wh_160mhz are just defines for the FM ones */
+#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_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/cfg/rf-wh.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c
index b5803ea1eb78..c432aa1a0af6 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 */
@@ -33,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/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c
index 749d46dc0236..6aaa49aeec99 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 <linux/module.h>
#include <linux/stringify.h>
@@ -10,17 +10,10 @@
#include "fw/api/txq.h"
/* Highest firmware core release supported */
-#define IWL_SC_UCODE_CORE_MAX 102
+#define IWL_SC_UCODE_CORE_MAX 105
-/* 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
+/* Lowest firmware core release supported */
+#define IWL_SC_UCODE_CORE_MIN 101
#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"
@@ -31,14 +24,9 @@
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,
.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,
@@ -78,7 +66,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 = {
diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h b/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h
index 36159a769916..abd259350589 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__
@@ -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,
@@ -59,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,
@@ -386,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
*/
@@ -560,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,
@@ -653,7 +650,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/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
@@ -92,6 +92,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/fw/api/location.h b/drivers/net/wireless/intel/iwlwifi/fw/api/location.h
index 2ee3a48aa5df..421ea94ace01 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,13 +81,54 @@ 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,
};
/**
+ * 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)
* @one_sided_disabled: indicates if one-sided is disabled (or not)
@@ -263,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
@@ -302,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
@@ -348,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
@@ -400,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
@@ -676,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
@@ -763,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
@@ -810,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
@@ -868,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
@@ -939,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
@@ -1024,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/fw/api/mac-cfg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h
index b398c582b867..09b2cddc4ad2 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
@@ -72,6 +73,23 @@ enum iwl_mac_conf_subcmd_ids {
*/
NAN_CFG_CMD = 0x12,
/**
+ * @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_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
*/
NAN_DW_END_NOTIF = 0xf4,
@@ -536,12 +554,20 @@ 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
+ * @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 {
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_FLG_DPS = BIT(6),
+ LINK_FLG_MLPM = BIT(7),
+ LINK_FLG_DUO = BIT(8),
}; /* LINK_CONTEXT_FLAG_E_VER_1 */
/**
@@ -578,7 +604,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
*/
@@ -726,6 +752,12 @@ 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_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.
@@ -736,6 +768,9 @@ 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_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 */
@@ -873,7 +908,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
@@ -908,7 +945,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 {
@@ -1204,7 +1242,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.
@@ -1242,7 +1281,102 @@ 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
+ * @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_v1 {
+ 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 */
+
+#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
+ * @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
@@ -1280,4 +1414,44 @@ 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 */
+
+/**
+ * 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/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/fw/api/rx.h b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h
index ac6c1ef2cbcd..699343cf0279 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),
@@ -732,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)
@@ -1169,8 +1174,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
@@ -1275,15 +1286,16 @@ 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
+#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
@@ -1305,6 +1317,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
@@ -1330,7 +1343,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
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/api/stats.h b/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h
index 68983f6a0026..8d16782a129c 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,6 +557,65 @@ struct iwl_stats_ntfy_per_mac {
#define IWL_STATS_MAX_BW_INDEX 5
/**
+ * 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
+ * @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
+ */
+struct iwl_stats_ntfy_per_phy_v1 {
+ __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;
+} __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
@@ -571,6 +630,7 @@ struct iwl_stats_ntfy_per_mac {
* @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;
@@ -583,7 +643,8 @@ struct iwl_stats_ntfy_per_phy {
__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;
-} __packed; /* STATISTICS_NTFY_PER_PHY_API_S_VER_1 */
+ 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
@@ -600,11 +661,26 @@ struct iwl_stats_ntfy_per_sta {
#define IWL_STATS_MAX_PHY_OPERATIONAL 3
/**
+ * 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_v1
+ * @per_sta: per sta statistics, &struct iwl_stats_ntfy_per_sta
+ */
+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_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 */
+
+/**
* 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
+ * @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 {
@@ -612,7 +688,7 @@ struct iwl_system_statistics_notif_oper {
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_3 */
+} __packed; /* STATISTICS_FW_NTFY_OPERATIONAL_API_S_VER_4 */
/**
* struct iwl_system_statistics_part1_notif_oper - part1 stats notification
@@ -642,7 +718,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 +729,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/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 <linux/devcoredump.h>
+#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 0cffa5493704..069c3bad6f29 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
*/
@@ -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_write_prph_no_grab(fwrt->trans, RSP_RADIO_CMD, rd_cmd);
- *pos = (u8)iwl_read_prph_no_grab(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_read_prph_no_grab(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
@@ -1071,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]) +
@@ -1084,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;
@@ -1132,16 +172,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 +189,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);
}
@@ -1161,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]);
@@ -1174,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;
@@ -1187,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;
@@ -1206,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;
@@ -1234,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;
@@ -1256,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;
@@ -1276,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;
@@ -1285,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;
@@ -1301,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;
@@ -1320,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;
@@ -1388,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;
@@ -1410,7 +450,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 +460,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 +472,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,
@@ -1457,7 +497,7 @@ out:
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;
@@ -1487,12 +527,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);
}
@@ -1570,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;
@@ -1601,8 +641,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 +655,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);
@@ -1637,7 +676,7 @@ out:
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 = &reg->err_table;
@@ -1656,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 =
@@ -1677,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;
@@ -1690,9 +729,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;
@@ -1705,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;
@@ -1726,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;
@@ -1791,7 +832,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));
}
@@ -2236,24 +1277,22 @@ 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);
};
-/**
- * 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)
+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;
+
+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;
@@ -2261,58 +1300,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;
@@ -2329,7 +1369,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,
@@ -2350,9 +1412,8 @@ 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++) {
- int range_size = ops->fill_range(fwrt, reg_data, range,
- free_size, i);
+ for (i = 0; i < le32_to_cpu(header->num_of_ranges); i++) {
+ int range_size = ops->fill_range(fwrt, reg_data, range, i);
if (range_size < 0) {
IWL_ERR(fwrt,
@@ -2372,11 +1433,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;
@@ -2605,22 +1665,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))
@@ -2638,8 +1695,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) &&
@@ -2650,19 +1705,6 @@ iwl_dump_ini_dump_regions(struct iwl_fw_runtime *fwrt,
continue;
}
- switch (which) {
- case IWL_INI_DUMP_ALL_REGIONS:
- break;
- case IWL_INI_DUMP_EARLY_REGIONS:
- if (!(dp & IWL_FW_IWL_DEBUG_DUMP_POLICY_BEFORE_RESET))
- continue;
- break;
- case IWL_INI_DUMP_LATE_REGIONS:
- if (dp & IWL_FW_IWL_DEBUG_DUMP_POLICY_BEFORE_RESET)
- continue;
- break;
- }
-
/*
* DRAM_IMR can be collected only for FW/HW error timepoint
* when fw is not alive. In addition, it must be collected
@@ -2671,19 +1713,45 @@ iwl_dump_ini_dump_regions(struct iwl_fw_runtime *fwrt,
*/
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];
+ *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 to next region */
continue;
}
+ iwl_dump_ini_mem_prep(fwrt, list, &reg_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;
+ case IWL_INI_DUMP_EARLY_REGIONS:
+ if (!(dp & IWL_FW_IWL_DEBUG_DUMP_POLICY_BEFORE_RESET))
+ continue;
+ break;
+ case IWL_INI_DUMP_LATE_REGIONS:
+ if (dp & IWL_FW_IWL_DEBUG_DUMP_POLICY_BEFORE_RESET)
+ continue;
+ break;
+ }
- size += iwl_dump_ini_mem(fwrt, list, &reg_data,
- &iwl_dump_ini_region_ops[reg_type]);
+ size += iwl_dump_ini_mem(fwrt, entry);
}
return size;
@@ -2706,32 +1774,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;
}
@@ -2797,52 +1865,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)) {
@@ -2871,7 +1893,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,
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;
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
*/
@@ -266,18 +266,6 @@ struct iwl_fw_ini_error_dump_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
* @file_len: the length of all the file including the header
diff --git a/drivers/net/wireless/intel/iwlwifi/fw/file.h b/drivers/net/wireless/intel/iwlwifi/fw/file.h
index f7a6f21267e9..197c88c25f72 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,
@@ -1063,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)
{
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/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 <linux/dmi.h>
#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"),
+ },
+ },
{}
};
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 <net/mac80211.h>
@@ -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 */
}
diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-config.h b/drivers/net/wireless/intel/iwlwifi/iwl-config.h
index 5f40cd15e27f..30d5ec31b9c3 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;
@@ -674,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[];
@@ -681,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[];
@@ -744,7 +748,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-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/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
index d5ded4d3a30b..488524529538 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:
@@ -1330,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;
@@ -1640,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/iwl-io.c b/drivers/net/wireless/intel/iwlwifi/iwl-io.c
index b1944584c693..bb746112ddad 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 <linux/device.h>
@@ -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)
{
@@ -168,6 +167,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)) {
@@ -396,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 5bcec239ffc4..6dce2e5267a6 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,14 +51,14 @@ 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);
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-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c
index 8f3f651451bb..7027bca249a0 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
*/
@@ -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"
@@ -206,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
@@ -546,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),
@@ -690,6 +691,16 @@ 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,
+ .mac.mac_cap = {
+ [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP |
+ IEEE80211_UHR_MAC_CAP0_DPS_SUPP,
+ [1] = IEEE80211_UHR_MAC_CAP1_DUO_SUPP,
+ },
+ },
},
{
.types_mask = BIT(NL80211_IFTYPE_AP) |
@@ -788,6 +799,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,
+ },
},
};
@@ -855,6 +871,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
*/
@@ -1023,9 +1042,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:
@@ -1042,10 +1061,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],
@@ -1054,6 +1073,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)
{
@@ -1082,6 +1141,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);
@@ -1611,6 +1672,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);
@@ -1621,11 +1685,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;
@@ -1683,8 +1749,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);
@@ -1758,14 +1832,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 = &regd->reg_rules[valid_rules - 1];
@@ -2079,6 +2146,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);
@@ -2106,6 +2174,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-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 ac0a29a1c31f..52d35b73ed74 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__
@@ -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;
@@ -55,6 +56,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/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/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c
index 16b2c313e72b..0009488ca51b 100644
--- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c
+++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c
@@ -2,16 +2,14 @@
/*
* 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 <linux/kernel.h>
#include <linux/bsearch.h>
#include <linux/list.h>
-#include "fw/api/tx.h"
#include "iwl-trans.h"
#include "iwl-drv.h"
-#include "iwl-fh.h"
#include <linux/dmapool.h>
#include "fw/api/commands.h"
#include "pcie/gen1_2/internal.h"
@@ -461,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)
{
@@ -822,3 +826,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 61e4f4776dcb..3ae840e546e8 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
@@ -1019,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);
@@ -1034,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);
@@ -1153,6 +1069,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);
@@ -1236,9 +1154,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/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]);
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ap.c b/drivers/net/wireless/intel/iwlwifi/mld/ap.c
index 5c59acc8c4c5..bc426b911ce5 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 <linux/crc32.h>
@@ -239,6 +239,58 @@ int iwl_mld_store_ap_early_key(struct iwl_mld *mld,
return -ENOSPC;
}
+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 = {};
+ 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);
+}
+
+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)
@@ -276,10 +328,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..f10e9c9a38ff 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,12 @@ int iwl_mld_update_beacon_template(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,
struct ieee80211_bss_conf *link);
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c
index 3a595a1c2e00..fc0a5871df2f 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"
@@ -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,
- &notif->gtk[0].sc);
- iwl_mld_convert_ptk_resume_seq(mld, wowlan_status, &notif->gtk[0].sc);
+ &notif->gtk[0].sc, rsc_notif_ver);
+ if (rsc_notif_ver == IWL_FW_CMD_VER_UNKNOWN)
+ iwl_mld_convert_ptk_resume_seq(mld, wowlan_status,
+ &notif->gtk[0].sc);
/* only one igtk is passed by FW */
iwl_mld_convert_igtk_resume_data(wowlan_status, &notif->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;
-
- if (key->keyidx >= 0 && key->keyidx <= 3) {
+ int rsc_notif_ver = iwl_fw_lookup_notif_ver(data->mld->fw,
+ DATA_PATH_GROUP,
+ RSC_NOTIF,
+ IWL_FW_CMD_VER_UNKNOWN);
+
+ /* 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,
@@ -933,6 +956,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,
struct iwl_mld_mcast_key_data *key_data,
@@ -951,19 +1073,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/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/debugfs.c b/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c
index b05b58eb1281..351a4f177e92 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"
@@ -979,15 +979,39 @@ 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)
+/*
+ * 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,
@@ -1008,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,
@@ -1035,7 +1061,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 :
@@ -1050,20 +1075,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;
@@ -1090,18 +1117,42 @@ static ssize_t iwl_dbgfs_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_##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)
+
+/*
+ * 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,
@@ -1111,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);
}
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/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c
index 1e85a9168d2b..2b837c6fa5fe 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c
@@ -1,10 +1,11 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
*/
#include <net/cfg80211.h>
#include "iface.h"
+#include "nan.h"
#include "hcmd.h"
#include "key.h"
#include "mlo.h"
@@ -55,6 +56,24 @@ 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;
+ /* 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);
+
+ mld_vif->nan.tx_igtk = NULL;
+ }
+
+ 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);
}
@@ -94,6 +113,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 +383,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 +431,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 +475,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 +488,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)
{
@@ -430,7 +505,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
@@ -456,9 +531,40 @@ 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->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);
}
+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 +574,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 +593,52 @@ int iwl_mld_add_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
return ret;
}
+int iwl_mld_add_nan_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
+{
+ struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
+ int ret;
+
+ lockdep_assert_wiphy(mld->wiphy);
+
+ if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
+ return -EINVAL;
+
+ ret = iwl_mld_allocate_vif_fw_id(mld, &mld_vif->fw_id, vif);
+ if (ret)
+ return ret;
+
+ ret = iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_ADD);
+ if (ret) {
+ RCU_INIT_POINTER(mld->fw_id_to_vif[mld_vif->fw_id], NULL);
+ return ret;
+ }
+
+ mld_vif->nan.mac_added = true;
+
+ return 0;
+}
+
void iwl_mld_rm_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
lockdep_assert_wiphy(mld->wiphy);
- /* NAN interface type is not known to FW */
- if (vif->type == NL80211_IFTYPE_NAN)
+ if (vif->type == NL80211_IFTYPE_NAN_DATA) {
+ iwl_mld_update_nan_mac(mld, NULL);
return;
+ }
- iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_REMOVE);
-
- if (WARN_ON(mld_vif->fw_id >= ARRAY_SIZE(mld->fw_id_to_vif)))
+ if (!iwl_mld_vif_fw_id_valid(mld_vif))
return;
+ iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_REMOVE);
+
RCU_INIT_POINTER(mld->fw_id_to_vif[mld_vif->fw_id], NULL);
+ if (vif->type == NL80211_IFTYPE_NAN)
+ mld_vif->nan.mac_added = false;
+
iwl_mld_cancel_notifications_of_object(mld, IWL_MLD_OBJECT_TYPE_VIF,
mld_vif->fw_id);
}
@@ -616,24 +755,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)
{
@@ -748,6 +869,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/iface.h b/drivers/net/wireless/intel/iwlwifi/mld/iface.h
index 8dfc79fed253..bc6f45ff76f8 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h
@@ -8,6 +8,7 @@
#include <net/mac80211.h>
#include "link.h"
+#include "nan.h"
#include "session-protect.h"
#include "d3.h"
#include "fw/api/time-event.h"
@@ -137,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.
@@ -152,6 +152,16 @@ 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.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.
+ * @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 */
@@ -167,7 +177,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 */
@@ -175,6 +184,16 @@ 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 */
+ 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;
+ struct iwl_mld_int_sta mcast_data_sta;
+ struct ieee80211_key_conf *tx_igtk;
+ } nan;
+
struct iwl_mld_emlsr emlsr;
#ifdef CONFIG_PM_SLEEP
@@ -206,6 +225,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;
@@ -221,6 +254,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)
@@ -235,6 +274,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);
@@ -245,9 +285,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);
@@ -258,4 +295,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 04192c5f07ff..bf80b4770b5a 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.
@@ -53,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;
}
@@ -67,6 +69,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)) {
@@ -92,11 +128,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;
@@ -178,34 +210,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->nan.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--;
}
@@ -222,46 +289,43 @@ 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);
- 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;
+ }
- if (igtk) {
- if (mld->num_igtks == IWL_MAX_NUM_IGTKS)
+ igtk_ptr = iwl_mld_get_igtk_ptr(vif, sta, key);
+ if (igtk_ptr) {
+ if (mld->num_igtks == mld->fw->ucode_capa.num_mcast_key_entries)
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)
+ if (ret) {
+ IWL_WARN(mld, "failed to add key to FW (%d)\n", 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.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c
index be2cdf43c72e..234821f6a441 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/link.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c
@@ -16,14 +16,34 @@
#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)
+/**
+ * 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)
{
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),
@@ -315,10 +335,28 @@ 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;
+
+ 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;
+
+ 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;
@@ -354,6 +392,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);
@@ -437,7 +490,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
@@ -608,15 +662,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);
@@ -651,35 +710,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),
+};
+
+/* 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),
};
-#define MAX_GRADE (rssi_to_grade_map[ARRAY_SIZE(rssi_to_grade_map) - 1].grade)
+/* 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
@@ -689,31 +844,27 @@ 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
-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;
@@ -787,6 +938,51 @@ 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;
+}
+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)
{
@@ -828,16 +1024,214 @@ 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;
+ }
+}
+
+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)
{
+ 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))
+ 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;
@@ -852,25 +1246,64 @@ 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 =
+ iwl_mld_get_psd_eirp_rssi_adjust(link_conf);
+
+ if (!rssi_adj_6g)
+ rssi_adj_6g = 4;
+
+ 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;
@@ -879,8 +1312,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/link.h b/drivers/net/wireless/intel/iwlwifi/mld/link.h
index ca691259fc5e..f1997e280058 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__
@@ -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
@@ -28,14 +36,17 @@ 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.
* @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.
@@ -49,6 +60,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;
@@ -57,11 +70,13 @@ 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;
- struct ieee80211_key_conf *igtk;
+ 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;
@@ -99,6 +114,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,
@@ -123,10 +145,17 @@ 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);
+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/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index b48ebec18dd5..92858b8f7395 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
*/
#include <net/mac80211.h>
@@ -62,17 +62,16 @@ 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),
},
- /* Removed when two channels are permitted */
{
- .max = 1,
- .types = BIT(NL80211_IFTYPE_AP),
+ .max = 2,
+ .types = BIT(NL80211_IFTYPE_NAN_DATA),
},
};
@@ -90,19 +89,13 @@ iwl_mld_iface_combinations[] = {
.limits = iwl_mld_limits_ap,
.n_limits = ARRAY_SIZE(iwl_mld_limits_ap),
},
- /* NAN combinations follow, these exclude P2P */
- {
- .num_different_channels = 2,
- .max_interfaces = 3,
- .limits = iwl_mld_limits_nan,
- .n_limits = ARRAY_SIZE(iwl_mld_limits_nan) - 1,
- },
+ /* NAN combination follow, this excludes P2P and AP */
{
- .num_different_channels = 1,
+ .num_different_channels = 3,
.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] = {
@@ -272,6 +265,40 @@ 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->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)
+ 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;
+
+ 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 * 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;
+ 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,37 +361,16 @@ 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;
+ iwl_mld_hw_set_nan(mld);
} else {
- wiphy->iface_combinations = iwl_mld_iface_combinations;
- /* Do not include NAN combinations */
+ /* Do not include NAN combination */
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);
@@ -414,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;
}
@@ -449,22 +457,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;
@@ -673,6 +665,28 @@ int iwl_mld_mac80211_add_interface(struct ieee80211_hw *hw,
if (ret)
return ret;
+ 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
* the HW is restarting and it will be configured by change_vif_links.
@@ -739,10 +753,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
+ } 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);
@@ -988,6 +1009,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)
@@ -1003,9 +1048,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 &&
@@ -1231,33 +1286,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)
- iwl_mld_update_beacon_template(mld, vif, link);
-}
-
static
u32 iwl_mld_link_changed_mapping(struct iwl_mld *mld,
struct ieee80211_vif *vif,
@@ -1289,6 +1317,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;
}
@@ -1367,6 +1398,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);
@@ -1382,28 +1417,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,
@@ -1414,6 +1427,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;
@@ -1436,10 +1454,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 */
}
@@ -1609,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);
@@ -1825,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 */
@@ -1846,7 +1862,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.
@@ -1891,7 +1906,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
@@ -2236,7 +2250,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);
@@ -2278,7 +2291,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 */
@@ -2296,9 +2311,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);
@@ -2559,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
@@ -2847,4 +2966,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/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 <net/cfg80211.h>
@@ -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:
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c
index 9af79297c3b6..78c78cf891cd 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
*/
#include <linux/rtnetlink.h>
#include <net/mac80211.h>
@@ -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),
@@ -236,6 +235,10 @@ 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_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),
@@ -259,6 +262,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),
@@ -411,9 +415,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);
@@ -675,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,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.h b/drivers/net/wireless/intel/iwlwifi/mld/mld.h
index 606cb64f8ea4..922aa3dbff54 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;
@@ -555,15 +552,24 @@ 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)
{
+ 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/mlo.c b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c
index 8227ccb31d60..a2f8a6957535 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,32 @@ 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;
+
+ /* 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)) {
+ /* 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 +1138,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/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
index 4d8e85f2bd7c..d34a9a2cbeae 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c
@@ -5,8 +5,12 @@
#include "mld.h"
#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"
#define IWL_NAN_DISOVERY_BEACON_INTERNVAL_TU 512
#define IWL_NAN_RSSI_CLOSE 55
@@ -14,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,
@@ -38,6 +50,98 @@ static int iwl_mld_nan_send_config_cmd(struct iwl_mld *mld,
return iwl_mld_send_cmd(mld, &hcmd);
}
+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,
@@ -114,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);
@@ -124,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);
@@ -134,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);
@@ -174,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),
};
@@ -191,8 +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
*/
- 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
@@ -247,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)
{
@@ -257,7 +396,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;
@@ -266,13 +405,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
@@ -293,6 +426,581 @@ 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);
}
+
+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;
+
+ lockdep_assert_wiphy(mld->wiphy);
+
+ ret = iwl_mld_allocate_link_fw_id(mld, &fw_id, ERR_PTR(-ENODEV));
+ /*
+ * 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];
+
+ 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 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,
+ 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;
+ /* TODO: when FW is ready, Update phy in TLC to invalid after */
+ }
+
+ return 0;
+}
+
+static void iwl_mld_nan_link_remove(struct iwl_mld *mld,
+ struct iwl_mld_nan_link *nan_link,
+ u32 link_id)
+{
+ struct iwl_link_config_cmd cmd = {
+ .link_id = cpu_to_le32(link_id),
+ .phy_id = cpu_to_le32(FW_CTXT_ID_INVALID),
+ };
+
+ iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_REMOVE);
+
+ 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;
+}
+
+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;
+}
+
+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)
+{
+ 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;
+ unsigned long remove_link_ids = 0;
+ 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;
+
+ 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))) {
+ 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",
+ version);
+ return;
+ }
+
+ for (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;
+ }
+
+ /* this currently just uses the same index */
+ 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;
+ 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 (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]) {
+ added_links = true;
+ link_used[links[i]->fw_id] = true;
+ iwl_mld_nan_link_set_active(mld, vif, links[i], true);
+ }
+ }
+
+ /* 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, cmd_id, &cmd, cmd_size);
+ if (ret)
+ IWL_ERR(mld, "NAN: failed to update schedule (%d)\n", ret);
+
+ /* 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, 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;
+ }
+ }
+
+ 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);
+ }
+
+ /*
+ * 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 */
+ 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 */
+ 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_error_before_recovery(mld)))
+ iwl_mld_rm_vif(mld, vif);
+ }
+}
+
+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)
+{
+ 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,
+ };
+ 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)
+ 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 c04d77208971..5411bca52cde 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/nan.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.h
@@ -1,12 +1,31 @@
/* 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__
#include <net/cfg80211.h>
#include <linux/etherdevice.h>
+/**
+ * struct iwl_mld_nan_link - struct representing a NAN link
+ * @chanctx: the channel context
+ * @active: indicates the NAN link is currently active
+ * @fw_id: FW link ID
+ */
+struct iwl_mld_nan_link {
+ struct ieee80211_chanctx_conf *chanctx;
+ bool active;
+ u8 fw_id;
+};
+
+/* Cleanup function for struct iwl_mld_nan_link, will be called in restart */
+static inline void iwl_mld_cleanup_nan_link(struct iwl_mld_nan_link *nan_link)
+{
+ memset(nan_link, 0, sizeof(*nan_link));
+ nan_link->fw_id = FW_CTXT_ID_INVALID;
+}
+
bool iwl_mld_nan_supported(struct iwl_mld *mld);
int iwl_mld_start_nan(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
@@ -19,13 +38,33 @@ 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);
+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);
+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);
+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);
+
+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);
+
+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/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c
index 9c88a8579a75..7574689e4088 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"
@@ -307,11 +307,8 @@ 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)
CMD_VER_ENTRY(4, iwl_tlc_update_notif))
CMD_VERSIONS(mu_mimo_grp_notif,
CMD_VER_ENTRY(1, iwl_mu_group_mgmt_notif))
@@ -330,7 +327,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))
+ 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,
@@ -341,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,
@@ -350,7 +346,10 @@ 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))
+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)
@@ -365,8 +364,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)
@@ -457,8 +454,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)
@@ -471,8 +466,12 @@ 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)
+ 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);
@@ -607,6 +606,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
@@ -630,6 +634,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)
@@ -685,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);
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 <net/mac80211.h>
@@ -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);
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/power.c b/drivers/net/wireless/intel/iwlwifi/mld/power.c
index 49b0d9f8f865..da065a446f81 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 <net/mac80211.h>
@@ -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);
@@ -113,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;
@@ -175,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;
@@ -196,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;
@@ -252,9 +271,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 &&
@@ -265,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 =
@@ -289,17 +308,118 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld,
#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
iwl_mld_power_configure_uapsd(mld, link, cmd, ps_poll);
}
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);
+
+ if (cmd_ver >= 3) {
+ struct iwl_mac_power_cmd cmd = {};
- iwl_mld_power_build_cmd(mld, vif, &cmd, d3);
+ 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 = {};
- return iwl_mld_send_cmd_pdu(mld, MAC_PM_POWER_TABLE, &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/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);
}
}
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c
index a2e586c6ea67..269439d789f4 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;
@@ -158,7 +157,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);
@@ -1206,6 +1205,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 +1320,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);
}
@@ -2262,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,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.c b/drivers/net/wireless/intel/iwlwifi/mld/sta.c
index 4c97d12ce2d0..77eeeed66116 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/sta.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
*/
#include <linux/ieee80211.h>
@@ -13,6 +13,7 @@
#include "key.h"
#include "agg.h"
#include "tlc.h"
+#include "nan.h"
#include "fw/api/sta.h"
#include "fw/api/mac.h"
#include "fw/api/rx.h"
@@ -43,13 +44,13 @@ int iwl_mld_fw_sta_id_from_link_sta(struct iwl_mld *mld,
static void
iwl_mld_fill_ampdu_size_and_dens(struct ieee80211_link_sta *link_sta,
- struct ieee80211_bss_conf *link,
+ bool is_6ghz,
__le32 *tx_ampdu_max_size,
__le32 *tx_ampdu_spacing)
{
u32 agg_size = 0, mpdu_dens = 0;
- if (WARN_ON(!link_sta || !link))
+ if (WARN_ON(!link_sta))
return;
/* Note that we always use only legacy & highest supported PPDUs, so
@@ -63,7 +64,7 @@ iwl_mld_fill_ampdu_size_and_dens(struct ieee80211_link_sta *link_sta,
mpdu_dens = link_sta->ht_cap.ampdu_density;
}
- if (link->chanreq.oper.chan->band == NL80211_BAND_6GHZ) {
+ if (is_6ghz) {
/* overwrite HT values on 6 GHz */
mpdu_dens =
le16_get_bits(link_sta->he_6ghz_capa.capa,
@@ -427,8 +428,14 @@ 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) &&
+ cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_MGMT) &&
hweight32(le32_to_cpu(cmd->link_mask)) != 1)) {
return -EINVAL;
}
@@ -439,29 +446,61 @@ 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)
+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)
{
struct ieee80211_sta *sta = link_sta->sta;
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
- struct ieee80211_bss_conf *link;
- struct iwl_mld_link *mld_link;
struct iwl_sta_cfg_cmd cmd = {};
int fw_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta);
+ bool is_6ghz, uora_exists;
+ u32 link_mask;
lockdep_assert_wiphy(mld->wiphy);
- link = link_conf_dereference_protected(mld_sta->vif,
- link_sta->link_id);
+ if (WARN_ON(fw_id < 0))
+ return -EINVAL;
- mld_link = iwl_mld_link_from_mac80211(link);
+ if (mld_sta->sta_type == STATION_TYPE_NAN_PEER_NMI ||
+ mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI) {
+ if (WARN_ON(!mld->nan_device_vif))
+ return -EINVAL;
- if (WARN_ON(!link || !mld_link) || fw_id < 0)
- return -EINVAL;
+ is_6ghz = false;
+ uora_exists = false;
+
+ link_mask = iwl_mld_get_nan_link_mask(mld);
+ } 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 +538,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 +550,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,10 +564,29 @@ 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);
}
-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)
@@ -759,10 +817,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;
@@ -1001,10 +1072,9 @@ 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,
- u8 fw_link_id,
- const u8 *addr)
+ u32 link_mask, const u8 *addr)
{
struct iwl_sta_cfg_cmd cmd = {};
@@ -1012,20 +1082,30 @@ 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.
* 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) {
- 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);
@@ -1034,7 +1114,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;
@@ -1046,11 +1127,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_set_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);
@@ -1085,8 +1169,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,
@@ -1105,14 +1189,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,
@@ -1129,23 +1215,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);
@@ -1346,3 +1434,82 @@ remove_added_link_stas:
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);
+}
+
+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)
+{
+ 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);
+}
+
+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 36288c2fb38c..98d693235c66 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__
@@ -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;
@@ -195,6 +199,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);
@@ -270,4 +276,28 @@ 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);
+
+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/stats.c b/drivers/net/wireless/intel/iwlwifi/mld/stats.c
index 54eb0ead78ee..e7b283cbe199 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,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 *notif =
- (void *)&pkt->data;
- const struct iwl_stats_ntfy_per_sta *per_sta =
- &notif->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 = &notif->per_sta[fw_sta_id];
+ } else {
+ const struct iwl_system_statistics_notif_oper_v3 *notif =
+ (void *)&pkt->data;
+ per_sta = &notif->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
*/
@@ -303,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,
@@ -321,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 */
@@ -435,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);
@@ -448,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)*/
}
@@ -507,6 +558,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);
}
@@ -523,17 +578,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 *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,
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 <kunit/static_stub.h>
+#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);
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 <kunit/static_stub.h>
@@ -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,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c
index dce747270167..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 <kunit/test.h>
#include <kunit/test-bug.h>
@@ -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)
@@ -94,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)
@@ -199,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,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c
index 78d6162d9297..a03834d3ac65 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
*/
#include <net/mac80211.h>
@@ -15,9 +15,9 @@
#include "fw/api/context.h"
#include "fw/api/dhc.h"
-static u8 iwl_mld_fw_bw_from_sta_bw(const struct ieee80211_link_sta *link_sta)
+static u8 iwl_mld_fw_bw_from_sta_bw(enum ieee80211_sta_rx_bandwidth bandwidth)
{
- switch (link_sta->bandwidth) {
+ switch (bandwidth) {
case IEEE80211_STA_RX_BW_320:
return IWL_TLC_MNG_CH_WIDTH_320MHZ;
case IEEE80211_STA_RX_BW_160:
@@ -32,40 +32,61 @@ static u8 iwl_mld_fw_bw_from_sta_bw(const struct ieee80211_link_sta *link_sta)
}
}
+struct iwl_mld_tlc_sta_capa {
+ u16 non_ht_rates;
+ u16 max_amsdu_len;
+ u8 rx_nss;
+ enum ieee80211_sta_rx_bandwidth bandwidth;
+ enum ieee80211_smps_mode smps_mode;
+ const struct ieee80211_sta_ht_cap *ht_cap;
+ const struct ieee80211_sta_vht_cap *vht_cap;
+ const struct ieee80211_sta_he_cap *he_cap;
+ const struct ieee80211_sta_eht_cap *eht_cap;
+ const struct ieee80211_sta_uhr_cap *uhr_cap;
+
+ const struct ieee80211_sta_he_cap *own_he_cap;
+ const struct ieee80211_sta_eht_cap *own_eht_cap;
+ const struct ieee80211_sta_uhr_cap *own_uhr_cap;
+};
+
static __le16
iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld,
- struct ieee80211_vif *vif,
- struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_he_cap *own_he_cap,
- const struct ieee80211_sta_eht_cap *own_eht_cap,
- const struct ieee80211_sta_uhr_cap *own_uhr_cap)
+ struct iwl_mld_tlc_sta_capa *capa)
{
- struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap;
- struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap;
- struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
- bool has_vht = vht_cap->vht_supported;
+ const struct ieee80211_sta_ht_cap *ht_cap = capa->ht_cap;
+ const struct ieee80211_sta_vht_cap *vht_cap = capa->vht_cap;
+ const struct ieee80211_sta_he_cap *he_cap = capa->he_cap;
+ const struct ieee80211_sta_eht_cap *eht_cap = capa->eht_cap;
+ const struct ieee80211_sta_uhr_cap *uhr_cap = capa->uhr_cap;
+ const struct ieee80211_sta_he_cap *own_he_cap = capa->own_he_cap;
+ const struct ieee80211_sta_eht_cap *own_eht_cap = capa->own_eht_cap;
+ const struct ieee80211_sta_uhr_cap *own_uhr_cap = capa->own_uhr_cap;
+ bool has_vht = vht_cap && vht_cap->vht_supported;
u16 flags = 0;
/* STBC flags */
if (mld->cfg->ht_params.stbc &&
(hweight8(iwl_mld_get_valid_tx_ant(mld)) > 1)) {
- if (he_cap->has_he && he_cap->he_cap_elem.phy_cap_info[2] &
- IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ)
+ if (he_cap && he_cap->has_he &&
+ he_cap->he_cap_elem.phy_cap_info[2] &
+ IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ)
flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK;
- else if (vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK)
+ else if (vht_cap &&
+ vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK)
flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK;
- else if (ht_cap->cap & IEEE80211_HT_CAP_RX_STBC)
+ else if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_RX_STBC)
flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK;
}
/* LDPC */
if (mld->cfg->ht_params.ldpc &&
- ((ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) ||
+ ((ht_cap && ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) ||
(has_vht && (vht_cap->cap & IEEE80211_VHT_CAP_RXLDPC))))
flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK;
- if (he_cap->has_he && (he_cap->he_cap_elem.phy_cap_info[1] &
- IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD))
+ if (he_cap && he_cap->has_he &&
+ (he_cap->he_cap_elem.phy_cap_info[1] &
+ IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD))
flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK;
if (own_he_cap &&
@@ -74,7 +95,7 @@ iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld,
flags &= ~IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK;
/* DCM */
- if (he_cap->has_he &&
+ if (he_cap && he_cap->has_he &&
(he_cap->he_cap_elem.phy_cap_info[3] &
IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_MASK &&
own_he_cap &&
@@ -85,15 +106,15 @@ iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld,
/* Extra EHT LTF */
if (own_eht_cap &&
own_eht_cap->eht_cap_elem.phy_cap_info[5] &
- IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF &&
- link_sta->eht_cap.has_eht &&
- link_sta->eht_cap.eht_cap_elem.phy_cap_info[5] &
- IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF) {
+ IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF &&
+ eht_cap && eht_cap->has_eht &&
+ eht_cap->eht_cap_elem.phy_cap_info[5] &
+ IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF) {
flags |= IWL_TLC_MNG_CFG_FLAGS_EHT_EXTRA_LTF_MSK;
}
- if (link_sta->uhr_cap.has_uhr && own_uhr_cap &&
- link_sta->uhr_cap.phy.cap & IEEE80211_UHR_PHY_CAP_ELR_RX &&
+ if (uhr_cap && uhr_cap->has_uhr && own_uhr_cap &&
+ uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_RX &&
own_uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_TX)
flags |= IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_1_5_MBPS_MSK |
IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_3_MBPS_MSK;
@@ -114,27 +135,27 @@ static u8 iwl_mld_get_fw_chains(struct iwl_mld *mld)
return fw_chains;
}
-static u8 iwl_mld_get_fw_sgi(struct ieee80211_link_sta *link_sta)
+static u8 iwl_mld_get_fw_sgi(struct iwl_mld_tlc_sta_capa *capa)
{
- struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap;
- struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap;
- struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
+ const struct ieee80211_sta_ht_cap *ht_cap = capa->ht_cap;
+ const struct ieee80211_sta_vht_cap *vht_cap = capa->vht_cap;
+ const struct ieee80211_sta_he_cap *he_cap = capa->he_cap;
u8 sgi_chwidths = 0;
/* If the association supports HE, HT/VHT rates will never be used for
* Tx and therefor there's no need to set the
* sgi-per-channel-width-support bits
*/
- if (he_cap->has_he)
+ if (he_cap && he_cap->has_he)
return 0;
- if (ht_cap->cap & IEEE80211_HT_CAP_SGI_20)
+ if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_SGI_20)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_20MHZ);
- if (ht_cap->cap & IEEE80211_HT_CAP_SGI_40)
+ if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_SGI_40)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_40MHZ);
- if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80)
+ if (vht_cap && vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_80MHZ);
- if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160)
+ if (vht_cap && vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160)
sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_160MHZ);
return sgi_chwidths;
@@ -164,31 +185,30 @@ iwl_mld_get_highest_fw_mcs(const struct ieee80211_sta_vht_cap *vht_cap,
}
static void
-iwl_mld_fill_vht_rates(const struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_vht_cap *vht_cap,
+iwl_mld_fill_vht_rates(struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
u32 supp;
int i, highest_mcs;
- u8 max_nss = link_sta->rx_nss;
+ u8 max_nss = capa->rx_nss;
struct ieee80211_vht_cap ieee_vht_cap = {
- .vht_cap_info = cpu_to_le32(vht_cap->cap),
- .supp_mcs = vht_cap->vht_mcs,
+ .vht_cap_info = cpu_to_le32(capa->vht_cap->cap),
+ .supp_mcs = capa->vht_cap->vht_mcs,
};
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC)
max_nss = 1;
for (i = 0; i < max_nss && i < IWL_TLC_NSS_MAX; i++) {
int nss = i + 1;
- highest_mcs = iwl_mld_get_highest_fw_mcs(vht_cap, nss);
+ highest_mcs = iwl_mld_get_highest_fw_mcs(capa->vht_cap, nss);
if (!highest_mcs)
continue;
supp = BIT(highest_mcs + 1) - 1;
- if (link_sta->bandwidth == IEEE80211_STA_RX_BW_20)
+ if (capa->bandwidth == IEEE80211_STA_RX_BW_20)
supp &= ~BIT(IWL_TLC_MNG_HT_RATE_MCS9);
cmd->ht_rates[i][IWL_TLC_MCS_PER_BW_80] = cpu_to_le32(supp);
@@ -196,7 +216,7 @@ iwl_mld_fill_vht_rates(const struct ieee80211_link_sta *link_sta,
* configuration is supported - only for MCS 0 since we already
* decoded the MCS bits anyway ourselves.
*/
- if (link_sta->bandwidth == IEEE80211_STA_RX_BW_160 &&
+ if (capa->bandwidth == IEEE80211_STA_RX_BW_160 &&
ieee80211_get_vht_max_nss(&ieee_vht_cap,
IEEE80211_VHT_CHANWIDTH_160MHZ,
0, true, nss) >= nss)
@@ -223,20 +243,19 @@ static u32 iwl_mld_he_mac80211_mcs_to_fw_mcs(u16 mcs)
}
static void
-iwl_mld_fill_he_rates(const struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_he_cap *own_he_cap,
+iwl_mld_fill_he_rates(struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
- const struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
+ const struct ieee80211_sta_he_cap *he_cap = capa->he_cap;
u16 mcs_160 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160);
u16 mcs_80 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80);
- u16 tx_mcs_80 = le16_to_cpu(own_he_cap->he_mcs_nss_supp.tx_mcs_80);
- u16 tx_mcs_160 = le16_to_cpu(own_he_cap->he_mcs_nss_supp.tx_mcs_160);
+ u16 tx_mcs_80 = le16_to_cpu(capa->own_he_cap->he_mcs_nss_supp.tx_mcs_80);
+ u16 tx_mcs_160 = le16_to_cpu(capa->own_he_cap->he_mcs_nss_supp.tx_mcs_160);
int i;
- u8 nss = link_sta->rx_nss;
+ u8 nss = capa->rx_nss;
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC)
nss = 1;
for (i = 0; i < nss && i < IWL_TLC_NSS_MAX; i++) {
@@ -313,17 +332,15 @@ static u8 iwl_mld_get_eht_max_nss(u8 rx_nss, u8 tx_nss)
static void
iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
- const struct ieee80211_link_sta *link_sta,
- const struct ieee80211_sta_he_cap *own_he_cap,
- const struct ieee80211_sta_eht_cap *own_eht_cap,
+ struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
/* peer RX mcs capa */
const struct ieee80211_eht_mcs_nss_supp *eht_rx_mcs =
- &link_sta->eht_cap.eht_mcs_nss_supp;
+ &capa->eht_cap->eht_mcs_nss_supp;
/* our TX mcs capa */
const struct ieee80211_eht_mcs_nss_supp *eht_tx_mcs =
- &own_eht_cap->eht_mcs_nss_supp;
+ &capa->own_eht_cap->eht_mcs_nss_supp;
enum IWL_TLC_MCS_PER_BW bw;
struct ieee80211_eht_mcs_nss_supp_20mhz_only mcs_rx_20;
@@ -331,7 +348,7 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
/* peer is 20 MHz only */
if (vif->type == NL80211_IFTYPE_AP &&
- !(link_sta->he_cap.he_cap_elem.phy_cap_info[0] &
+ !(capa->he_cap->he_cap_elem.phy_cap_info[0] &
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL)) {
mcs_rx_20 = eht_rx_mcs->only_20mhz;
} else {
@@ -346,7 +363,7 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
}
/* NIC is capable of 20 MHz only */
- if (!(own_he_cap->he_cap_elem.phy_cap_info[0] &
+ if (!(capa->own_he_cap->he_cap_elem.phy_cap_info[0] &
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL)) {
mcs_tx_20 = eht_tx_mcs->only_20mhz;
} else {
@@ -402,67 +419,51 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif,
}
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC ||
- link_sta->rx_nss < 2)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC ||
+ capa->rx_nss < 2)
memset(cmd->ht_rates[IWL_TLC_NSS_2], 0,
sizeof(cmd->ht_rates[IWL_TLC_NSS_2]));
}
static void
-iwl_mld_fill_supp_rates(struct iwl_mld *mld, struct ieee80211_vif *vif,
- struct ieee80211_link_sta *link_sta,
- struct ieee80211_supported_band *sband,
- const struct ieee80211_sta_he_cap *own_he_cap,
- const struct ieee80211_sta_eht_cap *own_eht_cap,
- const struct ieee80211_sta_uhr_cap *own_uhr_cap,
+iwl_mld_fill_supp_rates(struct iwl_mld *mld,
+ struct ieee80211_vif *vif,
+ struct iwl_mld_tlc_sta_capa *capa,
struct iwl_tlc_config_cmd *cmd)
{
- int i;
- u16 non_ht_rates = 0;
- unsigned long rates_bitmap;
- const struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap;
- const struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap;
- const struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
-
- /* non HT rates */
- rates_bitmap = link_sta->supp_rates[sband->band];
- for_each_set_bit(i, &rates_bitmap, BITS_PER_LONG)
- non_ht_rates |= BIT(sband->bitrates[i].hw_value);
-
- cmd->non_ht_rates = cpu_to_le16(non_ht_rates);
+ cmd->non_ht_rates = cpu_to_le16(capa->non_ht_rates);
cmd->mode = IWL_TLC_MNG_MODE_NON_HT;
- if (link_sta->uhr_cap.has_uhr && own_uhr_cap) {
+ if (capa->uhr_cap && capa->uhr_cap->has_uhr && capa->own_uhr_cap) {
cmd->mode = IWL_TLC_MNG_MODE_UHR;
/*
* FIXME: spec currently inherits from EHT but has no
* finer MCS bits. Once that's there, need to add them
* to the bitmaps (and maybe copy this to UHR, or so.)
*/
- iwl_mld_fill_eht_rates(vif, link_sta, own_he_cap,
- own_eht_cap, cmd);
- } else if (link_sta->eht_cap.has_eht && own_he_cap && own_eht_cap) {
+ iwl_mld_fill_eht_rates(vif, capa, cmd);
+ } else if (capa->eht_cap && capa->eht_cap->has_eht &&
+ capa->own_he_cap && capa->own_eht_cap) {
cmd->mode = IWL_TLC_MNG_MODE_EHT;
- iwl_mld_fill_eht_rates(vif, link_sta, own_he_cap,
- own_eht_cap, cmd);
- } else if (he_cap->has_he && own_he_cap) {
+ iwl_mld_fill_eht_rates(vif, capa, cmd);
+ } else if (capa->he_cap && capa->he_cap->has_he && capa->own_he_cap) {
cmd->mode = IWL_TLC_MNG_MODE_HE;
- iwl_mld_fill_he_rates(link_sta, own_he_cap, cmd);
- } else if (vht_cap->vht_supported) {
+ iwl_mld_fill_he_rates(capa, cmd);
+ } else if (capa->vht_cap && capa->vht_cap->vht_supported) {
cmd->mode = IWL_TLC_MNG_MODE_VHT;
- iwl_mld_fill_vht_rates(link_sta, vht_cap, cmd);
- } else if (ht_cap->ht_supported) {
+ iwl_mld_fill_vht_rates(capa, cmd);
+ } else if (capa->ht_cap && capa->ht_cap->ht_supported) {
cmd->mode = IWL_TLC_MNG_MODE_HT;
cmd->ht_rates[IWL_TLC_NSS_1][IWL_TLC_MCS_PER_BW_80] =
- cpu_to_le32(ht_cap->mcs.rx_mask[0]);
+ cpu_to_le32(capa->ht_cap->mcs.rx_mask[0]);
/* the station support only a single receive chain */
- if (link_sta->smps_mode == IEEE80211_SMPS_STATIC)
+ if (capa->smps_mode == IEEE80211_SMPS_STATIC)
cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_MCS_PER_BW_80] =
0;
else
cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_MCS_PER_BW_80] =
- cpu_to_le32(ht_cap->mcs.rx_mask[1]);
+ cpu_to_le32(capa->ht_cap->mcs.rx_mask[1]);
}
}
@@ -527,60 +528,32 @@ 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_mask, 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.sta_mask = cpu_to_le32(fw_sta_mask);
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;
@@ -641,16 +614,32 @@ 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 ieee80211_supported_band *sband;
+ unsigned long rates_bitmap;
+ enum nl80211_band band;
+ int i;
- if (WARN_ON_ONCE(!link_conf->chanreq.oper.chan))
+ /*
+ * 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.)
+ */
+
+ if (WARN_ON(!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 +649,21 @@ 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->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;
+
+ iwl_mld_send_tlc_cmd(mld, vif, mld_sta, fw_sta_mask,
+ iwl_mld_phy_from_mac80211(chan_ctx)->fw_id,
+ capa);
}
void iwl_mld_tlc_update_phy(struct iwl_mld *mld, struct ieee80211_vif *vif,
@@ -706,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)
{
@@ -714,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);
@@ -822,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 0bcb1ae69468..1e8716cbb4ce 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2024 - 2025 Intel Corporation
+ * Copyright (C) 2024 - 2026 Intel Corporation
*/
#include <net/ip.h>
@@ -71,14 +71,19 @@ static int iwl_mld_allocate_txq(struct iwl_mld *mld, struct ieee80211_txq *txq)
{
u8 tid = txq->tid == IEEE80211_NUM_TIDS ? IWL_MGMT_TID : txq->tid;
u32 fw_sta_mask = iwl_mld_fw_sta_id_mask(mld, txq->sta);
- /* We can't know when the station is asleep or awake, so we
- * must disable the queue hang detection.
- */
- unsigned int watchdog_timeout = txq->vif->type == NL80211_IFTYPE_AP ?
- IWL_WATCHDOG_DISABLED :
- mld->trans->mac_cfg->base->wd_timeout;
+ unsigned int watchdog_timeout;
int queue, size;
+ switch (txq->vif->type) {
+ case NL80211_IFTYPE_AP: /* STA might go to PS */
+ case NL80211_IFTYPE_NAN_DATA: /* peer might ULW/break schedule */
+ watchdog_timeout = IWL_WATCHDOG_DISABLED;
+ break;
+ default:
+ watchdog_timeout = mld->trans->mac_cfg->base->wd_timeout;
+ break;
+ }
+
lockdep_assert_wiphy(mld->wiphy);
if (tid == IWL_MGMT_TID)
@@ -346,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;
}
@@ -489,7 +495,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
@@ -552,10 +558,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,
@@ -673,11 +681,16 @@ 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 iwl_mld_nan_get_mgmt_queue(mld, info->control.vif);
+ case NL80211_IFTYPE_NAN_DATA:
+ WARN_ON(!ieee80211_is_data(fc));
- return mld_vif->aux_sta.queue_id;
+ 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;
@@ -1018,14 +1031,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;
@@ -1156,7 +1166,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);
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 <net/cfg80211.h>
#include <linux/etherdevice.h>
@@ -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;
}
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/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/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);
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);
}
}
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);
diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c
index dc99e7ac4726..608100bc6b11 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)},
@@ -1052,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,
@@ -1069,7 +1073,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)),
@@ -1077,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)),
@@ -1239,8 +1245,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,
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..abc0c831d1ca 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
*
@@ -1086,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,
@@ -1153,6 +1255,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);
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 64262bcca55d..0e324aeb9055 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;
}
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..1c4ee76d8387 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
*/
@@ -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;
@@ -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,
@@ -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)
{
@@ -3201,8 +3255,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;
}
@@ -4000,6 +4053,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;
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;
- }
+ 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++;
+ 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++;
}
}
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 67a33e5e7d54..6ea082157307 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 },
@@ -2816,7 +2842,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,
@@ -3505,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;
@@ -3516,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;
@@ -7440,8 +7476,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 };
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,8 +2305,15 @@ 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);
@@ -2256,15 +2321,6 @@ int _ieee80211_link_use_channel(struct ieee80211_link_data *link,
ctx = ieee80211_find_or_create_chanctx(sdata, chanreq, mode,
assign_on_failure, &reused_ctx);
if (IS_ERR(ctx)) {
- /* Try to evacuate a NAN channel to free up a chanctx */
- if (ieee80211_nan_try_evacuate(&local->hw, NULL))
- ctx = ieee80211_find_or_create_chanctx(sdata, chanreq,
- mode,
- assign_on_failure,
- &reused_ctx);
- }
-
- if (IS_ERR(ctx)) {
ret = PTR_ERR(ctx);
goto out;
}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index fc4424b125c1..18a101710432 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);
@@ -2054,6 +2059,29 @@ 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)
+{
+ 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);
@@ -2155,11 +2183,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,
@@ -2818,10 +2841,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,
@@ -2926,6 +2975,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/nan.c b/net/mac80211/nan.c
index cea620aaee6a..1800bb96dd29 100644
--- a/net/mac80211/nan.c
+++ b/net/mac80211/nan.c
@@ -712,7 +712,7 @@ out:
return ret;
}
-static void
+void
ieee80211_nan_evacuate_channel(struct ieee80211_sub_if_data *sdata,
struct ieee80211_nan_channel *nan_channel)
{
@@ -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)
+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);
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 <michael.bommarito@gmail.com>
+ */
+#include <kunit/test.h>
+#include <linux/ieee80211.h>
+#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);
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, &params,
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, &params, NULL, NULL);
+ ieee80211_fill_ifcomb_params(local, &params, NULL, NULL, NULL, NULL);
err = cfg80211_iter_combinations(local->hw.wiphy, &params,
ieee80211_iter_max_chans,
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;
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 <linux/if_arp.h>
#include <linux/etherdevice.h>
#include <linux/slab.h>
+#include <linux/string.h>
#include <net/iw_handler.h>
#include <net/cfg80211.h>
#include <net/cfg80211-wext.h>
@@ -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;
}