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 <[email protected]> Closes: https://bugs.debian.org/1152007 Fixes: d0998eb84ed3 ("ath11k: optimise ath11k_dp_tx_completion_handler") Cc: Jeroen Trappers <[email protected]> Cc: [email protected] Cc: [email protected] Signed-off-by: Yunseong Kim <[email protected]> --- 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

