mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 01:11:51 -04:00
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:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user