]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
realtek: eth: groundwork for jumbo frames
authorMarkus Stockhausen <markus.stockhausen@gmx.de>
Wed, 15 Jul 2026 09:14:32 +0000 (11:14 +0200)
committerMarkus Stockhausen <markus.stockhausen@gmx.de>
Tue, 21 Jul 2026 07:30:10 +0000 (09:30 +0200)
The Realtek SoC hardware can split incoming packets over multiple buffers.
This allows to receive packets that are larger than the current 2K page
pool limit without changing the design to higher order pages. Add a logic
to handle these fragments.

As a first proof of concept simulate fragmentation by reducing the buffer
size down to 400 bytes. With the current support for standard MTU (1500)
the packets might consist of up to 4 fragments.

Link: https://github.com/openwrt/openwrt/pull/24245
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
target/linux/realtek/files-6.18/drivers/net/ethernet/rtl838x_eth.c

index 7e31d73868271b0ab8fd859ebfff2fa19296bb2c..b3a07124dc6d4376db46c9c8392fc9874e50ede8 100644 (file)
@@ -43,7 +43,8 @@
 #define TX_PAD_EN_838X                 BIT(5)
 
 #define SKB_MTU                                1600
-#define SKB_FRAG_SIZE                  1600
+/* TODO: change this to 1568 after fragment handling has been tested in the wild */
+#define SKB_FRAG_SIZE                  400
 #define SKB_PAD                                MAX(32, L1_CACHE_BYTES)
 #define SKB_HEADROOM_FAST              (SKB_PAD + NET_IP_ALIGN)
 #define SKB_HEADROOM_SLOW              SKB_PAD
@@ -105,6 +106,7 @@ struct rtl838x_rx_q {
        struct rteth_ctrl *ctrl;
        struct napi_struct napi;
        struct page_pool *page_pool;
+       struct sk_buff *skb;
 };
 
 struct rteth_ctrl {
@@ -664,6 +666,7 @@ static void rteth_free_rx_buffers(struct rteth_ctrl *ctrl)
                        page_pool_put_full_page(ctrl->rx_qs[r].page_pool, packet->page, true);
                        packet->page = NULL;
                }
+               rteth_free_skb(&ctrl->rx_qs[r].skb);
        }
 }
 
@@ -710,6 +713,7 @@ static int rteth_setup_ring_buffer(struct rteth_ctrl *ctrl)
                                                   RING_OWN_HW;
                }
 
+               ctrl->rx_qs[r].skb = NULL;
                ctrl->rx_data[r].ring[RTETH_RX_RING_SIZE - 1] |= RING_WRAP;
                ctrl->rx_data[r].slot = 0;
        }
@@ -1120,7 +1124,7 @@ static int rteth_start_xmit(struct sk_buff *skb, struct net_device *dev)
 static struct sk_buff *rteth_create_skb(struct rteth_ctrl *ctrl, int ring,
                                        struct rteth_packet *packet)
 {
-       struct page_pool *pool = ctrl->rx_qs[ring].page_pool;
+       struct page_pool *pool = ctrl->rx_info[ring].pool;
        unsigned int offset = packet->page_offset;
        struct net_device *dev = ctrl->dev;
        struct page *page = packet->page;
@@ -1157,6 +1161,26 @@ static struct sk_buff *rteth_create_skb(struct rteth_ctrl *ctrl, int ring,
        return skb;
 }
 
+static int rteth_append_skb(struct sk_buff *skb, struct rteth_ctrl *ctrl, int ring,
+                            struct rteth_packet *packet)
+{
+       unsigned int nr_frags = skb_shinfo(skb)->nr_frags, len = packet->len;
+       struct page_pool *pool = ctrl->rx_qs[ring].page_pool;
+       unsigned int offset = packet->page_offset;
+       struct page *page = packet->page;
+
+       if (nr_frags >= MAX_SKB_FRAGS) {
+               page_pool_put_full_page(pool, packet->page, true);
+               return -ENOMEM;
+       }
+
+       page_pool_dma_sync_for_cpu(pool, page, offset + ctrl->r->skb_headroom, len);
+       skb_add_rx_frag(skb, nr_frags, page, offset + ctrl->r->skb_headroom,
+                       len, PPOOL_FRAG_SIZE);
+
+       return 0;
+}
+
 static int rteth_hw_receive(struct net_device *dev, int ring, int budget)
 {
        int slot, work_done = 0, rx_packets = 0, rx_bytes = 0, rx_dropped = 0, rx_errors = 0;
@@ -1166,11 +1190,12 @@ static int rteth_hw_receive(struct net_device *dev, int ring, int budget)
        struct page_pool *pool;
        struct page *new_page;
        dma_addr_t packet_dma;
+       bool is_head, is_tail;
        struct sk_buff *skb;
-       struct dsa_tag tag;
-       bool is_tail;
 
        pool = ctrl->rx_qs[ring].page_pool;
+       skb = ctrl->rx_qs[ring].skb;
+       is_tail = !skb;
 
        while (work_done < budget) {
                slot = ctrl->rx_data[ring].slot;
@@ -1182,16 +1207,20 @@ static int rteth_hw_receive(struct net_device *dev, int ring, int budget)
 
                packet = &ctrl->rx_data[ring].packet[slot];
                len = packet->len;
+               is_head = is_tail;
                is_tail = !packet->more;
                if (is_tail)
                        work_done++;
 
                if (unlikely(len > SKB_FRAG_SIZE)) {
                        netdev_err(dev, "invalid fragment with %d bytes received\n", len);
-                       rx_errors++;
+                       rx_errors += rteth_free_skb(&skb);
                        goto recycle;
                }
 
+               if (unlikely(!skb && !is_head))
+                       goto recycle;
+
                /*
                 * Avoid complex error handling by allocating the new page before SKB consumption.
                 * In case of failure drop the data and reuse the existing page.
@@ -1199,14 +1228,21 @@ static int rteth_hw_receive(struct net_device *dev, int ring, int budget)
                new_page = page_pool_dev_alloc_frag(pool, &new_offset, PPOOL_FRAG_SIZE);
                if (unlikely(!new_page)) {
                        netdev_err(dev, "fragment allocation failed\n");
-                       rx_dropped++;
+                       rx_dropped += rteth_free_skb(&skb);
                        goto recycle;
                }
 
-               skb = rteth_create_skb(ctrl, ring, packet);
-               if (unlikely(!skb)) {
-                       netdev_err(dev, "skb creation failed\n");
-                       dev->stats.rx_dropped++;
+               if (is_head) {
+                       skb = rteth_create_skb(ctrl, ring, packet);
+                       if (unlikely(!skb)) {
+                               netdev_err(dev, "skb creation failed\n");
+                               rx_dropped++;
+                       }
+               } else {
+                       if (unlikely(rteth_append_skb(skb, ctrl, ring, packet))) {
+                               netdev_err(dev, "skb append failed\n");
+                               rx_dropped += rteth_free_skb(&skb);
+                       }
                }
 
                if (is_tail && skb) {
@@ -1240,6 +1276,8 @@ recycle:
        dev->stats.rx_bytes += rx_bytes;
        spin_unlock(&ctrl->rx_lock);
 
+       ctrl->rx_qs[ring].skb = skb;
+
        return work_done;
 }