Netdev List
 help / color / mirror / Atom feed
* [PATCH v5] r8169: migrate Rx path to page_pool, prepare for XDP
@ 2026-07-13  3:50 Atharva Potdar
  0 siblings, 0 replies; only message in thread
From: Atharva Potdar @ 2026-07-13  3:50 UTC (permalink / raw)
  To: Heiner Kallweit, nic_swsd, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: Francois Romieu, netdev, atharvapotdar07

Replace the alloc_pages() + dma_map_page() + skb_copy() Rx model with
a page_pool-backed napi_build_skb() path for zero-copy delivery.
PP_FLAG_DMA_MAP and PP_FLAG_DMA_SYNC_DEV delegate DMA mapping and
cache sync to the page_pool core for correct cross-architecture
behaviour.

MACs up to RTL_GIGA_MAC_VER_06 are kept on the copy path with no
headroom. Newer MACs reserve XDP_PACKET_HEADROOM (256 B) per buffer
via pp_params.offset and desc->addr, avoiding pskb_expand_head()
reallocations on forwarded traffic and establishing the headroom
contract expected by later XDP patches.

The pool uses order-2 (SZ_16K) pages on all MACs. Old silicon in this
family ignores the descriptor buffer size limit, allowing oversized
packets to corrupt memory (see fdd7b4c3302c, c0cd884af045,
6f0333b8fde4). opts1 and napi_build_skb() both derive their sizes
from tp->rx_buf_sz = SZ_16K - SKB_DATA_ALIGN(sizeof(struct
skb_shared_info)) - rx_headroom, keeping the NIC write window and skb
tailroom consistent and within the allocation.

Rx consumption (cur_rx) is decoupled from refill (dirty_rx).
xdp_rxq_info is registered at pool creation time for the MEM_TYPE_
PAGE_POOL return path required by XDP redirect.

Signed-off-by: Atharva Potdar <atharvapotdar07@gmail.com>
---
Testing on RTL8168h, x86, bare metal:
- Verified napi_build_skb() is taken for all Rx packets via bpftrace.
  No copy-path fallback observed at 1Gbps line rate.
- Confirmed 256B skb headroom on eno1 via bpftrace (403091 packets).
  No pskb_expand_head() calls during routed traffic.
- Cycled ip link down/up under load. Ring repopulates cleanly after
  rtl_reset_work(); no stale descriptor accesses.
- 9000-byte MTU TCP streams at line rate (992 Mbps). nstat showed no
  TcpExtTCPRcvQDrop or OfoPrune regressions.

v5:
 - Introduced tp->rx_buf_sz, derived from SZ_16K -
   SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) - rx_headroom.
   Used for both opts1 and napi_build_skb() frag_size, fixing a
   255-byte DMA overflow past the end of the order-2 page on modern
   MACs and a potential skb_put() panic on large frames.
 - Fixed rtl8169_rx_clear() to iterate all NUM_RX_DESC entries
   unconditionally. Early exit on NULL leaked pool pages on teardown
   after any zero-copy traffic.
 - Fixed rtl_reset_work() to repopulate NULL Rx_databuff slots before
   re-arming descriptors, preventing DMA into stale addresses after
   a reset.
 - Removed spurious #include <linux/if_link.h>.
 - Fixed napi_build_skb() frag_size: must be the total page allocation
   (SZ_16K), not the pre-reduced rx_buf_sz + rx_headroom. The function
   subtracts SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) internally;
   passing an already-reduced value caused a double subtraction, giving
   skb tailroom SBI_ALIGN bytes smaller than opts1 permits and risking
   skb_over_panic on large frames.
v4:
 - Bifurcated headroom by MAC version (0 for legacy, 256 for modern).
   MACs <= RTL_GIGA_MAC_VER_06 retained on copy path as a conservative
   choice given limited test coverage, not a known version-specific
   erratum.
 - Locked pool to order-2 allocations matching R8169_RX_BUF_SIZE.
   Old silicon ignores the descriptor buffer size limit; undersized
   buffers cause memory corruption (fdd7b4c3302c, c0cd884af045,
   6f0333b8fde4).
 - Fixed prefetch offset to align with payload start.
v3:
 - Reserved XDP_PACKET_HEADROOM per buffer to prevent pskb_expand_head()
   on forwarded traffic.
 - Decoupled Rx consumption (cur_rx) from buffer refill (dirty_rx).
 - Registered xdp_rxq_info mem model at pool creation time.
v2:
 - Reverted buffer size to SZ_16K to prevent MTU regression and restore
   protection against the chipset buffer-limit erratum.
v1:
 - Initial page_pool migration.

 drivers/net/ethernet/realtek/r8169_main.c | 173 ++++++++++++++++++----
 1 file changed, 141 insertions(+), 32 deletions(-)

diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index ec4fc21fa..c548e7e8d 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -31,7 +31,9 @@
 #include <linux/unaligned.h>
 #include <net/ip6_checksum.h>
 #include <net/netdev_queues.h>
+#include <net/page_pool/helpers.h>
 #include <net/phy/realtek_phy.h>
+#include <net/xdp.h>
 
 #include "r8169.h"
 #include "r8169_firmware.h"
@@ -729,6 +731,10 @@ enum rtl_dash_type {
 };
 
 struct rtl8169_private {
+	struct page_pool *rx_pool;
+	struct xdp_rxq_info xdp_rxq;
+	u32 rx_headroom;
+	u32 rx_buf_sz;
 	void __iomem *mmio_addr;	/* memory map physical address */
 	struct pci_dev *pci_dev;
 	struct net_device *dev;
@@ -739,6 +745,7 @@ struct rtl8169_private {
 	u32 cur_rx; /* Index into the Rx descriptor buffer of next Rx pkt. */
 	u32 cur_tx; /* Index into the Tx descriptor buffer of next Rx pkt. */
 	u32 dirty_tx;
+	u32 dirty_rx;
 	struct TxDesc *TxDescArray;	/* 256-aligned Tx descriptor ring */
 	struct RxDesc *RxDescArray;	/* 256-aligned Rx descriptor ring */
 	dma_addr_t TxPhyAddr;
@@ -2622,6 +2629,7 @@ static void rtl_init_rxcfg(struct rtl8169_private *tp)
 static void rtl8169_init_ring_indexes(struct rtl8169_private *tp)
 {
 	tp->dirty_tx = tp->cur_tx = tp->cur_rx = 0;
+	tp->dirty_rx = 0;
 }
 
 static void rtl_jumbo_config(struct rtl8169_private *tp)
@@ -4148,37 +4156,30 @@ static int rtl8169_change_mtu(struct net_device *dev, int new_mtu)
 	return 0;
 }
 
-static void rtl8169_mark_to_asic(struct RxDesc *desc)
+static void rtl8169_mark_to_asic(struct RxDesc *desc, u32 rx_buf_sz)
 {
 	u32 eor = le32_to_cpu(desc->opts1) & RingEnd;
 
 	desc->opts2 = 0;
 	/* Force memory writes to complete before releasing descriptor */
 	dma_wmb();
-	WRITE_ONCE(desc->opts1, cpu_to_le32(DescOwn | eor | R8169_RX_BUF_SIZE));
+	WRITE_ONCE(desc->opts1, cpu_to_le32(DescOwn | eor | rx_buf_sz));
 }
 
 static struct page *rtl8169_alloc_rx_data(struct rtl8169_private *tp,
 					  struct RxDesc *desc)
 {
-	struct device *d = tp_to_dev(tp);
-	int node = dev_to_node(d);
 	dma_addr_t mapping;
 	struct page *data;
 
-	data = alloc_pages_node(node, GFP_KERNEL, get_order(R8169_RX_BUF_SIZE));
+	data = page_pool_dev_alloc_pages(tp->rx_pool);
 	if (!data)
 		return NULL;
 
-	mapping = dma_map_page(d, data, 0, R8169_RX_BUF_SIZE, DMA_FROM_DEVICE);
-	if (unlikely(dma_mapping_error(d, mapping))) {
-		netdev_err(tp->dev, "Failed to map RX DMA!\n");
-		__free_pages(data, get_order(R8169_RX_BUF_SIZE));
-		return NULL;
-	}
+	mapping = page_pool_get_dma_addr(data) + tp->rx_headroom;
 
 	desc->addr = cpu_to_le64(mapping);
-	rtl8169_mark_to_asic(desc);
+	rtl8169_mark_to_asic(desc, tp->rx_buf_sz);
 
 	return data;
 }
@@ -4187,15 +4188,21 @@ static void rtl8169_rx_clear(struct rtl8169_private *tp)
 {
 	int i;
 
-	for (i = 0; i < NUM_RX_DESC && tp->Rx_databuff[i]; i++) {
-		dma_unmap_page(tp_to_dev(tp),
-			       le64_to_cpu(tp->RxDescArray[i].addr),
-			       R8169_RX_BUF_SIZE, DMA_FROM_DEVICE);
-		__free_pages(tp->Rx_databuff[i], get_order(R8169_RX_BUF_SIZE));
+	for (i = 0; i < NUM_RX_DESC; i++) {
+		if (!tp->Rx_databuff[i])
+			continue;
+		page_pool_put_full_page(tp->rx_pool, tp->Rx_databuff[i], false);
 		tp->Rx_databuff[i] = NULL;
 		tp->RxDescArray[i].addr = 0;
 		tp->RxDescArray[i].opts1 = 0;
 	}
+
+	if (tp->rx_pool) {
+		if (xdp_rxq_info_is_reg(&tp->xdp_rxq))
+			xdp_rxq_info_unreg(&tp->xdp_rxq);
+		page_pool_destroy(tp->rx_pool);
+		tp->rx_pool = NULL;
+	}
 }
 
 static int rtl8169_rx_fill(struct rtl8169_private *tp)
@@ -4221,12 +4228,57 @@ static int rtl8169_rx_fill(struct rtl8169_private *tp)
 
 static int rtl8169_init_ring(struct rtl8169_private *tp)
 {
+	struct page_pool_params params = {0};
+	int err;
+
 	rtl8169_init_ring_indexes(tp);
 
+	if (tp->mac_version <= RTL_GIGA_MAC_VER_06) {
+		tp->rx_headroom = 0;
+		tp->rx_buf_sz = R8169_RX_BUF_SIZE;
+	} else {
+		tp->rx_headroom = XDP_PACKET_HEADROOM;
+		tp->rx_buf_sz = SZ_16K -
+						SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) -
+						XDP_PACKET_HEADROOM;
+	}
+
+	params.order = get_order(R8169_RX_BUF_SIZE);
+	params.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
+	params.pool_size = NUM_RX_DESC;
+	params.nid = dev_to_node(tp_to_dev(tp));
+	params.dev = tp_to_dev(tp);
+	params.napi = &tp->napi;
+	params.dma_dir = DMA_FROM_DEVICE;
+	params.offset = tp->rx_headroom;
+	params.max_len = tp->rx_buf_sz;
+
+	tp->rx_pool = page_pool_create(&params);
+	if (IS_ERR(tp->rx_pool)) {
+		err = PTR_ERR(tp->rx_pool);
+		tp->rx_pool = NULL;
+		return err;
+	}
+
+	err = xdp_rxq_info_reg(&tp->xdp_rxq, tp->dev, 0, tp->napi.napi_id);
+	if (err)
+		goto err_free_pool;
+
+	err = xdp_rxq_info_reg_mem_model(&tp->xdp_rxq, MEM_TYPE_PAGE_POOL, tp->rx_pool);
+	if (err)
+		goto err_unreg_rxq;
+
 	memset(tp->tx_skb, 0, sizeof(tp->tx_skb));
 	memset(tp->Rx_databuff, 0, sizeof(tp->Rx_databuff));
 
 	return rtl8169_rx_fill(tp);
+
+err_unreg_rxq:
+	xdp_rxq_info_unreg(&tp->xdp_rxq);
+err_free_pool:
+	page_pool_destroy(tp->rx_pool);
+	tp->rx_pool = NULL;
+	return err;
 }
 
 static void rtl8169_unmap_tx_skb(struct rtl8169_private *tp, unsigned int entry)
@@ -4311,8 +4363,20 @@ static void rtl_reset_work(struct rtl8169_private *tp)
 
 	rtl8169_cleanup(tp);
 
-	for (i = 0; i < NUM_RX_DESC; i++)
-		rtl8169_mark_to_asic(tp->RxDescArray + i);
+	for (i = 0; i < NUM_RX_DESC; i++) {
+		if (tp->Rx_databuff[i])
+			continue;
+		tp->Rx_databuff[i] =
+			rtl8169_alloc_rx_data(tp, tp->RxDescArray + i);
+		if (!tp->Rx_databuff[i])
+			break;
+	}
+
+	for (i = 0; i < NUM_RX_DESC; i++) {
+		if (!tp->Rx_databuff[i])
+			continue;
+		rtl8169_mark_to_asic(tp->RxDescArray + i, tp->rx_buf_sz);
+	}
 
 	napi_enable(&tp->napi);
 	rtl_hw_start(tp);
@@ -4768,16 +4832,39 @@ static inline void rtl8169_rx_csum(struct sk_buff *skb, u32 opts1)
 		skb_checksum_none_assert(skb);
 }
 
+static void rtl8169_rx_refill(struct rtl8169_private *tp)
+{
+	while (tp->dirty_rx != tp->cur_rx) {
+		unsigned int entry = tp->dirty_rx % NUM_RX_DESC;
+		struct RxDesc *desc = tp->RxDescArray + entry;
+
+		if (!tp->Rx_databuff[entry]) {
+			struct page *new_page = page_pool_dev_alloc_pages(tp->rx_pool);
+
+			if (unlikely(!new_page))
+				break;
+
+			tp->Rx_databuff[entry] = new_page;
+
+			desc->addr = cpu_to_le64(page_pool_get_dma_addr(new_page) +
+				tp->rx_headroom);
+		}
+		rtl8169_mark_to_asic(desc, tp->rx_buf_sz);
+
+		tp->dirty_rx++;
+	}
+}
+
 static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp, int budget)
 {
 	struct device *d = tp_to_dev(tp);
 	int count;
 
-	for (count = 0; count < budget; count++, tp->cur_rx++) {
+	for (count = 0; count < budget;) {
 		unsigned int pkt_size, entry = tp->cur_rx % NUM_RX_DESC;
 		struct RxDesc *desc = tp->RxDescArray + entry;
 		struct sk_buff *skb;
-		const void *rx_buf;
+		void *rx_buf;
 		dma_addr_t addr;
 		u32 status;
 
@@ -4820,21 +4907,40 @@ static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp, int budget
 			goto release_descriptor;
 		}
 
-		skb = napi_alloc_skb(&tp->napi, pkt_size);
-		if (unlikely(!skb)) {
-			dev->stats.rx_dropped++;
-			goto release_descriptor;
-		}
+		if (unlikely(!tp->Rx_databuff[entry]))
+			break;
 
 		addr = le64_to_cpu(desc->addr);
 		rx_buf = page_address(tp->Rx_databuff[entry]);
 
 		dma_sync_single_for_cpu(d, addr, pkt_size, DMA_FROM_DEVICE);
-		prefetch(rx_buf);
-		skb_copy_to_linear_data(skb, rx_buf, pkt_size);
-		skb->tail += pkt_size;
-		skb->len = pkt_size;
-		dma_sync_single_for_device(d, addr, pkt_size, DMA_FROM_DEVICE);
+		prefetch(rx_buf + tp->rx_headroom);
+
+		if (unlikely(tp->rx_headroom == 0)) {
+			skb = napi_alloc_skb(&tp->napi, pkt_size);
+			if (likely(skb)) {
+				skb_copy_to_linear_data(skb, rx_buf, pkt_size);
+				skb_put(skb, pkt_size);
+			}
+			dma_sync_single_for_device(d, addr, pkt_size, DMA_FROM_DEVICE);
+		} else {
+			skb = napi_build_skb(rx_buf, tp->rx_buf_sz + tp->rx_headroom
+				+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info)));
+			if (likely(skb)) {
+				skb_reserve(skb, tp->rx_headroom);
+				skb_put(skb, pkt_size);
+				skb_mark_for_recycle(skb);
+
+				tp->Rx_databuff[entry] = NULL;
+			} else {
+				dma_sync_single_for_device(d, addr, pkt_size, DMA_FROM_DEVICE);
+			}
+		}
+
+		if (unlikely(!skb)) {
+			dev->stats.rx_dropped++;
+			goto release_descriptor;
+		}
 
 		rtl8169_rx_csum(skb, status);
 		skb->protocol = eth_type_trans(skb, dev);
@@ -4848,9 +4954,12 @@ static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp, int budget
 
 		dev_sw_netstats_rx_add(dev, pkt_size);
 release_descriptor:
-		rtl8169_mark_to_asic(desc);
+		tp->cur_rx++;
+		count++;
 	}
 
+	rtl8169_rx_refill(tp);
+
 	return count;
 }
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-07-13  3:51 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-13  3:50 [PATCH v5] r8169: migrate Rx path to page_pool, prepare for XDP Atharva Potdar

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox