Thread (11 messages) 11 messages, 3 authors, 2025-11-08

Re: [PATCH net-next] net: airoha: Add TCP LRO support

From: Eric Dumazet <edumazet@google.com>
Date: 2025-06-10 10:56:48
Also in: linux-mediatek, netdev

On Tue, Jun 10, 2025 at 2:12 AM Lorenzo Bianconi [off-list ref] wrote:
quoted hunk ↗ jump to hunk
EN7581 SoC supports TCP hw Large Receive Offload (LRO) for 8 hw queues.
Introduce TCP LRO support to airoha_eth driver for RX queues 24-31.
In order to support hw TCP LRO, increase page_pool order to 5 for RX
queues 24-31.

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/ethernet/airoha/airoha_eth.c  | 191 +++++++++++++++++++++++++++---
 drivers/net/ethernet/airoha/airoha_eth.h  |  10 ++
 drivers/net/ethernet/airoha/airoha_regs.h |  25 +++-
 3 files changed, 210 insertions(+), 16 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index a7ec609d64dee9c8e901c7eb650bb3fe144ee00a..9378ca384fe2025a40cc528714859dd59300fbcd 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -12,6 +12,7 @@
 #include <net/dst_metadata.h>
 #include <net/page_pool/helpers.h>
 #include <net/pkt_cls.h>
+#include <net/tcp.h>
 #include <uapi/linux/ppp_defs.h>

 #include "airoha_regs.h"
@@ -439,6 +440,40 @@ static void airoha_fe_crsn_qsel_init(struct airoha_eth *eth)
                                 CDM_CRSN_QSEL_Q1));
 }

+static void airoha_fe_lro_init_rx_queue(struct airoha_eth *eth, int qdma_id,
+                                       int lro_queue_index, int qid,
+                                       int nbuf, int buf_size)
+{
+       airoha_fe_rmw(eth, REG_CDM_LRO_LIMIT(qdma_id),
+                     CDM_LRO_AGG_NUM_MASK | CDM_LRO_AGG_SIZE_MASK,
+                     FIELD_PREP(CDM_LRO_AGG_NUM_MASK, nbuf) |
+                     FIELD_PREP(CDM_LRO_AGG_SIZE_MASK, buf_size));
+       airoha_fe_rmw(eth, REG_CDM_LRO_AGE_TIME(qdma_id),
+                     CDM_LRO_AGE_TIME_MASK | CDM_LRO_AGG_TIME_MASK,
+                     FIELD_PREP(CDM_LRO_AGE_TIME_MASK,
+                                AIROHA_RXQ_LRO_MAX_AGE_TIME) |
+                     FIELD_PREP(CDM_LRO_AGG_TIME_MASK,
+                                AIROHA_RXQ_LRO_MAX_AGG_TIME));
+       airoha_fe_rmw(eth, REG_CDM_LRO_RXQ(qdma_id, lro_queue_index),
+                     LRO_RXQ_MASK(lro_queue_index),
+                     qid << __ffs(LRO_RXQ_MASK(lro_queue_index)));
+       airoha_fe_set(eth, REG_CDM_LRO_EN(qdma_id), BIT(lro_queue_index));
+}
+
+static void airoha_fe_lro_disable(struct airoha_eth *eth, int qdma_id)
+{
+       int i;
+
+       airoha_fe_clear(eth, REG_CDM_LRO_LIMIT(qdma_id),
+                       CDM_LRO_AGG_NUM_MASK | CDM_LRO_AGG_SIZE_MASK);
+       airoha_fe_clear(eth, REG_CDM_LRO_AGE_TIME(qdma_id),
+                       CDM_LRO_AGE_TIME_MASK | CDM_LRO_AGG_TIME_MASK);
+       airoha_fe_clear(eth, REG_CDM_LRO_EN(qdma_id), LRO_RXQ_EN_MASK);
+       for (i = 0; i < AIROHA_MAX_NUM_LRO_QUEUES; i++)
+               airoha_fe_clear(eth, REG_CDM_LRO_RXQ(qdma_id, i),
+                               LRO_RXQ_MASK(i));
+}
+
 static int airoha_fe_init(struct airoha_eth *eth)
 {
        airoha_fe_maccr_init(eth);
@@ -618,9 +653,87 @@ static int airoha_qdma_get_gdm_port(struct airoha_eth *eth,
        return port >= ARRAY_SIZE(eth->ports) ? -EINVAL : port;
 }

+static bool airoha_qdma_is_lro_rx_queue(struct airoha_queue *q,
+                                       struct airoha_qdma *qdma)
+{
+       int qid = q - &qdma->q_rx[0];
+
+       /* EN7581 SoC supports at most 8 LRO rx queues */
+       BUILD_BUG_ON(hweight32(AIROHA_RXQ_LRO_EN_MASK) >
+                    AIROHA_MAX_NUM_LRO_QUEUES);
+
+       return !!(AIROHA_RXQ_LRO_EN_MASK & BIT(qid));
+}
+
+static int airoha_qdma_lro_rx_process(struct airoha_queue *q,
+                                     struct airoha_qdma_desc *desc)
+{
+       u32 msg1 = le32_to_cpu(desc->msg1), msg2 = le32_to_cpu(desc->msg2);
+       u32 th_off, tcp_ack_seq, msg3 = le32_to_cpu(desc->msg3);
+       bool ipv4 = FIELD_GET(QDMA_ETH_RXMSG_IP4_MASK, msg1);
+       bool ipv6 = FIELD_GET(QDMA_ETH_RXMSG_IP6_MASK, msg1);
+       struct sk_buff *skb = q->skb;
+       u16 tcp_win, l2_len;
+       struct tcphdr *th;
+
+       if (FIELD_GET(QDMA_ETH_RXMSG_AGG_COUNT_MASK, msg2) <= 1)
+               return 0;
+
+       if (!ipv4 && !ipv6)
+               return -EOPNOTSUPP;
+
+       l2_len = FIELD_GET(QDMA_ETH_RXMSG_L2_LEN_MASK, msg2);
+       if (ipv4) {
+               u16 agg_len = FIELD_GET(QDMA_ETH_RXMSG_AGG_LEN_MASK, msg3);
+               struct iphdr *iph = (struct iphdr *)(skb->data + l2_len);
+
+               if (iph->protocol != IPPROTO_TCP)
+                       return -EOPNOTSUPP;
+
+               iph->tot_len = cpu_to_be16(agg_len);
+               iph->check = 0;
+               iph->check = ip_fast_csum((void *)iph, iph->ihl);
+               th_off = l2_len + (iph->ihl << 2);
+       } else {
+               struct ipv6hdr *ip6h = (struct ipv6hdr *)(skb->data + l2_len);
+               u32 len, desc_ctrl = le32_to_cpu(desc->ctrl);
+
+               if (ip6h->nexthdr != NEXTHDR_TCP)
+                       return -EOPNOTSUPP;
+
+               len = FIELD_GET(QDMA_DESC_LEN_MASK, desc_ctrl);
+               ip6h->payload_len = cpu_to_be16(len - l2_len - sizeof(*ip6h));
+               th_off = l2_len + sizeof(*ip6h);
+       }
+
+       tcp_win = FIELD_GET(QDMA_ETH_RXMSG_TCP_WIN_MASK, msg3);
+       tcp_ack_seq = le32_to_cpu(desc->data);
+
+       th = (struct tcphdr *)(skb->data + th_off);
+       th->ack_seq = cpu_to_be32(tcp_ack_seq);
+       th->window = cpu_to_be16(tcp_win);
+
+       /* check tcp timestamp option */
+       if (th->doff == sizeof(*th) + TCPOLEN_TSTAMP_ALIGNED) {
+               __be32 *topt = (__be32 *)(th + 1);
+
+               if (*topt == cpu_to_be32((TCPOPT_NOP << 24) |
+                                        (TCPOPT_NOP << 16) |
+                                        (TCPOPT_TIMESTAMP << 8) |
+                                        TCPOLEN_TIMESTAMP)) {
+                       u32 tcp_ts_reply = le32_to_cpu(desc->tcp_ts_reply);
+
+                       put_unaligned_be32(tcp_ts_reply, topt + 2);
+               }
+       }
+
+       return 0;
+}
+
 static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
 {
        enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
+       bool lro_queue = airoha_qdma_is_lro_rx_queue(q, q->qdma);
        struct airoha_qdma *qdma = q->qdma;
        struct airoha_eth *eth = qdma->eth;
        int qid = q - &qdma->q_rx[0];
@@ -663,9 +776,14 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
                        __skb_put(q->skb, len);
                        skb_mark_for_recycle(q->skb);
                        q->skb->dev = port->dev;
-                       q->skb->protocol = eth_type_trans(q->skb, port->dev);
                        q->skb->ip_summed = CHECKSUM_UNNECESSARY;
                        skb_record_rx_queue(q->skb, qid);
+
+                       if (lro_queue && (port->dev->features & NETIF_F_LRO) &&
+                           airoha_qdma_lro_rx_process(q, desc) < 0)
+                               goto free_frag;
+
+                       q->skb->protocol = eth_type_trans(q->skb, port->dev);
                } else { /* scattered frame */
                        struct skb_shared_info *shinfo = skb_shinfo(q->skb);
                        int nr_frags = shinfo->nr_frags;
@@ -751,14 +869,16 @@ static int airoha_qdma_rx_napi_poll(struct napi_struct *napi, int budget)
 }

 static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
-                                    struct airoha_qdma *qdma, int ndesc)
+                                    struct airoha_qdma *qdma,
+                                    int ndesc, bool lro_queue)
 {
+       int pp_order = lro_queue ? 5 : 0;
        const struct page_pool_params pp_params = {
-               .order = 0,
-               .pool_size = 256,
+               .order = pp_order,
+               .pool_size = 256 >> pp_order,
                .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
                .dma_dir = DMA_FROM_DEVICE,
-               .max_len = PAGE_SIZE,
+               .max_len = PAGE_SIZE << pp_order,
                .nid = NUMA_NO_NODE,
                .dev = qdma->eth->dev,
                .napi = &q->napi,
@@ -767,7 +887,7 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
        int qid = q - &qdma->q_rx[0], thr;
        dma_addr_t dma_addr;

-       q->buf_size = PAGE_SIZE / 2;
+       q->buf_size = pp_params.max_len / (2 * (1 + lro_queue));
Tell us more... It seems small LRO packets will consume a lot of
space, incurring a small skb->len/skb->truesize ratio, and bad TCP WAN
performance.

And order-5 pages are unlikely to be available in the long run anyway.

LRO support would only make sense if the NIC is able to use multiple
order-0 pages to store the payload.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help