From 25e638447e329f08febae2b64c7f85b3bb95e998 Mon Sep 17 00:00:00 2001 From: Hangtian Zhu Date: Tue, 19 May 2026 09:16:26 +0800 Subject: [PATCH 01/58] genirq: export irq_can_set_affinity() for module drivers Export irq_can_set_affinity() for loadable drivers that need a runtime check for IRQ affinity capability. In hierarchical IRQ setups where the effective irqchip path lacks .irq_set_affinity(), drivers may need to switch to a fallback policy. Without this export, module drivers cannot use the core helper and have to open-code equivalent checks. Signed-off-by: Hangtian Zhu Acked-by: Thomas Gleixner Link: https://patch.msgid.link/20260519011627.713068-2-hangtian.zhu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- kernel/irq/manage.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 7eb07e3bdb4c..f73fda08417a 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -171,6 +171,7 @@ int irq_can_set_affinity(unsigned int irq) { return __irq_can_set_affinity(irq_to_desc(irq)); } +EXPORT_SYMBOL_GPL(irq_can_set_affinity); /** * irq_can_set_affinity_usr - Check if affinity of a irq can be set from user space From 74e1a4762c79a2d6879495d83f22b83acc4dae33 Mon Sep 17 00:00:00 2001 From: Hangtian Zhu Date: Tue, 19 May 2026 09:16:27 +0800 Subject: [PATCH 02/58] wifi: ath12k: enable threaded NAPI when DP IRQ affinity is unavailable Determine threaded NAPI policy from runtime IRQ capability of the DP MSI IRQ. If irq_can_set_affinity() reports that affinity cannot be set, enable threaded NAPI for DP interrupt groups so datapath processing is not constrained by a single-CPU softirq context. On RB3Gen2, where IRQ affinity is unavailable in the effective IRQ path, EHT160 UDP downlink throughput improved from 802 Mbps to 2.58 Gbps after enabling threaded NAPI. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00074-QCACOLSWPL_V1_TO_SILICONZ-1 Signed-off-by: Hangtian Zhu Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260519011627.713068-3-hangtian.zhu@oss.qualcomm.com [Fixed checkpatch "Missing a blank line after declarations"] Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/pci.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index d9a22d6afbb0..ae8a9acee3f1 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -537,6 +538,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) int i, j, n, ret, num_vectors = 0; u32 user_base_data = 0, base_vector = 0, base_idx; struct ath12k_ext_irq_grp *irq_grp; + bool threaded_napi = false; + int irq; base_idx = ATH12K_PCI_IRQ_CE0_OFFSET + CE_COUNT_MAX; ret = ath12k_pci_get_user_msi_assignment(ab, "DP", @@ -546,6 +549,10 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) if (ret < 0) return ret; + irq = ath12k_pci_get_msi_irq(ab->dev, base_vector); + if (irq >= 0) + threaded_napi = !irq_can_set_affinity(irq); + for (i = 0; i < ATH12K_EXT_IRQ_GRP_NUM_MAX; i++) { irq_grp = &ab->ext_irq_grp[i]; u32 num_irq = 0; @@ -560,6 +567,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) netif_napi_add(irq_grp->napi_ndev, &irq_grp->napi, ath12k_pci_ext_grp_napi_poll); + if (threaded_napi) + netif_threaded_enable(irq_grp->napi_ndev); if (ab->hw_params->ring_mask->tx[i] || ab->hw_params->ring_mask->rx[i] || @@ -578,7 +587,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) for (j = 0; j < irq_grp->num_irq; j++) { int irq_idx = irq_grp->irqs[j]; int vector = (i % num_vectors) + base_vector; - int irq = ath12k_pci_get_msi_irq(ab->dev, vector); + + irq = ath12k_pci_get_msi_irq(ab->dev, vector); ab->irq_num[irq_idx] = irq; From c29d1550e166598b9ee6d48c488eec0e79f98d52 Mon Sep 17 00:00:00 2001 From: Raj Kumar Bhagat Date: Tue, 23 Jun 2026 09:43:09 +0530 Subject: [PATCH 03/58] wifi: ath12k: Fix inconsistencies in struct qmi_elem_info initializers Currently, the struct qmi_elem_info initializers in qmi.c are inconsistent in how they align the assignments, with tabs being used in the majority of places but spaces being used in some places. In those places replace the spaces with tabs for consistency. Also fix incorrect and missing terminating records in the following qmi_elem_info initializers: - qmi_wlanfw_shadow_reg_cfg_s_v01_ei[] - qmi_wlanfw_mem_ready_ind_msg_v01_ei[] - qmi_wlanfw_fw_ready_ind_msg_v01_ei[] Tested-on: Compile tested only. Signed-off-by: Raj Kumar Bhagat Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260623-qmi-inconsistencies-v1-1-0fc17f2b8338@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.c | 144 ++++++++++++++------------ 1 file changed, 75 insertions(+), 69 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index fd762b5d7bb5..c176cc150f64 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -21,45 +21,45 @@ static const struct qmi_elem_info wlfw_host_mlo_chip_info_s_v01_ei[] = { { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = 1, - .elem_size = sizeof(u8), + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), .array_type = NO_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, chip_id), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = 1, - .elem_size = sizeof(u8), + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), .array_type = NO_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, num_local_links), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, - .elem_size = sizeof(u8), - .array_type = STATIC_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, + .elem_size = sizeof(u8), + .array_type = STATIC_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, hw_link_id), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, - .elem_size = sizeof(u8), - .array_type = STATIC_ARRAY, - .tlv_type = 0, - .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01, + .elem_size = sizeof(u8), + .array_type = STATIC_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01, valid_mlo_link_id), }, { - .data_type = QMI_EOTI, + .data_type = QMI_EOTI, .array_type = NO_ARRAY, - .tlv_type = QMI_COMMON_TLV_TYPE, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; @@ -585,21 +585,21 @@ static const struct qmi_elem_info qmi_wlanfw_phy_cap_resp_msg_v01_ei[] = { board_id), }, { - .data_type = QMI_OPT_FLAG, - .elem_len = 1, - .elem_size = sizeof(u8), - .array_type = NO_ARRAY, - .tlv_type = 0x13, - .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, single_chip_mlo_support_valid), }, { - .data_type = QMI_UNSIGNED_1_BYTE, - .elem_len = 1, - .elem_size = sizeof(u8), - .array_type = NO_ARRAY, - .tlv_type = 0x13, - .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, single_chip_mlo_support), }, { @@ -1625,42 +1625,45 @@ static const struct qmi_elem_info qmi_wlanfw_m3_info_resp_msg_v01_ei[] = { static const struct qmi_elem_info qmi_wlanfw_aux_uc_info_req_msg_v01_ei[] = { { - .data_type = QMI_UNSIGNED_8_BYTE, - .elem_len = 1, - .elem_size = sizeof(u64), - .array_type = NO_ARRAY, - .tlv_type = 0x01, - .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, addr), + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, + addr), }, { - .data_type = QMI_UNSIGNED_4_BYTE, - .elem_len = 1, - .elem_size = sizeof(u32), - .array_type = NO_ARRAY, - .tlv_type = 0x02, - .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, size), + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, + size), }, { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, - .tlv_type = QMI_COMMON_TLV_TYPE, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; static const struct qmi_elem_info qmi_wlanfw_aux_uc_info_resp_msg_v01_ei[] = { { - .data_type = QMI_STRUCT, - .elem_len = 1, - .elem_size = sizeof(struct qmi_response_type_v01), - .array_type = NO_ARRAY, - .tlv_type = 0x02, - .offset = offsetof(struct qmi_wlanfw_aux_uc_info_resp_msg_v01, resp), - .ei_array = qmi_response_type_v01_ei, + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct qmi_wlanfw_aux_uc_info_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, }, { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, - .tlv_type = QMI_COMMON_TLV_TYPE, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; @@ -1772,7 +1775,8 @@ static const struct qmi_elem_info qmi_wlanfw_shadow_reg_cfg_s_v01_ei[] = { }, { .data_type = QMI_EOTI, - .array_type = QMI_COMMON_TLV_TYPE, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; @@ -1925,7 +1929,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = { .data_type = QMI_OPT_FLAG, .elem_len = 1, .elem_size = sizeof(u8), - .array_type = NO_ARRAY, + .array_type = NO_ARRAY, .tlv_type = 0x13, .offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01, shadow_reg_valid), @@ -1934,7 +1938,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = { .data_type = QMI_DATA_LEN, .elem_len = 1, .elem_size = sizeof(u8), - .array_type = NO_ARRAY, + .array_type = NO_ARRAY, .tlv_type = 0x13, .offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01, shadow_reg_len), @@ -1943,7 +1947,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = { .data_type = QMI_STRUCT, .elem_len = QMI_WLANFW_MAX_NUM_SHADOW_REG_V01, .elem_size = sizeof(struct qmi_wlanfw_shadow_reg_cfg_s_v01), - .array_type = VAR_LEN_ARRAY, + .array_type = VAR_LEN_ARRAY, .tlv_type = 0x13, .offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01, shadow_reg), @@ -2003,15 +2007,17 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_resp_msg_v01_ei[] = { static const struct qmi_elem_info qmi_wlanfw_mem_ready_ind_msg_v01_ei[] = { { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; static const struct qmi_elem_info qmi_wlanfw_fw_ready_ind_msg_v01_ei[] = { { - .data_type = QMI_EOTI, - .array_type = NO_ARRAY, + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, }, }; From 26d529b99861707ed0a4c626184b9399bedca808 Mon Sep 17 00:00:00 2001 From: Raj Kumar Bhagat Date: Tue, 23 Jun 2026 09:34:17 +0530 Subject: [PATCH 04/58] wifi: ath12k: use %u for unsigned variables in QMI debug logs Replace incorrect %d format specifiers with %u for unsigned variables in qmi.c debug messages. Also add missing trailing '\n' in log messages to ensure proper termination. No functional change intended. Tested-on: Compile tested only. Signed-off-by: Raj Kumar Bhagat Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260623-qmi-debug-log-v1-1-79471aa8b898@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.c | 70 +++++++++++++-------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index c176cc150f64..1f3efcc16ac3 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -2100,14 +2100,14 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab, if (!ag->mlo_capable) { ath12k_dbg(ab, ATH12K_DBG_QMI, - "MLO is disabled hence skip QMI MLO cap"); + "MLO is disabled hence skip QMI MLO cap\n"); return 0; } if (!ab->qmi.num_radios || ab->qmi.num_radios == U8_MAX) { ag->mlo_capable = false; ath12k_dbg(ab, ATH12K_DBG_QMI, - "skip QMI MLO cap due to invalid num_radio %d\n", + "skip QMI MLO cap due to invalid num_radio %u\n", ab->qmi.num_radios); return 0; } @@ -2131,7 +2131,7 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab, req->mlo_num_chips_valid = 1; req->mlo_num_chips = ag->num_devices; - ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo capability advertisement device_id %d group_id %d num_devices %d", + ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo capability advertisement device_id %u group_id %u num_devices %u\n", req->mlo_chip_id, req->mlo_group_id, req->mlo_num_chips); mutex_lock(&ag->mutex); @@ -2152,14 +2152,14 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab, info->chip_id = partner_ab->device_id; info->num_local_links = partner_ab->qmi.num_radios; - ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo device id %d num_link %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo device id %u num_link %u\n", info->chip_id, info->num_local_links); for (j = 0; j < info->num_local_links; j++) { info->hw_link_id[j] = partner_ab->wsi_info.hw_link_id_base + j; info->valid_mlo_link_id[j] = 1; - ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo hw_link_id %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo hw_link_id %u\n", info->hw_link_id[j]); hw_link_id++; @@ -2274,7 +2274,7 @@ int ath12k_qmi_host_cap_send(struct ath12k_base *ab) goto out; if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "Host capability request failed, result: %d, err: %d\n", + ath12k_warn(ab, "Host capability request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -2326,7 +2326,7 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab) ab->qmi.num_radios = resp.num_phy; ath12k_dbg(ab, ATH12K_DBG_QMI, - "phy capability resp valid %d single_chip_mlo_support %d valid %d num_phy %d valid %d board_id %d\n", + "phy capability resp valid %u single_chip_mlo_support %u valid %u num_phy %u valid %u board_id %u\n", resp.single_chip_mlo_support_valid, resp.single_chip_mlo_support, resp.num_phy_valid, resp.num_phy, resp.board_id_valid, resp.board_id); @@ -2338,7 +2338,7 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab) ab->qmi.num_radios = ab->hw_params->def_num_link; ath12k_dbg(ab, ATH12K_DBG_QMI, - "no valid response from PHY capability, choose default num_phy %d\n", + "no valid response from PHY capability, choose default num_phy %u\n", ab->qmi.num_radios); } @@ -2399,7 +2399,7 @@ static int ath12k_qmi_fw_ind_register_send(struct ath12k_base *ab) } if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "FW Ind register request failed, result: %d, err: %d\n", + ath12k_warn(ab, "FW Ind register request failed, result: %u, err: %u\n", resp->resp.result, resp->resp.error); ret = -EINVAL; goto out; @@ -2434,7 +2434,7 @@ int ath12k_qmi_respond_fw_mem_request(struct ath12k_base *ab) if (!test_bit(ATH12K_FLAG_FIXED_MEM_REGION, &ab->dev_flags) && ab->qmi.target_mem_delayed) { delayed = true; - ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi delays mem_request %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi delays mem_request %u\n", ab->qmi.mem_seg_count); } else { delayed = false; @@ -2480,7 +2480,7 @@ int ath12k_qmi_respond_fw_mem_request(struct ath12k_base *ab) if (delayed && resp.resp.error == 0) goto out; - ath12k_warn(ab, "Respond mem req failed, result: %d, err: %d\n", + ath12k_warn(ab, "Respond mem req failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -2612,13 +2612,13 @@ static int ath12k_qmi_alloc_chunk(struct ath12k_base *ab, if (chunk->size > ATH12K_QMI_MAX_CHUNK_SIZE) { ab->qmi.target_mem_delayed = true; ath12k_warn(ab, - "qmi dma allocation failed (%d B type %u), will try later with small size\n", + "qmi dma allocation failed (%u B type %u), will try later with small size\n", chunk->size, chunk->type); ath12k_qmi_free_target_mem_chunk(ab); return -EAGAIN; } - ath12k_warn(ab, "memory allocation failure for %u size: %d\n", + ath12k_warn(ab, "memory allocation failure for %u size: %u\n", chunk->type, chunk->size); return -ENOMEM; } @@ -2665,7 +2665,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) mlo_size += chunk->size; if (ag->mlo_mem.mlo_mem_size && mlo_size > ag->mlo_mem.mlo_mem_size) { - ath12k_err(ab, "QMI MLO memory allocation failure, requested size %d is more than allocated size %d", + ath12k_err(ab, "QMI MLO memory allocation failure, requested size %d is more than allocated size %d\n", mlo_size, ag->mlo_mem.mlo_mem_size); ret = -EINVAL; goto err; @@ -2674,7 +2674,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) mlo_chunk = &ag->mlo_mem.chunk[mlo_idx]; if (mlo_chunk->paddr) { if (chunk->size != mlo_chunk->size) { - ath12k_err(ab, "QMI MLO chunk memory allocation failure for index %d, requested size %d is more than allocated size %d", + ath12k_err(ab, "QMI MLO chunk memory allocation failure for index %d, requested size %u is more than allocated size %u\n", mlo_idx, chunk->size, mlo_chunk->size); ret = -EINVAL; goto err; @@ -2705,7 +2705,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) if (!ag->mlo_mem.mlo_mem_size) { ag->mlo_mem.mlo_mem_size = mlo_size; } else if (ag->mlo_mem.mlo_mem_size != mlo_size) { - ath12k_err(ab, "QMI MLO memory size error, expected size is %d but requested size is %d", + ath12k_err(ab, "QMI MLO memory size error, expected size is %d but requested size is %d\n", ag->mlo_mem.mlo_mem_size, mlo_size); ret = -EINVAL; goto err; @@ -2890,7 +2890,7 @@ int ath12k_qmi_request_target_cap(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi targetcap req failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi targetcap req failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -2942,7 +2942,7 @@ int ath12k_qmi_request_target_cap(struct ath12k_base *ab) ab->qmi.target.chip_id, ab->qmi.target.chip_family, ab->qmi.target.board_id, ab->qmi.target.soc_id); - ath12k_info(ab, "fw_version 0x%x fw_build_timestamp %s fw_build_id %s", + ath12k_info(ab, "fw_version 0x%x fw_build_timestamp %s fw_build_id %s\n", ab->qmi.target.fw_version, ab->qmi.target.fw_build_timestamp, ab->qmi.target.fw_build_id); @@ -3012,7 +3012,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab, if (ret < 0) goto out; - ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi bdf download req fixed addr type %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi bdf download req fixed addr type %u\n", type); ret = qmi_send_request(&ab->qmi.handle, NULL, &txn, @@ -3029,7 +3029,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab, goto out; if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi BDF download failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi BDF download failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3042,7 +3042,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab, temp += req->data_len; req->seg_id++; ath12k_dbg(ab, ATH12K_DBG_QMI, - "qmi bdf download request remaining %i\n", + "qmi bdf download request remaining %u\n", remaining); } } @@ -3133,7 +3133,7 @@ int ath12k_qmi_load_bdf_qmi(struct ath12k_base *ab, release_firmware(fw_entry); return ret; default: - ath12k_warn(ab, "unknown file type for load %d", type); + ath12k_warn(ab, "unknown file type for load %d\n", type); goto out; } @@ -3245,7 +3245,7 @@ int ath12k_qmi_wlanfw_m3_info_send(struct ath12k_base *ab) if (ab->hw_params->fw.m3_loader == ath12k_m3_fw_loader_driver) { ret = ath12k_qmi_m3_load(ab); if (ret) { - ath12k_err(ab, "failed to load m3 firmware: %d", ret); + ath12k_err(ab, "failed to load m3 firmware: %d\n", ret); return ret; } req.addr = m3_mem->paddr; @@ -3275,7 +3275,7 @@ int ath12k_qmi_wlanfw_m3_info_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi M3 info request failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi M3 info request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3370,7 +3370,7 @@ int ath12k_qmi_wlanfw_aux_uc_info_send(struct ath12k_base *ab) ret = ath12k_qmi_aux_uc_load(ab); if (ret) { - ath12k_err(ab, "failed to load aux_uc firmware: %d", ret); + ath12k_err(ab, "failed to load aux_uc firmware: %d\n", ret); return ret; } @@ -3400,7 +3400,7 @@ int ath12k_qmi_wlanfw_aux_uc_info_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi AUX_UC info request failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi AUX_UC info request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3432,7 +3432,7 @@ static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab, qmi_wlanfw_wlan_mode_req_msg_v01_ei, &req); if (ret < 0) { qmi_txn_cancel(&txn); - ath12k_warn(ab, "qmi failed to send mode request, mode: %d, err = %d\n", + ath12k_warn(ab, "qmi failed to send mode request, mode: %u, err = %d\n", mode, ret); goto out; } @@ -3443,13 +3443,13 @@ static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab, ath12k_warn(ab, "WLFW service is dis-connected\n"); return 0; } - ath12k_warn(ab, "qmi failed set mode request, mode: %d, err = %d\n", + ath12k_warn(ab, "qmi failed set mode request, mode: %u, err = %d\n", mode, ret); goto out; } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "Mode request failed, mode: %d, result: %d err: %d\n", + ath12k_warn(ab, "Mode request failed, mode: %u, result: %u err: %u\n", mode, resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3542,7 +3542,7 @@ static int ath12k_qmi_wlanfw_wlan_cfg_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "qmi wlan config request failed, result: %d, err: %d\n", + ath12k_warn(ab, "qmi wlan config request failed, result: %u, err: %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3586,7 +3586,7 @@ static int ath12k_qmi_wlanfw_wlan_ini_send(struct ath12k_base *ab) } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { - ath12k_warn(ab, "QMI wlan ini response failure: %d %d\n", + ath12k_warn(ab, "QMI wlan ini response failure: %u %u\n", resp.resp.result, resp.resp.error); ret = -EINVAL; goto out; @@ -3669,7 +3669,7 @@ void ath12k_qmi_trigger_host_cap(struct ath12k_base *ab) spin_unlock(&qmi->event_lock); - ath12k_dbg(ab, ATH12K_DBG_QMI, "trigger host cap for device id %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "trigger host cap for device id %u\n", ab->device_id); ath12k_qmi_driver_event_post(qmi, ATH12K_QMI_EVENT_HOST_CAP, NULL); @@ -3839,7 +3839,7 @@ static void ath12k_qmi_msg_mem_request_cb(struct qmi_handle *qmi_hdl, for (i = 0; i < qmi->mem_seg_count ; i++) { ab->qmi.target_mem[i].type = msg->mem_seg[i].type; ab->qmi.target_mem[i].size = msg->mem_seg[i].size; - ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi mem seg type %d size %d\n", + ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi mem seg type %d size %u\n", msg->mem_seg[i].type, msg->mem_seg[i].size); } @@ -3960,7 +3960,7 @@ static int ath12k_qmi_event_host_cap(struct ath12k_qmi *qmi) ret = ath12k_qmi_host_cap_send(ab); if (ret < 0) { - ath12k_warn(ab, "failed to send qmi host cap for device id %d: %d\n", + ath12k_warn(ab, "failed to send qmi host cap for device id %u: %d\n", ab->device_id, ret); return ret; } @@ -4030,7 +4030,7 @@ static void ath12k_qmi_driver_event_work(struct work_struct *work) set_bit(ATH12K_FLAG_QMI_FAIL, &ab->dev_flags); break; default: - ath12k_warn(ab, "invalid event type: %d", event->type); + ath12k_warn(ab, "invalid event type: %d\n", event->type); break; } From 007b638ed7242daeea7c1078e8f732d127c790f3 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 23 Jun 2026 09:21:04 +0530 Subject: [PATCH 05/58] wifi: ath12k: remove unused QMI definitions The driver contains several unused QMI definitions such as response length macros, message IDs, firmware segment length definitions, and CALDB address size definitions. Remove these unused definitions as they are not referenced anywhere in the driver. No functional change intended. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aaradhana Sahu Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260623035104.3765404-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.h | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index 2a63e214eb42..80a9b42a2548 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -13,7 +13,6 @@ #define ATH12K_HOST_VERSION_STRING "WIN" #define ATH12K_QMI_WLANFW_TIMEOUT_MS 10000 #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_VERS_V01 0x01 #define ATH12K_QMI_WLFW_SERVICE_INS_ID_V01 0x02 @@ -24,9 +23,7 @@ #define ATH12K_QMI_WLANFW_MAX_TIMESTAMP_LEN_V01 32 #define ATH12K_QMI_RESP_LEN_MAX 8192 #define ATH12K_QMI_WLANFW_MAX_NUM_MEM_SEG_V01 52 -#define ATH12K_QMI_CALDB_SIZE 0x480000 #define ATH12K_QMI_BDF_EXT_STR_LENGTH 0x20 -#define ATH12K_QMI_FW_MEM_REQ_SEGMENT_CNT 3 #define ATH12K_QMI_WLFW_MAX_DEV_MEM_NUM_V01 4 #define ATH12K_QMI_DEVMEM_CMEM_INDEX 0 @@ -160,8 +157,6 @@ struct ath12k_qmi { #define QMI_WLANFW_HOST_CAP_REQ_MSG_V01_MAX_LEN 261 #define QMI_WLANFW_HOST_CAP_REQ_V01 0x0034 -#define QMI_WLANFW_HOST_CAP_RESP_MSG_V01_MAX_LEN 7 -#define QMI_WLFW_HOST_CAP_RESP_V01 0x0034 #define QMI_WLFW_MAX_NUM_GPIO_V01 32 #define QMI_WLANFW_MAX_PLATFORM_NAME_LEN_V01 64 #define QMI_WLANFW_MAX_HOST_DDR_RANGE_SIZE_V01 3 @@ -267,8 +262,6 @@ struct qmi_wlanfw_host_cap_resp_msg_v01 { #define QMI_WLANFW_PHY_CAP_REQ_MSG_V01_MAX_LEN 0 #define QMI_WLANFW_PHY_CAP_REQ_V01 0x0057 -#define QMI_WLANFW_PHY_CAP_RESP_MSG_V01_MAX_LEN 18 -#define QMI_WLANFW_PHY_CAP_RESP_V01 0x0057 struct qmi_wlanfw_phy_cap_req_msg_v01 { }; @@ -285,8 +278,6 @@ struct qmi_wlanfw_phy_cap_resp_msg_v01 { #define QMI_WLANFW_IND_REGISTER_REQ_MSG_V01_MAX_LEN 54 #define QMI_WLANFW_IND_REGISTER_REQ_V01 0x0020 -#define QMI_WLANFW_IND_REGISTER_RESP_MSG_V01_MAX_LEN 18 -#define QMI_WLANFW_IND_REGISTER_RESP_V01 0x0020 #define QMI_WLANFW_CLIENT_ID 0x4b4e454c struct qmi_wlanfw_ind_register_req_msg_v01 { @@ -322,12 +313,8 @@ struct qmi_wlanfw_ind_register_resp_msg_v01 { u64 fw_status; }; -#define QMI_WLANFW_REQUEST_MEM_IND_MSG_V01_MAX_LEN 1824 #define QMI_WLANFW_RESPOND_MEM_REQ_MSG_V01_MAX_LEN 888 -#define QMI_WLANFW_RESPOND_MEM_RESP_MSG_V01_MAX_LEN 7 -#define QMI_WLANFW_REQUEST_MEM_IND_V01 0x0035 #define QMI_WLANFW_RESPOND_MEM_REQ_V01 0x0036 -#define QMI_WLANFW_RESPOND_MEM_RESP_V01 0x0036 #define QMI_WLANFW_MAX_NUM_MEM_CFG_V01 2 #define QMI_WLANFW_MAX_STR_LEN_V01 16 @@ -385,9 +372,7 @@ struct qmi_wlanfw_fw_ready_ind_msg_v01 { }; #define QMI_WLANFW_CAP_REQ_MSG_V01_MAX_LEN 0 -#define QMI_WLANFW_CAP_RESP_MSG_V01_MAX_LEN 207 #define QMI_WLANFW_CAP_REQ_V01 0x0024 -#define QMI_WLANFW_CAP_RESP_V01 0x0024 enum qmi_wlanfw_pipedir_enum_v01 { QMI_WLFW_PIPEDIR_NONE_V01 = 0, @@ -500,8 +485,6 @@ struct qmi_wlanfw_cap_req_msg_v01 { }; #define QMI_WLANFW_BDF_DOWNLOAD_REQ_MSG_V01_MAX_LEN 6182 -#define QMI_WLANFW_BDF_DOWNLOAD_RESP_MSG_V01_MAX_LEN 7 -#define QMI_WLANFW_BDF_DOWNLOAD_RESP_V01 0x0025 #define QMI_WLANFW_BDF_DOWNLOAD_REQ_V01 0x0025 /* TODO: Need to check with MCL and FW team that data can be pointer and * can be last element in structure @@ -529,8 +512,6 @@ struct qmi_wlanfw_bdf_download_resp_msg_v01 { }; #define QMI_WLANFW_M3_INFO_REQ_MSG_V01_MAX_MSG_LEN 18 -#define QMI_WLANFW_M3_INFO_RESP_MSG_V01_MAX_MSG_LEN 7 -#define QMI_WLANFW_M3_INFO_RESP_V01 0x003C #define QMI_WLANFW_M3_INFO_REQ_V01 0x003C struct qmi_wlanfw_m3_info_req_msg_v01 { @@ -543,7 +524,6 @@ struct qmi_wlanfw_m3_info_resp_msg_v01 { }; #define QMI_WLANFW_AUX_UC_INFO_REQ_MSG_V01_MAX_MSG_LEN 18 -#define QMI_WLANFW_AUX_UC_INFO_RESP_MSG_V01_MAX_MSG_LEN 7 #define QMI_WLANFW_AUX_UC_INFO_REQ_V01 0x005A struct qmi_wlanfw_aux_uc_info_req_msg_v01 { @@ -556,13 +536,9 @@ struct qmi_wlanfw_aux_uc_info_resp_msg_v01 { }; #define QMI_WLANFW_WLAN_MODE_REQ_MSG_V01_MAX_LEN 11 -#define QMI_WLANFW_WLAN_MODE_RESP_MSG_V01_MAX_LEN 7 #define QMI_WLANFW_WLAN_CFG_REQ_MSG_V01_MAX_LEN 803 -#define QMI_WLANFW_WLAN_CFG_RESP_MSG_V01_MAX_LEN 7 #define QMI_WLANFW_WLAN_MODE_REQ_V01 0x0022 -#define QMI_WLANFW_WLAN_MODE_RESP_V01 0x0022 #define QMI_WLANFW_WLAN_CFG_REQ_V01 0x0023 -#define QMI_WLANFW_WLAN_CFG_RESP_V01 0x0023 #define QMI_WLANFW_MAX_STR_LEN_V01 16 #define QMI_WLANFW_MAX_NUM_CE_V01 12 #define QMI_WLANFW_MAX_NUM_SVC_V01 24 @@ -605,9 +581,7 @@ struct qmi_wlanfw_wlan_cfg_resp_msg_v01 { }; #define ATH12K_QMI_WLANFW_WLAN_INI_REQ_V01 0x002F -#define ATH12K_QMI_WLANFW_WLAN_INI_RESP_V01 0x002F #define QMI_WLANFW_WLAN_INI_REQ_MSG_V01_MAX_LEN 7 -#define QMI_WLANFW_WLAN_INI_RESP_MSG_V01_MAX_LEN 7 struct qmi_wlanfw_wlan_ini_req_msg_v01 { /* Must be set to true if enable_fwlog is being passed */ From eaf478b3ea68e1b5df659acd24c4df5850e12325 Mon Sep 17 00:00:00 2001 From: Nicolas Escande Date: Tue, 23 Jun 2026 17:16:13 +0200 Subject: [PATCH 06/58] wifi: ath12k: avoid setting 320MHz support on non 6GHz band On a split phy qcn9274 (2.4GHz + 5GHz low), "iw phy" reports 320MHz related features on the 5GHz band while it should not: Wiphy phy1 [...] Band 2: [...] EHT Iftypes: managed [...] EHT PHY Capabilities: (0xe2ffdbe018778000): 320MHz in 6GHz Supported [...] Beamformee SS (320MHz): 7 [...] Number Of Sounding Dimensions (320MHz): 3 [...] EHT MCS/NSS: (0x22222222222222222200000000): This is also reflected in the beacons sent by a mesh interface started on that band. They erroneously advertise 320MHz support too. This should not happen as IEEE Std 802.11-2024, subclause 9.4.2.323.3 says we should not set the 320MHz related fields when not operating on a 6GHz band. For example it says about Bit 0 "Support For 320 MHz In 6 GHz" "Reserved if the EHT Capabilities element is indicating capabilities for the 2.4 GHz or 5 GHz bands." Fix this by clearing the related bits when converting from WMI eht phy capabilities to mac80211 phy capabilities, for bands other than 6GHz. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00218-QCAHKSWPL_SILICONZ-1 Signed-off-by: Nicolas Escande Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260623151613.72113-1-nico.escande@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 84a31b953db8..e7689ee3e701 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -5154,6 +5154,7 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band, __le32 cap_info_internal) { struct ath12k_band_cap *cap_band = &pdev->cap.band[band]; + u8 *phy_cap = (u8 *)&cap_band->eht_cap_phy_info[0]; u32 support_320mhz; u8 i; @@ -5167,8 +5168,22 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band, for (i = 0; i < WMI_MAX_EHTCAP_PHY_SIZE; i++) cap_band->eht_cap_phy_info[i] = le32_to_cpu(cap_phy_info[i]); - if (band == NL80211_BAND_6GHZ) + if (band == NL80211_BAND_6GHZ) { cap_band->eht_cap_phy_info[0] |= support_320mhz; + } else { + /* + * Firmware may report 6 GHz/320 MHz specific capabilities for + * non-6 GHz bands, so explicitly clear them. + */ + phy_cap[0] &= ~IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ; + phy_cap[1] &= ~IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK; + phy_cap[2] &= ~IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_320MHZ_MASK; + phy_cap[3] &= ~IEEE80211_EHT_PHY_CAP3_SOUNDING_DIM_320MHZ_MASK; + phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_320MHZ; + phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_EHT_DUP_6GHZ_SUPP; + phy_cap[7] &= ~IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ; + phy_cap[7] &= ~IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_320MHZ; + } cap_band->eht_mcs_20_only = le32_to_cpu(supp_mcs[0]); cap_band->eht_mcs_80 = le32_to_cpu(supp_mcs[1]); From d762bbc08ca70a1985c9f9420c4bf67e0ba0e9be Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 9 May 2026 10:58:15 +0800 Subject: [PATCH 07/58] wifi: ath12k: fix TLV32 length mask HAL_TLV_HDR_LEN was using the wrong bitmask; fix it to cover bits [21:10]. Also drop HAL_SRNG_TLV_HDR_{TAG,LEN} and use the generic TLV header bit definitions for TLV32/TLV64 encode/decode to avoid redundant macros. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260509025819.1641630-2-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/hal.c | 8 ++++---- drivers/net/wireless/ath/ath12k/hal.h | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c index a164563fff28..f03817b2fbc5 100644 --- a/drivers/net/wireless/ath/ath12k/hal.c +++ b/drivers/net/wireless/ath/ath12k/hal.c @@ -828,8 +828,8 @@ void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len) { struct hal_tlv_64_hdr *tlv64 = tlv; - tlv64->tl = le64_encode_bits(tag, HAL_TLV_HDR_TAG) | - le64_encode_bits(len, HAL_TLV_HDR_LEN); + tlv64->tl = le64_encode_bits(tag, HAL_TLV_64_HDR_TAG) | + le64_encode_bits(len, HAL_TLV_64_HDR_LEN); return tlv64->value; } @@ -851,7 +851,7 @@ u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc) struct hal_tlv_64_hdr *tlv64 = tlv; u16 tag; - tag = le64_get_bits(tlv64->tl, HAL_SRNG_TLV_HDR_TAG); + tag = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_TAG); *desc = tlv64->value; return tag; @@ -863,7 +863,7 @@ u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc) struct hal_tlv_hdr *tlv32 = tlv; u16 tag; - tag = le32_get_bits(tlv32->tl, HAL_SRNG_TLV_HDR_TAG); + tag = le32_get_bits(tlv32->tl, HAL_TLV_HDR_TAG); *desc = tlv32->value; return tag; diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index 21c551d8b248..3ee49d93e24a 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1444,7 +1444,7 @@ struct hal_ops { }; #define HAL_TLV_HDR_TAG GENMASK(9, 1) -#define HAL_TLV_HDR_LEN GENMASK(25, 10) +#define HAL_TLV_HDR_LEN GENMASK(21, 10) #define HAL_TLV_USR_ID GENMASK(31, 26) #define HAL_TLV_ALIGN 4 @@ -1464,9 +1464,6 @@ struct hal_tlv_64_hdr { u8 value[]; } __packed; -#define HAL_SRNG_TLV_HDR_TAG GENMASK(9, 1) -#define HAL_SRNG_TLV_HDR_LEN GENMASK(25, 10) - dma_addr_t ath12k_hal_srng_get_tp_addr(struct ath12k_base *ab, struct hal_srng *srng); dma_addr_t ath12k_hal_srng_get_hp_addr(struct ath12k_base *ab, From e264a3dbe866870ed21ef83c67ad956a45391859 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 9 May 2026 10:58:16 +0800 Subject: [PATCH 08/58] wifi: ath12k: refactor HAL TLV32/64 decode helpers Change TLV decode helpers to return the TLV value pointer and optionally decode tag/len/usrid via out parameters. This allows reusing the helpers for DP monitor RX status header TLV parsing and avoids duplicated header decoding in callers. No functional change intended. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_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: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260509025819.1641630-3-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/hal.c | 26 ++++++++++++------- drivers/net/wireless/ath/ath12k/hal.h | 4 +-- .../wireless/ath/ath12k/wifi7/hal_qcc2072.c | 2 +- .../wireless/ath/ath12k/wifi7/hal_qcn9274.c | 11 +++++++- .../wireless/ath/ath12k/wifi7/hal_wcn7850.c | 11 +++++++- 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c index f03817b2fbc5..d940f83cd92f 100644 --- a/drivers/net/wireless/ath/ath12k/hal.c +++ b/drivers/net/wireless/ath/ath12k/hal.c @@ -846,26 +846,32 @@ void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len) } EXPORT_SYMBOL(ath12k_hal_encode_tlv32_hdr); -u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc) +void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid) { struct hal_tlv_64_hdr *tlv64 = tlv; - u16 tag; - tag = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_TAG); - *desc = tlv64->value; + if (tag) + *tag = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_TAG); + if (len) + *len = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_LEN); + if (usrid) + *usrid = le64_get_bits(tlv64->tl, HAL_TLV_64_USR_ID); - return tag; + return tlv64->value; } EXPORT_SYMBOL(ath12k_hal_decode_tlv64_hdr); -u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc) +void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid) { struct hal_tlv_hdr *tlv32 = tlv; - u16 tag; - tag = le32_get_bits(tlv32->tl, HAL_TLV_HDR_TAG); - *desc = tlv32->value; + if (tag) + *tag = le32_get_bits(tlv32->tl, HAL_TLV_HDR_TAG); + if (len) + *len = le32_get_bits(tlv32->tl, HAL_TLV_HDR_LEN); + if (usrid) + *usrid = le32_get_bits(tlv32->tl, HAL_TLV_USR_ID); - return tag; + return tlv32->value; } EXPORT_SYMBOL(ath12k_hal_decode_tlv32_hdr); diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index 3ee49d93e24a..d141c516c97f 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1553,6 +1553,6 @@ void ath12k_hal_rx_reo_ent_buf_paddr_get(struct ath12k_hal *hal, void *rx_desc, u8 *rbm, u32 *msdu_cnt); void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len); void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len); -u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc); -u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc); +void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); +void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); #endif diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c index 8cebb229ebed..6383d6965625 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c @@ -455,7 +455,7 @@ static u16 ath12k_hal_reo_status_dec_tlv_hdr_qcc2072(void *tlv, void **desc) struct hal_reo_get_queue_stats_status_qcc2072 *status_tlv; u16 tag; - tag = ath12k_hal_decode_tlv32_hdr(tlv, (void **)&status_tlv); + status_tlv = ath12k_hal_decode_tlv32_hdr(tlv, &tag, NULL, NULL); /* * actual desc of REO status entry starts after tlv32_padding, * see hal_reo_get_queue_stats_status_qcc2072 diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c index 9d5180ef83b4..156a861e021d 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c @@ -950,6 +950,15 @@ void ath12k_hal_extract_rx_desc_data_qcn9274(struct hal_rx_desc_data *rx_desc_da rx_desc_data->err_bitmap = ath12k_hal_rx_h_mpdu_err_qcn9274(rx_desc); } +static u16 ath12k_hal_reo_status_dec_tlv_hdr_qcn9274(void *tlv, void **desc) +{ + u16 tag; + + *desc = ath12k_hal_decode_tlv64_hdr(tlv, &tag, NULL, NULL); + + return tag; +} + const struct ath12k_hw_hal_params ath12k_hw_hal_params_qcn9274 = { .rx_buf_rbm = HAL_RX_BUF_RBM_SW3_BM, .wbm2sw_cc_enable = HAL_WBM_SW_COOKIE_CONV_CFG_WBM2SW0_EN | @@ -1138,5 +1147,5 @@ const struct hal_ops hal_qcn9274_ops = { .rx_msdu_list_get = ath12k_wifi7_hal_rx_msdu_list_get, .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, - .reo_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcn9274, }; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c index efbbc1cbd3e4..1ae4ff9b9448 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c @@ -756,6 +756,15 @@ int ath12k_hal_srng_create_config_wcn7850(struct ath12k_hal *hal) return 0; } +static u16 ath12k_hal_reo_status_dec_tlv_hdr_wcn7850(void *tlv, void **desc) +{ + u16 tag; + + *desc = ath12k_hal_decode_tlv64_hdr(tlv, &tag, NULL, NULL); + + return tag; +} + const struct ath12k_hal_tcl_to_wbm_rbm_map ath12k_hal_tcl_to_wbm_rbm_map_wcn7850[DP_TCL_NUM_RING_MAX] = { { @@ -821,5 +830,5 @@ const struct hal_ops hal_wcn7850_ops = { .rx_msdu_list_get = ath12k_wifi7_hal_rx_msdu_list_get, .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, - .reo_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_wcn7850, }; From 9d1d61121b05c0a854e6da227e37b99b3740dae9 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 9 May 2026 10:58:17 +0800 Subject: [PATCH 09/58] wifi: ath12k: add HAL ops for monitor TLV header decode and alignment Wi-Fi 7 monitor RX status TLV parsing needs to decode TLV headers and advance the pointer with the correct header alignment. Different targets use different TLV header layouts (32-bit vs 64-bit), but the HAL ops for dp_mon RX status header decode and header alignment were not populated for all wifi7 targets. Add dp_mon RX status TLV header decode callbacks and TLV header alignment helpers to the wifi7 HAL ops for QCC2072, QCN9274 and WCN7850. Export helpers to query the required TLV header alignment for 32-bit and 64-bit TLV headers so the caller can align the TLV walk correctly across targets. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_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: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260509025819.1641630-4-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/hal.c | 12 ++++++++++++ drivers/net/wireless/ath/ath12k/hal.h | 4 ++++ drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c | 2 ++ drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c | 2 ++ drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c | 2 ++ 5 files changed, 22 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c index d940f83cd92f..c0c3d2f047ef 100644 --- a/drivers/net/wireless/ath/ath12k/hal.c +++ b/drivers/net/wireless/ath/ath12k/hal.c @@ -875,3 +875,15 @@ void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid) return tlv32->value; } EXPORT_SYMBOL(ath12k_hal_decode_tlv32_hdr); + +u32 ath12k_hal_get_tlv64_hdr_align(void) +{ + return HAL_TLV_64_ALIGN; +} +EXPORT_SYMBOL(ath12k_hal_get_tlv64_hdr_align); + +u32 ath12k_hal_get_tlv32_hdr_align(void) +{ + return HAL_TLV_ALIGN; +} +EXPORT_SYMBOL(ath12k_hal_get_tlv32_hdr_align); diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index d141c516c97f..ba2c22fb2982 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1441,6 +1441,8 @@ struct hal_ops { u8 *rbm, u32 *msdu_cnt); void *(*reo_cmd_enc_tlv_hdr)(void *tlv, u64 tag, u64 len); u16 (*reo_status_dec_tlv_hdr)(void *tlv, void **desc); + void *(*mon_rx_status_dec_tlv_hdr)(void *tlv, u16 *tag, u16 *len, u16 *usrid); + u32 (*get_tlv_hdr_align)(void); }; #define HAL_TLV_HDR_TAG GENMASK(9, 1) @@ -1555,4 +1557,6 @@ void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len); void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len); void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid); +u32 ath12k_hal_get_tlv64_hdr_align(void); +u32 ath12k_hal_get_tlv32_hdr_align(void); #endif diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c index 6383d6965625..7cf00fda996f 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcc2072.c @@ -506,6 +506,8 @@ const struct hal_ops hal_qcc2072_ops = { .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv32_hdr, .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcc2072, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv32_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv32_hdr_align, }; u32 ath12k_hal_rx_desc_get_mpdu_start_offset_qcc2072(void) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c index 156a861e021d..052b59265af8 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c @@ -1148,4 +1148,6 @@ const struct hal_ops hal_qcn9274_ops = { .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcn9274, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv64_hdr_align, }; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c index 1ae4ff9b9448..61be8443e46e 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_wcn7850.c @@ -831,4 +831,6 @@ const struct hal_ops hal_wcn7850_ops = { .rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get, .reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr, .reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_wcn7850, + .mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr, + .get_tlv_hdr_align = ath12k_hal_get_tlv64_hdr_align, }; From 4c09bbf0c1e11bae19a0643bd9824d4f05d9c281 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 9 May 2026 10:58:18 +0800 Subject: [PATCH 10/58] wifi: ath12k: add dp_mon support 32-bit TLV headers Wi-Fi 7 monitor status parsing in dp_mon currently assumes a 64-bit TLV header and directly decodes tag/len/userid from struct hal_tlv_64_hdr. On chips using a 32-bit TLV header (e.g. QCC2072), this causes monitor RX status packets to be dropped during TLV parsing. Introduce HAL helpers to decode TLV header fields (tag/len/userid/value) for both 32-bit and 64-bit header layouts. Without changing the actual TLV parsing logic. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_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: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260509025819.1641630-5-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- .../net/wireless/ath/ath12k/wifi7/dp_mon.c | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c index 7dd4a49d64d5..06ca96c3cc7e 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c @@ -1565,16 +1565,17 @@ ath12k_wifi7_dp_mon_parse_status_msdu_end(struct ath12k_mon_data *pmon, static enum hal_rx_mon_status ath12k_wifi7_dp_mon_rx_parse_status_tlv(struct ath12k_pdev_dp *dp_pdev, struct ath12k_mon_data *pmon, - const struct hal_tlv_64_hdr *tlv) + const void *tlv) { struct hal_rx_mon_ppdu_info *ppdu_info = &pmon->mon_ppdu_info; - const void *tlv_data = tlv->value; - u32 info[7], userid; - u16 tlv_tag, tlv_len; + struct ath12k *ar = ath12k_pdev_dp_to_ar(dp_pdev); + struct ath12k_hal *hal = &ar->ab->hal; + u16 tlv_tag, tlv_len, userid; + void *tlv_data; + u32 info[7]; - tlv_tag = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_TAG); - tlv_len = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_LEN); - userid = le64_get_bits(tlv->tl, HAL_TLV_64_USR_ID); + tlv_data = hal->ops->mon_rx_status_dec_tlv_hdr((void *)tlv, &tlv_tag, + &tlv_len, &userid); if (ppdu_info->tlv_aggr.in_progress && ppdu_info->tlv_aggr.tlv_tag != tlv_tag) { ath12k_wifi7_dp_mon_parse_eht_sig_hdr(ppdu_info, @@ -2930,11 +2931,12 @@ static enum dp_mon_status_buf_state ath12k_wifi7_dp_rx_mon_buf_done(struct ath12k_base *ab, struct hal_srng *srng, struct dp_rxdma_mon_ring *rx_ring) { + struct ath12k_hal *hal = &ab->hal; struct ath12k_skb_rxcb *rxcb; - struct hal_tlv_64_hdr *tlv; struct sk_buff *skb; void *status_desc; dma_addr_t paddr; + u16 tlv_tag; u32 cookie; int buf_id; u8 rbm; @@ -2959,8 +2961,8 @@ ath12k_wifi7_dp_rx_mon_buf_done(struct ath12k_base *ab, struct hal_srng *srng, skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); - tlv = (struct hal_tlv_64_hdr *)skb->data; - if (le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG) != HAL_RX_STATUS_BUFFER_DONE) + hal->ops->mon_rx_status_dec_tlv_hdr(skb->data, &tlv_tag, NULL, NULL); + if (tlv_tag != HAL_RX_STATUS_BUFFER_DONE) return DP_MON_STATUS_NO_DMA; return DP_MON_STATUS_REPLINISH; @@ -2972,39 +2974,38 @@ ath12k_wifi7_dp_mon_parse_rx_dest(struct ath12k_pdev_dp *dp_pdev, struct sk_buff *skb) { struct ath12k *ar = ath12k_pdev_dp_to_ar(dp_pdev); - struct hal_tlv_64_hdr *tlv; + struct ath12k_hal *hal = &ar->ab->hal; + u8 *tlv_value, *tlv = skb->data; struct ath12k_skb_rxcb *rxcb; enum hal_rx_mon_status hal_status; u16 tlv_tag, tlv_len; - u8 *ptr = skb->data; + u32 tlv_hdr_len; + + tlv_hdr_len = hal->ops->get_tlv_hdr_align(); do { - tlv = (struct hal_tlv_64_hdr *)ptr; - tlv_tag = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_TAG); + tlv_value = hal->ops->mon_rx_status_dec_tlv_hdr(tlv, &tlv_tag, + &tlv_len, NULL); /* The actual length of PPDU_END is the combined length of many PHY * TLVs that follow. Skip the TLV header and * rx_rxpcu_classification_overview that follows the header to get to * next TLV. */ - if (tlv_tag == HAL_RX_PPDU_END) tlv_len = sizeof(struct hal_rx_rxpcu_classification_overview); - else - tlv_len = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_LEN); hal_status = ath12k_wifi7_dp_mon_rx_parse_status_tlv(dp_pdev, pmon, tlv); if (ar->monitor_started && ar->ab->hw_params->rxdma1_enable && ath12k_wifi7_dp_mon_parse_rx_dest_tlv(dp_pdev, pmon, hal_status, - tlv->value)) + tlv_value)) return HAL_RX_MON_STATUS_PPDU_DONE; - ptr += sizeof(*tlv) + tlv_len; - ptr = PTR_ALIGN(ptr, HAL_TLV_64_ALIGN); + tlv = PTR_ALIGN(tlv + tlv_len + tlv_hdr_len, tlv_hdr_len); - if ((ptr - skb->data) > skb->len) + if ((tlv - skb->data) > skb->len) break; } while ((hal_status == HAL_RX_MON_STATUS_PPDU_NOT_DONE) || @@ -3056,15 +3057,16 @@ ath12k_wifi7_dp_rx_reap_mon_status_ring(struct ath12k_base *ab, int mac_id, int buf_id, srng_id, num_buffs_reaped = 0; enum dp_mon_status_buf_state reap_status; struct dp_rxdma_mon_ring *rx_ring; + struct ath12k_hal *hal = &ab->hal; struct ath12k_mon_data *pmon; struct ath12k_skb_rxcb *rxcb; - struct hal_tlv_64_hdr *tlv; void *rx_mon_status_desc; struct hal_srng *srng; struct ath12k_dp *dp; struct sk_buff *skb; struct ath12k *ar; dma_addr_t paddr; + u16 tlv_tag; u32 cookie; u8 rbm; @@ -3109,14 +3111,13 @@ ath12k_wifi7_dp_rx_reap_mon_status_ring(struct ath12k_base *ab, int mac_id, skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); - tlv = (struct hal_tlv_64_hdr *)skb->data; - if (le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG) != - HAL_RX_STATUS_BUFFER_DONE) { + hal->ops->mon_rx_status_dec_tlv_hdr(skb->data, &tlv_tag, + NULL, NULL); + if (tlv_tag != HAL_RX_STATUS_BUFFER_DONE) { pmon->buf_state = DP_MON_STATUS_NO_DMA; ath12k_warn(ab, - "mon status DONE not set %llx, buf_id %d\n", - le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG), - buf_id); + "mon status DONE not set %x, buf_id %d\n", + tlv_tag, buf_id); /* RxDMA status done bit might not be set even * though tp is moved by HW. */ From fffa54aeaeb2e9ac923254b39e89bf07799615aa Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 9 May 2026 10:58:19 +0800 Subject: [PATCH 11/58] wifi: ath12k: tighten RX monitor TLV bounds check Validate the pointer to the next RX monitor TLV more strictly by ensuring that at least a full TLV header is available within the status buffer before continuing TLV parsing. Prevent potential out-of-bounds access when handling malformed or truncated RX monitor status data. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_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: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260509025819.1641630-6-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c index 06ca96c3cc7e..c84c42a3d377 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c @@ -3005,9 +3005,9 @@ ath12k_wifi7_dp_mon_parse_rx_dest(struct ath12k_pdev_dp *dp_pdev, tlv = PTR_ALIGN(tlv + tlv_len + tlv_hdr_len, tlv_hdr_len); - if ((tlv - skb->data) > skb->len) + if (unlikely(tlv - skb->data > skb->len || + skb->len - (tlv - skb->data) < tlv_hdr_len)) break; - } while ((hal_status == HAL_RX_MON_STATUS_PPDU_NOT_DONE) || (hal_status == HAL_RX_MON_STATUS_BUF_ADDR) || (hal_status == HAL_RX_MON_STATUS_MPDU_START) || From c2d60ab8e3827de2cbf951491e5de339e4bb2eb9 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Thu, 4 Jun 2026 08:45:51 +0530 Subject: [PATCH 12/58] wifi: ath12k: expand UserPD ID mask to support up to 8 PDs Currently ATH12K_USERPD_ID_MASK uses GENMASK(9, 8), which defines a 2-bit field and limits supported UserPD IDs to values 0-3. Future IPQ5332 multi-PD platform variants support more than three UserPDs. Expand ATH12K_USERPD_ID_MASK to GENMASK(10, 8), increasing the field width to 3 bits and allowing UserPD IDs from 0-7. ATH12K_USERPD_ID_MASK is currently used only while constructing the ath12k AHB PAS ID, so this change does not affect existing platforms. Also remove the unused ATH12K_MAX_UPDS definition. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aaradhana Sahu Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260604031551.4178754-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ahb.c | 1 - drivers/net/wireless/ath/ath12k/ahb.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 30733a244454..4912172e106e 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -17,7 +17,6 @@ #include "hif.h" #define ATH12K_IRQ_CE0_OFFSET 4 -#define ATH12K_MAX_UPDS 1 #define ATH12K_UPD_IRQ_WRD_LEN 18 static struct ath12k_ahb_driver *ath12k_ahb_family_drivers[ATH12K_DEVICE_FAMILY_MAX]; diff --git a/drivers/net/wireless/ath/ath12k/ahb.h b/drivers/net/wireless/ath/ath12k/ahb.h index 0fa15daaa3e6..a153db6cf1d3 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.h +++ b/drivers/net/wireless/ath/ath12k/ahb.h @@ -27,7 +27,7 @@ #define ATH12K_USERPD_SPAWN_TIMEOUT (5 * HZ) #define ATH12K_USERPD_READY_TIMEOUT (10 * HZ) #define ATH12K_USERPD_STOP_TIMEOUT (5 * HZ) -#define ATH12K_USERPD_ID_MASK GENMASK(9, 8) +#define ATH12K_USERPD_ID_MASK GENMASK(10, 8) #define ATH12K_USERPD_FW_NAME_LEN 35 enum ath12k_ahb_smp2p_msg_id { From fe2b006c15f6b1f81524b4c1af8013bc32fa0abc Mon Sep 17 00:00:00 2001 From: Aishwarya R Date: Fri, 19 Jun 2026 17:37:51 +0530 Subject: [PATCH 13/58] wifi: ath12k: reset REOQ LUT addresses before firmware stop During module removal, REOQ LUT cleanup writes 0 to the REOQ/ML-REOQ LUT address registers. That cleanup runs from ath12k_core_stop(), after ath12k_qmi_firmware_stop() has already stopped the firmware (mode OFF), so the register writes can hit an invalid target access. Move the REOQ LUT register reset before ath12k_qmi_firmware_stop(), so the registers are cleared before stopping the firmware, while register access is still valid. Additionally, handle the error path where firmware-ready setup fails after LUT programming but before core_stop() is reached, ensuring the registers are properly reset in that case as well. On the crash-recovery path, ath12k_core_reconfigure_on_crash() calls ath12k_core_qmi_firmware_ready(), which re-enters ath12k_dp_setup() and ath12k_dp_reoq_lut_setup(), so the LUT registers are reprogrammed before use and stale values do not persist across recovery. There is a brief window between the crash and when the LUT registers are reprogrammed during recovery, during which the registers still hold the freed DMA memory addresses. This is safe because the device is non-functional in that window and will not initiate any DMA access until firmware is restarted and the registers are reprogrammed. No functional issue has been observed so far due to this sequence. However, this change proactively avoids potential issues such as invalid register accesses after firmware stop during module removal and error handling. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Co-developed-by: P Praneesh Signed-off-by: P Praneesh Signed-off-by: Aishwarya R Reviewed-by: Baochen Qiang Reviewed-by: Tamizh Chelvam Raja Link: https://patch.msgid.link/20260619120751.363340-1-aishwarya.r@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 5 ++++- drivers/net/wireless/ath/ath12k/dp.c | 14 ++++++++++++-- drivers/net/wireless/ath/ath12k/dp.h | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 742d4fd1b598..efe37dc91afd 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -708,8 +708,10 @@ static void ath12k_core_stop(struct ath12k_base *ab) ath12k_core_to_group_ref_put(ab); - if (!test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)) + if (!test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)) { + ath12k_dp_reoq_lut_addr_reset(ath12k_ab_to_dp(ab)); ath12k_qmi_firmware_stop(ab); + } ath12k_acpi_stop(ab); @@ -1371,6 +1373,7 @@ int ath12k_core_qmi_firmware_ready(struct ath12k_base *ab) goto exit; err_deinit: + ath12k_dp_reoq_lut_addr_reset(ath12k_ab_to_dp(ab)); ath12k_dp_cmn_device_deinit(ath12k_ab_to_dp(ab)); mutex_unlock(&ab->core_lock); mutex_unlock(&ag->mutex); diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c index af5f11fc1d84..fbc0788b37a0 100644 --- a/drivers/net/wireless/ath/ath12k/dp.c +++ b/drivers/net/wireless/ath/ath12k/dp.c @@ -1097,7 +1097,6 @@ static void ath12k_dp_reoq_lut_cleanup(struct ath12k_base *ab) return; if (dp->reoq_lut.vaddr_unaligned) { - ath12k_hal_write_reoq_lut_addr(ab, 0); dma_free_coherent(ab->dev, dp->reoq_lut.size, dp->reoq_lut.vaddr_unaligned, dp->reoq_lut.paddr_unaligned); @@ -1105,7 +1104,6 @@ static void ath12k_dp_reoq_lut_cleanup(struct ath12k_base *ab) } if (dp->ml_reoq_lut.vaddr_unaligned) { - ath12k_hal_write_ml_reoq_lut_addr(ab, 0); dma_free_coherent(ab->dev, dp->ml_reoq_lut.size, dp->ml_reoq_lut.vaddr_unaligned, dp->ml_reoq_lut.paddr_unaligned); @@ -1568,6 +1566,7 @@ static int ath12k_dp_setup(struct ath12k_base *ab) ath12k_dp_rx_free(ab); fail_cmn_reoq_cleanup: + ath12k_dp_reoq_lut_addr_reset(dp); ath12k_dp_reoq_lut_cleanup(ab); fail_cmn_srng_cleanup: @@ -1627,3 +1626,14 @@ void ath12k_dp_cmn_hw_group_assign(struct ath12k_dp *dp, dp->device_id = ab->device_id; dp_hw_grp->dp[dp->device_id] = dp; } + +void ath12k_dp_reoq_lut_addr_reset(struct ath12k_dp *dp) +{ + struct ath12k_base *ab = dp->ab; + + if (dp->reoq_lut.vaddr_unaligned) + ath12k_hal_write_reoq_lut_addr(ab, 0); + + if (dp->ml_reoq_lut.vaddr_unaligned) + ath12k_hal_write_ml_reoq_lut_addr(ab, 0); +} diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index f8cfc7bb29dd..9b39146e65e1 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -701,4 +701,5 @@ struct ath12k_rx_desc_info *ath12k_dp_get_rx_desc(struct ath12k_dp *dp, u32 cookie); struct ath12k_tx_desc_info *ath12k_dp_get_tx_desc(struct ath12k_dp *dp, u32 desc_id); +void ath12k_dp_reoq_lut_addr_reset(struct ath12k_dp *dp); #endif From 784f7dabf5d3bce23c69c48a0441ff2b1536f069 Mon Sep 17 00:00:00 2001 From: Manish Dharanenthiran Date: Tue, 23 Jun 2026 11:16:21 +0530 Subject: [PATCH 14/58] wifi: ath12k: advertise ieee_link_id in vdev start MLO params Firmware builds the AP MLD partner profile from the hw_link_id passed in the vdev start parameters. However, hw_link_id is not always the same as the logical per-MLD ieee_link_id, since ieee_link_id is assigned per MLD and not per pdev. This matters in mixed MLO and SLO setups. For example: MLD 1 - 5 GHz + 6 GHz (2-link MLO): ieee_link_id 0 and 1 MLD 2 - 6 GHz only (1-link SLO): ieee_link_id 0 MLD 3 - 5 GHz only (1-link SLO): ieee_link_id 0 The same physical 6 GHz radio can use ieee_link_id 1 for one MLD and ieee_link_id 0 for another. Pass the correct ieee_link_id to firmware so it can build accurate per-STA profile elements. Add ieee_link_id to wmi_vdev_start_mlo_params for the self link and to wmi_partner_link_info for each partner link. Populate these fields in ath12k_mac_mlo_get_vdev_args() from the corresponding vdev link_id before encoding the WMI command. Introduce two new flags in ML params to indicate to firmware when the new fields are valid: ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID BIT(18) for the self link ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER BIT(19) for partner links Firmware parses ieee_link_id only when the matching flag is set. Also fix the debug message by using correct format specifiers and host-endian values instead of __le32 values. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Co-developed-by: Hari Naraayana Desikan Kannan Signed-off-by: Hari Naraayana Desikan Kannan Co-developed-by: Karthik M Signed-off-by: Karthik M Signed-off-by: Manish Dharanenthiran Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260623-ieee_link_id-v2-1-8a89d71baf58@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 3 +++ drivers/net/wireless/ath/ath12k/wmi.c | 30 +++++++++++++++++---------- drivers/net/wireless/ath/ath12k/wmi.h | 7 +++++++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index af354bef5c0d..773ecd6da8e5 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -11253,6 +11253,8 @@ ath12k_mac_mlo_get_vdev_args(struct ath12k_link_vif *arvif, ml_arg->assoc_link = arvif->is_sta_assoc_link; + ml_arg->ieee_link_id = arvif->link_id; + partner_info = ml_arg->partner_info; links = ahvif->links_map; @@ -11276,6 +11278,7 @@ ath12k_mac_mlo_get_vdev_args(struct ath12k_link_vif *arvif, partner_info->vdev_id = arvif_p->vdev_id; partner_info->hw_link_id = arvif_p->ar->pdev->hw_link_id; + partner_info->ieee_link_id = arvif_p->link_id; ether_addr_copy(partner_info->addr, link_conf->addr); ml_arg->num_partner_links++; partner_info++; diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index e7689ee3e701..ad739bffcf88 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -1228,10 +1228,14 @@ int ath12k_wmi_vdev_start(struct ath12k *ar, struct wmi_vdev_start_req_arg *arg, le32_encode_bits(arg->ml.mcast_link, ATH12K_WMI_FLAG_MLO_MCAST_VDEV) | le32_encode_bits(arg->ml.link_add, - ATH12K_WMI_FLAG_MLO_LINK_ADD); + ATH12K_WMI_FLAG_MLO_LINK_ADD) | + cpu_to_le32(ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID); - ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "vdev %d start ml flags 0x%x\n", - arg->vdev_id, ml_params->flags); + ml_params->ieee_link_id = cpu_to_le32(arg->ml.ieee_link_id); + + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "vdev %u start link_id %u ml flags 0x%x\n", + arg->vdev_id, arg->ml.ieee_link_id, + le32_to_cpu(ml_params->flags)); ptr += sizeof(*ml_params); @@ -1244,19 +1248,23 @@ int ath12k_wmi_vdev_start(struct ath12k *ar, struct wmi_vdev_start_req_arg *arg, partner_info = ptr; for (i = 0; i < arg->ml.num_partner_links; i++) { + struct wmi_ml_partner_info *pinfo = &arg->ml.partner_info[i]; + partner_info->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_MLO_PARTNER_LINK_PARAMS, sizeof(*partner_info)); - partner_info->vdev_id = - cpu_to_le32(arg->ml.partner_info[i].vdev_id); - partner_info->hw_link_id = - cpu_to_le32(arg->ml.partner_info[i].hw_link_id); + partner_info->vdev_id = cpu_to_le32(pinfo->vdev_id); + partner_info->hw_link_id = cpu_to_le32(pinfo->hw_link_id); ether_addr_copy(partner_info->vdev_addr.addr, - arg->ml.partner_info[i].addr); + pinfo->addr); + partner_info->flags = + cpu_to_le32(ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER); + partner_info->ieee_link_id = cpu_to_le32(pinfo->ieee_link_id); - ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "partner vdev %d hw_link_id %d macaddr%pM\n", - partner_info->vdev_id, partner_info->hw_link_id, - partner_info->vdev_addr.addr); + ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "partner vdev %u hw_link_id %u macaddr %pM link_id %u ml flags 0x%x\n", + pinfo->vdev_id, pinfo->hw_link_id, + pinfo->addr, pinfo->ieee_link_id, + le32_to_cpu(partner_info->flags)); partner_info++; } diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index c452e3d57a29..51f3426e1fcd 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2954,10 +2954,13 @@ struct wmi_vdev_create_mlo_params { #define ATH12K_WMI_FLAG_MLO_EMLSR_SUPPORT BIT(6) #define ATH12K_WMI_FLAG_MLO_FORCED_INACTIVE BIT(7) #define ATH12K_WMI_FLAG_MLO_LINK_ADD BIT(8) +#define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID BIT(18) +#define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER BIT(19) struct wmi_vdev_start_mlo_params { __le32 tlv_header; __le32 flags; + __le32 ieee_link_id; } __packed; struct wmi_partner_link_info { @@ -2965,6 +2968,8 @@ struct wmi_partner_link_info { __le32 vdev_id; __le32 hw_link_id; struct ath12k_wmi_mac_addr_params vdev_addr; + __le32 flags; + __le32 ieee_link_id; } __packed; struct wmi_vdev_delete_cmd { @@ -3120,6 +3125,7 @@ struct wmi_ml_partner_info { bool primary_umac; bool logical_link_idx_valid; u32 logical_link_idx; + u32 ieee_link_id; }; struct wmi_ml_arg { @@ -3127,6 +3133,7 @@ struct wmi_ml_arg { bool assoc_link; bool mcast_link; bool link_add; + u32 ieee_link_id; u8 num_partner_links; struct wmi_ml_partner_info partner_info[ATH12K_WMI_MLO_MAX_LINKS]; }; From e47d6c9bb4165721f61356f5fccae8f7dd78876b Mon Sep 17 00:00:00 2001 From: Tamizh Chelvam Raja Date: Tue, 23 Jun 2026 15:35:01 +0530 Subject: [PATCH 15/58] wifi: ath12k: Advertise multicast Ethernet encapsulation offload support Advertise IEEE80211_OFFLOAD_ENCAP_MCAST to inform mac80211 that multicast frame encapsulation is handled in hardware. This allows mac80211 to pass Ethernet-formatted multicast frames directly to the driver. In ath12k_wifi7_mac_op_tx(), refine the logic that selects the MLO multicast replication path. Add a sta pointer check so that only unicast Hardware-encap frames use the direct transmit path, while multicast Hardware-encap frames fall through to the MLO replication loop and are transmitted on each active link. In the MLO replication loop, use skb_clone() for Hardware-encap frames. These frames are already in Ethernet format and do not require 802.11 link address rewriting by ath12k_mlo_mcast_update_tx_link_address(). Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Tamizh Chelvam Raja Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260623100501.2100119-1-tamizh.raja@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 6 ++- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 61 +++++++++++++++++----- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 773ecd6da8e5..16339469c24c 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -10117,7 +10117,8 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif) if (vif->type != NL80211_IFTYPE_STATION && vif->type != NL80211_IFTYPE_AP) vif->offload_flags &= ~(IEEE80211_OFFLOAD_ENCAP_ENABLED | - IEEE80211_OFFLOAD_DECAP_ENABLED); + IEEE80211_OFFLOAD_DECAP_ENABLED | + IEEE80211_OFFLOAD_ENCAP_MCAST); if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) { ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_ETHERNET; @@ -10136,6 +10137,9 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif) vif->offload_flags &= ~IEEE80211_OFFLOAD_ENCAP_ENABLED; } + if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) + vif->offload_flags |= IEEE80211_OFFLOAD_ENCAP_MCAST; + param_id = WMI_VDEV_PARAM_RX_DECAP_TYPE; if (vif->offload_flags & IEEE80211_OFFLOAD_DECAP_ENABLED) param_value = ATH12K_HW_TXRX_ETHERNET; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index 3d59fa452ec0..e5bf9d218104 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -903,6 +903,7 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, struct ethhdr *eth; bool is_prb_rsp; u16 mcbc_gsn; + u8 cb_flags; u8 link_id; int ret; struct ath12k_dp *tmp_dp; @@ -996,8 +997,13 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, ieee80211_has_protected(hdr->frame_control)) is_dvlan = true; + /* + * Add a sta pointer check to differentiate multicast encapsulation + * offload packets, as the ATH12K_SKB_HW_80211_ENCAP flag is also set + * for such packets. + */ if (!vif->valid_links || !is_mcast || is_dvlan || - (skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) || + ((skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) && sta) || test_bit(ATH12K_FLAG_RAW_MODE, &ar->ab->dev_flags)) { ret = ath12k_wifi7_dp_tx(dp_pdev, arvif, arsta, skb, false, 0, is_mcast); if (unlikely(ret)) { @@ -1009,6 +1015,7 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, mcbc_gsn = atomic_inc_return(&ahvif->dp_vif.mcbc_gsn) & 0xfff; links_map = ahvif->links_map; + cb_flags = skb_cb->flags; for_each_set_bit(link_id, &links_map, IEEE80211_MLD_MAX_NUM_LINKS) { tmp_arvif = rcu_dereference(ahvif->link[link_id]); @@ -1016,21 +1023,45 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, continue; tmp_ar = tmp_arvif->ar; - tmp_dp_pdev = ath12k_dp_to_pdev_dp(tmp_ar->ab->dp, + tmp_dp = ath12k_ab_to_dp(tmp_ar->ab); + tmp_dp_pdev = ath12k_dp_to_pdev_dp(tmp_dp, tmp_ar->pdev_idx); if (!tmp_dp_pdev) continue; - msdu_copied = skb_copy(skb, GFP_ATOMIC); - if (!msdu_copied) { - ath12k_err(ar->ab, - "skb copy failure link_id 0x%X vdevid 0x%X\n", - link_id, tmp_arvif->vdev_id); - continue; - } - ath12k_mlo_mcast_update_tx_link_address(vif, link_id, - msdu_copied, - info_flags); + if (cb_flags & ATH12K_SKB_HW_80211_ENCAP) { + /* + * skb->data may be modified for the iova_mask devices. + * It is better to use skb_copy() for such devices + * to avoid any potential skb corruption related issues. + */ + if (tmp_dp->hw_params->iova_mask) + msdu_copied = skb_copy(skb, GFP_ATOMIC); + else + /* + * ath12k_wifi7_dp_tx() should treat cloned HW-encap + * Ethernet multicast frames as read-only. + */ + msdu_copied = skb_clone(skb, GFP_ATOMIC); + if (!msdu_copied) { + ath12k_err(ar->ab, + "skb copy/clone failure link_id 0x%X vdevid 0x%X\n", + link_id, tmp_arvif->vdev_id); + continue; + } + } else { + msdu_copied = skb_copy(skb, GFP_ATOMIC); + if (!msdu_copied) { + ath12k_err(ar->ab, + "skb copy failure link_id 0x%X vdevid 0x%X\n", + link_id, tmp_arvif->vdev_id); + continue; + } + + ath12k_mlo_mcast_update_tx_link_address(vif, link_id, + msdu_copied, + info_flags); + } skb_cb = ATH12K_SKB_CB(msdu_copied); skb_cb->link_id = link_id; @@ -1046,7 +1077,6 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, if (unlikely(!ahvif->dp_vif.key_cipher)) goto skip_peer_find; - tmp_dp = ath12k_ab_to_dp(tmp_ar->ab); spin_lock_bh(&tmp_dp->dp_lock); peer = ath12k_dp_link_peer_find_by_addr(tmp_dp, tmp_arvif->bssid); @@ -1065,11 +1095,16 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, skb_cb->cipher = key->cipher; skb_cb->flags |= ATH12K_SKB_CIPHER_SET; + if (skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) + goto skip_fctl_protected_check; + hdr = (struct ieee80211_hdr *)msdu_copied->data; if (!ieee80211_has_protected(hdr->frame_control)) hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED); } + +skip_fctl_protected_check: spin_unlock_bh(&tmp_dp->dp_lock); skip_peer_find: From 34620d1890cc1fcf87a95219d6781d7f7b9d8cbd Mon Sep 17 00:00:00 2001 From: Sreeramya Soratkal Date: Fri, 26 Jun 2026 14:22:51 +0530 Subject: [PATCH 16/58] wifi: ath12k: Use runtime device count in dp stats display The REO Rx Received and Rx WBM REL SRC Errors display loops in ath12k_debugfs_dump_device_dp_stats() iterate up to the compile-time constant ATH12K_MAX_DEVICES. This unconditionally prints zeros in columns with no hardware behind it, making the output misleading. Replace the compile-time bound with the runtime ab->ag->num_devices so only live device slots appear in the output. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Sreeramya Soratkal Reviewed-by: Baochen Qiang Reviewed-by: Aishwarya R Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260626085253.3927269-2-sreeramya.soratkal@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/debugfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/debugfs.c b/drivers/net/wireless/ath/ath12k/debugfs.c index d17d4a8f1cb7..bab9d96d6acc 100644 --- a/drivers/net/wireless/ath/ath12k/debugfs.c +++ b/drivers/net/wireless/ath/ath12k/debugfs.c @@ -1173,7 +1173,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, for (i = 0; i < DP_REO_DST_RING_MAX; i++) { len += scnprintf(buf + len, size - len, "Ring%d:", i + 1); - for (j = 0; j < ATH12K_MAX_DEVICES; j++) { + for (j = 0; j < ab->ag->num_devices; j++) { len += scnprintf(buf + len, size - len, "\t%d:%u", j, device_stats->reo_rx[i][j]); @@ -1190,7 +1190,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, for (i = 0; i < HAL_WBM_REL_SRC_MODULE_MAX; i++) { len += scnprintf(buf + len, size - len, "%s:", wbm_rel_src[i]); - for (j = 0; j < ATH12K_MAX_DEVICES; j++) { + for (j = 0; j < ab->ag->num_devices; j++) { len += scnprintf(buf + len, size - len, "\t%d:%u", j, From 6cc84fce7b999b0c6c8aaccdfa8669f0a55e8586 Mon Sep 17 00:00:00 2001 From: Sreeramya Soratkal Date: Fri, 26 Jun 2026 14:22:52 +0530 Subject: [PATCH 17/58] wifi: ath12k: Add timestamp to dp stats display In MLO configurations the device_dp_stats debugfs file is read separately for each ath12k device. Without a timestamp it is impossible to know whether two snapshots were taken at the same moment, making counter comparisons across devices unreliable. Prepend a ktime-based millisecond timestamp to the output header so the reader can confirm when the snapshot was taken. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Sreeramya Soratkal Reviewed-by: Baochen Qiang Reviewed-by: Aishwarya R Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260626085253.3927269-3-sreeramya.soratkal@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/debugfs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/debugfs.c b/drivers/net/wireless/ath/ath12k/debugfs.c index bab9d96d6acc..57c213111259 100644 --- a/drivers/net/wireless/ath/ath12k/debugfs.c +++ b/drivers/net/wireless/ath/ath12k/debugfs.c @@ -1082,6 +1082,9 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, if (!buf) return -ENOMEM; + len += scnprintf(buf + len, size - len, + "DEVICE DP STATS (timestamp: %lldms):\n\n", + ktime_to_ms(ktime_get())); len += scnprintf(buf + len, size - len, "DEVICE RX STATS:\n\n"); len += scnprintf(buf + len, size - len, "err ring pkts: %u\n", device_stats->err_ring_pkts); From b1d8d626e206a757b745af2adcbc7127ec593a20 Mon Sep 17 00:00:00 2001 From: Sreeramya Soratkal Date: Fri, 26 Jun 2026 14:22:53 +0530 Subject: [PATCH 18/58] wifi: ath12k: Show per-radio center freq in dp stats Currently, the frequency on which each radio is operating is not available in device_dp_stats. This information is helpful in debugging the channel-specific throughput and is available with iw/nl80211 dump. Extend the device_dp_stats dump to display the center frequency in the existing per-radio loop. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Sreeramya Soratkal Reviewed-by: Baochen Qiang Reviewed-by: Aishwarya R Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260626085253.3927269-4-sreeramya.soratkal@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/debugfs.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/debugfs.c b/drivers/net/wireless/ath/ath12k/debugfs.c index 57c213111259..d54995b7adb2 100644 --- a/drivers/net/wireless/ath/ath12k/debugfs.c +++ b/drivers/net/wireless/ath/ath12k/debugfs.c @@ -1031,6 +1031,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, struct ath12k_device_dp_stats *device_stats = &dp->device_stats; int len = 0, i, j, ret; struct ath12k *ar; + u32 center_freq; const int size = 4096; static const char *rxdma_err[HAL_REO_ENTR_RING_RXDMA_ECODE_MAX] = { [HAL_REO_ENTR_RING_RXDMA_ECODE_OVERFLOW_ERR] = "Overflow", @@ -1164,6 +1165,12 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file, for (i = 0; i < ab->num_radios; i++) { ar = ath12k_mac_get_ar_by_pdev_id(ab, DP_SW2HW_MACID(i)); if (ar) { + spin_lock_bh(&ar->data_lock); + center_freq = ar->rx_channel ? ar->rx_channel->center_freq : 0; + spin_unlock_bh(&ar->data_lock); + len += scnprintf(buf + len, size - len, + "\nradio%d center_freq: %u\n", + i, center_freq); len += scnprintf(buf + len, size - len, "\nradio%d tx_pending: %u\n", i, atomic_read(&ar->dp.num_tx_pending)); From 5a2b5d6a5a4a19b86d1c0698a3eb3d21f0b06401 Mon Sep 17 00:00:00 2001 From: Sushant Butta Date: Tue, 9 Jun 2026 12:18:55 +0530 Subject: [PATCH 19/58] wifi: ath12k: Skip setting RX_FLAG_8023 for Ethernet-II (DIX) frames in monitor mode Monitor mode delivers raw 802.11 frames, not 802.3/Ethernet frames. Setting RX_FLAG_8023 for monitor RX is incorrect and can break userspace capture and analysis. Do not update this flag in the monitor path to ensure correct handling of captured frames. In the monitor path, RX_FLAG_ONLY_MONITOR is always set before decap is evaluated, which forces decap to remain DP_RX_DECAP_TYPE_RAW. As a result, the condition to set RX_FLAG_8023 can never be satisfied. Hence, drop this unreachable code. Also remove the unused hal_rx_mon_ppdu_info parameter from ath12k_dp_mon_rx_deliver_msdu(), as it was passed but never used. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Sushant Butta Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260609064856.547032-2-sushant.butta@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_mon.c | 16 +--------------- drivers/net/wireless/ath/ath12k/dp_mon.h | 4 +--- drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c | 7 +------ 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c index 44c5cff75f16..cfcfa93eeb44 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c @@ -493,9 +493,7 @@ EXPORT_SYMBOL(ath12k_dp_mon_update_radiotap); void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struct *napi, struct sk_buff *msdu, - const struct hal_rx_mon_ppdu_info *ppduinfo, - struct ieee80211_rx_status *status, - u8 decap) + struct ieee80211_rx_status *status) { struct ath12k_dp *dp = dp_pdev->dp; struct ath12k_base *ab = dp->ab; @@ -511,7 +509,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu); struct hal_rx_desc_data rx_info; bool is_mcbc = rxcb->is_mcbc; - bool is_eapol_tkip = rxcb->is_eapol; struct hal_rx_desc *rx_desc = (struct hal_rx_desc *)msdu->data; u8 addr[ETH_ALEN] = {}; @@ -570,17 +567,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, rx_status = IEEE80211_SKB_RXCB(msdu); *rx_status = *status; - /* 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_tkip && - !(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_mon_rx_deliver_msdu); diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.h b/drivers/net/wireless/ath/ath12k/dp_mon.h index 167028d27513..162cdcaa57a7 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.h +++ b/drivers/net/wireless/ath/ath12k/dp_mon.h @@ -112,9 +112,7 @@ void ath12k_dp_mon_update_radiotap(struct ath12k_pdev_dp *dp_pdev, void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struct *napi, struct sk_buff *msdu, - const struct hal_rx_mon_ppdu_info *ppduinfo, - struct ieee80211_rx_status *status, - u8 decap); + struct ieee80211_rx_status *status); struct sk_buff * ath12k_dp_mon_rx_merg_msdus(struct ath12k_pdev_dp *dp_pdev, struct dp_mon_mpdu *mon_mpdu, diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c index c84c42a3d377..016b0c38e51e 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c @@ -2481,7 +2481,6 @@ ath12k_wifi7_dp_mon_rx_deliver(struct ath12k_pdev_dp *dp_pdev, { struct sk_buff *mon_skb, *skb_next, *header; struct ieee80211_rx_status *rxs = &dp_pdev->rx_status; - u8 decap = DP_RX_DECAP_TYPE_RAW; mon_skb = ath12k_dp_mon_rx_merg_msdus(dp_pdev, mon_mpdu, ppduinfo, rxs); if (!mon_skb) @@ -2508,12 +2507,8 @@ ath12k_wifi7_dp_mon_rx_deliver(struct ath12k_pdev_dp *dp_pdev, } rxs->flag |= RX_FLAG_ONLY_MONITOR; - if (!(rxs->flag & RX_FLAG_ONLY_MONITOR)) - decap = mon_mpdu->decap_format; - ath12k_dp_mon_update_radiotap(dp_pdev, ppduinfo, mon_skb, rxs); - ath12k_dp_mon_rx_deliver_msdu(dp_pdev, napi, mon_skb, ppduinfo, - rxs, decap); + ath12k_dp_mon_rx_deliver_msdu(dp_pdev, napi, mon_skb, rxs); mon_skb = skb_next; } while (mon_skb); rxs->flag = 0; From 56f8f12c1a3c5312de0d7312b229d7bca03dbb81 Mon Sep 17 00:00:00 2001 From: Sushant Butta Date: Tue, 9 Jun 2026 12:18:56 +0530 Subject: [PATCH 20/58] wifi: ath12k: Skip peer link info update in rx_status for monitor MSDUs Do not populate peer and link_id in ieee80211_rx_status for monitor MSDUs. The monitor RX path is handled differently in mac80211 when RX_FLAG_ONLY_MONITOR is set, and does not consume peer/link metadata. As such, looking up the peer and updating link_id here is unnecessary. Additionally, this metadata is not required for monitor mode delivery, and performing the lookup/update introduces redundant work and the potential for inconsistent rx_status state if multiple paths modify it. Hence, remove the peer lookup and link_id update from the monitor MSDU delivery path. This also removes the per-MSDU debug logging in the monitor path, slightly reducing debuggability, but avoids unnecessary overhead in the monitor RX path. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Sushant Butta Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260609064856.547032-3-sushant.butta@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_mon.c | 54 +----------------------- 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c index cfcfa93eeb44..7d5be77b081f 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c @@ -495,8 +495,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct sk_buff *msdu, struct ieee80211_rx_status *status) { - struct ath12k_dp *dp = dp_pdev->dp; - struct ath12k_base *ab = dp->ab; static const struct ieee80211_radiotap_he known = { .data1 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN | IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN), @@ -504,13 +502,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, }; struct ieee80211_rx_status *rx_status; struct ieee80211_radiotap_he *he = NULL; - struct ieee80211_sta *pubsta = NULL; - struct ath12k_dp_link_peer *peer; - struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu); - struct hal_rx_desc_data rx_info; - bool is_mcbc = rxcb->is_mcbc; - struct hal_rx_desc *rx_desc = (struct hal_rx_desc *)msdu->data; - u8 addr[ETH_ALEN] = {}; status->link_valid = 0; @@ -521,53 +512,10 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, status->flag |= RX_FLAG_RADIOTAP_HE; } - ath12k_dp_extract_rx_desc_data(dp->hal, &rx_info, rx_desc, rx_desc); - - rcu_read_lock(); - spin_lock_bh(&dp->dp_lock); - peer = ath12k_dp_rx_h_find_link_peer(dp_pdev, msdu, &rx_info); - if (peer && peer->sta) { - pubsta = peer->sta; - memcpy(addr, peer->addr, ETH_ALEN); - if (pubsta->valid_links) { - status->link_valid = 1; - status->link_id = peer->link_id; - } - } - - spin_unlock_bh(&dp->dp_lock); - rcu_read_unlock(); - - ath12k_dbg(ab, ATH12K_DBG_DATA, - "rx skb %p len %u peer %pM %u %s %s%s%s%s%s%s%s%s %srate_idx %u vht_nss %u freq %u band %u flag 0x%x fcs-err %i mic-err %i amsdu-more %i\n", - msdu, - msdu->len, - addr, - rxcb->tid, - (is_mcbc) ? "mcast" : "ucast", - (status->encoding == RX_ENC_LEGACY) ? "legacy" : "", - (status->encoding == RX_ENC_HT) ? "ht" : "", - (status->encoding == RX_ENC_VHT) ? "vht" : "", - (status->encoding == RX_ENC_HE) ? "he" : "", - (status->bw == RATE_INFO_BW_40) ? "40" : "", - (status->bw == RATE_INFO_BW_80) ? "80" : "", - (status->bw == RATE_INFO_BW_160) ? "160" : "", - (status->bw == RATE_INFO_BW_320) ? "320" : "", - status->enc_flags & RX_ENC_FLAG_SHORT_GI ? "sgi " : "", - status->rate_idx, - status->nss, - status->freq, - status->band, status->flag, - !!(status->flag & RX_FLAG_FAILED_FCS_CRC), - !!(status->flag & RX_FLAG_MMIC_ERROR), - !!(status->flag & RX_FLAG_AMSDU_MORE)); - - ath12k_dbg_dump(ab, ATH12K_DBG_DP_RX, NULL, "dp rx msdu: ", - msdu->data, msdu->len); rx_status = IEEE80211_SKB_RXCB(msdu); *rx_status = *status; - ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi); + ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), NULL, msdu, napi); } EXPORT_SYMBOL(ath12k_dp_mon_rx_deliver_msdu); From 58aeb412495ada7fe5495c7805504d7cf1d45453 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Tue, 9 Jun 2026 20:13:58 -0700 Subject: [PATCH 21/58] wifi: ath12k: change MAC buffer ring size to 4096 For WCN7850, MAC buffer ring size is updated to 2048 in 955df16f2a4c3 ("wifi: ath12k: change MAC buffer ring size to 2048") to increase peak throughput. But during the RX process, a phenomenon can still be observed where the throughput drops by about 30% from its peak value and then recovers, and this behavior repeats during RX. After increasing MAC buffer ring size to 4096, the data rate drop has gone. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Yingying Tang Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260610031358.2043716-1-yingying.tang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index 9b39146e65e1..64f79e43341e 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -205,7 +205,7 @@ struct ath12k_pdev_dp { #define DP_REO_CMD_RING_SIZE 256 #define DP_REO_STATUS_RING_SIZE 2048 #define DP_RXDMA_BUF_RING_SIZE 4096 -#define DP_RX_MAC_BUF_RING_SIZE 2048 +#define DP_RX_MAC_BUF_RING_SIZE 4096 #define DP_RXDMA_REFILL_RING_SIZE 2048 #define DP_RXDMA_ERR_DST_RING_SIZE 1024 #define DP_RXDMA_MON_STATUS_RING_SIZE 1024 From 913998f903fb1432c0046c33003db38a9e8bedb1 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 16 Jun 2026 11:53:42 +0530 Subject: [PATCH 22/58] wifi: ath12k: correct monitor destination ring size The default memory profile configures rxdma_monitor_dst_ring_size as 8092, which is a typo. The intended value is 8192, consistent with all other ring sizes in the table being powers of two. Correct the monitor destination ring size to 8192. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Fixes: defae535dd63 ("wifi: ath12k: Add a table of parameters entries impacting memory consumption") Signed-off-by: Aaradhana Sahu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260616062342.4079796-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index efe37dc91afd..0e7c732f8222 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -49,7 +49,7 @@ ath12k_mem_profile_based_param ath12k_mem_profile_based_param[] = { .dp_params = { .tx_comp_ring_size = 32768, .rxdma_monitor_buf_ring_size = 4096, - .rxdma_monitor_dst_ring_size = 8092, + .rxdma_monitor_dst_ring_size = 8192, .num_pool_tx_desc = 32768, .rx_desc_count = 12288, }, From 08314e7c2c38b9ae6a5e01c58ed10a950859404d Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Thu, 2 Jul 2026 17:28:17 +0530 Subject: [PATCH 23/58] firmware: qcom: Add a generic PAS service Qcom platforms has the legacy of using non-standard SCM calls splintered over the various kernel drivers. These SCM calls aren't compliant with the standard SMC calling conventions which is a prerequisite to enable migration to the FF-A specifications from Arm. OP-TEE as an alternative trusted OS to Qualcomm TEE (QTEE) can't support these non-standard SCM calls. And even for newer architectures using S-EL2 with Hafnium support, QTEE won't be able to support SCM calls either with FF-A requirements coming in. And with both OP-TEE and QTEE drivers well integrated in the TEE subsystem, it makes further sense to reuse the TEE bus client drivers infrastructure. The added benefit of TEE bus infrastructure is that there is support for discoverable/enumerable services. With that client drivers don't have to manually invoke a special SCM call to know the service status. So enable the generic Peripheral Authentication Service (PAS) provided by the firmware. It acts as the common layer with different TZ backends plugged in whether it's an SCM implementation or a proper TEE bus based PAS service implementation. Reviewed-by: Mukesh Ojha Tested-by: Mukesh Ojha # Lemans Reviewed-by: Harshal Dev Tested-by: Vignesh Viswanathan # IPQ9650 Signed-off-by: Sumit Garg Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260702115835.167602-2-sumit.garg@kernel.org Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/Kconfig | 8 + drivers/firmware/qcom/Makefile | 1 + drivers/firmware/qcom/qcom_pas.c | 298 +++++++++++++++++++++++++ drivers/firmware/qcom/qcom_pas.h | 50 +++++ include/linux/firmware/qcom/qcom_pas.h | 43 ++++ 5 files changed, 400 insertions(+) create mode 100644 drivers/firmware/qcom/qcom_pas.c create mode 100644 drivers/firmware/qcom/qcom_pas.h create mode 100644 include/linux/firmware/qcom/qcom_pas.h diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig index b477d54b495a..9f66cc774508 100644 --- a/drivers/firmware/qcom/Kconfig +++ b/drivers/firmware/qcom/Kconfig @@ -6,6 +6,14 @@ menu "Qualcomm firmware drivers" +config QCOM_PAS + tristate "Qualcomm generic PAS interface driver" + help + Enable the generic Peripheral Authentication Service (PAS) provided + by the firmware. It acts as the common layer with different TZ + backends plugged in whether it's an SCM implementation or a proper + TEE bus based PAS service implementation. + config QCOM_SCM select QCOM_TZMEM tristate diff --git a/drivers/firmware/qcom/Makefile b/drivers/firmware/qcom/Makefile index 0be40a1abc13..dc5ab45f906a 100644 --- a/drivers/firmware/qcom/Makefile +++ b/drivers/firmware/qcom/Makefile @@ -8,3 +8,4 @@ qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o obj-$(CONFIG_QCOM_TZMEM) += qcom_tzmem.o obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o +obj-$(CONFIG_QCOM_PAS) += qcom_pas.o diff --git a/drivers/firmware/qcom/qcom_pas.c b/drivers/firmware/qcom/qcom_pas.c new file mode 100644 index 000000000000..24485dd0fa10 --- /dev/null +++ b/drivers/firmware/qcom/qcom_pas.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2010,2015,2019 The Linux Foundation. All rights reserved. + * Copyright (C) 2015 Linaro Ltd. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +#include "qcom_pas.h" + +static struct qcom_pas_ops *ops_ptr; + +/** + * devm_qcom_pas_context_alloc() - Allocate peripheral authentication service + * context for a given peripheral + * + * PAS context is device-resource managed, so the caller does not need + * to worry about freeing the context memory. + * + * @dev: PAS firmware device + * @pas_id: peripheral authentication service id + * @mem_phys: Subsystem reserve memory start address + * @mem_size: Subsystem reserve memory size + * + * Return: The new PAS context, or ERR_PTR() on failure. + */ +struct qcom_pas_context *devm_qcom_pas_context_alloc(struct device *dev, + u32 pas_id, + phys_addr_t mem_phys, + size_t mem_size) +{ + struct qcom_pas_context *ctx; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return ERR_PTR(-ENOMEM); + + ctx->dev = dev; + ctx->pas_id = pas_id; + ctx->mem_phys = mem_phys; + ctx->mem_size = mem_size; + + return ctx; +} +EXPORT_SYMBOL_GPL(devm_qcom_pas_context_alloc); + +/** + * qcom_pas_init_image() - Initialize peripheral authentication service state + * machine for a given peripheral, using the metadata + * @pas_id: peripheral authentication service id + * @metadata: pointer to memory containing ELF header, program header table + * and optional blob of data used for authenticating the metadata + * and the rest of the firmware + * @size: size of the metadata + * @ctx: optional pas context + * + * Return: 0 on success. + * + * Upon successful return, the PAS metadata context (@ctx) will be used to + * track the metadata allocation, this needs to be released by invoking + * qcom_pas_metadata_release() by the caller. + */ +int qcom_pas_init_image(u32 pas_id, const void *metadata, size_t size, + struct qcom_pas_context *ctx) +{ + if (!ops_ptr) + return -ENODEV; + + return ops_ptr->init_image(ops_ptr->dev, pas_id, metadata, size, ctx); +} +EXPORT_SYMBOL_GPL(qcom_pas_init_image); + +/** + * qcom_pas_metadata_release() - release metadata context + * @ctx: pas context + */ +void qcom_pas_metadata_release(struct qcom_pas_context *ctx) +{ + if (!ops_ptr || !ctx || !ctx->ptr) + return; + + ops_ptr->metadata_release(ops_ptr->dev, ctx); +} +EXPORT_SYMBOL_GPL(qcom_pas_metadata_release); + +/** + * qcom_pas_mem_setup() - Prepare the memory related to a given peripheral + * for firmware loading + * @pas_id: peripheral authentication service id + * @addr: start address of memory area to prepare + * @size: size of the memory area to prepare + * + * Return: 0 on success. + */ +int qcom_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size) +{ + if (!ops_ptr) + return -ENODEV; + + return ops_ptr->mem_setup(ops_ptr->dev, pas_id, addr, size); +} +EXPORT_SYMBOL_GPL(qcom_pas_mem_setup); + +/** + * qcom_pas_get_rsc_table() - Retrieve the resource table in passed output buffer + * for a given peripheral. + * + * Qualcomm remote processor may rely on both static and dynamic resources for + * its functionality. Static resources typically refer to memory-mapped + * addresses required by the subsystem and are often embedded within the + * firmware binary and dynamic resources, such as shared memory in DDR etc., + * are determined at runtime during the boot process. + * + * On Qualcomm Technologies devices, it's possible that static resources are + * not embedded in the firmware binary and instead are provided by TrustZone. + * However, dynamic resources are always expected to come from TrustZone. This + * indicates that for Qualcomm devices, all resources (static and dynamic) will + * be provided by TrustZone PAS service. + * + * If the remote processor firmware binary does contain static resources, they + * should be passed in input_rt. These will be forwarded to TrustZone for + * authentication. TrustZone will then append the dynamic resources and return + * the complete resource table in output_rt_tzm. + * + * If the remote processor firmware binary does not include a resource table, + * the caller of this function should set input_rt as NULL and input_rt_size + * as zero respectively. + * + * More about documentation on resource table data structures can be found in + * include/linux/remoteproc.h + * + * @ctx: PAS context + * @input_rt: resource table buffer which is present in firmware binary + * @input_rt_size: size of the resource table present in firmware binary + * @output_rt_size: TrustZone expects caller should pass worst case size for + * the output_rt_tzm. + * + * Return: + * On success, returns a pointer to the allocated buffer containing the final + * resource table and output_rt_size will have actual resource table size from + * TrustZone. The caller is responsible for freeing the buffer. On failure, + * returns ERR_PTR(-errno). + */ +struct resource_table *qcom_pas_get_rsc_table(struct qcom_pas_context *ctx, + void *input_rt, + size_t input_rt_size, + size_t *output_rt_size) +{ + if (!ops_ptr) + return ERR_PTR(-ENODEV); + if (!ctx) + return ERR_PTR(-EINVAL); + + return ops_ptr->get_rsc_table(ops_ptr->dev, ctx, input_rt, + input_rt_size, output_rt_size); +} +EXPORT_SYMBOL_GPL(qcom_pas_get_rsc_table); + +/** + * qcom_pas_auth_and_reset() - Authenticate the given peripheral firmware + * and reset the remote processor + * @pas_id: peripheral authentication service id + * + * Return: 0 on success. + */ +int qcom_pas_auth_and_reset(u32 pas_id) +{ + if (!ops_ptr) + return -ENODEV; + + return ops_ptr->auth_and_reset(ops_ptr->dev, pas_id); +} +EXPORT_SYMBOL_GPL(qcom_pas_auth_and_reset); + +/** + * qcom_pas_prepare_and_auth_reset() - Prepare, authenticate, and reset the + * remote processor + * + * @ctx: Context saved during call to devm_qcom_pas_context_alloc() + * + * This function performs the necessary steps to prepare a PAS subsystem, + * authenticate it using the provided metadata, and initiate a reset sequence. + * + * It should be used when Linux is in control setting up the IOMMU hardware + * for remote subsystem during secure firmware loading processes. The + * preparation step sets up a shmbridge over the firmware memory before + * TrustZone accesses the firmware memory region for authentication. The + * authentication step verifies the integrity and authenticity of the firmware + * or configuration using secure metadata. Finally, the reset step ensures the + * subsystem starts in a clean and sane state. + * + * Return: 0 on success, negative errno on failure. + */ +int qcom_pas_prepare_and_auth_reset(struct qcom_pas_context *ctx) +{ + if (!ops_ptr) + return -ENODEV; + if (!ctx) + return -EINVAL; + + return ops_ptr->prepare_and_auth_reset(ops_ptr->dev, ctx); +} +EXPORT_SYMBOL_GPL(qcom_pas_prepare_and_auth_reset); + +/** + * qcom_pas_set_remote_state() - Set the remote processor state + * @state: peripheral state + * @pas_id: peripheral authentication service id + * + * Return: 0 on success. + */ +int qcom_pas_set_remote_state(u32 state, u32 pas_id) +{ + if (!ops_ptr) + return -ENODEV; + + return ops_ptr->set_remote_state(ops_ptr->dev, state, pas_id); +} +EXPORT_SYMBOL_GPL(qcom_pas_set_remote_state); + +/** + * qcom_pas_shutdown() - Shut down the remote processor + * @pas_id: peripheral authentication service id + * + * Return: 0 on success. + */ +int qcom_pas_shutdown(u32 pas_id) +{ + if (!ops_ptr) + return -ENODEV; + + return ops_ptr->shutdown(ops_ptr->dev, pas_id); +} +EXPORT_SYMBOL_GPL(qcom_pas_shutdown); + +/** + * qcom_pas_supported() - Check if the peripheral authentication service is + * supported for the given peripheral + * @pas_id: peripheral authentication service id + * + * Return: true if PAS is supported for this peripheral, otherwise false. + */ +bool qcom_pas_supported(u32 pas_id) +{ + if (!ops_ptr) + return false; + + return ops_ptr->supported(ops_ptr->dev, pas_id); +} +EXPORT_SYMBOL_GPL(qcom_pas_supported); + +/** + * qcom_pas_is_available() - Check if the peripheral authentication service is + * available. Note that it is mandatory for any PAS + * client to invoke this API. If it returns true then + * only any other PAS API can be invoked. + * + * Return: true if PAS is available, otherwise false. + */ +bool qcom_pas_is_available(void) +{ + /* + * The barrier for ops_ptr is intended to synchronize the data stores + * for the ops data structure when client drivers are in parallel + * checking for PAS service availability. + * + * Once the PAS backend becomes available, it is allowed for multiple + * threads to enter TZ for parallel bringup of co-processors during + * boot. + */ + return !!smp_load_acquire(&ops_ptr); +} +EXPORT_SYMBOL_GPL(qcom_pas_is_available); + +void qcom_pas_ops_register(struct qcom_pas_ops *ops) +{ + if (!qcom_pas_is_available()) + /* Paired with smp_load_acquire() in qcom_pas_is_available() */ + smp_store_release(&ops_ptr, ops); + else + pr_err("qcom_pas: ops already registered by %s\n", + ops_ptr->drv_name); +} +EXPORT_SYMBOL_GPL(qcom_pas_ops_register); + +void qcom_pas_ops_unregister(void) +{ + /* Paired with smp_load_acquire() in qcom_pas_is_available() */ + smp_store_release(&ops_ptr, NULL); +} +EXPORT_SYMBOL_GPL(qcom_pas_ops_unregister); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Qualcomm generic TZ PAS driver"); diff --git a/drivers/firmware/qcom/qcom_pas.h b/drivers/firmware/qcom/qcom_pas.h new file mode 100644 index 000000000000..8643e2760602 --- /dev/null +++ b/drivers/firmware/qcom/qcom_pas.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __QCOM_PAS_INT_H +#define __QCOM_PAS_INT_H + +struct device; + +/** + * struct qcom_pas_ops - Qcom Peripheral Authentication Service (PAS) ops + * @drv_name: PAS driver name. + * @dev: PAS device pointer. + * @supported: Peripheral supported callback. + * @init_image: Peripheral image initialization callback. + * @mem_setup: Peripheral memory setup callback. + * @get_rsc_table: Peripheral get resource table callback. + * @prepare_and_auth_reset: Peripheral prepare firmware authentication and + * reset callback. + * @auth_and_reset: Peripheral firmware authentication and reset + * callback. + * @set_remote_state: Peripheral set remote state callback. + * @shutdown: Peripheral shutdown callback. + * @metadata_release: Image metadata release callback. + */ +struct qcom_pas_ops { + const char *drv_name; + struct device *dev; + bool (*supported)(struct device *dev, u32 pas_id); + int (*init_image)(struct device *dev, u32 pas_id, const void *metadata, + size_t size, struct qcom_pas_context *ctx); + int (*mem_setup)(struct device *dev, u32 pas_id, phys_addr_t addr, + phys_addr_t size); + void *(*get_rsc_table)(struct device *dev, struct qcom_pas_context *ctx, + void *input_rt, size_t input_rt_size, + size_t *output_rt_size); + int (*prepare_and_auth_reset)(struct device *dev, + struct qcom_pas_context *ctx); + int (*auth_and_reset)(struct device *dev, u32 pas_id); + int (*set_remote_state)(struct device *dev, u32 state, u32 pas_id); + int (*shutdown)(struct device *dev, u32 pas_id); + void (*metadata_release)(struct device *dev, + struct qcom_pas_context *ctx); +}; + +void qcom_pas_ops_register(struct qcom_pas_ops *ops); +void qcom_pas_ops_unregister(void); + +#endif /* __QCOM_PAS_INT_H */ diff --git a/include/linux/firmware/qcom/qcom_pas.h b/include/linux/firmware/qcom/qcom_pas.h new file mode 100644 index 000000000000..65b1c9564458 --- /dev/null +++ b/include/linux/firmware/qcom/qcom_pas.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2010-2015, 2018-2019 The Linux Foundation. All rights reserved. + * Copyright (C) 2015 Linaro Ltd. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __QCOM_PAS_H +#define __QCOM_PAS_H + +#include +#include + +struct qcom_pas_context { + struct device *dev; + u32 pas_id; + phys_addr_t mem_phys; + size_t mem_size; + void *ptr; + dma_addr_t phys; + ssize_t size; + bool use_tzmem; +}; + +bool qcom_pas_is_available(void); +struct qcom_pas_context *devm_qcom_pas_context_alloc(struct device *dev, + u32 pas_id, + phys_addr_t mem_phys, + size_t mem_size); +int qcom_pas_init_image(u32 pas_id, const void *metadata, size_t size, + struct qcom_pas_context *ctx); +struct resource_table *qcom_pas_get_rsc_table(struct qcom_pas_context *ctx, + void *input_rt, size_t input_rt_size, + size_t *output_rt_size); +int qcom_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size); +int qcom_pas_auth_and_reset(u32 pas_id); +int qcom_pas_prepare_and_auth_reset(struct qcom_pas_context *ctx); +int qcom_pas_set_remote_state(u32 state, u32 pas_id); +int qcom_pas_shutdown(u32 pas_id); +bool qcom_pas_supported(u32 pas_id); +void qcom_pas_metadata_release(struct qcom_pas_context *ctx); + +#endif /* __QCOM_PAS_H */ From 5c1a2975d23c51c01aca51945d0f10a4ee4c9020 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Thu, 2 Jul 2026 17:28:18 +0530 Subject: [PATCH 24/58] firmware: qcom_scm: Migrate to generic PAS service With the availability of generic PAS service, let's add SCM calls as a backend to keep supporting legacy QTEE interfaces. The exported qcom_scm* wrappers will get dropped once all the client drivers get migrated as part of future patches. Tested-by: Mukesh Ojha # Lemans Reviewed-by: Harshal Dev Tested-by: Vignesh Viswanathan # IPQ9650 Reviewed-by: Konrad Dybcio Signed-off-by: Sumit Garg Link: https://lore.kernel.org/r/20260702115835.167602-3-sumit.garg@kernel.org Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/Kconfig | 4 +- drivers/firmware/qcom/qcom_scm.c | 335 ++++++++++++++----------------- 2 files changed, 157 insertions(+), 182 deletions(-) diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig index 9f66cc774508..a5b3344289f7 100644 --- a/drivers/firmware/qcom/Kconfig +++ b/drivers/firmware/qcom/Kconfig @@ -15,8 +15,10 @@ config QCOM_PAS TEE bus based PAS service implementation. config QCOM_SCM + tristate "Qualcomm PAS SCM interface driver" + select QCOM_PAS select QCOM_TZMEM - tristate + default y if ARCH_QCOM config QCOM_TZMEM tristate diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index 6b601a4b89db..7933e55803dc 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include +#include "qcom_pas.h" #include "qcom_scm.h" #include "qcom_tzmem.h" @@ -479,25 +481,6 @@ void qcom_scm_cpu_power_down(u32 flags) } EXPORT_SYMBOL_GPL(qcom_scm_cpu_power_down); -int qcom_scm_set_remote_state(u32 state, u32 id) -{ - struct qcom_scm_desc desc = { - .svc = QCOM_SCM_SVC_BOOT, - .cmd = QCOM_SCM_BOOT_SET_REMOTE_STATE, - .arginfo = QCOM_SCM_ARGS(2), - .args[0] = state, - .args[1] = id, - .owner = ARM_SMCCC_OWNER_SIP, - }; - struct qcom_scm_res res; - int ret; - - ret = qcom_scm_call(__scm->dev, &desc, &res); - - return ret ? : res.result[0]; -} -EXPORT_SYMBOL_GPL(qcom_scm_set_remote_state); - static int qcom_scm_disable_sdi(void) { int ret; @@ -570,26 +553,12 @@ static void qcom_scm_set_download_mode(u32 dload_mode) dev_err(__scm->dev, "failed to set download mode: %d\n", ret); } -/** - * devm_qcom_scm_pas_context_alloc() - Allocate peripheral authentication service - * context for a given peripheral - * - * PAS context is device-resource managed, so the caller does not need - * to worry about freeing the context memory. - * - * @dev: PAS firmware device - * @pas_id: peripheral authentication service id - * @mem_phys: Subsystem reserve memory start address - * @mem_size: Subsystem reserve memory size - * - * Returns: The new PAS context, or ERR_PTR() on failure. - */ struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev, u32 pas_id, phys_addr_t mem_phys, size_t mem_size) { - struct qcom_scm_pas_context *ctx; + struct qcom_pas_context *ctx; ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) @@ -600,11 +569,12 @@ struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev, ctx->mem_phys = mem_phys; ctx->mem_size = mem_size; - return ctx; + return (struct qcom_scm_pas_context *)ctx; } EXPORT_SYMBOL_GPL(devm_qcom_scm_pas_context_alloc); -static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys, +static int __qcom_scm_pas_init_image(struct device *dev, u32 pas_id, + dma_addr_t mdata_phys, struct qcom_scm_res *res) { struct qcom_scm_desc desc = { @@ -626,7 +596,7 @@ static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys, desc.args[1] = mdata_phys; - ret = qcom_scm_call(__scm->dev, &desc, res); + ret = qcom_scm_call(dev, &desc, res); qcom_scm_bw_disable(); disable_clk: @@ -635,7 +605,8 @@ static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys, return ret; } -static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx, +static int qcom_scm_pas_prep_and_init_image(struct device *dev, + struct qcom_pas_context *ctx, const void *metadata, size_t size) { struct qcom_scm_res res; @@ -650,7 +621,7 @@ static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx, memcpy(mdata_buf, metadata, size); mdata_phys = qcom_tzmem_to_phys(mdata_buf); - ret = __qcom_scm_pas_init_image(ctx->pas_id, mdata_phys, &res); + ret = __qcom_scm_pas_init_image(dev, ctx->pas_id, mdata_phys, &res); if (ret < 0) qcom_tzmem_free(mdata_buf); else @@ -659,25 +630,9 @@ static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx, return ret ? : res.result[0]; } -/** - * qcom_scm_pas_init_image() - Initialize peripheral authentication service - * state machine for a given peripheral, using the - * metadata - * @pas_id: peripheral authentication service id - * @metadata: pointer to memory containing ELF header, program header table - * and optional blob of data used for authenticating the metadata - * and the rest of the firmware - * @size: size of the metadata - * @ctx: optional pas context - * - * Return: 0 on success. - * - * Upon successful return, the PAS metadata context (@ctx) will be used to - * track the metadata allocation, this needs to be released by invoking - * qcom_scm_pas_metadata_release() by the caller. - */ -int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, - struct qcom_scm_pas_context *ctx) +static int __qcom_scm_pas_init_image2(struct device *dev, u32 pas_id, + const void *metadata, size_t size, + struct qcom_pas_context *ctx) { struct qcom_scm_res res; dma_addr_t mdata_phys; @@ -685,7 +640,7 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, int ret; if (ctx && ctx->use_tzmem) - return qcom_scm_pas_prep_and_init_image(ctx, metadata, size); + return qcom_scm_pas_prep_and_init_image(dev, ctx, metadata, size); /* * During the scm call memory protection will be enabled for the meta @@ -699,16 +654,15 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, * If we pass a buffer that is already part of an SHM Bridge to this * call, it will fail. */ - mdata_buf = dma_alloc_coherent(__scm->dev, size, &mdata_phys, - GFP_KERNEL); + mdata_buf = dma_alloc_coherent(dev, size, &mdata_phys, GFP_KERNEL); if (!mdata_buf) return -ENOMEM; memcpy(mdata_buf, metadata, size); - ret = __qcom_scm_pas_init_image(pas_id, mdata_phys, &res); + ret = __qcom_scm_pas_init_image(dev, pas_id, mdata_phys, &res); if (ret < 0 || !ctx) { - dma_free_coherent(__scm->dev, size, mdata_buf, mdata_phys); + dma_free_coherent(dev, size, mdata_buf, mdata_phys); } else if (ctx) { ctx->ptr = mdata_buf; ctx->phys = mdata_phys; @@ -717,36 +671,35 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, return ret ? : res.result[0]; } + +int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, + struct qcom_scm_pas_context *ctx) +{ + return __qcom_scm_pas_init_image2(__scm->dev, pas_id, metadata, size, + (struct qcom_pas_context *)ctx); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_init_image); -/** - * qcom_scm_pas_metadata_release() - release metadata context - * @ctx: pas context - */ -void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx) +static void __qcom_scm_pas_metadata_release(struct device *dev, + struct qcom_pas_context *ctx) { - if (!ctx->ptr) - return; - if (ctx->use_tzmem) qcom_tzmem_free(ctx->ptr); else - dma_free_coherent(__scm->dev, ctx->size, ctx->ptr, ctx->phys); + dma_free_coherent(dev, ctx->size, ctx->ptr, ctx->phys); ctx->ptr = NULL; } + +void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx) +{ + __qcom_scm_pas_metadata_release(__scm->dev, + (struct qcom_pas_context *)ctx); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_metadata_release); -/** - * qcom_scm_pas_mem_setup() - Prepare the memory related to a given peripheral - * for firmware loading - * @pas_id: peripheral authentication service id - * @addr: start address of memory area to prepare - * @size: size of the memory area to prepare - * - * Returns 0 on success. - */ -int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size) +static int __qcom_scm_pas_mem_setup(struct device *dev, u32 pas_id, + phys_addr_t addr, phys_addr_t size) { int ret; struct qcom_scm_desc desc = { @@ -768,7 +721,7 @@ int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size) if (ret) goto disable_clk; - ret = qcom_scm_call(__scm->dev, &desc, &res); + ret = qcom_scm_call(dev, &desc, &res); qcom_scm_bw_disable(); disable_clk: @@ -776,9 +729,15 @@ int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size) return ret ? : res.result[0]; } + +int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size) +{ + return __qcom_scm_pas_mem_setup(__scm->dev, pas_id, addr, size); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_mem_setup); -static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm, +static void *__qcom_scm_pas_get_rsc_table(struct device *dev, u32 pas_id, + void *input_rt_tzm, size_t input_rt_size, size_t *output_rt_size) { @@ -813,7 +772,7 @@ static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm, * with output_rt_tzm buffer with res.result[2] size however, It should not * be of unresonable size. */ - ret = qcom_scm_call(__scm->dev, &desc, &res); + ret = qcom_scm_call(dev, &desc, &res); if (!ret && res.result[2] > SZ_1G) { ret = -E2BIG; goto free_output_rt; @@ -830,51 +789,11 @@ static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm, return ret ? ERR_PTR(ret) : output_rt_tzm; } -/** - * qcom_scm_pas_get_rsc_table() - Retrieve the resource table in passed output buffer - * for a given peripheral. - * - * Qualcomm remote processor may rely on both static and dynamic resources for - * its functionality. Static resources typically refer to memory-mapped addresses - * required by the subsystem and are often embedded within the firmware binary - * and dynamic resources, such as shared memory in DDR etc., are determined at - * runtime during the boot process. - * - * On Qualcomm Technologies devices, it's possible that static resources are not - * embedded in the firmware binary and instead are provided by TrustZone However, - * dynamic resources are always expected to come from TrustZone. This indicates - * that for Qualcomm devices, all resources (static and dynamic) will be provided - * by TrustZone via the SMC call. - * - * If the remote processor firmware binary does contain static resources, they - * should be passed in input_rt. These will be forwarded to TrustZone for - * authentication. TrustZone will then append the dynamic resources and return - * the complete resource table in output_rt_tzm. - * - * If the remote processor firmware binary does not include a resource table, - * the caller of this function should set input_rt as NULL and input_rt_size - * as zero respectively. - * - * More about documentation on resource table data structures can be found in - * include/linux/remoteproc.h - * - * @ctx: PAS context - * @pas_id: peripheral authentication service id - * @input_rt: resource table buffer which is present in firmware binary - * @input_rt_size: size of the resource table present in firmware binary - * @output_rt_size: TrustZone expects caller should pass worst case size for - * the output_rt_tzm. - * - * Return: - * On success, returns a pointer to the allocated buffer containing the final - * resource table and output_rt_size will have actual resource table size from - * TrustZone. The caller is responsible for freeing the buffer. On failure, - * returns ERR_PTR(-errno). - */ -struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx, - void *input_rt, - size_t input_rt_size, - size_t *output_rt_size) +static void *__qcom_scm_pas_get_rsc_table2(struct device *dev, + struct qcom_pas_context *ctx, + void *input_rt, + size_t input_rt_size, + size_t *output_rt_size) { struct resource_table empty_rsc = {}; size_t size = SZ_16K; @@ -909,11 +828,12 @@ struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *c memcpy(input_rt_tzm, input_rt, input_rt_size); - output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id, input_rt_tzm, + output_rt_tzm = __qcom_scm_pas_get_rsc_table(dev, ctx->pas_id, + input_rt_tzm, input_rt_size, &size); if (PTR_ERR(output_rt_tzm) == -EOVERFLOW) /* Try again with the size requested by the TZ */ - output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id, + output_rt_tzm = __qcom_scm_pas_get_rsc_table(dev, ctx->pas_id, input_rt_tzm, input_rt_size, &size); @@ -943,16 +863,20 @@ struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *c return ret ? ERR_PTR(ret) : tbl_ptr; } + +struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx, + void *input_rt, + size_t input_rt_size, + size_t *output_rt_size) +{ + return __qcom_scm_pas_get_rsc_table2(__scm->dev, + (struct qcom_pas_context *)ctx, + input_rt, input_rt_size, + output_rt_size); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_get_rsc_table); -/** - * qcom_scm_pas_auth_and_reset() - Authenticate the given peripheral firmware - * and reset the remote processor - * @pas_id: peripheral authentication service id - * - * Return 0 on success. - */ -int qcom_scm_pas_auth_and_reset(u32 pas_id) +static int __qcom_scm_pas_auth_and_reset(struct device *dev, u32 pas_id) { int ret; struct qcom_scm_desc desc = { @@ -972,7 +896,7 @@ int qcom_scm_pas_auth_and_reset(u32 pas_id) if (ret) goto disable_clk; - ret = qcom_scm_call(__scm->dev, &desc, &res); + ret = qcom_scm_call(dev, &desc, &res); qcom_scm_bw_disable(); disable_clk: @@ -980,28 +904,15 @@ int qcom_scm_pas_auth_and_reset(u32 pas_id) return ret ? : res.result[0]; } + +int qcom_scm_pas_auth_and_reset(u32 pas_id) +{ + return __qcom_scm_pas_auth_and_reset(__scm->dev, pas_id); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_auth_and_reset); -/** - * qcom_scm_pas_prepare_and_auth_reset() - Prepare, authenticate, and reset the - * remote processor - * - * @ctx: Context saved during call to qcom_scm_pas_context_init() - * - * This function performs the necessary steps to prepare a PAS subsystem, - * authenticate it using the provided metadata, and initiate a reset sequence. - * - * It should be used when Linux is in control setting up the IOMMU hardware - * for remote subsystem during secure firmware loading processes. The preparation - * step sets up a shmbridge over the firmware memory before TrustZone accesses the - * firmware memory region for authentication. The authentication step verifies - * the integrity and authenticity of the firmware or configuration using secure - * metadata. Finally, the reset step ensures the subsystem starts in a clean and - * sane state. - * - * Return: 0 on success, negative errno on failure. - */ -int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx) +static int __qcom_scm_pas_prepare_and_auth_reset(struct device *dev, + struct qcom_pas_context *ctx) { u64 handle; int ret; @@ -1012,7 +923,7 @@ int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx) * memory region and then invokes a call to TrustZone to authenticate. */ if (!ctx->use_tzmem) - return qcom_scm_pas_auth_and_reset(ctx->pas_id); + return __qcom_scm_pas_auth_and_reset(dev, ctx->pas_id); /* * When Linux runs @ EL2 Linux must create the shmbridge itself and then @@ -1022,20 +933,45 @@ int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx) if (ret) return ret; - ret = qcom_scm_pas_auth_and_reset(ctx->pas_id); + ret = __qcom_scm_pas_auth_and_reset(dev, ctx->pas_id); qcom_tzmem_shm_bridge_delete(handle); return ret; } + +int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx) +{ + return __qcom_scm_pas_prepare_and_auth_reset(__scm->dev, + (struct qcom_pas_context *)ctx); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_prepare_and_auth_reset); -/** - * qcom_scm_pas_shutdown() - Shut down the remote processor - * @pas_id: peripheral authentication service id - * - * Returns 0 on success. - */ -int qcom_scm_pas_shutdown(u32 pas_id) +static int __qcom_scm_pas_set_remote_state(struct device *dev, u32 state, + u32 pas_id) +{ + struct qcom_scm_desc desc = { + .svc = QCOM_SCM_SVC_BOOT, + .cmd = QCOM_SCM_BOOT_SET_REMOTE_STATE, + .arginfo = QCOM_SCM_ARGS(2), + .args[0] = state, + .args[1] = pas_id, + .owner = ARM_SMCCC_OWNER_SIP, + }; + struct qcom_scm_res res; + int ret; + + ret = qcom_scm_call(dev, &desc, &res); + + return ret ? : res.result[0]; +} + +int qcom_scm_set_remote_state(u32 state, u32 id) +{ + return __qcom_scm_pas_set_remote_state(__scm->dev, state, id); +} +EXPORT_SYMBOL_GPL(qcom_scm_set_remote_state); + +static int __qcom_scm_pas_shutdown(struct device *dev, u32 pas_id) { int ret; struct qcom_scm_desc desc = { @@ -1055,7 +991,7 @@ int qcom_scm_pas_shutdown(u32 pas_id) if (ret) goto disable_clk; - ret = qcom_scm_call(__scm->dev, &desc, &res); + ret = qcom_scm_call(dev, &desc, &res); qcom_scm_bw_disable(); disable_clk: @@ -1063,16 +999,14 @@ int qcom_scm_pas_shutdown(u32 pas_id) return ret ? : res.result[0]; } + +int qcom_scm_pas_shutdown(u32 pas_id) +{ + return __qcom_scm_pas_shutdown(__scm->dev, pas_id); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_shutdown); -/** - * qcom_scm_pas_supported() - Check if the peripheral authentication service is - * available for the given peripherial - * @pas_id: peripheral authentication service id - * - * Returns true if PAS is supported for this peripheral, otherwise false. - */ -bool qcom_scm_pas_supported(u32 pas_id) +static bool __qcom_scm_pas_supported(struct device *dev, u32 pas_id) { int ret; struct qcom_scm_desc desc = { @@ -1084,16 +1018,49 @@ bool qcom_scm_pas_supported(u32 pas_id) }; struct qcom_scm_res res; - if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL, + if (!__qcom_scm_is_call_available(dev, QCOM_SCM_SVC_PIL, QCOM_SCM_PIL_PAS_IS_SUPPORTED)) return false; - ret = qcom_scm_call(__scm->dev, &desc, &res); + ret = qcom_scm_call(dev, &desc, &res); return ret ? false : !!res.result[0]; } + +bool qcom_scm_pas_supported(u32 pas_id) +{ + return __qcom_scm_pas_supported(__scm->dev, pas_id); +} EXPORT_SYMBOL_GPL(qcom_scm_pas_supported); +static struct qcom_pas_ops qcom_pas_ops_scm = { + .drv_name = "qcom_scm", + .supported = __qcom_scm_pas_supported, + .init_image = __qcom_scm_pas_init_image2, + .mem_setup = __qcom_scm_pas_mem_setup, + .get_rsc_table = __qcom_scm_pas_get_rsc_table2, + .auth_and_reset = __qcom_scm_pas_auth_and_reset, + .prepare_and_auth_reset = __qcom_scm_pas_prepare_and_auth_reset, + .set_remote_state = __qcom_scm_pas_set_remote_state, + .shutdown = __qcom_scm_pas_shutdown, + .metadata_release = __qcom_scm_pas_metadata_release, +}; + +/** + * qcom_scm_is_pas_available() - Check if the peripheral authentication service + * is available via SCM or not + * + * Returns true if PAS is available, otherwise false. + */ +static bool qcom_scm_is_pas_available(void) +{ + if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL, + QCOM_SCM_PIL_PAS_AUTH_AND_RESET)) + return false; + + return true; +} + static int __qcom_scm_pas_mss_reset(struct device *dev, bool reset) { struct qcom_scm_desc desc = { @@ -2837,6 +2804,11 @@ static int qcom_scm_probe(struct platform_device *pdev) __get_convention(); + if (qcom_scm_is_pas_available()) { + qcom_pas_ops_scm.dev = scm->dev; + qcom_pas_ops_register(&qcom_pas_ops_scm); + } + /* * If "download mode" is requested, from this point on warmboot * will cause the boot stages to enter download mode, unless @@ -2876,6 +2848,7 @@ static void qcom_scm_shutdown(struct platform_device *pdev) { /* Clean shutdown, disable download mode to allow normal restart */ qcom_scm_set_download_mode(QCOM_DLOAD_NODUMP); + qcom_pas_ops_unregister(); } static const struct of_device_id qcom_scm_dt_match[] = { From b6f7978da0c4d26fe465aa6634f5a0b48f900de0 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Thu, 2 Jul 2026 17:28:19 +0530 Subject: [PATCH 25/58] firmware: qcom: Add a PAS TEE service Add support for Peripheral Authentication Service (PAS) driver based on TEE bus with OP-TEE providing the backend PAS service implementation. The TEE PAS service ABI is designed to be extensible with additional API as PTA_QCOM_PAS_CAPABILITIES. This allows to accommodate any future extensions of the PAS service needed while still maintaining backwards compatibility. Reviewed-by: Mukesh Ojha Tested-by: Mukesh Ojha # Lemans Reviewed-by: Harshal Dev Tested-by: Vignesh Viswanathan # IPQ9650 Signed-off-by: Sumit Garg Link: https://lore.kernel.org/r/20260702115835.167602-4-sumit.garg@kernel.org Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/Kconfig | 10 + drivers/firmware/qcom/Makefile | 1 + drivers/firmware/qcom/qcom_pas_tee.c | 479 +++++++++++++++++++++++++++ 3 files changed, 490 insertions(+) create mode 100644 drivers/firmware/qcom/qcom_pas_tee.c diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig index a5b3344289f7..c7f8413ab996 100644 --- a/drivers/firmware/qcom/Kconfig +++ b/drivers/firmware/qcom/Kconfig @@ -14,6 +14,16 @@ config QCOM_PAS backends plugged in whether it's an SCM implementation or a proper TEE bus based PAS service implementation. +config QCOM_PAS_TEE + tristate "Qualcomm PAS TEE interface driver" + select QCOM_PAS + depends on TEE + depends on !CPU_BIG_ENDIAN + default m if ARCH_QCOM + help + Enable the generic Peripheral Authentication Service (PAS) provided + by the firmware TEE implementation as the backend. + config QCOM_SCM tristate "Qualcomm PAS SCM interface driver" select QCOM_PAS diff --git a/drivers/firmware/qcom/Makefile b/drivers/firmware/qcom/Makefile index dc5ab45f906a..48801d18f37b 100644 --- a/drivers/firmware/qcom/Makefile +++ b/drivers/firmware/qcom/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_QCOM_TZMEM) += qcom_tzmem.o obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o obj-$(CONFIG_QCOM_PAS) += qcom_pas.o +obj-$(CONFIG_QCOM_PAS_TEE) += qcom_pas_tee.o diff --git a/drivers/firmware/qcom/qcom_pas_tee.c b/drivers/firmware/qcom/qcom_pas_tee.c new file mode 100644 index 000000000000..ac33a00687aa --- /dev/null +++ b/drivers/firmware/qcom/qcom_pas_tee.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qcom_pas.h" + +/* + * Peripheral Authentication Service (PAS) supported. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + */ +#define TA_QCOM_PAS_IS_SUPPORTED 1 + +/* + * PAS capabilities. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [out] params[1].value.a: PAS capability flags + */ +#define TA_QCOM_PAS_CAPABILITIES 2 + +/* + * PAS image initialization. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [in] params[1].memref: Loadable firmware metadata + */ +#define TA_QCOM_PAS_INIT_IMAGE 3 + +/* + * PAS memory setup. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [in] params[0].value.b: Relocatable firmware size + * [in] params[1].value.a: 32bit LSB relocatable firmware memory address + * [in] params[1].value.b: 32bit MSB relocatable firmware memory address + */ +#define TA_QCOM_PAS_MEM_SETUP 4 + +/* + * PAS get resource table. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [inout] params[1].memref: Resource table config + */ +#define TA_QCOM_PAS_GET_RESOURCE_TABLE 5 + +/* + * PAS image authentication and co-processor reset. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [in] params[0].value.b: Firmware size + * [in] params[1].value.a: 32bit LSB firmware memory address + * [in] params[1].value.b: 32bit MSB firmware memory address + * [in] params[2].memref: Optional fw memory space shared/lent + */ +#define TA_QCOM_PAS_AUTH_AND_RESET 6 + +/* + * PAS co-processor set suspend/resume state. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + * [in] params[0].value.b: Co-processor state identifier + */ +#define TA_QCOM_PAS_SET_REMOTE_STATE 7 + +/* + * PAS co-processor shutdown. + * + * [in] params[0].value.a: Unique 32bit remote processor identifier + */ +#define TA_QCOM_PAS_SHUTDOWN 8 + +#define TEE_NUM_PARAMS 4 + +/** + * struct qcom_pas_tee_private - PAS service private data + * @dev: PAS service device. + * @ctx: TEE context handler. + * @session_id: PAS TA session identifier. + */ +struct qcom_pas_tee_private { + struct device *dev; + struct tee_context *ctx; + u32 session_id; +}; + +static bool qcom_pas_tee_supported(struct device *dev, u32 pas_id) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_IS_SUPPORTED, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id + } + }; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS not supported, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + return false; + } + + return true; +} + +static int qcom_pas_tee_init_image(struct device *dev, u32 pas_id, + const void *metadata, size_t size, + struct qcom_pas_context *ctx) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_INIT_IMAGE, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id + }, + [1] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT, + } + }; + struct tee_shm *mdata_shm; + u8 *mdata_buf = NULL; + int ret; + + mdata_shm = tee_shm_alloc_kernel_buf(data->ctx, size); + if (IS_ERR(mdata_shm)) { + dev_err(dev, "mdata_shm allocation failed\n"); + return PTR_ERR(mdata_shm); + } + + mdata_buf = tee_shm_get_va(mdata_shm, 0); + if (IS_ERR(mdata_buf)) { + dev_err(dev, "mdata_buf get VA failed\n"); + tee_shm_free(mdata_shm); + return PTR_ERR(mdata_buf); + } + memcpy(mdata_buf, metadata, size); + + param[1].u.memref.shm = mdata_shm; + param[1].u.memref.size = size; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS init image failed, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + tee_shm_free(mdata_shm); + return ret ?: -EINVAL; + } + + if (ctx) + ctx->ptr = (void *)mdata_shm; + else + tee_shm_free(mdata_shm); + + return ret; +} + +static int qcom_pas_tee_mem_setup(struct device *dev, u32 pas_id, + phys_addr_t addr, phys_addr_t size) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_MEM_SETUP, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id, + .u.value.b = size, + }, + [1] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = lower_32_bits(addr), + .u.value.b = upper_32_bits(addr), + } + }; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS mem setup failed, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + return ret ?: -EINVAL; + } + + return ret; +} + +DEFINE_FREE(shm_free, struct tee_shm *, tee_shm_free(_T)) + +static void *qcom_pas_tee_get_rsc_table(struct device *dev, + struct qcom_pas_context *ctx, + void *input_rt, size_t input_rt_size, + size_t *output_rt_size) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_GET_RESOURCE_TABLE, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = ctx->pas_id, + }, + [1] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT, + .u.memref.size = input_rt_size, + } + }; + void *rt_buf = NULL; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, err: 0x%x\n", + ctx->pas_id, ret, inv_arg.ret); + return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL); + } + + if (param[1].u.memref.size >= input_rt_size) { + struct tee_shm *rt_shm __free(shm_free) = + tee_shm_alloc_kernel_buf(data->ctx, + param[1].u.memref.size); + void *rt_shm_va; + + if (IS_ERR_OR_NULL(rt_shm)) { + dev_err(dev, "rt_shm allocation failed\n"); + rt_shm = NULL; + return ERR_PTR(-ENOMEM); + } + + rt_shm_va = tee_shm_get_va(rt_shm, 0); + if (IS_ERR(rt_shm_va)) { + dev_err(dev, "rt_shm get VA failed\n"); + return ERR_CAST(rt_shm_va); + } + memcpy(rt_shm_va, input_rt, input_rt_size); + + param[1].u.memref.shm = rt_shm; + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, err: 0x%x\n", + ctx->pas_id, ret, inv_arg.ret); + return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL); + } + + if (param[1].u.memref.size) { + *output_rt_size = param[1].u.memref.size; + rt_buf = kmemdup(rt_shm_va, *output_rt_size, GFP_KERNEL); + if (!rt_buf) + return ERR_PTR(-ENOMEM); + } + } else { + *output_rt_size = 0; + } + + return rt_buf; +} + +static int __qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id, + phys_addr_t mem_phys, size_t mem_size) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_AUTH_AND_RESET, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id, + .u.value.b = mem_size, + }, + [1] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = lower_32_bits(mem_phys), + .u.value.b = upper_32_bits(mem_phys), + }, + /* Reserved for fw memory space to be shared or lent */ + [2] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT, + } + }; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS auth reset failed, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + return ret ?: -EINVAL; + } + + return ret; +} + +static int qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id) +{ + return __qcom_pas_tee_auth_and_reset(dev, pas_id, 0, 0); +} + +static int qcom_pas_tee_prepare_and_auth_reset(struct device *dev, + struct qcom_pas_context *ctx) +{ + return __qcom_pas_tee_auth_and_reset(dev, ctx->pas_id, ctx->mem_phys, + ctx->mem_size); +} + +static int qcom_pas_tee_set_remote_state(struct device *dev, u32 state, + u32 pas_id) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_SET_REMOTE_STATE, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id, + .u.value.b = state, + } + }; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS set remote state failed, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + return ret ?: -EINVAL; + } + + return ret; +} + +static int qcom_pas_tee_shutdown(struct device *dev, u32 pas_id) +{ + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + struct tee_ioctl_invoke_arg inv_arg = { + .func = TA_QCOM_PAS_SHUTDOWN, + .session = data->session_id, + .num_params = TEE_NUM_PARAMS + }; + struct tee_param param[4] = { + [0] = { + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, + .u.value.a = pas_id + } + }; + int ret; + + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(dev, "PAS shutdown failed, pas_id: %d, ret: %d, err: 0x%x\n", + pas_id, ret, inv_arg.ret); + return ret ?: -EINVAL; + } + + return ret; +} + +static void qcom_pas_tee_metadata_release(struct device *dev, + struct qcom_pas_context *ctx) +{ + struct tee_shm *mdata_shm = ctx->ptr; + + tee_shm_free(mdata_shm); + ctx->ptr = NULL; +} + +static struct qcom_pas_ops qcom_pas_ops_tee = { + .drv_name = "qcom-pas-tee", + .supported = qcom_pas_tee_supported, + .init_image = qcom_pas_tee_init_image, + .mem_setup = qcom_pas_tee_mem_setup, + .get_rsc_table = qcom_pas_tee_get_rsc_table, + .auth_and_reset = qcom_pas_tee_auth_and_reset, + .prepare_and_auth_reset = qcom_pas_tee_prepare_and_auth_reset, + .set_remote_state = qcom_pas_tee_set_remote_state, + .shutdown = qcom_pas_tee_shutdown, + .metadata_release = qcom_pas_tee_metadata_release, +}; + +static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) +{ + return ver->impl_id == TEE_IMPL_ID_OPTEE; +} + +static int qcom_pas_tee_probe(struct tee_client_device *pas_dev) +{ + struct device *dev = &pas_dev->dev; + struct qcom_pas_tee_private *data; + struct tee_ioctl_open_session_arg sess_arg = { + .clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL + }; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL); + if (IS_ERR(data->ctx)) + return -ENODEV; + + export_uuid(sess_arg.uuid, &pas_dev->id.uuid); + ret = tee_client_open_session(data->ctx, &sess_arg, NULL); + if (ret < 0 || sess_arg.ret != 0) { + dev_err(dev, "tee_client_open_session failed, ret: %d, err: 0x%x\n", + ret, sess_arg.ret); + tee_client_close_context(data->ctx); + return ret ?: -EINVAL; + } + + data->session_id = sess_arg.session; + dev_set_drvdata(dev, data); + qcom_pas_ops_tee.dev = dev; + qcom_pas_ops_register(&qcom_pas_ops_tee); + + return ret; +} + +static void qcom_pas_tee_remove(struct tee_client_device *pas_dev) +{ + struct device *dev = &pas_dev->dev; + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); + + qcom_pas_ops_unregister(); + tee_client_close_session(data->ctx, data->session_id); + tee_client_close_context(data->ctx); +} + +static const struct tee_client_device_id qcom_pas_tee_id_table[] = { + {UUID_INIT(0xcff7d191, 0x7ca0, 0x4784, + 0xaf, 0x13, 0x48, 0x22, 0x3b, 0x9a, 0x4f, 0xbe)}, + {} +}; +MODULE_DEVICE_TABLE(tee, qcom_pas_tee_id_table); + +static struct tee_client_driver optee_pas_tee_driver = { + .probe = qcom_pas_tee_probe, + .remove = qcom_pas_tee_remove, + .id_table = qcom_pas_tee_id_table, + .driver = { + .name = "qcom-pas-tee", + }, +}; + +module_tee_client_driver(optee_pas_tee_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Qualcomm PAS TEE driver"); From 6701259025d49139131a0eb2257659a066dcca22 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Thu, 2 Jul 2026 17:28:30 +0530 Subject: [PATCH 26/58] MAINTAINERS: Add maintainer entry for Qualcomm PAS TZ service Add Sumit Garg as the maintainer for the Qualcomm generic Peripheral Authentication Service (PAS) as well as the PAS TEE backend driver. Signed-off-by: Sumit Garg Link: https://lore.kernel.org/r/20260702115835.167602-15-sumit.garg@kernel.org Signed-off-by: Bjorn Andersson --- MAINTAINERS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..7847b2a98f90 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22315,6 +22315,15 @@ F: Documentation/devicetree/bindings/media/*qcom* F: drivers/media/platform/qcom F: include/dt-bindings/media/*qcom* +QUALCOMM PAS TZ SERVICE +M: Sumit Garg +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: drivers/firmware/qcom/qcom_pas.c +F: drivers/firmware/qcom/qcom_pas.h +F: drivers/firmware/qcom/qcom_pas_tee.c +F: include/linux/firmware/qcom/qcom_pas.h + QUALCOMM SMB CHARGER DRIVER M: Casey Connolly L: linux-arm-msm@vger.kernel.org From d418509383b0c884b70814ae85d3ef105a63b940 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Thu, 2 Jul 2026 17:28:28 +0530 Subject: [PATCH 27/58] wifi: ath12k: Switch to generic PAS TZ APIs Switch ath12k client driver over to generic PAS TZ APIs. Generic PAS TZ service allows to support multiple TZ implementation backends like QTEE based SCM PAS service, OP-TEE based PAS service and any further future TZ backend service. Acked-by: Jeff Johnson Signed-off-by: Sumit Garg Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260702115835.167602-13-sumit.garg@kernel.org Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/Kconfig | 2 +- drivers/net/wireless/ath/ath12k/ahb.c | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/Kconfig b/drivers/net/wireless/ath/ath12k/Kconfig index 4a2b240f967a..0d5d1c55bfc1 100644 --- a/drivers/net/wireless/ath/ath12k/Kconfig +++ b/drivers/net/wireless/ath/ath12k/Kconfig @@ -18,7 +18,7 @@ config ATH12K_AHB bool "Qualcomm ath12k AHB support" depends on ATH12K && REMOTEPROC select QCOM_MDT_LOADER - select QCOM_SCM + select QCOM_PAS help Enable support for Ath12k AHB bus chipsets, example IPQ5332. diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 4912172e106e..4944ea7855ca 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -5,7 +5,7 @@ */ #include -#include +#include #include #include #include @@ -419,7 +419,7 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab) if (ab_ahb->scm_auth_enabled) { /* Authenticate FW image using peripheral ID */ - ret = qcom_scm_pas_auth_and_reset(pasid); + ret = qcom_pas_auth_and_reset(pasid); if (ret) { ath12k_err(ab, "failed to boot the remote processor %d\n", ret); goto err_fw2; @@ -484,10 +484,10 @@ static void ath12k_ahb_power_down(struct ath12k_base *ab, bool is_suspend) pasid = (u32_encode_bits(ab_ahb->userpd_id, ATH12K_USERPD_ID_MASK)) | ATH12K_AHB_UPD_SWID; /* Release the firmware */ - ret = qcom_scm_pas_shutdown(pasid); + ret = qcom_pas_shutdown(pasid); if (ret) - ath12k_err(ab, "scm pas shutdown failed for userPD%d\n", - ab_ahb->userpd_id); + ath12k_err(ab, "PAS shutdown failed for userPD%d: %d\n", + ab_ahb->userpd_id, ret); } } From f066e1a93703c5be0fd905109d00587541711c97 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 29 Jun 2026 15:01:17 +0800 Subject: [PATCH 28/58] wifi: ath12k: fix dp_link_peer dangling references on AP vdev rollback ath12k_mac_vdev_create() for an AP vdev creates the bss self-peer via ath12k_peer_create(), which finishes by calling ath12k_dp_link_peer_assign() to publish the dp_link_peer in the dp_hw->dp_peers[peerid_index] RCU table, in the dp_peer's link_peers[] array, and in the per-addr rhashtable. If a step after ath12k_peer_create() fails the function jumps to err_peer_del, which open-codes a WMI peer_delete and waits for the unmap / delete_resp events. The wait_for_peer_delete_done() path relies on ath12k_dp_link_peer_unmap_event() freeing the dp_link_peer when the unmap arrives, but err_peer_del never calls ath12k_dp_link_peer_unassign() first. The published references in the dp_hw RCU table, dp_peer->link_peers[] and the rhashtable are left pointing at the dp_link_peer that unmap_event then frees, producing dangling pointers and use-after-free on subsequent lookups. Replace the open-coded sequence with a call to ath12k_peer_delete(), which already does ath12k_dp_link_peer_unassign() before sending the WMI command. This drops the published references before the dp_link_peer is freed, in the same order as the normal teardown path in ath12k_mac_remove_link_interface(). Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Fixes: 5525f12fa671 ("wifi: ath12k: Attach and detach ath12k_dp_link_peer to ath12k_dp_peer") Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260629-ath12k-mlo-peer-delete-race-v2-1-362b25590d19@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 16339469c24c..3b6720b7f3bd 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -10568,22 +10568,8 @@ int ath12k_mac_vdev_create(struct ath12k *ar, struct ath12k_link_vif *arvif) err_peer_del: if (ahvif->vdev_type == WMI_VDEV_TYPE_AP) { - reinit_completion(&ar->peer_delete_done); - - ret = ath12k_wmi_send_peer_delete_cmd(ar, arvif->bssid, - arvif->vdev_id); - if (ret) { - ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM\n", - arvif->vdev_id, arvif->bssid); - goto err_dp_peer_del; - } - - ret = ath12k_wait_for_peer_delete_done(ar, arvif->vdev_id, - arvif->bssid); - if (ret) - goto err_dp_peer_del; - - ar->num_peers--; + /* ignore return value: propagate the original error */ + ath12k_peer_delete(ar, arvif->vdev_id, arvif->bssid); } err_dp_peer_del: From 01cc0f59aac3574b6a7b02493a76bc8ed0aa758f Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 29 Jun 2026 15:01:18 +0800 Subject: [PATCH 29/58] wifi: ath12k: fix MLO peer delete race ath12k_peer_mlo_link_peers_delete() sends WMI peer_delete for every link before waiting for any peer_unmap / peer_delete_resp event. The shared per-radio completion ar->peer_delete_done could not disambiguate which peer a response was for: every call to ath12k_peer_delete_send() did reinit_completion(&ar->peer_delete_done), so when an event for the first link arrived between two sends it raised the count to 1 and the second send promptly cleared it; the wait for the second link then timed out with Timeout in receiving peer delete response Replace the shared completion with a per-radio waiter list, with each pending ath12k_peer_delete() caller queueing an ath12k_peer_delete_wait carrying its (vdev_id, addr) and a private struct completion. ath12k_peer_delete_resp_event() matches the response against the list under ar->data_lock and signals the matching waiter. Also correct the endian conversion in ath12k_peer_delete_resp_event() logging, and add the missing \n in some logging. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Fixes: 8e6f8bc28603 ("wifi: ath12k: Add MLO station state change handling") Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260629-ath12k-mlo-peer-delete-race-v2-2-362b25590d19@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 2 +- drivers/net/wireless/ath/ath12k/core.h | 5 +- drivers/net/wireless/ath/ath12k/mac.c | 2 +- drivers/net/wireless/ath/ath12k/peer.c | 132 ++++++++++++++++++++----- drivers/net/wireless/ath/ath12k/peer.h | 14 ++- drivers/net/wireless/ath/ath12k/wmi.c | 16 +-- 6 files changed, 132 insertions(+), 39 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 0e7c732f8222..fb599caa3aab 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1527,7 +1527,7 @@ static void ath12k_core_pre_reconfigure_recovery(struct ath12k_base *ab) complete_all(&ar->scan.completed); complete(&ar->scan.on_channel); complete(&ar->peer_assoc_done); - complete(&ar->peer_delete_done); + ath12k_peer_delete_wait_flush(ar); complete(&ar->install_key_done); complete(&ar->vdev_setup_done); complete(&ar->vdev_delete_done); diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index fc5127b5c1a3..1436ff4316e7 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -665,7 +665,8 @@ struct ath12k { /* protects the radio specific data like debug stats, ppdu_stats_info stats, * vdev_stop_status info, scan data, ath12k_sta info, ath12k_link_vif info, - * channel context data, survey info, test mode data, regd_channel_update_queue. + * channel context data, survey info, test mode data, regd_channel_update_queue, + * peer_delete_waits. */ spinlock_t data_lock; @@ -687,7 +688,7 @@ struct ath12k { u8 radio_idx; struct completion peer_assoc_done; - struct completion peer_delete_done; + struct list_head peer_delete_waits; int install_key_status; struct completion install_key_done; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 3b6720b7f3bd..6667019a0049 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -15047,11 +15047,11 @@ static void ath12k_mac_setup(struct ath12k *ar) spin_lock_init(&ar->dp.ppdu_list_lock); INIT_LIST_HEAD(&ar->arvifs); INIT_LIST_HEAD(&ar->dp.ppdu_stats_info); + INIT_LIST_HEAD(&ar->peer_delete_waits); init_completion(&ar->vdev_setup_done); init_completion(&ar->vdev_delete_done); init_completion(&ar->peer_assoc_done); - init_completion(&ar->peer_delete_done); init_completion(&ar->install_key_done); init_completion(&ar->bss_survey_done); init_completion(&ar->scan.started); diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c index c222bdaa333c..5084e8c42a3f 100644 --- a/drivers/net/wireless/ath/ath12k/peer.c +++ b/drivers/net/wireless/ath/ath12k/peer.c @@ -9,6 +9,55 @@ #include "debug.h" #include "debugfs.h" +static void ath12k_peer_delete_wait_register(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait, + u32 vdev_id, const u8 *addr) +{ + wait->vdev_id = vdev_id; + ether_addr_copy(wait->addr, addr); + init_completion(&wait->done); + + spin_lock_bh(&ar->data_lock); + list_add(&wait->list, &ar->peer_delete_waits); + spin_unlock_bh(&ar->data_lock); +} + +static void ath12k_peer_delete_wait_unregister(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait) +{ + spin_lock_bh(&ar->data_lock); + list_del(&wait->list); + spin_unlock_bh(&ar->data_lock); +} + +void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr) +{ + struct ath12k_peer_delete_wait *wait; + + guard(spinlock_bh)(&ar->data_lock); + + list_for_each_entry(wait, &ar->peer_delete_waits, list) { + if (wait->vdev_id == vdev_id && + ether_addr_equal(wait->addr, addr)) { + complete(&wait->done); + return; + } + } + + ath12k_warn(ar->ab, "failed to find link peer with vdev id %u addr %pM\n", + vdev_id, addr); +} + +void ath12k_peer_delete_wait_flush(struct ath12k *ar) +{ + struct ath12k_peer_delete_wait *wait; + + spin_lock_bh(&ar->data_lock); + list_for_each_entry(wait, &ar->peer_delete_waits, list) + complete(&wait->done); + spin_unlock_bh(&ar->data_lock); +} + static int ath12k_wait_for_dp_link_peer_common(struct ath12k_base *ab, int vdev_id, const u8 *addr, bool expect_mapped) { @@ -62,20 +111,19 @@ static int ath12k_wait_for_peer_deleted(struct ath12k *ar, int vdev_id, const u8 return ath12k_wait_for_dp_link_peer_common(ar->ab, vdev_id, addr, false); } -int ath12k_wait_for_peer_delete_done(struct ath12k *ar, u32 vdev_id, - const u8 *addr) +int ath12k_wait_for_peer_delete_done(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait) { - int ret; unsigned long time_left; + int ret; - ret = ath12k_wait_for_peer_deleted(ar, vdev_id, addr); + ret = ath12k_wait_for_peer_deleted(ar, wait->vdev_id, wait->addr); if (ret) { - ath12k_warn(ar->ab, "failed wait for peer deleted"); + ath12k_warn(ar->ab, "failed wait for peer deleted\n"); return ret; } - time_left = wait_for_completion_timeout(&ar->peer_delete_done, - 3 * HZ); + time_left = wait_for_completion_timeout(&wait->done, 3 * HZ); if (time_left == 0) { ath12k_warn(ar->ab, "Timeout in receiving peer delete response\n"); return -ETIMEDOUT; @@ -91,8 +139,6 @@ static int ath12k_peer_delete_send(struct ath12k *ar, u32 vdev_id, const u8 *add lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); - reinit_completion(&ar->peer_delete_done); - ret = ath12k_wmi_send_peer_delete_cmd(ar, addr, vdev_id); if (ret) { ath12k_warn(ab, @@ -106,6 +152,7 @@ static int ath12k_peer_delete_send(struct ath12k *ar, u32 vdev_id, const u8 *add int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr) { + struct ath12k_peer_delete_wait wait; int ret; lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); @@ -114,17 +161,25 @@ int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr) &(ath12k_ar_to_ah(ar)->dp_hw), vdev_id, addr, ar->hw_link_id); + /* + * Register the stack waiter before sending so the resp_event for + * this peer cannot arrive while no waiter is queued. + */ + ath12k_peer_delete_wait_register(ar, &wait, vdev_id, addr); + ret = ath12k_peer_delete_send(ar, vdev_id, addr); if (ret) - return ret; + goto out; - ret = ath12k_wait_for_peer_delete_done(ar, vdev_id, addr); + ret = ath12k_wait_for_peer_delete_done(ar, &wait); if (ret) - return ret; + goto out; ar->num_peers--; - return 0; +out: + ath12k_peer_delete_wait_unregister(ar, &wait); + return ret; } static int ath12k_wait_for_peer_created(struct ath12k *ar, int vdev_id, const u8 *addr) @@ -184,22 +239,26 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, peer = ath12k_dp_link_peer_find_by_vdev_and_addr(dp, arg->vdev_id, arg->peer_addr); if (!peer) { + struct ath12k_peer_delete_wait wait; + spin_unlock_bh(&dp->dp_lock); ath12k_warn(ar->ab, "failed to find peer %pM on vdev %i after creation\n", arg->peer_addr, arg->vdev_id); - reinit_completion(&ar->peer_delete_done); + ath12k_peer_delete_wait_register(ar, &wait, arg->vdev_id, + arg->peer_addr); ret = ath12k_wmi_send_peer_delete_cmd(ar, arg->peer_addr, arg->vdev_id); if (ret) { ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM\n", arg->vdev_id, arg->peer_addr); + ath12k_peer_delete_wait_unregister(ar, &wait); return ret; } - ret = ath12k_wait_for_peer_delete_done(ar, arg->vdev_id, - arg->peer_addr); + ret = ath12k_wait_for_peer_delete_done(ar, &wait); + ath12k_peer_delete_wait_unregister(ar, &wait); if (ret) return ret; @@ -283,13 +342,14 @@ u16 ath12k_peer_ml_alloc(struct ath12k_hw *ah) int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta) { + DECLARE_BITMAP(registered, IEEE80211_MLD_MAX_NUM_LINKS); struct ieee80211_sta *sta = ath12k_ahsta_to_sta(ahsta); struct ath12k_hw *ah = ahvif->ah; struct ath12k_link_vif *arvif; struct ath12k_link_sta *arsta; + int ret, err_ret = 0; unsigned long links; struct ath12k *ar; - int ret, err_ret = 0; u8 link_id; lockdep_assert_wiphy(ah->hw->wiphy); @@ -297,8 +357,19 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st if (!sta->mlo) return -EINVAL; - /* FW expects delete of all link peers at once before waiting for reception - * of peer unmap or delete responses + struct ath12k_peer_delete_wait *waits __free(kfree) = + kzalloc_objs(*waits, IEEE80211_MLD_MAX_NUM_LINKS); + if (!waits) + return -ENOMEM; + + bitmap_zero(registered, IEEE80211_MLD_MAX_NUM_LINKS); + + /* + * Firmware expects delete of all link peers at once before waiting + * for reception of peer unmap or delete responses. Phase 1 registers + * a per-link stack waiter and sends WMI peer delete for every + * link; the resp_event handler matches each response to its + * (vdev_id, addr) waiter on ar->peer_delete_waits. */ links = ahsta->links_map; for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { @@ -318,29 +389,36 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st arvif->vdev_id, arsta->addr, ar->hw_link_id); + ath12k_peer_delete_wait_register(ar, &waits[link_id], + arvif->vdev_id, arsta->addr); + ret = ath12k_peer_delete_send(ar, arvif->vdev_id, arsta->addr); if (ret) { ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM ret %d\n", arvif->vdev_id, arsta->addr, ret); err_ret = ret; + ath12k_peer_delete_wait_unregister(ar, &waits[link_id]); continue; } + + set_bit(link_id, registered); } - /* Ensure all link peers are deleted and unmapped */ + /* + * Phase 2: wait for unmap + delete_resp on each registered link + * and tear down the waiter. + */ links = ahsta->links_map; for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + if (!test_bit(link_id, registered)) + continue; + arvif = wiphy_dereference(ah->hw->wiphy, ahvif->link[link_id]); - arsta = wiphy_dereference(ah->hw->wiphy, ahsta->link[link_id]); - if (!arvif || !arsta) - continue; - ar = arvif->ar; - if (!ar) - continue; - ret = ath12k_wait_for_peer_delete_done(ar, arvif->vdev_id, arsta->addr); + ret = ath12k_wait_for_peer_delete_done(ar, &waits[link_id]); + ath12k_peer_delete_wait_unregister(ar, &waits[link_id]); if (ret) { err_ret = ret; continue; diff --git a/drivers/net/wireless/ath/ath12k/peer.h b/drivers/net/wireless/ath/ath12k/peer.h index 49d89796bc46..9343944c2b8e 100644 --- a/drivers/net/wireless/ath/ath12k/peer.h +++ b/drivers/net/wireless/ath/ath12k/peer.h @@ -9,13 +9,23 @@ #include "dp_peer.h" +struct ath12k_peer_delete_wait { + struct list_head list; + u32 vdev_id; + u8 addr[ETH_ALEN]; + struct completion done; +}; + +void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr); +void ath12k_peer_delete_wait_flush(struct ath12k *ar); + void ath12k_peer_cleanup(struct ath12k *ar, u32 vdev_id); int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr); int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, struct ieee80211_sta *sta, struct ath12k_wmi_peer_create_arg *arg); -int ath12k_wait_for_peer_delete_done(struct ath12k *ar, u32 vdev_id, - const u8 *addr); +int ath12k_wait_for_peer_delete_done(struct ath12k *ar, + struct ath12k_peer_delete_wait *wait); int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta); struct ath12k_ml_peer *ath12k_peer_ml_find(struct ath12k_hw *ah, const u8 *addr); diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index ad739bffcf88..da14cfda0528 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -7095,25 +7095,29 @@ static void ath12k_peer_delete_resp_event(struct ath12k_base *ab, struct sk_buff { struct wmi_peer_delete_resp_event peer_del_resp; struct ath12k *ar; + u32 vdev_id; if (ath12k_pull_peer_del_resp_ev(ab, skb, &peer_del_resp) != 0) { - ath12k_warn(ab, "failed to extract peer delete resp"); + ath12k_warn(ab, "failed to extract peer delete resp\n"); return; } + vdev_id = le32_to_cpu(peer_del_resp.vdev_id); + rcu_read_lock(); - ar = ath12k_mac_get_ar_by_vdev_id(ab, le32_to_cpu(peer_del_resp.vdev_id)); + ar = ath12k_mac_get_ar_by_vdev_id(ab, vdev_id); if (!ar) { - ath12k_warn(ab, "invalid vdev id in peer delete resp ev %d", - peer_del_resp.vdev_id); + ath12k_warn(ab, "invalid vdev id in peer delete resp ev %d\n", + vdev_id); rcu_read_unlock(); return; } - complete(&ar->peer_delete_done); + ath12k_peer_delete_resp_signal(ar, vdev_id, + peer_del_resp.peer_macaddr.addr); rcu_read_unlock(); ath12k_dbg(ab, ATH12K_DBG_WMI, "peer delete resp for vdev id %d addr %pM\n", - peer_del_resp.vdev_id, peer_del_resp.peer_macaddr.addr); + vdev_id, peer_del_resp.peer_macaddr.addr); } static void ath12k_vdev_delete_resp_event(struct ath12k_base *ab, From 6385fd09a399805e82e50b228f1af318316aaeb0 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sat, 11 Jul 2026 08:31:10 -0700 Subject: [PATCH 30/58] wifi: ath12k: Fix ath12k_wifi7_mac_op_tx() style issues Commit e47d6c9bb416 ("wifi: ath12k: Advertise multicast Ethernet encapsulation offload support") introduced a few style issues. ath12k-check reports: drivers/net/wireless/ath/ath12k/wifi7/hw.c:1042: line length of 92 exceeds 90 columns And automated review did not like one if/else that did not use braces for a single statement that also included a block comment. Fix these issues. Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260711-ath12k_wifi7_mac_op_tx-line-length-v1-1-10e4899b98ef@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index e5bf9d218104..d54c2a6d83b2 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -1031,18 +1031,22 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw, if (cb_flags & ATH12K_SKB_HW_80211_ENCAP) { /* - * skb->data may be modified for the iova_mask devices. - * It is better to use skb_copy() for such devices - * to avoid any potential skb corruption related issues. + * skb->data may be modified for the + * iova_mask devices. It is better to + * use skb_copy() for such devices to + * avoid any potential skb corruption + * related issues. */ - if (tmp_dp->hw_params->iova_mask) + if (tmp_dp->hw_params->iova_mask) { msdu_copied = skb_copy(skb, GFP_ATOMIC); - else + } else { /* - * ath12k_wifi7_dp_tx() should treat cloned HW-encap - * Ethernet multicast frames as read-only. + * ath12k_wifi7_dp_tx() should + * treat cloned HW-encap Ethernet + * multicast frames as read-only. */ msdu_copied = skb_clone(skb, GFP_ATOMIC); + } if (!msdu_copied) { ath12k_err(ar->ab, "skb copy/clone failure link_id 0x%X vdevid 0x%X\n", From 6b31c3451b12cfc5e86a41898014cb62d2a377b1 Mon Sep 17 00:00:00 2001 From: Thiraviyam Mariyappan Date: Mon, 22 Jun 2026 11:53:24 +0530 Subject: [PATCH 31/58] wifi: ath12k: Reduce RX SRNG interrupt timer threshold to 200us Currently when RX traffic is low or intermittent, the RX SRNG interrupt mitigation logic defers packet processing for up to 500us via HAL_SRNG_INT_TIMER_THRESHOLD_RX. This causes excessive RX servicing delay, leading to increased end-to-end latency and degraded TCP performance in low-concurrency scenarios. In single-client single-stream TCP tests using 5G EHT160 (NSS 2x2) mode, throughput drops to ~400 Mbps DL and UL instead of the expected ~600 Mbps. In addition, UDP UL end-to-end latency measured in 5G VHT80 (NSS 4x4) mode increases by up to ~48% (~570us versus ~270us) across frame sizes from 76 to 1518 bytes in uplink and bidirectional traffic, indicating delayed RX servicing under sparse traffic conditions. To address this issue, reduce the RX SRNG interrupt timer threshold from 500us to 200us so that received packets are serviced more promptly under low-rate and intermittent RX traffic. With this change, single-client single-stream TCP throughput in EHT160 is restored to expected levels ~600 Mbps TCP DL/UL and UDP UL end-to-end latency in VHT80 returns to baseline values ~270us across all tested frame sizes. Under high RX load, no throughput regression is observed, as RX rings are already serviced frequently. The primary implication is a modest increase in RX interrupt frequency under low traffic, with no observed functional, stability, or performance regressions on tested platforms. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01181-QCAHKSWPL_SILICONZ-1 Signed-off-by: Thiraviyam Mariyappan Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260622062324.758533-1-thiraviyam.mariyappan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/hal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index ba2c22fb2982..3a874db7968e 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -1024,7 +1024,7 @@ enum hal_wbm_rel_desc_type { /* Interrupt mitigation - timer threshold in us */ #define HAL_SRNG_INT_TIMER_THRESHOLD_TX 1000 -#define HAL_SRNG_INT_TIMER_THRESHOLD_RX 500 +#define HAL_SRNG_INT_TIMER_THRESHOLD_RX 200 #define HAL_SRNG_INT_TIMER_THRESHOLD_OTHER 256 enum hal_srng_mac_type { From 9eb29fd47595e8128775f8ac57ca671238cb798a Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Sun, 28 Jun 2026 23:15:28 -0700 Subject: [PATCH 32/58] wifi: ath12k: fix rx_mpdu_start layout for QCC2072 QCC2072's rx_mpdu_start TLV has a different field layout from QCN9274. Reusing struct rx_mpdu_start_qcn9274 in hal_rx_desc_qcc2072 causes the RX datapath to read the wrong offsets for info2, info4, pn[] and phy_ppdu_id, producing corrupted sequence number, PN, ppdu_id and mpdu-info flags (encrypted, fragment, addr2/addr4 valid). Add a dedicated struct rx_mpdu_start_qcc2072 that matches the actual hardware descriptor layout, and use it in hal_rx_desc_qcc2072. Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00188-QCACOLSWPL_V1_TO_SILICONZ-1 Fixes: 28badc78142e ("wifi: ath12k: add HAL descriptor and ops for QCC2072") Signed-off-by: Wei Zhang Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260629061529.1993932-1-wei.zhang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- .../wireless/ath/ath12k/wifi7/hal_rx_desc.h | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h b/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h index 0d19a9cbb68c..6d69851e529d 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h @@ -140,6 +140,38 @@ struct rx_mpdu_start_qcn9274 { __le32 res1; } __packed; +struct rx_mpdu_start_qcc2072 { + __le32 info0; + __le32 info2; + __le32 reo_queue_desc_lo; + __le32 info1; + __le32 pn[4]; + __le32 info4; + __le32 peer_meta_data; + __le16 ast_index; + __le16 sw_peer_id; + __le16 info3; + __le16 phy_ppdu_id; + __le32 info5; + __le32 info6; + __le16 frame_ctrl; + __le16 duration; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 addr3[ETH_ALEN]; + __le16 seq_ctrl; + u8 addr4[ETH_ALEN]; + __le16 qos_ctrl; + __le32 ht_ctrl; + __le32 info7; + __le32 res0; + __le32 res1; + __le32 res2; + __le32 info8; + __le32 res3; + __le32 res4; +} __packed; + #define QCN9274_MPDU_START_SELECT_MPDU_START_TAG BIT(0) #define QCN9274_MPDU_START_SELECT_INFO0_REO_QUEUE_DESC_LO BIT(1) #define QCN9274_MPDU_START_SELECT_INFO1_PN_31_0 BIT(2) @@ -1492,7 +1524,7 @@ struct hal_rx_desc_qcc2072 { struct rx_msdu_end_qcn9274 msdu_end; u8 rx_padding0[RX_BE_PADDING0_BYTES]; __le32 mpdu_start_tag; - struct rx_mpdu_start_qcn9274 mpdu_start; + struct rx_mpdu_start_qcc2072 mpdu_start; struct rx_pkt_hdr_tlv_qcc2072 pkt_hdr_tlv; u8 msdu_payload[]; }; From b93c1dc446847165fd4cafc3d4d1c67ee6c6e987 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Wed, 1 Jul 2026 09:46:11 +0530 Subject: [PATCH 33/58] wifi: ath12k: add QMI capability negotiation for dynamic memory mode On AHB platforms, firmware operates in two modes: fixed-memory mode where firmware uses hardcoded addresses for memory regions such as BDF and does not request HOST_DDR memory from the host, and dynamic-memory mode where firmware expects the host to provide memory addresses including HOST_DDR after the Q6 read-only region and relies on host allocation for all memory types. Introduce QMI capability negotiation to support both modes. Add a new QMI PHY capability flag dynamic_ddr_support which is advertised by firmware to indicate it supports dynamic memory mode. When the host detects this capability, set the dynamic_mem_support flag in the host capability message to signal the host is ready to provide dynamic memory allocation. This triggers firmware to send the HOST_DDR memory request and use the host-provided address. For backward compatibility, if firmware doesn't advertise dynamic_ddr_support, the firmware continues to operate in fixed-memory mode where firmware uses predefined addresses. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Signed-off-by: Aaradhana Sahu Link: https://patch.msgid.link/20260701041611.3077185-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.c | 49 +++++++++++++++++++++++++-- drivers/net/wireless/ath/ath12k/qmi.h | 8 +++-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index 1f3efcc16ac3..cabdd544bbc7 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -506,6 +506,24 @@ static const struct qmi_elem_info qmi_wlanfw_host_cap_req_msg_v01_ei[] = { .offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01, feature_list), }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x33, + .offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01, + dynamic_mem_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x33, + .offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01, + dynamic_mem_support), + }, { .data_type = QMI_EOTI, .array_type = NO_ARRAY, @@ -602,6 +620,24 @@ static const struct qmi_elem_info qmi_wlanfw_phy_cap_resp_msg_v01_ei[] = { .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, single_chip_mlo_support), }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + dynamic_ddr_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01, + dynamic_ddr_support), + }, { .data_type = QMI_EOTI, .array_type = NO_ARRAY, @@ -2254,6 +2290,11 @@ int ath12k_qmi_host_cap_send(struct ath12k_base *ab) if (ret < 0) goto out; + if (ab->qmi.dynamic_ddr_support) { + req.dynamic_mem_support_valid = 1; + req.dynamic_mem_support = 1; + } + ret = qmi_txn_init(&ab->qmi.handle, &txn, qmi_wlanfw_host_cap_resp_msg_v01_ei, &resp); if (ret < 0) @@ -2325,11 +2366,15 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab) ab->qmi.num_radios = resp.num_phy; + if (resp.dynamic_ddr_support_valid) + ab->qmi.dynamic_ddr_support = resp.dynamic_ddr_support; + ath12k_dbg(ab, ATH12K_DBG_QMI, - "phy capability resp valid %u single_chip_mlo_support %u valid %u num_phy %u valid %u board_id %u\n", + "phy capability resp valid %u single_chip_mlo_support %u valid %u num_phy %u valid %u board_id %u dynamic_ddr_valid %u dynamic_ddr_support %u\n", resp.single_chip_mlo_support_valid, resp.single_chip_mlo_support, resp.num_phy_valid, resp.num_phy, - resp.board_id_valid, resp.board_id); + resp.board_id_valid, resp.board_id, resp.dynamic_ddr_support_valid, + resp.dynamic_ddr_support); return; diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index 80a9b42a2548..cbe5be30053a 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -153,9 +153,10 @@ struct ath12k_qmi { struct m3_mem_region aux_uc_mem; unsigned int service_ins_id; struct dev_mem_info dev_mem[ATH12K_QMI_WLFW_MAX_DEV_MEM_NUM_V01]; + u8 dynamic_ddr_support; }; -#define QMI_WLANFW_HOST_CAP_REQ_MSG_V01_MAX_LEN 261 +#define QMI_WLANFW_HOST_CAP_REQ_MSG_V01_MAX_LEN 265 #define QMI_WLANFW_HOST_CAP_REQ_V01 0x0034 #define QMI_WLFW_MAX_NUM_GPIO_V01 32 #define QMI_WLANFW_MAX_PLATFORM_NAME_LEN_V01 64 @@ -253,7 +254,8 @@ struct qmi_wlanfw_host_cap_req_msg_v01 { struct wlfw_host_mlo_chip_info_s_v01 mlo_chip_info[QMI_WLFW_MAX_NUM_MLO_CHIPS_V01]; u8 feature_list_valid; u64 feature_list; - + u8 dynamic_mem_support_valid; + u8 dynamic_mem_support; }; struct qmi_wlanfw_host_cap_resp_msg_v01 { @@ -274,6 +276,8 @@ struct qmi_wlanfw_phy_cap_resp_msg_v01 { u32 board_id; u8 single_chip_mlo_support_valid; u8 single_chip_mlo_support; + u8 dynamic_ddr_support_valid; + u8 dynamic_ddr_support; }; #define QMI_WLANFW_IND_REGISTER_REQ_MSG_V01_MAX_LEN 54 From 12b09e478aa7459b7893a695ef77682202f2da83 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Wed, 1 Jul 2026 09:49:13 +0800 Subject: [PATCH 34/58] wifi: ath11k: cap out-of-range rx MCS instead of leaving bogus rate ath11k can receive HT/VHT/HE frames whose reported MCS is above the maximum that can be expressed in the corresponding mac80211 rate space (e.g. an HE frame reported with MCS 12, while HE tops out at MCS 11). The frame itself is valid and decodes correctly, but for such a frame ath11k_dp_rx_h_rate() leaves rx_status->rate_idx set to the out-of-range value and never assigns rx_status->encoding, so it stays RX_ENC_LEGACY from the ath11k_dp_rx_h_ppdu() initialization. Once that frame reaches mac80211 it trips the rate sanity check and the frame is dropped with a splat: ath11k_pci 0000:03:00.0: Received with invalid mcs in HE mode 12 WARNING: CPU: 0 PID: 0 at net/mac80211/rx.c:5433 ieee80211_rx_list+0xb0a/0xe90 [mac80211] Dropping the frame would discard otherwise valid data, so instead cap the reported MCS to the maximum the rate space can express and deliver the frame. Set rx_status->encoding before the range check and assign rate_idx from the capped value, so a frame with an out-of-range MCS no longer leaves partial or bogus rate metadata behind. Also downgrade the logging level since they are not treated as invalid frames now. The only loss is that such a frame is reported as the capped MCS in the rx rate statistics. Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.41 Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260701-ath11k-invalid-he-mcs-v1-1-7d963080c079@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/dp_rx.c | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c index 9e90d8e3f155..896d30181754 100644 --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -2334,10 +2334,10 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc, case RX_MSDU_START_PKT_TYPE_11N: rx_status->encoding = RX_ENC_HT; if (rate_mcs > ATH11K_HT_MCS_MAX) { - ath11k_warn(ar->ab, - "Received with invalid mcs in HT mode %d\n", - rate_mcs); - break; + ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX, + "Received HT frame with out-of-range mcs %d, capping to %d\n", + rate_mcs, ATH11K_HT_MCS_MAX); + rate_mcs = ATH11K_HT_MCS_MAX; } rx_status->rate_idx = rate_mcs + (8 * (nss - 1)); if (sgi) @@ -2346,13 +2346,13 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc, break; case RX_MSDU_START_PKT_TYPE_11AC: rx_status->encoding = RX_ENC_VHT; - rx_status->rate_idx = rate_mcs; if (rate_mcs > ATH11K_VHT_MCS_MAX) { - ath11k_warn(ar->ab, - "Received with invalid mcs in VHT mode %d\n", - rate_mcs); - break; + ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX, + "Received VHT frame with out-of-range mcs %d, capping to %d\n", + rate_mcs, ATH11K_VHT_MCS_MAX); + rate_mcs = ATH11K_VHT_MCS_MAX; } + rx_status->rate_idx = rate_mcs; rx_status->nss = nss; if (sgi) rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; @@ -2362,14 +2362,14 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc, rx_status->enc_flags |= RX_ENC_FLAG_LDPC; break; case RX_MSDU_START_PKT_TYPE_11AX: - rx_status->rate_idx = rate_mcs; - if (rate_mcs > ATH11K_HE_MCS_MAX) { - ath11k_warn(ar->ab, - "Received with invalid mcs in HE mode %d\n", - rate_mcs); - break; - } rx_status->encoding = RX_ENC_HE; + if (rate_mcs > ATH11K_HE_MCS_MAX) { + ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX, + "Received HE frame with out-of-range mcs %d, capping to %d\n", + rate_mcs, ATH11K_HE_MCS_MAX); + rate_mcs = ATH11K_HE_MCS_MAX; + } + rx_status->rate_idx = rate_mcs; rx_status->nss = nss; rx_status->he_gi = ath11k_mac_he_gi_to_nl80211_he_gi(sgi); rx_status->bw = ath11k_mac_bw_to_mac80211_bw(bw); From be72d6aecea491ee202de81fc3a71c7ea9d34d2b Mon Sep 17 00:00:00 2001 From: Tamizh Chelvam Raja Date: Wed, 1 Jul 2026 23:54:28 +0530 Subject: [PATCH 35/58] wifi: ath12k: Set IEEE80211_OFFLOAD_ENCAP_4ADDR after tx_encap_type vdev param Currently, IEEE80211_OFFLOAD_ENCAP_4ADDR is set when IEEE80211_OFFLOAD_ENCAP_ENABLED is present in vif->offload_flags at the beginning of ath12k_mac_update_vif_offload(). However, if the WMI vdev set_param for tx_encap_type fails, IEEE80211_OFFLOAD_ENCAP_ENABLED is cleared but IEEE80211_OFFLOAD_ENCAP_4ADDR remains set, leaving the flags in an inconsistent state. Fix this by setting IEEE80211_OFFLOAD_ENCAP_4ADDR only after the tx_encap_type has been configured via the WMI vdev set parameter. Compile tested only. Fixes: 729cad3c3c9e ("wifi: ath12k: Add 4-address mode support for eth offload") Signed-off-by: Tamizh Chelvam Raja Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260701182428.906441-1-tamizh.raja@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 6667019a0049..d7e93a20ca36 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -10118,16 +10118,15 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif) vif->type != NL80211_IFTYPE_AP) vif->offload_flags &= ~(IEEE80211_OFFLOAD_ENCAP_ENABLED | IEEE80211_OFFLOAD_DECAP_ENABLED | - IEEE80211_OFFLOAD_ENCAP_MCAST); + IEEE80211_OFFLOAD_ENCAP_MCAST | + IEEE80211_OFFLOAD_ENCAP_4ADDR); - if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) { + if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_ETHERNET; - vif->offload_flags |= IEEE80211_OFFLOAD_ENCAP_4ADDR; - } else if (test_bit(ATH12K_FLAG_RAW_MODE, &ab->dev_flags)) { + else if (test_bit(ATH12K_FLAG_RAW_MODE, &ab->dev_flags)) ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_RAW; - } else { + else ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_NATIVE_WIFI; - } ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id, param_id, ahvif->dp_vif.tx_encap_type); @@ -10138,7 +10137,8 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif) } if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) - vif->offload_flags |= IEEE80211_OFFLOAD_ENCAP_MCAST; + vif->offload_flags |= (IEEE80211_OFFLOAD_ENCAP_MCAST | + IEEE80211_OFFLOAD_ENCAP_4ADDR); param_id = WMI_VDEV_PARAM_RX_DECAP_TYPE; if (vif->offload_flags & IEEE80211_OFFLOAD_DECAP_ENABLED) From f78210dd4bea09090b828adc2733cad4b9916306 Mon Sep 17 00:00:00 2001 From: Manikanta Pubbisetty Date: Fri, 10 Jul 2026 11:34:06 +0530 Subject: [PATCH 36/58] wifi: ath10k: trigger hardware recovery upon rx failures When an error occurs during RX packet processing (e.g., MSDU done failure), the driver sets rx_confused and drops all subsequent RX packets until a Wi-Fi ON/OFF cycle clears the flag. This can leave the device in a bad state where it cannot process RX data traffic. Instead of leaving the device in such a state, trigger hardware recovery so that such an error state can be reset and the device can function again normally. Tested-on: WCN3990 hw1.0 WLAN.HL.3.2.2.c10-00754-QCAHLSWMTPL-1 Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00288-QCARMSWPZ-1 Tested-on: QCA6174 hw3.2 SDIO WLAN.RMH.4.4.1-00189 Signed-off-by: Manikanta Pubbisetty Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260710060406.3323260-1-manikanta.pubbisetty@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/htt_rx.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/htt_rx.c b/drivers/net/wireless/ath/ath10k/htt_rx.c index faac359aa9ac..1005daaaf158 100644 --- a/drivers/net/wireless/ath/ath10k/htt_rx.c +++ b/drivers/net/wireless/ath/ath10k/htt_rx.c @@ -2343,10 +2343,8 @@ static int ath10k_htt_rx_handle_amsdu(struct ath10k_htt *htt) if (ret < 0) { ath10k_warn(ar, "rx ring became corrupted: %d\n", ret); __skb_queue_purge(&amsdu); - /* FIXME: It's probably a good idea to reboot the - * device instead of leaving it inoperable. - */ htt->rx_confused = true; + ath10k_core_start_recovery(ar); return ret; } @@ -3311,6 +3309,7 @@ static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) if (ret < 0) { ath10k_warn(ar, "failed to pop paddr list: %d\n", ret); htt->rx_confused = true; + ath10k_core_start_recovery(ar); return -EIO; } @@ -3344,6 +3343,7 @@ static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) ath10k_warn(ar, "failed to extract amsdu: %d\n", ret); htt->rx_confused = true; __skb_queue_purge(&list); + ath10k_core_start_recovery(ar); return -EIO; } } From f8729a40ebcc94d164be8f154483a21669f3ab52 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 09:10:40 +0800 Subject: [PATCH 37/58] wifi: ath11k: validate regulatory capability phy_id ath11k_wmi_tlv_ext_hal_reg_caps() copies firmware regulatory capability records into soc->hal_reg_cap[] using reg_cap.phy_id as the destination index. The loop count is bounded by num_phy, but the phy_id embedded in each record is not checked against the fixed MAX_RADIOS-sized destination array. Reject firmware records whose phy_id does not fit soc->hal_reg_cap[] before copying the parsed capability. Signed-off-by: Pengpeng Hou Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260704011040.26233-1-pengpeng@iscas.ac.cn Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index dca6e011cc40..28acfc6829df 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -4858,6 +4858,12 @@ static int ath11k_wmi_tlv_ext_hal_reg_caps(struct ath11k_base *soc, return ret; } + if (reg_cap.phy_id >= ARRAY_SIZE(soc->hal_reg_cap)) { + ath11k_warn(soc, "invalid reg cap phy_id %u\n", + reg_cap.phy_id); + return -EINVAL; + } + memcpy(&soc->hal_reg_cap[reg_cap.phy_id], ®_cap, sizeof(reg_cap)); } From ff651212f2e237d790a5b36deef9d332360dc272 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Sat, 4 Jul 2026 15:30:00 +0800 Subject: [PATCH 38/58] wifi: ath12k: fix ML-STA authentication timeout on QCC2072 QCC2072 firmware interprets the MLO_LINK_ADD and MLO_START_AS_ACTIVE flags to control the link state during MLO vdev start. MLO_LINK_ADD indicates that a link is being added, while MLO_START_AS_ACTIVE specifies that the link should become active during the start. When an association link is added without setting MLO_START_AS_ACTIVE, the firmware may transition the link into a suspended state. In this case, authentication frames transmitted by the host can be dropped, leading to repeated authentication retries and eventual timeout, for example: wlp1s0: send auth to (try 1/3) wlp1s0: send auth to (try 2/3) wlp1s0: send auth to (try 3/3) wlp1s0: authentication with timed out Avoid triggering this behavior by setting the MLO_START_AS_ACTIVE flag when MLO_ASSOC_LINK is set, which tells the firmware that the current vdev must not enter suspend mode Note that this change relies on firmware behavior observed on the QCC2072 platform. The firmware on WCN7850 and QCN9274 does not use the MLO_START_AS_ACTIVE flag, so this change is effectively a no-op on those platforms Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00068-QCACOLSWPL_V1_TO_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: d8e1f4a19310 ("wifi: ath12k: enable QCC2072 support") Signed-off-by: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260704073000.3300099-1-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 2 ++ drivers/net/wireless/ath/ath12k/wmi.h | 1 + 2 files changed, 3 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index da14cfda0528..614e02dbb6f9 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -1229,6 +1229,8 @@ int ath12k_wmi_vdev_start(struct ath12k *ar, struct wmi_vdev_start_req_arg *arg, ATH12K_WMI_FLAG_MLO_MCAST_VDEV) | le32_encode_bits(arg->ml.link_add, ATH12K_WMI_FLAG_MLO_LINK_ADD) | + le32_encode_bits(arg->ml.assoc_link, + ATH12K_WMI_FLAG_MLO_START_AS_ACTIVE) | cpu_to_le32(ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID); ml_params->ieee_link_id = cpu_to_le32(arg->ml.ieee_link_id); diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 51f3426e1fcd..20e3939e8820 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2954,6 +2954,7 @@ struct wmi_vdev_create_mlo_params { #define ATH12K_WMI_FLAG_MLO_EMLSR_SUPPORT BIT(6) #define ATH12K_WMI_FLAG_MLO_FORCED_INACTIVE BIT(7) #define ATH12K_WMI_FLAG_MLO_LINK_ADD BIT(8) +#define ATH12K_WMI_FLAG_MLO_START_AS_ACTIVE BIT(17) #define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID BIT(18) #define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER BIT(19) From 408ec3ffcc766d540bc58120f59c8cc1343f2ed4 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Fri, 10 Jul 2026 11:05:34 +0530 Subject: [PATCH 39/58] wifi: ath12k: update IPQ5332 BDF address offset In the ath12k driver, the BDF_MEM_REGION_TYPE address is derived by adding a fixed bdf_addr_offset to the WCSS Q6 region base address. The current offset (0xC00000) works only when the Q6 region contains the IPQ5332 ucode alone. On some IPQ5332 platform variants, additional devices share the same WCSS Q6 processor and place their firmware ucode in the same Q6 region. This results in multiple ucode sections within the region, and the existing offset can cause the BDF memory region to overlap with firmware read-only sections, which can lead to firmware crash and driver boot failure. Increase the bdf_addr_offset to 0x1A00000, determined by analyzing firmware memory maps across all known IPQ5332 platform variants. This value represents the upper bound of the largest combined firmware and ensures all IPQ5332 variants can allocate the BDF region safely without overlapping firmware regions. 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/20260710053534.879233-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index d54c2a6d83b2..8bc841dbac00 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -689,7 +689,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .ce_ie_addr = &ath12k_wifi7_ce_ie_addr_ipq5332, .ce_remap = &ath12k_wifi7_ce_remap_ipq5332, - .bdf_addr_offset = 0xC00000, + .bdf_addr_offset = 0x1A00000, .dp_primary_link_only = true, .client = { From 007875346097ab22d0040bd61f7bee3e92321fff Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 5 Jul 2026 19:24:06 +0200 Subject: [PATCH 40/58] wifi: ath10k: Drop redundant NULL check on devm_clk_get() devm_clk_get() does not return NULL (only valid clock or ERR pointer), so simplify the code to drop redundant IS_ERR_OR_NULL(). Signed-off-by: Krzysztof Kozlowski Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260705172405.119084-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/ahb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/ahb.c b/drivers/net/wireless/ath/ath10k/ahb.c index eb8b35b6224d..7456f885d2b5 100644 --- a/drivers/net/wireless/ath/ath10k/ahb.c +++ b/drivers/net/wireless/ath/ath10k/ahb.c @@ -87,24 +87,24 @@ static int ath10k_ahb_clock_init(struct ath10k *ar) dev = &ar_ahb->pdev->dev; ar_ahb->cmd_clk = devm_clk_get(dev, "wifi_wcss_cmd"); - if (IS_ERR_OR_NULL(ar_ahb->cmd_clk)) { + if (IS_ERR(ar_ahb->cmd_clk)) { ath10k_err(ar, "failed to get cmd clk: %ld\n", PTR_ERR(ar_ahb->cmd_clk)); - return ar_ahb->cmd_clk ? PTR_ERR(ar_ahb->cmd_clk) : -ENODEV; + return PTR_ERR(ar_ahb->cmd_clk); } ar_ahb->ref_clk = devm_clk_get(dev, "wifi_wcss_ref"); - if (IS_ERR_OR_NULL(ar_ahb->ref_clk)) { + if (IS_ERR(ar_ahb->ref_clk)) { ath10k_err(ar, "failed to get ref clk: %ld\n", PTR_ERR(ar_ahb->ref_clk)); - return ar_ahb->ref_clk ? PTR_ERR(ar_ahb->ref_clk) : -ENODEV; + return PTR_ERR(ar_ahb->ref_clk); } ar_ahb->rtc_clk = devm_clk_get(dev, "wifi_wcss_rtc"); - if (IS_ERR_OR_NULL(ar_ahb->rtc_clk)) { + if (IS_ERR(ar_ahb->rtc_clk)) { ath10k_err(ar, "failed to get rtc clk: %ld\n", PTR_ERR(ar_ahb->rtc_clk)); - return ar_ahb->rtc_clk ? PTR_ERR(ar_ahb->rtc_clk) : -ENODEV; + return PTR_ERR(ar_ahb->rtc_clk); } return 0; From f57314aade9d74d30f3360ec5ef85a83654748be Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sat, 11 Jul 2026 11:04:43 -0700 Subject: [PATCH 41/58] wifi: ath6kl: avoid buffer overreads in WMI event handlers The following WMI event handlers currently read from the event buffer without first verifying that the message was large enough to hold the expected event: ath6kl_wmi_scan_complete_rx() ath6kl_wmi_addba_req_event_rx() ath6kl_wmi_delba_req_event_rx() Add length checks to prevent overread. Fixes: bdcd81707973 ("Add ath6kl cleaned up driver") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260711-ath6kl_wmi_scan_complete_rx-v2-1-22dc0f7f45e7@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/wmi.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath6kl/wmi.c b/drivers/net/wireless/ath/ath6kl/wmi.c index 72611a2ceb9d..c0c455d6e2dc 100644 --- a/drivers/net/wireless/ath/ath6kl/wmi.c +++ b/drivers/net/wireless/ath/ath6kl/wmi.c @@ -1276,6 +1276,9 @@ static int ath6kl_wmi_scan_complete_rx(struct wmi *wmi, u8 *datap, int len, { struct wmi_scan_complete_event *ev; + if (len < sizeof(*ev)) + return -EINVAL; + ev = (struct wmi_scan_complete_event *) datap; ath6kl_scan_complete_evt(vif, a_sle32_to_cpu(ev->status)); @@ -3352,7 +3355,12 @@ static int ath6kl_wmi_get_pmkid_list_event_rx(struct wmi *wmi, u8 *datap, static int ath6kl_wmi_addba_req_event_rx(struct wmi *wmi, u8 *datap, int len, struct ath6kl_vif *vif) { - struct wmi_addba_req_event *cmd = (struct wmi_addba_req_event *) datap; + struct wmi_addba_req_event *cmd; + + if (len < sizeof(*cmd)) + return -EINVAL; + + cmd = (struct wmi_addba_req_event *)datap; aggr_recv_addba_req_evt(vif, cmd->tid, le16_to_cpu(cmd->st_seq_no), cmd->win_sz); @@ -3363,7 +3371,12 @@ static int ath6kl_wmi_addba_req_event_rx(struct wmi *wmi, u8 *datap, int len, static int ath6kl_wmi_delba_req_event_rx(struct wmi *wmi, u8 *datap, int len, struct ath6kl_vif *vif) { - struct wmi_delba_event *cmd = (struct wmi_delba_event *) datap; + struct wmi_delba_event *cmd; + + if (len < sizeof(*cmd)) + return -EINVAL; + + cmd = (struct wmi_delba_event *)datap; aggr_recv_delba_req_evt(vif, cmd->tid); From e23d90cfcbe2ed9bf0662e478ab3985a852aefc4 Mon Sep 17 00:00:00 2001 From: Thiraviyam Mariyappan Date: Mon, 22 Jun 2026 11:56:14 +0530 Subject: [PATCH 42/58] wifi: ath12k: Set congestion control max MSDU count Currently when running 128 clients UDP DL test in 5 GHz HE80 (NSS 2x2), firmware uses the default max MSDU count (16K MSDUs). This lower limit causes the firmware to compute a smaller TQM drop threshold, aggregate packets at a reduced rate, and results in increased packet drops with TQM drop threshold as the completion reason. To fix this issue, set WMI_PDEV_PARAM_SET_CONG_CTRL_MAX_MSDUS to the TX descriptor count using ath12k_wmi_pdev_set_param(). This increases the TQM drop threshold, reduces drop events, and improves throughput from ~722 Mbps to ~1060 Mbps with 1200 Mbps ingress. Add a new HW capability flag (supports_cong_ctrl_max_msdus) and enable the WMI parameter only on supported platforms. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01181-QCAHKSWPL_SILICONZ-1 Signed-off-by: Thiraviyam Mariyappan Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260622062614.760166-1-thiraviyam.mariyappan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/hw.h | 1 + drivers/net/wireless/ath/ath12k/mac.c | 13 +++++++++++++ drivers/net/wireless/ath/ath12k/wifi7/hw.c | 6 ++++++ drivers/net/wireless/ath/ath12k/wmi.h | 1 + 4 files changed, 21 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h index d135b2936378..ad62f93441b3 100644 --- a/drivers/net/wireless/ath/ath12k/hw.h +++ b/drivers/net/wireless/ath/ath12k/hw.h @@ -192,6 +192,7 @@ struct ath12k_hw_params { bool supports_shadow_regs:1; bool supports_aspm:1; bool current_cc_support:1; + bool supports_cong_ctrl_max_msdus:1; u32 num_tcl_banks; u32 max_tx_ring; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index d7e93a20ca36..5e4798ae8e99 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -9722,6 +9722,19 @@ static int ath12k_mac_start(struct ath12k *ar) goto err; } + if (ab->hw_params->supports_cong_ctrl_max_msdus) { + ret = ath12k_wmi_pdev_set_param(ar, + WMI_PDEV_PARAM_SET_CONG_CTRL_MAX_MSDUS, + ATH12K_NUM_POOL_TX_DESC(ab), + pdev->pdev_id); + if (ret) { + ath12k_err(ab, + "failed to set congestion control MAX MSDUS: %d\n", + ret); + goto err; + } + } + __ath12k_set_antenna(ar, ar->cfg_tx_chainmask, ar->cfg_rx_chainmask); /* TODO: Do we need to enable ANI? */ diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index 8bc841dbac00..1ab1168510aa 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -390,6 +390,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_MESH_POINT) | BIT(NL80211_IFTYPE_AP_VLAN), .supports_monitor = false, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, @@ -480,6 +481,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = false, .idle_ps = true, .download_calib = false, @@ -568,6 +570,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_MESH_POINT) | BIT(NL80211_IFTYPE_AP_VLAN), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, @@ -654,6 +657,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_AP) | BIT(NL80211_IFTYPE_MESH_POINT), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, @@ -738,6 +742,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = false, .idle_ps = true, .download_calib = false, @@ -826,6 +831,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { BIT(NL80211_IFTYPE_AP) | BIT(NL80211_IFTYPE_MESH_POINT), .supports_monitor = true, + .supports_cong_ctrl_max_msdus = true, .idle_ps = false, .download_calib = true, diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 20e3939e8820..c813b2848b5b 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -1083,6 +1083,7 @@ enum wmi_tlv_pdev_param { WMI_PDEV_PARAM_RADIO_CHAN_STATS_ENABLE, WMI_PDEV_PARAM_RADIO_DIAGNOSIS_ENABLE, WMI_PDEV_PARAM_MESH_MCAST_ENABLE, + WMI_PDEV_PARAM_SET_CONG_CTRL_MAX_MSDUS = 0xa6, WMI_PDEV_PARAM_SET_CMD_OBSS_PD_THRESHOLD = 0xbc, WMI_PDEV_PARAM_SET_CMD_OBSS_PD_PER_AC = 0xbe, WMI_PDEV_PARAM_ENABLE_SR_PROHIBIT = 0xc6, From 3fe59edd1901c040e5b8e9d2428bf9ec6b4ce630 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 30 Jun 2026 11:50:46 +0530 Subject: [PATCH 43/58] wifi: ath12k: switch to name-based reserved memory lookup The driver currently retrieves reserved memory regions using index-based lookup, which depends on the ordering of reserved-memory nodes in the device tree. Since different platforms define these regions in varying orders and combinations, this approach is not compatible and can result in incorrect memory region access. Switch to looking up memory regions by name instead of index so it does not depend on node order. Use names already defined in qcom,ipq5332-wifi.yaml, so there are no backward compatibility issues. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Signed-off-by: Aaradhana Sahu Link: https://patch.msgid.link/20260630062048.1615178-2-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ahb.c | 18 ++++++------ drivers/net/wireless/ath/ath12k/core.c | 25 ----------------- drivers/net/wireless/ath/ath12k/core.h | 2 -- drivers/net/wireless/ath/ath12k/qmi.c | 38 +++++++++++++------------- 4 files changed, 29 insertions(+), 54 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 4944ea7855ca..07bb83710b1f 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "ahb.h" #include "debug.h" #include "hif.h" @@ -337,24 +338,25 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab) char fw2_name[ATH12K_USERPD_FW_NAME_LEN]; struct device *dev = ab->dev; const struct firmware *fw, *fw2; - struct reserved_mem *rmem = NULL; unsigned long time_left; phys_addr_t mem_phys; + struct resource res; void *mem_region; size_t mem_size; u32 pasid; int ret; - rmem = ath12k_core_get_reserved_mem(ab, 0); - if (!rmem) - return -ENODEV; + ret = of_reserved_mem_region_to_resource_byname(dev->of_node, "q6-region", + &res); + if (ret) + return ret; - mem_phys = rmem->base; - mem_size = rmem->size; + mem_phys = res.start; + mem_size = resource_size(&res); mem_region = devm_memremap(dev, mem_phys, mem_size, MEMREMAP_WC); if (IS_ERR(mem_region)) { - ath12k_err(ab, "unable to map memory region: %pa+%pa\n", - &rmem->base, &rmem->size); + ath12k_err(ab, "unable to map memory region: %pa+%zx\n", + &res.start, mem_size); return PTR_ERR(mem_region); } diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index fb599caa3aab..a9112760185f 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -637,31 +637,6 @@ u32 ath12k_core_get_max_peers_per_radio(struct ath12k_base *ab) } EXPORT_SYMBOL(ath12k_core_get_max_peers_per_radio); -struct reserved_mem *ath12k_core_get_reserved_mem(struct ath12k_base *ab, - int index) -{ - struct device *dev = ab->dev; - struct reserved_mem *rmem; - struct device_node *node; - - node = of_parse_phandle(dev->of_node, "memory-region", index); - if (!node) { - ath12k_dbg(ab, ATH12K_DBG_BOOT, - "failed to parse memory-region for index %d\n", index); - return NULL; - } - - rmem = of_reserved_mem_lookup(node); - of_node_put(node); - if (!rmem) { - ath12k_dbg(ab, ATH12K_DBG_BOOT, - "unable to get memory-region for index %d\n", index); - return NULL; - } - - return rmem; -} - static inline void ath12k_core_to_group_ref_get(struct ath12k_base *ab) { diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 1436ff4316e7..f9fb1399e0b6 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -1295,8 +1295,6 @@ void ath12k_fw_stats_init(struct ath12k *ar); void ath12k_fw_stats_bcn_free(struct list_head *head); void ath12k_fw_stats_free(struct ath12k_fw_stats *stats); void ath12k_fw_stats_reset(struct ath12k *ar); -struct reserved_mem *ath12k_core_get_reserved_mem(struct ath12k_base *ab, - int index); enum ath12k_qmi_mem_mode ath12k_core_get_memory_mode(struct ath12k_base *ab); static inline const char *ath12k_scan_state_str(enum ath12k_scan_state state) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index cabdd544bbc7..7abd93766918 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -13,6 +13,7 @@ #include #include #include +#include #define SLEEP_CLOCK_SELECT_INTERNAL_BIT 0x02 #define HOST_CSTATE_BIT 0x04 @@ -2778,20 +2779,20 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) { - struct reserved_mem *rmem; + struct device_node *np = ab->dev->of_node; size_t avail_rmem_size; + struct resource res; int i, idx, ret; for (i = 0, idx = 0; i < ab->qmi.mem_seg_count; i++) { switch (ab->qmi.target_mem[i].type) { case HOST_DDR_REGION_TYPE: - rmem = ath12k_core_get_reserved_mem(ab, 0); - if (!rmem) { - ret = -ENODEV; + ret = of_reserved_mem_region_to_resource_byname(np, "q6-region", + &res); + if (ret) goto out; - } - avail_rmem_size = rmem->size; + avail_rmem_size = resource_size(&res); if (avail_rmem_size < ab->qmi.target_mem[i].size) { ath12k_dbg(ab, ATH12K_DBG_QMI, "failed to assign mem type %u req size %u avail size %zu\n", @@ -2802,7 +2803,7 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) goto out; } - ab->qmi.target_mem[idx].paddr = rmem->base; + ab->qmi.target_mem[idx].paddr = res.start; ab->qmi.target_mem[idx].v.ioaddr = ioremap(ab->qmi.target_mem[idx].paddr, ab->qmi.target_mem[i].size); @@ -2815,13 +2816,13 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) idx++; break; case BDF_MEM_REGION_TYPE: - rmem = ath12k_core_get_reserved_mem(ab, 0); - if (!rmem) { - ret = -ENODEV; + ret = of_reserved_mem_region_to_resource_byname(np, "q6-region", + &res); + if (ret) goto out; - } - avail_rmem_size = rmem->size - ab->hw_params->bdf_addr_offset; + avail_rmem_size = resource_size(&res) - + ab->hw_params->bdf_addr_offset; if (avail_rmem_size < ab->qmi.target_mem[i].size) { ath12k_dbg(ab, ATH12K_DBG_QMI, "failed to assign mem type %u req size %u avail size %zu\n", @@ -2832,7 +2833,7 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) goto out; } ab->qmi.target_mem[idx].paddr = - rmem->base + ab->hw_params->bdf_addr_offset; + res.start + ab->hw_params->bdf_addr_offset; ab->qmi.target_mem[idx].v.ioaddr = ioremap(ab->qmi.target_mem[idx].paddr, ab->qmi.target_mem[i].size); @@ -2857,13 +2858,12 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) idx++; break; case M3_DUMP_REGION_TYPE: - rmem = ath12k_core_get_reserved_mem(ab, 1); - if (!rmem) { - ret = -EINVAL; + ret = of_reserved_mem_region_to_resource_byname(np, "m3-dump", + &res); + if (ret) goto out; - } - avail_rmem_size = rmem->size; + avail_rmem_size = resource_size(&res); if (avail_rmem_size < ab->qmi.target_mem[i].size) { ath12k_dbg(ab, ATH12K_DBG_QMI, "failed to assign mem type %u req size %u avail size %zu\n", @@ -2874,7 +2874,7 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) goto out; } - ab->qmi.target_mem[idx].paddr = rmem->base; + ab->qmi.target_mem[idx].paddr = res.start; ab->qmi.target_mem[idx].v.ioaddr = ioremap(ab->qmi.target_mem[idx].paddr, ab->qmi.target_mem[i].size); From ecb517f97e629d3b8c360cbb5db3fed4d599ea2e Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 30 Jun 2026 11:50:47 +0530 Subject: [PATCH 44/58] wifi: ath12k: refactor QMI memory assignment ath12k_qmi_assign_target_mem_chunk() uses a large switch-case to handle both memory region identification and allocation for each memory request type, leading to redundant allocation logic. Refactor this by introducing ath12k_qmi_get_mem_reg_name() to map memory request types to their corresponding reserved memory region names. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Signed-off-by: Aaradhana Sahu Link: https://patch.msgid.link/20260630062048.1615178-3-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.c | 161 ++++++++++---------------- 1 file changed, 63 insertions(+), 98 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index 7abd93766918..4636aef29300 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -2777,120 +2777,85 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab) return ret; } +static const char *ath12k_qmi_get_mem_reg_name(int mem_type) +{ + switch (mem_type) { + case HOST_DDR_REGION_TYPE: + case BDF_MEM_REGION_TYPE: + return "q6-region"; + case M3_DUMP_REGION_TYPE: + return "m3-dump"; + case CALDB_MEM_REGION_TYPE: + return "q6-caldb"; + case MLO_GLOBAL_MEM_REGION_TYPE: + return "mlo-global-mem"; + default: + return NULL; + } +} + static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) { struct device_node *np = ab->dev->of_node; + struct target_mem_chunk *chunk; size_t avail_rmem_size; struct resource res; + const char *rname; int i, idx, ret; for (i = 0, idx = 0; i < ab->qmi.mem_seg_count; i++) { - switch (ab->qmi.target_mem[i].type) { - case HOST_DDR_REGION_TYPE: - ret = of_reserved_mem_region_to_resource_byname(np, "q6-region", - &res); - if (ret) - goto out; - - avail_rmem_size = resource_size(&res); - if (avail_rmem_size < ab->qmi.target_mem[i].size) { - ath12k_dbg(ab, ATH12K_DBG_QMI, - "failed to assign mem type %u req size %u avail size %zu\n", - ab->qmi.target_mem[i].type, - ab->qmi.target_mem[i].size, - avail_rmem_size); - ret = -EINVAL; - goto out; - } - - ab->qmi.target_mem[idx].paddr = res.start; - ab->qmi.target_mem[idx].v.ioaddr = - ioremap(ab->qmi.target_mem[idx].paddr, - ab->qmi.target_mem[i].size); - if (!ab->qmi.target_mem[idx].v.ioaddr) { - ret = -EIO; - goto out; - } - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; - idx++; - break; - case BDF_MEM_REGION_TYPE: - ret = of_reserved_mem_region_to_resource_byname(np, "q6-region", - &res); - if (ret) - goto out; - - avail_rmem_size = resource_size(&res) - - ab->hw_params->bdf_addr_offset; - if (avail_rmem_size < ab->qmi.target_mem[i].size) { - ath12k_dbg(ab, ATH12K_DBG_QMI, - "failed to assign mem type %u req size %u avail size %zu\n", - ab->qmi.target_mem[i].type, - ab->qmi.target_mem[i].size, - avail_rmem_size); - ret = -EINVAL; - goto out; - } - ab->qmi.target_mem[idx].paddr = - res.start + ab->hw_params->bdf_addr_offset; - ab->qmi.target_mem[idx].v.ioaddr = - ioremap(ab->qmi.target_mem[idx].paddr, - ab->qmi.target_mem[i].size); - if (!ab->qmi.target_mem[idx].v.ioaddr) { - ret = -EIO; - goto out; - } - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; - idx++; - break; - case CALDB_MEM_REGION_TYPE: - /* Cold boot calibration is not enabled in Ath12k. Hence, + chunk = &ab->qmi.target_mem[i]; + if (chunk->type == CALDB_MEM_REGION_TYPE) { + /* + * Cold boot calibration is not enabled in Ath12k. Hence, * assign paddr = 0. * Once cold boot calibration is enabled add support to * assign reserved memory from DT. */ ab->qmi.target_mem[idx].paddr = 0; ab->qmi.target_mem[idx].v.ioaddr = NULL; - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; + ab->qmi.target_mem[idx].size = chunk->size; + ab->qmi.target_mem[idx].type = chunk->type; idx++; - break; - case M3_DUMP_REGION_TYPE: - ret = of_reserved_mem_region_to_resource_byname(np, "m3-dump", - &res); - if (ret) - goto out; - - avail_rmem_size = resource_size(&res); - if (avail_rmem_size < ab->qmi.target_mem[i].size) { - ath12k_dbg(ab, ATH12K_DBG_QMI, - "failed to assign mem type %u req size %u avail size %zu\n", - ab->qmi.target_mem[i].type, - ab->qmi.target_mem[i].size, - avail_rmem_size); - ret = -EINVAL; - goto out; - } - - ab->qmi.target_mem[idx].paddr = res.start; - ab->qmi.target_mem[idx].v.ioaddr = - ioremap(ab->qmi.target_mem[idx].paddr, - ab->qmi.target_mem[i].size); - if (!ab->qmi.target_mem[idx].v.ioaddr) { - ret = -EIO; - goto out; - } - ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size; - ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type; - idx++; - break; - default: - ath12k_warn(ab, "qmi ignore invalid mem req type %u\n", - ab->qmi.target_mem[i].type); - break; + continue; } + + rname = ath12k_qmi_get_mem_reg_name(chunk->type); + if (!rname) { + ath12k_warn(ab, "qmi ignore invalid mem req type %u\n", + chunk->type); + continue; + } + + ret = of_reserved_mem_region_to_resource_byname(np, rname, &res); + if (ret) + goto out; + + avail_rmem_size = resource_size(&res); + if (chunk->type == BDF_MEM_REGION_TYPE) { + avail_rmem_size -= ab->hw_params->bdf_addr_offset; + res.start += ab->hw_params->bdf_addr_offset; + } + + if (avail_rmem_size < chunk->size) { + ath12k_dbg(ab, ATH12K_DBG_QMI, + "failed to assign mem type %u req size %u avail size %zu\n", + chunk->type, chunk->size, avail_rmem_size); + ret = -EINVAL; + goto out; + } + + ab->qmi.target_mem[idx].paddr = res.start; + ab->qmi.target_mem[idx].v.ioaddr = ioremap(ab->qmi.target_mem[idx].paddr, + chunk->size); + if (!ab->qmi.target_mem[idx].v.ioaddr) { + ret = -EIO; + goto out; + } + + ab->qmi.target_mem[idx].size = chunk->size; + ab->qmi.target_mem[idx].type = chunk->type; + idx++; } ab->qmi.mem_seg_count = idx; From 42399be44b13eafb45c56b1c7d7c92107e50c289 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 30 Jun 2026 11:50:48 +0530 Subject: [PATCH 45/58] wifi: ath12k: allocate HOST_DDR and BDF regions after Q6 RO region Currently, the Q6 region contains a read-only firmware region along with the BDF_MEM_REGION_TYPE and HOST_DDR_REGION_TYPE memory areas. The firmware expects these writable memory regions to be assigned after the Q6 read-only section. However, the ath12k driver currently allocates the HOST_DDR_REGION_TYPE starting from the base of the Q6 region, which includes the read-only firmware area. As a result, the allocated memory regions overlap with the read-only section, causing the firmware to assert during QMI memory allocation. The Q6 memory region layout is as follows: Q6 Reserved Memory +--------------------------------------+ | | | Read-only Firmware Region | | (Q6 RO Region) | | | +--------------------------------------+ <--- bdf_addr_offset | Writable Memory Region | | (BDF + HOST_DDR allocations) | | | +--------------------------------------+ Fix this by allocating the required memory regions only after the end of the read-only region in the Q6 address space. The bdf_addr_offset parameter indicates where the writable region starts. Both HOST_DDR and BDF regions are allocated sequentially after this offset, with each region placed immediately after the previous one to avoid gaps and overlaps. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Fixes: 6757079c5890 ("wifi: ath12k: add support for fixed QMI firmware memory") Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Signed-off-by: Aaradhana Sahu Link: https://patch.msgid.link/20260630062048.1615178-4-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index 4636aef29300..bb61c78e5c29 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -2797,8 +2797,8 @@ static const char *ath12k_qmi_get_mem_reg_name(int mem_type) static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) { struct device_node *np = ab->dev->of_node; + size_t avail_rmem_size, offset = 0; struct target_mem_chunk *chunk; - size_t avail_rmem_size; struct resource res; const char *rname; int i, idx, ret; @@ -2832,9 +2832,20 @@ static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab) goto out; avail_rmem_size = resource_size(&res); - if (chunk->type == BDF_MEM_REGION_TYPE) { - avail_rmem_size -= ab->hw_params->bdf_addr_offset; - res.start += ab->hw_params->bdf_addr_offset; + if (chunk->type == BDF_MEM_REGION_TYPE || + chunk->type == HOST_DDR_REGION_TYPE) { + if (ab->hw_params->bdf_addr_offset > avail_rmem_size || + offset > avail_rmem_size - ab->hw_params->bdf_addr_offset) { + ath12k_err(ab, "qmi mem offset overflow: bdf_offset=%u offset=%zu size=%zu\n", + ab->hw_params->bdf_addr_offset, offset, + avail_rmem_size); + ret = -EINVAL; + goto out; + } + + avail_rmem_size -= ab->hw_params->bdf_addr_offset + offset; + res.start += ab->hw_params->bdf_addr_offset + offset; + offset += chunk->size; } if (avail_rmem_size < chunk->size) { From 7b0bd40e97a00991122122d5888ae455fb2bfc7a Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Mon, 13 Jul 2026 09:15:49 -0700 Subject: [PATCH 46/58] wifi: ath12k: Correctly copy the hint BSSID in WMI scan request Currently, in ath12k_wmi_send_scan_start_cmd(), the logic to populate the hint_bssid copies the BSSID in the wrong direction, from the firmware message to the argument buffer. Swap the parameters so that the BSSID is correctly populated in the firmware message from the argument buffer. Compile tested only. Reported-by: Baochen Qiang Closes: https://lore.kernel.org/linux-wireless/afbff608-a005-43c4-af76-968a58bf0cc3@oss.qualcomm.com/ 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/20260713-ath12k_wmi_send_scan_start_cmd-bad-hint_bssid-v1-1-4ffc4a472992@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 614e02dbb6f9..f16ec3d21242 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -2798,8 +2798,8 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, for (i = 0; i < arg->num_hint_bssid; ++i) { hint_bssid->freq_flags = arg->hint_bssid[i].freq_flags; - ether_addr_copy(&arg->hint_bssid[i].bssid.addr[0], - &hint_bssid->bssid.addr[0]); + ether_addr_copy(&hint_bssid->bssid.addr[0], + &arg->hint_bssid[i].bssid.addr[0]); hint_bssid++; } } From 6fe2dddf59bbb2a96be0fcf23a205807b25ac173 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Mon, 13 Jul 2026 09:15:50 -0700 Subject: [PATCH 47/58] wifi: ath11k: Correctly copy the hint BSSID in WMI scan request Currently, in ath11k_wmi_send_scan_start_cmd(), the logic to populate the hint_bssid copies the BSSID in the wrong direction, from the firmware message to the argument buffer. Swap the parameters so that the BSSID is correctly populated in the firmware message from the argument buffer. This issue was reported on ath12k, but exists in ath11k as well. Compile tested only. Reported-by: Baochen Qiang Closes: https://lore.kernel.org/linux-wireless/afbff608-a005-43c4-af76-968a58bf0cc3@oss.qualcomm.com/ Fixes: 74601ecfef6e ("ath11k: Add support for 6g scan hint") Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260713-ath12k_wmi_send_scan_start_cmd-bad-hint_bssid-v1-2-4ffc4a472992@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 28acfc6829df..2d2c6d7a4a3b 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -2423,8 +2423,8 @@ int ath11k_wmi_send_scan_start_cmd(struct ath11k *ar, for (i = 0; i < params->num_hint_bssid; ++i) { hint_bssid->freq_flags = params->hint_bssid[i].freq_flags; - ether_addr_copy(¶ms->hint_bssid[i].bssid.addr[0], - &hint_bssid->bssid.addr[0]); + ether_addr_copy(&hint_bssid->bssid.addr[0], + ¶ms->hint_bssid[i].bssid.addr[0]); hint_bssid++; } } From 6c90f68f97de591e0413f7d2de59449a0388bbab Mon Sep 17 00:00:00 2001 From: Alexander Wilhelm Date: Fri, 3 Jul 2026 09:35:38 +0200 Subject: [PATCH 48/58] wifi: ath12k: fix scan command endianness on big endian ath12k_wmi_scan_req_arg stores scan parameters in CPU-native byte order, while ath12k_wmi_send_scan_start_cmd() writes them into a WMI command buffer whose contents must be in little-endian format. The existing code copies the channel list and writes s_ssid and hint_bssid related values to the command buffer without endian conversion. As a result, scan requests contain invalid parameters on big-endian systems and fail. Convert the channel list as well as the s_ssid and hint_bssid related values to little-endian before writing them to the WMI command buffer. This preserves the existing behaviour on little-endian systems while fixing scan requests on big-endian architectures. Signed-off-by: Alexander Wilhelm Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260703-fix-channel-list-copy-v2-1-372c39306d79@westermo.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 20 ++++++++++++-------- drivers/net/wireless/ath/ath12k/wmi.h | 10 ++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index f16ec3d21242..672eae237ac6 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -2639,9 +2639,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, struct wmi_tlv *tlv; void *ptr; int i, ret, len; - u32 *tmp_ptr, extraie_len_with_pad = 0; - struct ath12k_wmi_hint_short_ssid_arg *s_ssid = NULL; - struct ath12k_wmi_hint_bssid_arg *hint_bssid = NULL; + __le32 *tmp_ptr; + u32 extraie_len_with_pad = 0; + struct ath12k_wmi_hint_short_ssid_params *s_ssid = NULL; + struct ath12k_wmi_hint_bssid_params *hint_bssid = NULL; len = sizeof(*cmd); @@ -2724,9 +2725,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, tlv = ptr; tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_UINT32, len); ptr += TLV_HDR_SIZE; - tmp_ptr = (u32 *)ptr; + tmp_ptr = (__le32 *)ptr; - memcpy(tmp_ptr, arg->chan_list, arg->num_chan * 4); + for (i = 0; i < arg->num_chan; i++) + tmp_ptr[i] = cpu_to_le32(arg->chan_list[i]); ptr += len; @@ -2782,8 +2784,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, ptr += TLV_HDR_SIZE; s_ssid = ptr; for (i = 0; i < arg->num_hint_s_ssid; ++i) { - s_ssid->freq_flags = arg->hint_s_ssid[i].freq_flags; - s_ssid->short_ssid = arg->hint_s_ssid[i].short_ssid; + s_ssid->freq_flags = + cpu_to_le32(arg->hint_s_ssid[i].freq_flags); + s_ssid->short_ssid = + cpu_to_le32(arg->hint_s_ssid[i].short_ssid); s_ssid++; } ptr += len; @@ -2797,7 +2801,7 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar, hint_bssid = ptr; for (i = 0; i < arg->num_hint_bssid; ++i) { hint_bssid->freq_flags = - arg->hint_bssid[i].freq_flags; + cpu_to_le32(arg->hint_bssid[i].freq_flags); ether_addr_copy(&hint_bssid->bssid.addr[0], &arg->hint_bssid[i].bssid.addr[0]); hint_bssid++; diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index c813b2848b5b..83103fdefafc 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -3558,6 +3558,16 @@ struct ath12k_wmi_hint_bssid_arg { struct ath12k_wmi_mac_addr_params bssid; }; +struct ath12k_wmi_hint_short_ssid_params { + __le32 freq_flags; + __le32 short_ssid; +}; + +struct ath12k_wmi_hint_bssid_params { + __le32 freq_flags; + struct ath12k_wmi_mac_addr_params bssid; +}; + struct ath12k_wmi_scan_req_arg { u32 scan_id; u32 scan_req_id; From d6850f1984eb1e5dcec3de2703d32ea4c34c8ad8 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Tue, 14 Jul 2026 10:39:14 +0200 Subject: [PATCH 49/58] wifi: ath12k: Constify struct ath12k_dp_arch_ops 'struct ath12k_dp_arch_ops' is not modified in this driver. Constifying this structure moves some data to a read-only section, so increases overall security, especially when the structure holds some function pointers. On a x86_64, with allmodconfig, as an example: Before: ====== text data bss dec hex filename 6318 3384 0 9702 25e6 drivers/net/wireless/ath/ath12k/wifi7/dp.o After: ===== text data bss dec hex filename 6478 3224 0 9702 25e6 drivers/net/wireless/ath/ath12k/wifi7/dp.o Signed-off-by: Christophe JAILLET Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/969d732e2c6f169e1aa5e89c7e01743a1adb55df.1784010931.git.christophe.jaillet@wanadoo.fr Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp.h | 2 +- drivers/net/wireless/ath/ath12k/wifi7/dp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index 64f79e43341e..a94bbc337df4 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -538,7 +538,7 @@ struct ath12k_dp { /* Lock for protection of peers and rhead_peer_addr */ spinlock_t dp_lock; - struct ath12k_dp_arch_ops *ops; + const struct ath12k_dp_arch_ops *ops; /* Linked list of struct ath12k_dp_link_peer */ struct list_head peers; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp.c b/drivers/net/wireless/ath/ath12k/wifi7/dp.c index c72f604661ce..397da016bc78 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp.c @@ -139,7 +139,7 @@ static int ath12k_wifi7_dp_service_srng(struct ath12k_dp *dp, return tot_work_done; } -static struct ath12k_dp_arch_ops ath12k_wifi7_dp_arch_ops = { +static const struct ath12k_dp_arch_ops ath12k_wifi7_dp_arch_ops = { .service_srng = ath12k_wifi7_dp_service_srng, .tx_get_vdev_bank_config = ath12k_wifi7_dp_tx_get_vdev_bank_config, .reo_cmd_send = ath12k_wifi7_dp_reo_cmd_send, From a711bf00b8b3c6d879cb5d95bbe921366228b318 Mon Sep 17 00:00:00 2001 From: Masi Osmani Date: Thu, 12 Mar 2026 11:37:58 +0100 Subject: [PATCH 50/58] wifi: carl9170: mac80211: document spatial multiplexing power save handler Replace the bare TODO comment in the SMPS configuration handler with documentation explaining why the driver accepts but does not act on SMPS mode changes. The AR9170 advertises SM_PS disabled (both chains always active) in its HT capabilities. While mac80211 may still send SMPS configuration requests, implementing static or dynamic SMPS would require firmware support for per-chain enable/disable that the AR9170 firmware (v1.9.9) does not provide. Signed-off-by: Masi Osmani Acked-by: Christian Lamparter Link: https://patch.msgid.link/AM7PPF5613FA0B6B76223A73FFC756A19A99444A@AM7PPF5613FA0B6.EURP251.PROD.OUTLOOK.COM Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/carl9170/main.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c index af632418fa06..61c7a1288743 100644 --- a/drivers/net/wireless/ath/carl9170/main.c +++ b/drivers/net/wireless/ath/carl9170/main.c @@ -908,7 +908,13 @@ static int carl9170_op_config(struct ieee80211_hw *hw, int radio_idx, u32 change } if (changed & IEEE80211_CONF_CHANGE_SMPS) { - /* TODO */ + /* + * We advertise SM_PS disabled (all chains active). + * mac80211 may still request mode changes, which we + * accept but only support OFF (both chains active). + * Static/dynamic SMPS would require firmware support + * for chain control that the AR9170 does not provide. + */ err = 0; } From 2ac1d4de8e5113b8b40bd22374ff51a5cd95dd9e Mon Sep 17 00:00:00 2001 From: Masi Osmani Date: Thu, 12 Mar 2026 11:38:00 +0100 Subject: [PATCH 51/58] wifi: carl9170: rx: track PHY errors via debugfs Count PHY errors reported by the hardware in the RX status and expose the counter through debugfs as rx_phy_errors. Previously, PHY errors from ar9170_rx_phystatus were silently ignored (marked with a TODO comment). The counter helps diagnose RF environment issues (interference, multipath, low SNR) without requiring monitor mode or additional tooling. Signed-off-by: Masi Osmani Acked-by: Christian Lamparter Link: https://patch.msgid.link/AM7PPF5613FA0B6B42814E38096301FE9429444A@AM7PPF5613FA0B6.EURP251.PROD.OUTLOOK.COM Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/carl9170/carl9170.h | 1 + drivers/net/wireless/ath/carl9170/debug.c | 2 ++ drivers/net/wireless/ath/carl9170/rx.c | 4 +++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/carl9170/carl9170.h b/drivers/net/wireless/ath/carl9170/carl9170.h index b13685e22a0d..e66e3e2ae952 100644 --- a/drivers/net/wireless/ath/carl9170/carl9170.h +++ b/drivers/net/wireless/ath/carl9170/carl9170.h @@ -381,6 +381,7 @@ struct ar9170 { unsigned int tx_ack_failures; unsigned int tx_fcs_errors; unsigned int rx_dropped; + unsigned int rx_phy_errors; /* EEPROM */ struct ar9170_eeprom eeprom; diff --git a/drivers/net/wireless/ath/carl9170/debug.c b/drivers/net/wireless/ath/carl9170/debug.c index 2d734567000a..0498df2a2160 100644 --- a/drivers/net/wireless/ath/carl9170/debug.c +++ b/drivers/net/wireless/ath/carl9170/debug.c @@ -794,6 +794,7 @@ DEBUGFS_READONLY_FILE(tx_janitor_last_run, 64, "last run:%d ms ago", DEBUGFS_READONLY_FILE(tx_dropped, 20, "%d", ar->tx_dropped); DEBUGFS_READONLY_FILE(rx_dropped, 20, "%d", ar->rx_dropped); +DEBUGFS_READONLY_FILE(rx_phy_errors, 20, "%d", ar->rx_phy_errors); DEBUGFS_READONLY_FILE(sniffer_enabled, 20, "%d", ar->sniffer_enabled); DEBUGFS_READONLY_FILE(rx_software_decryption, 20, "%d", @@ -830,6 +831,7 @@ void carl9170_debugfs_register(struct ar9170 *ar) DEBUGFS_ADD(tx_ampdu_list_len); DEBUGFS_ADD(rx_dropped); + DEBUGFS_ADD(rx_phy_errors); DEBUGFS_ADD(sniffer_enabled); DEBUGFS_ADD(rx_software_decryption); diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c index 6833430130f4..ec4d440e6ac8 100644 --- a/drivers/net/wireless/ath/carl9170/rx.c +++ b/drivers/net/wireless/ath/carl9170/rx.c @@ -455,7 +455,9 @@ static void carl9170_rx_phy_status(struct ar9170 *ar, if (phy->rssi[i] & 0x80) phy->rssi[i] = ((~phy->rssi[i] & 0x7f) + 1) & 0x7f; - /* TODO: we could do something with phy_errors */ + if (phy->phy_err) + ar->rx_phy_errors++; + status->signal = ar->noise[0] + phy->rssi_combined; } From 89f3630439f1555db4f12b98775c0399195d5027 Mon Sep 17 00:00:00 2001 From: Masi Osmani Date: Tue, 17 Mar 2026 12:06:34 +0100 Subject: [PATCH 52/58] wifi: carl9170: cmd: downgrade transient register I/O errors to wiphy_dbg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Register read/write failures during deauth/teardown transitions are harmless — mac80211 tries to read survey stats or write slot_time while the firmware is in a transitional state. The command times out with -EIO but the adapter recovers and re-authenticates normally. Downgrade both "writing reg ... failed" and "reading regs failed" from wiphy_err to wiphy_dbg to reduce dmesg noise. The errors are still visible with dynamic debug enabled for investigation. Signed-off-by: Masi Osmani Acked-by: Christian Lamparter Link: https://patch.msgid.link/AM7PPF5613FA0B67FB95CB5305CEF9DAA209441A@AM7PPF5613FA0B6.EURP251.PROD.OUTLOOK.COM Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/carl9170/cmd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/carl9170/cmd.c b/drivers/net/wireless/ath/carl9170/cmd.c index 402fd0633e09..ad0a018119c9 100644 --- a/drivers/net/wireless/ath/carl9170/cmd.c +++ b/drivers/net/wireless/ath/carl9170/cmd.c @@ -52,7 +52,7 @@ int carl9170_write_reg(struct ar9170 *ar, const u32 reg, const u32 val) (u8 *) buf, 0, NULL); if (err) { if (net_ratelimit()) { - wiphy_err(ar->hw->wiphy, "writing reg %#x " + wiphy_dbg(ar->hw->wiphy, "writing reg %#x " "(val %#x) failed (%d)\n", reg, val, err); } } @@ -78,7 +78,7 @@ int carl9170_read_mreg(struct ar9170 *ar, const int nregs, 4 * nregs, (u8 *)res); if (err) { if (net_ratelimit()) { - wiphy_err(ar->hw->wiphy, "reading regs failed (%d)\n", + wiphy_dbg(ar->hw->wiphy, "reading regs failed (%d)\n", err); } return err; From af50baccaa5fab96b045700a8dc378f73c77822c Mon Sep 17 00:00:00 2001 From: Daizhuang Bai Date: Fri, 17 Jul 2026 07:13:02 +0530 Subject: [PATCH 53/58] wifi: ath12k: Set DTIM policy to stick mode for station interface Currently, the station always follows the listen interval regardless of the DTIM value. The DTIM function does not work as expected. The default value of the listen interval is 5 so that the STA wakes up every 500ms when power save is on. This can cause a data transmission delay. Set the DTIM policy to DTIM stick mode so that the station follows the AP DTIM interval rather than the listen interval, which is set in the peer assoc command. DTIM stick mode is preferable per the firmware team's request. Apply this only for STA vdevs and only when STA power save is supported, to avoid affecting unsupported targets and P2P client vdevs. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Signed-off-by: Daizhuang Bai Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260717014302.284034-1-daizhuang.bai@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 11 +++++++++++ drivers/net/wireless/ath/ath12k/wmi.h | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 5e4798ae8e99..22dbc86578ab 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3985,6 +3985,17 @@ static void ath12k_bss_assoc(struct ath12k *ar, ath12k_warn(ar->ab, "failed to set vdev %i OBSS PD parameters: %d\n", arvif->vdev_id, ret); + if (ar->ab->hw_params->supports_sta_ps && + ahvif->vdev_type == WMI_VDEV_TYPE_STA && + ahvif->vdev_subtype == WMI_VDEV_SUBTYPE_NONE) { + ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id, + WMI_VDEV_PARAM_DTIM_POLICY, + WMI_DTIM_POLICY_STICK); + if (ret) + ath12k_warn(ar->ab, "failed to set vdev %d stick DTIM policy: %d\n", + arvif->vdev_id, ret); + } + if (test_bit(WMI_TLV_SERVICE_11D_OFFLOAD, ar->ab->wmi_ab.svc_map) && ahvif->vdev_type == WMI_VDEV_TYPE_STA && ahvif->vdev_subtype == WMI_VDEV_SUBTYPE_NONE) diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 83103fdefafc..b508aa759bd8 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -2331,6 +2331,13 @@ enum wmi_slot_time { WMI_VDEV_SLOT_TIME_SHORT = 2, }; +enum wmi_dtim_policy { + WMI_DTIM_POLICY_IGNORE = 1, + WMI_DTIM_POLICY_NORMAL = 2, + WMI_DTIM_POLICY_STICK = 3, + WMI_DTIM_POLICY_AUTO = 4, +}; + enum wmi_preamble { WMI_VDEV_PREAMBLE_LONG = 1, WMI_VDEV_PREAMBLE_SHORT = 2, From 3e66c9cc169d680df38bfbcb7cc2febbaa2899dc Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Tue, 14 Jul 2026 09:14:00 +0200 Subject: [PATCH 54/58] wifi: ath6kl: Constify struct cfg80211_ops 'struct cfg80211_ops' is not modified in this driver. Constifying this structure moves some data to a read-only section, so increases overall security, especially when the structure holds some function pointers. On a x86_64, with allmodconfig, as an example: Before: ====== text data bss dec hex filename 143726 34579 192 178497 2b941 drivers/net/wireless/ath/ath6kl/cfg80211.o After: ===== text data bss dec hex filename 144814 33491 192 178497 2b941 drivers/net/wireless/ath/ath6kl/cfg80211.o Signed-off-by: Christophe JAILLET Link: https://patch.msgid.link/5aace954b6ef5c42017b83a1bffb859618e9498a.1784013180.git.christophe.jaillet@wanadoo.fr Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/cfg80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.c b/drivers/net/wireless/ath/ath6kl/cfg80211.c index cc0f2c45fc3a..ecde91159b54 100644 --- a/drivers/net/wireless/ath/ath6kl/cfg80211.c +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.c @@ -3437,7 +3437,7 @@ ath6kl_mgmt_stypes[NUM_NL80211_IFTYPES] = { }, }; -static struct cfg80211_ops ath6kl_cfg80211_ops = { +static const struct cfg80211_ops ath6kl_cfg80211_ops = { .add_virtual_intf = ath6kl_cfg80211_add_iface, .del_virtual_intf = ath6kl_cfg80211_del_iface, .change_virtual_intf = ath6kl_cfg80211_change_iface, From c42b27336eeffd7926a77604cdefcc8918bed926 Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Fri, 3 Jul 2026 16:56:02 +0100 Subject: [PATCH 55/58] wifi: ath12k: fix survey indexing across bands When running 'iw dev wlan0 survey dump' the values for the channel busy time have the same sequence across bands. This is caused by indexing into the ath12k survey array using a band-local index rather than the global index passed by mac80211. This results in surveys for 5 GHz and 6 GHz channels returning values from 2.4 GHz slots, making the survey unusable on those bands. Further, there are redundant survey slots for multi-radio/single-phy instances. Fix by moving the survey data into ath12k_hw so multiple radios under a single wiphy share one table, and index into it using the global mac80211 index. A new spinlock in ath12k_hw serialises access to the survey array, which is now shared across all radios under a single hw. Band busy-times Before this fix: 2.4 GHz: 9, 2, 2, 2, 4, 2, 10, 16, 4, 12, 5 5 GHz: 9, 2, 2, 2, 4, 2, 10, 16, 4, 12, 5 6 GHz: 9, 2, 2, 2, 4, 2, 10, 16, 4, 12, 5 After this fix, times are independent: 2.4 GHz: 23, 5, 5, 12, 2, 12, 26, 5, 3, 1, 27 5 GHz: 30, 40, 29, 27, 118, 118, 112, 120, 11, 11, 11 6 GHz: 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 Tested-on: wcn7850 hw2.0 PCI WLAN.IOE_HMT.1.1-00018-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1 Fixes: 4f242b1d6996 ("wifi: ath12k: support get_survey mac op for single wiphy") Signed-off-by: Matthew Leach Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260703-ath12-survey-band-fix-v3-1-2fb050c2505a@collabora.com [fixed ath12k-check issues] Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.h | 8 ++- drivers/net/wireless/ath/ath12k/mac.c | 33 +++++++------ drivers/net/wireless/ath/ath12k/wmi.c | 68 ++++++++++++++------------ 3 files changed, 62 insertions(+), 47 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index f9fb1399e0b6..37a194e00248 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -665,7 +665,7 @@ struct ath12k { /* protects the radio specific data like debug stats, ppdu_stats_info stats, * vdev_stop_status info, scan data, ath12k_sta info, ath12k_link_vif info, - * channel context data, survey info, test mode data, regd_channel_update_queue, + * channel context data, test mode data, regd_channel_update_queue, * peer_delete_waits. */ spinlock_t data_lock; @@ -722,7 +722,6 @@ struct ath12k { * avoid reporting garbage data. */ bool ch_info_can_report_survey; - struct survey_info survey[ATH12K_NUM_CHANS]; struct completion bss_survey_done; struct work_struct regd_update_work; @@ -792,6 +791,11 @@ struct ath12k_hw { */ struct mutex hw_mutex; enum ath12k_hw_state state; + + /* protects survey[] shared across radios of this hw. */ + spinlock_t survey_lock; + struct survey_info survey[ATH12K_NUM_CHANS]; + bool regd_updated; bool use_6ghz_regd; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 22dbc86578ab..956414b953ec 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -13598,52 +13598,54 @@ ath12k_mac_update_bss_chan_survey(struct ath12k *ar, int ath12k_mac_op_get_survey(struct ieee80211_hw *hw, int idx, struct survey_info *survey) { + struct ath12k_hw *ah = hw->priv; struct ath12k *ar; struct ieee80211_supported_band *sband; - struct survey_info *ar_survey; + struct survey_info *ah_survey; + int sband_idx = idx; lockdep_assert_wiphy(hw->wiphy); - if (idx >= ATH12K_NUM_CHANS) + if (sband_idx >= ATH12K_NUM_CHANS) return -ENOENT; sband = hw->wiphy->bands[NL80211_BAND_2GHZ]; - if (sband && idx >= sband->n_channels) { - idx -= sband->n_channels; + if (sband && sband_idx >= sband->n_channels) { + sband_idx -= sband->n_channels; sband = NULL; } if (!sband) sband = hw->wiphy->bands[NL80211_BAND_5GHZ]; - if (sband && idx >= sband->n_channels) { - idx -= sband->n_channels; + if (sband && sband_idx >= sband->n_channels) { + sband_idx -= sband->n_channels; sband = NULL; } if (!sband) sband = hw->wiphy->bands[NL80211_BAND_6GHZ]; - if (!sband || idx >= sband->n_channels) + if (!sband || sband_idx >= sband->n_channels) return -ENOENT; - ar = ath12k_mac_get_ar_by_chan(hw, &sband->channels[idx]); + ar = ath12k_mac_get_ar_by_chan(hw, &sband->channels[sband_idx]); if (!ar) { - if (sband->channels[idx].flags & IEEE80211_CHAN_DISABLED) { + if (sband->channels[sband_idx].flags & IEEE80211_CHAN_DISABLED) { memset(survey, 0, sizeof(*survey)); return 0; } return -ENOENT; } - ar_survey = &ar->survey[idx]; + ah_survey = &ah->survey[idx]; - ath12k_mac_update_bss_chan_survey(ar, &sband->channels[idx]); + ath12k_mac_update_bss_chan_survey(ar, &sband->channels[sband_idx]); - spin_lock_bh(&ar->data_lock); - memcpy(survey, ar_survey, sizeof(*survey)); - spin_unlock_bh(&ar->data_lock); + scoped_guard(spinlock_bh, &ah->survey_lock) { + memcpy(survey, ah_survey, sizeof(*survey)); + } - survey->channel = &sband->channels[idx]; + survey->channel = &sband->channels[sband_idx]; if (ar->rx_channel == survey->channel) survey->filled |= SURVEY_INFO_IN_USE; @@ -15324,6 +15326,7 @@ static struct ath12k_hw *ath12k_mac_hw_allocate(struct ath12k_hw_group *ag, mutex_init(&ah->hw_mutex); + spin_lock_init(&ah->survey_lock); spin_lock_init(&ah->dp_hw.peer_lock); INIT_LIST_HEAD(&ah->dp_hw.dp_peers_list); diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 672eae237ac6..0178ab169754 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -6742,16 +6742,12 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb, return 0; } -static int freq_to_idx(struct ath12k *ar, int freq) +static int freq_to_idx(struct ieee80211_hw *hw, int freq) { struct ieee80211_supported_band *sband; - struct ieee80211_hw *hw = ath12k_ar_to_hw(ar); int band, ch, idx = 0; for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) { - if (!ar->mac.sbands[band].channels) - continue; - sband = hw->wiphy->bands[band]; if (!sband) continue; @@ -7662,6 +7658,7 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) { struct wmi_chan_info_event ch_info_ev = {}; struct ath12k *ar; + struct ath12k_hw *ah; struct survey_info *survey; int idx; /* HW channel counters frequency value in hertz */ @@ -7693,6 +7690,7 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) return; } spin_lock_bh(&ar->data_lock); + ah = ath12k_ar_to_ah(ar); switch (ar->scan.state) { case ATH12K_SCAN_IDLE: @@ -7704,8 +7702,8 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) break; } - idx = freq_to_idx(ar, le32_to_cpu(ch_info_ev.freq)); - if (idx >= ARRAY_SIZE(ar->survey)) { + idx = freq_to_idx(ath12k_ar_to_hw(ar), le32_to_cpu(ch_info_ev.freq)); + if (idx >= ARRAY_SIZE(ah->survey)) { ath12k_warn(ab, "chan info: invalid frequency %d (idx %d out of bounds)\n", ch_info_ev.freq, idx); goto exit; @@ -7718,14 +7716,20 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) cc_freq_hz = (le32_to_cpu(ch_info_ev.mac_clk_mhz) * 1000); if (ch_info_ev.cmd_flags == WMI_CHAN_INFO_START_RESP) { - survey = &ar->survey[idx]; - memset(survey, 0, sizeof(*survey)); - survey->noise = le32_to_cpu(ch_info_ev.noise_floor); - survey->filled = SURVEY_INFO_NOISE_DBM | SURVEY_INFO_TIME | - SURVEY_INFO_TIME_BUSY; - survey->time = div_u64(le32_to_cpu(ch_info_ev.cycle_count), cc_freq_hz); - survey->time_busy = div_u64(le32_to_cpu(ch_info_ev.rx_clear_count), - cc_freq_hz); + scoped_guard(spinlock_bh, &ah->survey_lock) { + survey = &ah->survey[idx]; + memset(survey, 0, sizeof(*survey)); + survey->noise = le32_to_cpu(ch_info_ev.noise_floor); + survey->time = + div_u64(le32_to_cpu(ch_info_ev.cycle_count), + cc_freq_hz); + survey->time_busy = + div_u64(le32_to_cpu(ch_info_ev.rx_clear_count), + cc_freq_hz); + survey->filled = SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY; + } } exit: spin_unlock_bh(&ar->data_lock); @@ -7738,6 +7742,7 @@ ath12k_pdev_bss_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) struct wmi_pdev_bss_chan_info_event bss_ch_info_ev = {}; struct survey_info *survey; struct ath12k *ar; + struct ath12k_hw *ah; u32 cc_freq_hz = ab->cc_freq_hz; u64 busy, total, tx, rx, rx_bss; int idx; @@ -7778,28 +7783,31 @@ ath12k_pdev_bss_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) return; } - spin_lock_bh(&ar->data_lock); - idx = freq_to_idx(ar, le32_to_cpu(bss_ch_info_ev.freq)); - if (idx >= ARRAY_SIZE(ar->survey)) { + ah = ath12k_ar_to_ah(ar); + + idx = freq_to_idx(ath12k_ar_to_hw(ar), le32_to_cpu(bss_ch_info_ev.freq)); + if (idx >= ARRAY_SIZE(ah->survey)) { ath12k_warn(ab, "bss chan info: invalid frequency %d (idx %d out of bounds)\n", bss_ch_info_ev.freq, idx); goto exit; } - survey = &ar->survey[idx]; + scoped_guard(spinlock_bh, &ah->survey_lock) { + survey = &ah->survey[idx]; + + survey->noise = le32_to_cpu(bss_ch_info_ev.noise_floor); + survey->time = div_u64(total, cc_freq_hz); + survey->time_busy = div_u64(busy, cc_freq_hz); + survey->time_rx = div_u64(rx_bss, cc_freq_hz); + survey->time_tx = div_u64(tx, cc_freq_hz); + survey->filled |= (SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY | + SURVEY_INFO_TIME_RX | + SURVEY_INFO_TIME_TX); + } - survey->noise = le32_to_cpu(bss_ch_info_ev.noise_floor); - survey->time = div_u64(total, cc_freq_hz); - survey->time_busy = div_u64(busy, cc_freq_hz); - survey->time_rx = div_u64(rx_bss, cc_freq_hz); - survey->time_tx = div_u64(tx, cc_freq_hz); - survey->filled |= (SURVEY_INFO_NOISE_DBM | - SURVEY_INFO_TIME | - SURVEY_INFO_TIME_BUSY | - SURVEY_INFO_TIME_RX | - SURVEY_INFO_TIME_TX); exit: - spin_unlock_bh(&ar->data_lock); complete(&ar->bss_survey_done); rcu_read_unlock(); From 7698656a2f7b045af5a6859766238cefea1b1945 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Thu, 16 Jul 2026 13:01:11 -0700 Subject: [PATCH 56/58] wifi: ath12k: Avoid buffer overread in ath12k_wmi_op_rx() Currently, in ath12k_wmi_op_rx(), the firmware buffer is read without first verifying that the buffer has enough data to hold a header. This could result in a buffer overread. Update the logic to verify the buffer contains at least enough data to hold a wmi_cmd_hdr before reading from the buffer. 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: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260716-ath12k_wmi_op_rx-overread-v1-1-327a4b1c2372@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 0178ab169754..2b707ffc1a20 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -10298,12 +10298,12 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb) struct wmi_cmd_hdr *cmd_hdr; enum wmi_tlv_event_id id; - cmd_hdr = (struct wmi_cmd_hdr *)skb->data; - id = le32_get_bits(cmd_hdr->cmd_id, WMI_CMD_HDR_CMD_ID); - - if (!skb_pull(skb, sizeof(struct wmi_cmd_hdr))) + cmd_hdr = skb_pull_data(skb, sizeof(*cmd_hdr)); + if (!cmd_hdr) goto out; + id = le32_get_bits(cmd_hdr->cmd_id, WMI_CMD_HDR_CMD_ID); + switch (id) { /* Process all the WMI events here */ case WMI_SERVICE_READY_EVENTID: From 9ef9dd30058cc9223c72f711dca1a28a5947d0c5 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Thu, 16 Jul 2026 13:01:31 -0700 Subject: [PATCH 57/58] wifi: ath11k: Avoid buffer overread in ath11k_wmi_tlv_op_rx() Currently, in ath11k_wmi_tlv_op_rx(), the firmware buffer is read without first verifying that the buffer has enough data to hold a header. This could result in a buffer overread. Add an upfront length check before dereferencing skb->data as a wmi_cmd_hdr. The check is placed before the trace_ath11k_wmi_event() call to preserve the existing trace semantics (tracing the full raw WMI event including the header), unlike the analogous ath12k fix which could use skb_pull_data() directly. 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/20260716-ath11k_wmi_tlv_op_rx-overread-v1-1-0b972b3f1368@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 2d2c6d7a4a3b..4cbd7293845a 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -8901,13 +8901,15 @@ static void ath11k_wmi_tlv_op_rx(struct ath11k_base *ab, struct sk_buff *skb) struct wmi_cmd_hdr *cmd_hdr; enum wmi_tlv_event_id id; + if (skb->len < sizeof(*cmd_hdr)) + goto out; + cmd_hdr = (struct wmi_cmd_hdr *)skb->data; id = FIELD_GET(WMI_CMD_HDR_CMD_ID, (cmd_hdr->cmd_id)); trace_ath11k_wmi_event(ab, id, skb->data, skb->len); - if (skb_pull(skb, sizeof(struct wmi_cmd_hdr)) == NULL) - goto out; + skb_pull(skb, sizeof(*cmd_hdr)); switch (id) { /* Process all the WMI events here */ From 3826a4d293abb82826dc79ad682d7f885f125dc5 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Mon, 13 Jul 2026 10:03:59 +0800 Subject: [PATCH 58/58] wifi: ath11k: add purwa-iot-evk and qcs6490-rb3gen2 to usecase firmware table Add purwa-iot-evk and qcs6490-rb3gen2 platform support to the usecase firmware lookup table for WCN6855 hw2.1. These platforms use the nfa765 firmware path for usecase-based firmware selection. Also reorder the table entries by compatible string. Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-04685-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1 Signed-off-by: Miaoqing Pan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260713020359.3618193-1-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c index 8dacc878c006..8039124e7832 100644 --- a/drivers/net/wireless/ath/ath11k/core.c +++ b/drivers/net/wireless/ath/ath11k/core.c @@ -1049,9 +1049,11 @@ static const struct __ath11k_core_usecase_firmware_table { const char *compatible; const char *firmware_name; } ath11k_core_usecase_firmware_table[] = { + { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"}, { ATH11K_HW_WCN6855_HW21, "qcom,lemans-evk", "nfa765"}, { ATH11K_HW_WCN6855_HW21, "qcom,monaco-evk", "nfa765"}, - { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,purwa-iot-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,qcs6490-rb3gen2", "nfa765"}, { /* Sentinel */ } };