]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: atlantic: free stranded TX buffers on ring deinit
authorYangyu Chen <cyy@cyyself.name>
Sun, 2 Aug 2026 15:46:00 +0000 (23:46 +0800)
committerJakub Kicinski <kuba@kernel.org>
Wed, 5 Aug 2026 01:15:33 +0000 (18:15 -0700)
aq_vec_deinit() drains the TX rings with a single aq_ring_tx_clean()
call, which frees at most AQ_CFG_TX_CLEAN_BUDGET (256) descriptors and
stops at hw_head, which no longer moves once aq_vec_stop() has stopped
the hardware and NAPI. Completed descriptors beyond the budget and
everything still posted in [hw_head, sw_tail) keep their skb or
xdp_frame when the interface goes down: aq_vec_ring_free() then frees
the buffer ring and the references are lost for good.

Today this is a silent memory leak on every interface down under
TX/XDP_TX load. With the conversion of the RX path to page_pool posted
for net-next it becomes much more visible: XDP_TX frames carry fragment
references on the RX ring's page_pool, so a single stranded frame keeps
the pool's inflight count above zero forever. page_pool_destroy() then
never completes, the pool is leaked together with its pages, and
"page_pool_release_retry() stalled pool shutdown" is warned every 60
seconds from that point on, on every ifdown, XDP detach or ring resize
under XDP_TX load.

Bring back aq_ring_tx_deinit() as it was before the removal and use it
for teardown again, with one extension: TX rings can hold xdp_frames
nowadays, so release those too. They are returned with
xdp_return_frame() since this runs in process context.

Fixes: eb36bedf28be ("net: aquantia: remove function aq_ring_tx_deinit")
Cc: stable@vger.kernel.org # v4.11+
Reviewed-by: Sukhdeep Singh <sukhdeeps@marvell.com>
Signed-off-by: Yangyu Chen <cyy@cyyself.name>
Acked-by: Mina Almasry <almasrymina@google.com>
Link: https://patch.msgid.link/tencent_EEDC35FAF2750A3A6A0B39BAE0E2C484860A@qq.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/ethernet/aquantia/atlantic/aq_ring.c
drivers/net/ethernet/aquantia/atlantic/aq_ring.h
drivers/net/ethernet/aquantia/atlantic/aq_vec.c

index 8ff07de2bd52401c1099ffc08dcf33e831c876d0..81685a4dc5a6d35e29d77f687843f32bdf9d2c1d 100644 (file)
@@ -360,6 +360,35 @@ out:
        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)
index a70b880ada67e956793140abe19306c520aa1a6f..6431cc62962f83c1b11eec30cec72850b8508640 100644 (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,
index 2f9033ceed8ce3273b900f3d9940c375e8036509..05814fea0f5f75f6d7e5a4b9397cd83dc57aa90f 100644 (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]);
        }