* [PATCH ath-next 1/3] wifi: ath12k: use per-device max QMI chunk size
2026-07-30 6:17 [PATCH ath-next 0/3] wifi: ath12k: align QMI memory to 64 KB boundary Baochen Qiang
@ 2026-07-30 6:17 ` Baochen Qiang
2026-07-30 6:17 ` [PATCH ath-next 2/3] wifi: ath12k: clear chunk paddr and size in ath12k_qmi_free_target_mem_chunk() Baochen Qiang
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Baochen Qiang @ 2026-07-30 6:17 UTC (permalink / raw)
To: Jeff Johnson; +Cc: linux-wireless, ath12k, Baochen Qiang
When a target memory chunk fails to allocate, ath12k_qmi_alloc_chunk()
compares the requested chunk size against ATH12K_QMI_MAX_CHUNK_SIZE to
decide whether to ask the firmware to retry with smaller chunks
(-EAGAIN) or to give up (-ENOMEM). This threshold is the boundary that
tells a first (big) request apart from a retry (small) request, so it
has to match the size the firmware actually falls back to on retry.
That fallback size is not the same across all firmware, though. On
WCN7850 and QCC2072 the firmware splits the request into 512 KB chunks
on retry, whereas on QCN9274, IPQ5332 and IPQ5424 the firmware retries
with larger 2 MB segments instead of 512 KB. A single hardcoded 2 MB
threshold is therefore wrong for the former, and a hardcoded 512 KB
threshold would be wrong for the latter.
Replace the hardcoded ATH12K_QMI_MAX_CHUNK_SIZE with a per-device
qmi_max_chunk_size parameter: SZ_512K for WCN7850 and QCC2072, SZ_2M for
QCN9274, IPQ5332 and IPQ5424. No functional change for the latter (the
value stays 2 MB); the former now use the correct 512 KB boundary.
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3
Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00074-QCACOLSWPL_V1_TO_SILICONZ-1
Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
---
drivers/net/wireless/ath/ath12k/hw.h | 1 +
drivers/net/wireless/ath/ath12k/qmi.c | 3 +--
drivers/net/wireless/ath/ath12k/wifi7/hw.c | 7 +++++++
3 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h
index bb1c104e1a65..78a13b169e99 100644
--- a/drivers/net/wireless/ath/ath12k/hw.h
+++ b/drivers/net/wireless/ath/ath12k/hw.h
@@ -167,6 +167,7 @@ struct ath12k_hw_params {
u8 max_radios;
bool single_pdev_only:1;
u32 qmi_service_ins_id;
+ u32 qmi_max_chunk_size;
bool internal_sleep_clock:1;
const struct ath12k_hw_ops *hw_ops;
diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c
index bb61c78e5c29..f96d847ac92f 100644
--- a/drivers/net/wireless/ath/ath12k/qmi.c
+++ b/drivers/net/wireless/ath/ath12k/qmi.c
@@ -18,7 +18,6 @@
#define SLEEP_CLOCK_SELECT_INTERNAL_BIT 0x02
#define HOST_CSTATE_BIT 0x04
#define PLATFORM_CAP_PCIE_GLOBAL_RESET 0x08
-#define ATH12K_QMI_MAX_CHUNK_SIZE 2097152
static const struct qmi_elem_info wlfw_host_mlo_chip_info_s_v01_ei[] = {
{
@@ -2655,7 +2654,7 @@ static int ath12k_qmi_alloc_chunk(struct ath12k_base *ab,
&chunk->paddr,
GFP_KERNEL | __GFP_NOWARN);
if (!chunk->v.addr) {
- if (chunk->size > ATH12K_QMI_MAX_CHUNK_SIZE) {
+ if (chunk->size > ab->hw_params->qmi_max_chunk_size) {
ab->qmi.target_mem_delayed = true;
ath12k_warn(ab,
"qmi dma allocation failed (%u B type %u), will try later with small size\n",
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
index 296180fbdc21..23a855018bc4 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
@@ -7,6 +7,7 @@
#include <linux/types.h>
#include <linux/bitops.h>
#include <linux/bitfield.h>
+#include <linux/sizes.h>
#include "../debug.h"
#include "../core.h"
@@ -369,6 +370,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
.max_radios = 1,
.single_pdev_only = false,
.qmi_service_ins_id = ATH12K_QMI_WLFW_SERVICE_INS_ID_V01_QCN9274,
+ .qmi_max_chunk_size = SZ_2M,
.internal_sleep_clock = false,
.hw_ops = &qcn9274_ops,
@@ -461,6 +463,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
.max_radios = 1,
.single_pdev_only = true,
.qmi_service_ins_id = ATH12K_QMI_WLFW_SERVICE_INS_ID_V01_WCN7850,
+ .qmi_max_chunk_size = SZ_512K,
.internal_sleep_clock = true,
.hw_ops = &wcn7850_ops,
@@ -553,6 +556,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
.max_radios = 2,
.single_pdev_only = false,
.qmi_service_ins_id = ATH12K_QMI_WLFW_SERVICE_INS_ID_V01_QCN9274,
+ .qmi_max_chunk_size = SZ_2M,
.internal_sleep_clock = false,
.hw_ops = &qcn9274_ops,
@@ -643,6 +647,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
.max_radios = 1,
.single_pdev_only = false,
.qmi_service_ins_id = ATH12K_QMI_WLFW_SERVICE_INS_ID_V01_IPQ5332,
+ .qmi_max_chunk_size = SZ_2M,
.internal_sleep_clock = false,
.hw_ops = &qcn9274_ops,
@@ -728,6 +733,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
.max_radios = 1,
.single_pdev_only = true,
.qmi_service_ins_id = ATH12K_QMI_WLFW_SERVICE_INS_ID_V01_WCN7850,
+ .qmi_max_chunk_size = SZ_512K,
.internal_sleep_clock = true,
.hw_ops = &qcc2072_ops,
@@ -821,6 +827,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
.max_radios = 1,
.single_pdev_only = false,
.qmi_service_ins_id = ATH12K_QMI_WLFW_SERVICE_INS_ID_V01_IPQ5332,
+ .qmi_max_chunk_size = SZ_2M,
.internal_sleep_clock = false,
.hw_ops = &qcn9274_ops,
--
2.25.1
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH ath-next 2/3] wifi: ath12k: clear chunk paddr and size in ath12k_qmi_free_target_mem_chunk()
2026-07-30 6:17 [PATCH ath-next 0/3] wifi: ath12k: align QMI memory to 64 KB boundary Baochen Qiang
2026-07-30 6:17 ` [PATCH ath-next 1/3] wifi: ath12k: use per-device max QMI chunk size Baochen Qiang
@ 2026-07-30 6:17 ` Baochen Qiang
2026-07-30 6:17 ` [PATCH ath-next 3/3] wifi: ath12k: align QMI target memory to 64 KB Baochen Qiang
2026-08-19 7:09 ` [PATCH ath-next 0/3] wifi: ath12k: align QMI memory to 64 KB boundary Rameshkumar Sundaram
3 siblings, 0 replies; 5+ messages in thread
From: Baochen Qiang @ 2026-07-30 6:17 UTC (permalink / raw)
To: Jeff Johnson; +Cc: linux-wireless, ath12k, Baochen Qiang
paddr and size of a chunk are not cleared after the chunk is freed, which
is safe now since the code checks vaddr only and it is properly cleared
on free. However, leaving stale values can mislead diagnostics and future
maintenance. Clear both to reduce such risk, this also aligns with
ath12k_qmi_free_mlo_mem_chunk().
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3
Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00074-QCACOLSWPL_V1_TO_SILICONZ-1
Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
---
drivers/net/wireless/ath/ath12k/qmi.c | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c
index f96d847ac92f..03ec2057ca27 100644
--- a/drivers/net/wireless/ath/ath12k/qmi.c
+++ b/drivers/net/wireless/ath/ath12k/qmi.c
@@ -2602,27 +2602,33 @@ static void ath12k_qmi_free_mlo_mem_chunk(struct ath12k_base *ab,
static void ath12k_qmi_free_target_mem_chunk(struct ath12k_base *ab)
{
struct ath12k_hw_group *ag = ab->ag;
+ struct target_mem_chunk *chunk;
int i, mlo_idx;
for (i = 0, mlo_idx = 0; i < ab->qmi.mem_seg_count; i++) {
- if (ab->qmi.target_mem[i].type == MLO_GLOBAL_MEM_REGION_TYPE) {
+ chunk = &ab->qmi.target_mem[i];
+
+ if (chunk->type == MLO_GLOBAL_MEM_REGION_TYPE) {
ath12k_qmi_free_mlo_mem_chunk(ab,
- &ab->qmi.target_mem[i],
+ chunk,
mlo_idx++);
} else {
if (test_bit(ATH12K_FLAG_FIXED_MEM_REGION, &ab->dev_flags) &&
- ab->qmi.target_mem[i].v.ioaddr) {
- iounmap(ab->qmi.target_mem[i].v.ioaddr);
- ab->qmi.target_mem[i].v.ioaddr = NULL;
+ chunk->v.ioaddr) {
+ iounmap(chunk->v.ioaddr);
+ chunk->v.ioaddr = NULL;
} else {
- if (!ab->qmi.target_mem[i].v.addr)
+ if (!chunk->v.addr)
continue;
dma_free_coherent(ab->dev,
- ab->qmi.target_mem[i].prev_size,
- ab->qmi.target_mem[i].v.addr,
- ab->qmi.target_mem[i].paddr);
- ab->qmi.target_mem[i].v.addr = NULL;
+ chunk->prev_size,
+ chunk->v.addr,
+ chunk->paddr);
+ chunk->v.addr = NULL;
}
+
+ chunk->paddr = 0;
+ chunk->size = 0;
}
}
--
2.25.1
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH ath-next 3/3] wifi: ath12k: align QMI target memory to 64 KB
2026-07-30 6:17 [PATCH ath-next 0/3] wifi: ath12k: align QMI memory to 64 KB boundary Baochen Qiang
2026-07-30 6:17 ` [PATCH ath-next 1/3] wifi: ath12k: use per-device max QMI chunk size Baochen Qiang
2026-07-30 6:17 ` [PATCH ath-next 2/3] wifi: ath12k: clear chunk paddr and size in ath12k_qmi_free_target_mem_chunk() Baochen Qiang
@ 2026-07-30 6:17 ` Baochen Qiang
2026-08-19 7:09 ` [PATCH ath-next 0/3] wifi: ath12k: align QMI memory to 64 KB boundary Rameshkumar Sundaram
3 siblings, 0 replies; 5+ messages in thread
From: Baochen Qiang @ 2026-07-30 6:17 UTC (permalink / raw)
To: Jeff Johnson; +Cc: linux-wireless, ath12k, Baochen Qiang
ath12k_qmi_alloc_chunk() allocates the target memory chunks the firmware
requests via dma_alloc_coherent() and hands the resulting physical
addresses to the firmware. The firmware does not require every chunk to
be aligned, but each unaligned chunk consumes extra TLB entries when the
firmware maps it, and too many unaligned chunks exhaust the TLB and make
the firmware crash. The firmware team recommends aligning the chunks to
64 KB so that the TLB stays within budget. dma_alloc_coherent() only
guarantees page alignment, so a chunk can end up unaligned.
Align the physical address handed to the firmware to 64 KB. A natural
allocation is often already 64 KB aligned, so try the exact size first
and keep it when it happens to be aligned; only when it is not, free it
and over-allocate by SZ_64K - PAGE_SIZE and round the base up. Since the
DMA allocator already guarantees page alignment, that padding is enough
to reach the next 64 KB boundary. Record the raw allocation in
paddr_unaligned/vaddr_unaligned/total_size so the chunk can still be
freed correctly.
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3
Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00074-QCACOLSWPL_V1_TO_SILICONZ-1
Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
---
drivers/net/wireless/ath/ath12k/qmi.c | 79 +++++++++++++++++++++++++++--------
drivers/net/wireless/ath/ath12k/qmi.h | 4 ++
2 files changed, 65 insertions(+), 18 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c
index 03ec2057ca27..bfb5b6659f21 100644
--- a/drivers/net/wireless/ath/ath12k/qmi.c
+++ b/drivers/net/wireless/ath/ath12k/qmi.c
@@ -14,6 +14,7 @@
#include <linux/of_address.h>
#include <linux/ioport.h>
#include <linux/of_reserved_mem.h>
+#include <linux/sizes.h>
#define SLEEP_CLOCK_SELECT_INTERNAL_BIT 0x02
#define HOST_CSTATE_BIT 0x04
@@ -2583,20 +2584,28 @@ static void ath12k_qmi_free_mlo_mem_chunk(struct ath12k_base *ab,
mlo_chunk->v.ioaddr = NULL;
} else if (mlo_chunk->v.addr) {
dma_free_coherent(ab->dev,
- mlo_chunk->size,
- mlo_chunk->v.addr,
- mlo_chunk->paddr);
+ mlo_chunk->total_size,
+ mlo_chunk->vaddr_unaligned,
+ mlo_chunk->paddr_unaligned);
+ mlo_chunk->vaddr_unaligned = NULL;
mlo_chunk->v.addr = NULL;
}
mlo_chunk->paddr = 0;
+ mlo_chunk->paddr_unaligned = 0;
mlo_chunk->size = 0;
- if (fixed_mem)
+ mlo_chunk->total_size = 0;
+
+ if (fixed_mem) {
chunk->v.ioaddr = NULL;
- else
+ } else {
chunk->v.addr = NULL;
+ chunk->vaddr_unaligned = NULL;
+ }
chunk->paddr = 0;
+ chunk->paddr_unaligned = 0;
chunk->size = 0;
+ chunk->total_size = 0;
}
static void ath12k_qmi_free_target_mem_chunk(struct ath12k_base *ab)
@@ -2621,14 +2630,17 @@ static void ath12k_qmi_free_target_mem_chunk(struct ath12k_base *ab)
if (!chunk->v.addr)
continue;
dma_free_coherent(ab->dev,
- chunk->prev_size,
- chunk->v.addr,
- chunk->paddr);
+ chunk->total_size,
+ chunk->vaddr_unaligned,
+ chunk->paddr_unaligned);
+ chunk->vaddr_unaligned = NULL;
chunk->v.addr = NULL;
}
chunk->paddr = 0;
+ chunk->paddr_unaligned = 0;
chunk->size = 0;
+ chunk->total_size = 0;
}
}
@@ -2641,6 +2653,10 @@ static void ath12k_qmi_free_target_mem_chunk(struct ath12k_base *ab)
static int ath12k_qmi_alloc_chunk(struct ath12k_base *ab,
struct target_mem_chunk *chunk)
{
+ dma_addr_t paddr;
+ size_t size;
+ void *vaddr;
+
/* Firmware reloads in recovery/resume.
* In such cases, no need to allocate memory for FW again.
*/
@@ -2650,29 +2666,56 @@ static int ath12k_qmi_alloc_chunk(struct ath12k_base *ab,
goto this_chunk_done;
/* cannot reuse the existing chunk */
- dma_free_coherent(ab->dev, chunk->prev_size,
- chunk->v.addr, chunk->paddr);
+ dma_free_coherent(ab->dev, chunk->total_size,
+ chunk->vaddr_unaligned, chunk->paddr_unaligned);
chunk->v.addr = NULL;
+ chunk->vaddr_unaligned = NULL;
+ chunk->paddr_unaligned = 0;
+ chunk->total_size = 0;
}
- chunk->v.addr = dma_alloc_coherent(ab->dev,
- chunk->size,
- &chunk->paddr,
+ /*
+ * Each unaligned chunk costs the firmware extra TLB entries when
+ * mapping it, and too many unaligned chunks exhaust the TLB and
+ * crash the firmware, so align the base to 64 KB. The DMA
+ * allocator only guarantees page alignment, but a natural
+ * allocation is often already 64 KB aligned. Try the exact size
+ * first and keep it when it happens to be aligned; only fall back
+ * to over-allocating and rounding up when it is not.
+ */
+ size = chunk->size;
+ vaddr = dma_alloc_coherent(ab->dev, size, &paddr,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (vaddr && !IS_ALIGNED(paddr, SZ_64K)) {
+ dma_free_coherent(ab->dev, size, vaddr, paddr);
+
+ size = chunk->size + SZ_64K - PAGE_SIZE;
+ vaddr = dma_alloc_coherent(ab->dev, size, &paddr,
GFP_KERNEL | __GFP_NOWARN);
- if (!chunk->v.addr) {
+ }
+
+ if (!vaddr) {
if (chunk->size > ab->hw_params->qmi_max_chunk_size) {
ab->qmi.target_mem_delayed = true;
ath12k_warn(ab,
- "qmi dma allocation failed (%u B type %u), will try later with small size\n",
- chunk->size,
+ "qmi dma allocation failed (%zu B type %u), will try later with small size\n",
+ size,
chunk->type);
ath12k_qmi_free_target_mem_chunk(ab);
return -EAGAIN;
}
- ath12k_warn(ab, "memory allocation failure for %u size: %u\n",
- chunk->type, chunk->size);
+ ath12k_warn(ab, "memory allocation failure for %u size: %zu\n",
+ chunk->type, size);
return -ENOMEM;
}
+
+ chunk->vaddr_unaligned = vaddr;
+ chunk->paddr_unaligned = paddr;
+ chunk->total_size = size;
+
+ chunk->paddr = ALIGN(paddr, SZ_64K);
+ chunk->v.addr = (u8 *)vaddr + (chunk->paddr - paddr);
+
chunk->prev_type = chunk->type;
chunk->prev_size = chunk->size;
this_chunk_done:
diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h
index cbe5be30053a..afa380f5e934 100644
--- a/drivers/net/wireless/ath/ath12k/qmi.h
+++ b/drivers/net/wireless/ath/ath12k/qmi.h
@@ -101,6 +101,10 @@ struct target_mem_chunk {
void __iomem *ioaddr;
void *addr;
} v;
+
+ dma_addr_t paddr_unaligned;
+ void *vaddr_unaligned;
+ u32 total_size;
};
struct target_info {
--
2.25.1
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH ath-next 0/3] wifi: ath12k: align QMI memory to 64 KB boundary
2026-07-30 6:17 [PATCH ath-next 0/3] wifi: ath12k: align QMI memory to 64 KB boundary Baochen Qiang
` (2 preceding siblings ...)
2026-07-30 6:17 ` [PATCH ath-next 3/3] wifi: ath12k: align QMI target memory to 64 KB Baochen Qiang
@ 2026-08-19 7:09 ` Rameshkumar Sundaram
3 siblings, 0 replies; 5+ messages in thread
From: Rameshkumar Sundaram @ 2026-08-19 7:09 UTC (permalink / raw)
To: Baochen Qiang, Jeff Johnson; +Cc: linux-wireless, ath12k
On 7/30/2026 11:47 AM, Baochen Qiang wrote:
> Firmware requests the QMI memory to be 64 KB aligned, or crash
> may happen. Fix it by manually aligning to that boundary.
>
> ---
> Baochen Qiang (3):
> wifi: ath12k: use per-device max QMI chunk size
> wifi: ath12k: clear chunk paddr and size in ath12k_qmi_free_target_mem_chunk()
> wifi: ath12k: align QMI target memory to 64 KB
>
> drivers/net/wireless/ath/ath12k/hw.h | 1 +
> drivers/net/wireless/ath/ath12k/qmi.c | 102 +++++++++++++++++++++--------
> drivers/net/wireless/ath/ath12k/qmi.h | 4 ++
> drivers/net/wireless/ath/ath12k/wifi7/hw.c | 7 ++
> 4 files changed, 87 insertions(+), 27 deletions(-)
> ---
> base-commit: 4fa10e991f77b4c929d1959900a6ed422b9e2ac5
> change-id: 20260710-ath12k-align-qmi-memory-a7c87d626ef4
>
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 5+ messages in thread