Merge branch 'net-atlantic-fix-two-ring-teardown-leaks'

Yangyu Chen says:

====================
net: atlantic: fix two ring teardown leaks

These are the two fixes from the page_pool conversion series [1],
resent against net as requested in the review of that series. The
page_pool conversion itself stays in net-next and is not part of this
posting; it depends on these fixes, but they stand on their own.

Both patches are unchanged from [1] apart from the collected
Reviewed-by tags, and each carries a Fixes tag and a Cc: stable with
the affected range (patch 1: v4.11+, patch 2: v5.2+). They apply and
were build- and runtime-tested independently of each other and of the
conversion.

Patch 1: aq_vec_deinit() drains the TX rings with a single
aq_ring_tx_clean() call, which is capped at AQ_CFG_TX_CLEAN_BUDGET
descriptors and stops at hw_head, frozen once the hardware and NAPI
have been stopped. Everything beyond that keeps its skb or xdp_frame
when the interface goes down and is lost when the buffer ring is
freed.

Patch 2: aq_ring_rx_deinit() only walks [sw_head, sw_tail). Since the
page reuse strategy was added, a cleaned RX buffer keeps its page for
reuse and refill is batched, so consumed but not yet reposted slots
accumulate in the [sw_tail, sw_head) gap and their pages and DMA
mappings are never released.

Reproduction logs for both leaks (as page_pool stalled shutdowns,
which is how they become visible) are in the notes of the respective
patches.

[1] https://lore.kernel.org/lkml/tencent_1F173E0FC1606D2AC704DC9C98AF10984607@qq.com/
====================

Link: https://patch.msgid.link/tencent_29B860317921D68DE77C718242DA418EB608@qq.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2026-08-04 18:15:35 -07:00
3 changed files with 49 additions and 5 deletions

View File

@@ -360,6 +360,35 @@ bool aq_ring_tx_clean(struct aq_ring_s *self)
return !!budget;
}
void aq_ring_tx_deinit(struct aq_ring_s *self)
{
if (!self)
return;
for (; self->sw_head != self->sw_tail;
self->sw_head = aq_ring_next_dx(self, self->sw_head)) {
struct aq_ring_buff_s *buff = &self->buff_ring[self->sw_head];
struct device *ndev = aq_nic_get_dev(self->aq_nic);
if (buff->is_mapped) {
if (buff->is_sop) {
dma_unmap_single(ndev, buff->pa, buff->len,
DMA_TO_DEVICE);
} else {
dma_unmap_page(ndev, buff->pa, buff->len,
DMA_TO_DEVICE);
}
}
if (buff->is_eop) {
if (buff->skb)
dev_kfree_skb_any(buff->skb);
else if (buff->xdpf)
xdp_return_frame(buff->xdpf);
}
}
}
static void aq_rx_checksum(struct aq_ring_s *self,
struct aq_ring_buff_s *buff,
struct sk_buff *skb)
@@ -921,15 +950,29 @@ int aq_ring_rx_fill(struct aq_ring_s *self)
void aq_ring_rx_deinit(struct aq_ring_s *self)
{
if (!self)
unsigned int i;
if (!self || !self->buff_ring)
return;
for (; self->sw_head != self->sw_tail;
self->sw_head = aq_ring_next_dx(self, self->sw_head)) {
struct aq_ring_buff_s *buff = &self->buff_ring[self->sw_head];
/* Release every page still owned by the ring.
*
* Walking [sw_head, sw_tail) is not enough: refill is batched
* (aq_ring_rx_fill() waits for AQ_CFG_RX_REFILL_THRES free slots),
* so slots that were cleaned but not yet reposted accumulate in the
* [sw_tail, sw_head) gap, and they keep their page for reuse. Walk
* the whole ring and release whatever is left.
*/
for (i = 0; i < self->size; i++) {
struct aq_ring_buff_s *buff = &self->buff_ring[i];
if (!buff->rxdata.page)
continue;
aq_free_rxpage(&buff->rxdata, aq_nic_get_dev(self->aq_nic));
}
self->sw_head = self->sw_tail;
}
void aq_ring_free(struct aq_ring_s *self)

View File

@@ -202,6 +202,7 @@ void aq_ring_update_queue_state(struct aq_ring_s *ring);
void aq_ring_queue_wake(struct aq_ring_s *ring);
void aq_ring_queue_stop(struct aq_ring_s *ring);
bool aq_ring_tx_clean(struct aq_ring_s *self);
void aq_ring_tx_deinit(struct aq_ring_s *self);
int aq_xdp_xmit(struct net_device *dev, int num_frames,
struct xdp_frame **frames, u32 flags);
int aq_ring_rx_clean(struct aq_ring_s *self,

View File

@@ -275,7 +275,7 @@ void aq_vec_deinit(struct aq_vec_s *self)
for (i = 0U; self->tx_rings > i; ++i) {
ring = self->ring[i];
aq_ring_tx_clean(&ring[AQ_VEC_TX_ID]);
aq_ring_tx_deinit(&ring[AQ_VEC_TX_ID]);
aq_ring_rx_deinit(&ring[AQ_VEC_RX_ID]);
}