Add Receive Segment Coalesce (RSC) support, exposed as the DPDK
RTE_ETH_RX_OFFLOAD_TCP_LRO Rx offload, for the ENETC4 PF and VF on
i.MX95. RSC lets the HW coalesce multiple in-order TCP segments of a
flow into a single receive frame, reducing per-packet overhead.

RSC is a port-level offload driven by a dedicated Rx burst
(enetc_recv_pkts_rsc). When TCP LRO is requested the ring is switched
to 32B extended receive descriptors (RBaMR[BDS] = 1), the FCS is
stripped (RBaMR[CRC] = 0, so RSC is incompatible with KEEP_CRC),
interrupt coalescing is enabled (RBaICR0[ICEN] with a non-zero
RBaICR1[ICTT] hold timer that doubles as the RSC flush window), and
RBaRSCR[EN] is set with the coalesced-frame size clamped to the HW
maximum.

Because a 32B descriptor spans two 16B ring slots, the ring length
register (RBaLENR) and the consumer-index register are programmed in
HW-descriptor units (slot count / 2), while the internal SW indices
continue to track 16B slots. Coalesced clusters are delivered with the
RTE_MBUF_F_RX_LRO flag set when RSC_FRAMES > 1.

Signed-off-by: Gagandeep Singh <[email protected]>
---
 doc/guides/nics/enetc4.rst             |   4 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/base/enetc4_hw.h     |  74 ++++++++
 drivers/net/enetc/enetc.h              |   5 +
 drivers/net/enetc/enetc4_ethdev.c      | 102 ++++++++++-
 drivers/net/enetc/enetc4_vf.c          |   1 +
 drivers/net/enetc/enetc_rxtx.c         | 239 ++++++++++++++++++++++++-
 8 files changed, 418 insertions(+), 9 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 4d27e4048e..e7f4348603 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -55,6 +55,10 @@ Key functionality includes:
 - Transmission: Packet transmission precedes reception, ensuring efficient 
data transfer.
 - TCP and UDP segmentation offload (TSO) on VFs, enabled per Tx queue
   when the TSO offload flag is requested.
+- Large receive offload (LRO) on the receive path via hardware Receive
+  Segment Coalesce (RSC), enabled when the TCP LRO Rx offload flag is
+  requested. RSC requires the FCS to be stripped, so it cannot be combined
+  with the KEEP_CRC Rx offload.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini 
b/doc/guides/nics/features/enetc4.ini
index 8ec1e8d00f..aae398e210 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -7,6 +7,7 @@
 Link status event    = Y
 Speed capabilities   = Y
 Link status          = Y
+LRO                  = Y
 TSO                  = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst 
b/doc/guides/rel_notes/release_26_11.rst
index 6a348ab4e0..0dd08e0259 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -62,6 +62,7 @@ New Features
 
   * Added KEEP_CRC Rx offload support for the ENETC4 PMD to preserve the 
Ethernet FCS.
   * Added TCP Segmentation Offload (TSO) support for the ENETC4 VF.
+  * Added Receive Segment Coalesce (RSC / hardware LRO) support for ENETC4 PF 
and VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h 
b/drivers/net/enetc/base/enetc4_hw.h
index 64c1a5e750..8ad8f169d2 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -103,7 +103,81 @@ struct enetc_tx_bd_ext {
 
 /* RBaMR[CRC]: 0 = FCS removed, 1 = FCS preserved (KEEP_CRC) */
 #define ENETC4_RBMR_CRC                        BIT(8)
+/*
+ * RBaMR[BDS]: buffer descriptor size select for a receive ring.
+ * 0 = standard 16B descriptors, 1 = extended 32B descriptors.
+ * RSC requires 32B descriptors (BDS = 1). Matches the Linux enetc
+ * driver definition (ENETC_RBMR_BDS = BIT(2)).
+ */
+#define ENETC4_RBMR_BDS                        BIT(2)
+
+/*
+ * Rx BDR a RSC register (RBaRSCR), offset 0x30 from the ring base.
+ * Controls Receive Segment Coalesce (RSC / LRO) for the ring.
+ */
+#define ENETC4_RBRSCR                  0x30
+/* Enable RSC on this ring */
+#define ENETC4_RBRSCR_EN               BIT(31)
+/* Permit coalescing of TCP segments that carry the timestamp option */
+#define ENETC4_RBRSCR_CT               BIT(29)
+/* SIZE field (bits 15-0): maximum coalesced frame size produced by RSC */
+#define ENETC4_RBRSCR_SIZE(x)          ((uint32_t)((x) & 0xffff))
+
+/*
+ * RBaICR0[ICEN]: interrupt coalescing enable. RSC requires interrupt
+ * coalescing to be enabled; the coalescing timer doubles as the RSC flush
+ * timer. ICPT (bits 8-0) is the packet-count threshold.
+ */
+#define ENETC4_RBICR0                  0xa8
+#define ENETC4_RBICR0_ICEN             BIT(31)
+#define ENETC4_RBICR0_ICPT(x)          ((uint32_t)((x) & 0x1ff))
+/* Rx BDR a interrupt coalescing register 1 (threshold timer) */
+#define ENETC4_RBICR1                  0xac
+
+/*
+ * SI-level Rx interrupt detect register 0 (SIRXIDR0). W1C, one bit per Rx
+ * ring (RX0..RX23). The interrupt-coalescing timer (which also gates RSC
+ * coalescing) does not re-arm while a ring's detect bit stays set, so the
+ * poll-mode driver writes BIT(ring index) here every poll to keep RSC
+ * coalescing. The per-ring RBaIDR (0xa4) is read-only and cannot be used.
+ */
+#define ENETC_SIRXIDR                  0xa28
+
+/*
+ * RSC (Receive Segment Coalesce) limits and defaults.
+ * Maximum coalesced frame size the HW will build (programmed in 
RBaRSCR[SIZE]).
+ * Bounded to 16 bits by the SIZE field width.
+ */
+#define ENETC4_RSC_MAX_FRAME           0xffff
+/* Default interrupt coalescing packet threshold used to satisfy RSC's ICEN
+ * precondition. The PMD is poll-mode, so this only gates the RSC flush timer.
+ */
+#define ENETC4_RSC_DEF_ICPT            1
+/*
+ * Default interrupt coalescing timer threshold (RBaICR1[ICTT]), in NETC
+ * platform clock cycles. This timer is the RSC coalesce-hold window: HW keeps
+ * a coalesced frame open while it runs and appends in-order segments. A value
+ * of 0 disables the timer and flushes every segment separately (no
+ * coalescing), so it must be non-zero for RSC to merge anything.
+ */
+#define ENETC4_RSC_DEF_ICTT            0x10000
+
+/*
+ * Extended 32B receive writeback buffer descriptor. Used only on RSC-enabled
+ * rings (RBaMR[BDS] = 1). The first 16 bytes match the standard descriptor
+ * writeback layout; the second 16 bytes carry the RSC and timestamp fields.
+ * RSC_FRAMES reports how many frames were coalesced (1 to 255; 1 means the
+ * frame was not coalesced).
+ */
+struct enetc_rx_bd_ext {
+       uint32_t timestamp;     /* offset 0x10: PTP timestamp */
+       uint32_t rsc_frames;    /* offset 0x14: RSC_FRAMES in bits 7-0 */
+       uint32_t rsc_abs_ts_delta; /* offset 0x18 */
+       uint32_t reserved;      /* offset 0x1c */
+};
 
+/* RSC_FRAMES occupies bits 7-0 of the rsc_frames word */
+#define ENETC4_RXBD_EXT_RSC_FRAMES(x)  ((x) & 0xff)
 
 /* i.MX95 supports jumbo frame, but it is recommended to set the max frame
  * size to 2000 bytes.
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index db349e051b..67f8ce1919 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -101,6 +101,8 @@ struct enetc_bdr {
        uint8_t rx_deferred_start;
        uint8_t tx_deferred_start;
        uint8_t lso_enable;
+       void *rbidr;
+       uint8_t rsc_enable;
 };
 
 struct enetc_eth_hw {
@@ -321,12 +323,15 @@ uint16_t enetc_recv_pkts(void *rxq, struct rte_mbuf 
**rx_pkts,
                uint16_t nb_pkts);
 uint16_t enetc_recv_pkts_nc(void *rxq, struct rte_mbuf **rx_pkts,
                uint16_t nb_pkts);
+uint16_t enetc_recv_pkts_rsc(void *rxq, struct rte_mbuf **rx_pkts,
+               uint16_t nb_pkts);
 uint16_t enetc_xmit_pkts_cacheable(void *txq, struct rte_mbuf **tx_pkts,
                uint16_t nb_pkts);
 uint16_t enetc_recv_pkts_cacheable(void *rxq, struct rte_mbuf **rx_pkts,
                uint16_t nb_pkts);
 
 int enetc_refill_rx_ring(struct enetc_bdr *rx_ring, const int buff_cnt);
+int enetc_refill_rx_ring_rsc(struct enetc_bdr *rx_ring, const int buff_cnt);
 
 /*
  * Cache-maintenance constants for cacheable BD ring mode.
diff --git a/drivers/net/enetc/enetc4_ethdev.c 
b/drivers/net/enetc/enetc4_ethdev.c
index 897e12e21f..7f95171b6d 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -16,7 +16,8 @@ static uint64_t dev_rx_offloads_sup =
        RTE_ETH_RX_OFFLOAD_IPV4_CKSUM |
        RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
        RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
-       RTE_ETH_RX_OFFLOAD_KEEP_CRC;
+       RTE_ETH_RX_OFFLOAD_KEEP_CRC |
+       RTE_ETH_RX_OFFLOAD_TCP_LRO;
 
 /* Supported Tx offloads */
 static uint64_t dev_tx_offloads_sup =
@@ -314,6 +315,8 @@ enetc4_dev_infos_get(struct rte_eth_dev *dev,
        dev_info->max_rx_pktlen = ENETC4_MAC_MAXFRM_SIZE;
        dev_info->rx_offload_capa = dev_rx_offloads_sup;
        dev_info->tx_offload_capa = dev_tx_offloads_sup;
+       /* Max RSC (LRO) coalesced frame size; matches RBaRSCR[SIZE]. */
+       dev_info->max_lro_pkt_size = ENETC4_RSC_MAX_FRAME;
        dev_info->flow_type_rss_offloads = ENETC_RSS_OFFLOAD_ALL;
 
        return 0;
@@ -520,22 +523,29 @@ static int
 enetc4_alloc_rxbdr(struct enetc_bdr *rxr, uint16_t nb_desc)
 {
        int size;
+       uint32_t ring_desc;
+
+       /*
+        * RSC rings use 32B descriptors (RBaMR[BDS] = 1), i.e. two 16B slots
+        * each, so allocate 2 * nb_desc slots. Non-RSC rings use 16B slots.
+        */
+       ring_desc = rxr->rsc_enable ? (uint32_t)nb_desc * 2 : (uint32_t)nb_desc;
 
-       size = nb_desc * sizeof(struct enetc_swbd);
+       size = ring_desc * sizeof(struct enetc_swbd);
        /* Zero q_swbd so buffer_addr is NULL for all uninitialized slots. */
        rxr->q_swbd = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
        if (rxr->q_swbd == NULL)
                return -ENOMEM;
 
        /* Allocate the RX BD ring: each BD is union enetc_rx_bd (16 bytes). */
-       size = nb_desc * sizeof(union enetc_rx_bd);
+       size = ring_desc * sizeof(union enetc_rx_bd);
        rxr->bd_base = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
        if (rxr->bd_base == NULL) {
                rte_free(rxr->q_swbd);
                rxr->q_swbd = NULL;
                return -ENOMEM;
        }
-       rxr->bd_count = nb_desc;
+       rxr->bd_count = ring_desc;
        rxr->next_to_clean = 0;
        rxr->next_to_use = 0;
        rxr->next_to_alloc = 0;
@@ -558,13 +568,30 @@ enetc4_setup_rxbdr(struct enetc_hw *hw, struct enetc_bdr 
*rx_ring,
                       lower_32_bits((uint64_t)bd_address));
        enetc4_rxbdr_wr(hw, idx, ENETC_RBBAR1,
                       upper_32_bits((uint64_t)bd_address));
+       /*
+        * RBaLENR counts HW descriptors. With RSC (BDS = 1) a descriptor is
+        * 32B (two 16B slots), so program bd_count / 2; otherwise bd_count.
+        */
        enetc4_rxbdr_wr(hw, idx, ENETC_RBLENR,
-                      ENETC_RTBLENR_LEN(rx_ring->bd_count));
+                      ENETC_RTBLENR_LEN(rx_ring->rsc_enable ?
+                               rx_ring->bd_count / 2 : rx_ring->bd_count));
 
        rx_ring->mb_pool = mb_pool;
        rx_ring->rcir = (void *)((size_t)hw->reg +
                        ENETC_BDR(RX, idx, ENETC_RBCIR));
-       enetc_refill_rx_ring(rx_ring, 
ENETC_BD_ALIGN_DOWN(enetc_bd_unused(rx_ring)));
+       /* RSC rings clear their Rx interrupt-detect event via SIRXIDR (see
+        * enetc_clean_rx_ring_rsc); cache the register address here.
+        */
+       if (rx_ring->rsc_enable)
+               rx_ring->rbidr = (void *)((size_t)hw->reg + ENETC_SIRXIDR);
+
+       /* RSC rings use the 2-slot-stride refill for 32B descriptors. */
+       if (rx_ring->rsc_enable)
+               enetc_refill_rx_ring_rsc(rx_ring,
+                       ENETC_BD_ALIGN_DOWN(enetc_bd_unused(rx_ring)));
+       else
+               enetc_refill_rx_ring(rx_ring,
+                       ENETC_BD_ALIGN_DOWN(enetc_bd_unused(rx_ring)));
        buf_size = (uint16_t)(rte_pktmbuf_data_room_size(rx_ring->mb_pool) -
                   RTE_PKTMBUF_HEADROOM);
        enetc4_rxbdr_wr(hw, idx, ENETC_RBBSR, buf_size);
@@ -587,7 +614,9 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
                        ENETC_DEV_PRIVATE(data->dev_private);
        uint64_t rx_offloads = data->dev_conf.rxmode.offloads;
        uint32_t rx_enable;
+       uint32_t rsc_size;
        bool keep_crc;
+       bool rsc_enable;
 
        PMD_INIT_FUNC_TRACE();
        if (nb_rx_desc > MAX_BD_COUNT)
@@ -604,6 +633,23 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
        keep_crc = !!(rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC);
        rx_ring->crc_len = (uint8_t)(keep_crc ? RTE_ETHER_CRC_LEN : 0);
 
+       /*
+        * RSC (LRO) is a port-level offload: the Rx burst is a single device
+        * function pointer, so decide from port offloads (not per-queue
+        * rx_conf) to keep all rings consistent. RSC needs HW FCS stripping
+        * (RBaMR[CRC] = 0), so it is incompatible with KEEP_CRC.
+        */
+       rsc_enable = !!(rx_offloads & RTE_ETH_RX_OFFLOAD_TCP_LRO);
+       if (rsc_enable) {
+               if (keep_crc) {
+                       ENETC_PMD_ERR("RSC (LRO) is incompatible with 
KEEP_CRC");
+                       rte_free(rx_ring);
+                       return -EINVAL;
+               }
+               rx_ring->rsc_enable = 1;
+               dev->rx_pkt_burst = &enetc_recv_pkts_rsc;
+       }
+
        err = enetc4_alloc_rxbdr(rx_ring, nb_rx_desc);
        if (err)
                goto fail;
@@ -622,6 +668,49 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
        else
                rx_enable &= ~ENETC4_RBMR_CRC;
 
+       if (rsc_enable) {
+               /*
+                * RSC preconditions (all must hold or RBaRSCR[EN] is ignored):
+                * BDS = 1 (32B descriptors), CRC = 0 (FCS stripped, enforced
+                * above), ICEN = 1 (its timer doubles as the RSC flush timer),
+                * and RBaRSCR[EN] with CT for timestamped segments. SIZE caps
+                * the coalesced frame; honor rxmode.max_lro_pkt_size clamped to
+                * the HW max, falling back to the HW max when unset.
+                */
+               rx_enable |= ENETC4_RBMR_BDS;
+
+               /*
+                * Commit RBMR[BDS] to HW now (ring still disabled, EN clear)
+                * so the 32B descriptor mode is active before RBaRSCR[EN] is
+                * set. BDS = 1 is a hard RSC precondition: if RBaRSCR[EN] is
+                * written while BDS is still 0 in HW, RSC may be silently
+                * ignored. The ring-enable step below writes RBMR again with
+                * EN added.
+                */
+               enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
+                              rx_enable);
+
+               rsc_size = data->dev_conf.rxmode.max_lro_pkt_size;
+               if (rsc_size == 0 || rsc_size > ENETC4_RSC_MAX_FRAME)
+                       rsc_size = ENETC4_RSC_MAX_FRAME;
+
+               /*
+                * Program ICTT FIRST (with ICEN = 0) then enable ICEN, as the
+                * RM requires. ICTT is the RSC coalesce-hold window; a zero
+                * value disables the timer and forces HW to flush every frame
+                * (no coalescing), so use a non-zero default.
+                */
+               enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index,
+                              ENETC4_RBICR1, ENETC4_RSC_DEF_ICTT);
+               enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index,
+                              ENETC4_RBICR0, ENETC4_RBICR0_ICEN |
+                              ENETC4_RBICR0_ICPT(ENETC4_RSC_DEF_ICPT));
+               enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index,
+                              ENETC4_RBRSCR, ENETC4_RBRSCR_EN |
+                              ENETC4_RBRSCR_CT |
+                              ENETC4_RBRSCR_SIZE(rsc_size));
+       }
+
        if (!rx_conf->rx_deferred_start) {
                /* enable ring */
                rx_enable |= ENETC_RBMR_EN;
@@ -643,7 +732,6 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
        return err;
 }
 
-
 void
 enetc4_rx_queue_release(struct rte_eth_dev *dev, uint16_t qid)
 {
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 3083a56335..be79a18a39 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -53,6 +53,7 @@ static uint64_t dev_rx_offloads_sup =
        RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
        RTE_ETH_RX_OFFLOAD_VLAN_FILTER |
        RTE_ETH_RX_OFFLOAD_KEEP_CRC |
+       RTE_ETH_RX_OFFLOAD_TCP_LRO |
        RTE_ETH_RX_OFFLOAD_SCATTER;
 
 /* Supported Tx offloads */
diff --git a/drivers/net/enetc/enetc_rxtx.c b/drivers/net/enetc/enetc_rxtx.c
index f19f32eace..3f2e85b871 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -816,7 +816,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
        int cleaned_cnt, i;
        struct enetc_swbd *rx_swbd;
        union enetc_rx_bd *rxbd, rxbd_temp;
-       struct rte_mbuf *first_seg = NULL, *cur_seg = NULL;
+       struct rte_mbuf *first_seg = NULL, *cur_seg = NULL, *prev_seg = NULL;
        uint32_t bd_status;
        uint8_t *data;
        uint32_t j;
@@ -905,6 +905,241 @@ enetc_recv_pkts_nc(void *rxq, struct rte_mbuf **rx_pkts,
        return enetc_clean_rx_ring_nc(rx_ring, rx_pkts, nb_pkts);
 }
 
+/*
+ * RSC (LRO) refill. buff_cnt is in 16B slots; each 32B descriptor is two
+ * slots. The even slot takes a fresh mbuf, the odd extension slot never owns
+ * a buffer, so walk the ring two slots at a time. Cache maintenance mirrors
+ * enetc_refill_rx_ring (dcbf per 64B line, i.e. two descriptors).
+ */
+int
+enetc_refill_rx_ring_rsc(struct enetc_bdr *rx_ring, const int buff_cnt)
+{
+       struct enetc_swbd *rx_swbd;
+       union enetc_rx_bd *rxbd, *grp_start_rxbd;
+       int i, j, k = ENETC_RXBD_BUNDLE;
+       struct rte_mbuf *m[ENETC_RXBD_BUNDLE];
+       struct rte_mempool *mb_pool;
+
+       i = rx_ring->next_to_use;
+       mb_pool = rx_ring->mb_pool;
+       rx_swbd = &rx_ring->q_swbd[i];
+       rxbd = ENETC_RXBD(*rx_ring, i);
+       grp_start_rxbd = rxbd;
+
+       for (j = 0; j < buff_cnt; j += 2) {
+               /* Bulk alloc one mbuf per descriptor (every two slots). */
+               if (k == ENETC_RXBD_BUNDLE) {
+                       int want = (buff_cnt - j) / 2;
+                       int m_cnt = RTE_MIN(want, ENETC_RXBD_BUNDLE);
+
+                       k = 0;
+                       if (rte_pktmbuf_alloc_bulk(mb_pool, m, m_cnt))
+                               return -1;
+               }
+
+               rx_swbd->buffer_addr = m[k];
+               rxbd->w.addr = (uint64_t)(uintptr_t)
+                              rx_swbd->buffer_addr->buf_iova +
+                              rx_swbd->buffer_addr->data_off;
+               /* clear 'R' as well */
+               rxbd->r.lstatus = 0;
+               k++;
+
+               /* Extension (odd) slot never owns a buffer. */
+               rx_swbd[1].buffer_addr = NULL;
+
+               rx_swbd += 2;
+               rxbd += 2;
+               i += 2;
+
+               if (unlikely(i == rx_ring->bd_count)) {
+                       /* Ring wrap: flush partial/full group before reset. */
+                       dcbf((void *)grp_start_rxbd);
+                       i = 0;
+                       rxbd = ENETC_RXBD(*rx_ring, i);
+                       rx_swbd = &rx_ring->q_swbd[i];
+                       grp_start_rxbd = rxbd;
+               } else if ((i & ENETC_BD_PER_CL_MASK) == 0) {
+                       /* Completed a full 4-slot (2-descriptor) cache line. */
+                       dcbf((void *)grp_start_rxbd);
+                       grp_start_rxbd = rxbd;
+               }
+       }
+
+       /* Flush any remaining partial group at the end of the fill. */
+       if (j && (i & ENETC_BD_PER_CL_MASK) != 0)
+               dcbf((void *)grp_start_rxbd);
+
+       if (likely(j)) {
+               rx_ring->next_to_alloc = i;
+               rx_ring->next_to_use = i;
+               /*
+                * Internal indices track 16B slots, but the HW consumer-index
+                * register counts 32B descriptors, so program i / 2.
+                */
+               enetc_wr_reg(rx_ring->rcir, i / 2);
+       }
+
+       return j;
+}
+
+/*
+ * RSC (LRO) clean. Same non-cache-coherent discipline as
+ * enetc_clean_rx_ring_cacheable but walks 32B descriptors (two 16B slots
+ * each); the extension half at slot i+1 carries the RSC coalesce count.
+ * RSC_FRAMES > 1 flags the cluster RTE_MBUF_F_RX_LRO. RSC always strips the
+ * FCS (RBaMR[CRC] = 0), so no CRC trim is needed.
+ */
+static int
+enetc_clean_rx_ring_rsc(struct enetc_bdr *rx_ring,
+                   struct rte_mbuf **rx_pkts,
+                   int work_limit)
+{
+       int rx_frm_cnt = 0;
+       int cleaned_cnt, i;
+       struct enetc_swbd *rx_swbd;
+       union enetc_rx_bd *rxbd, rxbd_temp;
+       struct enetc_rx_bd_ext *rxbd_ext;
+       struct rte_mbuf *first_seg = NULL, *cur_seg = NULL;
+       uint32_t bd_status;
+       uint8_t *data;
+       uint32_t j;
+       struct rte_mbuf *seg;
+       uint16_t data_len;
+       uint8_t rsc_frames;
+
+       /* next descriptor to process */
+       i = rx_ring->next_to_clean;
+       rxbd = ENETC_RXBD(*rx_ring, i);
+       cleaned_cnt = enetc_bd_unused(rx_ring);
+       rx_swbd = &rx_ring->q_swbd[i];
+
+       /*
+        * Always invalidate the cache line that contains next_to_clean before
+        * the first status read. On RSC rings a 64B line holds two 32B
+        * descriptors. See enetc_clean_rx_ring_cacheable for the full rationale
+        * on why dccivac (clean+invalidate) is used from EL0 instead of DC 
IVAC.
+        */
+       dccivac((void *)ENETC_RXBD(*rx_ring,
+                         (i & ~(int)ENETC_BD_PER_CL_MASK)));
+
+       while (likely(rx_frm_cnt < work_limit)) {
+               /* Atomically snapshot the 16B writeback half of the BD. */
+#ifdef RTE_ARCH_32
+               rte_memcpy(&rxbd_temp, rxbd, 16);
+#else
+               __uint128_t *dst128 = (__uint128_t *)&rxbd_temp;
+               const __uint128_t *src128 = (const __uint128_t *)rxbd;
+               *dst128 = *src128;
+#endif
+               bd_status = rte_le_to_cpu_32(rxbd_temp.r.lstatus);
+
+               if (!(bd_status & ENETC_RXBD_LSTATUS_R))
+                       break;
+               if (rxbd_temp.r.error)
+                       rx_ring->ierrors++;
+
+               /*
+                * Extension half (odd slot) carries the RSC coalesce count.
+                * Invariant: an RSC ring is allocated with bd_count = nb_desc 
* 2
+                * (always even), next_to_clean starts at 0, and i only ever
+                * advances by 2 with a wrap at i == bd_count. So i is always 
the
+                * even (writeback) slot of a 32B descriptor and never exceeds
+                * bd_count - 2; i + 1 is therefore always the matching odd
+                * extension slot and stays in bounds (never wraps past the 
ring).
+                */
+               rxbd_ext = (struct enetc_rx_bd_ext *)
+                          ENETC_RXBD(*rx_ring, i + 1);
+               rsc_frames = 
ENETC4_RXBD_EXT_RSC_FRAMES(rte_le_to_cpu_32(rxbd_ext->rsc_frames));
+
+               seg = rx_swbd->buffer_addr;
+               data_len = rte_le_to_cpu_16(rxbd_temp.r.buf_len);
+               seg->data_len = data_len;
+               if (!first_seg) {
+                       first_seg = seg;
+                       cur_seg = seg;
+                       first_seg->pkt_len = data_len;
+                       first_seg->ol_flags = 0;
+                       enetc_dev_rx_parse(first_seg,
+                                               rxbd_temp.r.parse_summary);
+                       first_seg->hash.rss = rxbd_temp.r.rss_hash;
+                       /* RSC_FRAMES > 1 means HW coalesced segments (LRO). */
+                       if (rsc_frames > 1)
+                               first_seg->ol_flags |= RTE_MBUF_F_RX_LRO;
+               } else {
+                       first_seg->pkt_len += data_len;
+                       first_seg->nb_segs++;
+                       cur_seg->next = seg;
+                       cur_seg = seg;
+               }
+
+               /* Invalidate packet data so the CPU reads HW-DMA'd payload. */
+               data = rte_pktmbuf_mtod(seg, void *);
+               for (j = 0; j < data_len; j += RTE_CACHE_LINE_SIZE)
+                       dccivac(data + j);
+               /*
+                * Cover the last byte of an unaligned buffer. Guard against a
+                * zero-length BD so data_len - 1 does not step before the
+                * payload start.
+                */
+               if (likely(data_len))
+                       dccivac(data + (data_len - 1));
+
+               if (bd_status & ENETC_RXBD_LSTATUS_F) {
+                       seg->next = NULL;
+                       ENETC_PMD_DP_DEBUG("RSC_FRAMES=%u pkt_len=%u 
nb_segs=%u",
+                                          rsc_frames, first_seg->pkt_len,
+                                          first_seg->nb_segs);
+                       rx_pkts[rx_frm_cnt] = first_seg;
+                       rx_frm_cnt++;
+                       first_seg = NULL;
+               }
+
+               /* Two 16B slots per 32B RSC descriptor. */
+               cleaned_cnt += 2;
+               rx_swbd += 2;
+               i += 2;
+               if (unlikely(i == rx_ring->bd_count)) {
+                       i = 0;
+                       rx_swbd = &rx_ring->q_swbd[i];
+               }
+               rxbd = ENETC_RXBD(*rx_ring, i);
+
+               /*
+                * Crossed a 4-slot (cache-line, two-descriptor) boundary:
+                * invalidate the new group so subsequent status reads fetch
+                * fresh DDR data written by HW.
+                */
+               if ((i & ENETC_BD_PER_CL_MASK) == 0 &&
+                   likely(rx_frm_cnt < work_limit))
+                       dccivac((void *)rxbd);
+       }
+
+       rx_ring->next_to_clean = i;
+       enetc_refill_rx_ring_rsc(rx_ring, ENETC_BD_ALIGN_DOWN(cleaned_cnt));
+
+       /*
+        * Write-1-to-clear this ring's event in SIRXIDR. Without this the
+        * interrupt-coalescing timer never re-arms and HW stops coalescing
+        * after the first RSC frame (this PMD is poll-mode and never services
+        * the interrupt). SIRXIDR is W1C, one bit per ring; RBaIDR is
+        * read-only. Matches the Linux driver's enetc_wr_reg_hot(idr, BIT()).
+        */
+       enetc4_wr_reg(rx_ring->rbidr, BIT(rx_ring->index));
+
+       return rx_frm_cnt;
+}
+
+uint16_t
+enetc_recv_pkts_rsc(void *rxq, struct rte_mbuf **rx_pkts,
+               uint16_t nb_pkts)
+{
+       struct enetc_bdr *rx_ring = (struct enetc_bdr *)rxq;
+
+       return enetc_clean_rx_ring_rsc(rx_ring, rx_pkts, nb_pkts);
+}
+
+
 uint16_t
 enetc_recv_pkts(void *rxq, struct rte_mbuf **rx_pkts,
                uint16_t nb_pkts)
@@ -1041,7 +1276,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
        int cleaned_cnt, i;
        struct enetc_swbd *rx_swbd;
        union enetc_rx_bd *rxbd;
-       struct rte_mbuf *first_seg = NULL, *cur_seg = NULL;
+       struct rte_mbuf *first_seg = NULL, *cur_seg = NULL, *prev_seg = NULL;
        uint32_t bd_status;
        uint8_t *data;
        uint32_t j;
-- 
2.25.1

Reply via email to