[PATCH 2/3] wifi: ath11k: use kvmalloc() for the TX completion status buffer
From: Yunseong Kim
Date: Mon Nov 02 2026 - 07:39:13 EST
On devices where resume from suspend (e.g. s2idle) goes through the
firmware crash recovery path (ath11k_core_restart() ->
ath11k_core_reconfigure_on_crash()), the driver reinitializes the data
path by calling ath11k_dp_alloc().
ath11k_dp_alloc() allocates a host-side copy of the TX completion ring:
size = sizeof(struct hal_wbm_release_ring) * DP_TX_COMP_RING_SIZE;
dp->tx_ring[i].tx_status = kmalloc(size, GFP_KERNEL);
With 32768 entries of 32 bytes each, this requires a 1 MiB allocation,
corresponding to an order-8 page request from the buddy allocator.
During suspend/resume, the PM core restricts the allocation flags by
clearing __GFP_IO and __GFP_FS from gfp_allowed_mask (via
pm_restrict_gfp_mask()). Under GFP_NOIO, page reclaim cannot perform
I/O to write out dirty pages or swap, and direct memory compaction is
inhibited (gfp_compaction_allowed() returns false without __GFP_IO).
When memory has become fragmented over time, reclaim fails to assemble
a 1 MiB contiguous block, and the order-8 allocation fails with -ENOMEM
even when hundreds of megabytes of total system memory remain free.
The tx_status buffer is accessed purely by host CPU software (memcpy from
the hardware WBM release ring descriptors) and is never mapped to DMA or
device physical addresses. It therefore does not require physically
contiguous pages.
Switch the allocation from kmalloc() to kvmalloc() and kvfree() so that
the allocation can fall back to virtually contiguous memory under
fragmentation, avoiding the reclaim failure.
Additionally, fix the error unwind path in ath11k_dp_alloc(): if an
allocation fails on a multi-ring device or on ring 0, previously allocated
tx_status buffers and initialized txbuf_idr radix trees were never freed
because the caller (ath11k_core_qmi_firmware_ready()) does not invoke
ath11k_dp_free() on failure. Unwind all initialized rings cleanly by
destroying their IDRs and freeing tx_status.
Reported-by: Jeroen Trappers <jeroen.trappers@xxxxxxxxx>
Closes: https://bugs.debian.org/1152007
Fixes: d0998eb84ed3 ("ath11k: optimise ath11k_dp_tx_completion_handler")
Cc: Jeroen Trappers <jeroen.trappers@xxxxxxxxx>
Cc: 1152007@xxxxxxxxxxxxxxx
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yunseong Kim <yunseong.kim@xxxxxxxx>
---
drivers/net/wireless/ath/ath11k/dp.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/dp.c b/drivers/net/wireless/ath/ath11k/dp.c
index be65d73b904b6..45602ec2dce63 100644
--- a/drivers/net/wireless/ath/ath11k/dp.c
+++ b/drivers/net/wireless/ath/ath11k/dp.c
@@ -1040,7 +1040,7 @@ void ath11k_dp_free(struct ath11k_base *ab)
ath11k_dp_tx_pending_cleanup, ab);
idr_destroy(&dp->tx_ring[i].txbuf_idr);
spin_unlock_bh(&dp->tx_ring[i].tx_idr_lock);
- kfree(dp->tx_ring[i].tx_status);
+ kvfree(dp->tx_ring[i].tx_status);
dp->tx_ring[i].tx_status = NULL;
}
@@ -1093,10 +1093,10 @@ int ath11k_dp_alloc(struct ath11k_base *ab)
dp->tx_ring[i].tx_status_head = 0;
dp->tx_ring[i].tx_status_tail = DP_TX_COMP_RING_SIZE - 1;
- dp->tx_ring[i].tx_status = kmalloc(size, GFP_KERNEL);
+ dp->tx_ring[i].tx_status = kvmalloc(size, GFP_KERNEL);
if (!dp->tx_ring[i].tx_status) {
ret = -ENOMEM;
- goto fail_cmn_srng_cleanup;
+ goto fail_tx_status;
}
}
@@ -1107,7 +1107,14 @@ int ath11k_dp_alloc(struct ath11k_base *ab)
return 0;
-fail_cmn_srng_cleanup:
+fail_tx_status:
+ while (i >= 0) {
+ idr_destroy(&dp->tx_ring[i].txbuf_idr);
+ kvfree(dp->tx_ring[i].tx_status);
+ dp->tx_ring[i].tx_status = NULL;
+ i--;
+ }
+
ath11k_dp_srng_common_cleanup(ab);
fail_link_desc_cleanup:
--
2.47.3