From 878654eb78c6aa0ff585baf1376567c775ca28ec Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Fri, 24 Jul 2026 08:56:13 -0700 Subject: [PATCH 01/18] wifi: ath12k: fix overreads in ath12k_wmi_process_csa_switch_count_event() There is no policy entry for WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT, so the parse infrastructure does not enforce a minimum length for the event struct. Additionally, the num_vdevs field is taken directly from firmware and used as a loop bound over the vdev_ids array without checking that it fits within the TLV payload. Either condition can cause an out-of-bounds read. Add a TLV policy entry for WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT so the parse infrastructure enforces a minimum length for the fixed-size event struct. Add a helper ath12k_wmi_tlv_data_len() to recover the payload length of a parsed TLV from the header preceding its data pointer. Use it in ath12k_wmi_process_csa_switch_count_event() to bound num_vdevs before the loop. Compile tested only. Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260724-ath12k_wmi_process_csa_switch_count_event-cleanup-v2-1-02a45d7246c0@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 2b707ffc1a20..c2833d1e95d3 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -207,6 +207,8 @@ static const struct ath12k_wmi_tlv_policy ath12k_wmi_tlv_policies[] = { .min_len = sizeof(struct wmi_per_chain_rssi_stat_params) }, [WMI_TAG_OBSS_COLOR_COLLISION_EVT] = { .min_len = sizeof(struct wmi_obss_color_collision_event) }, + [WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT] = { + .min_len = sizeof(struct ath12k_wmi_pdev_csa_event) }, }; __le32 ath12k_wmi_tlv_hdr(u32 cmd, u32 len) @@ -374,6 +376,13 @@ ath12k_wmi_tlv_parse(struct ath12k_base *ab, struct sk_buff *skb) return tb; } +static u32 ath12k_wmi_tlv_data_len(const void *data) +{ + const struct wmi_tlv *tlv = (const struct wmi_tlv *)data - 1; + + return le32_get_bits(tlv->header, WMI_TLV_LEN); +} + static int ath12k_wmi_cmd_send_nowait(struct ath12k_wmi_pdev *wmi, struct sk_buff *skb, u32 cmd_id) { @@ -9075,12 +9084,19 @@ ath12k_wmi_process_csa_switch_count_event(struct ath12k_base *ab, const u32 *vdev_ids) { u32 current_switch_count = le32_to_cpu(ev->current_switch_count); + u32 vdev_ids_len = ath12k_wmi_tlv_data_len(vdev_ids); u32 num_vdevs = le32_to_cpu(ev->num_vdevs); struct ieee80211_bss_conf *conf; struct ath12k_link_vif *arvif; struct ath12k_vif *ahvif; int i; + if (num_vdevs > vdev_ids_len / sizeof(*vdev_ids)) { + ath12k_warn(ab, "csa switch count num_vdevs %u exceeds tlv array length %u\n", + num_vdevs, vdev_ids_len); + return; + } + rcu_read_lock(); for (i = 0; i < num_vdevs; i++) { arvif = ath12k_mac_get_arvif_by_vdev_id(ab, vdev_ids[i]); From 208d7fdb85976a737a715b81d54efaff6703880c Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Fri, 24 Jul 2026 08:56:14 -0700 Subject: [PATCH 02/18] wifi: ath11k: fix overreads in ath11k_wmi_process_csa_switch_count_event() There is no policy entry for WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT, so the parse infrastructure does not enforce a minimum length for the event struct. Additionally, the num_vdevs field is taken directly from firmware and used as a loop bound over the vdev_ids array without checking that it fits within the TLV payload. Either condition can cause an out-of-bounds read. Add a TLV policy entry for WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT so the parse infrastructure enforces a minimum length for the fixed-size event struct. Add a helper ath11k_wmi_tlv_data_len() to recover the payload length of a parsed TLV from the header preceding its data pointer. Use it in ath11k_wmi_process_csa_switch_count_event() to bound num_vdevs before the loop. Compile tested only. Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260724-ath12k_wmi_process_csa_switch_count_event-cleanup-v2-2-02a45d7246c0@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 4cbd7293845a..d6feaa710fe2 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -159,6 +159,8 @@ static const struct wmi_tlv_policy wmi_tlv_policies[] = { .min_len = sizeof(struct ath11k_wmi_p2p_noa_info) }, [WMI_TAG_P2P_NOA_EVENT] = { .min_len = sizeof(struct wmi_p2p_noa_event) }, + [WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT] = { + .min_len = sizeof(struct wmi_pdev_csa_switch_ev) }, }; #define PRIMAP(_hw_mode_) \ @@ -262,6 +264,13 @@ const void **ath11k_wmi_tlv_parse_alloc(struct ath11k_base *ab, return tb; } +static u32 ath11k_wmi_tlv_data_len(const void *data) +{ + const struct wmi_tlv *tlv = (const struct wmi_tlv *)data - 1; + + return FIELD_GET(WMI_TLV_LEN, tlv->header); +} + static int ath11k_wmi_cmd_send_nowait(struct ath11k_pdev_wmi *wmi, struct sk_buff *skb, u32 cmd_id) { @@ -8359,15 +8368,23 @@ ath11k_wmi_process_csa_switch_count_event(struct ath11k_base *ab, const struct wmi_pdev_csa_switch_ev *ev, const u32 *vdev_ids) { - int i; + u32 vdev_ids_len = ath11k_wmi_tlv_data_len(vdev_ids); + u32 num_vdevs = ev->num_vdevs; struct ath11k_vif *arvif; + int i; /* Finish CSA once the switch count becomes NULL */ if (ev->current_switch_count) return; + if (num_vdevs > vdev_ids_len / sizeof(*vdev_ids)) { + ath11k_warn(ab, "csa switch count num_vdevs %u exceeds tlv array length %u\n", + num_vdevs, vdev_ids_len); + return; + } + rcu_read_lock(); - for (i = 0; i < ev->num_vdevs; i++) { + for (i = 0; i < num_vdevs; i++) { arvif = ath11k_mac_get_arvif_by_vdev_id(ab, vdev_ids[i]); if (!arvif) { From 8e415b8068480d51a057197ded974e2637e8c42b Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sun, 26 Jul 2026 16:02:07 -0700 Subject: [PATCH 03/18] wifi: ath12k: validate TLV length in process_tpc_stats() The outer skb->len guard only confirms the SKB is large enough to hold the full fixed_param struct, but the TLV's own WMI_TLV_LEN field is never checked. Firmware advertising a TLV length shorter than sizeof(*fixed_param) causes reads of pdev_id and event_count beyond the declared TLV payload. Add a check that the TLV length is at least sizeof(*fixed_param) before casting and dereferencing the pointer. Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260726-ath12k_wmi_process_tpc_stats-len-check-v1-1-c4ba2f84d9c6@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index c2833d1e95d3..e9e7566e0f69 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -9986,6 +9986,7 @@ static void ath12k_wmi_process_tpc_stats(struct ath12k_base *ab, void *ptr = skb->data; struct ath12k *ar; u16 tlv_tag; + u16 tlv_len; u32 event_count; int ret; @@ -10001,6 +10002,7 @@ static void ath12k_wmi_process_tpc_stats(struct ath12k_base *ab, tlv = (struct wmi_tlv *)ptr; tlv_tag = le32_get_bits(tlv->header, WMI_TLV_TAG); + tlv_len = le32_get_bits(tlv->header, WMI_TLV_LEN); ptr += sizeof(*tlv); if (tlv_tag != WMI_TAG_HALPHY_CTRL_PATH_EVENT_FIXED_PARAM) { @@ -10008,6 +10010,12 @@ static void ath12k_wmi_process_tpc_stats(struct ath12k_base *ab, return; } + if (tlv_len < sizeof(*fixed_param)) { + ath12k_warn(ab, "TPC stats fixed param tlv len %u too short\n", + tlv_len); + return; + } + fixed_param = (struct ath12k_wmi_pdev_tpc_stats_event_fixed_params *)ptr; rcu_read_lock(); ar = ath12k_mac_get_ar_by_pdev_id(ab, le32_to_cpu(fixed_param->pdev_id) + 1); From 0702eddffff1b637a9e90187785a0b44542bb365 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sat, 25 Jul 2026 11:11:45 -0700 Subject: [PATCH 04/18] wifi: ath12k: move firmware_mode enum to qmi.h The enum ath12k_firmware_mode defines values that are part of the QMI ABI, so it belongs in qmi.h rather than core.h. Consolidate it there along with ATH12K_FIRMWARE_MODE_OFF, which is currently a bare macro. Rename the enum to ath12k_qmi_firmware_mode to align with the naming convention of the other enums in qmi.h, and place it with the other ath12k_qmi_* enums. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260725-consolidate-firmware_mode-v1-1-aedff0ce0ba5@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 3 ++- drivers/net/wireless/ath/ath12k/core.h | 10 +--------- drivers/net/wireless/ath/ath12k/pci.c | 1 + drivers/net/wireless/ath/ath12k/qmi.c | 4 ++-- drivers/net/wireless/ath/ath12k/qmi.h | 14 ++++++++++++-- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index a9112760185f..a052a77828f3 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -23,6 +23,7 @@ #include "wow.h" #include "dp_cmn.h" #include "peer.h" +#include "qmi.h" unsigned int ath12k_debug_mask; module_param_named(debug_mask, ath12k_debug_mask, uint, 0644); @@ -1188,7 +1189,7 @@ static int ath12k_core_hw_group_start(struct ath12k_hw_group *ag) } static int ath12k_core_start_firmware(struct ath12k_base *ab, - enum ath12k_firmware_mode mode) + enum ath12k_qmi_firmware_mode mode) { int ret; diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 37a194e00248..ecb451d93f45 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -160,14 +160,6 @@ enum ath12k_hw_rev { ATH12K_HW_IPQ5424_HW10, }; -enum ath12k_firmware_mode { - /* the default mode, standard 802.11 functionality */ - ATH12K_FIRMWARE_MODE_NORMAL, - - /* factory tests etc */ - ATH12K_FIRMWARE_MODE_FTM, -}; - #define ATH12K_IRQ_NUM_MAX 57 #define ATH12K_EXT_IRQ_NUM_MAX 16 #define ATH12K_MAX_TCL_RING_NUM 3 @@ -1147,7 +1139,7 @@ struct ath12k_base { struct ath12k_hw_group *ag; struct ath12k_wsi_info wsi_info; - enum ath12k_firmware_mode fw_mode; + enum ath12k_qmi_firmware_mode fw_mode; struct ath12k_ftm_event_obj ftm_event_obj; bool hw_group_ref; diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index ad74140e0fa5..907d29b1020c 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -17,6 +17,7 @@ #include "mhi.h" #include "debug.h" #include "hal.h" +#include "qmi.h" #define ATH12K_PCI_BAR_NUM 0 #define ATH12K_PCI_DMA_MASK 36 diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index bb61c78e5c29..c466c3ae793a 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -3431,7 +3431,7 @@ int ath12k_qmi_wlanfw_aux_uc_info_send(struct ath12k_base *ab) } static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab, - u32 mode) + enum ath12k_qmi_firmware_mode mode) { struct qmi_wlanfw_wlan_mode_req_msg_v01 req = {}; struct qmi_wlanfw_wlan_mode_resp_msg_v01 resp = {}; @@ -3631,7 +3631,7 @@ void ath12k_qmi_firmware_stop(struct ath12k_base *ab) } int ath12k_qmi_firmware_start(struct ath12k_base *ab, - u32 mode) + enum ath12k_qmi_firmware_mode mode) { int ret; diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index cbe5be30053a..27b69847a15e 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -32,7 +32,6 @@ #define QMI_WLFW_FW_READY_IND_V01 0x0038 #define QMI_WLANFW_MAX_DATA_SIZE_V01 6144 -#define ATH12K_FIRMWARE_MODE_OFF 4 #define ATH12K_BOARD_ID_DEFAULT 0xFF @@ -602,6 +601,17 @@ enum ath12k_qmi_mem_mode { ATH12K_QMI_MEMORY_MODE_LOW_512_M, }; +enum ath12k_qmi_firmware_mode { + /* the default mode, standard 802.11 functionality */ + ATH12K_FIRMWARE_MODE_NORMAL, + + /* factory tests etc */ + ATH12K_FIRMWARE_MODE_FTM, + + /* firmware offline */ + ATH12K_FIRMWARE_MODE_OFF = 4, +}; + static inline void ath12k_qmi_set_event_block(struct ath12k_qmi *qmi, bool block) { lockdep_assert_held(&qmi->event_lock); @@ -617,7 +627,7 @@ static inline bool ath12k_qmi_get_event_block(struct ath12k_qmi *qmi) } int ath12k_qmi_firmware_start(struct ath12k_base *ab, - u32 mode); + enum ath12k_qmi_firmware_mode mode); void ath12k_qmi_firmware_stop(struct ath12k_base *ab); void ath12k_qmi_deinit_service(struct ath12k_base *ab); int ath12k_qmi_init_service(struct ath12k_base *ab); From 0090ec7ad252f1a597d782b7bce7eff7bdde00c0 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sat, 25 Jul 2026 11:11:46 -0700 Subject: [PATCH 05/18] wifi: ath12k: rename firmware_mode enum members to use QMI namespace The enumerator names ATH12K_FIRMWARE_MODE_* lack the QMI infix that all other constants in qmi.h use (ATH12K_QMI_FILE_TYPE_*, ATH12K_QMI_BDF_TYPE_*, ATH12K_QMI_MEMORY_MODE_*, etc.). Rename them to ATH12K_QMI_FIRMWARE_MODE_* for consistency and to prevent a future re-introduction of ATH12K_FIRMWARE_MODE_* names causing a silent collision. While here, add a comment noting that values 2-3 are reserved by the firmware QMI ABI to explain the gap before ATH12K_QMI_FIRMWARE_MODE_OFF = 4. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260725-consolidate-firmware_mode-v1-2-aedff0ce0ba5@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 2 +- drivers/net/wireless/ath/ath12k/mac.c | 2 +- drivers/net/wireless/ath/ath12k/pci.c | 2 +- drivers/net/wireless/ath/ath12k/qmi.c | 4 ++-- drivers/net/wireless/ath/ath12k/qmi.h | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index a052a77828f3..d023c646478f 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -795,7 +795,7 @@ static int ath12k_core_soc_create(struct ath12k_base *ab) int ret; if (ath12k_ftm_mode) { - ab->fw_mode = ATH12K_FIRMWARE_MODE_FTM; + ab->fw_mode = ATH12K_QMI_FIRMWARE_MODE_FTM; ath12k_info(ab, "Booting in ftm mode\n"); } diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 310976247dbb..9a775602775d 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -859,7 +859,7 @@ struct ath12k *ath12k_mac_get_ar_by_pdev_id(struct ath12k_base *ab, u32 pdev_id) return NULL; for (i = 0; i < ab->num_radios; i++) { - if (ab->fw_mode == ATH12K_FIRMWARE_MODE_FTM) + if (ab->fw_mode == ATH12K_QMI_FIRMWARE_MODE_FTM) pdev = &ab->pdevs[i]; else pdev = rcu_dereference(ab->pdevs_active[i]); diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 907d29b1020c..6441927b5382 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -1556,7 +1556,7 @@ static int ath12k_pci_probe(struct pci_dev *pdev, ab_pci->ab = ab; ab_pci->pdev = pdev; ab->hif.ops = &ath12k_pci_hif_ops; - ab->fw_mode = ATH12K_FIRMWARE_MODE_NORMAL; + ab->fw_mode = ATH12K_QMI_FIRMWARE_MODE_NORMAL; pci_set_drvdata(pdev, ab); spin_lock_init(&ab_pci->window_lock); diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index c466c3ae793a..280e50a1f31d 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -3460,7 +3460,7 @@ static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab, ret = qmi_txn_wait(&txn, msecs_to_jiffies(ATH12K_QMI_WLANFW_TIMEOUT_MS)); if (ret < 0) { - if (mode == ATH12K_FIRMWARE_MODE_OFF && ret == -ENETRESET) { + if (mode == ATH12K_QMI_FIRMWARE_MODE_OFF && ret == -ENETRESET) { ath12k_warn(ab, "WLFW service is dis-connected\n"); return 0; } @@ -3623,7 +3623,7 @@ void ath12k_qmi_firmware_stop(struct ath12k_base *ab) clear_bit(ATH12K_FLAG_QMI_FW_READY_COMPLETE, &ab->dev_flags); - ret = ath12k_qmi_wlanfw_mode_send(ab, ATH12K_FIRMWARE_MODE_OFF); + ret = ath12k_qmi_wlanfw_mode_send(ab, ATH12K_QMI_FIRMWARE_MODE_OFF); if (ret < 0) { ath12k_warn(ab, "qmi failed to send wlan mode off\n"); return; diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index 27b69847a15e..6da10f3cb597 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -603,13 +603,13 @@ enum ath12k_qmi_mem_mode { enum ath12k_qmi_firmware_mode { /* the default mode, standard 802.11 functionality */ - ATH12K_FIRMWARE_MODE_NORMAL, + ATH12K_QMI_FIRMWARE_MODE_NORMAL, /* factory tests etc */ - ATH12K_FIRMWARE_MODE_FTM, + ATH12K_QMI_FIRMWARE_MODE_FTM, - /* firmware offline */ - ATH12K_FIRMWARE_MODE_OFF = 4, + /* firmware offline; values 2-3 reserved by firmware ABI */ + ATH12K_QMI_FIRMWARE_MODE_OFF = 4, }; static inline void ath12k_qmi_set_event_block(struct ath12k_qmi *qmi, bool block) From 534459ac562b207ba1a9bb2c95dba77b5939e2da Mon Sep 17 00:00:00 2001 From: Pavankumar Nandeshwar Date: Tue, 21 Jul 2026 16:34:59 +0530 Subject: [PATCH 06/18] wifi: ath12k: Use different RX release ring sizes as per memory profiles Currently, the RX release ring size is hardcoded to 1024 entries via DP_RX_RELEASE_RING_SIZE. This value was sufficient for older generations, but is not adequate for Wi-Fi 7 scenarios with higher aggregation, parallel processing, and increased likelihood of error bursts. In Wi-Fi 7, a PPDU can carry up to 1024 MPDUs and each MPDU may contain multiple MSDUs. In error scenarios such as REO out-of-order (OOR) events, a large number of MSDUs can be pushed to the RX release ring in a short duration. With multiple PPDUs being processed in parallel (e.g. multi-core or MLO scenarios), this can lead to significant bursts of descriptors. Field observations have shown frequent OOR conditions and back-pressure issues with smaller ring sizes. Increasing the RX release ring size helps absorb these bursts and avoids back-pressure in the RXDMA/REO pipeline. Without sufficient ring capacity (e.g. 16K), back-pressure was observed under stress conditions. To address this, make the RX release ring size configurable per memory profile by adding rx_release_ring_size to ath12k_dp_profile_params: - Default memory profile: 16384 entries - Low memory profile (512M): 8192 entries The larger size in the default profile improves robustness under high traffic and error conditions by reducing the probability of ring overflow and pipeline stalls. The reduced size in the low memory profile balances memory usage while still providing sufficient headroom compared to the previous fixed value. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.0.1-00029-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Signed-off-by: Pavankumar Nandeshwar Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260721110459.2203038-1-pavankumar.nandeshwar@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 2 ++ drivers/net/wireless/ath/ath12k/core.h | 1 + drivers/net/wireless/ath/ath12k/dp.c | 2 +- drivers/net/wireless/ath/ath12k/dp.h | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index d023c646478f..cb4ca93f625c 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -53,6 +53,7 @@ ath12k_mem_profile_based_param ath12k_mem_profile_based_param[] = { .rxdma_monitor_dst_ring_size = 8192, .num_pool_tx_desc = 32768, .rx_desc_count = 12288, + .rx_release_ring_size = 16384, }, }, [ATH12K_QMI_MEMORY_MODE_LOW_512_M] = { @@ -66,6 +67,7 @@ ath12k_mem_profile_based_param ath12k_mem_profile_based_param[] = { .rxdma_monitor_dst_ring_size = 512, .num_pool_tx_desc = 16384, .rx_desc_count = 6144, + .rx_release_ring_size = 8192, }, }, }; diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index ecb451d93f45..3bd71bf02afb 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -931,6 +931,7 @@ struct ath12k_dp_profile_params { u32 rxdma_monitor_dst_ring_size; u32 num_pool_tx_desc; u32 rx_desc_count; + u32 rx_release_ring_size; }; struct ath12k_mem_profile_based_param { diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c index fbc0788b37a0..f9b37d75956d 100644 --- a/drivers/net/wireless/ath/ath12k/dp.c +++ b/drivers/net/wireless/ath/ath12k/dp.c @@ -487,7 +487,7 @@ static int ath12k_dp_srng_common_setup(struct ath12k_base *ab) ret = ath12k_dp_srng_setup(ab, &dp->rx_rel_ring, HAL_WBM2SW_RELEASE, HAL_WBM2SW_REL_ERR_RING_NUM, 0, - DP_RX_RELEASE_RING_SIZE); + DP_RX_RELEASE_RING_SIZE(ab)); if (ret) { ath12k_warn(ab, "failed to set up rx_rel ring :%d\n", ret); goto err; diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index a94bbc337df4..bef0f2ba0560 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -200,7 +200,8 @@ struct ath12k_pdev_dp { #define DP_REO_DST_RING_MAX 8 #define DP_REO_DST_RING_SIZE 2048 #define DP_REO_REINJECT_RING_SIZE 32 -#define DP_RX_RELEASE_RING_SIZE 1024 +#define DP_RX_RELEASE_RING_SIZE(ab) \ + ((ab)->profile_param->dp_params.rx_release_ring_size) #define DP_REO_EXCEPTION_RING_SIZE 128 #define DP_REO_CMD_RING_SIZE 256 #define DP_REO_STATUS_RING_SIZE 2048 From 43c521c11ce8fe904e14d1ae0566ffea931cf2e9 Mon Sep 17 00:00:00 2001 From: Pavankumar Nandeshwar Date: Thu, 23 Jul 2026 11:16:53 +0530 Subject: [PATCH 07/18] wifi: ath12k: skip MLO multicast links during crash recovery in Tx path In ath12k_wifi7_mac_op_tx(), the MLO multicast broadcast path iterates over all active links and copies the original skb for transmission on each link. When firmware crash recovery is underway (ATH12K_FLAG_CRASH_FLUSH set), the per-link copy is allocated and partially processed before ath12k_wifi7_dp_tx() eventually rejects it with -ESHUTDOWN. This wastes GFP_ATOMIC memory and produces spurious "failed to transmit frame" warnings for every active MLO link during the recovery window. The unicast and non-MLO paths are unaffected: they call ath12k_wifi7_dp_tx() directly, which already guards against the flag at its entry. Skip any link whose associated ath12k_base has ATH12K_FLAG_CRASH_FLUSH set before performing the skb_copy(), matching the behaviour of ath12k_wifi7_dp_tx() but avoiding the unnecessary allocation entirely. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Pavankumar Nandeshwar Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260723054653.2794550-1-pavankumar.nandeshwar@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index 4c1119edcaed..d890801d5822 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -1032,6 +1032,9 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, continue; tmp_ar = tmp_arvif->ar; + if (unlikely(test_bit(ATH12K_FLAG_CRASH_FLUSH, &tmp_ar->ab->dev_flags))) + continue; + tmp_dp = ath12k_ab_to_dp(tmp_ar->ab); tmp_dp_pdev = ath12k_dp_to_pdev_dp(tmp_dp, tmp_ar->pdev_idx); From 96f46607bbcee8aac00c4b5a1213b7d82ceee36d Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 21 Jul 2026 12:20:37 +0530 Subject: [PATCH 08/18] wifi: ath12k: add AHB platform descriptor support AHB-based platforms associate each device with a userPD ID that determines the firmware name and Peripheral Authentication Service ID (PASID) used during firmware authentication. Current implementation does not support platforms with multiple devices sharing the same compatible string but using different userPD IDs. As a result, the driver cannot uniquely identify each device for firmware selection and authentication. Add an AHB platform descriptor to store device-specific configuration. Implement userPD ID resolution by matching device tree reg properties, with node name matching as a fallback. Centralize platform configuration to simplify the probe path by removing hardware-specific conditionals. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aaradhana Sahu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260721065038.126046-2-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ahb.c | 4 +- drivers/net/wireless/ath/ath12k/ahb.h | 19 +++++ drivers/net/wireless/ath/ath12k/hw.h | 1 - drivers/net/wireless/ath/ath12k/wifi7/ahb.c | 90 +++++++++++++++++---- 4 files changed, 94 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 07bb83710b1f..14ee696960c7 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -704,7 +704,7 @@ static int ath12k_ahb_map_service_to_pipe(struct ath12k_base *ab, u16 service_id return 0; } -static const struct ath12k_hif_ops ath12k_ahb_hif_ops = { +const struct ath12k_hif_ops ath12k_ahb_hif_ops = { .start = ath12k_ahb_start, .stop = ath12k_ahb_stop, .read32 = ath12k_ahb_read32, @@ -715,6 +715,7 @@ static const struct ath12k_hif_ops ath12k_ahb_hif_ops = { .power_up = ath12k_ahb_power_up, .power_down = ath12k_ahb_power_down, }; +EXPORT_SYMBOL(ath12k_ahb_hif_ops); static irqreturn_t ath12k_userpd_irq_handler(int irq, void *data) { @@ -1038,7 +1039,6 @@ static int ath12k_ahb_probe(struct platform_device *pdev) ab_ahb = ath12k_ab_to_ahb(ab); ab_ahb->ab = ab; - ab->hif.ops = &ath12k_ahb_hif_ops; ab->pdev = pdev; platform_set_drvdata(pdev, ab); diff --git a/drivers/net/wireless/ath/ath12k/ahb.h b/drivers/net/wireless/ath/ath12k/ahb.h index a153db6cf1d3..037347ccd21b 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.h +++ b/drivers/net/wireless/ath/ath12k/ahb.h @@ -30,6 +30,24 @@ #define ATH12K_USERPD_ID_MASK GENMASK(10, 8) #define ATH12K_USERPD_FW_NAME_LEN 35 +enum ath12k_ahb_userpd_id { + ATH12K_AHB_USERPD_ID_0 = 1, + ATH12K_AHB_USERPD_ID_1, + ATH12K_AHB_USERPD_ID_2, +}; + +struct ath12k_ahb_userpd_map { + phys_addr_t io_start; + const char *node_name; + u32 upd_id; +}; + +struct ath12k_ahb_desc { + enum ath12k_hw_rev hw_rev; + bool auth_enabled; + const struct ath12k_hif_ops *ops; +}; + enum ath12k_ahb_smp2p_msg_id { ATH12K_AHB_POWER_SAVE_ENTER = 1, ATH12K_AHB_POWER_SAVE_EXIT, @@ -43,6 +61,7 @@ enum ath12k_ahb_userpd_irq { }; struct ath12k_base; +extern const struct ath12k_hif_ops ath12k_ahb_hif_ops; struct ath12k_ahb_device_family_ops { int (*probe)(struct platform_device *pdev); diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h index 49cfd5dfc70a..3ed38f8f2b48 100644 --- a/drivers/net/wireless/ath/ath12k/hw.h +++ b/drivers/net/wireless/ath/ath12k/hw.h @@ -100,7 +100,6 @@ struct ieee80211_rx_status; #define ATH12K_REGDB_FILE_NAME "regdb.bin" #define ATH12K_PCIE_MAX_PAYLOAD_SIZE 128 -#define ATH12K_IPQ5332_USERPD_ID 1 enum ath12k_hw_rate_cck { ATH12K_HW_RATE_CCK_LP_11M = 0, diff --git a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c index 6a8b8b2a56f9..98a6606ffd76 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c @@ -15,44 +15,100 @@ #include "dp.h" #include "core.h" +/* + * Node name to UserPD ID mapping + * + * The io_start field is used for additional validation when the reg + * property is present in the device tree. If io_start is 0, only + * node_name matching is performed. + * + * For platforms where not all WiFi nodes have a 'reg' property, set + * io_start to 0 for those entries. The driver will match purely by + * node name in such cases. + */ +static const struct ath12k_ahb_userpd_map ath12k_wifi7_ahb_userpd_map[] = { + { .io_start = 0x0c000000, .node_name = "wifi", .upd_id = ATH12K_AHB_USERPD_ID_0 }, +}; + +static const struct ath12k_ahb_desc ath12k_wifi7_ahb_desc[] = { + [ATH12K_HW_IPQ5332_HW10] = { + .hw_rev = ATH12K_HW_IPQ5332_HW10, + .auth_enabled = true, + .ops = &ath12k_ahb_hif_ops, + }, + [ATH12K_HW_IPQ5424_HW10] = { + .hw_rev = ATH12K_HW_IPQ5424_HW10, + .auth_enabled = false, + .ops = &ath12k_ahb_hif_ops, + }, +}; + static const struct of_device_id ath12k_wifi7_ahb_of_match[] = { { .compatible = "qcom,ipq5332-wifi", - .data = (void *)ATH12K_HW_IPQ5332_HW10, + .data = (void *)&ath12k_wifi7_ahb_desc[ATH12K_HW_IPQ5332_HW10], }, { .compatible = "qcom,ipq5424-wifi", - .data = (void *)ATH12K_HW_IPQ5424_HW10, + .data = (void *)&ath12k_wifi7_ahb_desc[ATH12K_HW_IPQ5424_HW10], }, { } }; MODULE_DEVICE_TABLE(of, ath12k_wifi7_ahb_of_match); +/* + * ath12k_wifi7_ahb_get_userpd_id - Resolve UserPD ID from DT properties + * @ab: ath12k base structure + * + * Returns: UserPD ID (1-based) on success, 0 on failure + * + * Resolution logic: + * 1. If reg property exist in DT, get userpd_id from io_start + * 2. If reg property is absent, get userpd_id from DT node name + * 3. Return 0 if no match found (probe will fail) + */ +static u32 ath12k_wifi7_ahb_get_userpd_id(struct ath12k_base *ab) +{ + const struct ath12k_ahb_userpd_map *map; + struct resource *res; + size_t i; + + res = platform_get_resource(ab->pdev, IORESOURCE_MEM, 0); + + for (i = 0; i < ARRAY_SIZE(ath12k_wifi7_ahb_userpd_map); i++) { + map = &ath12k_wifi7_ahb_userpd_map[i]; + + if (res) { + if (map->io_start && map->io_start == res->start) + return map->upd_id; + } else if (map->node_name && + of_node_name_eq(ab->dev->of_node, map->node_name)) { + return map->upd_id; + } + } + + return 0; +} + static int ath12k_wifi7_ahb_probe(struct platform_device *pdev) { + const struct ath12k_ahb_desc *desc; struct ath12k_ahb *ab_ahb; - enum ath12k_hw_rev hw_rev; struct ath12k_base *ab; int ret; ab = platform_get_drvdata(pdev); ab_ahb = ath12k_ab_to_ahb(ab); - - hw_rev = (enum ath12k_hw_rev)(kernel_ulong_t)of_device_get_match_data(&pdev->dev); - switch (hw_rev) { - case ATH12K_HW_IPQ5332_HW10: - ab_ahb->userpd_id = ATH12K_IPQ5332_USERPD_ID; - ab_ahb->scm_auth_enabled = true; - break; - case ATH12K_HW_IPQ5424_HW10: - ab_ahb->userpd_id = ATH12K_IPQ5332_USERPD_ID; - ab_ahb->scm_auth_enabled = false; - break; - default: + desc = of_device_get_match_data(&pdev->dev); + if (!desc) return -EOPNOTSUPP; - } ab->target_mem_mode = ATH12K_QMI_MEMORY_MODE_DEFAULT; - ab->hw_rev = hw_rev; + ab->hw_rev = desc->hw_rev; + ab->hif.ops = desc->ops; + ab_ahb->scm_auth_enabled = desc->auth_enabled; + ab_ahb->userpd_id = ath12k_wifi7_ahb_get_userpd_id(ab); + if (!ab_ahb->userpd_id) + return -EOPNOTSUPP; ret = ath12k_wifi7_hw_init(ab); if (ret) { From c6ab3b1dfa3e62dbf42c66121037de460a182642 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 21 Jul 2026 12:20:38 +0530 Subject: [PATCH 09/18] wifi: ath12k: Share RootPD state across UserPDs to avoid duplicate operations Currently, each ath12k AHB device maintains its own RootPD-related information. However, RootPD is shared across all UserPD devices, so RootPD-related operations such as RootPD boot, and notifier registration, should be performed only once during the first UserPD boot up. Due to per-device RootPD information, the driver is unable to track shared RootPD state across multiple UserPDs, which can result in these operations being performed multiple times. Fix this by introducing a new ath12k_ahb_rproc_info structure to hold shared RootPD-related information such as notifier callbacks, boot state, and number of userPD. Allocate this structure during the first device probe in ath12k_ahb_rproc_info_alloc() and reuse the same structure for all subsequent device probes. Also handle rproc deconfiguration correctly when multiple UserPDs share a common RootPD. The RootPD provides shared firmware services and resources for all UserPDs. Therefore, do not shut down the RootPD while any UserPD remains powered on or is still in the boot process. In addition, a UserPD can be powered down before its associated resources are fully released. Defer g_rproc_info cleanup until all UserPD-related state and resources have been cleaned up. For intermediate UserPD removal, cleanup only per-device information and remove the UserPD from the tracking array while keeping the RootPD running for remaining active UserPDs. Note: UserPD IDs start from 1, as ID 0 is used by RootPD, which is completely handled by the remoteproc driver. The multi-PD architecture on AHB platforms operates as follows: +-----------------------------+ | Q6 RootPD (rproc) | | (Shared Resource) | | | | - Manages UserPD lifecycle | | - Provides SSR notifiers | +--------------+--------------+ | | Manages | +---------------------+---------------------+ | | | +----v----+ +----v----+ +----v----+ | UserPD1 | | UserPD2 | | UserPD3 | | ID=1 | | ID=2 | | ID=3 | | (Radio) | | (Radio) | | (Radio) | +---------+ +---------+ +---------+ | | | | | | ath12k_ahb ath12k_ahb ath12k_ahb (device 1) (device 2) (device 3) | | | +---------------------+---------------------+ | | All reference | +---------v----------+ | ath12k_ahb_rproc_ | | info (shared) | | | | - tgt_rproc | | - notifiers | | - rootpd_ready | | - num_userpd | | - userpd[] array | +--------------------+ Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aaradhana Sahu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260721065038.126046-3-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ahb.c | 211 +++++++++++++++++++++----- drivers/net/wireless/ath/ath12k/ahb.h | 15 +- 2 files changed, 180 insertions(+), 46 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 14ee696960c7..0fc55c9169e1 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -25,6 +25,22 @@ static const char ath12k_userpd_irq[][9] = {"spawn", "ready", "stop-ack"}; +/* + * Multi-UserPD Architecture: + * + * One Q6 RootPD (managed by separate rproc driver) supports multiple + * ath12k UserPDs. Each UserPD represents a WiFi radio instance. + * + * Lifecycle: + * - RootPD boots when first UserPD probes + * - All UserPDs share RootPD's SSR notifier + * + * Locking: + * - ath12k_rproc_info_lock: Protects g_rproc_info allocation/free + */ +static struct ath12k_ahb_rproc_info *g_rproc_info; +static DEFINE_MUTEX(ath12k_rproc_info_lock); + static const char *irq_name[ATH12K_IRQ_NUM_MAX] = { "misc-pulse1", "misc-latch", @@ -786,44 +802,85 @@ static int ath12k_ahb_config_rproc_irq(struct ath12k_base *ab) static int ath12k_ahb_root_pd_state_notifier(struct notifier_block *nb, const unsigned long event, void *data) { - struct ath12k_ahb *ab_ahb = container_of(nb, struct ath12k_ahb, root_pd_nb); - struct ath12k_base *ab = ab_ahb->ab; + struct ath12k_ahb_rproc_info *rproc_info = + container_of(nb, struct ath12k_ahb_rproc_info, root_pd_nb); if (event == ATH12K_RPROC_AFTER_POWERUP) { - ath12k_dbg(ab, ATH12K_DBG_AHB, "Root PD is UP\n"); - complete(&ab_ahb->rootpd_ready); + ath12k_generic_dbg(ATH12K_DBG_AHB, "Root PD is UP\n"); + complete(&rproc_info->rootpd_ready); } return 0; } -static int ath12k_ahb_register_rproc_notifier(struct ath12k_base *ab) +static int ath12k_ahb_register_rproc_notifier(void) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + int ret; - ab_ahb->root_pd_nb.notifier_call = ath12k_ahb_root_pd_state_notifier; - init_completion(&ab_ahb->rootpd_ready); + lockdep_assert_held(&ath12k_rproc_info_lock); - ab_ahb->root_pd_notifier = qcom_register_ssr_notifier(ab_ahb->tgt_rproc->name, - &ab_ahb->root_pd_nb); - if (IS_ERR(ab_ahb->root_pd_notifier)) - return PTR_ERR(ab_ahb->root_pd_notifier); + if (g_rproc_info->root_pd_notifier) + return 0; + + g_rproc_info->root_pd_nb.notifier_call = ath12k_ahb_root_pd_state_notifier; + + g_rproc_info->root_pd_notifier = + qcom_register_ssr_notifier(g_rproc_info->tgt_rproc->name, + &g_rproc_info->root_pd_nb); + if (IS_ERR(g_rproc_info->root_pd_notifier)) { + ret = PTR_ERR(g_rproc_info->root_pd_notifier); + g_rproc_info->root_pd_notifier = NULL; + return ret; + } return 0; } -static void ath12k_ahb_unregister_rproc_notifier(struct ath12k_base *ab) +static void ath12k_ahb_unregister_rproc_notifier(void) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + lockdep_assert_held(&ath12k_rproc_info_lock); - if (!ab_ahb->root_pd_notifier) { - ath12k_err(ab, "Rproc notifier not registered\n"); + if (!g_rproc_info->root_pd_notifier) return; - } - qcom_unregister_ssr_notifier(ab_ahb->root_pd_notifier, - &ab_ahb->root_pd_nb); - ab_ahb->root_pd_notifier = NULL; + qcom_unregister_ssr_notifier(g_rproc_info->root_pd_notifier, + &g_rproc_info->root_pd_nb); + g_rproc_info->root_pd_notifier = NULL; +} + +static void ath12k_ahb_cleanup_userpd(struct ath12k_base *ab) +{ + struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info; + + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (!rproc_info) + return; + + rproc_info->userpd[ab_ahb->userpd_id - 1] = NULL; + rproc_info->num_userpd--; + ab_ahb->rproc_info = NULL; +} + +static struct ath12k_ahb_rproc_info *ath12k_ahb_rproc_info_alloc(struct ath12k_base *ab) +{ + struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info; + + lockdep_assert_held(&ath12k_rproc_info_lock); + + rproc_info = kzalloc_obj(*rproc_info, GFP_KERNEL); + if (!rproc_info) + return NULL; + + rproc_info->rootpd_booted_by_driver = false; + rproc_info->userpd[ab_ahb->userpd_id - 1] = ab_ahb; + rproc_info->num_userpd = 1; + init_completion(&rproc_info->rootpd_ready); + ab_ahb->rproc_info = rproc_info; + + return rproc_info; } static int ath12k_ahb_get_rproc(struct ath12k_base *ab) @@ -832,37 +889,69 @@ static int ath12k_ahb_get_rproc(struct ath12k_base *ab) struct device *dev = ab->dev; struct device_node *np; struct rproc *prproc; + int ret; + + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (ab_ahb->userpd_id > ATH12K_MAX_DEVICES) + return -ENOSPC; + + if (g_rproc_info) { + if (g_rproc_info->num_userpd >= ATH12K_MAX_DEVICES) { + ath12k_err(ab, "Max UserPD limit reached\n"); + return -ENOSPC; + } + + g_rproc_info->userpd[ab_ahb->userpd_id - 1] = ab_ahb; + g_rproc_info->num_userpd++; + ab_ahb->rproc_info = g_rproc_info; + return 0; + } + + g_rproc_info = ath12k_ahb_rproc_info_alloc(ab); + if (!g_rproc_info) + return -ENOMEM; np = of_parse_phandle(dev->of_node, "qcom,rproc", 0); if (!np) { ath12k_err(ab, "failed to get q6_rproc handle\n"); - return -ENOENT; + ret = -ENOENT; + goto err_free_rproc_info; } prproc = rproc_get_by_phandle(np->phandle); of_node_put(np); - if (!prproc) - return dev_err_probe(&ab->pdev->dev, -EPROBE_DEFER, - "failed to get rproc\n"); - - ab_ahb->tgt_rproc = prproc; + if (!prproc) { + ret = dev_err_probe(&ab->pdev->dev, -EPROBE_DEFER, + "failed to get rproc\n"); + goto err_free_rproc_info; + } + g_rproc_info->tgt_rproc = prproc; return 0; + +err_free_rproc_info: + ab_ahb->rproc_info = NULL; + kfree(g_rproc_info); + g_rproc_info = NULL; + return ret; } static int ath12k_ahb_boot_root_pd(struct ath12k_base *ab) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); unsigned long time_left; int ret; - ret = rproc_boot(ab_ahb->tgt_rproc); + lockdep_assert_held(&ath12k_rproc_info_lock); + reinit_completion(&g_rproc_info->rootpd_ready); + + ret = rproc_boot(g_rproc_info->tgt_rproc); if (ret < 0) { ath12k_err(ab, "RootPD boot failed\n"); return ret; } - time_left = wait_for_completion_timeout(&ab_ahb->rootpd_ready, + time_left = wait_for_completion_timeout(&g_rproc_info->rootpd_ready, ATH12K_ROOTPD_READY_TIMEOUT); if (!time_left) { ath12k_err(ab, "RootPD ready wait timed out\n"); @@ -874,44 +963,74 @@ static int ath12k_ahb_boot_root_pd(struct ath12k_base *ab) static int ath12k_ahb_configure_rproc(struct ath12k_base *ab) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); int ret; - ret = ath12k_ahb_get_rproc(ab); - if (ret < 0) - return ret; + mutex_lock(&ath12k_rproc_info_lock); - ret = ath12k_ahb_register_rproc_notifier(ab); + ret = ath12k_ahb_get_rproc(ab); + if (ret < 0) { + mutex_unlock(&ath12k_rproc_info_lock); + return ret; + } + + ret = ath12k_ahb_register_rproc_notifier(); if (ret < 0) { ret = dev_err_probe(&ab->pdev->dev, ret, "failed to register rproc notifier\n"); - goto err_put_rproc; + goto err_cleanup_userpd; } - if (ab_ahb->tgt_rproc->state != RPROC_RUNNING) { + if (g_rproc_info->tgt_rproc->state != RPROC_RUNNING) { ret = ath12k_ahb_boot_root_pd(ab); if (ret < 0) { ath12k_err(ab, "failed to boot the remote processor Q6\n"); goto err_unreg_notifier; } + g_rproc_info->rootpd_booted_by_driver = true; } - return ath12k_ahb_config_rproc_irq(ab); + mutex_unlock(&ath12k_rproc_info_lock); + return 0; err_unreg_notifier: - ath12k_ahb_unregister_rproc_notifier(ab); + ath12k_ahb_unregister_rproc_notifier(); -err_put_rproc: - rproc_put(ab_ahb->tgt_rproc); +err_cleanup_userpd: + ath12k_ahb_cleanup_userpd(ab); + + if (g_rproc_info && !g_rproc_info->num_userpd) { + rproc_put(g_rproc_info->tgt_rproc); + kfree(g_rproc_info); + g_rproc_info = NULL; + } + + mutex_unlock(&ath12k_rproc_info_lock); return ret; } static void ath12k_ahb_deconfigure_rproc(struct ath12k_base *ab) { struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info; - ath12k_ahb_unregister_rproc_notifier(ab); - rproc_put(ab_ahb->tgt_rproc); + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (!rproc_info || !g_rproc_info) + return; + + ath12k_ahb_cleanup_userpd(ab); + + if (!g_rproc_info->num_userpd) { + ath12k_ahb_unregister_rproc_notifier(); + + if (g_rproc_info->rootpd_booted_by_driver && + g_rproc_info->tgt_rproc->state == RPROC_RUNNING) + rproc_shutdown(g_rproc_info->tgt_rproc); + + rproc_put(g_rproc_info->tgt_rproc); + kfree(g_rproc_info); + g_rproc_info = NULL; + } } static int ath12k_ahb_resource_init(struct ath12k_base *ab) @@ -1094,6 +1213,10 @@ static int ath12k_ahb_probe(struct platform_device *pdev) if (ret) goto err_ce_free; + ret = ath12k_ahb_config_rproc_irq(ab); + if (ret) + goto err_rproc_deconfigure; + ret = ath12k_ahb_config_irq(ab); if (ret) { ath12k_err(ab, "failed to configure irq: %d\n", ret); @@ -1121,7 +1244,9 @@ static int ath12k_ahb_probe(struct platform_device *pdev) ab_ahb->device_family_ops->arch_deinit(ab); err_rproc_deconfigure: + mutex_lock(&ath12k_rproc_info_lock); ath12k_ahb_deconfigure_rproc(ab); + mutex_unlock(&ath12k_rproc_info_lock); err_ce_free: ath12k_ce_free_pipes(ab); @@ -1163,7 +1288,9 @@ static void ath12k_ahb_free_resources(struct ath12k_base *ab) ath12k_hal_srng_deinit(ab); ath12k_ce_free_pipes(ab); ath12k_ahb_resource_deinit(ab); + mutex_lock(&ath12k_rproc_info_lock); ath12k_ahb_deconfigure_rproc(ab); + mutex_unlock(&ath12k_rproc_info_lock); ab_ahb->device_family_ops->arch_deinit(ab); ath12k_core_free(ab); platform_set_drvdata(pdev, NULL); diff --git a/drivers/net/wireless/ath/ath12k/ahb.h b/drivers/net/wireless/ath/ath12k/ahb.h index 037347ccd21b..cdb58b07338f 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.h +++ b/drivers/net/wireless/ath/ath12k/ahb.h @@ -69,13 +69,19 @@ struct ath12k_ahb_device_family_ops { void (*arch_deinit)(struct ath12k_base *ab); }; -struct ath12k_ahb { - struct ath12k_base *ab; +struct ath12k_ahb_rproc_info { struct rproc *tgt_rproc; - struct clk *xo_clk; - struct completion rootpd_ready; struct notifier_block root_pd_nb; void *root_pd_notifier; + struct completion rootpd_ready; + u8 num_userpd; + bool rootpd_booted_by_driver; + struct ath12k_ahb *userpd[ATH12K_MAX_DEVICES]; +}; + +struct ath12k_ahb { + struct ath12k_base *ab; + struct clk *xo_clk; struct qcom_smem_state *spawn_state; struct qcom_smem_state *stop_state; struct completion userpd_spawned; @@ -88,6 +94,7 @@ struct ath12k_ahb { const struct ath12k_ahb_ops *ahb_ops; const struct ath12k_ahb_device_family_ops *device_family_ops; bool scm_auth_enabled; + struct ath12k_ahb_rproc_info *rproc_info; }; struct ath12k_ahb_driver { From 4c6eb712a91fa079be6f9f1419c96e0ad2227081 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Tue, 28 Jul 2026 18:05:28 -0700 Subject: [PATCH 10/18] wifi: ath12k: fix stride mismatch in mac_phy_caps_parse() Currently, in ath12k_wmi_mac_phy_caps_parse(), kzalloc() sizes the mac_phy_caps buffer as tot_phy_id * len, where len is clamped to min(firmware_len, sizeof(struct ath12k_wmi_mac_phy_caps_params)). The subsequent memcpy() destination advances by sizeof(full struct) per slot via C pointer arithmetic, not by the clamped len. When firmware sends short TLVs, the second and later slots are written past the end of the allocation. The reader in ath12k_pull_mac_phy_cap_svc_ready_ext() also indexes the buffer with full-struct pointer arithmetic, so the allocation must match that stride. Fix by using kzalloc_objs(), which derives the element size from the pointer type, making allocation size and pointer stride provably consistent regardless of what len the firmware provides. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260728-mac_phy_caps_parse-stride-mismatch-v1-1-27a9c1a3fbd0@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index e9e7566e0f69..d5160af60e00 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -4774,14 +4774,16 @@ static int ath12k_wmi_mac_phy_caps_parse(struct ath12k_base *soc, if (svc_rdy_ext->n_mac_phy_caps >= svc_rdy_ext->tot_phy_id) return -ENOBUFS; - len = min_t(u16, len, sizeof(struct ath12k_wmi_mac_phy_caps_params)); if (!svc_rdy_ext->n_mac_phy_caps) { - svc_rdy_ext->mac_phy_caps = kzalloc((svc_rdy_ext->tot_phy_id) * len, - GFP_ATOMIC); + svc_rdy_ext->mac_phy_caps = + kzalloc_objs(*svc_rdy_ext->mac_phy_caps, + svc_rdy_ext->tot_phy_id, + GFP_ATOMIC); if (!svc_rdy_ext->mac_phy_caps) return -ENOMEM; } + len = min_t(u16, len, sizeof(struct ath12k_wmi_mac_phy_caps_params)); memcpy(svc_rdy_ext->mac_phy_caps + svc_rdy_ext->n_mac_phy_caps, ptr, len); svc_rdy_ext->n_mac_phy_caps++; return 0; From 7a246c72132eb943b5844ba79dad597b47429dba Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Tue, 28 Jul 2026 18:05:29 -0700 Subject: [PATCH 11/18] wifi: ath11k: fix stride mismatch in mac_phy_caps_parse() Currently, in ath11k_wmi_tlv_mac_phy_caps_parse(), kcalloc() sizes the mac_phy_caps buffer as tot_phy_id * len, where len is clamped to min(firmware_len, sizeof(struct wmi_mac_phy_capabilities)). The subsequent memcpy() destination advances by sizeof(full struct) per slot via C pointer arithmetic, not by the clamped len. When firmware sends short TLVs, the second and later slots are written past the end of the allocation. The reader in ath11k_pull_mac_phy_cap_svc_ready_ext() also indexes the buffer with full-struct pointer arithmetic, so the allocation must match that stride. Fix by using kzalloc_objs(), which derives the element size from the pointer type, making allocation size and pointer stride provably consistent regardless of what len the firmware provides. Compile tested only. Fixes: 5b90fc760db5 ("ath11k: fix wmi service ready ext tlv parsing") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260728-mac_phy_caps_parse-stride-mismatch-v1-2-27a9c1a3fbd0@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index d6feaa710fe2..66547e9ee16c 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -4809,14 +4809,16 @@ static int ath11k_wmi_tlv_mac_phy_caps_parse(struct ath11k_base *soc, if (svc_rdy_ext->n_mac_phy_caps >= svc_rdy_ext->tot_phy_id) return -ENOBUFS; - len = min_t(u16, len, sizeof(struct wmi_mac_phy_capabilities)); if (!svc_rdy_ext->n_mac_phy_caps) { - svc_rdy_ext->mac_phy_caps = kcalloc(svc_rdy_ext->tot_phy_id, - len, GFP_ATOMIC); + svc_rdy_ext->mac_phy_caps = + kzalloc_objs(*svc_rdy_ext->mac_phy_caps, + svc_rdy_ext->tot_phy_id, + GFP_ATOMIC); if (!svc_rdy_ext->mac_phy_caps) return -ENOMEM; } + len = min_t(u16, len, sizeof(struct wmi_mac_phy_capabilities)); memcpy(svc_rdy_ext->mac_phy_caps + svc_rdy_ext->n_mac_phy_caps, ptr, len); svc_rdy_ext->n_mac_phy_caps++; return 0; From 8b8202b2e31367434a5079a6faee31588e5b4aa4 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Thu, 30 Jul 2026 01:04:56 +0900 Subject: [PATCH 12/18] wifi: ath6kl: return 0 explicitly in ath6kl_init_upload() status is always zero at the last return in ath6kl_init_upload(). Explicitly return 0 on the success path instead of returning status. No functional change. Signed-off-by: Sang-Heon Jeon Link: https://patch.msgid.link/20260729160458.201962-1-ekffu200098@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath6kl/init.c b/drivers/net/wireless/ath/ath6kl/init.c index 782209dcb782..6481da4c1991 100644 --- a/drivers/net/wireless/ath/ath6kl/init.c +++ b/drivers/net/wireless/ath/ath6kl/init.c @@ -1570,7 +1570,7 @@ static int ath6kl_init_upload(struct ath6kl *ar) if (status) return status; - return status; + return 0; } int ath6kl_init_hw_params(struct ath6kl *ar) From 8ab38bbac02298e3fb03008ed4a0227485c0fd62 Mon Sep 17 00:00:00 2001 From: ZhaoJinming Date: Wed, 29 Jul 2026 10:00:05 +0800 Subject: [PATCH 13/18] wifi: ath11k: fix resource leak on error in ext IRQ setup In ath11k_ahb_config_irq(), when a CE request_irq() fails, the function returns the error immediately without freeing the CE IRQs that were successfully registered in previous loop iterations. The probe error path does not call ath11k_ahb_free_irq() either, so the previously registered CE IRQ handlers remain attached to the interrupt lines and are never released. In ath11k_ahb_config_ext_irq(), when an external request_irq() fails, the error is only logged and the loop continues. The function then returns 0 indicating success, leaving the device in a partially configured state where some external IRQs are not registered. This causes enable_irq()/disable_irq()/free_irq() to be called on unregistered IRQs during runtime and remove/shutdown, triggering WARN_ON(!desc->action), and missing interrupt handlers lead to data loss. Additionally, if alloc_netdev_dummy() fails for a later IRQ group, the function returns -ENOMEM without freeing the ext IRQs and napi_ndev that were successfully set up for earlier groups. Fix all three issues: propagate the error up to the caller and unwind all successfully registered IRQs and allocated resources on failure. Also move ab->irq_num[irq_idx] assignment after request_irq() succeeds in the ext IRQ path to match the CE IRQ path and avoid storing a stale IRQ number on failure. Signed-off-by: ZhaoJinming Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260729020005.219253-1-zhaojinming@uniontech.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/ahb.c | 79 +++++++++++++++++++-------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index 1e1dea485760..ec5bf5c9fd79 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -431,36 +431,44 @@ static void ath11k_ahb_init_qmi_ce_config(struct ath11k_base *ab) ab->qmi.service_ins_id = ab->hw_params.qmi_service_ins_id; } -static void ath11k_ahb_free_ext_irq(struct ath11k_base *ab) +static void ath11k_ahb_free_ext_irq_grp(struct ath11k_base *ab, + struct ath11k_ext_irq_grp *irq_grp) { - int i, j; + int j; - for (i = 0; i < ATH11K_EXT_IRQ_GRP_NUM_MAX; i++) { - struct ath11k_ext_irq_grp *irq_grp = &ab->ext_irq_grp[i]; + for (j = 0; j < irq_grp->num_irq; j++) + free_irq(ab->irq_num[irq_grp->irqs[j]], irq_grp); - for (j = 0; j < irq_grp->num_irq; j++) - free_irq(ab->irq_num[irq_grp->irqs[j]], irq_grp); - - netif_napi_del(&irq_grp->napi); - free_netdev(irq_grp->napi_ndev); - } + netif_napi_del(&irq_grp->napi); + free_netdev(irq_grp->napi_ndev); } -static void ath11k_ahb_free_irq(struct ath11k_base *ab) +static void ath11k_ahb_free_ext_irq(struct ath11k_base *ab) { - int irq_idx; int i; - if (ab->hw_params.hybrid_bus_type) - return ath11k_pcic_free_irq(ab); + for (i = 0; i < ATH11K_EXT_IRQ_GRP_NUM_MAX; i++) + ath11k_ahb_free_ext_irq_grp(ab, &ab->ext_irq_grp[i]); +} - for (i = 0; i < ab->hw_params.ce_count; i++) { +static void ath11k_ahb_free_ce_irqs(struct ath11k_base *ab, int max_idx) +{ + int irq_idx, i; + + for (i = 0; i < max_idx; i++) { if (ath11k_ce_get_attr_flags(ab, i) & CE_ATTR_DIS_INTR) continue; irq_idx = ATH11K_IRQ_CE0_OFFSET + i; free_irq(ab->irq_num[irq_idx], &ab->ce.ce_pipe[i]); } +} +static void ath11k_ahb_free_irq(struct ath11k_base *ab) +{ + if (ab->hw_params.hybrid_bus_type) + return ath11k_pcic_free_irq(ab); + + ath11k_ahb_free_ce_irqs(ab, ab->hw_params.ce_count); ath11k_ahb_free_ext_irq(ab); } @@ -524,20 +532,25 @@ static irqreturn_t ath11k_ahb_ext_interrupt_handler(int irq, void *arg) static int ath11k_ahb_config_ext_irq(struct ath11k_base *ab) { struct ath11k_hw_params *hw = &ab->hw_params; + struct ath11k_ext_irq_grp *irq_grp; int i, j; int irq; int ret; for (i = 0; i < ATH11K_EXT_IRQ_GRP_NUM_MAX; i++) { - struct ath11k_ext_irq_grp *irq_grp = &ab->ext_irq_grp[i]; u32 num_irq = 0; + irq_grp = &ab->ext_irq_grp[i]; + irq_grp->ab = ab; irq_grp->grp_id = i; irq_grp->napi_ndev = alloc_netdev_dummy(0); - if (!irq_grp->napi_ndev) - return -ENOMEM; + if (!irq_grp->napi_ndev) { + ret = -ENOMEM; + irq_grp->num_irq = 0; + goto err_request_irq; + } netif_napi_add(irq_grp->napi_ndev, &irq_grp->napi, ath11k_ahb_ext_grp_napi_poll); @@ -585,14 +598,11 @@ static int ath11k_ahb_config_ext_irq(struct ath11k_base *ab) } } } - irq_grp->num_irq = num_irq; - - for (j = 0; j < irq_grp->num_irq; j++) { + for (j = 0; j < num_irq; j++) { int irq_idx = irq_grp->irqs[j]; irq = platform_get_irq_byname(ab->pdev, irq_name[irq_idx]); - ab->irq_num[irq_idx] = irq; irq_set_status_flags(irq, IRQ_NOAUTOEN | IRQ_DISABLE_UNLAZY); ret = request_irq(irq, ath11k_ahb_ext_interrupt_handler, IRQF_TRIGGER_RISING, @@ -600,11 +610,24 @@ static int ath11k_ahb_config_ext_irq(struct ath11k_base *ab) if (ret) { ath11k_err(ab, "failed request_irq for %d\n", irq); + irq_grp->num_irq = j; + ath11k_ahb_free_ext_irq_grp(ab, irq_grp); + goto err_request_irq; } + ab->irq_num[irq_idx] = irq; } + + irq_grp->num_irq = num_irq; } return 0; + +err_request_irq: + for (i--; i >= 0; i--) { + irq_grp = &ab->ext_irq_grp[i]; + ath11k_ahb_free_ext_irq_grp(ab, irq_grp); + } + return ret; } static int ath11k_ahb_config_irq(struct ath11k_base *ab) @@ -629,16 +652,24 @@ static int ath11k_ahb_config_irq(struct ath11k_base *ab) ret = request_irq(irq, ath11k_ahb_ce_interrupt_handler, IRQF_TRIGGER_RISING, irq_name[irq_idx], ce_pipe); - if (ret) + if (ret) { + ath11k_err(ab, "failed request_irq for %d\n", irq); + ath11k_ahb_free_ce_irqs(ab, i); return ret; + } ab->irq_num[irq_idx] = irq; } /* Configure external interrupts */ ret = ath11k_ahb_config_ext_irq(ab); + if (ret) { + ath11k_err(ab, "failed to configure ext irq: %d\n", ret); + ath11k_ahb_free_ce_irqs(ab, ab->hw_params.ce_count); + return ret; + } - return ret; + return 0; } static int ath11k_ahb_map_service_to_pipe(struct ath11k_base *ab, u16 service_id, From 0293be2212d319d59589082461abf2a9b626cd1c Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Mon, 27 Jul 2026 16:39:41 -0700 Subject: [PATCH 14/18] wifi: ath11k: fix leak in ath11k_service_ready_ext_event() Currently, during ath11k_service_ready_ext_event() processing, svc_rdy_ext.mac_phy_caps can be allocated during TLV parsing. This is a temporary allocation that is freed on the success path, but not on the error path. If parsing succeeds far enough to allocate mac_phy_caps and then fails on a later TLV, the allocation leaks. So free the allocation on the error path. Compile tested only. Fixes: 5b90fc760db5 ("ath11k: fix wmi service ready ext tlv parsing") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260727-ath11k_service_ready_ext_event-memleak-v1-1-e8373d27bdd1@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 66547e9ee16c..bbca275a8289 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -5135,6 +5135,7 @@ static int ath11k_service_ready_ext_event(struct ath11k_base *ab, return 0; err: + kfree(svc_rdy_ext.mac_phy_caps); ath11k_wmi_free_dbring_caps(ab); return ret; } From 3bbd05723d15dd06f0560bcd94fbf9a91b5f5613 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Mon, 13 Jul 2026 23:32:51 +0200 Subject: [PATCH 15/18] wifi: ath6kl: clamp assoc request/response lengths before subtracting IE offsets ath6kl_cfg80211_connect_event() subtracts fixed IE offsets from assoc_req_len (-= 4) and assoc_resp_len (-= 6), both u8, with no lower bound. The aggregate check recently added to ath6kl_wmi_connect_event_rx() bounds the declared lengths from above (their sum must fit the received event), but an assoc request/response shorter than its fixed offset still underflows here: the u8 wraps to ~250, and cfg80211_connect_result() / cfg80211_roamed() then treat that wrapped value as the IE length and copy that many bytes out of the small assoc_info buffer to user space via nl80211, disclosing adjacent slab memory. Clamp both lengths to their offsets before subtracting. Found by 0sec (https://0sec.ai) using automated source analysis; the missing lower bound is evident from source. Compile-tested. Fixes: bdcd81707973 ("Add ath6kl cleaned up driver") Cc: stable@vger.kernel.org Assisted-by: 0sec:claude-opus-4-8 Signed-off-by: Doruk Tan Ozturk Link: https://patch.msgid.link/20260713213251.21161-1-doruk@0sec.ai Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/cfg80211.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.c b/drivers/net/wireless/ath/ath6kl/cfg80211.c index ecde91159b54..59cf1d0e7f19 100644 --- a/drivers/net/wireless/ath/ath6kl/cfg80211.c +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.c @@ -754,6 +754,11 @@ void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel, u8 *assoc_resp_ie = assoc_info + beacon_ie_len + assoc_req_len + assoc_resp_ie_offset; + if (assoc_req_len < assoc_req_ie_offset) + assoc_req_len = assoc_req_ie_offset; + if (assoc_resp_len < assoc_resp_ie_offset) + assoc_resp_len = assoc_resp_ie_offset; + assoc_req_len -= assoc_req_ie_offset; assoc_resp_len -= assoc_resp_ie_offset; From 1e33f8acd837420160ea088160d8648a3db54c3b Mon Sep 17 00:00:00 2001 From: Reshma Immaculate Rajkumar Date: Wed, 29 Jul 2026 22:47:32 +0530 Subject: [PATCH 16/18] wifi: ath12k: fix encrypted EAPOL TX in encap offload mode When a vif operates with IEEE80211_OFFLOAD_ENCAP_ENABLED, mac80211 delivers EAPOL frames to ath12k in native-WiFi format. Unencrypted EAPOL frames used during the initial 4-way handshake are already handled through the existing is_diff_encap path. However, EAPOL frames transmitted during GTK rekeying carry ATH12K_SKB_CIPHER_SET and continue through the normal native-WiFi transmit path. Firmware encryption requires RAW frames with cipher-specific IV and ICV fields correctly provisioned in the skb. Passing encrypted EAPOL frames in native-WiFi format results in incorrect IV provisioning, leading to an invalid ICV and frame drop. Fix this by detecting the EAPOL frames that need HW encryption and converting them to firmware-encrypted RAW frames before transmission. Reserve IV space after the MAC header, append ICV space at the tail, select the appropriate firmware encryption type and request firmware-side encryption. Introduce ath12k_dp_tx_crypto_iv_len() and ath12k_dp_tx_crypto_icv_len() helpers in the TX path to obtain cipher-specific IV and ICV lengths. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01270-QCAHKSWPL_SILICONZ-1 Fixes: d29591d5b52e ("wifi: ath12k: Advertise encapsulation/decapsulation offload support to mac80211") Signed-off-by: Reshma Immaculate Rajkumar Reviewed-by: Aishwarya R Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260729171732.668367-1-reshma.rajkumar@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_tx.c | 46 +++++++++++++ drivers/net/wireless/ath/ath12k/dp_tx.h | 2 + drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c | 64 ++++++++++++++++++- 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_tx.c b/drivers/net/wireless/ath/ath12k/dp_tx.c index c10da6195c9c..9644f9ef2c74 100644 --- a/drivers/net/wireless/ath/ath12k/dp_tx.c +++ b/drivers/net/wireless/ath/ath12k/dp_tx.c @@ -82,6 +82,52 @@ enum hal_encrypt_type ath12k_dp_tx_get_encrypt_type(u32 cipher) } EXPORT_SYMBOL(ath12k_dp_tx_get_encrypt_type); +u8 ath12k_dp_tx_crypto_iv_len(enum hal_encrypt_type enc_type) +{ + switch (enc_type) { + case HAL_ENCRYPT_TYPE_TKIP_NO_MIC: + case HAL_ENCRYPT_TYPE_TKIP_MIC: + return IEEE80211_TKIP_IV_LEN; + case HAL_ENCRYPT_TYPE_CCMP_128: + return IEEE80211_CCMP_HDR_LEN; + case HAL_ENCRYPT_TYPE_CCMP_256: + return IEEE80211_CCMP_256_HDR_LEN; + case HAL_ENCRYPT_TYPE_GCMP_128: + case HAL_ENCRYPT_TYPE_AES_GCMP_256: + return IEEE80211_GCMP_HDR_LEN; + case HAL_ENCRYPT_TYPE_WEP_40: + case HAL_ENCRYPT_TYPE_WEP_104: + case HAL_ENCRYPT_TYPE_WEP_128: + return IEEE80211_WEP_IV_LEN; + default: + return 0; + } +} +EXPORT_SYMBOL(ath12k_dp_tx_crypto_iv_len); + +u8 ath12k_dp_tx_crypto_icv_len(enum hal_encrypt_type enc_type) +{ + switch (enc_type) { + case HAL_ENCRYPT_TYPE_CCMP_128: + return IEEE80211_CCMP_MIC_LEN; + case HAL_ENCRYPT_TYPE_CCMP_256: + return IEEE80211_CCMP_256_MIC_LEN; + case HAL_ENCRYPT_TYPE_GCMP_128: + case HAL_ENCRYPT_TYPE_AES_GCMP_256: + return IEEE80211_GCMP_MIC_LEN; + case HAL_ENCRYPT_TYPE_TKIP_NO_MIC: + case HAL_ENCRYPT_TYPE_TKIP_MIC: + return IEEE80211_TKIP_ICV_LEN; + case HAL_ENCRYPT_TYPE_WEP_40: + case HAL_ENCRYPT_TYPE_WEP_104: + case HAL_ENCRYPT_TYPE_WEP_128: + return IEEE80211_WEP_ICV_LEN; + default: + return 0; + } +} +EXPORT_SYMBOL(ath12k_dp_tx_crypto_icv_len); + void ath12k_dp_tx_release_txbuf(struct ath12k_dp *dp, struct ath12k_tx_desc_info *tx_desc, u8 pool_id) diff --git a/drivers/net/wireless/ath/ath12k/dp_tx.h b/drivers/net/wireless/ath/ath12k/dp_tx.h index 7cef20540179..1af79af2ada2 100644 --- a/drivers/net/wireless/ath/ath12k/dp_tx.h +++ b/drivers/net/wireless/ath/ath12k/dp_tx.h @@ -19,6 +19,8 @@ enum hal_tcl_encap_type ath12k_dp_tx_get_encap_type(struct ath12k_base *ab, struct sk_buff *skb); void ath12k_dp_tx_encap_nwifi(struct sk_buff *skb); u8 ath12k_dp_tx_get_tid(struct sk_buff *skb); +u8 ath12k_dp_tx_crypto_iv_len(enum hal_encrypt_type enc_type); +u8 ath12k_dp_tx_crypto_icv_len(enum hal_encrypt_type enc_type); void *ath12k_dp_metadata_align_skb(struct sk_buff *skb, u8 tail_len); int ath12k_dp_tx_align_payload(struct ath12k_dp *dp, struct sk_buff **pskb); void ath12k_dp_tx_release_txbuf(struct ath12k_dp *dp, diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c index d2749de44553..587d58eeccfa 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c @@ -13,6 +13,49 @@ #include "hal.h" #include "hal_tx.h" +/* + * Convert an encrypted EAPOL frame from native-WiFi format to + * the layout expected by the firmware RAW encrypt pipeline: + * + * [802.11 hdr][IV (zeroed)][LLC/SNAP][EAPOL payload][ICV (zeroed)] + * + * mac80211 delivers the frame as [802.11 hdr][LLC/SNAP][EAPOL payload]. + * The MAC header length is read from the unmodified skb and is safe because + * ieee80211_hdrlen() only inspects the 2-byte frame_control field. + * pskb_expand_head() is used to grow both head (for the IV) and tail + * (for the ICV) in a single call and allocation. + */ +static int +ath12k_wifi7_dp_tx_encap_eapol(struct sk_buff *skb, + struct hal_tx_info *ti, + struct ath12k_skb_cb *skb_cb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + enum hal_encrypt_type enc_type = + ath12k_dp_tx_get_encrypt_type(skb_cb->cipher); + u16 mac_hdr_len = ieee80211_hdrlen(hdr->frame_control); + u8 iv_len = ath12k_dp_tx_crypto_iv_len(enc_type); + u8 icv_len = ath12k_dp_tx_crypto_icv_len(enc_type); + + if (pskb_expand_head(skb, iv_len, icv_len, GFP_ATOMIC)) + return -ENOMEM; + + if (iv_len) { + skb_push(skb, iv_len); + memmove(skb->data, skb->data + iv_len, mac_hdr_len); + memset(skb->data + mac_hdr_len, 0, iv_len); + } + + if (icv_len) + memset(skb_put(skb, icv_len), 0, icv_len); + + ti->flags0 |= u32_encode_bits(1, HAL_TCL_DATA_CMD_INFO2_TO_FW); + ti->encap_type = HAL_TCL_ENCAP_TYPE_RAW; + ti->encrypt_type = enc_type; + + return 0; +} + static void ath12k_wifi7_hal_tx_cmd_ext_desc_setup(struct ath12k_base *ab, struct hal_tx_msdu_ext_desc *tcl_ext_cmd, @@ -91,6 +134,7 @@ int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *a u32 iova_mask = dp->hw_params->iova_mask; bool is_diff_encap = false; bool is_null_frame = false; + bool eapol_encap_done = false; if (test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)) return -ESHUTDOWN; @@ -211,9 +255,27 @@ int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *a case HAL_TCL_ENCAP_TYPE_NATIVE_WIFI: is_null_frame = ieee80211_is_nullfunc(hdr->frame_control); if (ahvif->vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) { - if (skb->protocol == cpu_to_be16(ETH_P_PAE) || is_null_frame) + if ((skb->protocol == cpu_to_be16(ETH_P_PAE) && + !(skb_cb->flags & ATH12K_SKB_CIPHER_SET)) || is_null_frame) is_diff_encap = true; + if (skb->protocol == cpu_to_be16(ETH_P_PAE) && + (skb_cb->flags & ATH12K_SKB_CIPHER_SET)) { + if (!eapol_encap_done) { + ret = ath12k_wifi7_dp_tx_encap_eapol(skb, &ti, + skb_cb); + if (ret) + goto fail_remove_tx_buf; + hdr = (void *)skb->data; + eapol_encap_done = true; + } else { + ti.flags0 |= u32_encode_bits(1, + HAL_TCL_DATA_CMD_INFO2_TO_FW); + ti.encap_type = HAL_TCL_ENCAP_TYPE_RAW; + ti.encrypt_type = + ath12k_dp_tx_get_encrypt_type(skb_cb->cipher); + } + } /* Firmware expects msdu ext descriptor for nwifi/raw packets * received in ETH mode. Without this, observed tx fail for * Multicast packets in ETH mode. From 35a3da9fe1b212a9012952a04f383b8dc3708dd7 Mon Sep 17 00:00:00 2001 From: Linghui Wu Date: Thu, 30 Jul 2026 08:02:26 +0530 Subject: [PATCH 17/18] wifi: ath10k: filter non-UTF testmode events When UTF monitor is enabled, ath10k forwards WMI events to nl80211 testmode. Non-UTF events can therefore be delivered to userspace and confuse FTM tools which expect only UTF responses. Only forward known UTF event IDs from WMI event namespaces that route events through ath10k_tm_event_wmi(), and drop other WMI events while UTF monitor is active. READY events are still handled by the normal WMI receive path. Tested-on: WCN3990 hw1.0 SNOC WLAN.HL.3.3.7.c5-00093.2-QCAHLSWMTPL-1 Signed-off-by: Linghui Wu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260730023226.707008-1-linghui.wu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/testmode.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/net/wireless/ath/ath10k/testmode.c b/drivers/net/wireless/ath/ath10k/testmode.c index d3bd385694d6..282ae6e20c8e 100644 --- a/drivers/net/wireless/ath/ath10k/testmode.c +++ b/drivers/net/wireless/ath/ath10k/testmode.c @@ -156,6 +156,14 @@ static void ath10k_tm_event_segmented(struct ath10k *ar, u32 cmd_id, struct sk_b cfg80211_testmode_event(nl_skb, GFP_ATOMIC); } +static bool ath10k_tm_is_utf_event(u32 cmd_id) +{ + return cmd_id == WMI_10X_PDEV_UTF_EVENTID || + cmd_id == WMI_10_2_PDEV_UTF_EVENTID || + cmd_id == WMI_10_4_PDEV_UTF_EVENTID || + cmd_id == WMI_TLV_PDEV_UTF_EVENTID; +} + /* Returns true if callee consumes the skb and the skb should be discarded. * Returns false if skb is not used. Does not sleep. */ @@ -182,6 +190,12 @@ bool ath10k_tm_event_wmi(struct ath10k *ar, u32 cmd_id, struct sk_buff *skb) */ consumed = true; + if (!ath10k_tm_is_utf_event(cmd_id)) { + ath10k_dbg(ar, ATH10K_DBG_TESTMODE, + "testmode drop non-utf event cmd_id %u\n", cmd_id); + goto out; + } + if (ar->testmode.expected_seq != ATH10K_FTM_SEG_NONE) ath10k_tm_event_segmented(ar, cmd_id, skb); else From 4f25071afe9218aaae1c63fbf75e229aa6405319 Mon Sep 17 00:00:00 2001 From: Linghui Wu Date: Mon, 27 Jul 2026 12:56:29 +0530 Subject: [PATCH 18/18] wifi: ath10k: snoc: use memcpy_fromio() for MSA ramdump On WCN3990/SNOC the MSA region is mapped with devm_memremap(MEMREMAP_WT). On arm64 such a mapping is not Normal-cacheable, so unaligned accesses to it are not permitted. ath10k_msa_dump_memory() copies the region with a plain memcpy(), whose optimized __pi_memcpy_generic implementation issues wide/unaligned loads. This triggers an alignment fault (FSC=0x21) Oops in ath10k_snoc_fw_crashed_dump() while collecting the devcoredump: Unable to handle kernel paging request ... FSC=0x21: alignment fault pc : __pi_memcpy_generic lr : ath10k_snoc_fw_crashed_dump [ath10k_snoc] The Oops both leaves the firmware RAM dump buffer zeroed (no dump is captured) and crashes the kernel, which in turn breaks modem SSR recovery. Use memcpy_fromio(), which only performs accesses that are valid for such a device-memory mapping. The generic memcpy_fromio() implementation aligns the source before issuing word-sized reads and stores the destination with put_unaligned(), so it is also safe for the coherent DMA allocation used on the non-reserved-memory path. ath11k and ath12k use the same pattern when copying target memory into crash dumps, so call it unconditionally here too. The MEMREMAP_WT pointer is a plain void *, so an explicit __iomem cast is needed; use __force to keep sparse happy. Tested-on: WCN3990 hw1.0 SNOC WLAN.HL.3.3.7.c5-00107-QCAHLSWMTPL-1 Fixes: 3f14b73c3843 ("ath10k: Enable MSA region dump support for WCN3990") Signed-off-by: Linghui Wu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260727072629.2297208-1-linghui.wu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/snoc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c index 310650227578..33c98927e8fe 100644 --- a/drivers/net/wireless/ath/ath10k/snoc.c +++ b/drivers/net/wireless/ath/ath10k/snoc.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -1475,11 +1476,15 @@ static void ath10k_msa_dump_memory(struct ath10k *ar, hdr->length = cpu_to_le32(ar->msa.mem_size); if (current_region->len < ar->msa.mem_size) { - memcpy(buf, ar->msa.vaddr, current_region->len); + memcpy_fromio(buf, + (const void __iomem __force *)ar->msa.vaddr, + current_region->len); ath10k_warn(ar, "msa dump length is less than msa size %x, %x\n", current_region->len, ar->msa.mem_size); } else { - memcpy(buf, ar->msa.vaddr, ar->msa.mem_size); + memcpy_fromio(buf, + (const void __iomem __force *)ar->msa.vaddr, + ar->msa.mem_size); } }