DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95
@ 2026-08-06 11:28 Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
                   ` (15 more replies)
  0 siblings, 16 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal

This series adds new PMD features to the ENETC4 driver targeting the
NXP i.MX95 NETC IP.

The series covers:

 - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
 - TSO: TCP Segmentation Offload for the VF Tx path.
 - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
 - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
   8-bit to support speeds beyond 10G.
 - Firmware version: report the NETC IP version via fw_version_get.
 - Register dump: dump SI, port (PF) and BD ring registers.
 - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
 - Link-up interrupt: refresh the cached link speed on each VF link-up
   interrupt so that link_update returns the current speed immediately.
 - Stats reset: software snapshot/delta approach for VF counter reset.
 - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
   enabling interrupt-driven receive with l3fwd-power.
 - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
 - VF link status bitmask: switch VF link status to bitmask encoding
   to align with the PF and newer kernel driver conventions.
 - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
   negotiated on the wire; adds Flow control = Y to enetc4.ini.
 - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.

Gagandeep Singh (14):
  net/enetc: add KEEP_CRC offload support for ENETC4
  net/enetc: add TSO support for ENETC4 VF
  net/enetc: add RSC (hardware LRO) support for ENETC4
  net/enetc: extend link speed code field to 8-bit for PF-to-VF message
  net/enetc: support firmware version get for VF
  net/enetc: support registers dump
  net/enetc: support ethtool ring parameters
  net/enetc: refresh link speed on VF link-up interrupt
  net/enetc: support stats reset for VF
  net/enetc4: add per-queue Rx interrupt support for VF
  net/enetc4: add SI-based port VLAN insertion and removal
  net/enetc4: update VF link status to bitmask encoding
  net/enetc4: enable TX PAUSE via VF RX congestion mode
  net/enetc4: add WRR Tx scheduler devarg for VF rings

 doc/guides/nics/enetc4.rst             |  81 ++-
 doc/guides/nics/features/enetc4.ini    |   8 +
 doc/guides/rel_notes/release_26_11.rst |  19 +
 drivers/net/enetc/base/enetc4_hw.h     | 129 +++-
 drivers/net/enetc/base/enetc_hw.h      |   6 +
 drivers/net/enetc/enetc.h              | 135 ++++-
 drivers/net/enetc/enetc4_ethdev.c      | 394 ++++++++++++-
 drivers/net/enetc/enetc4_vf.c          | 780 ++++++++++++++++++++++---
 drivers/net/enetc/enetc_rxtx.c         | 531 ++++++++++++++++-
 9 files changed, 1950 insertions(+), 133 deletions(-)


^ permalink raw reply	[flat|nested] 110+ messages in thread

* [PATCH 01/14] net/enetc: add KEEP_CRC offload support for ENETC4
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
                   ` (14 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

The legacy ENETC (LS1028A) driver already supports the
RTE_ETH_RX_OFFLOAD_KEEP_CRC offload, but ENETC4 (i.MX95 NETC) did not.

Add KEEP_CRC support for ENETC4 (both PF and VF):

- Advertise RTE_ETH_RX_OFFLOAD_KEEP_CRC in the supported Rx offloads
  for the ENETC4 PF and VF. The VF reuses the PF Rx queue setup, so the
  HW configuration and datapath handling apply to both.
- Configure the per-ring RBaMR[CRC] bit in the Rx queue setup so that
  HW preserves the Ethernet FCS in the receive buffer when the offload
  is requested (0 = FCS removed, 1 = FCS preserved).
- Follow the DPDK convention used by the legacy ENETC and ixgbe drivers:
  set crc_len to RTE_ETHER_CRC_LEN when KEEP_CRC is enabled and have the
  datapath subtract crc_len from pkt_len/data_len. ENETC and ENETC4 share
  the same datapath (enetc_rxtx.c), so the semantics stay consistent.
- Handle the scatter-gather boundary case where the 4-byte FCS straddles
  the last two segments: drop the trailing segment, decrement nb_segs and
  trim the carry-over from its predecessor.

Update the enetc4 feature matrix to list CRC offload.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  6 ++++
 drivers/net/enetc/base/enetc4_hw.h     |  4 +++
 drivers/net/enetc/enetc4_ethdev.c      | 31 ++++++++++++++++++--
 drivers/net/enetc/enetc4_vf.c          |  1 +
 drivers/net/enetc/enetc_rxtx.c         | 39 +++++++++++++++++++++++---
 6 files changed, 75 insertions(+), 7 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 698140e30b..91b18d979e 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ Packet type parsing  = Y
 Basic stats          = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
+CRC offload          = Y
 Queue start/stop     = Y
 Scattered Rx         = Y
 Linux                = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..3678dd6894 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -56,6 +56,12 @@ New Features
      =======================================================
 
 
+* **Updated NXP ENETC4 PMD.**
+
+  Updated the NXP ENETC4 poll mode driver for i.MX95:
+
+  * Added KEEP_CRC Rx offload support for the ENETC4 PMD to preserve the Ethernet FCS.
+
 Removed Items
 -------------
 
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 9685e7e1e5..0105549857 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -68,6 +68,10 @@ struct enetc_msg_swbd {
 #define PM_CMD_CFG_TX_EN		BIT(0)
 #define PM_CMD_CFG_RX_EN		BIT(1)
 
+/* RBaMR[CRC]: 0 = FCS removed, 1 = FCS preserved (KEEP_CRC) */
+#define ENETC4_RBMR_CRC			BIT(8)
+
+
 /* i.MX95 supports jumbo frame, but it is recommended to set the max frame
  * size to 2000 bytes.
  */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 2ddd63dafb..1a8056580c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -11,6 +11,19 @@
 #include "enetc_logs.h"
 #include "enetc.h"
 
+/* Supported Rx offloads */
+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;
+
+/* Supported Tx offloads */
+static uint64_t dev_tx_offloads_sup =
+	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
+	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
+
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
 
@@ -536,6 +549,8 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			ENETC_DEV_PRIVATE(data->dev_private);
 	uint64_t rx_offloads = data->dev_conf.rxmode.offloads;
+	uint32_t rx_enable;
+	bool keep_crc;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_rx_desc > MAX_BD_COUNT)
@@ -549,6 +564,9 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	rx_ring->index = rx_queue_id;
+	keep_crc = !!(rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC);
+	rx_ring->crc_len = (uint8_t)(keep_crc ? RTE_ETHER_CRC_LEN : 0);
+
 	err = enetc4_alloc_rxbdr(rx_ring, nb_rx_desc);
 	if (err)
 		goto fail;
@@ -562,19 +580,25 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	data->rx_queues[rx_queue_id] = rx_ring;
 	rx_ring->rx_deferred_start = rx_conf->rx_deferred_start;
 
+	if (keep_crc)
+		rx_enable |= ENETC4_RBMR_CRC;
+	else
+		rx_enable &= ~ENETC4_RBMR_CRC;
+
 	if (!rx_conf->rx_deferred_start) {
 		/* enable ring */
+		rx_enable |= ENETC_RBMR_EN;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
-			       ENETC_RBMR_EN);
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STARTED;
 	} else {
+		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STOPPED;
 	}
 
-	rx_ring->crc_len = (uint8_t)((rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC) ?
-				     RTE_ETHER_CRC_LEN : 0);
 	return 0;
 fail:
 	rte_free(rx_ring);
@@ -582,6 +606,7 @@ 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 ef5f1e6d66..83a1e4931f 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -52,6 +52,7 @@ static uint64_t dev_rx_offloads_sup =
 	RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_VLAN_FILTER |
+	RTE_ETH_RX_OFFLOAD_KEEP_CRC |
 	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 8678bafece..e3bef607dd 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -530,6 +530,28 @@ enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
 	return rx_frm_cnt;
 }
 
+/*
+ * Trim the Ethernet FCS from a received cluster when HW CRC strip is
+ * disabled. pkt_len is reduced by crc_len. If the FCS straddles the last
+ * two segments (last seg holds fewer bytes than crc_len), drop the trailing
+ * segment and trim the carry-over from its predecessor. prev_seg is the
+ * segment preceding last_seg in the chain (the caller already tracks it).
+ */
+static inline void
+enetc_rx_crc_trim(struct rte_mbuf *first_seg, struct rte_mbuf *prev_seg,
+		  struct rte_mbuf *last_seg, uint16_t crc_len)
+{
+	first_seg->pkt_len -= crc_len;
+	if (likely(last_seg->data_len > crc_len)) {
+		last_seg->data_len -= crc_len;
+	} else if (prev_seg != NULL) {
+		first_seg->nb_segs--;
+		prev_seg->data_len -= crc_len - last_seg->data_len;
+		prev_seg->next = NULL;
+		rte_pktmbuf_free_seg(last_seg);
+	}
+}
+
 static int
 enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		    struct rte_mbuf **rx_pkts,
@@ -539,7 +561,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -572,6 +594,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg, rxbd_temp.r.parse_summary);
 			first_seg->hash.rss = rxbd_temp.r.rss_hash;
@@ -579,6 +602,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -590,7 +614,9 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
@@ -760,7 +786,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -815,6 +841,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg,
 					   rxbd->r.parse_summary);
@@ -823,6 +850,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -838,7 +866,10 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
+
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 02/14] net/enetc: add TSO support for ENETC4 VF
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

Add TCP and UDP segmentation offload (TSO) for the ENETC4 virtual
function. The offload is advertised through the VF Tx capabilities and
is enabled on a per-queue basis when an application requests the TSO
offload flags during Tx queue setup.

A dedicated Tx burst, enetc_xmit_pkts_lso(), builds the large-send
descriptor chain: a standard header BD, a 16B extension BD carrying the
segment size and frame length extension, and one BD per payload buffer
with the final-frame flag on the last BD. To make room for the extra
extension BD, an LSO-enabled Tx ring is allocated with twice the number
of descriptors. The existing non-TSO Tx paths are left unchanged.

Update the ENETC4 feature list and documentation accordingly.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   2 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/base/enetc4_hw.h     |  35 +++-
 drivers/net/enetc/enetc.h              |   5 +
 drivers/net/enetc/enetc4_ethdev.c      |  45 ++++-
 drivers/net/enetc/enetc4_vf.c          |   2 +
 drivers/net/enetc/enetc_rxtx.c         | 256 +++++++++++++++++++++++++
 8 files changed, 342 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index f0dd039f6e..4d27e4048e 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -53,6 +53,8 @@ Key functionality includes:
 - Receive processing: Upon packet reception, the BD Ring status bit is set,
   facilitating packet processing.
 - 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.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 91b18d979e..8ec1e8d00f 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
+TSO                  = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 3678dd6894..6a348ab4e0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -61,6 +61,7 @@ New Features
   Updated the NXP ENETC4 poll mode driver for i.MX95:
 
   * 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.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 0105549857..64c1a5e750 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2024 NXP
+ * Copyright 2024-2026 NXP
  *
  * This header file defines the register offsets and bit fields
  * of ENETC4 PF and VFs.
@@ -24,8 +24,14 @@ struct enetc_msg_swbd {
 
 /* enetc4 txbd flags */
 #define ENETC4_TXBD_FLAGS_L4CS		BIT(0)
+/* Request LSO (Large Send Offload) segmentation for this frame */
+#define ENETC4_TXBD_FLAGS_LSO		BIT(1)
+/* L4 checksum insertion (also used for checksum update on LSO) */
 #define ENETC4_TXBD_FLAGS_L_TX_CKSUM	BIT(3)
+/* Extended descriptor: the next 16B ring entry is an extension BD */
+#define ENETC4_TXBD_FLAGS_EXT		BIT(6)
 #define ENETC4_TXBD_FLAGS_F		BIT(7)
+
 /* L4 type */
 #define ENETC4_TXBD_L4T_UDP		BIT(0)
 #define ENETC4_TXBD_L4T_TCP		BIT(1)
@@ -34,6 +40,33 @@ struct enetc_msg_swbd {
 /* IPv4 checksum */
 #define ENETC4_TXBD_IPCS		1
 
+/*
+ * Extension Transmit Buffer Descriptor (16B). When the standard BD has the
+ * extended flag set, this occupies the next ring entry and carries the extra
+ * fields required by LSO.
+ */
+struct enetc_tx_bd_ext {
+	uint32_t timestamp;	/* PTP timestamp, unused for LSO */
+	uint32_t vlan;		/* VLAN insert, unused for LSO */
+	uint32_t lso;		/* LSO_MAX_SEG_SIZE and FRM_LEN_EXT */
+	uint32_t flags;		/* extension flags */
+};
+
+/* LSO_MAX_SEG_SIZE occupies bits 13-0 of the LSO word */
+#define ENETC4_TXBD_EXT_LSO_SEG_MASK	0x3fff
+#define ENETC4_TXBD_EXT_LSO_SEG(mss) \
+		((uint32_t)((mss) & ENETC4_TXBD_EXT_LSO_SEG_MASK))
+/* FRM_LEN_EXT occupies bits 19-16 of the LSO word (top 4 bits of data len) */
+#define ENETC4_TXBD_EXT_FRM_LEN_EXT(x) \
+		(((uint32_t)((x) & 0xf)) << 16)
+/* Final flag in the extension flags word (bit 127 -> local bit 31) */
+#define ENETC4_TXBD_EXT_FLAGS_F		BIT(31)
+
+/* NETC does not create LSO frames larger than this many bytes */
+#define ENETC4_LSO_MAX_FRAME		9600
+/* Maximum LSO data unit (payload to be segmented) supported by HW: 256KB */
+#define ENETC4_LSO_MAX_DATA_UNIT	(256 * 1024)
+
 /***************************ENETC port registers**************************/
 #define ENETC4_PMR		0x10
 #define ENETC4_PMR_EN		(BIT(16) | BIT(17) | BIT(18))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index d3a8b8e34a..db349e051b 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -91,6 +91,7 @@ struct enetc_bdr {
 		void *tcisr; /* Tx */
 		int next_to_alloc; /* Rx */
 	};
+
 	struct rte_mempool *mb_pool;   /* mbuf pool to populate RX ring. */
 	/* Partial scatter-gather chain persisted across burst calls. */
 	struct rte_mbuf *pkt_first_seg; /* first segment of in-progress frame */
@@ -99,6 +100,7 @@ struct enetc_bdr {
 	uint64_t ierrors;
 	uint8_t rx_deferred_start;
 	uint8_t tx_deferred_start;
+	uint8_t lso_enable;
 };
 
 struct enetc_eth_hw {
@@ -312,6 +314,9 @@ uint16_t enetc_xmit_pkts(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
 uint16_t enetc_xmit_pkts_nc(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
+uint16_t enetc_xmit_pkts_lso(void *txq, struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts);
+
 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,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 1a8056580c..897e12e21f 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -22,7 +22,9 @@ static uint64_t dev_rx_offloads_sup =
 static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
+	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
@@ -321,28 +323,38 @@ static int
 enetc4_alloc_txbdr(struct enetc_bdr *txr, uint16_t nb_desc)
 {
 	int size;
+	uint32_t ring_desc;
 
-	size = nb_desc * sizeof(struct enetc_swbd);
+	/*
+	 * On LSO-enabled rings each frame uses an extra 16B extension BD
+	 * (logical 32B descriptor) plus the payload BDs.  Size the ring with
+	 * twice the requested entries so the effective frame capacity is
+	 * preserved.  Non-LSO rings are sized exactly as requested.
+	 */
+	ring_desc = txr->lso_enable ? (uint32_t)nb_desc * 2 : (uint32_t)nb_desc;
+
+	size = ring_desc * sizeof(struct enetc_swbd);
 	/* Zero q_swbd so buffer_addr is NULL for all uninitialized slots. */
 	txr->q_swbd = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->q_swbd == NULL)
 		return -ENOMEM;
 
 	/* Allocate the TX BD ring: each BD is struct enetc_tx_bd (16 bytes). */
-	size = nb_desc * sizeof(struct enetc_tx_bd);
+	size = ring_desc * sizeof(struct enetc_tx_bd);
 	txr->bd_base = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->bd_base == NULL) {
 		rte_free(txr->q_swbd);
 		txr->q_swbd = NULL;
 		return -ENOMEM;
 	}
-	txr->bd_count = nb_desc;
+	txr->bd_count = ring_desc;
 	txr->next_to_clean = 0;
 	txr->next_to_use = 0;
 
 	return 0;
 }
 
+
 static void
 enetc4_free_bdr(struct enetc_bdr *rxr)
 {
@@ -352,6 +364,7 @@ enetc4_free_bdr(struct enetc_bdr *rxr)
 	rxr->bd_base = NULL;
 }
 
+
 static void
 enetc4_setup_txbdr(struct enetc_hw *hw, struct enetc_bdr *tx_ring)
 {
@@ -388,6 +401,7 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	struct rte_eth_dev_data *data = dev->data;
 	struct enetc_eth_adapter *priv =
 			ENETC_DEV_PRIVATE(data->dev_private);
+	uint64_t tx_offloads;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_desc > MAX_BD_COUNT)
@@ -401,6 +415,29 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	tx_ring->index = queue_idx;
+	/*
+	 * LSO (TCP/UDP segmentation) is a port-level offload. The Tx burst is
+	 * a single device-level function pointer, so it must be consistent for
+	 * all queues; likewise every ring must be sized the same way. Decide
+	 * from the port offloads only (not the per-queue tx_conf) so that all
+	 * queues either use the LSO burst with a doubled ring, or none do.
+	 * The LSO burst also handles non-TSO packets, so it is safe on queues
+	 * that never carry TSO traffic. LSO needs HW FCS insertion, so it is
+	 * incompatible with KEEP_CRC on Rx.
+	 */
+	tx_offloads = data->dev_conf.txmode.offloads;
+	if (tx_offloads & (RTE_ETH_TX_OFFLOAD_TCP_TSO |
+			   RTE_ETH_TX_OFFLOAD_UDP_TSO)) {
+		if (data->dev_conf.rxmode.offloads &
+		    RTE_ETH_RX_OFFLOAD_KEEP_CRC) {
+			ENETC_PMD_ERR("LSO (TSO) is incompatible with KEEP_CRC");
+			rte_free(tx_ring);
+			return -EINVAL;
+		}
+		tx_ring->lso_enable = 1;
+		dev->tx_pkt_burst = &enetc_xmit_pkts_lso;
+	}
+
 	err = enetc4_alloc_txbdr(tx_ring, nb_desc);
 	if (err)
 		goto fail;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 83a1e4931f..3083a56335 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -60,6 +60,8 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO |
 	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
 
 static void
diff --git a/drivers/net/enetc/enetc_rxtx.c b/drivers/net/enetc/enetc_rxtx.c
index e3bef607dd..b7d71db17e 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -221,6 +221,262 @@ enetc_xmit_pkts_nc(void *tx_queue,
 	return start;
 }
 
+/*
+ * LSO (Large Send Offload) Tx burst.
+ *
+ * Dedicated burst used on Tx rings with LSO enabled (txr->lso_enable). It
+ * follows the same non-cache-coherent discipline as enetc_xmit_pkts_cacheable:
+ * payload lines are flushed with dcbf before handing the frame to HW and the
+ * written BD cache lines are flushed at the end of the batch.
+ *
+ * A TSO/USO frame uses an extended Tx descriptor: the standard BD points at
+ * the L2/L3/L4 header template (BUF_LEN = header length, FRM_LEN = payload
+ * length), followed by an extension BD in the next ring slot holding the
+ * segment size, then one BD per payload buffer with the F flag on the last.
+ * Non-TSO packets fall through to the normal encoding so mixed traffic works.
+ */
+uint16_t
+enetc_xmit_pkts_lso(void *tx_queue,
+		struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts)
+{
+	int i, start, bds_to_use;
+	struct enetc_tx_bd *txbd;
+	struct enetc_tx_bd_ext *txbd_ext;
+	struct enetc_bdr *tx_ring = (struct enetc_bdr *)tx_queue;
+	unsigned int j;
+	uint8_t *data;
+	struct rte_mbuf *seg;
+	uint16_t seg_len, segs_per_pkt;
+	bool is_first_seg;
+	int first_bd_idx, bd_count;
+
+	i = tx_ring->next_to_use;
+	bds_to_use = enetc_bd_unused(tx_ring);
+	bd_count = tx_ring->bd_count;
+	start = 0;
+
+	first_bd_idx = i;
+
+	while (start < nb_pkts) {
+		seg = tx_pkts[start];
+		segs_per_pkt = seg->nb_segs;
+
+		if (seg->ol_flags &
+		    (RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_UDP_SEG)) {
+			bool is_udp_seg =
+				!!(seg->ol_flags & RTE_MBUF_F_TX_UDP_SEG);
+			uint32_t hdr_len = seg->l2_len + seg->l3_len +
+					   seg->l4_len;
+			uint32_t data_unit;
+			uint16_t first_payload;
+			struct rte_mbuf *dseg;
+			int bds_needed;
+
+			/* Header BD + extension BD + one BD per segment. */
+			bds_needed = 2 + segs_per_pkt;
+			if (bds_to_use < bds_needed)
+				break;
+
+			/*
+			 * Skip frames with nothing to segment, or whose
+			 * headers are not fully contained in the first
+			 * segment. The first BD carries the header template
+			 * and first_payload is computed as (first segment
+			 * data_len - hdr_len), so the L2/L3/L4 headers must
+			 * reside contiguously in the first mbuf; otherwise the
+			 * unsigned subtraction would underflow.
+			 */
+			if (unlikely(hdr_len >= rte_pktmbuf_pkt_len(seg) ||
+				     hdr_len > rte_pktmbuf_data_len(seg))) {
+				start++;
+				continue;
+			}
+			data_unit = rte_pktmbuf_pkt_len(seg) - hdr_len;
+
+			/*
+			 * Skip frames that violate HW LSO limits: a zero
+			 * segment size, a payload larger than the HW data
+			 * unit, or a per-segment frame (headers + segment)
+			 * bigger than the maximum LSO frame size.
+			 */
+			if (unlikely(seg->tso_segsz == 0 ||
+				     data_unit > ENETC4_LSO_MAX_DATA_UNIT ||
+				     hdr_len + seg->tso_segsz >
+					     ENETC4_LSO_MAX_FRAME)) {
+				start++;
+				continue;
+			}
+
+			/* Standard (first) BD: header template only. */
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			seg_len = rte_pktmbuf_data_len(seg);
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			memset(txbd, 0, sizeof(*txbd));
+			tx_ring->q_swbd[i].buffer_addr = seg;
+
+			/* FRM_LEN low 16 bits = payload length, BUF_LEN = hdr. */
+			txbd->frm_len = rte_cpu_to_le_16(data_unit & 0xffff);
+			txbd->buf_len = rte_cpu_to_le_16((uint16_t)hdr_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+
+			/* Checksum control for the per-segment L3/L4 rewrite. */
+			txbd->l3_start = seg->l2_len;
+			txbd->l3_hdr_size = seg->l3_len / 4;
+			if (seg->ol_flags & RTE_MBUF_F_TX_IPV6) {
+				txbd->l3t = 1;
+			} else {
+				txbd->l3t = ENETC4_TXBD_L3T;
+				txbd->ipcs = ENETC4_TXBD_IPCS;
+			}
+			txbd->l4t = is_udp_seg ? ENETC4_TXBD_L4T_UDP :
+						 ENETC4_TXBD_L4T_TCP;
+			txbd->flags = ENETC4_TXBD_FLAGS_L_TX_CKSUM |
+				      ENETC4_TXBD_FLAGS_L4CS |
+				      ENETC4_TXBD_FLAGS_LSO |
+				      ENETC4_TXBD_FLAGS_EXT;
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/* Extension BD occupies the next ring slot. */
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			txbd_ext = (struct enetc_tx_bd_ext *)
+				   ENETC_TXBD(*tx_ring, i);
+			memset(txbd_ext, 0, sizeof(*txbd_ext));
+			txbd_ext->lso = rte_cpu_to_le_32(
+				ENETC4_TXBD_EXT_LSO_SEG(seg->tso_segsz) |
+				ENETC4_TXBD_EXT_FRM_LEN_EXT(data_unit >> 16));
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/*
+			 * Payload BDs. The first segment's payload starts after
+			 * the header template; remaining segments are pure
+			 * payload.
+			 */
+			first_payload = (uint16_t)(seg_len - hdr_len);
+			dseg = seg;
+			is_first_seg = true;
+			while (dseg) {
+				uint16_t dlen;
+				uint64_t daddr;
+
+				if (is_first_seg) {
+					dlen = first_payload;
+					daddr = rte_mbuf_data_iova(dseg) +
+						hdr_len;
+					is_first_seg = false;
+				} else {
+					dlen = rte_pktmbuf_data_len(dseg);
+					data = rte_pktmbuf_mtod(dseg, void *);
+					for (j = 0; j < dlen;
+					     j += RTE_CACHE_LINE_SIZE)
+						dcbf(data + j);
+					dcbf(data + (dlen - 1));
+					daddr = rte_mbuf_data_iova(dseg);
+				}
+
+				if (dlen == 0) {
+					dseg = dseg->next;
+					continue;
+				}
+
+				tx_ring->q_swbd[i].buffer_addr = NULL;
+				txbd = ENETC_TXBD(*tx_ring, i);
+				memset(txbd, 0, sizeof(*txbd));
+				txbd->buf_len = rte_cpu_to_le_16(dlen);
+				txbd->addr = rte_cpu_to_le_64(daddr);
+				i++;
+				bds_to_use--;
+				if (unlikely(i == bd_count))
+					i = 0;
+				dseg = dseg->next;
+			}
+
+			/* Mark the last written BD as frame-last. */
+			txbd->flags |= ENETC4_TXBD_FLAGS_F;
+			start++;
+			continue;
+		}
+
+		/* Non-TSO packet: standard single/multi-seg encoding. */
+		if (bds_to_use < segs_per_pkt)
+			break;
+
+		is_first_seg = true;
+		while (seg) {
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			seg_len = rte_pktmbuf_data_len(seg);
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			txbd->flags = 0;
+			if (is_first_seg) {
+				tx_ring->q_swbd[i].buffer_addr = seg;
+				txbd->frm_len = rte_pktmbuf_pkt_len(seg);
+				if (seg->ol_flags & ENETC4_TX_CKSUM_OFFLOAD_MASK)
+					enetc4_tx_offload_checksum(seg, txbd);
+				is_first_seg = false;
+			}
+
+			txbd->buf_len = rte_cpu_to_le_16(seg_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+			seg = seg->next;
+			i++;
+			bds_to_use--;
+
+			if (unlikely(i == bd_count))
+				i = 0;
+		}
+
+		txbd->flags |= rte_cpu_to_le_16(ENETC4_TXBD_FLAGS_F);
+		start++;
+	}
+
+	/*
+	 * Flush TX BDs to PoC so HW (non-cache-coherent i.MX95) can read the
+	 * descriptors from memory.  Same discipline as enetc_xmit_pkts_cacheable:
+	 * walk from the cache-line-aligned start of first_bd_idx to just past
+	 * the last written BD, one dcbf per 64-byte (4-BD) line.
+	 */
+	if (likely(start > 0)) {
+		int n = first_bd_idx & ~ENETC_BD_PER_CL_MASK;
+		int written = (i - n + bd_count) % bd_count;
+
+		if (written == 0)
+			written = bd_count;
+		written = (written + ENETC_BD_PER_CL_MASK) &
+			  ~ENETC_BD_PER_CL_MASK;
+
+		while (written > 0) {
+			dcbf((void *)ENETC_TXBD(*tx_ring, n));
+			n = (n + ENETC_BD_PER_CL) % bd_count;
+			written -= ENETC_BD_PER_CL;
+		}
+	}
+
+	enetc_clean_tx_ring(tx_ring);
+	tx_ring->next_to_use = i;
+	enetc_wr_reg(tx_ring->tcir, i);
+
+	return start;
+}
+
 int
 enetc_refill_rx_ring(struct enetc_bdr *rx_ring, const int buff_cnt)
 {

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
                   ` (12 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

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 <g.singh@nxp.com>
---
 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         | 236 +++++++++++++++++++++++++
 8 files changed, 417 insertions(+), 7 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 b7d71db17e..a008ae17cf 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -906,6 +906,242 @@ 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)

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (2 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

The PF-to-VF message is only 16-bit wide, where the upper 8-bit is the
class_id and the lower 8-bit carries the message content. For the
link speed message, the lower 4-bit was previously occupied by the cookie
field, leaving only 4-bit for the speed code, which allows at most
15 distinct speed values.

As the NETC IP evolves and supports more and more link speeds, 15 speed
values are clearly insufficient. Since the cookie field is designed
for non-blocking messages and has no meaningful use in link speed
messages, remove it and expand the speed code field from 4-bit to 8-bit,
allowing up to 255 speed values (ENETC_SPEED_MAX = 0xff).

Instead of enumerating every speed value greater than 5Gbps
explicitly, introduce a formula-based approach:

	speed_code = (link_speed - 5000) / 1000 + ENETC_SPEED_5000

This removes the explicit enum entries for 10G, 25G, 50G and 100G,
and replaces the individual switch-case branches with a generic
implementation to get the speed, making it easy to support any
future high speed without modifying the enum or the switch statement.

For backward compatibility with a PF running an older kernel (before
6.18.37) that still uses the legacy 4-bit speed code / 4-bit cookie
message layout, add a "vf_link_legacy" devarg. When set to 1, the VF
extracts the message result from the upper 4 bits of the lower byte
and decodes speeds greater than 5Gbps using the legacy fixed class
codes (10G/25G/50G/100G). The default (0) uses the new 8-bit layout.

	Usage: -a <pci_addr>,vf_link_legacy=1

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |  32 ++++-
 drivers/net/enetc/enetc4_vf.c          | 175 +++++++++++++++++++++----
 3 files changed, 175 insertions(+), 33 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 0dd08e0259..d7961d3c85 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -63,6 +63,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.
+  * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 67f8ce1919..c983cb059e 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -120,6 +120,10 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
+	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
+	 */
+	uint8_t vf_link_legacy;
 };
 
 /*
@@ -211,11 +215,29 @@ enum speed {
 	ENETC_SPEED_1000 = 0x5,
 	ENETC_SPEED_2500 = 0x6,
 	ENETC_SPEED_5000 = 0x7,
-	ENETC_SPEED_10G = 0x8,
-	ENETC_SPEED_25G = 0x9,
-	ENETC_SPEED_50G = 0xA,
-	ENETC_SPEED_100G = 0xB,
-	ENETC_SPEED_NOT_SUPPORTED = 0xF
+	/* Base speed class code used by the >5Gbps formula below */
+	/* Do not add enumeration values for any speed greater than
+	 * 5Gbps. For any speed greater than 5Gbps, its speed class
+	 * code should follow the formula below.
+	 *
+	 * SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+	 *
+	 * The unit of link_speed should be Mbps, the max SPEED
+	 * should <= ENETC_SPEED_MAX.
+	 */
+	ENETC_SPEED_MAX = 0xff,
+};
+
+/* Legacy speed class codes for backward compatibility with an older kernel PF
+ * (before 6.18.37) that encoded a 4-bit speed code and 4-bit cookie in the
+ * message's lower byte. Used only when the vf_link_legacy devarg is set.
+ */
+enum speed_legacy {
+	ENETC_SPEED_LEGACY_10G = 0x8,
+	ENETC_SPEED_LEGACY_25G = 0x9,
+	ENETC_SPEED_LEGACY_50G = 0xA,
+	ENETC_SPEED_LEGACY_100G = 0xB,
+	ENETC_SPEED_LEGACY_NOT_SUPPORTED = 0xF
 };
 
 /* PSI-VSI command header format */
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index be79a18a39..d34b8019ea 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -5,6 +5,7 @@
 #include <stdbool.h>
 #include <rte_kvargs.h>
 #include <rte_random.h>
+#include <rte_kvargs.h>
 #include <dpaax_iova_table.h>
 #include "enetc_logs.h"
 #include "enetc.h"
@@ -43,6 +44,12 @@ enetc4_vf_get_devarg_nc(struct rte_eth_dev *dev)
 #define ENETC_BYTE_SIZE			8
 #define ENETC_MSB_BIT			0x8000
 
+/* Backward-compat devarg for a PF running a kernel before 6.18.37, which uses
+ * the legacy PF-to-VF link message layout (4-bit speed code + 4-bit cookie).
+ * Usage: -a <pci_addr>,vf_link_legacy=1
+ */
+#define ENETC_VF_LINK_LEGACY		"vf_link_legacy"
+
 uint16_t enetc_crc_table[ENETC_CRC_TABLE_SIZE];
 bool enetc_crc_gen;
 
@@ -101,6 +108,59 @@ enetc_crc_calc(uint16_t crc, const uint8_t *buffer, size_t len)
 	return crc;
 }
 
+static int
+parse_vf_link_legacy(const char *key __rte_unused, const char *value,
+		     void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *endptr;
+	unsigned long val;
+
+	if (!value || *value == '\0') {
+		ENETC_PMD_WARN("Empty value for devarg %s, ignoring",
+			       ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	errno = 0;
+	val = strtoul(value, &endptr, 0);
+	if (errno != 0 || *endptr != '\0' || val > 1) {
+		ENETC_PMD_WARN("Invalid value '%s' for devarg %s, expected 0 or 1",
+			       value, ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	hw->vf_link_legacy = (uint8_t)val;
+
+	return 0;
+}
+
+static void
+enetc4_vf_get_devargs(struct rte_eth_dev *dev)
+{
+	struct rte_devargs *devargs;
+	struct rte_kvargs *kvlist;
+
+	devargs = dev->device->devargs;
+	if (!devargs)
+		return;
+
+	kvlist = rte_kvargs_parse(devargs->args, NULL);
+	if (!kvlist)
+		return;
+
+	if (rte_kvargs_count(kvlist, ENETC_VF_LINK_LEGACY)) {
+		if (rte_kvargs_process(kvlist, ENETC_VF_LINK_LEGACY,
+				       parse_vf_link_legacy, (void *)dev) < 0)
+			ENETC_PMD_WARN("Failed to parse devarg %s",
+				       ENETC_VF_LINK_LEGACY);
+	}
+
+	rte_kvargs_free(kvlist);
+}
+
 static int
 enetc4_vf_dev_infos_get(struct rte_eth_dev *dev,
 			struct rte_eth_dev_info *dev_info)
@@ -212,6 +272,7 @@ enetc4_msg_vsi_write_msg(struct enetc_hw *hw,
 static void
 enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgsr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -221,8 +282,15 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgsr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -231,6 +299,7 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 static void
 enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgrr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -240,8 +309,15 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgrr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -731,6 +807,8 @@ enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 static int
 enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused)
 {
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
 	struct enetc_psi_reply_msg *reply_msg;
 	struct rte_eth_link link;
 	int err;
@@ -809,28 +887,62 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 			link.link_speed = RTE_ETH_SPEED_NUM_5G;
 			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 			break;
-		case ENETC_SPEED_10G:
-			link.link_speed = RTE_ETH_SPEED_NUM_10G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_25G:
-			link.link_speed = RTE_ETH_SPEED_NUM_25G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_50G:
-			link.link_speed = RTE_ETH_SPEED_NUM_50G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100G:
-			link.link_speed = RTE_ETH_SPEED_NUM_100G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_NOT_SUPPORTED:
-			ENETC_PMD_DEBUG("Speed not supported");
-			link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-			break;
 		default:
-			ENETC_PMD_ERR("Unknown speed status");
+			if (hw->vf_link_legacy) {
+				/* Legacy PF-to-VF message layout (older kernel
+				 * PF): speeds greater than 5Gbps are encoded
+				 * with fixed 4-bit class codes rather than the
+				 * formula below.
+				 */
+				switch (reply_msg->status) {
+				case ENETC_SPEED_LEGACY_10G:
+					link.link_speed = RTE_ETH_SPEED_NUM_10G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_25G:
+					link.link_speed = RTE_ETH_SPEED_NUM_25G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_50G:
+					link.link_speed = RTE_ETH_SPEED_NUM_50G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_100G:
+					link.link_speed = RTE_ETH_SPEED_NUM_100G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+					ENETC_PMD_DEBUG("Speed not supported");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				default:
+					ENETC_PMD_ERR("Unknown speed status");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				}
+				break;
+			}
+
+			/* Any status reaching here is greater than
+			 * ENETC_SPEED_5000, as all values from 0x0 to
+			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
+			 * greater than 5Gbps are not enumerated and follow the
+			 * formula:
+			 *
+			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+			 *
+			 * where link_speed is in Mbps. Reverse it here to get the
+			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
+			 *
+			 * The PF only reports speeds that map to a well-known
+			 * RTE_ETH_SPEED_NUM_* value, so the computed value is a
+			 * valid DPDK speed. If a future speed not yet defined in
+			 * DPDK needs to be supported, the corresponding
+			 * RTE_ETH_SPEED_NUM_* value must first be added upstream.
+			 */
+			link.link_speed = (reply_msg->status - ENETC_SPEED_5000)
+					  * 1000 + 5000;
+			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 			break;
 		}
 	} else {
@@ -839,10 +951,9 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	link.link_autoneg = 1;
-
 	rte_eth_linkstatus_set(dev, &link);
-
 	rte_free(reply_msg);
+
 	return 0;
 }
 
@@ -1421,6 +1532,12 @@ enetc4_vf_dev_init(struct rte_eth_dev *eth_dev)
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_populate();
 
+	/* Parse VF specific devargs (e.g. vf_link_legacy) before the first
+	 * link update so that PF-to-VF link messages are interpreted using
+	 * the correct (legacy or current) layout.
+	 */
+	enetc4_vf_get_devargs(eth_dev);
+
 	ENETC_PMD_DEBUG("port_id %d vendorID=0x%x deviceID=0x%x",
 			eth_dev->data->port_id, pci_dev->id.vendor_id,
 			pci_dev->id.device_id);
@@ -1514,5 +1631,7 @@ RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
 			      ENETC4_VSI_DELAY "=<uint> "
-			      ENETC4_NC_MEMORY "=<int>");
+			      ENETC4_NC_MEMORY "=<int> "
+			      ENETC_VF_LINK_LEGACY "=<0|1>");
+
 RTE_LOG_REGISTER_DEFAULT(enetc4_vf_logtype_pmd, NOTICE);

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 05/14] net/enetc: support firmware version get for VF
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (3 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 06/14] net/enetc: support registers dump Gagandeep Singh
                   ` (10 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

Implement the ``.fw_version_get`` eth_dev op for the ENETC4 VF PMD to
report the NETC IP revision.

The NETC IP major revision is read from the PCI revision ID register,
while the IP minor revision is retrieved from the PSI over VSI-PSI
messaging using the "Get IP version" command class (class ID 0xF0,
command ID 0x1). The version is reported in the "<major>.<minor>"
format.

The PSI firmware only implements the IP_MN command of this class, so
the major revision is obtained from the PCI revision ID, matching the
behaviour of the kernel PF/VF drivers.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   1 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |  19 +++-
 drivers/net/enetc/enetc4_vf.c          | 135 +++++++++++++++++++++++++
 5 files changed, 155 insertions(+), 2 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index e7f4348603..ea0e37896d 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,7 @@ Key functionality includes:
   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.
+- Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index aae398e210..eaf474e7ba 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ VLAN filter          = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
+FW version           = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index d7961d3c85..5ac2bb4de0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,7 @@ New Features
   * Added TCP Segmentation Offload (TSO) support for the ENETC4 VF.
   * Added Receive Segment Coalesce (RSC / hardware LRO) support for ENETC4 PF and VF.
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
+  * Added firmware version reporting for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index c983cb059e..b1428f1bca 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -170,7 +170,8 @@ enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
-	ENETC_CLASS_ID_LINK_SPEED = 0x81
+	ENETC_CLASS_ID_LINK_SPEED = 0x81,
+	ENETC_CLASS_ID_GET_IP_VER = 0xF0
 };
 
 /* Enum for command IDs */
@@ -184,9 +185,18 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_LINK_STATUS = 0,
 	ENETC_CMD_ID_REGISTER_LINK_NOTIF = 1,
 	ENETC_CMD_ID_UNREGISTER_LINK_NOTIF = 2,
-	ENETC_CMD_ID_GET_LINK_SPEED = 0
+	ENETC_CMD_ID_GET_LINK_SPEED = 0,
+	/* Get IP version command IDs (class ID 0xF0) */
+	ENETC_CMD_ID_GET_IP_MJ = 0,
+	ENETC_CMD_ID_GET_IP_MN = 1,
+	ENETC_CMD_ID_GET_IP_INT = 2,
+	ENETC_CMD_ID_GET_IP_MNT = 3,
+	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* IP_VER value returned when the version is not available */
+#define ENETC_IP_VER_NOT_AVAILABLE	0xFF
+
 enum mac_addr_status {
 	ENETC_INVALID_MAC_ADDR = 0x0,
 	ENETC_DUPLICATE_MAC_ADDR = 0X1,
@@ -274,6 +284,11 @@ struct enetc_msg_cmd_get_link_speed {
 	struct enetc_msg_cmd_header header;
 };
 
+/* Get IP version command message format (class ID 0xF0) */
+struct enetc_msg_cmd_get_ip_ver {
+	struct enetc_msg_cmd_header header;
+};
+
 struct enetc_msg_cmd_set_vlan_promisc {
 	struct enetc_msg_cmd_header header;
 	uint8_t op;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index d34b8019ea..a8864cc6ce 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -5,6 +5,7 @@
 #include <stdbool.h>
 #include <rte_kvargs.h>
 #include <rte_random.h>
+#include <rte_bus_pci.h>
 #include <rte_kvargs.h>
 #include <dpaax_iova_table.h>
 #include "enetc_logs.h"
@@ -434,6 +435,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_MAC_FILTER:
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
+		case ENETC_CLASS_ID_GET_IP_VER:
 			break;
 		default:
 			err = -EIO;
@@ -797,6 +799,138 @@ enetc4_vf_get_link_speed(struct rte_eth_dev *dev, struct enetc_psi_reply_msg *re
 	return err;
 }
 
+/*
+ * Get the NETC IP minor revision (IP_MN) from the PSI using the 'Get IP
+ * version' command class (class ID 0xF0, cmd_id 0x1). In the reply, the
+ * low 8 bits of the return code carry the minor revision and the upper 8
+ * bits carry the class code (0xF0). The PSI only implements the IP_MN
+ * command of this class; the major revision comes from the PCI revision.
+ */
+static int
+enetc4_vf_get_ip_minor_revision(struct rte_eth_dev *dev, uint8_t *ip_mn)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_swbd *msg;
+	uint32_t msg_size;
+	uint16_t mc;
+	uint8_t class_id;
+	int vsimsgsr;
+	int err = 0;
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_cmd_get_ip_ver),
+				ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg");
+		rte_free(msg);
+		return -ENOMEM;
+	}
+
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	msg->size = msg_size;
+
+	/* COOKIE is 0 so that the command is executed as blocking on PSI */
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_GET_IP_VER,
+			ENETC_CMD_ID_GET_IP_MN, 0, 0, 0);
+
+	/* send the command and wait */
+	err = enetc4_msg_vsi_send(enetc_hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error");
+		goto end;
+	}
+
+	/*
+	 * For the IP version command class the class-specific field is
+	 * reused to carry the 8-bit version value in the lower byte of the
+	 * return code, so parse the full low byte here instead of using the
+	 * generic reply parser.
+	 */
+	vsimsgsr = enetc4_rd(enetc_hw, ENETC4_VSIMSGSR);
+	mc = ENETC_SIMSGSR_GET_MC(vsimsgsr);
+	class_id = (mc >> 8) & 0xff;
+
+	if (class_id != ENETC_CLASS_ID_GET_IP_VER) {
+		ENETC_PMD_ERR("Wrong reply message 0x%x", class_id);
+		err = -EIO;
+		goto end;
+	}
+
+	*ip_mn = mc & 0xff;
+	if (*ip_mn == ENETC_IP_VER_NOT_AVAILABLE) {
+		ENETC_PMD_DEBUG("IP minor revision not available");
+		err = -ENOTSUP;
+	}
+
+end:
+	/* free memory no longer required */
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	return err;
+}
+
+/*
+ * Retrieve the NETC firmware/IP version and format it as
+ * "<major>.<minor>". The IP major revision is taken from the PCI
+ * revision ID register, and the IP minor revision is fetched from the
+ * PSI via VSI-PSI messaging ('Get IP version' command class). This
+ * mirrors the behaviour of the kernel PF/VF drivers, where the PSI only
+ * implements the IP_MN command of the 0xF0 class.
+ */
+static int
+enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_size)
+{
+	struct rte_pci_device *pci_dev = RTE_ETH_DEV_TO_PCI(dev);
+	uint8_t ip_mj = 0, ip_mn = 0;
+	int ret;
+
+	PMD_INIT_FUNC_TRACE();
+
+	if (fw_version == NULL)
+		return -EINVAL;
+
+	if (fw_size == 0)
+		return snprintf(NULL, 0, "%u.%u", ip_mj, ip_mn) + 1;
+
+	/* IP major revision is exposed through the PCI revision ID */
+	ret = rte_pci_read_config(pci_dev, &ip_mj, sizeof(ip_mj),
+			RTE_PCI_REVISION_ID);
+	if (ret != sizeof(ip_mj)) {
+		ENETC_PMD_ERR("Failed to read PCI revision ID");
+		return -EIO;
+	}
+
+	/* IP minor revision is fetched from the PSI via VSI-PSI messaging.
+	 * If the PSI reports the version as unavailable, fall back to a
+	 * partial version string so the caller still gets useful output.
+	 */
+	ret = enetc4_vf_get_ip_minor_revision(dev, &ip_mn);
+	if (ret && ret != -ENOTSUP) {
+		ENETC_PMD_ERR("Failed to get NETC minor revision");
+		return ret;
+	}
+
+	if (ret == -ENOTSUP)
+		ret = snprintf(fw_version, fw_size, "%u.unknown", ip_mj);
+	else
+		ret = snprintf(fw_version, fw_size, "%u.%u", ip_mj, ip_mn);
+	if (ret < 0)
+		return -EINVAL;
+
+	ret += 1; /* add trailing '\0' */
+	if ((size_t)ret > fw_size)
+		return ret;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1306,6 +1440,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.fw_version_get       = enetc4_vf_fw_version_get,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.rx_queue_setup       = enetc4_rx_queue_setup,

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 06/14] net/enetc: support registers dump
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (4 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
                   ` (9 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

Implement the .get_reg operation for the ENETC4 PF and VF PMDs to dump
the device registers via rte_eth_dev_get_reg_info.

The VF dumps the registers reachable by a virtual station interface
(VSI): the station interface and per-ring BD ring registers. Port
registers are not accessible by a VF.

The PF additionally dumps the port registers, which are only
accessible by the physical station interface (PSI).

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |  1 +
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 13 +++++
 drivers/net/enetc/enetc4_ethdev.c      | 70 ++++++++++++++++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 62 +++++++++++++++++++++++
 6 files changed, 148 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index ea0e37896d..cd757be12f 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -60,6 +60,7 @@ Key functionality includes:
   requested. RSC requires the FCS to be stripped, so it cannot be combined
   with the KEEP_CRC Rx offload.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
+- Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index eaf474e7ba..6540a43909 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -17,6 +17,7 @@ RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
 FW version           = Y
+Registers dump       = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 5ac2bb4de0..deb3bc5d52 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -65,6 +65,7 @@ New Features
   * Added Receive Segment Coalesce (RSC / hardware LRO) support for ENETC4 PF and VF.
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
   * Added firmware version reporting for the ENETC4 VF.
+  * Added register dump support for ENETC4 PF and VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index b1428f1bca..73404308fe 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -397,6 +397,19 @@ enetc_bd_unused(struct enetc_bdr *bdr)
 	return bdr->bd_count + bdr->next_to_clean - bdr->next_to_use - 1;
 }
 
+/* Per-ring Tx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_txbdr_regs[] = {
+	ENETC_TBMR, ENETC_TBSR, ENETC_TBBAR0, ENETC_TBBAR1,
+	ENETC_TBCIR, ENETC_TBLENR,
+};
+
+/* Per-ring Rx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_rxbdr_regs[] = {
+	ENETC_RBMR, ENETC_RBSR, ENETC_RBBSR, ENETC_RBCIR,
+	ENETC_RBBAR0, ENETC_RBBAR1, ENETC_RBPIR, ENETC_RBLENR,
+};
+
+
 /* CBDR prototypes */
 int enetc4_setup_cbdr(struct rte_eth_dev *dev, struct enetc_hw *hw,
 			int bd_count, struct netc_cbdr *cbdr);
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 7f95171b6d..681bac3ecc 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1176,6 +1176,75 @@ enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 	return ptypes;
 }
 
+/* Station interface registers dumped by .get_reg */
+static const uint32_t enetc4_pf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR,
+};
+
+/* Port registers dumped by .get_reg (accessible only by the PF) */
+static const uint32_t enetc4_pf_port_regs[] = {
+	ENETC4_PMR, ENETC4_PSIPMMR, ENETC4_PMAR0, ENETC4_PMAR1,
+	ENETC4_PM_CMD_CFG(0), ENETC4_PM_MAXFRM(0), ENETC4_PM_IF_MODE(0),
+	ENETC4_PM_IF_STATUS(0),
+};
+
+/*
+ * Dump the PF-accessible registers: station interface, port and per-ring
+ * BD ring registers. When info->data is NULL, only the register count and
+ * width are reported so the caller can size its buffer.
+ */
+static int
+enetc4_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_pf_si_regs);
+	count += RTE_DIM(enetc4_pf_port_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_si_regs); i++)
+		*buf++ = enetc4_rd(enetc_hw, enetc4_pf_si_regs[i]);
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_port_regs); i++)
+		*buf++ = enetc4_port_rd(enetc_hw, enetc4_pf_port_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 /*
  * The set of PCI devices this driver supports
  */
@@ -1194,6 +1263,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.link_update          = enetc4_link_update,
 	.stats_get            = enetc4_stats_get,
 	.stats_reset          = enetc4_stats_reset,
+	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index a8864cc6ce..8af22f069b 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -931,6 +931,66 @@ enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_si
 	return 0;
 }
 
+/* VF station interface registers dumped by .get_reg */
+static const uint32_t enetc4_vf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR, ENETC4_SIMSIVR, ENETC4_VSIIER, ENETC4_VSIIDR,
+	ENETC4_VSIMSGSR, ENETC4_VSIMSGRR,
+};
+
+/*
+ * Dump the VF-accessible registers. Only station interface and per-ring
+ * BD ring registers are reachable by a VF; port registers are not.
+ * When info->data is NULL, only the register count and width are
+ * reported so the caller can size its buffer.
+ */
+static int
+enetc4_vf_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_vf_si_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_vf_si_regs); i++)
+		*buf++ = enetc_rd(enetc_hw, enetc4_vf_si_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1441,6 +1501,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
@@ -1461,6 +1522,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.mac_addr_set         = enetc4_vf_set_mac_addr,
 	.mac_addr_add	      = enetc4_vf_mac_addr_add,

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 07/14] net/enetc: support ethtool ring parameters
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (5 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 06/14] net/enetc: support registers dump Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
                   ` (8 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

The ethtool ring parameter query relies on the queue info ethdev
ops (.rxq_info_get/.txq_info_get) together with the descriptor
limits reported in dev_info. The PF already registered these ops,
but the VF ops tables did not, so ring parameter queries failed on
the VF.

Make enetc4_rxq_info_get() and enetc4_txq_info_get() non-static and
register them in both VF ops tables so ring parameters are reported
for both the PF and VF.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst | 1 +
 drivers/net/enetc/enetc.h              | 4 ++++
 drivers/net/enetc/enetc4_ethdev.c      | 4 ++--
 drivers/net/enetc/enetc4_vf.c          | 4 ++++
 4 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index deb3bc5d52..9e333edf4f 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -66,6 +66,7 @@ New Features
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
+  * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 73404308fe..367d350dde 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -339,6 +339,10 @@ int enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx);
 void enetc4_tx_queue_release(struct rte_eth_dev *dev, uint16_t qid);
 const uint32_t *enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 			size_t *no_of_elements);
+void enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_rxq_info *qinfo);
+void enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_txq_info *qinfo);
 
 /*
  * enetc4_vf function prototype
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 681bac3ecc..afee0b90a3 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1125,7 +1125,7 @@ enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx)
 	return 0;
 }
 
-static void
+void
 enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_rxq_info *qinfo)
 {
@@ -1139,7 +1139,7 @@ enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 	qinfo->conf.rx_drop_en = 0;
 }
 
-static void
+void
 enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_txq_info *qinfo)
 {
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 8af22f069b..7623a66e98 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1508,10 +1508,12 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
@@ -1537,10 +1539,12 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 08/14] net/enetc: refresh link speed on VF link-up interrupt
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (6 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 09/14] net/enetc: support stats reset for VF Gagandeep Singh
                   ` (7 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

The interrupt-driven link-status path (enetc4_process_psi_msg) only
updated link_status when a PSI-to-VSI notification arrived; it never
re-queried the link speed from the PF.  As a result, after a link-down
followed by a link-up the cached link_speed showed the speed from the
previous session rather than the freshly negotiated one.

Fix this by:

1. Introducing enetc4_decode_link_speed() - a shared helper that maps
   a PF-to-VF speed status code to the corresponding RTE_ETH_SPEED_NUM_*
   / RTE_ETH_LINK_*_DUPLEX values, handling both the current and the
   legacy (vf_link_legacy) 4-bit message layout.

2. Calling enetc4_vf_get_link_speed() inside enetc4_process_psi_msg()
   on ENETC_LINK_UP so the negotiated speed is fetched from the PF and
   decoded immediately, before rte_eth_linkstatus_set() is called and
   the LSC callback is fired.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc4_vf.c          | 197 +++++++++++++------------
 2 files changed, 107 insertions(+), 91 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 9e333edf4f..eca0dc1dea 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -67,6 +67,7 @@ New Features
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
+  * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 7623a66e98..58e38fe398 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -324,9 +324,99 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	reply_msg->status = status;
 }
 
+/* Forward declaration: defined later in this file */
+static int enetc4_vf_get_link_speed(struct rte_eth_dev *dev,
+				     struct enetc_psi_reply_msg *reply_msg);
+
+/*
+ * Decode a PF-to-VF link-speed status code into the link_speed and
+ * link_duplex fields of *link.  vf_link_legacy selects the older
+ * 4-bit code layout used by kernel PFs before v6.18.37.
+ */
+static void
+enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
+			 struct rte_eth_link *link)
+{
+	switch (status) {
+	case ENETC_SPEED_UNKNOWN:
+		ENETC_PMD_DEBUG("Speed unknown");
+		link->link_speed = RTE_ETH_SPEED_NUM_NONE;
+		break;
+	case ENETC_SPEED_10_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_10_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_100_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_100_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_1000:
+		link->link_speed = RTE_ETH_SPEED_NUM_1G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_2500:
+		link->link_speed = RTE_ETH_SPEED_NUM_2_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_5000:
+		link->link_speed = RTE_ETH_SPEED_NUM_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	default:
+		if (vf_link_legacy) {
+			/* Legacy PF-to-VF message layout (older kernel PF):
+			 * speeds above 5Gbps use fixed 4-bit class codes.
+			 */
+			switch (status) {
+			case ENETC_SPEED_LEGACY_10G:
+				link->link_speed = RTE_ETH_SPEED_NUM_10G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_25G:
+				link->link_speed = RTE_ETH_SPEED_NUM_25G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_50G:
+				link->link_speed = RTE_ETH_SPEED_NUM_50G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_100G:
+				link->link_speed = RTE_ETH_SPEED_NUM_100G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+				ENETC_PMD_DEBUG("Speed not supported");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			default:
+				ENETC_PMD_ERR("Unknown speed status");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			}
+			break;
+		}
+		/* Any status here is > ENETC_SPEED_5000.  Reverse the formula:
+		 *   SPEED = (status - ENETC_SPEED_5000) * 1000 + 5000  (in Mbps)
+		 */
+		link->link_speed = (status - ENETC_SPEED_5000) * 1000 + 5000;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	}
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
 	int ret = 0;
@@ -345,6 +435,20 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		case ENETC_LINK_UP:
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Re-query speed from PF so the cached value reflects
+			 * the current negotiated speed after link-up.
+			 */
+			rte_free(msg);
+			msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+			if (msg) {
+				if (!enetc4_vf_get_link_speed(eth_dev, msg) &&
+				    msg->class_id == ENETC_CLASS_ID_LINK_SPEED)
+					enetc4_decode_link_speed(msg->status,
+							hw->vf_link_legacy,
+							&link);
+			} else {
+				ENETC_PMD_WARN("Failed to alloc msg for speed query");
+			}
 			break;
 		case ENETC_LINK_DOWN:
 			ENETC_PMD_DEBUG("Link is down");
@@ -1048,97 +1152,8 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_SPEED) {
-		switch (reply_msg->status) {
-		case ENETC_SPEED_UNKNOWN:
-			ENETC_PMD_DEBUG("Speed unknown");
-			link.link_speed = RTE_ETH_SPEED_NUM_NONE;
-			break;
-		case ENETC_SPEED_10_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_10_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_100_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_1000:
-			link.link_speed = RTE_ETH_SPEED_NUM_1G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_2500:
-			link.link_speed = RTE_ETH_SPEED_NUM_2_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_5000:
-			link.link_speed = RTE_ETH_SPEED_NUM_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		default:
-			if (hw->vf_link_legacy) {
-				/* Legacy PF-to-VF message layout (older kernel
-				 * PF): speeds greater than 5Gbps are encoded
-				 * with fixed 4-bit class codes rather than the
-				 * formula below.
-				 */
-				switch (reply_msg->status) {
-				case ENETC_SPEED_LEGACY_10G:
-					link.link_speed = RTE_ETH_SPEED_NUM_10G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_25G:
-					link.link_speed = RTE_ETH_SPEED_NUM_25G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_50G:
-					link.link_speed = RTE_ETH_SPEED_NUM_50G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_100G:
-					link.link_speed = RTE_ETH_SPEED_NUM_100G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
-					ENETC_PMD_DEBUG("Speed not supported");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				default:
-					ENETC_PMD_ERR("Unknown speed status");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				}
-				break;
-			}
-
-			/* Any status reaching here is greater than
-			 * ENETC_SPEED_5000, as all values from 0x0 to
-			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
-			 * greater than 5Gbps are not enumerated and follow the
-			 * formula:
-			 *
-			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
-			 *
-			 * where link_speed is in Mbps. Reverse it here to get the
-			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
-			 *
-			 * The PF only reports speeds that map to a well-known
-			 * RTE_ETH_SPEED_NUM_* value, so the computed value is a
-			 * valid DPDK speed. If a future speed not yet defined in
-			 * DPDK needs to be supported, the corresponding
-			 * RTE_ETH_SPEED_NUM_* value must first be added upstream.
-			 */
-			link.link_speed = (reply_msg->status - ENETC_SPEED_5000)
-					  * 1000 + 5000;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		}
+		enetc4_decode_link_speed(reply_msg->status,
+					 hw->vf_link_legacy, &link);
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 09/14] net/enetc: support stats reset for VF
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (7 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
                   ` (6 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

The NETC SI-level hardware counters (SIROCT0, SIRFRM0, SITOCT0,
SITFRM0, SITDFCR) are read-only for a VF and cannot be directly
zeroed. No VSI-PSI command class exists for stats reset, and using
FLR/soft-reset to clear counters is not reliable due to a known
hardware erratum on some NETC platforms.

Implement stats_reset for the enetc4 VF PMD using a software
snapshot/delta approach: on stats_reset, the current HW counter
values are captured as a baseline in a new per-device
enetc4_vf_stats_saved struct. stats_get then reports the delta
(current_hw_value - saved_baseline), so counters appear to start
from zero after each reset call. Per-ring software Rx error
accumulators (ierrors) are zeroed directly on reset.

Register the stats_reset callback in both VF ops tables
(enetc4_vf_ops and enetc4_vf_ops_no_vsi_m) so the feature is
available regardless of whether the VSI messaging path is enabled.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 17 +++++++
 drivers/net/enetc/enetc4_vf.c          | 61 +++++++++++++++++++++++---
 3 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index eca0dc1dea..b88e2825a6 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -68,6 +68,7 @@ New Features
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
+  * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 367d350dde..3590cf6639 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -105,6 +105,21 @@ struct enetc_bdr {
 	uint8_t rsc_enable;
 };
 
+/*
+ * Saved SI counter baseline for VF stats reset. Since the SI-level
+ * hardware counters (SIROCT0, SIRFRM0, SITOCT0, SITFRM0, SITDFCR)
+ * are read-only for a VF and cannot be directly zeroed, stats_reset
+ * captures the current counter values as a baseline. stats_get then
+ * reports the delta: current_hw_value - baseline.
+ */
+struct enetc4_vf_stats_saved {
+	uint64_t ipackets;
+	uint64_t opackets;
+	uint64_t ibytes;
+	uint64_t obytes;
+	uint64_t oerrors;
+};
+
 struct enetc_eth_hw {
 	struct rte_eth_dev *ndev;
 	struct enetc_hw hw;
@@ -124,6 +139,8 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	/* Baseline snapshot for VF stats reset (software delta approach). */
+	struct enetc4_vf_stats_saved vf_stats_saved;
 };
 
 /*
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 58e38fe398..70cef39116 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -225,15 +225,64 @@ enetc4_vf_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
 	uint8_t i;
 
 	PMD_INIT_FUNC_TRACE();
-	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
-	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
-	stats->ibytes = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
-	stats->obytes = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
-	stats->oerrors = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/*
+	 * The SI-level counters are read-only for a VF; they cannot be
+	 * zeroed directly. Instead, stats_reset captures a baseline
+	 * snapshot, and stats_get reports the delta so that the reported
+	 * values appear to start from zero after each reset call.
+	 */
+	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0) -
+			  hw->vf_stats_saved.ipackets;
+	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0) -
+			  hw->vf_stats_saved.opackets;
+	stats->ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0) -
+			  hw->vf_stats_saved.ibytes;
+	stats->obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0) -
+			  hw->vf_stats_saved.obytes;
+	stats->oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR) -
+			  hw->vf_stats_saved.oerrors;
+
 	for (i = 0; i < dev->data->nb_rx_queues; i++) {
 		rx_ring = dev->data->rx_queues[i];
 		stats->ierrors += rx_ring->ierrors;
 	}
+
+	return 0;
+}
+
+/*
+ * Reset VF statistics by capturing a new baseline snapshot of the
+ * SI-level hardware counters. Because those counters are read-only
+ * for a VF (hardware erratum prevents reliable clear via FLR/soft
+ * reset too), the driver uses a software delta approach: every
+ * stats_get call reports current_hw_value - saved_baseline.
+ */
+static int
+enetc4_vf_stats_reset(struct rte_eth_dev *dev)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_bdr *rx_ring;
+	uint8_t i;
+
+	PMD_INIT_FUNC_TRACE();
+
+	/* Snapshot current HW SI counter values as the new zero baseline. */
+	hw->vf_stats_saved.ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
+	hw->vf_stats_saved.opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
+	hw->vf_stats_saved.ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
+	hw->vf_stats_saved.obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
+	hw->vf_stats_saved.oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/* Reset the per-ring software Rx error accumulators. */
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		rx_ring = dev->data->rx_queues[i];
+		if (rx_ring)
+			rx_ring->ierrors = 0;
+	}
+
 	return 0;
 }
 
@@ -1514,6 +1563,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
 	.get_reg              = enetc4_vf_get_regs,
@@ -1538,6 +1588,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 10/14] net/enetc4: add per-queue Rx interrupt support for VF
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (8 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 09/14] net/enetc: support stats reset for VF Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
                   ` (5 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

Add MSI-X per-queue Rx interrupt support to the ENETC4 VF PMD on the
cacheable (default) Rx path. This allows applications such as l3fwd-power
to block in epoll_wait when no traffic is present, reducing CPU utilization
to near zero.

MSI-X vector 0 is reserved for the PSI-to-VSI mailbox. Rx queue i is
mapped to MSI-X vector i + 1 via ENETC_SIMSIRRV. Per-ring coalescing is
enabled with ICPT=1 so the first arriving packet fires the interrupt
immediately. The SIRXIDR W1C detect bit is cleared before re-arming
ENETC_RBIER to prevent spurious interrupts after traffic stops.

The interrupt infrastructure (rte_intr_efd_enable +
rte_intr_vec_list_alloc) is set up before rte_intr_enable() so that
vfio-pci can wire each MSI-X vector to its eventfd when programming
the MSI-X table.

enetc4_dev_configure() is updated to trigger interrupt setup when
intr_conf.rxq is set (not only when intr_conf.lsc is set), as applications
like l3fwd-power set intr_conf.rxq without setting lsc.

Documentation is updated to describe the feature, list the kernel and
host setup steps, and provide an example l3fwd-power command for single
queue/core interrupt-driven operation.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             | 69 +++++++++++++++++++-
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc4_hw.h     |  2 +
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      |  9 +--
 drivers/net/enetc/enetc4_vf.c          | 89 +++++++++++++++++++++++++-
 7 files changed, 166 insertions(+), 6 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index cd757be12f..d8e323d614 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,10 @@ Key functionality includes:
   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.
+- Per-queue Rx interrupts on VFs (cacheable Rx path only), enabling
+  interrupt-driven receive with ``vfio-pci``. Applications set
+  ``intr_conf.rxq = 1`` in ``rte_eth_conf`` to activate this feature.
+  See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
@@ -95,7 +99,9 @@ The following dependencies are not part of DPDK and must be installed separately
 Driver compilation and testing
 ------------------------------
 
-Follow instructions available in the document :doc:`build_and_test` to launch **testpmd**.
+Follow instructions available in the document
+:ref:`compiling and testing a PMD for a NIC <pmd_build_and_test>`
+to launch **testpmd**.
 
 
 Driver Arguments (devargs)
@@ -156,3 +162,64 @@ PF/Common devargs
   Usage example::
 
     dpdk-testpmd -a 0000:00:00.0,nc=1 -- -i
+
+
+Rx Interrupt Mode (VF)
+----------------------
+
+The ENETC4 VF PMD supports per-queue MSI-X Rx interrupts on the cacheable
+(default) Rx path. This allows applications to block in ``epoll_wait``
+instead of busy-polling, reducing CPU utilization when traffic is absent.
+
+**MSI-X vector assignment**
+
+ENETC4 VF MSI-X vector 0 is reserved for the PSI-to-VSI mailbox interrupt
+(link status notifications). Rx queue ``i`` is mapped to vector ``i + 1``.
+The driver allocates all required eventfds before calling
+``rte_intr_enable()`` so that ``vfio-pci`` can wire each MSI-X vector to
+its eventfd when it programs the MSI-X table.
+
+**Kernel and driver requirements**
+
+- ``vfio-pci`` kernel module with no-IOMMU mode enabled (no SMMU required).
+- The non-cacheable memory mode (``nc=1`` devarg) does **not** support
+  Rx interrupts and returns ``-ENOTSUP`` from ``rx_queue_intr_enable``.
+
+**Host setup**
+
+.. code-block:: console
+
+   # Enable vfio-pci no-IOMMU mode (if SMMU is not available)
+   modprobe vfio enable_unsafe_noiommu_mode=1
+   modprobe vfio-pci
+
+   # Bind the VF to vfio-pci
+   echo vfio-pci > /sys/bus/pci/devices/<vf_pci_addr>/driver_override
+   echo <vf_pci_addr> > /sys/bus/pci/drivers_probe
+
+**Running l3fwd-power with a single queue and core**
+
+The ``l3fwd-power`` sample application demonstrates interrupt-driven Rx.
+It sets ``intr_conf.rxq = 1`` in ``rte_eth_conf``, which triggers the VF
+interrupt setup in the driver. The ``--vfio-intr=msix`` EAL flag instructs
+DPDK to use MSI-X eventfds for interrupt signalling.
+
+.. code-block:: console
+
+   ./dpdk-l3fwd-power -l 0-1 -n 1 --vfio-intr=msix \
+       -a <vf_pci_addr> -- \
+       -p 0x1 --config="(0,0,1)" --no-numa --interrupt-only
+
+Where:
+
+- ``-l 0-1`` assigns the main thread to core 0 and the forwarding lcore to
+  core 1.
+- ``-a <vf_pci_addr>`` specifies the VF PCI address (e.g. ``0000:01:00.1``).
+- ``--config="(0,0,1)"`` maps port 0, queue 0 to lcore 1.
+- ``--interrupt-only`` enables pure interrupt mode (no busy-poll fallback).
+
+With no incoming traffic the forwarding lcore sleeps in ``epoll_wait``;
+CPU utilization drops to near zero. On the first arriving packet the MSI-X
+interrupt fires, the lcore wakes, drains the ring, disables the interrupt,
+processes the burst, then re-enables and re-arms the interrupt before
+returning to sleep.
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 6540a43909..812c215b15 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -5,6 +5,7 @@
 ;
 [Features]
 Link status event    = Y
+Queue interrupt      = Y
 Speed capabilities   = Y
 Link status          = Y
 LRO                  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index b88e2825a6..9b39debc1d 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -69,6 +69,7 @@ New Features
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
+  * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 8ad8f169d2..9543e982a2 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -250,6 +250,8 @@ struct enetc_rx_bd_ext {
 #define ENETC4_VSIIDR            0xA08
 #define ENETC4_VSIIER_MRIE       BIT(9)
 #define ENETC4_SI_INT_IDX        0
+/* MSI-X vector base for per-Rx-queue interrupts; vector 0 is the mailbox. */
+#define ENETC4_VF_RX_VEC_BASE    1
 
 /* VSI Registers */
 #define ENETC4_VSIMSGSR  0x204   /* RO */
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 3590cf6639..903829323e 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -135,6 +135,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index afee0b90a3..d987f1288c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -839,7 +839,8 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		return 0;
 
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0)
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0)
 			enetc4_vf_dev_intr(dev, false);
 		ret = enetc4_vf_dev_stop(dev);
 	} else {
@@ -961,12 +962,12 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	if (hw->device_id != ENETC4_DEV_ID_VF)
 		enetc4_port_wr(enetc_hw, ENETC4_PARCSCR, checksum);
 
-	/* Enable interrupts */
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0) {
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0) {
 			ret = enetc4_vf_dev_intr(dev, true);
 			if (ret)
-				ENETC_PMD_WARN("Failed to setup link interrupts");
+				ENETC_PMD_WARN("Failed to setup VF interrupts: %d", ret);
 		}
 	}
 
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 70cef39116..f6e2ee46bc 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1555,6 +1555,52 @@ static const struct rte_pci_id pci_vf_id_enetc4_map[] = {
 	{ .vendor_id = 0, /* sentinel */ },
 };
 
+static int
+enetc4_vf_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t vec;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return -ENOTSUP;
+
+	vec = queue_id + ENETC4_VF_RX_VEC_BASE;
+
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), vec);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR1, 0);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR0,
+			ENETC4_RBICR0_ICEN | ENETC4_RBICR0_ICPT(1));
+	enetc_wr(enetc_hw, ENETC_SIRXIDR, BIT(queue_id));
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, ENETC_RBIER_RXTIE);
+
+	return 0;
+}
+
+static int
+enetc4_vf_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return 0;
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, 0);
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), 0);
+
+	return 0;
+}
+
 /* Features supported by this driver */
 /* ops table used when VSI messaging is disabled */
 static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
@@ -1568,7 +1614,15 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.fw_version_get       = enetc4_vf_fw_version_get,
 	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
+	.mac_addr_set         = enetc4_vf_set_mac_addr,
+	.mac_addr_add	      = enetc4_vf_mac_addr_add,
+	.promiscuous_enable   = enetc4_vf_promisc_enable,
+	.promiscuous_disable  = enetc4_vf_promisc_disable,
+	.allmulticast_enable  = enetc4_vf_multicast_enable,
+	.allmulticast_disable = enetc4_vf_multicast_disable,
 	.link_update	      = enetc4_vf_link_update_dummy,
+	.vlan_filter_set      = enetc4_vf_vlan_filter_set,
+	.vlan_offload_set     = enetc4_vf_vlan_offload_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
@@ -1606,6 +1660,8 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
 	.rxq_info_get         = enetc4_rxq_info_get,
+	.rx_queue_intr_enable  = enetc4_vf_rx_queue_intr_enable,
+	.rx_queue_intr_disable = enetc4_vf_rx_queue_intr_disable,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
@@ -1851,6 +1907,36 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		/* Vector index 0 */
 		enetc_wr(enetc_hw, ENETC4_SIMSIVR, ENETC4_SI_INT_IDX);
 
+		if (rte_intr_cap_multiple(intr_handle) &&
+		    eth_dev->data->nb_rx_queues > 0) {
+			uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+			uint16_t i;
+
+			ret = rte_intr_efd_enable(intr_handle,
+					nb_rx + ENETC4_VF_RX_VEC_BASE);
+			if (ret) {
+				ENETC_PMD_WARN("Failed to enable per-queue Rx eventfds: %d",
+					       ret);
+				ret = 0;
+				hw->rxq_intr_en = 0;
+			} else {
+				ret = rte_intr_vec_list_alloc(intr_handle,
+						"enetc4_vf_rx_intr", nb_rx);
+				if (ret) {
+					ENETC_PMD_WARN("Failed to alloc intr vec list: %d",
+						       ret);
+					rte_intr_efd_disable(intr_handle);
+					hw->rxq_intr_en = 0;
+				} else {
+					for (i = 0; i < nb_rx; i++)
+						rte_intr_vec_list_index_set(
+							intr_handle, i,
+							i + ENETC4_VF_RX_VEC_BASE);
+					hw->rxq_intr_en = 1;
+				}
+			}
+		}
+
 		/* enable uio/vfio intr/eventfd mapping */
 		ret = rte_intr_enable(intr_handle);
 		if (ret) {
@@ -1874,6 +1960,7 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		ENETC_PMD_WARN("Failed to un-register link notification %d", ret);
 disable:
 	enetc_vf_enable_mr_int(enetc_hw, false);
+	hw->rxq_intr_en = 0;
 	ret = rte_intr_disable(intr_handle);
 	if (ret)
 		ENETC_PMD_WARN("Failed to disable INTR %d", ret);
@@ -1893,7 +1980,7 @@ static struct rte_pci_driver rte_enetc4_vf_pmd = {
 
 RTE_PMD_REGISTER_PCI(net_enetc4_vf, rte_enetc4_vf_pmd);
 RTE_PMD_REGISTER_PCI_TABLE(net_enetc4_vf, pci_vf_id_enetc4_map);
-RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic");
+RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic | vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 11/14] net/enetc4: add SI-based port VLAN insertion and removal
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (9 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
                   ` (4 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

Implement hardware VLAN tag insertion on Tx and removal on Rx
via the per-SI VLAN isolation (pvid) mechanism on ENETC4.

On a PF, the driver writes directly to PSIaVLANR(0) (VID + enable
bit) and PSIaCFGR0(0) (SIVIE for Tx insertion, VTE for Rx removal,
SIVC_CVLAN to allow C-VLAN 0x8100 TPID).

On a privileged VF, the request is forwarded to the kernel PF via
the VSI-PSI mailbox using a new command class 0x24 (SI VLAN
isolation, cmd ID 0x1). The kernel PF handler programs the same
registers on behalf of the VF's station interface. The class 0x24
reply is added to the pass-through list in enetc4_msg_vsi_send() so
it is handled correctly alongside the existing command classes.

New additions:
- ENETC4_PSIVLANR(a) and ENETC4_PSICFGR0(a) register macros with
  field definitions in enetc4_hw.h
- ENETC_CLASS_ID_SI_VLAN_ISO (0x24) and ENETC_CMD_ID_SET_SI_VLAN_ISO
  enum values, plus struct enetc_msg_si_vlan_iso message layout and
  bit definitions in enetc.h
- enetc4_vlan_pvid_set() for PF in enetc4_ethdev.c
- enetc4_vf_vlan_pvid_set() for VF in enetc4_vf.c, registered as
  .vlan_pvid_set in both enetc4_ops and enetc4_vf_ops

The feature is activated in testpmd with:
  tx_vlan set pvid <port_id> <vlan_id> on|off

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 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     | 14 +++++
 drivers/net/enetc/enetc.h              | 23 +++++++
 drivers/net/enetc/enetc4_ethdev.c      | 39 ++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 85 ++++++++++++++++++++++++++
 7 files changed, 167 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index d8e323d614..bb0628ccf6 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -65,6 +65,10 @@ Key functionality includes:
   See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
+- SI-based port VLAN (pvid): Hardware VLAN tag insertion on Tx and removal on Rx, configured
+  via ``rte_eth_dev_set_vlan_pvid``. On a PF the registers are written directly; on a privileged
+  VF the request is forwarded to the kernel PF through the VSI-PSI mailbox (class 0x24).
+  Use the testpmd command ``tx_vlan set pvid <port_id> <vlan_id> on|off`` to enable or disable.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 812c215b15..9dc8fd33f8 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 9b39debc1d..e6d96b2a50 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -70,6 +70,7 @@ New Features
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
+  * Added SI-based port VLAN insertion (Tx) and removal (Rx) 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 9543e982a2..a9552f21f4 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -277,6 +277,20 @@ struct enetc_rx_bd_ext {
 #define ENETC4_SICTR0		0x18
 #define ENETC4_SICTR1		0x1c
 
+/* PSI SI VLAN register: per-SI VLAN tag for insertion/removal */
+#define ENETC4_PSIVLANR(a)		((a) * 0x80 + 0x2008)
+#define ENETC4_PSIVLANR_E		BIT(31)  /* enable SI VLAN processing */
+#define ENETC4_PSIVLANR_VTEA		BIT(30)  /* 0=strip tag, 1=zero VID */
+#define ENETC4_PSIVLANR_PCP(v)		(((uint32_t)(v) & 0x7) << 13)
+#define ENETC4_PSIVLANR_DEI		BIT(12)
+#define ENETC4_PSIVLANR_VID(v)		((uint32_t)(v) & 0xfff)
+
+/* PSI SI configuration register 0 */
+#define ENETC4_PSICFGR0(a)		((a) * 0x80 + 0x2010)
+#define ENETC4_PSICFGR0_SIVC_CVLAN	BIT(24)  /* allow C-VLAN 0x8100 */
+#define ENETC4_PSICFGR0_SIVIE		BIT(14)  /* SI VLAN insertion enable */
+#define ENETC4_PSICFGR0_VTE		BIT(12)  /* SI VLAN removal enable */
+
 /* general register accessors */
 #define enetc4_rd_reg(reg)	rte_read32((void *)(reg))
 #define enetc4_wr_reg(reg, val)  rte_write32((val), (void *)(reg))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 903829323e..72e43572f7 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -187,6 +187,8 @@ struct enetc_eth_adapter {
 enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
+	/* Configure SI VLAN isolation (insert / remove) */
+	ENETC_CLASS_ID_SI_VLAN_ISO = 0x24,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
 	ENETC_CLASS_ID_LINK_SPEED = 0x81,
 	ENETC_CLASS_ID_GET_IP_VER = 0xF0
@@ -212,6 +214,11 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* Command IDs for class ID 0x24 (SI VLAN isolation) */
+enum enetc_msg_si_vlan_cmd_id {
+	ENETC_CMD_ID_SET_SI_VLAN_ISO = 1,
+};
+
 /* IP_VER value returned when the version is not available */
 #define ENETC_IP_VER_NOT_AVAILABLE	0xFF
 
@@ -323,6 +330,22 @@ struct enetc_msg_vlan_exact_filter {
 	uint8_t reserved2;
 };
 
+/* VSI-PSI SI VLAN isolation message (class 0x24, cmd 0x1) */
+struct enetc_msg_si_vlan_iso {
+	struct enetc_msg_cmd_header header;
+	uint8_t ctrl;          /* E[7], VTEA[6], TPID[1:0] */
+	uint8_t pcp_dei_vid_hi; /* PCP[7:5], DEI[4], VID[11:8] */
+	uint8_t vid_lo;        /* VID[7:0] */
+	uint8_t reserved_0;
+	uint8_t flags;         /* SVIE[2], VTE[1] */
+	uint8_t reserved_1[3];
+};
+
+#define ENETC_SI_VLAN_ISO_E	BIT(7)  /* ctrl: enable SI VLAN */
+#define ENETC_SI_VLAN_ISO_VTEA	BIT(6)  /* ctrl: 0=strip, 1=zero VID */
+#define ENETC_SI_VLAN_ISO_SVIE	BIT(2)  /* flags: Tx insertion enable */
+#define ENETC_SI_VLAN_ISO_VTE	BIT(1)  /* flags: Rx removal enable */
+
 struct enetc_psi_reply_msg {
 	uint8_t class_id;
 	uint8_t status;
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index d987f1288c..ae40cc69e0 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -872,6 +872,44 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 	return ret;
 }
 
+/* Configure SI-based VLAN insertion (Tx) and removal (Rx) for the PF. */
+static int
+enetc4_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t psivlanr, psicfgr0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	psicfgr0 = enetc4_port_rd(enetc_hw, ENETC4_PSICFGR0(0));
+
+	if (on) {
+		/* Build the VLAN register: enable bit + VID (PCP/DEI left 0) */
+		psivlanr = (uint32_t)ENETC4_PSIVLANR_E |
+			   ENETC4_PSIVLANR_VID(vlan_id);
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), psivlanr);
+
+		/* Allow C-VLAN TPID, enable Tx insertion and Rx removal */
+		psicfgr0 |= ENETC4_PSICFGR0_SIVC_CVLAN |
+			    ENETC4_PSICFGR0_SIVIE |
+			    ENETC4_PSICFGR0_VTE;
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+	} else {
+		/* Clear Tx insertion, Rx removal and C-VLAN allow bits */
+		psicfgr0 &= ~(ENETC4_PSICFGR0_SIVC_CVLAN |
+			      ENETC4_PSICFGR0_SIVIE |
+			      ENETC4_PSICFGR0_VTE);
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+
+		/* Clear the VLAN register (disable SI VLAN processing) */
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), 0);
+	}
+
+	return 0;
+}
+
 static int
 enetc4_promiscuous_enable(struct rte_eth_dev *dev)
 {
@@ -1267,6 +1305,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
+	.vlan_pvid_set        = enetc4_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index f6e2ee46bc..208543e2da 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -589,8 +589,11 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
 		case ENETC_CLASS_ID_GET_IP_VER:
+		case ENETC_CLASS_ID_SI_VLAN_ISO:
 			break;
 		default:
+			ENETC_PMD_ERR("Unexpected reply class_id=0x%02x vsimsgsr=0x%08x",
+				      class_id, vsimsgsr);
 			err = -EIO;
 		}
 	}
@@ -1493,6 +1496,87 @@ static int enetc4_vf_vlan_offload_set(struct rte_eth_dev *dev, int mask __rte_un
 	return 0;
 }
 
+/* Configure SI-based VLAN insertion/removal via VSI-PSI mailbox (class 0x24). */
+static int
+enetc4_vf_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_si_vlan_iso *cmd;
+	struct enetc_msg_swbd *msg;
+	struct enetc_psi_reply_msg *reply_msg;
+	uint32_t msg_size;
+	int err = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	reply_msg = rte_zmalloc(NULL, sizeof(*reply_msg), RTE_CACHE_LINE_SIZE);
+	if (!reply_msg) {
+		ENETC_PMD_ERR("Failed to alloc memory for reply_msg");
+		return -ENOMEM;
+	}
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_si_vlan_iso),
+			     ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg body");
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	if (msg->dma == RTE_BAD_IOVA) {
+		ENETC_PMD_ERR("Failed to get IOVA for SI VLAN isolation msg body");
+		rte_free(msg->vaddr);
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->size = msg_size;
+
+	cmd = (struct enetc_msg_si_vlan_iso *)msg->vaddr;
+
+	if (on) {
+		cmd->ctrl = (uint8_t)ENETC_SI_VLAN_ISO_E;
+		cmd->pcp_dei_vid_hi = (uint8_t)((vlan_id >> 8) & 0x0f);
+		cmd->vid_lo = (uint8_t)(vlan_id & 0xff);
+		cmd->flags = (uint8_t)(ENETC_SI_VLAN_ISO_SVIE | ENETC_SI_VLAN_ISO_VTE);
+	}
+	/* on=0: all fields zero (rte_zmalloc cleared the buffer) */
+
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_SI_VLAN_ISO,
+				     ENETC_CMD_ID_SET_SI_VLAN_ISO, 0, 0, 0);
+
+	/* send the command and wait for PSI reply */
+	err = enetc4_msg_vsi_send(enetc_hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error for SI VLAN isolation");
+		goto end;
+	}
+
+	enetc4_msg_vsi_reply_msg(enetc_hw, reply_msg);
+
+	if (reply_msg->class_id != ENETC_MSG_CLASS_ID_CMD_SUCCESS) {
+		ENETC_PMD_ERR("SI VLAN isolation command failed: class_id=0x%x status=0x%x",
+			      reply_msg->class_id, reply_msg->status);
+		err = -EINVAL;
+	}
+
+end:
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	rte_free(reply_msg);
+	return err;
+}
+
 static int
 enetc4_vf_mtu_set(struct rte_eth_dev *dev __rte_unused, uint16_t mtu __rte_unused)
 {
@@ -1623,6 +1707,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.vlan_filter_set      = enetc4_vf_vlan_filter_set,
 	.vlan_offload_set     = enetc4_vf_vlan_offload_set,
+	.vlan_pvid_set        = enetc4_vf_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 12/14] net/enetc4: update VF link status to bitmask encoding
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (10 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

The PF-to-VF link status notification code was previously a two-value
enum (UP=0x0, DOWN=0x1). Change this to a bitmask so future bits can
carry additional state alongside the link up/down indication:

  ENETC_LINK_DOWN  BIT(0)  -- set when link is down

Link up is now encoded as the DOWN bit being clear, which keeps the
wire value for link-down identical (0x1) and ensures backward
compatibility with older kernel PFs.

Changes:
- enetc.h: replace enum link_status with a #define bitmask;
  drop ENETC_LINK_UP (no longer a named constant).
- enetc4_vf.c enetc4_process_psi_msg(): replace switch/case on
  ENETC_LINK_UP/DOWN with bitmask decode.
- enetc4_vf.c enetc4_vf_link_update(): same bitmask decode.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              |  8 ++++----
 drivers/net/enetc/enetc4_vf.c          | 27 +++++++-------------------
 3 files changed, 12 insertions(+), 24 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index e6d96b2a50..5323f4fea3 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -71,6 +71,7 @@ New Features
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
+  * Updated ENETC4 VF link status reporting to use bitmask encoding.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 72e43572f7..40bad56341 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -236,10 +236,10 @@ enum vlan_status {
 	ENETC_VLAN_NO_RESOURCE = 0x3
 };
 
-enum link_status {
-	ENETC_LINK_UP = 0x0,
-	ENETC_LINK_DOWN = 0x1
-};
+/* Link status bitmask in PF-to-VF mailbox notification.
+ * Link up is encoded as the DOWN bit being clear.
+ */
+#define ENETC_LINK_DOWN  (1u << 0)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 208543e2da..d38b31c10a 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -480,8 +480,10 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 	enetc4_msg_get_psi_msg(enetc_hw, msg);
 
 	if (msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (msg->status) {
-		case ENETC_LINK_UP:
+		if (msg->status & ENETC_LINK_DOWN) {
+			ENETC_PMD_DEBUG("Link is down");
+			link.link_status = RTE_ETH_LINK_DOWN;
+		} else {
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
 			/* Re-query speed from PF so the cached value reflects
@@ -498,14 +500,6 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 			} else {
 				ENETC_PMD_WARN("Failed to alloc msg for speed query");
 			}
-			break;
-		case ENETC_LINK_DOWN:
-			ENETC_PMD_DEBUG("Link is down");
-			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status 0x%x", msg->status);
-			break;
 		}
 		ret = rte_eth_linkstatus_set(eth_dev, &link);
 		if (!ret)
@@ -1180,17 +1174,10 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (reply_msg->status) {
-		case ENETC_LINK_UP:
-			link.link_status = RTE_ETH_LINK_UP;
-			break;
-		case ENETC_LINK_DOWN:
+		if (reply_msg->status & ENETC_LINK_DOWN)
 			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status");
-			break;
-		}
+		else
+			link.link_status = RTE_ETH_LINK_UP;
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (11 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 11:28 ` [PATCH 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
                   ` (2 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

When the PF negotiates TX PAUSE on the port it signals this to the VF
via BIT(1) of the PF-to-VF link status mailbox message. The VF PMD must
respond by setting RBMR_CM (BIT(4)) on all active RX rings so the MAC
emits PAUSE frames on ingress pressure.

Add ENETC_RBMR_CM register definition, ENETC_LINK_TX_PAUSE bitmask,
and tx_pause_active state flag. Add enetc4_vf_set_congestion_mode() to
update all active RX rings and persist the state for rings started
later. Hook it into both the interrupt and poll link-update paths, and
apply the saved state in rx_queue_setup() and rx_queue_start().

RX PAUSE (honoring received PAUSE frames) is handled at the MAC level
by the PF and requires no VF PMD changes.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  1 +
 drivers/net/enetc/enetc.h              |  9 +++-
 drivers/net/enetc/enetc4_ethdev.c      | 13 +++++-
 drivers/net/enetc/enetc4_vf.c          | 59 ++++++++++++++++++++++++--
 6 files changed, 78 insertions(+), 6 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 9dc8fd33f8..d4e186559e 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+Flow control         = Y
 VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 5323f4fea3..d69888aafb 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -72,6 +72,7 @@ New Features
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
+  * Added TX PAUSE support for the ENETC4 VF via RX congestion mode.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 6e96562850..33d075fe59 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -51,6 +51,7 @@ enum enetc_bdr_type {TX, RX};
 							+ (off))
 /* RX BDR reg offsets */
 #define ENETC_RBMR		0x0 /* RX BDR mode register*/
+#define ENETC_RBMR_CM		BIT(4)  /* congestion mode: assert congestion to emit TX PAUSE */
 #define ENETC_RBMR_EN		BIT(31)
 
 #define ENETC_BMR_RESET		0x0 /* BDR reset*/
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 40bad56341..8aaf8a7eb1 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -140,6 +140,10 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	/* 1 = TX PAUSE negotiated on port; VF RX rings must have RBMR_CM set.
+	 * Updated from the PF-to-VF link status mailbox message (BIT(1)).
+	 */
+	uint8_t tx_pause_active;
 	/* Baseline snapshot for VF stats reset (software delta approach). */
 	struct enetc4_vf_stats_saved vf_stats_saved;
 };
@@ -238,8 +242,11 @@ enum vlan_status {
 
 /* Link status bitmask in PF-to-VF mailbox notification.
  * Link up is encoded as the DOWN bit being clear.
+ * TX_PAUSE is set when the port has negotiated TX PAUSE; VF must enable
+ * congestion mode (ENETC_RBMR_CM) on its RX rings accordingly.
  */
-#define ENETC_LINK_DOWN  (1u << 0)
+#define ENETC_LINK_DOWN      (1u << 0)
+#define ENETC_LINK_TX_PAUSE  (1u << 1)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index ae40cc69e0..2355522515 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -712,8 +712,12 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	if (!rx_conf->rx_deferred_start) {
-		/* enable ring */
+		/* Enable ring; apply congestion mode if TX PAUSE is already active. */
 		rx_enable |= ENETC_RBMR_EN;
+		if (adapter->hw.tx_pause_active)
+			rx_enable |= ENETC_RBMR_CM;
+		else
+			rx_enable &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
@@ -1089,7 +1093,12 @@ enetc4_rx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->rx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		rx_data = enetc4_rxbdr_rd(&priv->hw.hw, rx_ring->index,
 					 ENETC_RBMR);
-		rx_data = rx_data | ENETC_RBMR_EN;
+		rx_data |= ENETC_RBMR_EN;
+		/* Restore congestion mode if TX PAUSE is active. */
+		if (priv->hw.tx_pause_active)
+			rx_data |= ENETC_RBMR_CM;
+		else
+			rx_data &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&priv->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_data);
 		dev->data->rx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index d38b31c10a..9223d5d179 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -461,6 +461,37 @@ enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
 	}
 }
 
+/*
+ * Set or clear ENETC_RBMR_CM (congestion mode) on all active VF RX rings.
+ * When set, the ring signals congestion to the MAC, causing it to emit TX
+ * PAUSE frames on ingress pressure. hw->tx_pause_active is updated so rings
+ * started later inherit the correct state.
+ */
+static void
+enetc4_vf_set_congestion_mode(struct rte_eth_dev *eth_dev, bool enable)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+	uint16_t i;
+	uint32_t rbmr;
+
+	hw->tx_pause_active = enable ? 1 : 0;
+
+	for (i = 0; i < nb_rx; i++) {
+		rbmr = enetc4_rxbdr_rd(enetc_hw, i, ENETC_RBMR);
+		if (enable)
+			rbmr |= ENETC_RBMR_CM;
+		else
+			rbmr &= ~(uint32_t)ENETC_RBMR_CM;
+		enetc4_rxbdr_wr(enetc_hw, i, ENETC_RBMR, rbmr);
+	}
+
+	ENETC_PMD_DEBUG("VF congestion mode %s on %u RX rings",
+			enable ? "enabled" : "disabled", nb_rx);
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
@@ -468,6 +499,7 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
+	bool tx_pause;
 	int ret = 0;
 
 	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
@@ -483,9 +515,24 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		if (msg->status & ENETC_LINK_DOWN) {
 			ENETC_PMD_DEBUG("Link is down");
 			link.link_status = RTE_ETH_LINK_DOWN;
+			/* Clear congestion mode on link-down so VF rings do not
+			 * assert congestion while the port is offline.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, false);
 		} else {
-			ENETC_PMD_DEBUG("Link is up");
+			/* BIT(1) is set when the port has negotiated TX PAUSE.
+			 * Legacy PF does not set this bit so tx_pause stays false.
+			 */
+			tx_pause = !!(msg->status & ENETC_LINK_TX_PAUSE);
+			ENETC_PMD_DEBUG("Link is up, tx_pause=%d", tx_pause);
 			link.link_status = RTE_ETH_LINK_UP;
+
+			/* Apply congestion mode before raising the carrier so
+			 * the VF rings are ready to emit PAUSE before traffic
+			 * starts flowing.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, tx_pause);
+
 			/* Re-query speed from PF so the cached value reflects
 			 * the current negotiated speed after link-up.
 			 */
@@ -1174,10 +1221,16 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		if (reply_msg->status & ENETC_LINK_DOWN)
+		if (reply_msg->status & ENETC_LINK_DOWN) {
 			link.link_status = RTE_ETH_LINK_DOWN;
-		else
+			/* Link is down: disable congestion mode on all RX rings. */
+			enetc4_vf_set_congestion_mode(dev, false);
+		} else {
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Restore congestion mode from the TX PAUSE bit. */
+			enetc4_vf_set_congestion_mode(dev,
+				!!(reply_msg->status & ENETC_LINK_TX_PAUSE));
+		}
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (12 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
@ 2026-08-06 11:28 ` Gagandeep Singh
  2026-08-06 17:00 ` [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Stephen Hemminger
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-06 11:28 UTC (permalink / raw)
  To: dev, Sachin Saxena, Vanshika Shukla; +Cc: hemant.agrawal

Add enetc4_txq_wrr devarg to configure per-ring WRR weights in the
NETC LEAF-level Tx scheduler (TBaMR register, bits [6:4]).

The NETC Tx scheduler has three levels:
  - ROOT (port/TC): strict priority + CBS (PF/port space)
  - MID  (SI/VSI):  WBFS shaping (PF space)
  - LEAF (Tx BDR):  strict priority + frame-based WRR (VF/SI space)

TBaMR is in the VF own SI space, so no Linux PF involvement is
needed for PRIO or WRR configuration.

Changes:
- enetc_hw.h: add ENETC_TBMR_WRR_MASK, ENETC_TBMR_WRR(n) macros for
  TBaMR bits [6:4], and ENETC_TBMR_PRIO_MASK for bits [2:0]
- enetc.h: add txq_wrr pointer to enetc_eth_hw struct
- enetc4_ethdev.c: add parse_txq_wrr() and wire ENETC4_TXQ_WRR devarg
  through enetc4_get_devargs() and enetc4_dev_configure(); apply WRR
  bits in enetc4_tx_queue_setup() and enetc4_tx_queue_start()

Usage:
  # strict priority: ring 0 highest
  -a 0002:00:12.0,enetc4_txq_prior="3|2|1"

  # WRR 2:4:1 on same-priority rings
  -a 0002:00:12.0,enetc4_txq_prior="1|1|1",enetc4_txq_wrr="2|4|1"

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  5 ++
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      | 87 +++++++++++++++++++++++---
 4 files changed, 86 insertions(+), 8 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index d69888aafb..3ea34523fe 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -73,6 +73,7 @@ New Features
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
   * Added TX PAUSE support for the ENETC4 VF via RX congestion mode.
+  * Added WRR Tx scheduler devarg (``enetc4_txq_wrr``) for ENETC4 VF ring weights.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 33d075fe59..5a71f01db9 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -86,6 +86,11 @@ enum enetc_bdr_type {TX, RX};
 
 #define ENETC_RTBLENR_LEN(n)		((n) & ~0x7)
 #define ENETC_TBMR_EN			BIT(31)
+/* TBaMR[WRR] bits [6:4]: weight for same-priority ring arbitration (0=1x .. 7=8x). */
+#define ENETC_TBMR_WRR_MASK		GENMASK(6, 4)
+#define ENETC_TBMR_WRR(n)		((((n) - 1) & 0x7) << 4)
+/* TBaMR[PRIO] bits [2:0]: strict priority (0=lowest, 7=highest). */
+#define ENETC_TBMR_PRIO_MASK		GENMASK(2, 0)
 
 /* Port regs, offset: 1_0000h */
 #define ENETC_PORT_BASE			0x10000
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 8aaf8a7eb1..e0943d093a 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -134,6 +134,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_timeout; /* VSI-PSI message wait timeout (iterations) */
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
+	uint32_t *txq_wrr;    /* per-queue TX WRR weight pre-shifted for TBMR[WRR] */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
 	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 2355522515..cbb48052c3 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -28,22 +28,26 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
+#define ENETC4_TXQ_WRR		"enetc4_txq_wrr"
 #define ENETC4_NC_MEMORY	"nc"
 
+
 static int
 parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 {
 	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
 	struct enetc_eth_hw *hw =
-		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
-	char *input_str = strdup(value);
+				ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
 	char *str;
 	uint32_t i = 0;
 
+	input_str = strdup(value);
 	if (!input_str)
-		return -ENOMEM;
+		return -1;
 
-	hw->txq_prior = calloc(hw->max_tx_queues, sizeof(uint32_t));
+	rte_free(hw->txq_prior);
+	hw->txq_prior = rte_zmalloc(NULL, hw->max_tx_queues * sizeof(uint32_t), 0);
 	if (!hw->txq_prior) {
 		free(input_str);
 		return -ENOMEM;
@@ -51,7 +55,46 @@ parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 
 	str = strtok(input_str, "|");
 	while (str != NULL && i < hw->max_tx_queues) {
-		hw->txq_prior[i++] = (uint32_t)atoi(str);
+		hw->txq_prior[i++] = atoi(str) & ENETC_TBMR_PRIO_MASK;
+		str = strtok(NULL, "|");
+	}
+
+	free(input_str);
+	return 0;
+}
+
+/* Parse enetc4_txq_wrr="w0|w1|..." devarg; weight 1..8 per ring. */
+static int parse_txq_wrr(const char *key __rte_unused, const char *value,
+			  void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
+	char *str;
+	uint32_t i = 0;
+	int w;
+
+	input_str = strdup(value);
+	if (!input_str)
+		return -1;
+
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = rte_zmalloc(NULL,
+			hw->max_tx_queues * sizeof(uint32_t), 0);
+	if (!hw->txq_wrr) {
+		free(input_str);
+		return -1;
+	}
+
+	str = strtok(input_str, "|");
+	while (str != NULL && i < hw->max_tx_queues) {
+		w = atoi(str);
+		if (w < 1)
+			w = 1;
+		if (w > 8)
+			w = 8;
+		hw->txq_wrr[i++] = ENETC_TBMR_WRR(w);
 		str = strtok(NULL, "|");
 	}
 
@@ -97,6 +140,17 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 			return 0;
 		}
 	}
+	if (!strcmp(key, ENETC4_TXQ_WRR)) {
+		if (rte_kvargs_process(kvlist, key,
+					parse_txq_wrr, (void *)dev) < 0) {
+			rte_kvargs_free(kvlist);
+			return 0;
+		}
+	}
+			rte_kvargs_free(kvlist);
+			return 0;
+		}
+	}
 	if (!strcmp(key, ENETC4_NC_MEMORY)) {
 		if (rte_kvargs_process(kvlist, key,
 				       parse_nc, (void *)dev) < 0) {
@@ -458,7 +512,9 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 
 		/* apply TX queue priority if configured */
 		if (priv->hw.txq_prior)
-			tx_en |= priv->hw.txq_prior[tx_ring->index];
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		/* enable ring */
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index,
 			       ENETC_TBMR, tx_en);
@@ -869,7 +925,10 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		dev->data->tx_queues[i] = NULL;
 	}
 	dev->data->nb_tx_queues = 0;
-
+	rte_free(hw->txq_prior);
+	hw->txq_prior = NULL;
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = NULL;
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_depopulate();
 
@@ -1020,6 +1079,11 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	for (i = 0; i < dev->data->nb_tx_queues; i++)
 		enetc4_rxbdr_wr(enetc_hw, i, ENETC_TBMR, ENETC_BMR_RESET);
 
+	hw->nc_mode = 0;
+	enetc4_get_devargs(dev, ENETC4_TXQ_PRIORITIES);
+	enetc4_get_devargs(dev, ENETC4_TXQ_WRR);
+	enetc4_get_devargs(dev, ENETC4_NC_MEMORY);
+
 	if (dev->data->nb_rx_queues <= 1)
 		return 0;
 
@@ -1142,7 +1206,13 @@ enetc4_tx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->tx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		tx_data = enetc4_txbdr_rd(&priv->hw.hw, tx_ring->index,
 					 ENETC_TBMR);
-		tx_data = tx_data | ENETC_TBMR_EN;
+		/* Clear scheduler bits before applying fresh devarg values. */
+		tx_data &= ~(ENETC_TBMR_PRIO_MASK | ENETC_TBMR_WRR_MASK);
+		tx_data |= ENETC_TBMR_EN;
+		if (priv->hw.txq_prior)
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index, ENETC_TBMR,
 			       tx_data);
 		dev->data->tx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
@@ -1457,5 +1527,6 @@ RTE_PMD_REGISTER_PCI_TABLE(net_enetc4, pci_id_enetc4_map);
 RTE_PMD_REGISTER_KMOD_DEP(net_enetc4, "* vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4,
 			      ENETC4_TXQ_PRIORITIES "=<string> "
+			      ENETC4_TXQ_WRR "=<string> "
 			      ENETC4_NC_MEMORY "=<int>");
 RTE_LOG_REGISTER_DEFAULT(enetc4_logtype_pmd, NOTICE);

^ permalink raw reply related	[flat|nested] 110+ messages in thread

* Re: [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (13 preceding siblings ...)
  2026-08-06 11:28 ` [PATCH 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
@ 2026-08-06 17:00 ` Stephen Hemminger
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
  15 siblings, 0 replies; 110+ messages in thread
From: Stephen Hemminger @ 2026-08-06 17:00 UTC (permalink / raw)
  To: Gagandeep Singh; +Cc: dev, hemant.agrawal

On Thu,  6 Aug 2026 16:58:22 +0530
Gagandeep Singh <g.singh@nxp.com> wrote:

> This series adds new PMD features to the ENETC4 driver targeting the
> NXP i.MX95 NETC IP.
> 
> The series covers:
> 
>  - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
>  - TSO: TCP Segmentation Offload for the VF Tx path.
>  - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
>  - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
>    8-bit to support speeds beyond 10G.
>  - Firmware version: report the NETC IP version via fw_version_get.
>  - Register dump: dump SI, port (PF) and BD ring registers.
>  - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
>  - Link-up interrupt: refresh the cached link speed on each VF link-up
>    interrupt so that link_update returns the current speed immediately.
>  - Stats reset: software snapshot/delta approach for VF counter reset.
>  - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
>    enabling interrupt-driven receive with l3fwd-power.
>  - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
>  - VF link status bitmask: switch VF link status to bitmask encoding
>    to align with the PF and newer kernel driver conventions.
>  - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
>    negotiated on the wire; adds Flow control = Y to enetc4.ini.
>  - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.
> 
> Gagandeep Singh (14):
>   net/enetc: add KEEP_CRC offload support for ENETC4
>   net/enetc: add TSO support for ENETC4 VF
>   net/enetc: add RSC (hardware LRO) support for ENETC4
>   net/enetc: extend link speed code field to 8-bit for PF-to-VF message
>   net/enetc: support firmware version get for VF
>   net/enetc: support registers dump
>   net/enetc: support ethtool ring parameters
>   net/enetc: refresh link speed on VF link-up interrupt
>   net/enetc: support stats reset for VF
>   net/enetc4: add per-queue Rx interrupt support for VF
>   net/enetc4: add SI-based port VLAN insertion and removal
>   net/enetc4: update VF link status to bitmask encoding
>   net/enetc4: enable TX PAUSE via VF RX congestion mode
>   net/enetc4: add WRR Tx scheduler devarg for VF rings
> 
>  doc/guides/nics/enetc4.rst             |  81 ++-
>  doc/guides/nics/features/enetc4.ini    |   8 +
>  doc/guides/rel_notes/release_26_11.rst |  19 +
>  drivers/net/enetc/base/enetc4_hw.h     | 129 +++-
>  drivers/net/enetc/base/enetc_hw.h      |   6 +
>  drivers/net/enetc/enetc.h              | 135 ++++-
>  drivers/net/enetc/enetc4_ethdev.c      | 394 ++++++++++++-
>  drivers/net/enetc/enetc4_vf.c          | 780 ++++++++++++++++++++++---
>  drivers/net/enetc/enetc_rxtx.c         | 531 ++++++++++++++++-
>  9 files changed, 1950 insertions(+), 133 deletions(-)

Lots of build errors, please send your patches based off main branch
and do local build tests (ideally with gcc, clang) before sending.

^ permalink raw reply	[flat|nested] 110+ messages in thread

* [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9
  2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
                   ` (14 preceding siblings ...)
  2026-08-06 17:00 ` [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Stephen Hemminger
@ 2026-08-07  3:48 ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
                     ` (14 more replies)
  15 siblings, 15 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal

V2-changes:
 - compilation fixes.

V1-changes:
This series adds new PMD features to the ENETC4 driver targeting the
NXP i.MX95 NETC IP.

The series covers:

 - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
 - TSO: TCP Segmentation Offload for the VF Tx path.
 - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
 - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
   8-bit to support speeds beyond 10G.
 - Firmware version: report the NETC IP version via fw_version_get.
 - Register dump: dump SI, port (PF) and BD ring registers.
 - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
 - Link-up interrupt: refresh the cached link speed on each VF link-up
   interrupt so that link_update returns the current speed immediately.
 - Stats reset: software snapshot/delta approach for VF counter reset.
 - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
   enabling interrupt-driven receive with l3fwd-power.
 - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
 - VF link status bitmask: switch VF link status to bitmask encoding
   to align with the PF and newer kernel driver conventions.
 - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
   negotiated on the wire; adds Flow control = Y to enetc4.ini.
 - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.

Gagandeep Singh (14):
  net/enetc: add KEEP_CRC offload support for ENETC4
  net/enetc: add TSO support for ENETC4 VF
  net/enetc: add RSC (hardware LRO) support for ENETC4
  net/enetc: extend link speed code field to 8-bit for PF-to-VF message
  net/enetc: support firmware version get for VF
  net/enetc: support registers dump
  net/enetc: support ethtool ring parameters
  net/enetc: refresh link speed on VF link-up interrupt
  net/enetc: support stats reset for VF
  net/enetc4: add per-queue Rx interrupt support for VF
  net/enetc4: add SI-based port VLAN insertion and removal
  net/enetc4: update VF link status to bitmask encoding
  net/enetc4: enable TX PAUSE via VF RX congestion mode
  net/enetc4: add WRR Tx scheduler devarg for VF rings

 doc/guides/nics/enetc4.rst             |  81 ++-
 doc/guides/nics/features/enetc4.ini    |   8 +
 doc/guides/rel_notes/release_26_11.rst |  19 +
 drivers/net/enetc/base/enetc4_hw.h     | 129 +++-
 drivers/net/enetc/base/enetc_hw.h      |   6 +
 drivers/net/enetc/enetc.h              | 135 ++++-
 drivers/net/enetc/enetc4_ethdev.c      | 402 +++++++++++--
 drivers/net/enetc/enetc4_vf.c          | 779 ++++++++++++++++++++++---
 drivers/net/enetc/enetc_rxtx.c         | 529 ++++++++++++++++-
 9 files changed, 1942 insertions(+), 146 deletions(-)

-- 
2.25.1


^ permalink raw reply	[flat|nested] 110+ messages in thread

* [PATCH v2 01/14] net/enetc: add KEEP_CRC offload support for ENETC4
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
                     ` (13 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The legacy ENETC (LS1028A) driver already supports the
RTE_ETH_RX_OFFLOAD_KEEP_CRC offload, but ENETC4 (i.MX95 NETC) did not.

Add KEEP_CRC support for ENETC4 (both PF and VF):

- Advertise RTE_ETH_RX_OFFLOAD_KEEP_CRC in the supported Rx offloads
  for the ENETC4 PF and VF. The VF reuses the PF Rx queue setup, so the
  HW configuration and datapath handling apply to both.
- Configure the per-ring RBaMR[CRC] bit in the Rx queue setup so that
  HW preserves the Ethernet FCS in the receive buffer when the offload
  is requested (0 = FCS removed, 1 = FCS preserved).
- Follow the DPDK convention used by the legacy ENETC and ixgbe drivers:
  set crc_len to RTE_ETHER_CRC_LEN when KEEP_CRC is enabled and have the
  datapath subtract crc_len from pkt_len/data_len. ENETC and ENETC4 share
  the same datapath (enetc_rxtx.c), so the semantics stay consistent.
- Handle the scatter-gather boundary case where the 4-byte FCS straddles
  the last two segments: drop the trailing segment, decrement nb_segs and
  trim the carry-over from its predecessor.

Update the enetc4 feature matrix to list CRC offload.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  6 ++++
 drivers/net/enetc/base/enetc4_hw.h     |  4 +++
 drivers/net/enetc/enetc4_ethdev.c      | 31 ++++++++++++++++++--
 drivers/net/enetc/enetc4_vf.c          |  1 +
 drivers/net/enetc/enetc_rxtx.c         | 39 +++++++++++++++++++++++---
 6 files changed, 75 insertions(+), 7 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 698140e30b..91b18d979e 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ Packet type parsing  = Y
 Basic stats          = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
+CRC offload          = Y
 Queue start/stop     = Y
 Scattered Rx         = Y
 Linux                = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..3678dd6894 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -56,6 +56,12 @@ New Features
      =======================================================
 
 
+* **Updated NXP ENETC4 PMD.**
+
+  Updated the NXP ENETC4 poll mode driver for i.MX95:
+
+  * Added KEEP_CRC Rx offload support for the ENETC4 PMD to preserve the Ethernet FCS.
+
 Removed Items
 -------------
 
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 9685e7e1e5..0105549857 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -68,6 +68,10 @@ struct enetc_msg_swbd {
 #define PM_CMD_CFG_TX_EN		BIT(0)
 #define PM_CMD_CFG_RX_EN		BIT(1)
 
+/* RBaMR[CRC]: 0 = FCS removed, 1 = FCS preserved (KEEP_CRC) */
+#define ENETC4_RBMR_CRC			BIT(8)
+
+
 /* i.MX95 supports jumbo frame, but it is recommended to set the max frame
  * size to 2000 bytes.
  */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 2ddd63dafb..1a8056580c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -11,6 +11,19 @@
 #include "enetc_logs.h"
 #include "enetc.h"
 
+/* Supported Rx offloads */
+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;
+
+/* Supported Tx offloads */
+static uint64_t dev_tx_offloads_sup =
+	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
+	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
+
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
 
@@ -536,6 +549,8 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			ENETC_DEV_PRIVATE(data->dev_private);
 	uint64_t rx_offloads = data->dev_conf.rxmode.offloads;
+	uint32_t rx_enable;
+	bool keep_crc;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_rx_desc > MAX_BD_COUNT)
@@ -549,6 +564,9 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	rx_ring->index = rx_queue_id;
+	keep_crc = !!(rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC);
+	rx_ring->crc_len = (uint8_t)(keep_crc ? RTE_ETHER_CRC_LEN : 0);
+
 	err = enetc4_alloc_rxbdr(rx_ring, nb_rx_desc);
 	if (err)
 		goto fail;
@@ -562,19 +580,25 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	data->rx_queues[rx_queue_id] = rx_ring;
 	rx_ring->rx_deferred_start = rx_conf->rx_deferred_start;
 
+	if (keep_crc)
+		rx_enable |= ENETC4_RBMR_CRC;
+	else
+		rx_enable &= ~ENETC4_RBMR_CRC;
+
 	if (!rx_conf->rx_deferred_start) {
 		/* enable ring */
+		rx_enable |= ENETC_RBMR_EN;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
-			       ENETC_RBMR_EN);
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STARTED;
 	} else {
+		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STOPPED;
 	}
 
-	rx_ring->crc_len = (uint8_t)((rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC) ?
-				     RTE_ETHER_CRC_LEN : 0);
 	return 0;
 fail:
 	rte_free(rx_ring);
@@ -582,6 +606,7 @@ 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 ef5f1e6d66..83a1e4931f 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -52,6 +52,7 @@ static uint64_t dev_rx_offloads_sup =
 	RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_VLAN_FILTER |
+	RTE_ETH_RX_OFFLOAD_KEEP_CRC |
 	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 8678bafece..e3bef607dd 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -530,6 +530,28 @@ enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
 	return rx_frm_cnt;
 }
 
+/*
+ * Trim the Ethernet FCS from a received cluster when HW CRC strip is
+ * disabled. pkt_len is reduced by crc_len. If the FCS straddles the last
+ * two segments (last seg holds fewer bytes than crc_len), drop the trailing
+ * segment and trim the carry-over from its predecessor. prev_seg is the
+ * segment preceding last_seg in the chain (the caller already tracks it).
+ */
+static inline void
+enetc_rx_crc_trim(struct rte_mbuf *first_seg, struct rte_mbuf *prev_seg,
+		  struct rte_mbuf *last_seg, uint16_t crc_len)
+{
+	first_seg->pkt_len -= crc_len;
+	if (likely(last_seg->data_len > crc_len)) {
+		last_seg->data_len -= crc_len;
+	} else if (prev_seg != NULL) {
+		first_seg->nb_segs--;
+		prev_seg->data_len -= crc_len - last_seg->data_len;
+		prev_seg->next = NULL;
+		rte_pktmbuf_free_seg(last_seg);
+	}
+}
+
 static int
 enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		    struct rte_mbuf **rx_pkts,
@@ -539,7 +561,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -572,6 +594,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg, rxbd_temp.r.parse_summary);
 			first_seg->hash.rss = rxbd_temp.r.rss_hash;
@@ -579,6 +602,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -590,7 +614,9 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
@@ -760,7 +786,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -815,6 +841,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg,
 					   rxbd->r.parse_summary);
@@ -823,6 +850,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -838,7 +866,10 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
+
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 02/14] net/enetc: add TSO support for ENETC4 VF
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
                     ` (12 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add TCP and UDP segmentation offload (TSO) for the ENETC4 virtual
function. The offload is advertised through the VF Tx capabilities and
is enabled on a per-queue basis when an application requests the TSO
offload flags during Tx queue setup.

A dedicated Tx burst, enetc_xmit_pkts_lso(), builds the large-send
descriptor chain: a standard header BD, a 16B extension BD carrying the
segment size and frame length extension, and one BD per payload buffer
with the final-frame flag on the last BD. To make room for the extra
extension BD, an LSO-enabled Tx ring is allocated with twice the number
of descriptors. The existing non-TSO Tx paths are left unchanged.

Update the ENETC4 feature list and documentation accordingly.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   2 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/base/enetc4_hw.h     |  35 +++-
 drivers/net/enetc/enetc.h              |   5 +
 drivers/net/enetc/enetc4_ethdev.c      |  45 ++++-
 drivers/net/enetc/enetc4_vf.c          |   2 +
 drivers/net/enetc/enetc_rxtx.c         | 255 +++++++++++++++++++++++++
 8 files changed, 341 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index f0dd039f6e..4d27e4048e 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -53,6 +53,8 @@ Key functionality includes:
 - Receive processing: Upon packet reception, the BD Ring status bit is set,
   facilitating packet processing.
 - 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.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 91b18d979e..8ec1e8d00f 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
+TSO                  = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 3678dd6894..6a348ab4e0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -61,6 +61,7 @@ New Features
   Updated the NXP ENETC4 poll mode driver for i.MX95:
 
   * 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.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 0105549857..64c1a5e750 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2024 NXP
+ * Copyright 2024-2026 NXP
  *
  * This header file defines the register offsets and bit fields
  * of ENETC4 PF and VFs.
@@ -24,8 +24,14 @@ struct enetc_msg_swbd {
 
 /* enetc4 txbd flags */
 #define ENETC4_TXBD_FLAGS_L4CS		BIT(0)
+/* Request LSO (Large Send Offload) segmentation for this frame */
+#define ENETC4_TXBD_FLAGS_LSO		BIT(1)
+/* L4 checksum insertion (also used for checksum update on LSO) */
 #define ENETC4_TXBD_FLAGS_L_TX_CKSUM	BIT(3)
+/* Extended descriptor: the next 16B ring entry is an extension BD */
+#define ENETC4_TXBD_FLAGS_EXT		BIT(6)
 #define ENETC4_TXBD_FLAGS_F		BIT(7)
+
 /* L4 type */
 #define ENETC4_TXBD_L4T_UDP		BIT(0)
 #define ENETC4_TXBD_L4T_TCP		BIT(1)
@@ -34,6 +40,33 @@ struct enetc_msg_swbd {
 /* IPv4 checksum */
 #define ENETC4_TXBD_IPCS		1
 
+/*
+ * Extension Transmit Buffer Descriptor (16B). When the standard BD has the
+ * extended flag set, this occupies the next ring entry and carries the extra
+ * fields required by LSO.
+ */
+struct enetc_tx_bd_ext {
+	uint32_t timestamp;	/* PTP timestamp, unused for LSO */
+	uint32_t vlan;		/* VLAN insert, unused for LSO */
+	uint32_t lso;		/* LSO_MAX_SEG_SIZE and FRM_LEN_EXT */
+	uint32_t flags;		/* extension flags */
+};
+
+/* LSO_MAX_SEG_SIZE occupies bits 13-0 of the LSO word */
+#define ENETC4_TXBD_EXT_LSO_SEG_MASK	0x3fff
+#define ENETC4_TXBD_EXT_LSO_SEG(mss) \
+		((uint32_t)((mss) & ENETC4_TXBD_EXT_LSO_SEG_MASK))
+/* FRM_LEN_EXT occupies bits 19-16 of the LSO word (top 4 bits of data len) */
+#define ENETC4_TXBD_EXT_FRM_LEN_EXT(x) \
+		(((uint32_t)((x) & 0xf)) << 16)
+/* Final flag in the extension flags word (bit 127 -> local bit 31) */
+#define ENETC4_TXBD_EXT_FLAGS_F		BIT(31)
+
+/* NETC does not create LSO frames larger than this many bytes */
+#define ENETC4_LSO_MAX_FRAME		9600
+/* Maximum LSO data unit (payload to be segmented) supported by HW: 256KB */
+#define ENETC4_LSO_MAX_DATA_UNIT	(256 * 1024)
+
 /***************************ENETC port registers**************************/
 #define ENETC4_PMR		0x10
 #define ENETC4_PMR_EN		(BIT(16) | BIT(17) | BIT(18))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index d3a8b8e34a..db349e051b 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -91,6 +91,7 @@ struct enetc_bdr {
 		void *tcisr; /* Tx */
 		int next_to_alloc; /* Rx */
 	};
+
 	struct rte_mempool *mb_pool;   /* mbuf pool to populate RX ring. */
 	/* Partial scatter-gather chain persisted across burst calls. */
 	struct rte_mbuf *pkt_first_seg; /* first segment of in-progress frame */
@@ -99,6 +100,7 @@ struct enetc_bdr {
 	uint64_t ierrors;
 	uint8_t rx_deferred_start;
 	uint8_t tx_deferred_start;
+	uint8_t lso_enable;
 };
 
 struct enetc_eth_hw {
@@ -312,6 +314,9 @@ uint16_t enetc_xmit_pkts(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
 uint16_t enetc_xmit_pkts_nc(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
+uint16_t enetc_xmit_pkts_lso(void *txq, struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts);
+
 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,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 1a8056580c..897e12e21f 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -22,7 +22,9 @@ static uint64_t dev_rx_offloads_sup =
 static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
+	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
@@ -321,28 +323,38 @@ static int
 enetc4_alloc_txbdr(struct enetc_bdr *txr, uint16_t nb_desc)
 {
 	int size;
+	uint32_t ring_desc;
 
-	size = nb_desc * sizeof(struct enetc_swbd);
+	/*
+	 * On LSO-enabled rings each frame uses an extra 16B extension BD
+	 * (logical 32B descriptor) plus the payload BDs.  Size the ring with
+	 * twice the requested entries so the effective frame capacity is
+	 * preserved.  Non-LSO rings are sized exactly as requested.
+	 */
+	ring_desc = txr->lso_enable ? (uint32_t)nb_desc * 2 : (uint32_t)nb_desc;
+
+	size = ring_desc * sizeof(struct enetc_swbd);
 	/* Zero q_swbd so buffer_addr is NULL for all uninitialized slots. */
 	txr->q_swbd = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->q_swbd == NULL)
 		return -ENOMEM;
 
 	/* Allocate the TX BD ring: each BD is struct enetc_tx_bd (16 bytes). */
-	size = nb_desc * sizeof(struct enetc_tx_bd);
+	size = ring_desc * sizeof(struct enetc_tx_bd);
 	txr->bd_base = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->bd_base == NULL) {
 		rte_free(txr->q_swbd);
 		txr->q_swbd = NULL;
 		return -ENOMEM;
 	}
-	txr->bd_count = nb_desc;
+	txr->bd_count = ring_desc;
 	txr->next_to_clean = 0;
 	txr->next_to_use = 0;
 
 	return 0;
 }
 
+
 static void
 enetc4_free_bdr(struct enetc_bdr *rxr)
 {
@@ -352,6 +364,7 @@ enetc4_free_bdr(struct enetc_bdr *rxr)
 	rxr->bd_base = NULL;
 }
 
+
 static void
 enetc4_setup_txbdr(struct enetc_hw *hw, struct enetc_bdr *tx_ring)
 {
@@ -388,6 +401,7 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	struct rte_eth_dev_data *data = dev->data;
 	struct enetc_eth_adapter *priv =
 			ENETC_DEV_PRIVATE(data->dev_private);
+	uint64_t tx_offloads;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_desc > MAX_BD_COUNT)
@@ -401,6 +415,29 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	tx_ring->index = queue_idx;
+	/*
+	 * LSO (TCP/UDP segmentation) is a port-level offload. The Tx burst is
+	 * a single device-level function pointer, so it must be consistent for
+	 * all queues; likewise every ring must be sized the same way. Decide
+	 * from the port offloads only (not the per-queue tx_conf) so that all
+	 * queues either use the LSO burst with a doubled ring, or none do.
+	 * The LSO burst also handles non-TSO packets, so it is safe on queues
+	 * that never carry TSO traffic. LSO needs HW FCS insertion, so it is
+	 * incompatible with KEEP_CRC on Rx.
+	 */
+	tx_offloads = data->dev_conf.txmode.offloads;
+	if (tx_offloads & (RTE_ETH_TX_OFFLOAD_TCP_TSO |
+			   RTE_ETH_TX_OFFLOAD_UDP_TSO)) {
+		if (data->dev_conf.rxmode.offloads &
+		    RTE_ETH_RX_OFFLOAD_KEEP_CRC) {
+			ENETC_PMD_ERR("LSO (TSO) is incompatible with KEEP_CRC");
+			rte_free(tx_ring);
+			return -EINVAL;
+		}
+		tx_ring->lso_enable = 1;
+		dev->tx_pkt_burst = &enetc_xmit_pkts_lso;
+	}
+
 	err = enetc4_alloc_txbdr(tx_ring, nb_desc);
 	if (err)
 		goto fail;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 83a1e4931f..3083a56335 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -60,6 +60,8 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO |
 	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
 
 static void
diff --git a/drivers/net/enetc/enetc_rxtx.c b/drivers/net/enetc/enetc_rxtx.c
index e3bef607dd..fb921de360 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -221,6 +221,261 @@ enetc_xmit_pkts_nc(void *tx_queue,
 	return start;
 }
 
+/*
+ * LSO (Large Send Offload) Tx burst.
+ *
+ * Dedicated burst used on Tx rings with LSO enabled (txr->lso_enable). It
+ * follows the same non-cache-coherent discipline as enetc_xmit_pkts_cacheable:
+ * payload lines are flushed with dcbf before handing the frame to HW and the
+ * written BD cache lines are flushed at the end of the batch.
+ *
+ * A TSO/USO frame uses an extended Tx descriptor: the standard BD points at
+ * the L2/L3/L4 header template (BUF_LEN = header length, FRM_LEN = payload
+ * length), followed by an extension BD in the next ring slot holding the
+ * segment size, then one BD per payload buffer with the F flag on the last.
+ * Non-TSO packets fall through to the normal encoding so mixed traffic works.
+ */
+uint16_t
+enetc_xmit_pkts_lso(void *tx_queue,
+		struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts)
+{
+	int i, start, bds_to_use;
+	struct enetc_tx_bd *txbd;
+	struct enetc_tx_bd_ext *txbd_ext;
+	struct enetc_bdr *tx_ring = (struct enetc_bdr *)tx_queue;
+	unsigned int j;
+	uint8_t *data;
+	struct rte_mbuf *seg;
+	uint16_t seg_len, segs_per_pkt;
+	bool is_first_seg;
+	int first_bd_idx, bd_count;
+
+	i = tx_ring->next_to_use;
+	bds_to_use = enetc_bd_unused(tx_ring);
+	bd_count = tx_ring->bd_count;
+	start = 0;
+
+	first_bd_idx = i;
+
+	while (start < nb_pkts) {
+		seg = tx_pkts[start];
+		segs_per_pkt = seg->nb_segs;
+
+		if (seg->ol_flags &
+		    (RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_UDP_SEG)) {
+			bool is_udp_seg =
+				!!(seg->ol_flags & RTE_MBUF_F_TX_UDP_SEG);
+			uint32_t hdr_len = seg->l2_len + seg->l3_len +
+					   seg->l4_len;
+			uint32_t data_unit;
+			uint16_t first_payload;
+			struct rte_mbuf *dseg;
+			int bds_needed;
+
+			/* Header BD + extension BD + one BD per segment. */
+			bds_needed = 2 + segs_per_pkt;
+			if (bds_to_use < bds_needed)
+				break;
+
+			/*
+			 * Skip frames with nothing to segment, or whose
+			 * headers are not fully contained in the first
+			 * segment. The first BD carries the header template
+			 * and first_payload is computed as (first segment
+			 * data_len - hdr_len), so the L2/L3/L4 headers must
+			 * reside contiguously in the first mbuf; otherwise the
+			 * unsigned subtraction would underflow.
+			 */
+			if (unlikely(hdr_len >= rte_pktmbuf_pkt_len(seg) ||
+				     hdr_len > rte_pktmbuf_data_len(seg))) {
+				start++;
+				continue;
+			}
+			data_unit = rte_pktmbuf_pkt_len(seg) - hdr_len;
+
+			/*
+			 * Skip frames that violate HW LSO limits: a zero
+			 * segment size, a payload larger than the HW data
+			 * unit, or a per-segment frame (headers + segment)
+			 * bigger than the maximum LSO frame size.
+			 */
+			if (unlikely(seg->tso_segsz == 0 ||
+				     data_unit > ENETC4_LSO_MAX_DATA_UNIT ||
+				     hdr_len + seg->tso_segsz >
+					     ENETC4_LSO_MAX_FRAME)) {
+				start++;
+				continue;
+			}
+
+			/* Standard (first) BD: header template only. */
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			seg_len = rte_pktmbuf_data_len(seg);
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			memset(txbd, 0, sizeof(*txbd));
+			tx_ring->q_swbd[i].buffer_addr = seg;
+
+			/* FRM_LEN low 16 bits = payload length, BUF_LEN = hdr. */
+			txbd->frm_len = rte_cpu_to_le_16(data_unit & 0xffff);
+			txbd->buf_len = rte_cpu_to_le_16((uint16_t)hdr_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+
+			/* Checksum control for the per-segment L3/L4 rewrite. */
+			txbd->l3_start = seg->l2_len;
+			txbd->l3_hdr_size = seg->l3_len / 4;
+			if (seg->ol_flags & RTE_MBUF_F_TX_IPV6) {
+				txbd->l3t = 1;
+			} else {
+				txbd->l3t = ENETC4_TXBD_L3T;
+				txbd->ipcs = ENETC4_TXBD_IPCS;
+			}
+			txbd->l4t = is_udp_seg ? ENETC4_TXBD_L4T_UDP :
+						 ENETC4_TXBD_L4T_TCP;
+			txbd->flags = ENETC4_TXBD_FLAGS_L_TX_CKSUM |
+				      ENETC4_TXBD_FLAGS_L4CS |
+				      ENETC4_TXBD_FLAGS_LSO |
+				      ENETC4_TXBD_FLAGS_EXT;
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/* Extension BD occupies the next ring slot. */
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			txbd_ext = (struct enetc_tx_bd_ext *)
+				   ENETC_TXBD(*tx_ring, i);
+			memset(txbd_ext, 0, sizeof(*txbd_ext));
+			txbd_ext->lso = rte_cpu_to_le_32(ENETC4_TXBD_EXT_LSO_SEG(seg->tso_segsz) |
+					ENETC4_TXBD_EXT_FRM_LEN_EXT(data_unit >> 16));
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/*
+			 * Payload BDs. The first segment's payload starts after
+			 * the header template; remaining segments are pure
+			 * payload.
+			 */
+			first_payload = (uint16_t)(seg_len - hdr_len);
+			dseg = seg;
+			is_first_seg = true;
+			while (dseg) {
+				uint16_t dlen;
+				uint64_t daddr;
+
+				if (is_first_seg) {
+					dlen = first_payload;
+					daddr = rte_mbuf_data_iova(dseg) +
+						hdr_len;
+					is_first_seg = false;
+				} else {
+					dlen = rte_pktmbuf_data_len(dseg);
+					data = rte_pktmbuf_mtod(dseg, void *);
+					for (j = 0; j < dlen;
+					     j += RTE_CACHE_LINE_SIZE)
+						dcbf(data + j);
+					dcbf(data + (dlen - 1));
+					daddr = rte_mbuf_data_iova(dseg);
+				}
+
+				if (dlen == 0) {
+					dseg = dseg->next;
+					continue;
+				}
+
+				tx_ring->q_swbd[i].buffer_addr = NULL;
+				txbd = ENETC_TXBD(*tx_ring, i);
+				memset(txbd, 0, sizeof(*txbd));
+				txbd->buf_len = rte_cpu_to_le_16(dlen);
+				txbd->addr = rte_cpu_to_le_64(daddr);
+				i++;
+				bds_to_use--;
+				if (unlikely(i == bd_count))
+					i = 0;
+				dseg = dseg->next;
+			}
+
+			/* Mark the last written BD as frame-last. */
+			txbd->flags |= ENETC4_TXBD_FLAGS_F;
+			start++;
+			continue;
+		}
+
+		/* Non-TSO packet: standard single/multi-seg encoding. */
+		if (bds_to_use < segs_per_pkt)
+			break;
+
+		is_first_seg = true;
+		while (seg) {
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			seg_len = rte_pktmbuf_data_len(seg);
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			txbd->flags = 0;
+			if (is_first_seg) {
+				tx_ring->q_swbd[i].buffer_addr = seg;
+				txbd->frm_len = rte_pktmbuf_pkt_len(seg);
+				if (seg->ol_flags & ENETC4_TX_CKSUM_OFFLOAD_MASK)
+					enetc4_tx_offload_checksum(seg, txbd);
+				is_first_seg = false;
+			}
+
+			txbd->buf_len = rte_cpu_to_le_16(seg_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+			seg = seg->next;
+			i++;
+			bds_to_use--;
+
+			if (unlikely(i == bd_count))
+				i = 0;
+		}
+
+		txbd->flags |= rte_cpu_to_le_16(ENETC4_TXBD_FLAGS_F);
+		start++;
+	}
+
+	/*
+	 * Flush TX BDs to PoC so HW (non-cache-coherent i.MX95) can read the
+	 * descriptors from memory.  Same discipline as enetc_xmit_pkts_cacheable:
+	 * walk from the cache-line-aligned start of first_bd_idx to just past
+	 * the last written BD, one dcbf per 64-byte (4-BD) line.
+	 */
+	if (likely(start > 0)) {
+		int n = first_bd_idx & ~ENETC_BD_PER_CL_MASK;
+		int written = (i - n + bd_count) % bd_count;
+
+		if (written == 0)
+			written = bd_count;
+		written = (written + ENETC_BD_PER_CL_MASK) &
+			  ~ENETC_BD_PER_CL_MASK;
+
+		while (written > 0) {
+			dcbf((void *)ENETC_TXBD(*tx_ring, n));
+			n = (n + ENETC_BD_PER_CL) % bd_count;
+			written -= ENETC_BD_PER_CL;
+		}
+	}
+
+	enetc_clean_tx_ring(tx_ring);
+	tx_ring->next_to_use = i;
+	enetc_wr_reg(tx_ring->tcir, i);
+
+	return start;
+}
+
 int
 enetc_refill_rx_ring(struct enetc_bdr *rx_ring, const int buff_cnt)
 {
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
                     ` (11 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

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 <g.singh@nxp.com>
---
 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 fb921de360..7b08ad8478 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


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (2 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
                     ` (10 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF message is only 16-bit wide, where the upper 8-bit is the
class_id and the lower 8-bit carries the message content. For the
link speed message, the lower 4-bit was previously occupied by the cookie
field, leaving only 4-bit for the speed code, which allows at most
15 distinct speed values.

As the NETC IP evolves and supports more and more link speeds, 15 speed
values are clearly insufficient. Since the cookie field is designed
for non-blocking messages and has no meaningful use in link speed
messages, remove it and expand the speed code field from 4-bit to 8-bit,
allowing up to 255 speed values (ENETC_SPEED_MAX = 0xff).

Instead of enumerating every speed value greater than 5Gbps
explicitly, introduce a formula-based approach:

	speed_code = (link_speed - 5000) / 1000 + ENETC_SPEED_5000

This removes the explicit enum entries for 10G, 25G, 50G and 100G,
and replaces the individual switch-case branches with a generic
implementation to get the speed, making it easy to support any
future high speed without modifying the enum or the switch statement.

For backward compatibility with a PF running an older kernel (before
6.18.37) that still uses the legacy 4-bit speed code / 4-bit cookie
message layout, add a "vf_link_legacy" devarg. When set to 1, the VF
extracts the message result from the upper 4 bits of the lower byte
and decodes speeds greater than 5Gbps using the legacy fixed class
codes (10G/25G/50G/100G). The default (0) uses the new 8-bit layout.

	Usage: -a <pci_addr>,vf_link_legacy=1

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |  32 ++++-
 drivers/net/enetc/enetc4_vf.c          | 175 +++++++++++++++++++++----
 3 files changed, 175 insertions(+), 33 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 0dd08e0259..d7961d3c85 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -63,6 +63,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.
+  * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 67f8ce1919..c983cb059e 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -120,6 +120,10 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
+	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
+	 */
+	uint8_t vf_link_legacy;
 };
 
 /*
@@ -211,11 +215,29 @@ enum speed {
 	ENETC_SPEED_1000 = 0x5,
 	ENETC_SPEED_2500 = 0x6,
 	ENETC_SPEED_5000 = 0x7,
-	ENETC_SPEED_10G = 0x8,
-	ENETC_SPEED_25G = 0x9,
-	ENETC_SPEED_50G = 0xA,
-	ENETC_SPEED_100G = 0xB,
-	ENETC_SPEED_NOT_SUPPORTED = 0xF
+	/* Base speed class code used by the >5Gbps formula below */
+	/* Do not add enumeration values for any speed greater than
+	 * 5Gbps. For any speed greater than 5Gbps, its speed class
+	 * code should follow the formula below.
+	 *
+	 * SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+	 *
+	 * The unit of link_speed should be Mbps, the max SPEED
+	 * should <= ENETC_SPEED_MAX.
+	 */
+	ENETC_SPEED_MAX = 0xff,
+};
+
+/* Legacy speed class codes for backward compatibility with an older kernel PF
+ * (before 6.18.37) that encoded a 4-bit speed code and 4-bit cookie in the
+ * message's lower byte. Used only when the vf_link_legacy devarg is set.
+ */
+enum speed_legacy {
+	ENETC_SPEED_LEGACY_10G = 0x8,
+	ENETC_SPEED_LEGACY_25G = 0x9,
+	ENETC_SPEED_LEGACY_50G = 0xA,
+	ENETC_SPEED_LEGACY_100G = 0xB,
+	ENETC_SPEED_LEGACY_NOT_SUPPORTED = 0xF
 };
 
 /* PSI-VSI command header format */
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index be79a18a39..d34b8019ea 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -5,6 +5,7 @@
 #include <stdbool.h>
 #include <rte_kvargs.h>
 #include <rte_random.h>
+#include <rte_kvargs.h>
 #include <dpaax_iova_table.h>
 #include "enetc_logs.h"
 #include "enetc.h"
@@ -43,6 +44,12 @@ enetc4_vf_get_devarg_nc(struct rte_eth_dev *dev)
 #define ENETC_BYTE_SIZE			8
 #define ENETC_MSB_BIT			0x8000
 
+/* Backward-compat devarg for a PF running a kernel before 6.18.37, which uses
+ * the legacy PF-to-VF link message layout (4-bit speed code + 4-bit cookie).
+ * Usage: -a <pci_addr>,vf_link_legacy=1
+ */
+#define ENETC_VF_LINK_LEGACY		"vf_link_legacy"
+
 uint16_t enetc_crc_table[ENETC_CRC_TABLE_SIZE];
 bool enetc_crc_gen;
 
@@ -101,6 +108,59 @@ enetc_crc_calc(uint16_t crc, const uint8_t *buffer, size_t len)
 	return crc;
 }
 
+static int
+parse_vf_link_legacy(const char *key __rte_unused, const char *value,
+		     void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *endptr;
+	unsigned long val;
+
+	if (!value || *value == '\0') {
+		ENETC_PMD_WARN("Empty value for devarg %s, ignoring",
+			       ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	errno = 0;
+	val = strtoul(value, &endptr, 0);
+	if (errno != 0 || *endptr != '\0' || val > 1) {
+		ENETC_PMD_WARN("Invalid value '%s' for devarg %s, expected 0 or 1",
+			       value, ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	hw->vf_link_legacy = (uint8_t)val;
+
+	return 0;
+}
+
+static void
+enetc4_vf_get_devargs(struct rte_eth_dev *dev)
+{
+	struct rte_devargs *devargs;
+	struct rte_kvargs *kvlist;
+
+	devargs = dev->device->devargs;
+	if (!devargs)
+		return;
+
+	kvlist = rte_kvargs_parse(devargs->args, NULL);
+	if (!kvlist)
+		return;
+
+	if (rte_kvargs_count(kvlist, ENETC_VF_LINK_LEGACY)) {
+		if (rte_kvargs_process(kvlist, ENETC_VF_LINK_LEGACY,
+				       parse_vf_link_legacy, (void *)dev) < 0)
+			ENETC_PMD_WARN("Failed to parse devarg %s",
+				       ENETC_VF_LINK_LEGACY);
+	}
+
+	rte_kvargs_free(kvlist);
+}
+
 static int
 enetc4_vf_dev_infos_get(struct rte_eth_dev *dev,
 			struct rte_eth_dev_info *dev_info)
@@ -212,6 +272,7 @@ enetc4_msg_vsi_write_msg(struct enetc_hw *hw,
 static void
 enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgsr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -221,8 +282,15 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgsr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -231,6 +299,7 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 static void
 enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgrr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -240,8 +309,15 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgrr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -731,6 +807,8 @@ enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 static int
 enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused)
 {
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
 	struct enetc_psi_reply_msg *reply_msg;
 	struct rte_eth_link link;
 	int err;
@@ -809,28 +887,62 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 			link.link_speed = RTE_ETH_SPEED_NUM_5G;
 			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 			break;
-		case ENETC_SPEED_10G:
-			link.link_speed = RTE_ETH_SPEED_NUM_10G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_25G:
-			link.link_speed = RTE_ETH_SPEED_NUM_25G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_50G:
-			link.link_speed = RTE_ETH_SPEED_NUM_50G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100G:
-			link.link_speed = RTE_ETH_SPEED_NUM_100G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_NOT_SUPPORTED:
-			ENETC_PMD_DEBUG("Speed not supported");
-			link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-			break;
 		default:
-			ENETC_PMD_ERR("Unknown speed status");
+			if (hw->vf_link_legacy) {
+				/* Legacy PF-to-VF message layout (older kernel
+				 * PF): speeds greater than 5Gbps are encoded
+				 * with fixed 4-bit class codes rather than the
+				 * formula below.
+				 */
+				switch (reply_msg->status) {
+				case ENETC_SPEED_LEGACY_10G:
+					link.link_speed = RTE_ETH_SPEED_NUM_10G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_25G:
+					link.link_speed = RTE_ETH_SPEED_NUM_25G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_50G:
+					link.link_speed = RTE_ETH_SPEED_NUM_50G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_100G:
+					link.link_speed = RTE_ETH_SPEED_NUM_100G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+					ENETC_PMD_DEBUG("Speed not supported");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				default:
+					ENETC_PMD_ERR("Unknown speed status");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				}
+				break;
+			}
+
+			/* Any status reaching here is greater than
+			 * ENETC_SPEED_5000, as all values from 0x0 to
+			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
+			 * greater than 5Gbps are not enumerated and follow the
+			 * formula:
+			 *
+			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+			 *
+			 * where link_speed is in Mbps. Reverse it here to get the
+			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
+			 *
+			 * The PF only reports speeds that map to a well-known
+			 * RTE_ETH_SPEED_NUM_* value, so the computed value is a
+			 * valid DPDK speed. If a future speed not yet defined in
+			 * DPDK needs to be supported, the corresponding
+			 * RTE_ETH_SPEED_NUM_* value must first be added upstream.
+			 */
+			link.link_speed = (reply_msg->status - ENETC_SPEED_5000)
+					  * 1000 + 5000;
+			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 			break;
 		}
 	} else {
@@ -839,10 +951,9 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	link.link_autoneg = 1;
-
 	rte_eth_linkstatus_set(dev, &link);
-
 	rte_free(reply_msg);
+
 	return 0;
 }
 
@@ -1421,6 +1532,12 @@ enetc4_vf_dev_init(struct rte_eth_dev *eth_dev)
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_populate();
 
+	/* Parse VF specific devargs (e.g. vf_link_legacy) before the first
+	 * link update so that PF-to-VF link messages are interpreted using
+	 * the correct (legacy or current) layout.
+	 */
+	enetc4_vf_get_devargs(eth_dev);
+
 	ENETC_PMD_DEBUG("port_id %d vendorID=0x%x deviceID=0x%x",
 			eth_dev->data->port_id, pci_dev->id.vendor_id,
 			pci_dev->id.device_id);
@@ -1514,5 +1631,7 @@ RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
 			      ENETC4_VSI_DELAY "=<uint> "
-			      ENETC4_NC_MEMORY "=<int>");
+			      ENETC4_NC_MEMORY "=<int> "
+			      ENETC_VF_LINK_LEGACY "=<0|1>");
+
 RTE_LOG_REGISTER_DEFAULT(enetc4_vf_logtype_pmd, NOTICE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 05/14] net/enetc: support firmware version get for VF
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (3 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 06/14] net/enetc: support registers dump Gagandeep Singh
                     ` (9 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the ``.fw_version_get`` eth_dev op for the ENETC4 VF PMD to
report the NETC IP revision.

The NETC IP major revision is read from the PCI revision ID register,
while the IP minor revision is retrieved from the PSI over VSI-PSI
messaging using the "Get IP version" command class (class ID 0xF0,
command ID 0x1). The version is reported in the "<major>.<minor>"
format.

The PSI firmware only implements the IP_MN command of this class, so
the major revision is obtained from the PCI revision ID, matching the
behaviour of the kernel PF/VF drivers.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   1 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |  19 +++-
 drivers/net/enetc/enetc4_vf.c          | 135 +++++++++++++++++++++++++
 5 files changed, 155 insertions(+), 2 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index e7f4348603..ea0e37896d 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,7 @@ Key functionality includes:
   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.
+- Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index aae398e210..eaf474e7ba 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ VLAN filter          = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
+FW version           = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index d7961d3c85..5ac2bb4de0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,7 @@ New Features
   * Added TCP Segmentation Offload (TSO) support for the ENETC4 VF.
   * Added Receive Segment Coalesce (RSC / hardware LRO) support for ENETC4 PF and VF.
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
+  * Added firmware version reporting for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index c983cb059e..b1428f1bca 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -170,7 +170,8 @@ enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
-	ENETC_CLASS_ID_LINK_SPEED = 0x81
+	ENETC_CLASS_ID_LINK_SPEED = 0x81,
+	ENETC_CLASS_ID_GET_IP_VER = 0xF0
 };
 
 /* Enum for command IDs */
@@ -184,9 +185,18 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_LINK_STATUS = 0,
 	ENETC_CMD_ID_REGISTER_LINK_NOTIF = 1,
 	ENETC_CMD_ID_UNREGISTER_LINK_NOTIF = 2,
-	ENETC_CMD_ID_GET_LINK_SPEED = 0
+	ENETC_CMD_ID_GET_LINK_SPEED = 0,
+	/* Get IP version command IDs (class ID 0xF0) */
+	ENETC_CMD_ID_GET_IP_MJ = 0,
+	ENETC_CMD_ID_GET_IP_MN = 1,
+	ENETC_CMD_ID_GET_IP_INT = 2,
+	ENETC_CMD_ID_GET_IP_MNT = 3,
+	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* IP_VER value returned when the version is not available */
+#define ENETC_IP_VER_NOT_AVAILABLE	0xFF
+
 enum mac_addr_status {
 	ENETC_INVALID_MAC_ADDR = 0x0,
 	ENETC_DUPLICATE_MAC_ADDR = 0X1,
@@ -274,6 +284,11 @@ struct enetc_msg_cmd_get_link_speed {
 	struct enetc_msg_cmd_header header;
 };
 
+/* Get IP version command message format (class ID 0xF0) */
+struct enetc_msg_cmd_get_ip_ver {
+	struct enetc_msg_cmd_header header;
+};
+
 struct enetc_msg_cmd_set_vlan_promisc {
 	struct enetc_msg_cmd_header header;
 	uint8_t op;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index d34b8019ea..4b84a90cc9 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -5,6 +5,7 @@
 #include <stdbool.h>
 #include <rte_kvargs.h>
 #include <rte_random.h>
+#include <rte_bus_pci.h>
 #include <rte_kvargs.h>
 #include <dpaax_iova_table.h>
 #include "enetc_logs.h"
@@ -434,6 +435,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_MAC_FILTER:
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
+		case ENETC_CLASS_ID_GET_IP_VER:
 			break;
 		default:
 			err = -EIO;
@@ -797,6 +799,138 @@ enetc4_vf_get_link_speed(struct rte_eth_dev *dev, struct enetc_psi_reply_msg *re
 	return err;
 }
 
+/*
+ * Get the NETC IP minor revision (IP_MN) from the PSI using the 'Get IP
+ * version' command class (class ID 0xF0, cmd_id 0x1). In the reply, the
+ * low 8 bits of the return code carry the minor revision and the upper 8
+ * bits carry the class code (0xF0). The PSI only implements the IP_MN
+ * command of this class; the major revision comes from the PCI revision.
+ */
+static int
+enetc4_vf_get_ip_minor_revision(struct rte_eth_dev *dev, uint8_t *ip_mn)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_swbd *msg;
+	uint32_t msg_size;
+	uint16_t mc;
+	uint8_t class_id;
+	int vsimsgsr;
+	int err = 0;
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_cmd_get_ip_ver),
+				ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg");
+		rte_free(msg);
+		return -ENOMEM;
+	}
+
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	msg->size = msg_size;
+
+	/* COOKIE is 0 so that the command is executed as blocking on PSI */
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_GET_IP_VER,
+			ENETC_CMD_ID_GET_IP_MN, 0, 0, 0);
+
+	/* send the command and wait */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error");
+		goto end;
+	}
+
+	/*
+	 * For the IP version command class the class-specific field is
+	 * reused to carry the 8-bit version value in the lower byte of the
+	 * return code, so parse the full low byte here instead of using the
+	 * generic reply parser.
+	 */
+	vsimsgsr = enetc4_rd(enetc_hw, ENETC4_VSIMSGSR);
+	mc = ENETC_SIMSGSR_GET_MC(vsimsgsr);
+	class_id = (mc >> 8) & 0xff;
+
+	if (class_id != ENETC_CLASS_ID_GET_IP_VER) {
+		ENETC_PMD_ERR("Wrong reply message 0x%x", class_id);
+		err = -EIO;
+		goto end;
+	}
+
+	*ip_mn = mc & 0xff;
+	if (*ip_mn == ENETC_IP_VER_NOT_AVAILABLE) {
+		ENETC_PMD_DEBUG("IP minor revision not available");
+		err = -ENOTSUP;
+	}
+
+end:
+	/* free memory no longer required */
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	return err;
+}
+
+/*
+ * Retrieve the NETC firmware/IP version and format it as
+ * "<major>.<minor>". The IP major revision is taken from the PCI
+ * revision ID register, and the IP minor revision is fetched from the
+ * PSI via VSI-PSI messaging ('Get IP version' command class). This
+ * mirrors the behaviour of the kernel PF/VF drivers, where the PSI only
+ * implements the IP_MN command of the 0xF0 class.
+ */
+static int
+enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_size)
+{
+	struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
+	uint8_t ip_mj = 0, ip_mn = 0;
+	int ret;
+
+	PMD_INIT_FUNC_TRACE();
+
+	if (fw_version == NULL)
+		return -EINVAL;
+
+	if (fw_size == 0)
+		return snprintf(NULL, 0, "%u.%u", ip_mj, ip_mn) + 1;
+
+	/* IP major revision is exposed through the PCI revision ID */
+	ret = rte_pci_read_config(pci_dev, &ip_mj, sizeof(ip_mj),
+			RTE_PCI_REVISION_ID);
+	if (ret != sizeof(ip_mj)) {
+		ENETC_PMD_ERR("Failed to read PCI revision ID");
+		return -EIO;
+	}
+
+	/* IP minor revision is fetched from the PSI via VSI-PSI messaging.
+	 * If the PSI reports the version as unavailable, fall back to a
+	 * partial version string so the caller still gets useful output.
+	 */
+	ret = enetc4_vf_get_ip_minor_revision(dev, &ip_mn);
+	if (ret && ret != -ENOTSUP) {
+		ENETC_PMD_ERR("Failed to get NETC minor revision");
+		return ret;
+	}
+
+	if (ret == -ENOTSUP)
+		ret = snprintf(fw_version, fw_size, "%u.unknown", ip_mj);
+	else
+		ret = snprintf(fw_version, fw_size, "%u.%u", ip_mj, ip_mn);
+	if (ret < 0)
+		return -EINVAL;
+
+	ret += 1; /* add trailing '\0' */
+	if ((size_t)ret > fw_size)
+		return ret;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1306,6 +1440,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.fw_version_get       = enetc4_vf_fw_version_get,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 06/14] net/enetc: support registers dump
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (4 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
                     ` (8 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the .get_reg operation for the ENETC4 PF and VF PMDs to dump
the device registers via rte_eth_dev_get_reg_info.

The VF dumps the registers reachable by a virtual station interface
(VSI): the station interface and per-ring BD ring registers. Port
registers are not accessible by a VF.

The PF additionally dumps the port registers, which are only
accessible by the physical station interface (PSI).

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |  1 +
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 13 +++++
 drivers/net/enetc/enetc4_ethdev.c      | 70 ++++++++++++++++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 62 +++++++++++++++++++++++
 6 files changed, 148 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index ea0e37896d..cd757be12f 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -60,6 +60,7 @@ Key functionality includes:
   requested. RSC requires the FCS to be stripped, so it cannot be combined
   with the KEEP_CRC Rx offload.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
+- Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index eaf474e7ba..6540a43909 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -17,6 +17,7 @@ RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
 FW version           = Y
+Registers dump       = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 5ac2bb4de0..deb3bc5d52 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -65,6 +65,7 @@ New Features
   * Added Receive Segment Coalesce (RSC / hardware LRO) support for ENETC4 PF and VF.
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
   * Added firmware version reporting for the ENETC4 VF.
+  * Added register dump support for ENETC4 PF and VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index b1428f1bca..73404308fe 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -397,6 +397,19 @@ enetc_bd_unused(struct enetc_bdr *bdr)
 	return bdr->bd_count + bdr->next_to_clean - bdr->next_to_use - 1;
 }
 
+/* Per-ring Tx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_txbdr_regs[] = {
+	ENETC_TBMR, ENETC_TBSR, ENETC_TBBAR0, ENETC_TBBAR1,
+	ENETC_TBCIR, ENETC_TBLENR,
+};
+
+/* Per-ring Rx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_rxbdr_regs[] = {
+	ENETC_RBMR, ENETC_RBSR, ENETC_RBBSR, ENETC_RBCIR,
+	ENETC_RBBAR0, ENETC_RBBAR1, ENETC_RBPIR, ENETC_RBLENR,
+};
+
+
 /* CBDR prototypes */
 int enetc4_setup_cbdr(struct rte_eth_dev *dev, struct enetc_hw *hw,
 			int bd_count, struct netc_cbdr *cbdr);
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 7f95171b6d..681bac3ecc 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1176,6 +1176,75 @@ enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 	return ptypes;
 }
 
+/* Station interface registers dumped by .get_reg */
+static const uint32_t enetc4_pf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR,
+};
+
+/* Port registers dumped by .get_reg (accessible only by the PF) */
+static const uint32_t enetc4_pf_port_regs[] = {
+	ENETC4_PMR, ENETC4_PSIPMMR, ENETC4_PMAR0, ENETC4_PMAR1,
+	ENETC4_PM_CMD_CFG(0), ENETC4_PM_MAXFRM(0), ENETC4_PM_IF_MODE(0),
+	ENETC4_PM_IF_STATUS(0),
+};
+
+/*
+ * Dump the PF-accessible registers: station interface, port and per-ring
+ * BD ring registers. When info->data is NULL, only the register count and
+ * width are reported so the caller can size its buffer.
+ */
+static int
+enetc4_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_pf_si_regs);
+	count += RTE_DIM(enetc4_pf_port_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_si_regs); i++)
+		*buf++ = enetc4_rd(enetc_hw, enetc4_pf_si_regs[i]);
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_port_regs); i++)
+		*buf++ = enetc4_port_rd(enetc_hw, enetc4_pf_port_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 /*
  * The set of PCI devices this driver supports
  */
@@ -1194,6 +1263,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.link_update          = enetc4_link_update,
 	.stats_get            = enetc4_stats_get,
 	.stats_reset          = enetc4_stats_reset,
+	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 4b84a90cc9..77bec0425a 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -931,6 +931,66 @@ enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_si
 	return 0;
 }
 
+/* VF station interface registers dumped by .get_reg */
+static const uint32_t enetc4_vf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR, ENETC4_SIMSIVR, ENETC4_VSIIER, ENETC4_VSIIDR,
+	ENETC4_VSIMSGSR, ENETC4_VSIMSGRR,
+};
+
+/*
+ * Dump the VF-accessible registers. Only station interface and per-ring
+ * BD ring registers are reachable by a VF; port registers are not.
+ * When info->data is NULL, only the register count and width are
+ * reported so the caller can size its buffer.
+ */
+static int
+enetc4_vf_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_vf_si_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_vf_si_regs); i++)
+		*buf++ = enetc_rd(enetc_hw, enetc4_vf_si_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1441,6 +1501,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
@@ -1461,6 +1522,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.mac_addr_set         = enetc4_vf_set_mac_addr,
 	.mac_addr_add	      = enetc4_vf_mac_addr_add,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 07/14] net/enetc: support ethtool ring parameters
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (5 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 06/14] net/enetc: support registers dump Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
                     ` (7 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The ethtool ring parameter query relies on the queue info ethdev
ops (.rxq_info_get/.txq_info_get) together with the descriptor
limits reported in dev_info. The PF already registered these ops,
but the VF ops tables did not, so ring parameter queries failed on
the VF.

Make enetc4_rxq_info_get() and enetc4_txq_info_get() non-static and
register them in both VF ops tables so ring parameters are reported
for both the PF and VF.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst | 1 +
 drivers/net/enetc/enetc.h              | 4 ++++
 drivers/net/enetc/enetc4_ethdev.c      | 4 ++--
 drivers/net/enetc/enetc4_vf.c          | 4 ++++
 4 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index deb3bc5d52..9e333edf4f 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -66,6 +66,7 @@ New Features
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
+  * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 73404308fe..367d350dde 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -339,6 +339,10 @@ int enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx);
 void enetc4_tx_queue_release(struct rte_eth_dev *dev, uint16_t qid);
 const uint32_t *enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 			size_t *no_of_elements);
+void enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_rxq_info *qinfo);
+void enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_txq_info *qinfo);
 
 /*
  * enetc4_vf function prototype
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 681bac3ecc..afee0b90a3 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1125,7 +1125,7 @@ enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx)
 	return 0;
 }
 
-static void
+void
 enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_rxq_info *qinfo)
 {
@@ -1139,7 +1139,7 @@ enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 	qinfo->conf.rx_drop_en = 0;
 }
 
-static void
+void
 enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_txq_info *qinfo)
 {
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 77bec0425a..90b7b99e9a 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1508,10 +1508,12 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
@@ -1537,10 +1539,12 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 08/14] net/enetc: refresh link speed on VF link-up interrupt
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (6 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 09/14] net/enetc: support stats reset for VF Gagandeep Singh
                     ` (6 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The interrupt-driven link-status path (enetc4_process_psi_msg) only
updated link_status when a PSI-to-VSI notification arrived; it never
re-queried the link speed from the PF.  As a result, after a link-down
followed by a link-up the cached link_speed showed the speed from the
previous session rather than the freshly negotiated one.

Fix this by:

1. Introducing enetc4_decode_link_speed() - a shared helper that maps
   a PF-to-VF speed status code to the corresponding RTE_ETH_SPEED_NUM_*
   / RTE_ETH_LINK_*_DUPLEX values, handling both the current and the
   legacy (vf_link_legacy) 4-bit message layout.

2. Calling enetc4_vf_get_link_speed() inside enetc4_process_psi_msg()
   on ENETC_LINK_UP so the negotiated speed is fetched from the PF and
   decoded immediately, before rte_eth_linkstatus_set() is called and
   the LSC callback is fired.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc4_vf.c          | 197 +++++++++++++------------
 2 files changed, 107 insertions(+), 91 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 9e333edf4f..eca0dc1dea 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -67,6 +67,7 @@ New Features
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
+  * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 90b7b99e9a..ede11a7b61 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -324,9 +324,99 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	reply_msg->status = status;
 }
 
+/* Forward declaration: defined later in this file */
+static int enetc4_vf_get_link_speed(struct rte_eth_dev *dev,
+				     struct enetc_psi_reply_msg *reply_msg);
+
+/*
+ * Decode a PF-to-VF link-speed status code into the link_speed and
+ * link_duplex fields of *link.  vf_link_legacy selects the older
+ * 4-bit code layout used by kernel PFs before v6.18.37.
+ */
+static void
+enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
+			 struct rte_eth_link *link)
+{
+	switch (status) {
+	case ENETC_SPEED_UNKNOWN:
+		ENETC_PMD_DEBUG("Speed unknown");
+		link->link_speed = RTE_ETH_SPEED_NUM_NONE;
+		break;
+	case ENETC_SPEED_10_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_10_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_100_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_100_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_1000:
+		link->link_speed = RTE_ETH_SPEED_NUM_1G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_2500:
+		link->link_speed = RTE_ETH_SPEED_NUM_2_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_5000:
+		link->link_speed = RTE_ETH_SPEED_NUM_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	default:
+		if (vf_link_legacy) {
+			/* Legacy PF-to-VF message layout (older kernel PF):
+			 * speeds above 5Gbps use fixed 4-bit class codes.
+			 */
+			switch (status) {
+			case ENETC_SPEED_LEGACY_10G:
+				link->link_speed = RTE_ETH_SPEED_NUM_10G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_25G:
+				link->link_speed = RTE_ETH_SPEED_NUM_25G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_50G:
+				link->link_speed = RTE_ETH_SPEED_NUM_50G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_100G:
+				link->link_speed = RTE_ETH_SPEED_NUM_100G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+				ENETC_PMD_DEBUG("Speed not supported");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			default:
+				ENETC_PMD_ERR("Unknown speed status");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			}
+			break;
+		}
+		/* Any status here is > ENETC_SPEED_5000.  Reverse the formula:
+		 *   SPEED = (status - ENETC_SPEED_5000) * 1000 + 5000  (in Mbps)
+		 */
+		link->link_speed = (status - ENETC_SPEED_5000) * 1000 + 5000;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	}
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
 	int ret = 0;
@@ -345,6 +435,20 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		case ENETC_LINK_UP:
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Re-query speed from PF so the cached value reflects
+			 * the current negotiated speed after link-up.
+			 */
+			rte_free(msg);
+			msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+			if (msg) {
+				if (!enetc4_vf_get_link_speed(eth_dev, msg) &&
+				    msg->class_id == ENETC_CLASS_ID_LINK_SPEED)
+					enetc4_decode_link_speed(msg->status,
+							hw->vf_link_legacy,
+							&link);
+			} else {
+				ENETC_PMD_WARN("Failed to alloc msg for speed query");
+			}
 			break;
 		case ENETC_LINK_DOWN:
 			ENETC_PMD_DEBUG("Link is down");
@@ -1048,97 +1152,8 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_SPEED) {
-		switch (reply_msg->status) {
-		case ENETC_SPEED_UNKNOWN:
-			ENETC_PMD_DEBUG("Speed unknown");
-			link.link_speed = RTE_ETH_SPEED_NUM_NONE;
-			break;
-		case ENETC_SPEED_10_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_10_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_100_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_1000:
-			link.link_speed = RTE_ETH_SPEED_NUM_1G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_2500:
-			link.link_speed = RTE_ETH_SPEED_NUM_2_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_5000:
-			link.link_speed = RTE_ETH_SPEED_NUM_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		default:
-			if (hw->vf_link_legacy) {
-				/* Legacy PF-to-VF message layout (older kernel
-				 * PF): speeds greater than 5Gbps are encoded
-				 * with fixed 4-bit class codes rather than the
-				 * formula below.
-				 */
-				switch (reply_msg->status) {
-				case ENETC_SPEED_LEGACY_10G:
-					link.link_speed = RTE_ETH_SPEED_NUM_10G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_25G:
-					link.link_speed = RTE_ETH_SPEED_NUM_25G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_50G:
-					link.link_speed = RTE_ETH_SPEED_NUM_50G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_100G:
-					link.link_speed = RTE_ETH_SPEED_NUM_100G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
-					ENETC_PMD_DEBUG("Speed not supported");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				default:
-					ENETC_PMD_ERR("Unknown speed status");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				}
-				break;
-			}
-
-			/* Any status reaching here is greater than
-			 * ENETC_SPEED_5000, as all values from 0x0 to
-			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
-			 * greater than 5Gbps are not enumerated and follow the
-			 * formula:
-			 *
-			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
-			 *
-			 * where link_speed is in Mbps. Reverse it here to get the
-			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
-			 *
-			 * The PF only reports speeds that map to a well-known
-			 * RTE_ETH_SPEED_NUM_* value, so the computed value is a
-			 * valid DPDK speed. If a future speed not yet defined in
-			 * DPDK needs to be supported, the corresponding
-			 * RTE_ETH_SPEED_NUM_* value must first be added upstream.
-			 */
-			link.link_speed = (reply_msg->status - ENETC_SPEED_5000)
-					  * 1000 + 5000;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		}
+		enetc4_decode_link_speed(reply_msg->status,
+					 hw->vf_link_legacy, &link);
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 09/14] net/enetc: support stats reset for VF
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (7 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
                     ` (5 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The NETC SI-level hardware counters (SIROCT0, SIRFRM0, SITOCT0,
SITFRM0, SITDFCR) are read-only for a VF and cannot be directly
zeroed. No VSI-PSI command class exists for stats reset, and using
FLR/soft-reset to clear counters is not reliable due to a known
hardware erratum on some NETC platforms.

Implement stats_reset for the enetc4 VF PMD using a software
snapshot/delta approach: on stats_reset, the current HW counter
values are captured as a baseline in a new per-device
enetc4_vf_stats_saved struct. stats_get then reports the delta
(current_hw_value - saved_baseline), so counters appear to start
from zero after each reset call. Per-ring software Rx error
accumulators (ierrors) are zeroed directly on reset.

Register the stats_reset callback in both VF ops tables
(enetc4_vf_ops and enetc4_vf_ops_no_vsi_m) so the feature is
available regardless of whether the VSI messaging path is enabled.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 17 +++++++
 drivers/net/enetc/enetc4_vf.c          | 61 +++++++++++++++++++++++---
 3 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index eca0dc1dea..b88e2825a6 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -68,6 +68,7 @@ New Features
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
+  * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 367d350dde..3590cf6639 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -105,6 +105,21 @@ struct enetc_bdr {
 	uint8_t rsc_enable;
 };
 
+/*
+ * Saved SI counter baseline for VF stats reset. Since the SI-level
+ * hardware counters (SIROCT0, SIRFRM0, SITOCT0, SITFRM0, SITDFCR)
+ * are read-only for a VF and cannot be directly zeroed, stats_reset
+ * captures the current counter values as a baseline. stats_get then
+ * reports the delta: current_hw_value - baseline.
+ */
+struct enetc4_vf_stats_saved {
+	uint64_t ipackets;
+	uint64_t opackets;
+	uint64_t ibytes;
+	uint64_t obytes;
+	uint64_t oerrors;
+};
+
 struct enetc_eth_hw {
 	struct rte_eth_dev *ndev;
 	struct enetc_hw hw;
@@ -124,6 +139,8 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	/* Baseline snapshot for VF stats reset (software delta approach). */
+	struct enetc4_vf_stats_saved vf_stats_saved;
 };
 
 /*
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index ede11a7b61..63ee9cb346 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -225,15 +225,64 @@ enetc4_vf_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
 	uint8_t i;
 
 	PMD_INIT_FUNC_TRACE();
-	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
-	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
-	stats->ibytes = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
-	stats->obytes = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
-	stats->oerrors = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/*
+	 * The SI-level counters are read-only for a VF; they cannot be
+	 * zeroed directly. Instead, stats_reset captures a baseline
+	 * snapshot, and stats_get reports the delta so that the reported
+	 * values appear to start from zero after each reset call.
+	 */
+	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0) -
+			  hw->vf_stats_saved.ipackets;
+	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0) -
+			  hw->vf_stats_saved.opackets;
+	stats->ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0) -
+			  hw->vf_stats_saved.ibytes;
+	stats->obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0) -
+			  hw->vf_stats_saved.obytes;
+	stats->oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR) -
+			  hw->vf_stats_saved.oerrors;
+
 	for (i = 0; i < dev->data->nb_rx_queues; i++) {
 		rx_ring = dev->data->rx_queues[i];
 		stats->ierrors += rx_ring->ierrors;
 	}
+
+	return 0;
+}
+
+/*
+ * Reset VF statistics by capturing a new baseline snapshot of the
+ * SI-level hardware counters. Because those counters are read-only
+ * for a VF (hardware erratum prevents reliable clear via FLR/soft
+ * reset too), the driver uses a software delta approach: every
+ * stats_get call reports current_hw_value - saved_baseline.
+ */
+static int
+enetc4_vf_stats_reset(struct rte_eth_dev *dev)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_bdr *rx_ring;
+	uint8_t i;
+
+	PMD_INIT_FUNC_TRACE();
+
+	/* Snapshot current HW SI counter values as the new zero baseline. */
+	hw->vf_stats_saved.ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
+	hw->vf_stats_saved.opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
+	hw->vf_stats_saved.ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
+	hw->vf_stats_saved.obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
+	hw->vf_stats_saved.oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/* Reset the per-ring software Rx error accumulators. */
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		rx_ring = dev->data->rx_queues[i];
+		if (rx_ring)
+			rx_ring->ierrors = 0;
+	}
+
 	return 0;
 }
 
@@ -1514,6 +1563,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
 	.get_reg              = enetc4_vf_get_regs,
@@ -1538,6 +1588,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 10/14] net/enetc4: add per-queue Rx interrupt support for VF
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (8 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 09/14] net/enetc: support stats reset for VF Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
                     ` (4 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add MSI-X per-queue Rx interrupt support to the ENETC4 VF PMD on the
cacheable (default) Rx path. This allows applications such as l3fwd-power
to block in epoll_wait when no traffic is present, reducing CPU utilization
to near zero.

MSI-X vector 0 is reserved for the PSI-to-VSI mailbox. Rx queue i is
mapped to MSI-X vector i + 1 via ENETC_SIMSIRRV. Per-ring coalescing is
enabled with ICPT=1 so the first arriving packet fires the interrupt
immediately. The SIRXIDR W1C detect bit is cleared before re-arming
ENETC_RBIER to prevent spurious interrupts after traffic stops.

The interrupt infrastructure (rte_intr_efd_enable +
rte_intr_vec_list_alloc) is set up before rte_intr_enable() so that
vfio-pci can wire each MSI-X vector to its eventfd when programming
the MSI-X table.

enetc4_dev_configure() is updated to trigger interrupt setup when
intr_conf.rxq is set (not only when intr_conf.lsc is set), as applications
like l3fwd-power set intr_conf.rxq without setting lsc.

Documentation is updated to describe the feature, list the kernel and
host setup steps, and provide an example l3fwd-power command for single
queue/core interrupt-driven operation.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             | 69 +++++++++++++++++++-
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc4_hw.h     |  2 +
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      |  9 +--
 drivers/net/enetc/enetc4_vf.c          | 88 +++++++++++++++++++++++++-
 7 files changed, 165 insertions(+), 6 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index cd757be12f..d8e323d614 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,10 @@ Key functionality includes:
   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.
+- Per-queue Rx interrupts on VFs (cacheable Rx path only), enabling
+  interrupt-driven receive with ``vfio-pci``. Applications set
+  ``intr_conf.rxq = 1`` in ``rte_eth_conf`` to activate this feature.
+  See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
@@ -95,7 +99,9 @@ The following dependencies are not part of DPDK and must be installed separately
 Driver compilation and testing
 ------------------------------
 
-Follow instructions available in the document :doc:`build_and_test` to launch **testpmd**.
+Follow instructions available in the document
+:ref:`compiling and testing a PMD for a NIC <pmd_build_and_test>`
+to launch **testpmd**.
 
 
 Driver Arguments (devargs)
@@ -156,3 +162,64 @@ PF/Common devargs
   Usage example::
 
     dpdk-testpmd -a 0000:00:00.0,nc=1 -- -i
+
+
+Rx Interrupt Mode (VF)
+----------------------
+
+The ENETC4 VF PMD supports per-queue MSI-X Rx interrupts on the cacheable
+(default) Rx path. This allows applications to block in ``epoll_wait``
+instead of busy-polling, reducing CPU utilization when traffic is absent.
+
+**MSI-X vector assignment**
+
+ENETC4 VF MSI-X vector 0 is reserved for the PSI-to-VSI mailbox interrupt
+(link status notifications). Rx queue ``i`` is mapped to vector ``i + 1``.
+The driver allocates all required eventfds before calling
+``rte_intr_enable()`` so that ``vfio-pci`` can wire each MSI-X vector to
+its eventfd when it programs the MSI-X table.
+
+**Kernel and driver requirements**
+
+- ``vfio-pci`` kernel module with no-IOMMU mode enabled (no SMMU required).
+- The non-cacheable memory mode (``nc=1`` devarg) does **not** support
+  Rx interrupts and returns ``-ENOTSUP`` from ``rx_queue_intr_enable``.
+
+**Host setup**
+
+.. code-block:: console
+
+   # Enable vfio-pci no-IOMMU mode (if SMMU is not available)
+   modprobe vfio enable_unsafe_noiommu_mode=1
+   modprobe vfio-pci
+
+   # Bind the VF to vfio-pci
+   echo vfio-pci > /sys/bus/pci/devices/<vf_pci_addr>/driver_override
+   echo <vf_pci_addr> > /sys/bus/pci/drivers_probe
+
+**Running l3fwd-power with a single queue and core**
+
+The ``l3fwd-power`` sample application demonstrates interrupt-driven Rx.
+It sets ``intr_conf.rxq = 1`` in ``rte_eth_conf``, which triggers the VF
+interrupt setup in the driver. The ``--vfio-intr=msix`` EAL flag instructs
+DPDK to use MSI-X eventfds for interrupt signalling.
+
+.. code-block:: console
+
+   ./dpdk-l3fwd-power -l 0-1 -n 1 --vfio-intr=msix \
+       -a <vf_pci_addr> -- \
+       -p 0x1 --config="(0,0,1)" --no-numa --interrupt-only
+
+Where:
+
+- ``-l 0-1`` assigns the main thread to core 0 and the forwarding lcore to
+  core 1.
+- ``-a <vf_pci_addr>`` specifies the VF PCI address (e.g. ``0000:01:00.1``).
+- ``--config="(0,0,1)"`` maps port 0, queue 0 to lcore 1.
+- ``--interrupt-only`` enables pure interrupt mode (no busy-poll fallback).
+
+With no incoming traffic the forwarding lcore sleeps in ``epoll_wait``;
+CPU utilization drops to near zero. On the first arriving packet the MSI-X
+interrupt fires, the lcore wakes, drains the ring, disables the interrupt,
+processes the burst, then re-enables and re-arms the interrupt before
+returning to sleep.
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 6540a43909..812c215b15 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -5,6 +5,7 @@
 ;
 [Features]
 Link status event    = Y
+Queue interrupt      = Y
 Speed capabilities   = Y
 Link status          = Y
 LRO                  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index b88e2825a6..9b39debc1d 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -69,6 +69,7 @@ New Features
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
+  * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 8ad8f169d2..9543e982a2 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -250,6 +250,8 @@ struct enetc_rx_bd_ext {
 #define ENETC4_VSIIDR            0xA08
 #define ENETC4_VSIIER_MRIE       BIT(9)
 #define ENETC4_SI_INT_IDX        0
+/* MSI-X vector base for per-Rx-queue interrupts; vector 0 is the mailbox. */
+#define ENETC4_VF_RX_VEC_BASE    1
 
 /* VSI Registers */
 #define ENETC4_VSIMSGSR  0x204   /* RO */
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 3590cf6639..903829323e 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -135,6 +135,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index afee0b90a3..d987f1288c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -839,7 +839,8 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		return 0;
 
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0)
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0)
 			enetc4_vf_dev_intr(dev, false);
 		ret = enetc4_vf_dev_stop(dev);
 	} else {
@@ -961,12 +962,12 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	if (hw->device_id != ENETC4_DEV_ID_VF)
 		enetc4_port_wr(enetc_hw, ENETC4_PARCSCR, checksum);
 
-	/* Enable interrupts */
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0) {
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0) {
 			ret = enetc4_vf_dev_intr(dev, true);
 			if (ret)
-				ENETC_PMD_WARN("Failed to setup link interrupts");
+				ENETC_PMD_WARN("Failed to setup VF interrupts: %d", ret);
 		}
 	}
 
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 63ee9cb346..8a57a8cad4 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1555,6 +1555,52 @@ static const struct rte_pci_id pci_vf_id_enetc4_map[] = {
 	{ .vendor_id = 0, /* sentinel */ },
 };
 
+static int
+enetc4_vf_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t vec;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return -ENOTSUP;
+
+	vec = queue_id + ENETC4_VF_RX_VEC_BASE;
+
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), vec);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR1, 0);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR0,
+			ENETC4_RBICR0_ICEN | ENETC4_RBICR0_ICPT(1));
+	enetc_wr(enetc_hw, ENETC_SIRXIDR, BIT(queue_id));
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, ENETC_RBIER_RXTIE);
+
+	return 0;
+}
+
+static int
+enetc4_vf_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return 0;
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, 0);
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), 0);
+
+	return 0;
+}
+
 /* Features supported by this driver */
 /* ops table used when VSI messaging is disabled */
 static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
@@ -1568,7 +1614,15 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.fw_version_get       = enetc4_vf_fw_version_get,
 	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
+	.mac_addr_set         = enetc4_vf_set_mac_addr,
+	.mac_addr_add	      = enetc4_vf_mac_addr_add,
+	.promiscuous_enable   = enetc4_vf_promisc_enable,
+	.promiscuous_disable  = enetc4_vf_promisc_disable,
+	.allmulticast_enable  = enetc4_vf_multicast_enable,
+	.allmulticast_disable = enetc4_vf_multicast_disable,
 	.link_update	      = enetc4_vf_link_update_dummy,
+	.vlan_filter_set      = enetc4_vf_vlan_filter_set,
+	.vlan_offload_set     = enetc4_vf_vlan_offload_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
@@ -1606,6 +1660,8 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
 	.rxq_info_get         = enetc4_rxq_info_get,
+	.rx_queue_intr_enable  = enetc4_vf_rx_queue_intr_enable,
+	.rx_queue_intr_disable = enetc4_vf_rx_queue_intr_disable,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
@@ -1851,6 +1907,35 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		/* Vector index 0 */
 		enetc_wr(enetc_hw, ENETC4_SIMSIVR, ENETC4_SI_INT_IDX);
 
+		if (rte_intr_cap_multiple(intr_handle) &&
+		    eth_dev->data->nb_rx_queues > 0) {
+			uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+			uint16_t i;
+
+			ret = rte_intr_efd_enable(intr_handle,
+					nb_rx + ENETC4_VF_RX_VEC_BASE);
+			if (ret) {
+				ENETC_PMD_WARN("Failed to enable per-queue Rx eventfds: %d",
+					       ret);
+				ret = 0;
+				hw->rxq_intr_en = 0;
+			} else {
+				ret = rte_intr_vec_list_alloc(intr_handle,
+						"enetc4_vf_rx_intr", nb_rx);
+				if (ret) {
+					ENETC_PMD_WARN("Failed to alloc intr vec list: %d",
+						       ret);
+					rte_intr_efd_disable(intr_handle);
+					hw->rxq_intr_en = 0;
+				} else {
+					for (i = 0; i < nb_rx; i++)
+						rte_intr_vec_list_index_set(intr_handle, i,
+							i + ENETC4_VF_RX_VEC_BASE);
+					hw->rxq_intr_en = 1;
+				}
+			}
+		}
+
 		/* enable uio/vfio intr/eventfd mapping */
 		ret = rte_intr_enable(intr_handle);
 		if (ret) {
@@ -1874,6 +1959,7 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		ENETC_PMD_WARN("Failed to un-register link notification %d", ret);
 disable:
 	enetc_vf_enable_mr_int(enetc_hw, false);
+	hw->rxq_intr_en = 0;
 	ret = rte_intr_disable(intr_handle);
 	if (ret)
 		ENETC_PMD_WARN("Failed to disable INTR %d", ret);
@@ -1893,7 +1979,7 @@ static struct rte_pci_driver rte_enetc4_vf_pmd = {
 
 RTE_PMD_REGISTER_PCI(net_enetc4_vf, rte_enetc4_vf_pmd);
 RTE_PMD_REGISTER_PCI_TABLE(net_enetc4_vf, pci_vf_id_enetc4_map);
-RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic");
+RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic | vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 11/14] net/enetc4: add SI-based port VLAN insertion and removal
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (9 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
                     ` (3 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement hardware VLAN tag insertion on Tx and removal on Rx
via the per-SI VLAN isolation (pvid) mechanism on ENETC4.

On a PF, the driver writes directly to PSIaVLANR(0) (VID + enable
bit) and PSIaCFGR0(0) (SIVIE for Tx insertion, VTE for Rx removal,
SIVC_CVLAN to allow C-VLAN 0x8100 TPID).

On a privileged VF, the request is forwarded to the kernel PF via
the VSI-PSI mailbox using a new command class 0x24 (SI VLAN
isolation, cmd ID 0x1). The kernel PF handler programs the same
registers on behalf of the VF's station interface. The class 0x24
reply is added to the pass-through list in enetc4_msg_vsi_send() so
it is handled correctly alongside the existing command classes.

New additions:
- ENETC4_PSIVLANR(a) and ENETC4_PSICFGR0(a) register macros with
  field definitions in enetc4_hw.h
- ENETC_CLASS_ID_SI_VLAN_ISO (0x24) and ENETC_CMD_ID_SET_SI_VLAN_ISO
  enum values, plus struct enetc_msg_si_vlan_iso message layout and
  bit definitions in enetc.h
- enetc4_vlan_pvid_set() for PF in enetc4_ethdev.c
- enetc4_vf_vlan_pvid_set() for VF in enetc4_vf.c, registered as
  .vlan_pvid_set in both enetc4_ops and enetc4_vf_ops

The feature is activated in testpmd with:
  tx_vlan set pvid <port_id> <vlan_id> on|off

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 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     | 14 +++++
 drivers/net/enetc/enetc.h              | 23 +++++++
 drivers/net/enetc/enetc4_ethdev.c      | 39 ++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 85 ++++++++++++++++++++++++++
 7 files changed, 167 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index d8e323d614..bb0628ccf6 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -65,6 +65,10 @@ Key functionality includes:
   See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
+- SI-based port VLAN (pvid): Hardware VLAN tag insertion on Tx and removal on Rx, configured
+  via ``rte_eth_dev_set_vlan_pvid``. On a PF the registers are written directly; on a privileged
+  VF the request is forwarded to the kernel PF through the VSI-PSI mailbox (class 0x24).
+  Use the testpmd command ``tx_vlan set pvid <port_id> <vlan_id> on|off`` to enable or disable.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 812c215b15..9dc8fd33f8 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 9b39debc1d..e6d96b2a50 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -70,6 +70,7 @@ New Features
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
+  * Added SI-based port VLAN insertion (Tx) and removal (Rx) 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 9543e982a2..a9552f21f4 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -277,6 +277,20 @@ struct enetc_rx_bd_ext {
 #define ENETC4_SICTR0		0x18
 #define ENETC4_SICTR1		0x1c
 
+/* PSI SI VLAN register: per-SI VLAN tag for insertion/removal */
+#define ENETC4_PSIVLANR(a)		((a) * 0x80 + 0x2008)
+#define ENETC4_PSIVLANR_E		BIT(31)  /* enable SI VLAN processing */
+#define ENETC4_PSIVLANR_VTEA		BIT(30)  /* 0=strip tag, 1=zero VID */
+#define ENETC4_PSIVLANR_PCP(v)		(((uint32_t)(v) & 0x7) << 13)
+#define ENETC4_PSIVLANR_DEI		BIT(12)
+#define ENETC4_PSIVLANR_VID(v)		((uint32_t)(v) & 0xfff)
+
+/* PSI SI configuration register 0 */
+#define ENETC4_PSICFGR0(a)		((a) * 0x80 + 0x2010)
+#define ENETC4_PSICFGR0_SIVC_CVLAN	BIT(24)  /* allow C-VLAN 0x8100 */
+#define ENETC4_PSICFGR0_SIVIE		BIT(14)  /* SI VLAN insertion enable */
+#define ENETC4_PSICFGR0_VTE		BIT(12)  /* SI VLAN removal enable */
+
 /* general register accessors */
 #define enetc4_rd_reg(reg)	rte_read32((void *)(reg))
 #define enetc4_wr_reg(reg, val)  rte_write32((val), (void *)(reg))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 903829323e..72e43572f7 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -187,6 +187,8 @@ struct enetc_eth_adapter {
 enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
+	/* Configure SI VLAN isolation (insert / remove) */
+	ENETC_CLASS_ID_SI_VLAN_ISO = 0x24,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
 	ENETC_CLASS_ID_LINK_SPEED = 0x81,
 	ENETC_CLASS_ID_GET_IP_VER = 0xF0
@@ -212,6 +214,11 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* Command IDs for class ID 0x24 (SI VLAN isolation) */
+enum enetc_msg_si_vlan_cmd_id {
+	ENETC_CMD_ID_SET_SI_VLAN_ISO = 1,
+};
+
 /* IP_VER value returned when the version is not available */
 #define ENETC_IP_VER_NOT_AVAILABLE	0xFF
 
@@ -323,6 +330,22 @@ struct enetc_msg_vlan_exact_filter {
 	uint8_t reserved2;
 };
 
+/* VSI-PSI SI VLAN isolation message (class 0x24, cmd 0x1) */
+struct enetc_msg_si_vlan_iso {
+	struct enetc_msg_cmd_header header;
+	uint8_t ctrl;          /* E[7], VTEA[6], TPID[1:0] */
+	uint8_t pcp_dei_vid_hi; /* PCP[7:5], DEI[4], VID[11:8] */
+	uint8_t vid_lo;        /* VID[7:0] */
+	uint8_t reserved_0;
+	uint8_t flags;         /* SVIE[2], VTE[1] */
+	uint8_t reserved_1[3];
+};
+
+#define ENETC_SI_VLAN_ISO_E	BIT(7)  /* ctrl: enable SI VLAN */
+#define ENETC_SI_VLAN_ISO_VTEA	BIT(6)  /* ctrl: 0=strip, 1=zero VID */
+#define ENETC_SI_VLAN_ISO_SVIE	BIT(2)  /* flags: Tx insertion enable */
+#define ENETC_SI_VLAN_ISO_VTE	BIT(1)  /* flags: Rx removal enable */
+
 struct enetc_psi_reply_msg {
 	uint8_t class_id;
 	uint8_t status;
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index d987f1288c..ae40cc69e0 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -872,6 +872,44 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 	return ret;
 }
 
+/* Configure SI-based VLAN insertion (Tx) and removal (Rx) for the PF. */
+static int
+enetc4_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t psivlanr, psicfgr0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	psicfgr0 = enetc4_port_rd(enetc_hw, ENETC4_PSICFGR0(0));
+
+	if (on) {
+		/* Build the VLAN register: enable bit + VID (PCP/DEI left 0) */
+		psivlanr = (uint32_t)ENETC4_PSIVLANR_E |
+			   ENETC4_PSIVLANR_VID(vlan_id);
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), psivlanr);
+
+		/* Allow C-VLAN TPID, enable Tx insertion and Rx removal */
+		psicfgr0 |= ENETC4_PSICFGR0_SIVC_CVLAN |
+			    ENETC4_PSICFGR0_SIVIE |
+			    ENETC4_PSICFGR0_VTE;
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+	} else {
+		/* Clear Tx insertion, Rx removal and C-VLAN allow bits */
+		psicfgr0 &= ~(ENETC4_PSICFGR0_SIVC_CVLAN |
+			      ENETC4_PSICFGR0_SIVIE |
+			      ENETC4_PSICFGR0_VTE);
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+
+		/* Clear the VLAN register (disable SI VLAN processing) */
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), 0);
+	}
+
+	return 0;
+}
+
 static int
 enetc4_promiscuous_enable(struct rte_eth_dev *dev)
 {
@@ -1267,6 +1305,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
+	.vlan_pvid_set        = enetc4_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 8a57a8cad4..fc5ea4d609 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -589,8 +589,11 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
 		case ENETC_CLASS_ID_GET_IP_VER:
+		case ENETC_CLASS_ID_SI_VLAN_ISO:
 			break;
 		default:
+			ENETC_PMD_ERR("Unexpected reply class_id=0x%02x vsimsgsr=0x%08x",
+				      class_id, vsimsgsr);
 			err = -EIO;
 		}
 	}
@@ -1493,6 +1496,87 @@ static int enetc4_vf_vlan_offload_set(struct rte_eth_dev *dev, int mask __rte_un
 	return 0;
 }
 
+/* Configure SI-based VLAN insertion/removal via VSI-PSI mailbox (class 0x24). */
+static int
+enetc4_vf_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_si_vlan_iso *cmd;
+	struct enetc_msg_swbd *msg;
+	struct enetc_psi_reply_msg *reply_msg;
+	uint32_t msg_size;
+	int err = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	reply_msg = rte_zmalloc(NULL, sizeof(*reply_msg), RTE_CACHE_LINE_SIZE);
+	if (!reply_msg) {
+		ENETC_PMD_ERR("Failed to alloc memory for reply_msg");
+		return -ENOMEM;
+	}
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_si_vlan_iso),
+			     ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg body");
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	if (msg->dma == RTE_BAD_IOVA) {
+		ENETC_PMD_ERR("Failed to get IOVA for SI VLAN isolation msg body");
+		rte_free(msg->vaddr);
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->size = msg_size;
+
+	cmd = (struct enetc_msg_si_vlan_iso *)msg->vaddr;
+
+	if (on) {
+		cmd->ctrl = (uint8_t)ENETC_SI_VLAN_ISO_E;
+		cmd->pcp_dei_vid_hi = (uint8_t)((vlan_id >> 8) & 0x0f);
+		cmd->vid_lo = (uint8_t)(vlan_id & 0xff);
+		cmd->flags = (uint8_t)(ENETC_SI_VLAN_ISO_SVIE | ENETC_SI_VLAN_ISO_VTE);
+	}
+	/* on=0: all fields zero (rte_zmalloc cleared the buffer) */
+
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_SI_VLAN_ISO,
+				     ENETC_CMD_ID_SET_SI_VLAN_ISO, 0, 0, 0);
+
+	/* send the command and wait for PSI reply */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error for SI VLAN isolation");
+		goto end;
+	}
+
+	enetc4_msg_vsi_reply_msg(enetc_hw, reply_msg);
+
+	if (reply_msg->class_id != ENETC_MSG_CLASS_ID_CMD_SUCCESS) {
+		ENETC_PMD_ERR("SI VLAN isolation command failed: class_id=0x%x status=0x%x",
+			      reply_msg->class_id, reply_msg->status);
+		err = -EINVAL;
+	}
+
+end:
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	rte_free(reply_msg);
+	return err;
+}
+
 static int
 enetc4_vf_mtu_set(struct rte_eth_dev *dev __rte_unused, uint16_t mtu __rte_unused)
 {
@@ -1623,6 +1707,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.vlan_filter_set      = enetc4_vf_vlan_filter_set,
 	.vlan_offload_set     = enetc4_vf_vlan_offload_set,
+	.vlan_pvid_set        = enetc4_vf_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 12/14] net/enetc4: update VF link status to bitmask encoding
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (10 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
                     ` (2 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF link status notification code was previously a two-value
enum (UP=0x0, DOWN=0x1). Change this to a bitmask so future bits can
carry additional state alongside the link up/down indication:

  ENETC_LINK_DOWN  BIT(0)  -- set when link is down

Link up is now encoded as the DOWN bit being clear, which keeps the
wire value for link-down identical (0x1) and ensures backward
compatibility with older kernel PFs.

Changes:
- enetc.h: replace enum link_status with a #define bitmask;
  drop ENETC_LINK_UP (no longer a named constant).
- enetc4_vf.c enetc4_process_psi_msg(): replace switch/case on
  ENETC_LINK_UP/DOWN with bitmask decode.
- enetc4_vf.c enetc4_vf_link_update(): same bitmask decode.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              |  8 ++++----
 drivers/net/enetc/enetc4_vf.c          | 27 +++++++-------------------
 3 files changed, 12 insertions(+), 24 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index e6d96b2a50..5323f4fea3 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -71,6 +71,7 @@ New Features
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
+  * Updated ENETC4 VF link status reporting to use bitmask encoding.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 72e43572f7..40bad56341 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -236,10 +236,10 @@ enum vlan_status {
 	ENETC_VLAN_NO_RESOURCE = 0x3
 };
 
-enum link_status {
-	ENETC_LINK_UP = 0x0,
-	ENETC_LINK_DOWN = 0x1
-};
+/* Link status bitmask in PF-to-VF mailbox notification.
+ * Link up is encoded as the DOWN bit being clear.
+ */
+#define ENETC_LINK_DOWN  (1u << 0)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index fc5ea4d609..1b93835dd7 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -480,8 +480,10 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 	enetc4_msg_get_psi_msg(enetc_hw, msg);
 
 	if (msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (msg->status) {
-		case ENETC_LINK_UP:
+		if (msg->status & ENETC_LINK_DOWN) {
+			ENETC_PMD_DEBUG("Link is down");
+			link.link_status = RTE_ETH_LINK_DOWN;
+		} else {
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
 			/* Re-query speed from PF so the cached value reflects
@@ -498,14 +500,6 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 			} else {
 				ENETC_PMD_WARN("Failed to alloc msg for speed query");
 			}
-			break;
-		case ENETC_LINK_DOWN:
-			ENETC_PMD_DEBUG("Link is down");
-			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status 0x%x", msg->status);
-			break;
 		}
 		ret = rte_eth_linkstatus_set(eth_dev, &link);
 		if (!ret)
@@ -1180,17 +1174,10 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (reply_msg->status) {
-		case ENETC_LINK_UP:
-			link.link_status = RTE_ETH_LINK_UP;
-			break;
-		case ENETC_LINK_DOWN:
+		if (reply_msg->status & ENETC_LINK_DOWN)
 			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status");
-			break;
-		}
+		else
+			link.link_status = RTE_ETH_LINK_UP;
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (11 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  3:48   ` [PATCH v2 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

When the PF negotiates TX PAUSE on the port it signals this to the VF
via BIT(1) of the PF-to-VF link status mailbox message. The VF PMD must
respond by setting RBMR_CM (BIT(4)) on all active RX rings so the MAC
emits PAUSE frames on ingress pressure.

Add ENETC_RBMR_CM register definition, ENETC_LINK_TX_PAUSE bitmask,
and tx_pause_active state flag. Add enetc4_vf_set_congestion_mode() to
update all active RX rings and persist the state for rings started
later. Hook it into both the interrupt and poll link-update paths, and
apply the saved state in rx_queue_setup() and rx_queue_start().

RX PAUSE (honoring received PAUSE frames) is handled at the MAC level
by the PF and requires no VF PMD changes.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  1 +
 drivers/net/enetc/enetc.h              |  9 +++-
 drivers/net/enetc/enetc4_ethdev.c      | 13 +++++-
 drivers/net/enetc/enetc4_vf.c          | 59 ++++++++++++++++++++++++--
 6 files changed, 78 insertions(+), 6 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 9dc8fd33f8..d4e186559e 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+Flow control         = Y
 VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 5323f4fea3..d69888aafb 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -72,6 +72,7 @@ New Features
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
+  * Added TX PAUSE support for the ENETC4 VF via RX congestion mode.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 6e96562850..33d075fe59 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -51,6 +51,7 @@ enum enetc_bdr_type {TX, RX};
 							+ (off))
 /* RX BDR reg offsets */
 #define ENETC_RBMR		0x0 /* RX BDR mode register*/
+#define ENETC_RBMR_CM		BIT(4)  /* congestion mode: assert congestion to emit TX PAUSE */
 #define ENETC_RBMR_EN		BIT(31)
 
 #define ENETC_BMR_RESET		0x0 /* BDR reset*/
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 40bad56341..8aaf8a7eb1 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -140,6 +140,10 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	/* 1 = TX PAUSE negotiated on port; VF RX rings must have RBMR_CM set.
+	 * Updated from the PF-to-VF link status mailbox message (BIT(1)).
+	 */
+	uint8_t tx_pause_active;
 	/* Baseline snapshot for VF stats reset (software delta approach). */
 	struct enetc4_vf_stats_saved vf_stats_saved;
 };
@@ -238,8 +242,11 @@ enum vlan_status {
 
 /* Link status bitmask in PF-to-VF mailbox notification.
  * Link up is encoded as the DOWN bit being clear.
+ * TX_PAUSE is set when the port has negotiated TX PAUSE; VF must enable
+ * congestion mode (ENETC_RBMR_CM) on its RX rings accordingly.
  */
-#define ENETC_LINK_DOWN  (1u << 0)
+#define ENETC_LINK_DOWN      (1u << 0)
+#define ENETC_LINK_TX_PAUSE  (1u << 1)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index ae40cc69e0..2355522515 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -712,8 +712,12 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	if (!rx_conf->rx_deferred_start) {
-		/* enable ring */
+		/* Enable ring; apply congestion mode if TX PAUSE is already active. */
 		rx_enable |= ENETC_RBMR_EN;
+		if (adapter->hw.tx_pause_active)
+			rx_enable |= ENETC_RBMR_CM;
+		else
+			rx_enable &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
@@ -1089,7 +1093,12 @@ enetc4_rx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->rx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		rx_data = enetc4_rxbdr_rd(&priv->hw.hw, rx_ring->index,
 					 ENETC_RBMR);
-		rx_data = rx_data | ENETC_RBMR_EN;
+		rx_data |= ENETC_RBMR_EN;
+		/* Restore congestion mode if TX PAUSE is active. */
+		if (priv->hw.tx_pause_active)
+			rx_data |= ENETC_RBMR_CM;
+		else
+			rx_data &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&priv->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_data);
 		dev->data->rx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 1b93835dd7..c68bc8521b 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -461,6 +461,37 @@ enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
 	}
 }
 
+/*
+ * Set or clear ENETC_RBMR_CM (congestion mode) on all active VF RX rings.
+ * When set, the ring signals congestion to the MAC, causing it to emit TX
+ * PAUSE frames on ingress pressure. hw->tx_pause_active is updated so rings
+ * started later inherit the correct state.
+ */
+static void
+enetc4_vf_set_congestion_mode(struct rte_eth_dev *eth_dev, bool enable)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+	uint16_t i;
+	uint32_t rbmr;
+
+	hw->tx_pause_active = enable ? 1 : 0;
+
+	for (i = 0; i < nb_rx; i++) {
+		rbmr = enetc4_rxbdr_rd(enetc_hw, i, ENETC_RBMR);
+		if (enable)
+			rbmr |= ENETC_RBMR_CM;
+		else
+			rbmr &= ~(uint32_t)ENETC_RBMR_CM;
+		enetc4_rxbdr_wr(enetc_hw, i, ENETC_RBMR, rbmr);
+	}
+
+	ENETC_PMD_DEBUG("VF congestion mode %s on %u RX rings",
+			enable ? "enabled" : "disabled", nb_rx);
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
@@ -468,6 +499,7 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
+	bool tx_pause;
 	int ret = 0;
 
 	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
@@ -483,9 +515,24 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		if (msg->status & ENETC_LINK_DOWN) {
 			ENETC_PMD_DEBUG("Link is down");
 			link.link_status = RTE_ETH_LINK_DOWN;
+			/* Clear congestion mode on link-down so VF rings do not
+			 * assert congestion while the port is offline.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, false);
 		} else {
-			ENETC_PMD_DEBUG("Link is up");
+			/* BIT(1) is set when the port has negotiated TX PAUSE.
+			 * Legacy PF does not set this bit so tx_pause stays false.
+			 */
+			tx_pause = !!(msg->status & ENETC_LINK_TX_PAUSE);
+			ENETC_PMD_DEBUG("Link is up, tx_pause=%d", tx_pause);
 			link.link_status = RTE_ETH_LINK_UP;
+
+			/* Apply congestion mode before raising the carrier so
+			 * the VF rings are ready to emit PAUSE before traffic
+			 * starts flowing.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, tx_pause);
+
 			/* Re-query speed from PF so the cached value reflects
 			 * the current negotiated speed after link-up.
 			 */
@@ -1174,10 +1221,16 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		if (reply_msg->status & ENETC_LINK_DOWN)
+		if (reply_msg->status & ENETC_LINK_DOWN) {
 			link.link_status = RTE_ETH_LINK_DOWN;
-		else
+			/* Link is down: disable congestion mode on all RX rings. */
+			enetc4_vf_set_congestion_mode(dev, false);
+		} else {
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Restore congestion mode from the TX PAUSE bit. */
+			enetc4_vf_set_congestion_mode(dev,
+				!!(reply_msg->status & ENETC_LINK_TX_PAUSE));
+		}
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v2 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (12 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
@ 2026-08-07  3:48   ` Gagandeep Singh
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  3:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add enetc4_txq_wrr devarg to configure per-ring WRR weights in the
NETC LEAF-level Tx scheduler (TBaMR register, bits [6:4]).

The NETC Tx scheduler has three levels:
  - ROOT (port/TC): strict priority + CBS (PF/port space)
  - MID  (SI/VSI):  WBFS shaping (PF space)
  - LEAF (Tx BDR):  strict priority + frame-based WRR (VF/SI space)

TBaMR is in the VF own SI space, so no Linux PF involvement is
needed for PRIO or WRR configuration.

Changes:
- enetc_hw.h: add ENETC_TBMR_WRR_MASK, ENETC_TBMR_WRR(n) macros for
  TBaMR bits [6:4], and ENETC_TBMR_PRIO_MASK for bits [2:0]
- enetc.h: add txq_wrr pointer to enetc_eth_hw struct
- enetc4_ethdev.c: add parse_txq_wrr() and wire ENETC4_TXQ_WRR devarg
  through enetc4_get_devargs() and enetc4_dev_configure(); apply WRR
  bits in enetc4_tx_queue_setup() and enetc4_tx_queue_start()

Usage:
  # strict priority: ring 0 highest
  -a 0002:00:12.0,enetc4_txq_prior="3|2|1"

  # WRR 2:4:1 on same-priority rings
  -a 0002:00:12.0,enetc4_txq_prior="1|1|1",enetc4_txq_wrr="2|4|1"

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  5 ++
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      | 97 ++++++++++++++++++++------
 4 files changed, 82 insertions(+), 22 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index d69888aafb..3ea34523fe 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -73,6 +73,7 @@ New Features
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
   * Added TX PAUSE support for the ENETC4 VF via RX congestion mode.
+  * Added WRR Tx scheduler devarg (``enetc4_txq_wrr``) for ENETC4 VF ring weights.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 33d075fe59..5a71f01db9 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -86,6 +86,11 @@ enum enetc_bdr_type {TX, RX};
 
 #define ENETC_RTBLENR_LEN(n)		((n) & ~0x7)
 #define ENETC_TBMR_EN			BIT(31)
+/* TBaMR[WRR] bits [6:4]: weight for same-priority ring arbitration (0=1x .. 7=8x). */
+#define ENETC_TBMR_WRR_MASK		GENMASK(6, 4)
+#define ENETC_TBMR_WRR(n)		((((n) - 1) & 0x7) << 4)
+/* TBaMR[PRIO] bits [2:0]: strict priority (0=lowest, 7=highest). */
+#define ENETC_TBMR_PRIO_MASK		GENMASK(2, 0)
 
 /* Port regs, offset: 1_0000h */
 #define ENETC_PORT_BASE			0x10000
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 8aaf8a7eb1..e0943d093a 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -134,6 +134,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_timeout; /* VSI-PSI message wait timeout (iterations) */
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
+	uint32_t *txq_wrr;    /* per-queue TX WRR weight pre-shifted for TBMR[WRR] */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
 	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 2355522515..e490ecaf4c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -28,22 +28,26 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
+#define ENETC4_TXQ_WRR		"enetc4_txq_wrr"
 #define ENETC4_NC_MEMORY	"nc"
 
+
 static int
 parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 {
 	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
 	struct enetc_eth_hw *hw =
-		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
-	char *input_str = strdup(value);
+				ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
 	char *str;
 	uint32_t i = 0;
 
+	input_str = strdup(value);
 	if (!input_str)
-		return -ENOMEM;
+		return -1;
 
-	hw->txq_prior = calloc(hw->max_tx_queues, sizeof(uint32_t));
+	rte_free(hw->txq_prior);
+	hw->txq_prior = rte_zmalloc(NULL, hw->max_tx_queues * sizeof(uint32_t), 0);
 	if (!hw->txq_prior) {
 		free(input_str);
 		return -ENOMEM;
@@ -51,7 +55,46 @@ parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 
 	str = strtok(input_str, "|");
 	while (str != NULL && i < hw->max_tx_queues) {
-		hw->txq_prior[i++] = (uint32_t)atoi(str);
+		hw->txq_prior[i++] = atoi(str) & ENETC_TBMR_PRIO_MASK;
+		str = strtok(NULL, "|");
+	}
+
+	free(input_str);
+	return 0;
+}
+
+/* Parse enetc4_txq_wrr="w0|w1|..." devarg; weight 1..8 per ring. */
+static int parse_txq_wrr(const char *key __rte_unused, const char *value,
+			  void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
+	char *str;
+	uint32_t i = 0;
+	int w;
+
+	input_str = strdup(value);
+	if (!input_str)
+		return -1;
+
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = rte_zmalloc(NULL,
+			hw->max_tx_queues * sizeof(uint32_t), 0);
+	if (!hw->txq_wrr) {
+		free(input_str);
+		return -1;
+	}
+
+	str = strtok(input_str, "|");
+	while (str != NULL && i < hw->max_tx_queues) {
+		w = atoi(str);
+		if (w < 1)
+			w = 1;
+		if (w > 8)
+			w = 8;
+		hw->txq_wrr[i++] = ENETC_TBMR_WRR(w);
 		str = strtok(NULL, "|");
 	}
 
@@ -97,6 +140,13 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 			return 0;
 		}
 	}
+	if (!strcmp(key, ENETC4_TXQ_WRR)) {
+		if (rte_kvargs_process(kvlist, key,
+				       parse_txq_wrr, (void *)dev) < 0) {
+			rte_kvargs_free(kvlist);
+			return 0;
+		}
+	}
 	if (!strcmp(key, ENETC4_NC_MEMORY)) {
 		if (rte_kvargs_process(kvlist, key,
 				       parse_nc, (void *)dev) < 0) {
@@ -109,19 +159,6 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 	return 0;
 }
 
-/* Supported Rx offloads */
-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_SCATTER;
-
-/* Supported Tx offloads */
-static uint64_t dev_tx_offloads_sup =
-	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
-	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
 
 static int
 enetc4_dev_start(struct rte_eth_dev *dev)
@@ -458,7 +495,9 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 
 		/* apply TX queue priority if configured */
 		if (priv->hw.txq_prior)
-			tx_en |= priv->hw.txq_prior[tx_ring->index];
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		/* enable ring */
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index,
 			       ENETC_TBMR, tx_en);
@@ -613,7 +652,6 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			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;
@@ -869,7 +907,10 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		dev->data->tx_queues[i] = NULL;
 	}
 	dev->data->nb_tx_queues = 0;
-
+	rte_free(hw->txq_prior);
+	hw->txq_prior = NULL;
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = NULL;
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_depopulate();
 
@@ -1020,6 +1061,11 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	for (i = 0; i < dev->data->nb_tx_queues; i++)
 		enetc4_rxbdr_wr(enetc_hw, i, ENETC_TBMR, ENETC_BMR_RESET);
 
+	hw->nc_mode = 0;
+	enetc4_get_devargs(dev, ENETC4_TXQ_PRIORITIES);
+	enetc4_get_devargs(dev, ENETC4_TXQ_WRR);
+	enetc4_get_devargs(dev, ENETC4_NC_MEMORY);
+
 	if (dev->data->nb_rx_queues <= 1)
 		return 0;
 
@@ -1142,7 +1188,13 @@ enetc4_tx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->tx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		tx_data = enetc4_txbdr_rd(&priv->hw.hw, tx_ring->index,
 					 ENETC_TBMR);
-		tx_data = tx_data | ENETC_TBMR_EN;
+		/* Clear scheduler bits before applying fresh devarg values. */
+		tx_data &= ~(ENETC_TBMR_PRIO_MASK | ENETC_TBMR_WRR_MASK);
+		tx_data |= ENETC_TBMR_EN;
+		if (priv->hw.txq_prior)
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index, ENETC_TBMR,
 			       tx_data);
 		dev->data->tx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
@@ -1457,5 +1509,6 @@ RTE_PMD_REGISTER_PCI_TABLE(net_enetc4, pci_id_enetc4_map);
 RTE_PMD_REGISTER_KMOD_DEP(net_enetc4, "* vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4,
 			      ENETC4_TXQ_PRIORITIES "=<string> "
+			      ENETC4_TXQ_WRR "=<string> "
 			      ENETC4_NC_MEMORY "=<int>");
 RTE_LOG_REGISTER_DEFAULT(enetc4_logtype_pmd, NOTICE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9
  2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                     ` (13 preceding siblings ...)
  2026-08-07  3:48   ` [PATCH v2 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
@ 2026-08-07  7:02   ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
                       ` (14 more replies)
  14 siblings, 15 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal

V3-changes:
 - fix doc build issue.
 - fix compilation issue on fedore:43-gcc-minsize

V2-changes:
 - compilation fixes.

V1-changes:
This series adds new PMD features to the ENETC4 driver targeting the
NXP i.MX95 NETC IP.

The series covers:

 - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
 - TSO: TCP Segmentation Offload for the VF Tx path.
 - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
 - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
   8-bit to support speeds beyond 10G.
 - Firmware version: report the NETC IP version via fw_version_get.
 - Register dump: dump SI, port (PF) and BD ring registers.
 - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
 - Link-up interrupt: refresh the cached link speed on each VF link-up
   interrupt so that link_update returns the current speed immediately.
 - Stats reset: software snapshot/delta approach for VF counter reset.
 - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
   enabling interrupt-driven receive with l3fwd-power.
 - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
 - VF link status bitmask: switch VF link status to bitmask encoding
   to align with the PF and newer kernel driver conventions.
 - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
   negotiated on the wire; adds Flow control = Y to enetc4.ini.
 - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.

Gagandeep Singh (14):
  net/enetc: add KEEP_CRC offload support for ENETC4
  net/enetc: add TSO support for ENETC4 VF
  net/enetc: add RSC (hardware LRO) support for ENETC4
  net/enetc: extend link speed code field to 8-bit for PF-to-VF message
  net/enetc: support firmware version get for VF
  net/enetc: support registers dump
  net/enetc: support ethtool ring parameters
  net/enetc: refresh link speed on VF link-up interrupt
  net/enetc: support stats reset for VF
  net/enetc4: add per-queue Rx interrupt support for VF
  net/enetc4: add SI-based port VLAN insertion and removal
  net/enetc4: update VF link status to bitmask encoding
  net/enetc4: enable TX PAUSE via VF RX congestion mode
  net/enetc4: add WRR Tx scheduler devarg for VF rings

 doc/guides/nics/enetc4.rst             |  81 ++-
 doc/guides/nics/features/enetc4.ini    |   8 +
 doc/guides/rel_notes/release_26_11.rst |  19 +
 drivers/net/enetc/base/enetc4_hw.h     | 129 +++-
 drivers/net/enetc/base/enetc_hw.h      |   6 +
 drivers/net/enetc/enetc.h              | 135 ++++-
 drivers/net/enetc/enetc4_ethdev.c      | 402 +++++++++++--
 drivers/net/enetc/enetc4_vf.c          | 779 ++++++++++++++++++++++---
 drivers/net/enetc/enetc_rxtx.c         | 529 ++++++++++++++++-
 9 files changed, 1942 insertions(+), 146 deletions(-)

-- 
2.25.1


^ permalink raw reply	[flat|nested] 110+ messages in thread

* [PATCH v3 01/14] net/enetc: add KEEP_CRC offload support for ENETC4
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
                       ` (13 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The legacy ENETC (LS1028A) driver already supports the
RTE_ETH_RX_OFFLOAD_KEEP_CRC offload, but ENETC4 (i.MX95 NETC) did not.

Add KEEP_CRC support for ENETC4 (both PF and VF):

- Advertise RTE_ETH_RX_OFFLOAD_KEEP_CRC in the supported Rx offloads
  for the ENETC4 PF and VF. The VF reuses the PF Rx queue setup, so the
  HW configuration and datapath handling apply to both.
- Configure the per-ring RBaMR[CRC] bit in the Rx queue setup so that
  HW preserves the Ethernet FCS in the receive buffer when the offload
  is requested (0 = FCS removed, 1 = FCS preserved).
- Follow the DPDK convention used by the legacy ENETC and ixgbe drivers:
  set crc_len to RTE_ETHER_CRC_LEN when KEEP_CRC is enabled and have the
  datapath subtract crc_len from pkt_len/data_len. ENETC and ENETC4 share
  the same datapath (enetc_rxtx.c), so the semantics stay consistent.
- Handle the scatter-gather boundary case where the 4-byte FCS straddles
  the last two segments: drop the trailing segment, decrement nb_segs and
  trim the carry-over from its predecessor.

Update the enetc4 feature matrix to list CRC offload.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  6 ++++
 drivers/net/enetc/base/enetc4_hw.h     |  4 +++
 drivers/net/enetc/enetc4_ethdev.c      | 31 ++++++++++++++++++--
 drivers/net/enetc/enetc4_vf.c          |  1 +
 drivers/net/enetc/enetc_rxtx.c         | 39 +++++++++++++++++++++++---
 6 files changed, 75 insertions(+), 7 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 698140e30b..91b18d979e 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ Packet type parsing  = Y
 Basic stats          = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
+CRC offload          = Y
 Queue start/stop     = Y
 Scattered Rx         = Y
 Linux                = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..3678dd6894 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -56,6 +56,12 @@ New Features
      =======================================================
 
 
+* **Updated NXP ENETC4 PMD.**
+
+  Updated the NXP ENETC4 poll mode driver for i.MX95:
+
+  * Added KEEP_CRC Rx offload support for the ENETC4 PMD to preserve the Ethernet FCS.
+
 Removed Items
 -------------
 
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 9685e7e1e5..0105549857 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -68,6 +68,10 @@ struct enetc_msg_swbd {
 #define PM_CMD_CFG_TX_EN		BIT(0)
 #define PM_CMD_CFG_RX_EN		BIT(1)
 
+/* RBaMR[CRC]: 0 = FCS removed, 1 = FCS preserved (KEEP_CRC) */
+#define ENETC4_RBMR_CRC			BIT(8)
+
+
 /* i.MX95 supports jumbo frame, but it is recommended to set the max frame
  * size to 2000 bytes.
  */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 2ddd63dafb..1a8056580c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -11,6 +11,19 @@
 #include "enetc_logs.h"
 #include "enetc.h"
 
+/* Supported Rx offloads */
+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;
+
+/* Supported Tx offloads */
+static uint64_t dev_tx_offloads_sup =
+	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
+	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
+
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
 
@@ -536,6 +549,8 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			ENETC_DEV_PRIVATE(data->dev_private);
 	uint64_t rx_offloads = data->dev_conf.rxmode.offloads;
+	uint32_t rx_enable;
+	bool keep_crc;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_rx_desc > MAX_BD_COUNT)
@@ -549,6 +564,9 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	rx_ring->index = rx_queue_id;
+	keep_crc = !!(rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC);
+	rx_ring->crc_len = (uint8_t)(keep_crc ? RTE_ETHER_CRC_LEN : 0);
+
 	err = enetc4_alloc_rxbdr(rx_ring, nb_rx_desc);
 	if (err)
 		goto fail;
@@ -562,19 +580,25 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	data->rx_queues[rx_queue_id] = rx_ring;
 	rx_ring->rx_deferred_start = rx_conf->rx_deferred_start;
 
+	if (keep_crc)
+		rx_enable |= ENETC4_RBMR_CRC;
+	else
+		rx_enable &= ~ENETC4_RBMR_CRC;
+
 	if (!rx_conf->rx_deferred_start) {
 		/* enable ring */
+		rx_enable |= ENETC_RBMR_EN;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
-			       ENETC_RBMR_EN);
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STARTED;
 	} else {
+		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STOPPED;
 	}
 
-	rx_ring->crc_len = (uint8_t)((rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC) ?
-				     RTE_ETHER_CRC_LEN : 0);
 	return 0;
 fail:
 	rte_free(rx_ring);
@@ -582,6 +606,7 @@ 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 ef5f1e6d66..83a1e4931f 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -52,6 +52,7 @@ static uint64_t dev_rx_offloads_sup =
 	RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_VLAN_FILTER |
+	RTE_ETH_RX_OFFLOAD_KEEP_CRC |
 	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 8678bafece..e3bef607dd 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -530,6 +530,28 @@ enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
 	return rx_frm_cnt;
 }
 
+/*
+ * Trim the Ethernet FCS from a received cluster when HW CRC strip is
+ * disabled. pkt_len is reduced by crc_len. If the FCS straddles the last
+ * two segments (last seg holds fewer bytes than crc_len), drop the trailing
+ * segment and trim the carry-over from its predecessor. prev_seg is the
+ * segment preceding last_seg in the chain (the caller already tracks it).
+ */
+static inline void
+enetc_rx_crc_trim(struct rte_mbuf *first_seg, struct rte_mbuf *prev_seg,
+		  struct rte_mbuf *last_seg, uint16_t crc_len)
+{
+	first_seg->pkt_len -= crc_len;
+	if (likely(last_seg->data_len > crc_len)) {
+		last_seg->data_len -= crc_len;
+	} else if (prev_seg != NULL) {
+		first_seg->nb_segs--;
+		prev_seg->data_len -= crc_len - last_seg->data_len;
+		prev_seg->next = NULL;
+		rte_pktmbuf_free_seg(last_seg);
+	}
+}
+
 static int
 enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		    struct rte_mbuf **rx_pkts,
@@ -539,7 +561,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -572,6 +594,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg, rxbd_temp.r.parse_summary);
 			first_seg->hash.rss = rxbd_temp.r.rss_hash;
@@ -579,6 +602,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -590,7 +614,9 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
@@ -760,7 +786,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -815,6 +841,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg,
 					   rxbd->r.parse_summary);
@@ -823,6 +850,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -838,7 +866,10 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
+
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 02/14] net/enetc: add TSO support for ENETC4 VF
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
                       ` (12 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add TCP and UDP segmentation offload (TSO) for the ENETC4 virtual
function. The offload is advertised through the VF Tx capabilities and
is enabled on a per-queue basis when an application requests the TSO
offload flags during Tx queue setup.

A dedicated Tx burst, enetc_xmit_pkts_lso(), builds the large-send
descriptor chain: a standard header BD, a 16B extension BD carrying the
segment size and frame length extension, and one BD per payload buffer
with the final-frame flag on the last BD. To make room for the extra
extension BD, an LSO-enabled Tx ring is allocated with twice the number
of descriptors. The existing non-TSO Tx paths are left unchanged.

Update the ENETC4 feature list and documentation accordingly.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   2 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/base/enetc4_hw.h     |  35 +++-
 drivers/net/enetc/enetc.h              |   5 +
 drivers/net/enetc/enetc4_ethdev.c      |  45 ++++-
 drivers/net/enetc/enetc4_vf.c          |   2 +
 drivers/net/enetc/enetc_rxtx.c         | 255 +++++++++++++++++++++++++
 8 files changed, 341 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index f0dd039f6e..4d27e4048e 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -53,6 +53,8 @@ Key functionality includes:
 - Receive processing: Upon packet reception, the BD Ring status bit is set,
   facilitating packet processing.
 - 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.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 91b18d979e..8ec1e8d00f 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
+TSO                  = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 3678dd6894..6a348ab4e0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -61,6 +61,7 @@ New Features
   Updated the NXP ENETC4 poll mode driver for i.MX95:
 
   * 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.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 0105549857..64c1a5e750 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2024 NXP
+ * Copyright 2024-2026 NXP
  *
  * This header file defines the register offsets and bit fields
  * of ENETC4 PF and VFs.
@@ -24,8 +24,14 @@ struct enetc_msg_swbd {
 
 /* enetc4 txbd flags */
 #define ENETC4_TXBD_FLAGS_L4CS		BIT(0)
+/* Request LSO (Large Send Offload) segmentation for this frame */
+#define ENETC4_TXBD_FLAGS_LSO		BIT(1)
+/* L4 checksum insertion (also used for checksum update on LSO) */
 #define ENETC4_TXBD_FLAGS_L_TX_CKSUM	BIT(3)
+/* Extended descriptor: the next 16B ring entry is an extension BD */
+#define ENETC4_TXBD_FLAGS_EXT		BIT(6)
 #define ENETC4_TXBD_FLAGS_F		BIT(7)
+
 /* L4 type */
 #define ENETC4_TXBD_L4T_UDP		BIT(0)
 #define ENETC4_TXBD_L4T_TCP		BIT(1)
@@ -34,6 +40,33 @@ struct enetc_msg_swbd {
 /* IPv4 checksum */
 #define ENETC4_TXBD_IPCS		1
 
+/*
+ * Extension Transmit Buffer Descriptor (16B). When the standard BD has the
+ * extended flag set, this occupies the next ring entry and carries the extra
+ * fields required by LSO.
+ */
+struct enetc_tx_bd_ext {
+	uint32_t timestamp;	/* PTP timestamp, unused for LSO */
+	uint32_t vlan;		/* VLAN insert, unused for LSO */
+	uint32_t lso;		/* LSO_MAX_SEG_SIZE and FRM_LEN_EXT */
+	uint32_t flags;		/* extension flags */
+};
+
+/* LSO_MAX_SEG_SIZE occupies bits 13-0 of the LSO word */
+#define ENETC4_TXBD_EXT_LSO_SEG_MASK	0x3fff
+#define ENETC4_TXBD_EXT_LSO_SEG(mss) \
+		((uint32_t)((mss) & ENETC4_TXBD_EXT_LSO_SEG_MASK))
+/* FRM_LEN_EXT occupies bits 19-16 of the LSO word (top 4 bits of data len) */
+#define ENETC4_TXBD_EXT_FRM_LEN_EXT(x) \
+		(((uint32_t)((x) & 0xf)) << 16)
+/* Final flag in the extension flags word (bit 127 -> local bit 31) */
+#define ENETC4_TXBD_EXT_FLAGS_F		BIT(31)
+
+/* NETC does not create LSO frames larger than this many bytes */
+#define ENETC4_LSO_MAX_FRAME		9600
+/* Maximum LSO data unit (payload to be segmented) supported by HW: 256KB */
+#define ENETC4_LSO_MAX_DATA_UNIT	(256 * 1024)
+
 /***************************ENETC port registers**************************/
 #define ENETC4_PMR		0x10
 #define ENETC4_PMR_EN		(BIT(16) | BIT(17) | BIT(18))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index d3a8b8e34a..db349e051b 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -91,6 +91,7 @@ struct enetc_bdr {
 		void *tcisr; /* Tx */
 		int next_to_alloc; /* Rx */
 	};
+
 	struct rte_mempool *mb_pool;   /* mbuf pool to populate RX ring. */
 	/* Partial scatter-gather chain persisted across burst calls. */
 	struct rte_mbuf *pkt_first_seg; /* first segment of in-progress frame */
@@ -99,6 +100,7 @@ struct enetc_bdr {
 	uint64_t ierrors;
 	uint8_t rx_deferred_start;
 	uint8_t tx_deferred_start;
+	uint8_t lso_enable;
 };
 
 struct enetc_eth_hw {
@@ -312,6 +314,9 @@ uint16_t enetc_xmit_pkts(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
 uint16_t enetc_xmit_pkts_nc(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
+uint16_t enetc_xmit_pkts_lso(void *txq, struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts);
+
 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,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 1a8056580c..897e12e21f 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -22,7 +22,9 @@ static uint64_t dev_rx_offloads_sup =
 static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
+	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
@@ -321,28 +323,38 @@ static int
 enetc4_alloc_txbdr(struct enetc_bdr *txr, uint16_t nb_desc)
 {
 	int size;
+	uint32_t ring_desc;
 
-	size = nb_desc * sizeof(struct enetc_swbd);
+	/*
+	 * On LSO-enabled rings each frame uses an extra 16B extension BD
+	 * (logical 32B descriptor) plus the payload BDs.  Size the ring with
+	 * twice the requested entries so the effective frame capacity is
+	 * preserved.  Non-LSO rings are sized exactly as requested.
+	 */
+	ring_desc = txr->lso_enable ? (uint32_t)nb_desc * 2 : (uint32_t)nb_desc;
+
+	size = ring_desc * sizeof(struct enetc_swbd);
 	/* Zero q_swbd so buffer_addr is NULL for all uninitialized slots. */
 	txr->q_swbd = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->q_swbd == NULL)
 		return -ENOMEM;
 
 	/* Allocate the TX BD ring: each BD is struct enetc_tx_bd (16 bytes). */
-	size = nb_desc * sizeof(struct enetc_tx_bd);
+	size = ring_desc * sizeof(struct enetc_tx_bd);
 	txr->bd_base = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->bd_base == NULL) {
 		rte_free(txr->q_swbd);
 		txr->q_swbd = NULL;
 		return -ENOMEM;
 	}
-	txr->bd_count = nb_desc;
+	txr->bd_count = ring_desc;
 	txr->next_to_clean = 0;
 	txr->next_to_use = 0;
 
 	return 0;
 }
 
+
 static void
 enetc4_free_bdr(struct enetc_bdr *rxr)
 {
@@ -352,6 +364,7 @@ enetc4_free_bdr(struct enetc_bdr *rxr)
 	rxr->bd_base = NULL;
 }
 
+
 static void
 enetc4_setup_txbdr(struct enetc_hw *hw, struct enetc_bdr *tx_ring)
 {
@@ -388,6 +401,7 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	struct rte_eth_dev_data *data = dev->data;
 	struct enetc_eth_adapter *priv =
 			ENETC_DEV_PRIVATE(data->dev_private);
+	uint64_t tx_offloads;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_desc > MAX_BD_COUNT)
@@ -401,6 +415,29 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	tx_ring->index = queue_idx;
+	/*
+	 * LSO (TCP/UDP segmentation) is a port-level offload. The Tx burst is
+	 * a single device-level function pointer, so it must be consistent for
+	 * all queues; likewise every ring must be sized the same way. Decide
+	 * from the port offloads only (not the per-queue tx_conf) so that all
+	 * queues either use the LSO burst with a doubled ring, or none do.
+	 * The LSO burst also handles non-TSO packets, so it is safe on queues
+	 * that never carry TSO traffic. LSO needs HW FCS insertion, so it is
+	 * incompatible with KEEP_CRC on Rx.
+	 */
+	tx_offloads = data->dev_conf.txmode.offloads;
+	if (tx_offloads & (RTE_ETH_TX_OFFLOAD_TCP_TSO |
+			   RTE_ETH_TX_OFFLOAD_UDP_TSO)) {
+		if (data->dev_conf.rxmode.offloads &
+		    RTE_ETH_RX_OFFLOAD_KEEP_CRC) {
+			ENETC_PMD_ERR("LSO (TSO) is incompatible with KEEP_CRC");
+			rte_free(tx_ring);
+			return -EINVAL;
+		}
+		tx_ring->lso_enable = 1;
+		dev->tx_pkt_burst = &enetc_xmit_pkts_lso;
+	}
+
 	err = enetc4_alloc_txbdr(tx_ring, nb_desc);
 	if (err)
 		goto fail;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 83a1e4931f..3083a56335 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -60,6 +60,8 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO |
 	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
 
 static void
diff --git a/drivers/net/enetc/enetc_rxtx.c b/drivers/net/enetc/enetc_rxtx.c
index e3bef607dd..f19f32eace 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -221,6 +221,261 @@ enetc_xmit_pkts_nc(void *tx_queue,
 	return start;
 }
 
+/*
+ * LSO (Large Send Offload) Tx burst.
+ *
+ * Dedicated burst used on Tx rings with LSO enabled (txr->lso_enable). It
+ * follows the same non-cache-coherent discipline as enetc_xmit_pkts_cacheable:
+ * payload lines are flushed with dcbf before handing the frame to HW and the
+ * written BD cache lines are flushed at the end of the batch.
+ *
+ * A TSO/USO frame uses an extended Tx descriptor: the standard BD points at
+ * the L2/L3/L4 header template (BUF_LEN = header length, FRM_LEN = payload
+ * length), followed by an extension BD in the next ring slot holding the
+ * segment size, then one BD per payload buffer with the F flag on the last.
+ * Non-TSO packets fall through to the normal encoding so mixed traffic works.
+ */
+uint16_t
+enetc_xmit_pkts_lso(void *tx_queue,
+		struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts)
+{
+	int i, start, bds_to_use;
+	struct enetc_tx_bd *txbd = NULL;
+	struct enetc_tx_bd_ext *txbd_ext;
+	struct enetc_bdr *tx_ring = (struct enetc_bdr *)tx_queue;
+	unsigned int j;
+	uint8_t *data;
+	struct rte_mbuf *seg;
+	uint16_t seg_len, segs_per_pkt;
+	bool is_first_seg;
+	int first_bd_idx, bd_count;
+
+	i = tx_ring->next_to_use;
+	bds_to_use = enetc_bd_unused(tx_ring);
+	bd_count = tx_ring->bd_count;
+	start = 0;
+
+	first_bd_idx = i;
+
+	while (start < nb_pkts) {
+		seg = tx_pkts[start];
+		segs_per_pkt = seg->nb_segs;
+
+		if (seg->ol_flags &
+		    (RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_UDP_SEG)) {
+			bool is_udp_seg =
+				!!(seg->ol_flags & RTE_MBUF_F_TX_UDP_SEG);
+			uint32_t hdr_len = seg->l2_len + seg->l3_len +
+					   seg->l4_len;
+			uint32_t data_unit;
+			uint16_t first_payload;
+			struct rte_mbuf *dseg;
+			int bds_needed;
+
+			/* Header BD + extension BD + one BD per segment. */
+			bds_needed = 2 + segs_per_pkt;
+			if (bds_to_use < bds_needed)
+				break;
+
+			/*
+			 * Skip frames with nothing to segment, or whose
+			 * headers are not fully contained in the first
+			 * segment. The first BD carries the header template
+			 * and first_payload is computed as (first segment
+			 * data_len - hdr_len), so the L2/L3/L4 headers must
+			 * reside contiguously in the first mbuf; otherwise the
+			 * unsigned subtraction would underflow.
+			 */
+			if (unlikely(hdr_len >= rte_pktmbuf_pkt_len(seg) ||
+				     hdr_len > rte_pktmbuf_data_len(seg))) {
+				start++;
+				continue;
+			}
+			data_unit = rte_pktmbuf_pkt_len(seg) - hdr_len;
+
+			/*
+			 * Skip frames that violate HW LSO limits: a zero
+			 * segment size, a payload larger than the HW data
+			 * unit, or a per-segment frame (headers + segment)
+			 * bigger than the maximum LSO frame size.
+			 */
+			if (unlikely(seg->tso_segsz == 0 ||
+				     data_unit > ENETC4_LSO_MAX_DATA_UNIT ||
+				     hdr_len + seg->tso_segsz >
+					     ENETC4_LSO_MAX_FRAME)) {
+				start++;
+				continue;
+			}
+
+			/* Standard (first) BD: header template only. */
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			seg_len = rte_pktmbuf_data_len(seg);
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			memset(txbd, 0, sizeof(*txbd));
+			tx_ring->q_swbd[i].buffer_addr = seg;
+
+			/* FRM_LEN low 16 bits = payload length, BUF_LEN = hdr. */
+			txbd->frm_len = rte_cpu_to_le_16(data_unit & 0xffff);
+			txbd->buf_len = rte_cpu_to_le_16((uint16_t)hdr_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+
+			/* Checksum control for the per-segment L3/L4 rewrite. */
+			txbd->l3_start = seg->l2_len;
+			txbd->l3_hdr_size = seg->l3_len / 4;
+			if (seg->ol_flags & RTE_MBUF_F_TX_IPV6) {
+				txbd->l3t = 1;
+			} else {
+				txbd->l3t = ENETC4_TXBD_L3T;
+				txbd->ipcs = ENETC4_TXBD_IPCS;
+			}
+			txbd->l4t = is_udp_seg ? ENETC4_TXBD_L4T_UDP :
+						 ENETC4_TXBD_L4T_TCP;
+			txbd->flags = ENETC4_TXBD_FLAGS_L_TX_CKSUM |
+				      ENETC4_TXBD_FLAGS_L4CS |
+				      ENETC4_TXBD_FLAGS_LSO |
+				      ENETC4_TXBD_FLAGS_EXT;
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/* Extension BD occupies the next ring slot. */
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			txbd_ext = (struct enetc_tx_bd_ext *)
+				   ENETC_TXBD(*tx_ring, i);
+			memset(txbd_ext, 0, sizeof(*txbd_ext));
+			txbd_ext->lso = rte_cpu_to_le_32(ENETC4_TXBD_EXT_LSO_SEG(seg->tso_segsz) |
+					ENETC4_TXBD_EXT_FRM_LEN_EXT(data_unit >> 16));
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/*
+			 * Payload BDs. The first segment's payload starts after
+			 * the header template; remaining segments are pure
+			 * payload.
+			 */
+			first_payload = (uint16_t)(seg_len - hdr_len);
+			dseg = seg;
+			is_first_seg = true;
+			while (dseg) {
+				uint16_t dlen;
+				uint64_t daddr;
+
+				if (is_first_seg) {
+					dlen = first_payload;
+					daddr = rte_mbuf_data_iova(dseg) +
+						hdr_len;
+					is_first_seg = false;
+				} else {
+					dlen = rte_pktmbuf_data_len(dseg);
+					data = rte_pktmbuf_mtod(dseg, void *);
+					for (j = 0; j < dlen;
+					     j += RTE_CACHE_LINE_SIZE)
+						dcbf(data + j);
+					dcbf(data + (dlen - 1));
+					daddr = rte_mbuf_data_iova(dseg);
+				}
+
+				if (dlen == 0) {
+					dseg = dseg->next;
+					continue;
+				}
+
+				tx_ring->q_swbd[i].buffer_addr = NULL;
+				txbd = ENETC_TXBD(*tx_ring, i);
+				memset(txbd, 0, sizeof(*txbd));
+				txbd->buf_len = rte_cpu_to_le_16(dlen);
+				txbd->addr = rte_cpu_to_le_64(daddr);
+				i++;
+				bds_to_use--;
+				if (unlikely(i == bd_count))
+					i = 0;
+				dseg = dseg->next;
+			}
+
+			/* Mark the last written BD as frame-last. */
+			txbd->flags |= ENETC4_TXBD_FLAGS_F;
+			start++;
+			continue;
+		}
+
+		/* Non-TSO packet: standard single/multi-seg encoding. */
+		if (bds_to_use < segs_per_pkt)
+			break;
+
+		is_first_seg = true;
+		while (seg) {
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			seg_len = rte_pktmbuf_data_len(seg);
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			txbd->flags = 0;
+			if (is_first_seg) {
+				tx_ring->q_swbd[i].buffer_addr = seg;
+				txbd->frm_len = rte_pktmbuf_pkt_len(seg);
+				if (seg->ol_flags & ENETC4_TX_CKSUM_OFFLOAD_MASK)
+					enetc4_tx_offload_checksum(seg, txbd);
+				is_first_seg = false;
+			}
+
+			txbd->buf_len = rte_cpu_to_le_16(seg_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+			seg = seg->next;
+			i++;
+			bds_to_use--;
+
+			if (unlikely(i == bd_count))
+				i = 0;
+		}
+
+		txbd->flags |= rte_cpu_to_le_16(ENETC4_TXBD_FLAGS_F);
+		start++;
+	}
+
+	/*
+	 * Flush TX BDs to PoC so HW (non-cache-coherent i.MX95) can read the
+	 * descriptors from memory.  Same discipline as enetc_xmit_pkts_cacheable:
+	 * walk from the cache-line-aligned start of first_bd_idx to just past
+	 * the last written BD, one dcbf per 64-byte (4-BD) line.
+	 */
+	if (likely(start > 0)) {
+		int n = first_bd_idx & ~ENETC_BD_PER_CL_MASK;
+		int written = (i - n + bd_count) % bd_count;
+
+		if (written == 0)
+			written = bd_count;
+		written = (written + ENETC_BD_PER_CL_MASK) &
+			  ~ENETC_BD_PER_CL_MASK;
+
+		while (written > 0) {
+			dcbf((void *)ENETC_TXBD(*tx_ring, n));
+			n = (n + ENETC_BD_PER_CL) % bd_count;
+			written -= ENETC_BD_PER_CL;
+		}
+	}
+
+	enetc_clean_tx_ring(tx_ring);
+	tx_ring->next_to_use = i;
+	enetc_wr_reg(tx_ring->tcir, i);
+
+	return start;
+}
+
 int
 enetc_refill_rx_ring(struct enetc_bdr *rx_ring, const int buff_cnt)
 {
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
                       ` (11 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

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 <g.singh@nxp.com>
---
 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


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (2 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
                       ` (10 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF message is only 16-bit wide, where the upper 8-bit is the
class_id and the lower 8-bit carries the message content. For the
link speed message, the lower 4-bit was previously occupied by the cookie
field, leaving only 4-bit for the speed code, which allows at most
15 distinct speed values.

As the NETC IP evolves and supports more and more link speeds, 15 speed
values are clearly insufficient. Since the cookie field is designed
for non-blocking messages and has no meaningful use in link speed
messages, remove it and expand the speed code field from 4-bit to 8-bit,
allowing up to 255 speed values (ENETC_SPEED_MAX = 0xff).

Instead of enumerating every speed value greater than 5Gbps
explicitly, introduce a formula-based approach:

	speed_code = (link_speed - 5000) / 1000 + ENETC_SPEED_5000

This removes the explicit enum entries for 10G, 25G, 50G and 100G,
and replaces the individual switch-case branches with a generic
implementation to get the speed, making it easy to support any
future high speed without modifying the enum or the switch statement.

For backward compatibility with a PF running an older kernel (before
6.18.37) that still uses the legacy 4-bit speed code / 4-bit cookie
message layout, add a "vf_link_legacy" devarg. When set to 1, the VF
extracts the message result from the upper 4 bits of the lower byte
and decodes speeds greater than 5Gbps using the legacy fixed class
codes (10G/25G/50G/100G). The default (0) uses the new 8-bit layout.

	Usage: -a <pci_addr>,vf_link_legacy=1

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |  32 ++++-
 drivers/net/enetc/enetc4_vf.c          | 175 +++++++++++++++++++++----
 3 files changed, 175 insertions(+), 33 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 0dd08e0259..d7961d3c85 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -63,6 +63,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.
+  * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 67f8ce1919..c983cb059e 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -120,6 +120,10 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
+	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
+	 */
+	uint8_t vf_link_legacy;
 };
 
 /*
@@ -211,11 +215,29 @@ enum speed {
 	ENETC_SPEED_1000 = 0x5,
 	ENETC_SPEED_2500 = 0x6,
 	ENETC_SPEED_5000 = 0x7,
-	ENETC_SPEED_10G = 0x8,
-	ENETC_SPEED_25G = 0x9,
-	ENETC_SPEED_50G = 0xA,
-	ENETC_SPEED_100G = 0xB,
-	ENETC_SPEED_NOT_SUPPORTED = 0xF
+	/* Base speed class code used by the >5Gbps formula below */
+	/* Do not add enumeration values for any speed greater than
+	 * 5Gbps. For any speed greater than 5Gbps, its speed class
+	 * code should follow the formula below.
+	 *
+	 * SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+	 *
+	 * The unit of link_speed should be Mbps, the max SPEED
+	 * should <= ENETC_SPEED_MAX.
+	 */
+	ENETC_SPEED_MAX = 0xff,
+};
+
+/* Legacy speed class codes for backward compatibility with an older kernel PF
+ * (before 6.18.37) that encoded a 4-bit speed code and 4-bit cookie in the
+ * message's lower byte. Used only when the vf_link_legacy devarg is set.
+ */
+enum speed_legacy {
+	ENETC_SPEED_LEGACY_10G = 0x8,
+	ENETC_SPEED_LEGACY_25G = 0x9,
+	ENETC_SPEED_LEGACY_50G = 0xA,
+	ENETC_SPEED_LEGACY_100G = 0xB,
+	ENETC_SPEED_LEGACY_NOT_SUPPORTED = 0xF
 };
 
 /* PSI-VSI command header format */
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index be79a18a39..d34b8019ea 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -5,6 +5,7 @@
 #include <stdbool.h>
 #include <rte_kvargs.h>
 #include <rte_random.h>
+#include <rte_kvargs.h>
 #include <dpaax_iova_table.h>
 #include "enetc_logs.h"
 #include "enetc.h"
@@ -43,6 +44,12 @@ enetc4_vf_get_devarg_nc(struct rte_eth_dev *dev)
 #define ENETC_BYTE_SIZE			8
 #define ENETC_MSB_BIT			0x8000
 
+/* Backward-compat devarg for a PF running a kernel before 6.18.37, which uses
+ * the legacy PF-to-VF link message layout (4-bit speed code + 4-bit cookie).
+ * Usage: -a <pci_addr>,vf_link_legacy=1
+ */
+#define ENETC_VF_LINK_LEGACY		"vf_link_legacy"
+
 uint16_t enetc_crc_table[ENETC_CRC_TABLE_SIZE];
 bool enetc_crc_gen;
 
@@ -101,6 +108,59 @@ enetc_crc_calc(uint16_t crc, const uint8_t *buffer, size_t len)
 	return crc;
 }
 
+static int
+parse_vf_link_legacy(const char *key __rte_unused, const char *value,
+		     void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *endptr;
+	unsigned long val;
+
+	if (!value || *value == '\0') {
+		ENETC_PMD_WARN("Empty value for devarg %s, ignoring",
+			       ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	errno = 0;
+	val = strtoul(value, &endptr, 0);
+	if (errno != 0 || *endptr != '\0' || val > 1) {
+		ENETC_PMD_WARN("Invalid value '%s' for devarg %s, expected 0 or 1",
+			       value, ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	hw->vf_link_legacy = (uint8_t)val;
+
+	return 0;
+}
+
+static void
+enetc4_vf_get_devargs(struct rte_eth_dev *dev)
+{
+	struct rte_devargs *devargs;
+	struct rte_kvargs *kvlist;
+
+	devargs = dev->device->devargs;
+	if (!devargs)
+		return;
+
+	kvlist = rte_kvargs_parse(devargs->args, NULL);
+	if (!kvlist)
+		return;
+
+	if (rte_kvargs_count(kvlist, ENETC_VF_LINK_LEGACY)) {
+		if (rte_kvargs_process(kvlist, ENETC_VF_LINK_LEGACY,
+				       parse_vf_link_legacy, (void *)dev) < 0)
+			ENETC_PMD_WARN("Failed to parse devarg %s",
+				       ENETC_VF_LINK_LEGACY);
+	}
+
+	rte_kvargs_free(kvlist);
+}
+
 static int
 enetc4_vf_dev_infos_get(struct rte_eth_dev *dev,
 			struct rte_eth_dev_info *dev_info)
@@ -212,6 +272,7 @@ enetc4_msg_vsi_write_msg(struct enetc_hw *hw,
 static void
 enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgsr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -221,8 +282,15 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgsr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -231,6 +299,7 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 static void
 enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgrr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -240,8 +309,15 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgrr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -731,6 +807,8 @@ enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 static int
 enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused)
 {
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
 	struct enetc_psi_reply_msg *reply_msg;
 	struct rte_eth_link link;
 	int err;
@@ -809,28 +887,62 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 			link.link_speed = RTE_ETH_SPEED_NUM_5G;
 			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 			break;
-		case ENETC_SPEED_10G:
-			link.link_speed = RTE_ETH_SPEED_NUM_10G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_25G:
-			link.link_speed = RTE_ETH_SPEED_NUM_25G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_50G:
-			link.link_speed = RTE_ETH_SPEED_NUM_50G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100G:
-			link.link_speed = RTE_ETH_SPEED_NUM_100G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_NOT_SUPPORTED:
-			ENETC_PMD_DEBUG("Speed not supported");
-			link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-			break;
 		default:
-			ENETC_PMD_ERR("Unknown speed status");
+			if (hw->vf_link_legacy) {
+				/* Legacy PF-to-VF message layout (older kernel
+				 * PF): speeds greater than 5Gbps are encoded
+				 * with fixed 4-bit class codes rather than the
+				 * formula below.
+				 */
+				switch (reply_msg->status) {
+				case ENETC_SPEED_LEGACY_10G:
+					link.link_speed = RTE_ETH_SPEED_NUM_10G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_25G:
+					link.link_speed = RTE_ETH_SPEED_NUM_25G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_50G:
+					link.link_speed = RTE_ETH_SPEED_NUM_50G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_100G:
+					link.link_speed = RTE_ETH_SPEED_NUM_100G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+					ENETC_PMD_DEBUG("Speed not supported");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				default:
+					ENETC_PMD_ERR("Unknown speed status");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				}
+				break;
+			}
+
+			/* Any status reaching here is greater than
+			 * ENETC_SPEED_5000, as all values from 0x0 to
+			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
+			 * greater than 5Gbps are not enumerated and follow the
+			 * formula:
+			 *
+			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+			 *
+			 * where link_speed is in Mbps. Reverse it here to get the
+			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
+			 *
+			 * The PF only reports speeds that map to a well-known
+			 * RTE_ETH_SPEED_NUM_* value, so the computed value is a
+			 * valid DPDK speed. If a future speed not yet defined in
+			 * DPDK needs to be supported, the corresponding
+			 * RTE_ETH_SPEED_NUM_* value must first be added upstream.
+			 */
+			link.link_speed = (reply_msg->status - ENETC_SPEED_5000)
+					  * 1000 + 5000;
+			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 			break;
 		}
 	} else {
@@ -839,10 +951,9 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	link.link_autoneg = 1;
-
 	rte_eth_linkstatus_set(dev, &link);
-
 	rte_free(reply_msg);
+
 	return 0;
 }
 
@@ -1421,6 +1532,12 @@ enetc4_vf_dev_init(struct rte_eth_dev *eth_dev)
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_populate();
 
+	/* Parse VF specific devargs (e.g. vf_link_legacy) before the first
+	 * link update so that PF-to-VF link messages are interpreted using
+	 * the correct (legacy or current) layout.
+	 */
+	enetc4_vf_get_devargs(eth_dev);
+
 	ENETC_PMD_DEBUG("port_id %d vendorID=0x%x deviceID=0x%x",
 			eth_dev->data->port_id, pci_dev->id.vendor_id,
 			pci_dev->id.device_id);
@@ -1514,5 +1631,7 @@ RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
 			      ENETC4_VSI_DELAY "=<uint> "
-			      ENETC4_NC_MEMORY "=<int>");
+			      ENETC4_NC_MEMORY "=<int> "
+			      ENETC_VF_LINK_LEGACY "=<0|1>");
+
 RTE_LOG_REGISTER_DEFAULT(enetc4_vf_logtype_pmd, NOTICE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 05/14] net/enetc: support firmware version get for VF
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (3 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 06/14] net/enetc: support registers dump Gagandeep Singh
                       ` (9 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the ``.fw_version_get`` eth_dev op for the ENETC4 VF PMD to
report the NETC IP revision.

The NETC IP major revision is read from the PCI revision ID register,
while the IP minor revision is retrieved from the PSI over VSI-PSI
messaging using the "Get IP version" command class (class ID 0xF0,
command ID 0x1). The version is reported in the "<major>.<minor>"
format.

The PSI firmware only implements the IP_MN command of this class, so
the major revision is obtained from the PCI revision ID, matching the
behaviour of the kernel PF/VF drivers.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   1 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |  19 +++-
 drivers/net/enetc/enetc4_vf.c          | 135 +++++++++++++++++++++++++
 5 files changed, 155 insertions(+), 2 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index e7f4348603..ea0e37896d 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,7 @@ Key functionality includes:
   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.
+- Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index aae398e210..eaf474e7ba 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ VLAN filter          = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
+FW version           = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index d7961d3c85..5ac2bb4de0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,7 @@ New Features
   * Added TCP Segmentation Offload (TSO) support for the ENETC4 VF.
   * Added Receive Segment Coalesce (RSC / hardware LRO) support for ENETC4 PF and VF.
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
+  * Added firmware version reporting for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index c983cb059e..b1428f1bca 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -170,7 +170,8 @@ enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
-	ENETC_CLASS_ID_LINK_SPEED = 0x81
+	ENETC_CLASS_ID_LINK_SPEED = 0x81,
+	ENETC_CLASS_ID_GET_IP_VER = 0xF0
 };
 
 /* Enum for command IDs */
@@ -184,9 +185,18 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_LINK_STATUS = 0,
 	ENETC_CMD_ID_REGISTER_LINK_NOTIF = 1,
 	ENETC_CMD_ID_UNREGISTER_LINK_NOTIF = 2,
-	ENETC_CMD_ID_GET_LINK_SPEED = 0
+	ENETC_CMD_ID_GET_LINK_SPEED = 0,
+	/* Get IP version command IDs (class ID 0xF0) */
+	ENETC_CMD_ID_GET_IP_MJ = 0,
+	ENETC_CMD_ID_GET_IP_MN = 1,
+	ENETC_CMD_ID_GET_IP_INT = 2,
+	ENETC_CMD_ID_GET_IP_MNT = 3,
+	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* IP_VER value returned when the version is not available */
+#define ENETC_IP_VER_NOT_AVAILABLE	0xFF
+
 enum mac_addr_status {
 	ENETC_INVALID_MAC_ADDR = 0x0,
 	ENETC_DUPLICATE_MAC_ADDR = 0X1,
@@ -274,6 +284,11 @@ struct enetc_msg_cmd_get_link_speed {
 	struct enetc_msg_cmd_header header;
 };
 
+/* Get IP version command message format (class ID 0xF0) */
+struct enetc_msg_cmd_get_ip_ver {
+	struct enetc_msg_cmd_header header;
+};
+
 struct enetc_msg_cmd_set_vlan_promisc {
 	struct enetc_msg_cmd_header header;
 	uint8_t op;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index d34b8019ea..4b84a90cc9 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -5,6 +5,7 @@
 #include <stdbool.h>
 #include <rte_kvargs.h>
 #include <rte_random.h>
+#include <rte_bus_pci.h>
 #include <rte_kvargs.h>
 #include <dpaax_iova_table.h>
 #include "enetc_logs.h"
@@ -434,6 +435,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_MAC_FILTER:
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
+		case ENETC_CLASS_ID_GET_IP_VER:
 			break;
 		default:
 			err = -EIO;
@@ -797,6 +799,138 @@ enetc4_vf_get_link_speed(struct rte_eth_dev *dev, struct enetc_psi_reply_msg *re
 	return err;
 }
 
+/*
+ * Get the NETC IP minor revision (IP_MN) from the PSI using the 'Get IP
+ * version' command class (class ID 0xF0, cmd_id 0x1). In the reply, the
+ * low 8 bits of the return code carry the minor revision and the upper 8
+ * bits carry the class code (0xF0). The PSI only implements the IP_MN
+ * command of this class; the major revision comes from the PCI revision.
+ */
+static int
+enetc4_vf_get_ip_minor_revision(struct rte_eth_dev *dev, uint8_t *ip_mn)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_swbd *msg;
+	uint32_t msg_size;
+	uint16_t mc;
+	uint8_t class_id;
+	int vsimsgsr;
+	int err = 0;
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_cmd_get_ip_ver),
+				ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg");
+		rte_free(msg);
+		return -ENOMEM;
+	}
+
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	msg->size = msg_size;
+
+	/* COOKIE is 0 so that the command is executed as blocking on PSI */
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_GET_IP_VER,
+			ENETC_CMD_ID_GET_IP_MN, 0, 0, 0);
+
+	/* send the command and wait */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error");
+		goto end;
+	}
+
+	/*
+	 * For the IP version command class the class-specific field is
+	 * reused to carry the 8-bit version value in the lower byte of the
+	 * return code, so parse the full low byte here instead of using the
+	 * generic reply parser.
+	 */
+	vsimsgsr = enetc4_rd(enetc_hw, ENETC4_VSIMSGSR);
+	mc = ENETC_SIMSGSR_GET_MC(vsimsgsr);
+	class_id = (mc >> 8) & 0xff;
+
+	if (class_id != ENETC_CLASS_ID_GET_IP_VER) {
+		ENETC_PMD_ERR("Wrong reply message 0x%x", class_id);
+		err = -EIO;
+		goto end;
+	}
+
+	*ip_mn = mc & 0xff;
+	if (*ip_mn == ENETC_IP_VER_NOT_AVAILABLE) {
+		ENETC_PMD_DEBUG("IP minor revision not available");
+		err = -ENOTSUP;
+	}
+
+end:
+	/* free memory no longer required */
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	return err;
+}
+
+/*
+ * Retrieve the NETC firmware/IP version and format it as
+ * "<major>.<minor>". The IP major revision is taken from the PCI
+ * revision ID register, and the IP minor revision is fetched from the
+ * PSI via VSI-PSI messaging ('Get IP version' command class). This
+ * mirrors the behaviour of the kernel PF/VF drivers, where the PSI only
+ * implements the IP_MN command of the 0xF0 class.
+ */
+static int
+enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_size)
+{
+	struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
+	uint8_t ip_mj = 0, ip_mn = 0;
+	int ret;
+
+	PMD_INIT_FUNC_TRACE();
+
+	if (fw_version == NULL)
+		return -EINVAL;
+
+	if (fw_size == 0)
+		return snprintf(NULL, 0, "%u.%u", ip_mj, ip_mn) + 1;
+
+	/* IP major revision is exposed through the PCI revision ID */
+	ret = rte_pci_read_config(pci_dev, &ip_mj, sizeof(ip_mj),
+			RTE_PCI_REVISION_ID);
+	if (ret != sizeof(ip_mj)) {
+		ENETC_PMD_ERR("Failed to read PCI revision ID");
+		return -EIO;
+	}
+
+	/* IP minor revision is fetched from the PSI via VSI-PSI messaging.
+	 * If the PSI reports the version as unavailable, fall back to a
+	 * partial version string so the caller still gets useful output.
+	 */
+	ret = enetc4_vf_get_ip_minor_revision(dev, &ip_mn);
+	if (ret && ret != -ENOTSUP) {
+		ENETC_PMD_ERR("Failed to get NETC minor revision");
+		return ret;
+	}
+
+	if (ret == -ENOTSUP)
+		ret = snprintf(fw_version, fw_size, "%u.unknown", ip_mj);
+	else
+		ret = snprintf(fw_version, fw_size, "%u.%u", ip_mj, ip_mn);
+	if (ret < 0)
+		return -EINVAL;
+
+	ret += 1; /* add trailing '\0' */
+	if ((size_t)ret > fw_size)
+		return ret;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1306,6 +1440,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.fw_version_get       = enetc4_vf_fw_version_get,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 06/14] net/enetc: support registers dump
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (4 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
                       ` (8 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the .get_reg operation for the ENETC4 PF and VF PMDs to dump
the device registers via rte_eth_dev_get_reg_info.

The VF dumps the registers reachable by a virtual station interface
(VSI): the station interface and per-ring BD ring registers. Port
registers are not accessible by a VF.

The PF additionally dumps the port registers, which are only
accessible by the physical station interface (PSI).

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |  1 +
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 13 +++++
 drivers/net/enetc/enetc4_ethdev.c      | 70 ++++++++++++++++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 62 +++++++++++++++++++++++
 6 files changed, 148 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index ea0e37896d..cd757be12f 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -60,6 +60,7 @@ Key functionality includes:
   requested. RSC requires the FCS to be stripped, so it cannot be combined
   with the KEEP_CRC Rx offload.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
+- Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index eaf474e7ba..6540a43909 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -17,6 +17,7 @@ RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
 FW version           = Y
+Registers dump       = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 5ac2bb4de0..deb3bc5d52 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -65,6 +65,7 @@ New Features
   * Added Receive Segment Coalesce (RSC / hardware LRO) support for ENETC4 PF and VF.
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
   * Added firmware version reporting for the ENETC4 VF.
+  * Added register dump support for ENETC4 PF and VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index b1428f1bca..73404308fe 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -397,6 +397,19 @@ enetc_bd_unused(struct enetc_bdr *bdr)
 	return bdr->bd_count + bdr->next_to_clean - bdr->next_to_use - 1;
 }
 
+/* Per-ring Tx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_txbdr_regs[] = {
+	ENETC_TBMR, ENETC_TBSR, ENETC_TBBAR0, ENETC_TBBAR1,
+	ENETC_TBCIR, ENETC_TBLENR,
+};
+
+/* Per-ring Rx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_rxbdr_regs[] = {
+	ENETC_RBMR, ENETC_RBSR, ENETC_RBBSR, ENETC_RBCIR,
+	ENETC_RBBAR0, ENETC_RBBAR1, ENETC_RBPIR, ENETC_RBLENR,
+};
+
+
 /* CBDR prototypes */
 int enetc4_setup_cbdr(struct rte_eth_dev *dev, struct enetc_hw *hw,
 			int bd_count, struct netc_cbdr *cbdr);
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 7f95171b6d..681bac3ecc 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1176,6 +1176,75 @@ enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 	return ptypes;
 }
 
+/* Station interface registers dumped by .get_reg */
+static const uint32_t enetc4_pf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR,
+};
+
+/* Port registers dumped by .get_reg (accessible only by the PF) */
+static const uint32_t enetc4_pf_port_regs[] = {
+	ENETC4_PMR, ENETC4_PSIPMMR, ENETC4_PMAR0, ENETC4_PMAR1,
+	ENETC4_PM_CMD_CFG(0), ENETC4_PM_MAXFRM(0), ENETC4_PM_IF_MODE(0),
+	ENETC4_PM_IF_STATUS(0),
+};
+
+/*
+ * Dump the PF-accessible registers: station interface, port and per-ring
+ * BD ring registers. When info->data is NULL, only the register count and
+ * width are reported so the caller can size its buffer.
+ */
+static int
+enetc4_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_pf_si_regs);
+	count += RTE_DIM(enetc4_pf_port_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_si_regs); i++)
+		*buf++ = enetc4_rd(enetc_hw, enetc4_pf_si_regs[i]);
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_port_regs); i++)
+		*buf++ = enetc4_port_rd(enetc_hw, enetc4_pf_port_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 /*
  * The set of PCI devices this driver supports
  */
@@ -1194,6 +1263,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.link_update          = enetc4_link_update,
 	.stats_get            = enetc4_stats_get,
 	.stats_reset          = enetc4_stats_reset,
+	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 4b84a90cc9..77bec0425a 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -931,6 +931,66 @@ enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_si
 	return 0;
 }
 
+/* VF station interface registers dumped by .get_reg */
+static const uint32_t enetc4_vf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR, ENETC4_SIMSIVR, ENETC4_VSIIER, ENETC4_VSIIDR,
+	ENETC4_VSIMSGSR, ENETC4_VSIMSGRR,
+};
+
+/*
+ * Dump the VF-accessible registers. Only station interface and per-ring
+ * BD ring registers are reachable by a VF; port registers are not.
+ * When info->data is NULL, only the register count and width are
+ * reported so the caller can size its buffer.
+ */
+static int
+enetc4_vf_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_vf_si_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_vf_si_regs); i++)
+		*buf++ = enetc_rd(enetc_hw, enetc4_vf_si_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1441,6 +1501,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
@@ -1461,6 +1522,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.mac_addr_set         = enetc4_vf_set_mac_addr,
 	.mac_addr_add	      = enetc4_vf_mac_addr_add,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 07/14] net/enetc: support ethtool ring parameters
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (5 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 06/14] net/enetc: support registers dump Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
                       ` (7 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The ethtool ring parameter query relies on the queue info ethdev
ops (.rxq_info_get/.txq_info_get) together with the descriptor
limits reported in dev_info. The PF already registered these ops,
but the VF ops tables did not, so ring parameter queries failed on
the VF.

Make enetc4_rxq_info_get() and enetc4_txq_info_get() non-static and
register them in both VF ops tables so ring parameters are reported
for both the PF and VF.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst | 1 +
 drivers/net/enetc/enetc.h              | 4 ++++
 drivers/net/enetc/enetc4_ethdev.c      | 4 ++--
 drivers/net/enetc/enetc4_vf.c          | 4 ++++
 4 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index deb3bc5d52..9e333edf4f 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -66,6 +66,7 @@ New Features
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
+  * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 73404308fe..367d350dde 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -339,6 +339,10 @@ int enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx);
 void enetc4_tx_queue_release(struct rte_eth_dev *dev, uint16_t qid);
 const uint32_t *enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 			size_t *no_of_elements);
+void enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_rxq_info *qinfo);
+void enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_txq_info *qinfo);
 
 /*
  * enetc4_vf function prototype
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 681bac3ecc..afee0b90a3 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1125,7 +1125,7 @@ enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx)
 	return 0;
 }
 
-static void
+void
 enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_rxq_info *qinfo)
 {
@@ -1139,7 +1139,7 @@ enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 	qinfo->conf.rx_drop_en = 0;
 }
 
-static void
+void
 enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_txq_info *qinfo)
 {
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 77bec0425a..90b7b99e9a 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1508,10 +1508,12 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
@@ -1537,10 +1539,12 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 08/14] net/enetc: refresh link speed on VF link-up interrupt
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (6 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 09/14] net/enetc: support stats reset for VF Gagandeep Singh
                       ` (6 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The interrupt-driven link-status path (enetc4_process_psi_msg) only
updated link_status when a PSI-to-VSI notification arrived; it never
re-queried the link speed from the PF.  As a result, after a link-down
followed by a link-up the cached link_speed showed the speed from the
previous session rather than the freshly negotiated one.

Fix this by:

1. Introducing enetc4_decode_link_speed() - a shared helper that maps
   a PF-to-VF speed status code to the corresponding RTE_ETH_SPEED_NUM_*
   / RTE_ETH_LINK_*_DUPLEX values, handling both the current and the
   legacy (vf_link_legacy) 4-bit message layout.

2. Calling enetc4_vf_get_link_speed() inside enetc4_process_psi_msg()
   on ENETC_LINK_UP so the negotiated speed is fetched from the PF and
   decoded immediately, before rte_eth_linkstatus_set() is called and
   the LSC callback is fired.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc4_vf.c          | 197 +++++++++++++------------
 2 files changed, 107 insertions(+), 91 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 9e333edf4f..eca0dc1dea 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -67,6 +67,7 @@ New Features
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
+  * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 90b7b99e9a..ede11a7b61 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -324,9 +324,99 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	reply_msg->status = status;
 }
 
+/* Forward declaration: defined later in this file */
+static int enetc4_vf_get_link_speed(struct rte_eth_dev *dev,
+				     struct enetc_psi_reply_msg *reply_msg);
+
+/*
+ * Decode a PF-to-VF link-speed status code into the link_speed and
+ * link_duplex fields of *link.  vf_link_legacy selects the older
+ * 4-bit code layout used by kernel PFs before v6.18.37.
+ */
+static void
+enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
+			 struct rte_eth_link *link)
+{
+	switch (status) {
+	case ENETC_SPEED_UNKNOWN:
+		ENETC_PMD_DEBUG("Speed unknown");
+		link->link_speed = RTE_ETH_SPEED_NUM_NONE;
+		break;
+	case ENETC_SPEED_10_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_10_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_100_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_100_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_1000:
+		link->link_speed = RTE_ETH_SPEED_NUM_1G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_2500:
+		link->link_speed = RTE_ETH_SPEED_NUM_2_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_5000:
+		link->link_speed = RTE_ETH_SPEED_NUM_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	default:
+		if (vf_link_legacy) {
+			/* Legacy PF-to-VF message layout (older kernel PF):
+			 * speeds above 5Gbps use fixed 4-bit class codes.
+			 */
+			switch (status) {
+			case ENETC_SPEED_LEGACY_10G:
+				link->link_speed = RTE_ETH_SPEED_NUM_10G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_25G:
+				link->link_speed = RTE_ETH_SPEED_NUM_25G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_50G:
+				link->link_speed = RTE_ETH_SPEED_NUM_50G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_100G:
+				link->link_speed = RTE_ETH_SPEED_NUM_100G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+				ENETC_PMD_DEBUG("Speed not supported");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			default:
+				ENETC_PMD_ERR("Unknown speed status");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			}
+			break;
+		}
+		/* Any status here is > ENETC_SPEED_5000.  Reverse the formula:
+		 *   SPEED = (status - ENETC_SPEED_5000) * 1000 + 5000  (in Mbps)
+		 */
+		link->link_speed = (status - ENETC_SPEED_5000) * 1000 + 5000;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	}
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
 	int ret = 0;
@@ -345,6 +435,20 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		case ENETC_LINK_UP:
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Re-query speed from PF so the cached value reflects
+			 * the current negotiated speed after link-up.
+			 */
+			rte_free(msg);
+			msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+			if (msg) {
+				if (!enetc4_vf_get_link_speed(eth_dev, msg) &&
+				    msg->class_id == ENETC_CLASS_ID_LINK_SPEED)
+					enetc4_decode_link_speed(msg->status,
+							hw->vf_link_legacy,
+							&link);
+			} else {
+				ENETC_PMD_WARN("Failed to alloc msg for speed query");
+			}
 			break;
 		case ENETC_LINK_DOWN:
 			ENETC_PMD_DEBUG("Link is down");
@@ -1048,97 +1152,8 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_SPEED) {
-		switch (reply_msg->status) {
-		case ENETC_SPEED_UNKNOWN:
-			ENETC_PMD_DEBUG("Speed unknown");
-			link.link_speed = RTE_ETH_SPEED_NUM_NONE;
-			break;
-		case ENETC_SPEED_10_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_10_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_100_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_1000:
-			link.link_speed = RTE_ETH_SPEED_NUM_1G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_2500:
-			link.link_speed = RTE_ETH_SPEED_NUM_2_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_5000:
-			link.link_speed = RTE_ETH_SPEED_NUM_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		default:
-			if (hw->vf_link_legacy) {
-				/* Legacy PF-to-VF message layout (older kernel
-				 * PF): speeds greater than 5Gbps are encoded
-				 * with fixed 4-bit class codes rather than the
-				 * formula below.
-				 */
-				switch (reply_msg->status) {
-				case ENETC_SPEED_LEGACY_10G:
-					link.link_speed = RTE_ETH_SPEED_NUM_10G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_25G:
-					link.link_speed = RTE_ETH_SPEED_NUM_25G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_50G:
-					link.link_speed = RTE_ETH_SPEED_NUM_50G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_100G:
-					link.link_speed = RTE_ETH_SPEED_NUM_100G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
-					ENETC_PMD_DEBUG("Speed not supported");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				default:
-					ENETC_PMD_ERR("Unknown speed status");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				}
-				break;
-			}
-
-			/* Any status reaching here is greater than
-			 * ENETC_SPEED_5000, as all values from 0x0 to
-			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
-			 * greater than 5Gbps are not enumerated and follow the
-			 * formula:
-			 *
-			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
-			 *
-			 * where link_speed is in Mbps. Reverse it here to get the
-			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
-			 *
-			 * The PF only reports speeds that map to a well-known
-			 * RTE_ETH_SPEED_NUM_* value, so the computed value is a
-			 * valid DPDK speed. If a future speed not yet defined in
-			 * DPDK needs to be supported, the corresponding
-			 * RTE_ETH_SPEED_NUM_* value must first be added upstream.
-			 */
-			link.link_speed = (reply_msg->status - ENETC_SPEED_5000)
-					  * 1000 + 5000;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		}
+		enetc4_decode_link_speed(reply_msg->status,
+					 hw->vf_link_legacy, &link);
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 09/14] net/enetc: support stats reset for VF
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (7 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
                       ` (5 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The NETC SI-level hardware counters (SIROCT0, SIRFRM0, SITOCT0,
SITFRM0, SITDFCR) are read-only for a VF and cannot be directly
zeroed. No VSI-PSI command class exists for stats reset, and using
FLR/soft-reset to clear counters is not reliable due to a known
hardware erratum on some NETC platforms.

Implement stats_reset for the enetc4 VF PMD using a software
snapshot/delta approach: on stats_reset, the current HW counter
values are captured as a baseline in a new per-device
enetc4_vf_stats_saved struct. stats_get then reports the delta
(current_hw_value - saved_baseline), so counters appear to start
from zero after each reset call. Per-ring software Rx error
accumulators (ierrors) are zeroed directly on reset.

Register the stats_reset callback in both VF ops tables
(enetc4_vf_ops and enetc4_vf_ops_no_vsi_m) so the feature is
available regardless of whether the VSI messaging path is enabled.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 17 +++++++
 drivers/net/enetc/enetc4_vf.c          | 61 +++++++++++++++++++++++---
 3 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index eca0dc1dea..b88e2825a6 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -68,6 +68,7 @@ New Features
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
+  * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 367d350dde..3590cf6639 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -105,6 +105,21 @@ struct enetc_bdr {
 	uint8_t rsc_enable;
 };
 
+/*
+ * Saved SI counter baseline for VF stats reset. Since the SI-level
+ * hardware counters (SIROCT0, SIRFRM0, SITOCT0, SITFRM0, SITDFCR)
+ * are read-only for a VF and cannot be directly zeroed, stats_reset
+ * captures the current counter values as a baseline. stats_get then
+ * reports the delta: current_hw_value - baseline.
+ */
+struct enetc4_vf_stats_saved {
+	uint64_t ipackets;
+	uint64_t opackets;
+	uint64_t ibytes;
+	uint64_t obytes;
+	uint64_t oerrors;
+};
+
 struct enetc_eth_hw {
 	struct rte_eth_dev *ndev;
 	struct enetc_hw hw;
@@ -124,6 +139,8 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	/* Baseline snapshot for VF stats reset (software delta approach). */
+	struct enetc4_vf_stats_saved vf_stats_saved;
 };
 
 /*
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index ede11a7b61..63ee9cb346 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -225,15 +225,64 @@ enetc4_vf_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
 	uint8_t i;
 
 	PMD_INIT_FUNC_TRACE();
-	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
-	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
-	stats->ibytes = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
-	stats->obytes = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
-	stats->oerrors = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/*
+	 * The SI-level counters are read-only for a VF; they cannot be
+	 * zeroed directly. Instead, stats_reset captures a baseline
+	 * snapshot, and stats_get reports the delta so that the reported
+	 * values appear to start from zero after each reset call.
+	 */
+	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0) -
+			  hw->vf_stats_saved.ipackets;
+	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0) -
+			  hw->vf_stats_saved.opackets;
+	stats->ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0) -
+			  hw->vf_stats_saved.ibytes;
+	stats->obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0) -
+			  hw->vf_stats_saved.obytes;
+	stats->oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR) -
+			  hw->vf_stats_saved.oerrors;
+
 	for (i = 0; i < dev->data->nb_rx_queues; i++) {
 		rx_ring = dev->data->rx_queues[i];
 		stats->ierrors += rx_ring->ierrors;
 	}
+
+	return 0;
+}
+
+/*
+ * Reset VF statistics by capturing a new baseline snapshot of the
+ * SI-level hardware counters. Because those counters are read-only
+ * for a VF (hardware erratum prevents reliable clear via FLR/soft
+ * reset too), the driver uses a software delta approach: every
+ * stats_get call reports current_hw_value - saved_baseline.
+ */
+static int
+enetc4_vf_stats_reset(struct rte_eth_dev *dev)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_bdr *rx_ring;
+	uint8_t i;
+
+	PMD_INIT_FUNC_TRACE();
+
+	/* Snapshot current HW SI counter values as the new zero baseline. */
+	hw->vf_stats_saved.ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
+	hw->vf_stats_saved.opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
+	hw->vf_stats_saved.ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
+	hw->vf_stats_saved.obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
+	hw->vf_stats_saved.oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/* Reset the per-ring software Rx error accumulators. */
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		rx_ring = dev->data->rx_queues[i];
+		if (rx_ring)
+			rx_ring->ierrors = 0;
+	}
+
 	return 0;
 }
 
@@ -1514,6 +1563,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
 	.get_reg              = enetc4_vf_get_regs,
@@ -1538,6 +1588,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 10/14] net/enetc4: add per-queue Rx interrupt support for VF
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (8 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 09/14] net/enetc: support stats reset for VF Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
                       ` (4 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add MSI-X per-queue Rx interrupt support to the ENETC4 VF PMD on the
cacheable (default) Rx path. This allows applications such as l3fwd-power
to block in epoll_wait when no traffic is present, reducing CPU utilization
to near zero.

MSI-X vector 0 is reserved for the PSI-to-VSI mailbox. Rx queue i is
mapped to MSI-X vector i + 1 via ENETC_SIMSIRRV. Per-ring coalescing is
enabled with ICPT=1 so the first arriving packet fires the interrupt
immediately. The SIRXIDR W1C detect bit is cleared before re-arming
ENETC_RBIER to prevent spurious interrupts after traffic stops.

The interrupt infrastructure (rte_intr_efd_enable +
rte_intr_vec_list_alloc) is set up before rte_intr_enable() so that
vfio-pci can wire each MSI-X vector to its eventfd when programming
the MSI-X table.

enetc4_dev_configure() is updated to trigger interrupt setup when
intr_conf.rxq is set (not only when intr_conf.lsc is set), as applications
like l3fwd-power set intr_conf.rxq without setting lsc.

Documentation is updated to describe the feature, list the kernel and
host setup steps, and provide an example l3fwd-power command for single
queue/core interrupt-driven operation.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             | 69 +++++++++++++++++++-
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc4_hw.h     |  2 +
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      |  9 +--
 drivers/net/enetc/enetc4_vf.c          | 88 +++++++++++++++++++++++++-
 7 files changed, 165 insertions(+), 6 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index cd757be12f..d8e323d614 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,10 @@ Key functionality includes:
   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.
+- Per-queue Rx interrupts on VFs (cacheable Rx path only), enabling
+  interrupt-driven receive with ``vfio-pci``. Applications set
+  ``intr_conf.rxq = 1`` in ``rte_eth_conf`` to activate this feature.
+  See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
@@ -95,7 +99,9 @@ The following dependencies are not part of DPDK and must be installed separately
 Driver compilation and testing
 ------------------------------
 
-Follow instructions available in the document :doc:`build_and_test` to launch **testpmd**.
+Follow instructions available in the document
+:ref:`compiling and testing a PMD for a NIC <pmd_build_and_test>`
+to launch **testpmd**.
 
 
 Driver Arguments (devargs)
@@ -156,3 +162,64 @@ PF/Common devargs
   Usage example::
 
     dpdk-testpmd -a 0000:00:00.0,nc=1 -- -i
+
+
+Rx Interrupt Mode (VF)
+----------------------
+
+The ENETC4 VF PMD supports per-queue MSI-X Rx interrupts on the cacheable
+(default) Rx path. This allows applications to block in ``epoll_wait``
+instead of busy-polling, reducing CPU utilization when traffic is absent.
+
+**MSI-X vector assignment**
+
+ENETC4 VF MSI-X vector 0 is reserved for the PSI-to-VSI mailbox interrupt
+(link status notifications). Rx queue ``i`` is mapped to vector ``i + 1``.
+The driver allocates all required eventfds before calling
+``rte_intr_enable()`` so that ``vfio-pci`` can wire each MSI-X vector to
+its eventfd when it programs the MSI-X table.
+
+**Kernel and driver requirements**
+
+- ``vfio-pci`` kernel module with no-IOMMU mode enabled (no SMMU required).
+- The non-cacheable memory mode (``nc=1`` devarg) does **not** support
+  Rx interrupts and returns ``-ENOTSUP`` from ``rx_queue_intr_enable``.
+
+**Host setup**
+
+.. code-block:: console
+
+   # Enable vfio-pci no-IOMMU mode (if SMMU is not available)
+   modprobe vfio enable_unsafe_noiommu_mode=1
+   modprobe vfio-pci
+
+   # Bind the VF to vfio-pci
+   echo vfio-pci > /sys/bus/pci/devices/<vf_pci_addr>/driver_override
+   echo <vf_pci_addr> > /sys/bus/pci/drivers_probe
+
+**Running l3fwd-power with a single queue and core**
+
+The ``l3fwd-power`` sample application demonstrates interrupt-driven Rx.
+It sets ``intr_conf.rxq = 1`` in ``rte_eth_conf``, which triggers the VF
+interrupt setup in the driver. The ``--vfio-intr=msix`` EAL flag instructs
+DPDK to use MSI-X eventfds for interrupt signalling.
+
+.. code-block:: console
+
+   ./dpdk-l3fwd-power -l 0-1 -n 1 --vfio-intr=msix \
+       -a <vf_pci_addr> -- \
+       -p 0x1 --config="(0,0,1)" --no-numa --interrupt-only
+
+Where:
+
+- ``-l 0-1`` assigns the main thread to core 0 and the forwarding lcore to
+  core 1.
+- ``-a <vf_pci_addr>`` specifies the VF PCI address (e.g. ``0000:01:00.1``).
+- ``--config="(0,0,1)"`` maps port 0, queue 0 to lcore 1.
+- ``--interrupt-only`` enables pure interrupt mode (no busy-poll fallback).
+
+With no incoming traffic the forwarding lcore sleeps in ``epoll_wait``;
+CPU utilization drops to near zero. On the first arriving packet the MSI-X
+interrupt fires, the lcore wakes, drains the ring, disables the interrupt,
+processes the burst, then re-enables and re-arms the interrupt before
+returning to sleep.
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 6540a43909..005c64a3ca 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -5,6 +5,7 @@
 ;
 [Features]
 Link status event    = Y
+Rx interrupt         = Y
 Speed capabilities   = Y
 Link status          = Y
 LRO                  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index b88e2825a6..9b39debc1d 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -69,6 +69,7 @@ New Features
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
+  * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 8ad8f169d2..9543e982a2 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -250,6 +250,8 @@ struct enetc_rx_bd_ext {
 #define ENETC4_VSIIDR            0xA08
 #define ENETC4_VSIIER_MRIE       BIT(9)
 #define ENETC4_SI_INT_IDX        0
+/* MSI-X vector base for per-Rx-queue interrupts; vector 0 is the mailbox. */
+#define ENETC4_VF_RX_VEC_BASE    1
 
 /* VSI Registers */
 #define ENETC4_VSIMSGSR  0x204   /* RO */
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 3590cf6639..903829323e 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -135,6 +135,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index afee0b90a3..d987f1288c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -839,7 +839,8 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		return 0;
 
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0)
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0)
 			enetc4_vf_dev_intr(dev, false);
 		ret = enetc4_vf_dev_stop(dev);
 	} else {
@@ -961,12 +962,12 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	if (hw->device_id != ENETC4_DEV_ID_VF)
 		enetc4_port_wr(enetc_hw, ENETC4_PARCSCR, checksum);
 
-	/* Enable interrupts */
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0) {
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0) {
 			ret = enetc4_vf_dev_intr(dev, true);
 			if (ret)
-				ENETC_PMD_WARN("Failed to setup link interrupts");
+				ENETC_PMD_WARN("Failed to setup VF interrupts: %d", ret);
 		}
 	}
 
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 63ee9cb346..8a57a8cad4 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1555,6 +1555,52 @@ static const struct rte_pci_id pci_vf_id_enetc4_map[] = {
 	{ .vendor_id = 0, /* sentinel */ },
 };
 
+static int
+enetc4_vf_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t vec;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return -ENOTSUP;
+
+	vec = queue_id + ENETC4_VF_RX_VEC_BASE;
+
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), vec);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR1, 0);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR0,
+			ENETC4_RBICR0_ICEN | ENETC4_RBICR0_ICPT(1));
+	enetc_wr(enetc_hw, ENETC_SIRXIDR, BIT(queue_id));
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, ENETC_RBIER_RXTIE);
+
+	return 0;
+}
+
+static int
+enetc4_vf_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return 0;
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, 0);
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), 0);
+
+	return 0;
+}
+
 /* Features supported by this driver */
 /* ops table used when VSI messaging is disabled */
 static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
@@ -1568,7 +1614,15 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.fw_version_get       = enetc4_vf_fw_version_get,
 	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
+	.mac_addr_set         = enetc4_vf_set_mac_addr,
+	.mac_addr_add	      = enetc4_vf_mac_addr_add,
+	.promiscuous_enable   = enetc4_vf_promisc_enable,
+	.promiscuous_disable  = enetc4_vf_promisc_disable,
+	.allmulticast_enable  = enetc4_vf_multicast_enable,
+	.allmulticast_disable = enetc4_vf_multicast_disable,
 	.link_update	      = enetc4_vf_link_update_dummy,
+	.vlan_filter_set      = enetc4_vf_vlan_filter_set,
+	.vlan_offload_set     = enetc4_vf_vlan_offload_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
@@ -1606,6 +1660,8 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
 	.rxq_info_get         = enetc4_rxq_info_get,
+	.rx_queue_intr_enable  = enetc4_vf_rx_queue_intr_enable,
+	.rx_queue_intr_disable = enetc4_vf_rx_queue_intr_disable,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
@@ -1851,6 +1907,35 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		/* Vector index 0 */
 		enetc_wr(enetc_hw, ENETC4_SIMSIVR, ENETC4_SI_INT_IDX);
 
+		if (rte_intr_cap_multiple(intr_handle) &&
+		    eth_dev->data->nb_rx_queues > 0) {
+			uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+			uint16_t i;
+
+			ret = rte_intr_efd_enable(intr_handle,
+					nb_rx + ENETC4_VF_RX_VEC_BASE);
+			if (ret) {
+				ENETC_PMD_WARN("Failed to enable per-queue Rx eventfds: %d",
+					       ret);
+				ret = 0;
+				hw->rxq_intr_en = 0;
+			} else {
+				ret = rte_intr_vec_list_alloc(intr_handle,
+						"enetc4_vf_rx_intr", nb_rx);
+				if (ret) {
+					ENETC_PMD_WARN("Failed to alloc intr vec list: %d",
+						       ret);
+					rte_intr_efd_disable(intr_handle);
+					hw->rxq_intr_en = 0;
+				} else {
+					for (i = 0; i < nb_rx; i++)
+						rte_intr_vec_list_index_set(intr_handle, i,
+							i + ENETC4_VF_RX_VEC_BASE);
+					hw->rxq_intr_en = 1;
+				}
+			}
+		}
+
 		/* enable uio/vfio intr/eventfd mapping */
 		ret = rte_intr_enable(intr_handle);
 		if (ret) {
@@ -1874,6 +1959,7 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		ENETC_PMD_WARN("Failed to un-register link notification %d", ret);
 disable:
 	enetc_vf_enable_mr_int(enetc_hw, false);
+	hw->rxq_intr_en = 0;
 	ret = rte_intr_disable(intr_handle);
 	if (ret)
 		ENETC_PMD_WARN("Failed to disable INTR %d", ret);
@@ -1893,7 +1979,7 @@ static struct rte_pci_driver rte_enetc4_vf_pmd = {
 
 RTE_PMD_REGISTER_PCI(net_enetc4_vf, rte_enetc4_vf_pmd);
 RTE_PMD_REGISTER_PCI_TABLE(net_enetc4_vf, pci_vf_id_enetc4_map);
-RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic");
+RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic | vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 11/14] net/enetc4: add SI-based port VLAN insertion and removal
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (9 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
                       ` (3 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement hardware VLAN tag insertion on Tx and removal on Rx
via the per-SI VLAN isolation (pvid) mechanism on ENETC4.

On a PF, the driver writes directly to PSIaVLANR(0) (VID + enable
bit) and PSIaCFGR0(0) (SIVIE for Tx insertion, VTE for Rx removal,
SIVC_CVLAN to allow C-VLAN 0x8100 TPID).

On a privileged VF, the request is forwarded to the kernel PF via
the VSI-PSI mailbox using a new command class 0x24 (SI VLAN
isolation, cmd ID 0x1). The kernel PF handler programs the same
registers on behalf of the VF's station interface. The class 0x24
reply is added to the pass-through list in enetc4_msg_vsi_send() so
it is handled correctly alongside the existing command classes.

New additions:
- ENETC4_PSIVLANR(a) and ENETC4_PSICFGR0(a) register macros with
  field definitions in enetc4_hw.h
- ENETC_CLASS_ID_SI_VLAN_ISO (0x24) and ENETC_CMD_ID_SET_SI_VLAN_ISO
  enum values, plus struct enetc_msg_si_vlan_iso message layout and
  bit definitions in enetc.h
- enetc4_vlan_pvid_set() for PF in enetc4_ethdev.c
- enetc4_vf_vlan_pvid_set() for VF in enetc4_vf.c, registered as
  .vlan_pvid_set in both enetc4_ops and enetc4_vf_ops

The feature is activated in testpmd with:
  tx_vlan set pvid <port_id> <vlan_id> on|off

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 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     | 14 +++++
 drivers/net/enetc/enetc.h              | 23 +++++++
 drivers/net/enetc/enetc4_ethdev.c      | 39 ++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 85 ++++++++++++++++++++++++++
 7 files changed, 167 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index d8e323d614..bb0628ccf6 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -65,6 +65,10 @@ Key functionality includes:
   See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
+- SI-based port VLAN (pvid): Hardware VLAN tag insertion on Tx and removal on Rx, configured
+  via ``rte_eth_dev_set_vlan_pvid``. On a PF the registers are written directly; on a privileged
+  VF the request is forwarded to the kernel PF through the VSI-PSI mailbox (class 0x24).
+  Use the testpmd command ``tx_vlan set pvid <port_id> <vlan_id> on|off`` to enable or disable.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 005c64a3ca..01b0dc5b80 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 9b39debc1d..e6d96b2a50 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -70,6 +70,7 @@ New Features
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
+  * Added SI-based port VLAN insertion (Tx) and removal (Rx) 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 9543e982a2..a9552f21f4 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -277,6 +277,20 @@ struct enetc_rx_bd_ext {
 #define ENETC4_SICTR0		0x18
 #define ENETC4_SICTR1		0x1c
 
+/* PSI SI VLAN register: per-SI VLAN tag for insertion/removal */
+#define ENETC4_PSIVLANR(a)		((a) * 0x80 + 0x2008)
+#define ENETC4_PSIVLANR_E		BIT(31)  /* enable SI VLAN processing */
+#define ENETC4_PSIVLANR_VTEA		BIT(30)  /* 0=strip tag, 1=zero VID */
+#define ENETC4_PSIVLANR_PCP(v)		(((uint32_t)(v) & 0x7) << 13)
+#define ENETC4_PSIVLANR_DEI		BIT(12)
+#define ENETC4_PSIVLANR_VID(v)		((uint32_t)(v) & 0xfff)
+
+/* PSI SI configuration register 0 */
+#define ENETC4_PSICFGR0(a)		((a) * 0x80 + 0x2010)
+#define ENETC4_PSICFGR0_SIVC_CVLAN	BIT(24)  /* allow C-VLAN 0x8100 */
+#define ENETC4_PSICFGR0_SIVIE		BIT(14)  /* SI VLAN insertion enable */
+#define ENETC4_PSICFGR0_VTE		BIT(12)  /* SI VLAN removal enable */
+
 /* general register accessors */
 #define enetc4_rd_reg(reg)	rte_read32((void *)(reg))
 #define enetc4_wr_reg(reg, val)  rte_write32((val), (void *)(reg))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 903829323e..72e43572f7 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -187,6 +187,8 @@ struct enetc_eth_adapter {
 enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
+	/* Configure SI VLAN isolation (insert / remove) */
+	ENETC_CLASS_ID_SI_VLAN_ISO = 0x24,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
 	ENETC_CLASS_ID_LINK_SPEED = 0x81,
 	ENETC_CLASS_ID_GET_IP_VER = 0xF0
@@ -212,6 +214,11 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* Command IDs for class ID 0x24 (SI VLAN isolation) */
+enum enetc_msg_si_vlan_cmd_id {
+	ENETC_CMD_ID_SET_SI_VLAN_ISO = 1,
+};
+
 /* IP_VER value returned when the version is not available */
 #define ENETC_IP_VER_NOT_AVAILABLE	0xFF
 
@@ -323,6 +330,22 @@ struct enetc_msg_vlan_exact_filter {
 	uint8_t reserved2;
 };
 
+/* VSI-PSI SI VLAN isolation message (class 0x24, cmd 0x1) */
+struct enetc_msg_si_vlan_iso {
+	struct enetc_msg_cmd_header header;
+	uint8_t ctrl;          /* E[7], VTEA[6], TPID[1:0] */
+	uint8_t pcp_dei_vid_hi; /* PCP[7:5], DEI[4], VID[11:8] */
+	uint8_t vid_lo;        /* VID[7:0] */
+	uint8_t reserved_0;
+	uint8_t flags;         /* SVIE[2], VTE[1] */
+	uint8_t reserved_1[3];
+};
+
+#define ENETC_SI_VLAN_ISO_E	BIT(7)  /* ctrl: enable SI VLAN */
+#define ENETC_SI_VLAN_ISO_VTEA	BIT(6)  /* ctrl: 0=strip, 1=zero VID */
+#define ENETC_SI_VLAN_ISO_SVIE	BIT(2)  /* flags: Tx insertion enable */
+#define ENETC_SI_VLAN_ISO_VTE	BIT(1)  /* flags: Rx removal enable */
+
 struct enetc_psi_reply_msg {
 	uint8_t class_id;
 	uint8_t status;
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index d987f1288c..ae40cc69e0 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -872,6 +872,44 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 	return ret;
 }
 
+/* Configure SI-based VLAN insertion (Tx) and removal (Rx) for the PF. */
+static int
+enetc4_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t psivlanr, psicfgr0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	psicfgr0 = enetc4_port_rd(enetc_hw, ENETC4_PSICFGR0(0));
+
+	if (on) {
+		/* Build the VLAN register: enable bit + VID (PCP/DEI left 0) */
+		psivlanr = (uint32_t)ENETC4_PSIVLANR_E |
+			   ENETC4_PSIVLANR_VID(vlan_id);
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), psivlanr);
+
+		/* Allow C-VLAN TPID, enable Tx insertion and Rx removal */
+		psicfgr0 |= ENETC4_PSICFGR0_SIVC_CVLAN |
+			    ENETC4_PSICFGR0_SIVIE |
+			    ENETC4_PSICFGR0_VTE;
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+	} else {
+		/* Clear Tx insertion, Rx removal and C-VLAN allow bits */
+		psicfgr0 &= ~(ENETC4_PSICFGR0_SIVC_CVLAN |
+			      ENETC4_PSICFGR0_SIVIE |
+			      ENETC4_PSICFGR0_VTE);
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+
+		/* Clear the VLAN register (disable SI VLAN processing) */
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), 0);
+	}
+
+	return 0;
+}
+
 static int
 enetc4_promiscuous_enable(struct rte_eth_dev *dev)
 {
@@ -1267,6 +1305,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
+	.vlan_pvid_set        = enetc4_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 8a57a8cad4..fc5ea4d609 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -589,8 +589,11 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
 		case ENETC_CLASS_ID_GET_IP_VER:
+		case ENETC_CLASS_ID_SI_VLAN_ISO:
 			break;
 		default:
+			ENETC_PMD_ERR("Unexpected reply class_id=0x%02x vsimsgsr=0x%08x",
+				      class_id, vsimsgsr);
 			err = -EIO;
 		}
 	}
@@ -1493,6 +1496,87 @@ static int enetc4_vf_vlan_offload_set(struct rte_eth_dev *dev, int mask __rte_un
 	return 0;
 }
 
+/* Configure SI-based VLAN insertion/removal via VSI-PSI mailbox (class 0x24). */
+static int
+enetc4_vf_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_si_vlan_iso *cmd;
+	struct enetc_msg_swbd *msg;
+	struct enetc_psi_reply_msg *reply_msg;
+	uint32_t msg_size;
+	int err = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	reply_msg = rte_zmalloc(NULL, sizeof(*reply_msg), RTE_CACHE_LINE_SIZE);
+	if (!reply_msg) {
+		ENETC_PMD_ERR("Failed to alloc memory for reply_msg");
+		return -ENOMEM;
+	}
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_si_vlan_iso),
+			     ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg body");
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	if (msg->dma == RTE_BAD_IOVA) {
+		ENETC_PMD_ERR("Failed to get IOVA for SI VLAN isolation msg body");
+		rte_free(msg->vaddr);
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->size = msg_size;
+
+	cmd = (struct enetc_msg_si_vlan_iso *)msg->vaddr;
+
+	if (on) {
+		cmd->ctrl = (uint8_t)ENETC_SI_VLAN_ISO_E;
+		cmd->pcp_dei_vid_hi = (uint8_t)((vlan_id >> 8) & 0x0f);
+		cmd->vid_lo = (uint8_t)(vlan_id & 0xff);
+		cmd->flags = (uint8_t)(ENETC_SI_VLAN_ISO_SVIE | ENETC_SI_VLAN_ISO_VTE);
+	}
+	/* on=0: all fields zero (rte_zmalloc cleared the buffer) */
+
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_SI_VLAN_ISO,
+				     ENETC_CMD_ID_SET_SI_VLAN_ISO, 0, 0, 0);
+
+	/* send the command and wait for PSI reply */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error for SI VLAN isolation");
+		goto end;
+	}
+
+	enetc4_msg_vsi_reply_msg(enetc_hw, reply_msg);
+
+	if (reply_msg->class_id != ENETC_MSG_CLASS_ID_CMD_SUCCESS) {
+		ENETC_PMD_ERR("SI VLAN isolation command failed: class_id=0x%x status=0x%x",
+			      reply_msg->class_id, reply_msg->status);
+		err = -EINVAL;
+	}
+
+end:
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	rte_free(reply_msg);
+	return err;
+}
+
 static int
 enetc4_vf_mtu_set(struct rte_eth_dev *dev __rte_unused, uint16_t mtu __rte_unused)
 {
@@ -1623,6 +1707,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.vlan_filter_set      = enetc4_vf_vlan_filter_set,
 	.vlan_offload_set     = enetc4_vf_vlan_offload_set,
+	.vlan_pvid_set        = enetc4_vf_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 12/14] net/enetc4: update VF link status to bitmask encoding
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (10 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
                       ` (2 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF link status notification code was previously a two-value
enum (UP=0x0, DOWN=0x1). Change this to a bitmask so future bits can
carry additional state alongside the link up/down indication:

  ENETC_LINK_DOWN  BIT(0)  -- set when link is down

Link up is now encoded as the DOWN bit being clear, which keeps the
wire value for link-down identical (0x1) and ensures backward
compatibility with older kernel PFs.

Changes:
- enetc.h: replace enum link_status with a #define bitmask;
  drop ENETC_LINK_UP (no longer a named constant).
- enetc4_vf.c enetc4_process_psi_msg(): replace switch/case on
  ENETC_LINK_UP/DOWN with bitmask decode.
- enetc4_vf.c enetc4_vf_link_update(): same bitmask decode.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              |  8 ++++----
 drivers/net/enetc/enetc4_vf.c          | 27 +++++++-------------------
 3 files changed, 12 insertions(+), 24 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index e6d96b2a50..5323f4fea3 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -71,6 +71,7 @@ New Features
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
+  * Updated ENETC4 VF link status reporting to use bitmask encoding.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 72e43572f7..40bad56341 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -236,10 +236,10 @@ enum vlan_status {
 	ENETC_VLAN_NO_RESOURCE = 0x3
 };
 
-enum link_status {
-	ENETC_LINK_UP = 0x0,
-	ENETC_LINK_DOWN = 0x1
-};
+/* Link status bitmask in PF-to-VF mailbox notification.
+ * Link up is encoded as the DOWN bit being clear.
+ */
+#define ENETC_LINK_DOWN  (1u << 0)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index fc5ea4d609..1b93835dd7 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -480,8 +480,10 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 	enetc4_msg_get_psi_msg(enetc_hw, msg);
 
 	if (msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (msg->status) {
-		case ENETC_LINK_UP:
+		if (msg->status & ENETC_LINK_DOWN) {
+			ENETC_PMD_DEBUG("Link is down");
+			link.link_status = RTE_ETH_LINK_DOWN;
+		} else {
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
 			/* Re-query speed from PF so the cached value reflects
@@ -498,14 +500,6 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 			} else {
 				ENETC_PMD_WARN("Failed to alloc msg for speed query");
 			}
-			break;
-		case ENETC_LINK_DOWN:
-			ENETC_PMD_DEBUG("Link is down");
-			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status 0x%x", msg->status);
-			break;
 		}
 		ret = rte_eth_linkstatus_set(eth_dev, &link);
 		if (!ret)
@@ -1180,17 +1174,10 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (reply_msg->status) {
-		case ENETC_LINK_UP:
-			link.link_status = RTE_ETH_LINK_UP;
-			break;
-		case ENETC_LINK_DOWN:
+		if (reply_msg->status & ENETC_LINK_DOWN)
 			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status");
-			break;
-		}
+		else
+			link.link_status = RTE_ETH_LINK_UP;
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (11 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07  7:02     ` [PATCH v3 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

When the PF negotiates TX PAUSE on the port it signals this to the VF
via BIT(1) of the PF-to-VF link status mailbox message. The VF PMD must
respond by setting RBMR_CM (BIT(4)) on all active RX rings so the MAC
emits PAUSE frames on ingress pressure.

Add ENETC_RBMR_CM register definition, ENETC_LINK_TX_PAUSE bitmask,
and tx_pause_active state flag. Add enetc4_vf_set_congestion_mode() to
update all active RX rings and persist the state for rings started
later. Hook it into both the interrupt and poll link-update paths, and
apply the saved state in rx_queue_setup() and rx_queue_start().

RX PAUSE (honoring received PAUSE frames) is handled at the MAC level
by the PF and requires no VF PMD changes.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  1 +
 drivers/net/enetc/enetc.h              |  9 +++-
 drivers/net/enetc/enetc4_ethdev.c      | 13 +++++-
 drivers/net/enetc/enetc4_vf.c          | 59 ++++++++++++++++++++++++--
 6 files changed, 78 insertions(+), 6 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 01b0dc5b80..1f599dace7 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+Flow control         = Y
 VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 5323f4fea3..d69888aafb 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -72,6 +72,7 @@ New Features
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
+  * Added TX PAUSE support for the ENETC4 VF via RX congestion mode.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 6e96562850..33d075fe59 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -51,6 +51,7 @@ enum enetc_bdr_type {TX, RX};
 							+ (off))
 /* RX BDR reg offsets */
 #define ENETC_RBMR		0x0 /* RX BDR mode register*/
+#define ENETC_RBMR_CM		BIT(4)  /* congestion mode: assert congestion to emit TX PAUSE */
 #define ENETC_RBMR_EN		BIT(31)
 
 #define ENETC_BMR_RESET		0x0 /* BDR reset*/
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 40bad56341..8aaf8a7eb1 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -140,6 +140,10 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	/* 1 = TX PAUSE negotiated on port; VF RX rings must have RBMR_CM set.
+	 * Updated from the PF-to-VF link status mailbox message (BIT(1)).
+	 */
+	uint8_t tx_pause_active;
 	/* Baseline snapshot for VF stats reset (software delta approach). */
 	struct enetc4_vf_stats_saved vf_stats_saved;
 };
@@ -238,8 +242,11 @@ enum vlan_status {
 
 /* Link status bitmask in PF-to-VF mailbox notification.
  * Link up is encoded as the DOWN bit being clear.
+ * TX_PAUSE is set when the port has negotiated TX PAUSE; VF must enable
+ * congestion mode (ENETC_RBMR_CM) on its RX rings accordingly.
  */
-#define ENETC_LINK_DOWN  (1u << 0)
+#define ENETC_LINK_DOWN      (1u << 0)
+#define ENETC_LINK_TX_PAUSE  (1u << 1)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index ae40cc69e0..2355522515 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -712,8 +712,12 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	if (!rx_conf->rx_deferred_start) {
-		/* enable ring */
+		/* Enable ring; apply congestion mode if TX PAUSE is already active. */
 		rx_enable |= ENETC_RBMR_EN;
+		if (adapter->hw.tx_pause_active)
+			rx_enable |= ENETC_RBMR_CM;
+		else
+			rx_enable &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
@@ -1089,7 +1093,12 @@ enetc4_rx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->rx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		rx_data = enetc4_rxbdr_rd(&priv->hw.hw, rx_ring->index,
 					 ENETC_RBMR);
-		rx_data = rx_data | ENETC_RBMR_EN;
+		rx_data |= ENETC_RBMR_EN;
+		/* Restore congestion mode if TX PAUSE is active. */
+		if (priv->hw.tx_pause_active)
+			rx_data |= ENETC_RBMR_CM;
+		else
+			rx_data &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&priv->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_data);
 		dev->data->rx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 1b93835dd7..c68bc8521b 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -461,6 +461,37 @@ enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
 	}
 }
 
+/*
+ * Set or clear ENETC_RBMR_CM (congestion mode) on all active VF RX rings.
+ * When set, the ring signals congestion to the MAC, causing it to emit TX
+ * PAUSE frames on ingress pressure. hw->tx_pause_active is updated so rings
+ * started later inherit the correct state.
+ */
+static void
+enetc4_vf_set_congestion_mode(struct rte_eth_dev *eth_dev, bool enable)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+	uint16_t i;
+	uint32_t rbmr;
+
+	hw->tx_pause_active = enable ? 1 : 0;
+
+	for (i = 0; i < nb_rx; i++) {
+		rbmr = enetc4_rxbdr_rd(enetc_hw, i, ENETC_RBMR);
+		if (enable)
+			rbmr |= ENETC_RBMR_CM;
+		else
+			rbmr &= ~(uint32_t)ENETC_RBMR_CM;
+		enetc4_rxbdr_wr(enetc_hw, i, ENETC_RBMR, rbmr);
+	}
+
+	ENETC_PMD_DEBUG("VF congestion mode %s on %u RX rings",
+			enable ? "enabled" : "disabled", nb_rx);
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
@@ -468,6 +499,7 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
+	bool tx_pause;
 	int ret = 0;
 
 	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
@@ -483,9 +515,24 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		if (msg->status & ENETC_LINK_DOWN) {
 			ENETC_PMD_DEBUG("Link is down");
 			link.link_status = RTE_ETH_LINK_DOWN;
+			/* Clear congestion mode on link-down so VF rings do not
+			 * assert congestion while the port is offline.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, false);
 		} else {
-			ENETC_PMD_DEBUG("Link is up");
+			/* BIT(1) is set when the port has negotiated TX PAUSE.
+			 * Legacy PF does not set this bit so tx_pause stays false.
+			 */
+			tx_pause = !!(msg->status & ENETC_LINK_TX_PAUSE);
+			ENETC_PMD_DEBUG("Link is up, tx_pause=%d", tx_pause);
 			link.link_status = RTE_ETH_LINK_UP;
+
+			/* Apply congestion mode before raising the carrier so
+			 * the VF rings are ready to emit PAUSE before traffic
+			 * starts flowing.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, tx_pause);
+
 			/* Re-query speed from PF so the cached value reflects
 			 * the current negotiated speed after link-up.
 			 */
@@ -1174,10 +1221,16 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		if (reply_msg->status & ENETC_LINK_DOWN)
+		if (reply_msg->status & ENETC_LINK_DOWN) {
 			link.link_status = RTE_ETH_LINK_DOWN;
-		else
+			/* Link is down: disable congestion mode on all RX rings. */
+			enetc4_vf_set_congestion_mode(dev, false);
+		} else {
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Restore congestion mode from the TX PAUSE bit. */
+			enetc4_vf_set_congestion_mode(dev,
+				!!(reply_msg->status & ENETC_LINK_TX_PAUSE));
+		}
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v3 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (12 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
@ 2026-08-07  7:02     ` Gagandeep Singh
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07  7:02 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add enetc4_txq_wrr devarg to configure per-ring WRR weights in the
NETC LEAF-level Tx scheduler (TBaMR register, bits [6:4]).

The NETC Tx scheduler has three levels:
  - ROOT (port/TC): strict priority + CBS (PF/port space)
  - MID  (SI/VSI):  WBFS shaping (PF space)
  - LEAF (Tx BDR):  strict priority + frame-based WRR (VF/SI space)

TBaMR is in the VF own SI space, so no Linux PF involvement is
needed for PRIO or WRR configuration.

Changes:
- enetc_hw.h: add ENETC_TBMR_WRR_MASK, ENETC_TBMR_WRR(n) macros for
  TBaMR bits [6:4], and ENETC_TBMR_PRIO_MASK for bits [2:0]
- enetc.h: add txq_wrr pointer to enetc_eth_hw struct
- enetc4_ethdev.c: add parse_txq_wrr() and wire ENETC4_TXQ_WRR devarg
  through enetc4_get_devargs() and enetc4_dev_configure(); apply WRR
  bits in enetc4_tx_queue_setup() and enetc4_tx_queue_start()

Usage:
  # strict priority: ring 0 highest
  -a 0002:00:12.0,enetc4_txq_prior="3|2|1"

  # WRR 2:4:1 on same-priority rings
  -a 0002:00:12.0,enetc4_txq_prior="1|1|1",enetc4_txq_wrr="2|4|1"

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  5 ++
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      | 97 ++++++++++++++++++++------
 4 files changed, 82 insertions(+), 22 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index d69888aafb..3ea34523fe 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -73,6 +73,7 @@ New Features
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
   * Added TX PAUSE support for the ENETC4 VF via RX congestion mode.
+  * Added WRR Tx scheduler devarg (``enetc4_txq_wrr``) for ENETC4 VF ring weights.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 33d075fe59..5a71f01db9 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -86,6 +86,11 @@ enum enetc_bdr_type {TX, RX};
 
 #define ENETC_RTBLENR_LEN(n)		((n) & ~0x7)
 #define ENETC_TBMR_EN			BIT(31)
+/* TBaMR[WRR] bits [6:4]: weight for same-priority ring arbitration (0=1x .. 7=8x). */
+#define ENETC_TBMR_WRR_MASK		GENMASK(6, 4)
+#define ENETC_TBMR_WRR(n)		((((n) - 1) & 0x7) << 4)
+/* TBaMR[PRIO] bits [2:0]: strict priority (0=lowest, 7=highest). */
+#define ENETC_TBMR_PRIO_MASK		GENMASK(2, 0)
 
 /* Port regs, offset: 1_0000h */
 #define ENETC_PORT_BASE			0x10000
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 8aaf8a7eb1..e0943d093a 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -134,6 +134,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_timeout; /* VSI-PSI message wait timeout (iterations) */
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
+	uint32_t *txq_wrr;    /* per-queue TX WRR weight pre-shifted for TBMR[WRR] */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
 	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 2355522515..e490ecaf4c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -28,22 +28,26 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
+#define ENETC4_TXQ_WRR		"enetc4_txq_wrr"
 #define ENETC4_NC_MEMORY	"nc"
 
+
 static int
 parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 {
 	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
 	struct enetc_eth_hw *hw =
-		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
-	char *input_str = strdup(value);
+				ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
 	char *str;
 	uint32_t i = 0;
 
+	input_str = strdup(value);
 	if (!input_str)
-		return -ENOMEM;
+		return -1;
 
-	hw->txq_prior = calloc(hw->max_tx_queues, sizeof(uint32_t));
+	rte_free(hw->txq_prior);
+	hw->txq_prior = rte_zmalloc(NULL, hw->max_tx_queues * sizeof(uint32_t), 0);
 	if (!hw->txq_prior) {
 		free(input_str);
 		return -ENOMEM;
@@ -51,7 +55,46 @@ parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 
 	str = strtok(input_str, "|");
 	while (str != NULL && i < hw->max_tx_queues) {
-		hw->txq_prior[i++] = (uint32_t)atoi(str);
+		hw->txq_prior[i++] = atoi(str) & ENETC_TBMR_PRIO_MASK;
+		str = strtok(NULL, "|");
+	}
+
+	free(input_str);
+	return 0;
+}
+
+/* Parse enetc4_txq_wrr="w0|w1|..." devarg; weight 1..8 per ring. */
+static int parse_txq_wrr(const char *key __rte_unused, const char *value,
+			  void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
+	char *str;
+	uint32_t i = 0;
+	int w;
+
+	input_str = strdup(value);
+	if (!input_str)
+		return -1;
+
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = rte_zmalloc(NULL,
+			hw->max_tx_queues * sizeof(uint32_t), 0);
+	if (!hw->txq_wrr) {
+		free(input_str);
+		return -1;
+	}
+
+	str = strtok(input_str, "|");
+	while (str != NULL && i < hw->max_tx_queues) {
+		w = atoi(str);
+		if (w < 1)
+			w = 1;
+		if (w > 8)
+			w = 8;
+		hw->txq_wrr[i++] = ENETC_TBMR_WRR(w);
 		str = strtok(NULL, "|");
 	}
 
@@ -97,6 +140,13 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 			return 0;
 		}
 	}
+	if (!strcmp(key, ENETC4_TXQ_WRR)) {
+		if (rte_kvargs_process(kvlist, key,
+				       parse_txq_wrr, (void *)dev) < 0) {
+			rte_kvargs_free(kvlist);
+			return 0;
+		}
+	}
 	if (!strcmp(key, ENETC4_NC_MEMORY)) {
 		if (rte_kvargs_process(kvlist, key,
 				       parse_nc, (void *)dev) < 0) {
@@ -109,19 +159,6 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 	return 0;
 }
 
-/* Supported Rx offloads */
-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_SCATTER;
-
-/* Supported Tx offloads */
-static uint64_t dev_tx_offloads_sup =
-	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
-	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
 
 static int
 enetc4_dev_start(struct rte_eth_dev *dev)
@@ -458,7 +495,9 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 
 		/* apply TX queue priority if configured */
 		if (priv->hw.txq_prior)
-			tx_en |= priv->hw.txq_prior[tx_ring->index];
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		/* enable ring */
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index,
 			       ENETC_TBMR, tx_en);
@@ -613,7 +652,6 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			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;
@@ -869,7 +907,10 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		dev->data->tx_queues[i] = NULL;
 	}
 	dev->data->nb_tx_queues = 0;
-
+	rte_free(hw->txq_prior);
+	hw->txq_prior = NULL;
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = NULL;
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_depopulate();
 
@@ -1020,6 +1061,11 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	for (i = 0; i < dev->data->nb_tx_queues; i++)
 		enetc4_rxbdr_wr(enetc_hw, i, ENETC_TBMR, ENETC_BMR_RESET);
 
+	hw->nc_mode = 0;
+	enetc4_get_devargs(dev, ENETC4_TXQ_PRIORITIES);
+	enetc4_get_devargs(dev, ENETC4_TXQ_WRR);
+	enetc4_get_devargs(dev, ENETC4_NC_MEMORY);
+
 	if (dev->data->nb_rx_queues <= 1)
 		return 0;
 
@@ -1142,7 +1188,13 @@ enetc4_tx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->tx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		tx_data = enetc4_txbdr_rd(&priv->hw.hw, tx_ring->index,
 					 ENETC_TBMR);
-		tx_data = tx_data | ENETC_TBMR_EN;
+		/* Clear scheduler bits before applying fresh devarg values. */
+		tx_data &= ~(ENETC_TBMR_PRIO_MASK | ENETC_TBMR_WRR_MASK);
+		tx_data |= ENETC_TBMR_EN;
+		if (priv->hw.txq_prior)
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index, ENETC_TBMR,
 			       tx_data);
 		dev->data->tx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
@@ -1457,5 +1509,6 @@ RTE_PMD_REGISTER_PCI_TABLE(net_enetc4, pci_id_enetc4_map);
 RTE_PMD_REGISTER_KMOD_DEP(net_enetc4, "* vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4,
 			      ENETC4_TXQ_PRIORITIES "=<string> "
+			      ENETC4_TXQ_WRR "=<string> "
 			      ENETC4_NC_MEMORY "=<int>");
 RTE_LOG_REGISTER_DEFAULT(enetc4_logtype_pmd, NOTICE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95
  2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
                       ` (13 preceding siblings ...)
  2026-08-07  7:02     ` [PATCH v3 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
@ 2026-08-07 11:26     ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
                         ` (15 more replies)
  14 siblings, 16 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal

V4-changes:
 - fix doc build issue: WARNING: undefined label: pmd_build_and_test

v3-changes:
 - fix doc build issue.
 - fix compilation issue on fedore:43-gcc-minsize

V2-changes:
 - compilation fixes.

V1-changes:
This series adds new PMD features to the ENETC4 driver targeting the
NXP i.MX95 NETC IP.

The series covers:

 - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
 - TSO: TCP Segmentation Offload for the VF Tx path.
 - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
 - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
   8-bit to support speeds beyond 10G.
 - Firmware version: report the NETC IP version via fw_version_get.
 - Register dump: dump SI, port (PF) and BD ring registers.
 - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
 - Link-up interrupt: refresh the cached link speed on each VF link-up
   interrupt so that link_update returns the current speed immediately.
 - Stats reset: software snapshot/delta approach for VF counter reset.
 - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
   enabling interrupt-driven receive with l3fwd-power.
 - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
 - VF link status bitmask: switch VF link status to bitmask encoding
   to align with the PF and newer kernel driver conventions.
 - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
   negotiated on the wire; adds Flow control = Y to enetc4.ini.
 - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.

Gagandeep Singh (14):
  net/enetc: add KEEP_CRC offload support for ENETC4
  net/enetc: add TSO support for ENETC4 VF
  net/enetc: add RSC (hardware LRO) support for ENETC4
  net/enetc: extend link speed code field to 8-bit for PF-to-VF message
  net/enetc: support firmware version get for VF
  net/enetc: support registers dump
  net/enetc: support ethtool ring parameters
  net/enetc: refresh link speed on VF link-up interrupt
  net/enetc: support stats reset for VF
  net/enetc4: add per-queue Rx interrupt support for VF
  net/enetc4: add SI-based port VLAN insertion and removal
  net/enetc4: update VF link status to bitmask encoding
  net/enetc4: enable TX PAUSE via VF RX congestion mode
  net/enetc4: add WRR Tx scheduler devarg for VF rings

 doc/guides/nics/enetc4.rst             |  77 +++
 doc/guides/nics/features/enetc4.ini    |   8 +
 doc/guides/rel_notes/release_26_11.rst |  19 +
 drivers/net/enetc/base/enetc4_hw.h     | 129 +++-
 drivers/net/enetc/base/enetc_hw.h      |   6 +
 drivers/net/enetc/enetc.h              | 135 ++++-
 drivers/net/enetc/enetc4_ethdev.c      | 402 +++++++++++--
 drivers/net/enetc/enetc4_vf.c          | 779 ++++++++++++++++++++++---
 drivers/net/enetc/enetc_rxtx.c         | 529 ++++++++++++++++-
 9 files changed, 1939 insertions(+), 145 deletions(-)

-- 
2.25.1


^ permalink raw reply	[flat|nested] 110+ messages in thread

* [PATCH v4 01/14] net/enetc: add KEEP_CRC offload support for ENETC4
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
                         ` (14 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The legacy ENETC (LS1028A) driver already supports the
RTE_ETH_RX_OFFLOAD_KEEP_CRC offload, but ENETC4 (i.MX95 NETC) did not.

Add KEEP_CRC support for ENETC4 (both PF and VF):

- Advertise RTE_ETH_RX_OFFLOAD_KEEP_CRC in the supported Rx offloads
  for the ENETC4 PF and VF. The VF reuses the PF Rx queue setup, so the
  HW configuration and datapath handling apply to both.
- Configure the per-ring RBaMR[CRC] bit in the Rx queue setup so that
  HW preserves the Ethernet FCS in the receive buffer when the offload
  is requested (0 = FCS removed, 1 = FCS preserved).
- Follow the DPDK convention used by the legacy ENETC and ixgbe drivers:
  set crc_len to RTE_ETHER_CRC_LEN when KEEP_CRC is enabled and have the
  datapath subtract crc_len from pkt_len/data_len. ENETC and ENETC4 share
  the same datapath (enetc_rxtx.c), so the semantics stay consistent.
- Handle the scatter-gather boundary case where the 4-byte FCS straddles
  the last two segments: drop the trailing segment, decrement nb_segs and
  trim the carry-over from its predecessor.

Update the enetc4 feature matrix to list CRC offload.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  6 ++++
 drivers/net/enetc/base/enetc4_hw.h     |  4 +++
 drivers/net/enetc/enetc4_ethdev.c      | 31 ++++++++++++++++++--
 drivers/net/enetc/enetc4_vf.c          |  1 +
 drivers/net/enetc/enetc_rxtx.c         | 39 +++++++++++++++++++++++---
 6 files changed, 75 insertions(+), 7 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 698140e30b..91b18d979e 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ Packet type parsing  = Y
 Basic stats          = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
+CRC offload          = Y
 Queue start/stop     = Y
 Scattered Rx         = Y
 Linux                = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..3678dd6894 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -56,6 +56,12 @@ New Features
      =======================================================
 
 
+* **Updated NXP ENETC4 PMD.**
+
+  Updated the NXP ENETC4 poll mode driver for i.MX95:
+
+  * Added KEEP_CRC Rx offload support for the ENETC4 PMD to preserve the Ethernet FCS.
+
 Removed Items
 -------------
 
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 9685e7e1e5..0105549857 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -68,6 +68,10 @@ struct enetc_msg_swbd {
 #define PM_CMD_CFG_TX_EN		BIT(0)
 #define PM_CMD_CFG_RX_EN		BIT(1)
 
+/* RBaMR[CRC]: 0 = FCS removed, 1 = FCS preserved (KEEP_CRC) */
+#define ENETC4_RBMR_CRC			BIT(8)
+
+
 /* i.MX95 supports jumbo frame, but it is recommended to set the max frame
  * size to 2000 bytes.
  */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 2ddd63dafb..1a8056580c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -11,6 +11,19 @@
 #include "enetc_logs.h"
 #include "enetc.h"
 
+/* Supported Rx offloads */
+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;
+
+/* Supported Tx offloads */
+static uint64_t dev_tx_offloads_sup =
+	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
+	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
+
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
 
@@ -536,6 +549,8 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			ENETC_DEV_PRIVATE(data->dev_private);
 	uint64_t rx_offloads = data->dev_conf.rxmode.offloads;
+	uint32_t rx_enable;
+	bool keep_crc;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_rx_desc > MAX_BD_COUNT)
@@ -549,6 +564,9 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	rx_ring->index = rx_queue_id;
+	keep_crc = !!(rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC);
+	rx_ring->crc_len = (uint8_t)(keep_crc ? RTE_ETHER_CRC_LEN : 0);
+
 	err = enetc4_alloc_rxbdr(rx_ring, nb_rx_desc);
 	if (err)
 		goto fail;
@@ -562,19 +580,25 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	data->rx_queues[rx_queue_id] = rx_ring;
 	rx_ring->rx_deferred_start = rx_conf->rx_deferred_start;
 
+	if (keep_crc)
+		rx_enable |= ENETC4_RBMR_CRC;
+	else
+		rx_enable &= ~ENETC4_RBMR_CRC;
+
 	if (!rx_conf->rx_deferred_start) {
 		/* enable ring */
+		rx_enable |= ENETC_RBMR_EN;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
-			       ENETC_RBMR_EN);
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STARTED;
 	} else {
+		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STOPPED;
 	}
 
-	rx_ring->crc_len = (uint8_t)((rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC) ?
-				     RTE_ETHER_CRC_LEN : 0);
 	return 0;
 fail:
 	rte_free(rx_ring);
@@ -582,6 +606,7 @@ 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 ef5f1e6d66..83a1e4931f 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -52,6 +52,7 @@ static uint64_t dev_rx_offloads_sup =
 	RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_VLAN_FILTER |
+	RTE_ETH_RX_OFFLOAD_KEEP_CRC |
 	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 8678bafece..e3bef607dd 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -530,6 +530,28 @@ enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
 	return rx_frm_cnt;
 }
 
+/*
+ * Trim the Ethernet FCS from a received cluster when HW CRC strip is
+ * disabled. pkt_len is reduced by crc_len. If the FCS straddles the last
+ * two segments (last seg holds fewer bytes than crc_len), drop the trailing
+ * segment and trim the carry-over from its predecessor. prev_seg is the
+ * segment preceding last_seg in the chain (the caller already tracks it).
+ */
+static inline void
+enetc_rx_crc_trim(struct rte_mbuf *first_seg, struct rte_mbuf *prev_seg,
+		  struct rte_mbuf *last_seg, uint16_t crc_len)
+{
+	first_seg->pkt_len -= crc_len;
+	if (likely(last_seg->data_len > crc_len)) {
+		last_seg->data_len -= crc_len;
+	} else if (prev_seg != NULL) {
+		first_seg->nb_segs--;
+		prev_seg->data_len -= crc_len - last_seg->data_len;
+		prev_seg->next = NULL;
+		rte_pktmbuf_free_seg(last_seg);
+	}
+}
+
 static int
 enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		    struct rte_mbuf **rx_pkts,
@@ -539,7 +561,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -572,6 +594,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg, rxbd_temp.r.parse_summary);
 			first_seg->hash.rss = rxbd_temp.r.rss_hash;
@@ -579,6 +602,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -590,7 +614,9 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
@@ -760,7 +786,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -815,6 +841,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg,
 					   rxbd->r.parse_summary);
@@ -823,6 +850,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -838,7 +866,10 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
+
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 02/14] net/enetc: add TSO support for ENETC4 VF
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
                         ` (13 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add TCP and UDP segmentation offload (TSO) for the ENETC4 virtual
function. The offload is advertised through the VF Tx capabilities and
is enabled on a per-queue basis when an application requests the TSO
offload flags during Tx queue setup.

A dedicated Tx burst, enetc_xmit_pkts_lso(), builds the large-send
descriptor chain: a standard header BD, a 16B extension BD carrying the
segment size and frame length extension, and one BD per payload buffer
with the final-frame flag on the last BD. To make room for the extra
extension BD, an LSO-enabled Tx ring is allocated with twice the number
of descriptors. The existing non-TSO Tx paths are left unchanged.

Update the ENETC4 feature list and documentation accordingly.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   2 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/base/enetc4_hw.h     |  35 +++-
 drivers/net/enetc/enetc.h              |   5 +
 drivers/net/enetc/enetc4_ethdev.c      |  45 ++++-
 drivers/net/enetc/enetc4_vf.c          |   2 +
 drivers/net/enetc/enetc_rxtx.c         | 255 +++++++++++++++++++++++++
 8 files changed, 341 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index f0dd039f6e..4d27e4048e 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -53,6 +53,8 @@ Key functionality includes:
 - Receive processing: Upon packet reception, the BD Ring status bit is set,
   facilitating packet processing.
 - 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.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 91b18d979e..8ec1e8d00f 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
+TSO                  = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 3678dd6894..6a348ab4e0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -61,6 +61,7 @@ New Features
   Updated the NXP ENETC4 poll mode driver for i.MX95:
 
   * 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.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 0105549857..64c1a5e750 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2024 NXP
+ * Copyright 2024-2026 NXP
  *
  * This header file defines the register offsets and bit fields
  * of ENETC4 PF and VFs.
@@ -24,8 +24,14 @@ struct enetc_msg_swbd {
 
 /* enetc4 txbd flags */
 #define ENETC4_TXBD_FLAGS_L4CS		BIT(0)
+/* Request LSO (Large Send Offload) segmentation for this frame */
+#define ENETC4_TXBD_FLAGS_LSO		BIT(1)
+/* L4 checksum insertion (also used for checksum update on LSO) */
 #define ENETC4_TXBD_FLAGS_L_TX_CKSUM	BIT(3)
+/* Extended descriptor: the next 16B ring entry is an extension BD */
+#define ENETC4_TXBD_FLAGS_EXT		BIT(6)
 #define ENETC4_TXBD_FLAGS_F		BIT(7)
+
 /* L4 type */
 #define ENETC4_TXBD_L4T_UDP		BIT(0)
 #define ENETC4_TXBD_L4T_TCP		BIT(1)
@@ -34,6 +40,33 @@ struct enetc_msg_swbd {
 /* IPv4 checksum */
 #define ENETC4_TXBD_IPCS		1
 
+/*
+ * Extension Transmit Buffer Descriptor (16B). When the standard BD has the
+ * extended flag set, this occupies the next ring entry and carries the extra
+ * fields required by LSO.
+ */
+struct enetc_tx_bd_ext {
+	uint32_t timestamp;	/* PTP timestamp, unused for LSO */
+	uint32_t vlan;		/* VLAN insert, unused for LSO */
+	uint32_t lso;		/* LSO_MAX_SEG_SIZE and FRM_LEN_EXT */
+	uint32_t flags;		/* extension flags */
+};
+
+/* LSO_MAX_SEG_SIZE occupies bits 13-0 of the LSO word */
+#define ENETC4_TXBD_EXT_LSO_SEG_MASK	0x3fff
+#define ENETC4_TXBD_EXT_LSO_SEG(mss) \
+		((uint32_t)((mss) & ENETC4_TXBD_EXT_LSO_SEG_MASK))
+/* FRM_LEN_EXT occupies bits 19-16 of the LSO word (top 4 bits of data len) */
+#define ENETC4_TXBD_EXT_FRM_LEN_EXT(x) \
+		(((uint32_t)((x) & 0xf)) << 16)
+/* Final flag in the extension flags word (bit 127 -> local bit 31) */
+#define ENETC4_TXBD_EXT_FLAGS_F		BIT(31)
+
+/* NETC does not create LSO frames larger than this many bytes */
+#define ENETC4_LSO_MAX_FRAME		9600
+/* Maximum LSO data unit (payload to be segmented) supported by HW: 256KB */
+#define ENETC4_LSO_MAX_DATA_UNIT	(256 * 1024)
+
 /***************************ENETC port registers**************************/
 #define ENETC4_PMR		0x10
 #define ENETC4_PMR_EN		(BIT(16) | BIT(17) | BIT(18))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index d3a8b8e34a..db349e051b 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -91,6 +91,7 @@ struct enetc_bdr {
 		void *tcisr; /* Tx */
 		int next_to_alloc; /* Rx */
 	};
+
 	struct rte_mempool *mb_pool;   /* mbuf pool to populate RX ring. */
 	/* Partial scatter-gather chain persisted across burst calls. */
 	struct rte_mbuf *pkt_first_seg; /* first segment of in-progress frame */
@@ -99,6 +100,7 @@ struct enetc_bdr {
 	uint64_t ierrors;
 	uint8_t rx_deferred_start;
 	uint8_t tx_deferred_start;
+	uint8_t lso_enable;
 };
 
 struct enetc_eth_hw {
@@ -312,6 +314,9 @@ uint16_t enetc_xmit_pkts(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
 uint16_t enetc_xmit_pkts_nc(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
+uint16_t enetc_xmit_pkts_lso(void *txq, struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts);
+
 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,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 1a8056580c..897e12e21f 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -22,7 +22,9 @@ static uint64_t dev_rx_offloads_sup =
 static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
+	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
@@ -321,28 +323,38 @@ static int
 enetc4_alloc_txbdr(struct enetc_bdr *txr, uint16_t nb_desc)
 {
 	int size;
+	uint32_t ring_desc;
 
-	size = nb_desc * sizeof(struct enetc_swbd);
+	/*
+	 * On LSO-enabled rings each frame uses an extra 16B extension BD
+	 * (logical 32B descriptor) plus the payload BDs.  Size the ring with
+	 * twice the requested entries so the effective frame capacity is
+	 * preserved.  Non-LSO rings are sized exactly as requested.
+	 */
+	ring_desc = txr->lso_enable ? (uint32_t)nb_desc * 2 : (uint32_t)nb_desc;
+
+	size = ring_desc * sizeof(struct enetc_swbd);
 	/* Zero q_swbd so buffer_addr is NULL for all uninitialized slots. */
 	txr->q_swbd = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->q_swbd == NULL)
 		return -ENOMEM;
 
 	/* Allocate the TX BD ring: each BD is struct enetc_tx_bd (16 bytes). */
-	size = nb_desc * sizeof(struct enetc_tx_bd);
+	size = ring_desc * sizeof(struct enetc_tx_bd);
 	txr->bd_base = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->bd_base == NULL) {
 		rte_free(txr->q_swbd);
 		txr->q_swbd = NULL;
 		return -ENOMEM;
 	}
-	txr->bd_count = nb_desc;
+	txr->bd_count = ring_desc;
 	txr->next_to_clean = 0;
 	txr->next_to_use = 0;
 
 	return 0;
 }
 
+
 static void
 enetc4_free_bdr(struct enetc_bdr *rxr)
 {
@@ -352,6 +364,7 @@ enetc4_free_bdr(struct enetc_bdr *rxr)
 	rxr->bd_base = NULL;
 }
 
+
 static void
 enetc4_setup_txbdr(struct enetc_hw *hw, struct enetc_bdr *tx_ring)
 {
@@ -388,6 +401,7 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	struct rte_eth_dev_data *data = dev->data;
 	struct enetc_eth_adapter *priv =
 			ENETC_DEV_PRIVATE(data->dev_private);
+	uint64_t tx_offloads;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_desc > MAX_BD_COUNT)
@@ -401,6 +415,29 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	tx_ring->index = queue_idx;
+	/*
+	 * LSO (TCP/UDP segmentation) is a port-level offload. The Tx burst is
+	 * a single device-level function pointer, so it must be consistent for
+	 * all queues; likewise every ring must be sized the same way. Decide
+	 * from the port offloads only (not the per-queue tx_conf) so that all
+	 * queues either use the LSO burst with a doubled ring, or none do.
+	 * The LSO burst also handles non-TSO packets, so it is safe on queues
+	 * that never carry TSO traffic. LSO needs HW FCS insertion, so it is
+	 * incompatible with KEEP_CRC on Rx.
+	 */
+	tx_offloads = data->dev_conf.txmode.offloads;
+	if (tx_offloads & (RTE_ETH_TX_OFFLOAD_TCP_TSO |
+			   RTE_ETH_TX_OFFLOAD_UDP_TSO)) {
+		if (data->dev_conf.rxmode.offloads &
+		    RTE_ETH_RX_OFFLOAD_KEEP_CRC) {
+			ENETC_PMD_ERR("LSO (TSO) is incompatible with KEEP_CRC");
+			rte_free(tx_ring);
+			return -EINVAL;
+		}
+		tx_ring->lso_enable = 1;
+		dev->tx_pkt_burst = &enetc_xmit_pkts_lso;
+	}
+
 	err = enetc4_alloc_txbdr(tx_ring, nb_desc);
 	if (err)
 		goto fail;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 83a1e4931f..3083a56335 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -60,6 +60,8 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO |
 	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
 
 static void
diff --git a/drivers/net/enetc/enetc_rxtx.c b/drivers/net/enetc/enetc_rxtx.c
index e3bef607dd..f19f32eace 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -221,6 +221,261 @@ enetc_xmit_pkts_nc(void *tx_queue,
 	return start;
 }
 
+/*
+ * LSO (Large Send Offload) Tx burst.
+ *
+ * Dedicated burst used on Tx rings with LSO enabled (txr->lso_enable). It
+ * follows the same non-cache-coherent discipline as enetc_xmit_pkts_cacheable:
+ * payload lines are flushed with dcbf before handing the frame to HW and the
+ * written BD cache lines are flushed at the end of the batch.
+ *
+ * A TSO/USO frame uses an extended Tx descriptor: the standard BD points at
+ * the L2/L3/L4 header template (BUF_LEN = header length, FRM_LEN = payload
+ * length), followed by an extension BD in the next ring slot holding the
+ * segment size, then one BD per payload buffer with the F flag on the last.
+ * Non-TSO packets fall through to the normal encoding so mixed traffic works.
+ */
+uint16_t
+enetc_xmit_pkts_lso(void *tx_queue,
+		struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts)
+{
+	int i, start, bds_to_use;
+	struct enetc_tx_bd *txbd = NULL;
+	struct enetc_tx_bd_ext *txbd_ext;
+	struct enetc_bdr *tx_ring = (struct enetc_bdr *)tx_queue;
+	unsigned int j;
+	uint8_t *data;
+	struct rte_mbuf *seg;
+	uint16_t seg_len, segs_per_pkt;
+	bool is_first_seg;
+	int first_bd_idx, bd_count;
+
+	i = tx_ring->next_to_use;
+	bds_to_use = enetc_bd_unused(tx_ring);
+	bd_count = tx_ring->bd_count;
+	start = 0;
+
+	first_bd_idx = i;
+
+	while (start < nb_pkts) {
+		seg = tx_pkts[start];
+		segs_per_pkt = seg->nb_segs;
+
+		if (seg->ol_flags &
+		    (RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_UDP_SEG)) {
+			bool is_udp_seg =
+				!!(seg->ol_flags & RTE_MBUF_F_TX_UDP_SEG);
+			uint32_t hdr_len = seg->l2_len + seg->l3_len +
+					   seg->l4_len;
+			uint32_t data_unit;
+			uint16_t first_payload;
+			struct rte_mbuf *dseg;
+			int bds_needed;
+
+			/* Header BD + extension BD + one BD per segment. */
+			bds_needed = 2 + segs_per_pkt;
+			if (bds_to_use < bds_needed)
+				break;
+
+			/*
+			 * Skip frames with nothing to segment, or whose
+			 * headers are not fully contained in the first
+			 * segment. The first BD carries the header template
+			 * and first_payload is computed as (first segment
+			 * data_len - hdr_len), so the L2/L3/L4 headers must
+			 * reside contiguously in the first mbuf; otherwise the
+			 * unsigned subtraction would underflow.
+			 */
+			if (unlikely(hdr_len >= rte_pktmbuf_pkt_len(seg) ||
+				     hdr_len > rte_pktmbuf_data_len(seg))) {
+				start++;
+				continue;
+			}
+			data_unit = rte_pktmbuf_pkt_len(seg) - hdr_len;
+
+			/*
+			 * Skip frames that violate HW LSO limits: a zero
+			 * segment size, a payload larger than the HW data
+			 * unit, or a per-segment frame (headers + segment)
+			 * bigger than the maximum LSO frame size.
+			 */
+			if (unlikely(seg->tso_segsz == 0 ||
+				     data_unit > ENETC4_LSO_MAX_DATA_UNIT ||
+				     hdr_len + seg->tso_segsz >
+					     ENETC4_LSO_MAX_FRAME)) {
+				start++;
+				continue;
+			}
+
+			/* Standard (first) BD: header template only. */
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			seg_len = rte_pktmbuf_data_len(seg);
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			memset(txbd, 0, sizeof(*txbd));
+			tx_ring->q_swbd[i].buffer_addr = seg;
+
+			/* FRM_LEN low 16 bits = payload length, BUF_LEN = hdr. */
+			txbd->frm_len = rte_cpu_to_le_16(data_unit & 0xffff);
+			txbd->buf_len = rte_cpu_to_le_16((uint16_t)hdr_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+
+			/* Checksum control for the per-segment L3/L4 rewrite. */
+			txbd->l3_start = seg->l2_len;
+			txbd->l3_hdr_size = seg->l3_len / 4;
+			if (seg->ol_flags & RTE_MBUF_F_TX_IPV6) {
+				txbd->l3t = 1;
+			} else {
+				txbd->l3t = ENETC4_TXBD_L3T;
+				txbd->ipcs = ENETC4_TXBD_IPCS;
+			}
+			txbd->l4t = is_udp_seg ? ENETC4_TXBD_L4T_UDP :
+						 ENETC4_TXBD_L4T_TCP;
+			txbd->flags = ENETC4_TXBD_FLAGS_L_TX_CKSUM |
+				      ENETC4_TXBD_FLAGS_L4CS |
+				      ENETC4_TXBD_FLAGS_LSO |
+				      ENETC4_TXBD_FLAGS_EXT;
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/* Extension BD occupies the next ring slot. */
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			txbd_ext = (struct enetc_tx_bd_ext *)
+				   ENETC_TXBD(*tx_ring, i);
+			memset(txbd_ext, 0, sizeof(*txbd_ext));
+			txbd_ext->lso = rte_cpu_to_le_32(ENETC4_TXBD_EXT_LSO_SEG(seg->tso_segsz) |
+					ENETC4_TXBD_EXT_FRM_LEN_EXT(data_unit >> 16));
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/*
+			 * Payload BDs. The first segment's payload starts after
+			 * the header template; remaining segments are pure
+			 * payload.
+			 */
+			first_payload = (uint16_t)(seg_len - hdr_len);
+			dseg = seg;
+			is_first_seg = true;
+			while (dseg) {
+				uint16_t dlen;
+				uint64_t daddr;
+
+				if (is_first_seg) {
+					dlen = first_payload;
+					daddr = rte_mbuf_data_iova(dseg) +
+						hdr_len;
+					is_first_seg = false;
+				} else {
+					dlen = rte_pktmbuf_data_len(dseg);
+					data = rte_pktmbuf_mtod(dseg, void *);
+					for (j = 0; j < dlen;
+					     j += RTE_CACHE_LINE_SIZE)
+						dcbf(data + j);
+					dcbf(data + (dlen - 1));
+					daddr = rte_mbuf_data_iova(dseg);
+				}
+
+				if (dlen == 0) {
+					dseg = dseg->next;
+					continue;
+				}
+
+				tx_ring->q_swbd[i].buffer_addr = NULL;
+				txbd = ENETC_TXBD(*tx_ring, i);
+				memset(txbd, 0, sizeof(*txbd));
+				txbd->buf_len = rte_cpu_to_le_16(dlen);
+				txbd->addr = rte_cpu_to_le_64(daddr);
+				i++;
+				bds_to_use--;
+				if (unlikely(i == bd_count))
+					i = 0;
+				dseg = dseg->next;
+			}
+
+			/* Mark the last written BD as frame-last. */
+			txbd->flags |= ENETC4_TXBD_FLAGS_F;
+			start++;
+			continue;
+		}
+
+		/* Non-TSO packet: standard single/multi-seg encoding. */
+		if (bds_to_use < segs_per_pkt)
+			break;
+
+		is_first_seg = true;
+		while (seg) {
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			seg_len = rte_pktmbuf_data_len(seg);
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			txbd->flags = 0;
+			if (is_first_seg) {
+				tx_ring->q_swbd[i].buffer_addr = seg;
+				txbd->frm_len = rte_pktmbuf_pkt_len(seg);
+				if (seg->ol_flags & ENETC4_TX_CKSUM_OFFLOAD_MASK)
+					enetc4_tx_offload_checksum(seg, txbd);
+				is_first_seg = false;
+			}
+
+			txbd->buf_len = rte_cpu_to_le_16(seg_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+			seg = seg->next;
+			i++;
+			bds_to_use--;
+
+			if (unlikely(i == bd_count))
+				i = 0;
+		}
+
+		txbd->flags |= rte_cpu_to_le_16(ENETC4_TXBD_FLAGS_F);
+		start++;
+	}
+
+	/*
+	 * Flush TX BDs to PoC so HW (non-cache-coherent i.MX95) can read the
+	 * descriptors from memory.  Same discipline as enetc_xmit_pkts_cacheable:
+	 * walk from the cache-line-aligned start of first_bd_idx to just past
+	 * the last written BD, one dcbf per 64-byte (4-BD) line.
+	 */
+	if (likely(start > 0)) {
+		int n = first_bd_idx & ~ENETC_BD_PER_CL_MASK;
+		int written = (i - n + bd_count) % bd_count;
+
+		if (written == 0)
+			written = bd_count;
+		written = (written + ENETC_BD_PER_CL_MASK) &
+			  ~ENETC_BD_PER_CL_MASK;
+
+		while (written > 0) {
+			dcbf((void *)ENETC_TXBD(*tx_ring, n));
+			n = (n + ENETC_BD_PER_CL) % bd_count;
+			written -= ENETC_BD_PER_CL;
+		}
+	}
+
+	enetc_clean_tx_ring(tx_ring);
+	tx_ring->next_to_use = i;
+	enetc_wr_reg(tx_ring->tcir, i);
+
+	return start;
+}
+
 int
 enetc_refill_rx_ring(struct enetc_bdr *rx_ring, const int buff_cnt)
 {
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
                         ` (12 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

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 <g.singh@nxp.com>
---
 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


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (2 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
                         ` (11 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF message is only 16-bit wide, where the upper 8-bit is the
class_id and the lower 8-bit carries the message content. For the
link speed message, the lower 4-bit was previously occupied by the cookie
field, leaving only 4-bit for the speed code, which allows at most
15 distinct speed values.

As the NETC IP evolves and supports more and more link speeds, 15 speed
values are clearly insufficient. Since the cookie field is designed
for non-blocking messages and has no meaningful use in link speed
messages, remove it and expand the speed code field from 4-bit to 8-bit,
allowing up to 255 speed values (ENETC_SPEED_MAX = 0xff).

Instead of enumerating every speed value greater than 5Gbps
explicitly, introduce a formula-based approach:

	speed_code = (link_speed - 5000) / 1000 + ENETC_SPEED_5000

This removes the explicit enum entries for 10G, 25G, 50G and 100G,
and replaces the individual switch-case branches with a generic
implementation to get the speed, making it easy to support any
future high speed without modifying the enum or the switch statement.

For backward compatibility with a PF running an older kernel (before
6.18.37) that still uses the legacy 4-bit speed code / 4-bit cookie
message layout, add a "vf_link_legacy" devarg. When set to 1, the VF
extracts the message result from the upper 4 bits of the lower byte
and decodes speeds greater than 5Gbps using the legacy fixed class
codes (10G/25G/50G/100G). The default (0) uses the new 8-bit layout.

	Usage: -a <pci_addr>,vf_link_legacy=1

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |  32 ++++-
 drivers/net/enetc/enetc4_vf.c          | 175 +++++++++++++++++++++----
 3 files changed, 175 insertions(+), 33 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 0dd08e0259..d7961d3c85 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -63,6 +63,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.
+  * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 67f8ce1919..c983cb059e 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -120,6 +120,10 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
+	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
+	 */
+	uint8_t vf_link_legacy;
 };
 
 /*
@@ -211,11 +215,29 @@ enum speed {
 	ENETC_SPEED_1000 = 0x5,
 	ENETC_SPEED_2500 = 0x6,
 	ENETC_SPEED_5000 = 0x7,
-	ENETC_SPEED_10G = 0x8,
-	ENETC_SPEED_25G = 0x9,
-	ENETC_SPEED_50G = 0xA,
-	ENETC_SPEED_100G = 0xB,
-	ENETC_SPEED_NOT_SUPPORTED = 0xF
+	/* Base speed class code used by the >5Gbps formula below */
+	/* Do not add enumeration values for any speed greater than
+	 * 5Gbps. For any speed greater than 5Gbps, its speed class
+	 * code should follow the formula below.
+	 *
+	 * SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+	 *
+	 * The unit of link_speed should be Mbps, the max SPEED
+	 * should <= ENETC_SPEED_MAX.
+	 */
+	ENETC_SPEED_MAX = 0xff,
+};
+
+/* Legacy speed class codes for backward compatibility with an older kernel PF
+ * (before 6.18.37) that encoded a 4-bit speed code and 4-bit cookie in the
+ * message's lower byte. Used only when the vf_link_legacy devarg is set.
+ */
+enum speed_legacy {
+	ENETC_SPEED_LEGACY_10G = 0x8,
+	ENETC_SPEED_LEGACY_25G = 0x9,
+	ENETC_SPEED_LEGACY_50G = 0xA,
+	ENETC_SPEED_LEGACY_100G = 0xB,
+	ENETC_SPEED_LEGACY_NOT_SUPPORTED = 0xF
 };
 
 /* PSI-VSI command header format */
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index be79a18a39..d34b8019ea 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -5,6 +5,7 @@
 #include <stdbool.h>
 #include <rte_kvargs.h>
 #include <rte_random.h>
+#include <rte_kvargs.h>
 #include <dpaax_iova_table.h>
 #include "enetc_logs.h"
 #include "enetc.h"
@@ -43,6 +44,12 @@ enetc4_vf_get_devarg_nc(struct rte_eth_dev *dev)
 #define ENETC_BYTE_SIZE			8
 #define ENETC_MSB_BIT			0x8000
 
+/* Backward-compat devarg for a PF running a kernel before 6.18.37, which uses
+ * the legacy PF-to-VF link message layout (4-bit speed code + 4-bit cookie).
+ * Usage: -a <pci_addr>,vf_link_legacy=1
+ */
+#define ENETC_VF_LINK_LEGACY		"vf_link_legacy"
+
 uint16_t enetc_crc_table[ENETC_CRC_TABLE_SIZE];
 bool enetc_crc_gen;
 
@@ -101,6 +108,59 @@ enetc_crc_calc(uint16_t crc, const uint8_t *buffer, size_t len)
 	return crc;
 }
 
+static int
+parse_vf_link_legacy(const char *key __rte_unused, const char *value,
+		     void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *endptr;
+	unsigned long val;
+
+	if (!value || *value == '\0') {
+		ENETC_PMD_WARN("Empty value for devarg %s, ignoring",
+			       ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	errno = 0;
+	val = strtoul(value, &endptr, 0);
+	if (errno != 0 || *endptr != '\0' || val > 1) {
+		ENETC_PMD_WARN("Invalid value '%s' for devarg %s, expected 0 or 1",
+			       value, ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	hw->vf_link_legacy = (uint8_t)val;
+
+	return 0;
+}
+
+static void
+enetc4_vf_get_devargs(struct rte_eth_dev *dev)
+{
+	struct rte_devargs *devargs;
+	struct rte_kvargs *kvlist;
+
+	devargs = dev->device->devargs;
+	if (!devargs)
+		return;
+
+	kvlist = rte_kvargs_parse(devargs->args, NULL);
+	if (!kvlist)
+		return;
+
+	if (rte_kvargs_count(kvlist, ENETC_VF_LINK_LEGACY)) {
+		if (rte_kvargs_process(kvlist, ENETC_VF_LINK_LEGACY,
+				       parse_vf_link_legacy, (void *)dev) < 0)
+			ENETC_PMD_WARN("Failed to parse devarg %s",
+				       ENETC_VF_LINK_LEGACY);
+	}
+
+	rte_kvargs_free(kvlist);
+}
+
 static int
 enetc4_vf_dev_infos_get(struct rte_eth_dev *dev,
 			struct rte_eth_dev_info *dev_info)
@@ -212,6 +272,7 @@ enetc4_msg_vsi_write_msg(struct enetc_hw *hw,
 static void
 enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgsr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -221,8 +282,15 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgsr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -231,6 +299,7 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 static void
 enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgrr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -240,8 +309,15 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgrr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -731,6 +807,8 @@ enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 static int
 enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused)
 {
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
 	struct enetc_psi_reply_msg *reply_msg;
 	struct rte_eth_link link;
 	int err;
@@ -809,28 +887,62 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 			link.link_speed = RTE_ETH_SPEED_NUM_5G;
 			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 			break;
-		case ENETC_SPEED_10G:
-			link.link_speed = RTE_ETH_SPEED_NUM_10G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_25G:
-			link.link_speed = RTE_ETH_SPEED_NUM_25G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_50G:
-			link.link_speed = RTE_ETH_SPEED_NUM_50G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100G:
-			link.link_speed = RTE_ETH_SPEED_NUM_100G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_NOT_SUPPORTED:
-			ENETC_PMD_DEBUG("Speed not supported");
-			link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-			break;
 		default:
-			ENETC_PMD_ERR("Unknown speed status");
+			if (hw->vf_link_legacy) {
+				/* Legacy PF-to-VF message layout (older kernel
+				 * PF): speeds greater than 5Gbps are encoded
+				 * with fixed 4-bit class codes rather than the
+				 * formula below.
+				 */
+				switch (reply_msg->status) {
+				case ENETC_SPEED_LEGACY_10G:
+					link.link_speed = RTE_ETH_SPEED_NUM_10G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_25G:
+					link.link_speed = RTE_ETH_SPEED_NUM_25G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_50G:
+					link.link_speed = RTE_ETH_SPEED_NUM_50G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_100G:
+					link.link_speed = RTE_ETH_SPEED_NUM_100G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+					ENETC_PMD_DEBUG("Speed not supported");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				default:
+					ENETC_PMD_ERR("Unknown speed status");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				}
+				break;
+			}
+
+			/* Any status reaching here is greater than
+			 * ENETC_SPEED_5000, as all values from 0x0 to
+			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
+			 * greater than 5Gbps are not enumerated and follow the
+			 * formula:
+			 *
+			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+			 *
+			 * where link_speed is in Mbps. Reverse it here to get the
+			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
+			 *
+			 * The PF only reports speeds that map to a well-known
+			 * RTE_ETH_SPEED_NUM_* value, so the computed value is a
+			 * valid DPDK speed. If a future speed not yet defined in
+			 * DPDK needs to be supported, the corresponding
+			 * RTE_ETH_SPEED_NUM_* value must first be added upstream.
+			 */
+			link.link_speed = (reply_msg->status - ENETC_SPEED_5000)
+					  * 1000 + 5000;
+			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 			break;
 		}
 	} else {
@@ -839,10 +951,9 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	link.link_autoneg = 1;
-
 	rte_eth_linkstatus_set(dev, &link);
-
 	rte_free(reply_msg);
+
 	return 0;
 }
 
@@ -1421,6 +1532,12 @@ enetc4_vf_dev_init(struct rte_eth_dev *eth_dev)
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_populate();
 
+	/* Parse VF specific devargs (e.g. vf_link_legacy) before the first
+	 * link update so that PF-to-VF link messages are interpreted using
+	 * the correct (legacy or current) layout.
+	 */
+	enetc4_vf_get_devargs(eth_dev);
+
 	ENETC_PMD_DEBUG("port_id %d vendorID=0x%x deviceID=0x%x",
 			eth_dev->data->port_id, pci_dev->id.vendor_id,
 			pci_dev->id.device_id);
@@ -1514,5 +1631,7 @@ RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
 			      ENETC4_VSI_DELAY "=<uint> "
-			      ENETC4_NC_MEMORY "=<int>");
+			      ENETC4_NC_MEMORY "=<int> "
+			      ENETC_VF_LINK_LEGACY "=<0|1>");
+
 RTE_LOG_REGISTER_DEFAULT(enetc4_vf_logtype_pmd, NOTICE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 05/14] net/enetc: support firmware version get for VF
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (3 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 06/14] net/enetc: support registers dump Gagandeep Singh
                         ` (10 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the ``.fw_version_get`` eth_dev op for the ENETC4 VF PMD to
report the NETC IP revision.

The NETC IP major revision is read from the PCI revision ID register,
while the IP minor revision is retrieved from the PSI over VSI-PSI
messaging using the "Get IP version" command class (class ID 0xF0,
command ID 0x1). The version is reported in the "<major>.<minor>"
format.

The PSI firmware only implements the IP_MN command of this class, so
the major revision is obtained from the PCI revision ID, matching the
behaviour of the kernel PF/VF drivers.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   1 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |  19 +++-
 drivers/net/enetc/enetc4_vf.c          | 135 +++++++++++++++++++++++++
 5 files changed, 155 insertions(+), 2 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index e7f4348603..ea0e37896d 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,7 @@ Key functionality includes:
   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.
+- Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index aae398e210..eaf474e7ba 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ VLAN filter          = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
+FW version           = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index d7961d3c85..5ac2bb4de0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,7 @@ New Features
   * Added TCP Segmentation Offload (TSO) support for the ENETC4 VF.
   * Added Receive Segment Coalesce (RSC / hardware LRO) support for ENETC4 PF and VF.
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
+  * Added firmware version reporting for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index c983cb059e..b1428f1bca 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -170,7 +170,8 @@ enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
-	ENETC_CLASS_ID_LINK_SPEED = 0x81
+	ENETC_CLASS_ID_LINK_SPEED = 0x81,
+	ENETC_CLASS_ID_GET_IP_VER = 0xF0
 };
 
 /* Enum for command IDs */
@@ -184,9 +185,18 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_LINK_STATUS = 0,
 	ENETC_CMD_ID_REGISTER_LINK_NOTIF = 1,
 	ENETC_CMD_ID_UNREGISTER_LINK_NOTIF = 2,
-	ENETC_CMD_ID_GET_LINK_SPEED = 0
+	ENETC_CMD_ID_GET_LINK_SPEED = 0,
+	/* Get IP version command IDs (class ID 0xF0) */
+	ENETC_CMD_ID_GET_IP_MJ = 0,
+	ENETC_CMD_ID_GET_IP_MN = 1,
+	ENETC_CMD_ID_GET_IP_INT = 2,
+	ENETC_CMD_ID_GET_IP_MNT = 3,
+	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* IP_VER value returned when the version is not available */
+#define ENETC_IP_VER_NOT_AVAILABLE	0xFF
+
 enum mac_addr_status {
 	ENETC_INVALID_MAC_ADDR = 0x0,
 	ENETC_DUPLICATE_MAC_ADDR = 0X1,
@@ -274,6 +284,11 @@ struct enetc_msg_cmd_get_link_speed {
 	struct enetc_msg_cmd_header header;
 };
 
+/* Get IP version command message format (class ID 0xF0) */
+struct enetc_msg_cmd_get_ip_ver {
+	struct enetc_msg_cmd_header header;
+};
+
 struct enetc_msg_cmd_set_vlan_promisc {
 	struct enetc_msg_cmd_header header;
 	uint8_t op;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index d34b8019ea..4b84a90cc9 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -5,6 +5,7 @@
 #include <stdbool.h>
 #include <rte_kvargs.h>
 #include <rte_random.h>
+#include <rte_bus_pci.h>
 #include <rte_kvargs.h>
 #include <dpaax_iova_table.h>
 #include "enetc_logs.h"
@@ -434,6 +435,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_MAC_FILTER:
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
+		case ENETC_CLASS_ID_GET_IP_VER:
 			break;
 		default:
 			err = -EIO;
@@ -797,6 +799,138 @@ enetc4_vf_get_link_speed(struct rte_eth_dev *dev, struct enetc_psi_reply_msg *re
 	return err;
 }
 
+/*
+ * Get the NETC IP minor revision (IP_MN) from the PSI using the 'Get IP
+ * version' command class (class ID 0xF0, cmd_id 0x1). In the reply, the
+ * low 8 bits of the return code carry the minor revision and the upper 8
+ * bits carry the class code (0xF0). The PSI only implements the IP_MN
+ * command of this class; the major revision comes from the PCI revision.
+ */
+static int
+enetc4_vf_get_ip_minor_revision(struct rte_eth_dev *dev, uint8_t *ip_mn)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_swbd *msg;
+	uint32_t msg_size;
+	uint16_t mc;
+	uint8_t class_id;
+	int vsimsgsr;
+	int err = 0;
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_cmd_get_ip_ver),
+				ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg");
+		rte_free(msg);
+		return -ENOMEM;
+	}
+
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	msg->size = msg_size;
+
+	/* COOKIE is 0 so that the command is executed as blocking on PSI */
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_GET_IP_VER,
+			ENETC_CMD_ID_GET_IP_MN, 0, 0, 0);
+
+	/* send the command and wait */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error");
+		goto end;
+	}
+
+	/*
+	 * For the IP version command class the class-specific field is
+	 * reused to carry the 8-bit version value in the lower byte of the
+	 * return code, so parse the full low byte here instead of using the
+	 * generic reply parser.
+	 */
+	vsimsgsr = enetc4_rd(enetc_hw, ENETC4_VSIMSGSR);
+	mc = ENETC_SIMSGSR_GET_MC(vsimsgsr);
+	class_id = (mc >> 8) & 0xff;
+
+	if (class_id != ENETC_CLASS_ID_GET_IP_VER) {
+		ENETC_PMD_ERR("Wrong reply message 0x%x", class_id);
+		err = -EIO;
+		goto end;
+	}
+
+	*ip_mn = mc & 0xff;
+	if (*ip_mn == ENETC_IP_VER_NOT_AVAILABLE) {
+		ENETC_PMD_DEBUG("IP minor revision not available");
+		err = -ENOTSUP;
+	}
+
+end:
+	/* free memory no longer required */
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	return err;
+}
+
+/*
+ * Retrieve the NETC firmware/IP version and format it as
+ * "<major>.<minor>". The IP major revision is taken from the PCI
+ * revision ID register, and the IP minor revision is fetched from the
+ * PSI via VSI-PSI messaging ('Get IP version' command class). This
+ * mirrors the behaviour of the kernel PF/VF drivers, where the PSI only
+ * implements the IP_MN command of the 0xF0 class.
+ */
+static int
+enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_size)
+{
+	struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
+	uint8_t ip_mj = 0, ip_mn = 0;
+	int ret;
+
+	PMD_INIT_FUNC_TRACE();
+
+	if (fw_version == NULL)
+		return -EINVAL;
+
+	if (fw_size == 0)
+		return snprintf(NULL, 0, "%u.%u", ip_mj, ip_mn) + 1;
+
+	/* IP major revision is exposed through the PCI revision ID */
+	ret = rte_pci_read_config(pci_dev, &ip_mj, sizeof(ip_mj),
+			RTE_PCI_REVISION_ID);
+	if (ret != sizeof(ip_mj)) {
+		ENETC_PMD_ERR("Failed to read PCI revision ID");
+		return -EIO;
+	}
+
+	/* IP minor revision is fetched from the PSI via VSI-PSI messaging.
+	 * If the PSI reports the version as unavailable, fall back to a
+	 * partial version string so the caller still gets useful output.
+	 */
+	ret = enetc4_vf_get_ip_minor_revision(dev, &ip_mn);
+	if (ret && ret != -ENOTSUP) {
+		ENETC_PMD_ERR("Failed to get NETC minor revision");
+		return ret;
+	}
+
+	if (ret == -ENOTSUP)
+		ret = snprintf(fw_version, fw_size, "%u.unknown", ip_mj);
+	else
+		ret = snprintf(fw_version, fw_size, "%u.%u", ip_mj, ip_mn);
+	if (ret < 0)
+		return -EINVAL;
+
+	ret += 1; /* add trailing '\0' */
+	if ((size_t)ret > fw_size)
+		return ret;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1306,6 +1440,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.fw_version_get       = enetc4_vf_fw_version_get,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 06/14] net/enetc: support registers dump
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (4 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
                         ` (9 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the .get_reg operation for the ENETC4 PF and VF PMDs to dump
the device registers via rte_eth_dev_get_reg_info.

The VF dumps the registers reachable by a virtual station interface
(VSI): the station interface and per-ring BD ring registers. Port
registers are not accessible by a VF.

The PF additionally dumps the port registers, which are only
accessible by the physical station interface (PSI).

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |  1 +
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 13 +++++
 drivers/net/enetc/enetc4_ethdev.c      | 70 ++++++++++++++++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 62 +++++++++++++++++++++++
 6 files changed, 148 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index ea0e37896d..cd757be12f 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -60,6 +60,7 @@ Key functionality includes:
   requested. RSC requires the FCS to be stripped, so it cannot be combined
   with the KEEP_CRC Rx offload.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
+- Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index eaf474e7ba..6540a43909 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -17,6 +17,7 @@ RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
 FW version           = Y
+Registers dump       = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 5ac2bb4de0..deb3bc5d52 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -65,6 +65,7 @@ New Features
   * Added Receive Segment Coalesce (RSC / hardware LRO) support for ENETC4 PF and VF.
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
   * Added firmware version reporting for the ENETC4 VF.
+  * Added register dump support for ENETC4 PF and VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index b1428f1bca..73404308fe 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -397,6 +397,19 @@ enetc_bd_unused(struct enetc_bdr *bdr)
 	return bdr->bd_count + bdr->next_to_clean - bdr->next_to_use - 1;
 }
 
+/* Per-ring Tx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_txbdr_regs[] = {
+	ENETC_TBMR, ENETC_TBSR, ENETC_TBBAR0, ENETC_TBBAR1,
+	ENETC_TBCIR, ENETC_TBLENR,
+};
+
+/* Per-ring Rx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_rxbdr_regs[] = {
+	ENETC_RBMR, ENETC_RBSR, ENETC_RBBSR, ENETC_RBCIR,
+	ENETC_RBBAR0, ENETC_RBBAR1, ENETC_RBPIR, ENETC_RBLENR,
+};
+
+
 /* CBDR prototypes */
 int enetc4_setup_cbdr(struct rte_eth_dev *dev, struct enetc_hw *hw,
 			int bd_count, struct netc_cbdr *cbdr);
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 7f95171b6d..681bac3ecc 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1176,6 +1176,75 @@ enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 	return ptypes;
 }
 
+/* Station interface registers dumped by .get_reg */
+static const uint32_t enetc4_pf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR,
+};
+
+/* Port registers dumped by .get_reg (accessible only by the PF) */
+static const uint32_t enetc4_pf_port_regs[] = {
+	ENETC4_PMR, ENETC4_PSIPMMR, ENETC4_PMAR0, ENETC4_PMAR1,
+	ENETC4_PM_CMD_CFG(0), ENETC4_PM_MAXFRM(0), ENETC4_PM_IF_MODE(0),
+	ENETC4_PM_IF_STATUS(0),
+};
+
+/*
+ * Dump the PF-accessible registers: station interface, port and per-ring
+ * BD ring registers. When info->data is NULL, only the register count and
+ * width are reported so the caller can size its buffer.
+ */
+static int
+enetc4_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_pf_si_regs);
+	count += RTE_DIM(enetc4_pf_port_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_si_regs); i++)
+		*buf++ = enetc4_rd(enetc_hw, enetc4_pf_si_regs[i]);
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_port_regs); i++)
+		*buf++ = enetc4_port_rd(enetc_hw, enetc4_pf_port_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 /*
  * The set of PCI devices this driver supports
  */
@@ -1194,6 +1263,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.link_update          = enetc4_link_update,
 	.stats_get            = enetc4_stats_get,
 	.stats_reset          = enetc4_stats_reset,
+	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 4b84a90cc9..77bec0425a 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -931,6 +931,66 @@ enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_si
 	return 0;
 }
 
+/* VF station interface registers dumped by .get_reg */
+static const uint32_t enetc4_vf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR, ENETC4_SIMSIVR, ENETC4_VSIIER, ENETC4_VSIIDR,
+	ENETC4_VSIMSGSR, ENETC4_VSIMSGRR,
+};
+
+/*
+ * Dump the VF-accessible registers. Only station interface and per-ring
+ * BD ring registers are reachable by a VF; port registers are not.
+ * When info->data is NULL, only the register count and width are
+ * reported so the caller can size its buffer.
+ */
+static int
+enetc4_vf_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_vf_si_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_vf_si_regs); i++)
+		*buf++ = enetc_rd(enetc_hw, enetc4_vf_si_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1441,6 +1501,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
@@ -1461,6 +1522,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.mac_addr_set         = enetc4_vf_set_mac_addr,
 	.mac_addr_add	      = enetc4_vf_mac_addr_add,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 07/14] net/enetc: support ethtool ring parameters
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (5 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 06/14] net/enetc: support registers dump Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
                         ` (8 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The ethtool ring parameter query relies on the queue info ethdev
ops (.rxq_info_get/.txq_info_get) together with the descriptor
limits reported in dev_info. The PF already registered these ops,
but the VF ops tables did not, so ring parameter queries failed on
the VF.

Make enetc4_rxq_info_get() and enetc4_txq_info_get() non-static and
register them in both VF ops tables so ring parameters are reported
for both the PF and VF.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst | 1 +
 drivers/net/enetc/enetc.h              | 4 ++++
 drivers/net/enetc/enetc4_ethdev.c      | 4 ++--
 drivers/net/enetc/enetc4_vf.c          | 4 ++++
 4 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index deb3bc5d52..9e333edf4f 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -66,6 +66,7 @@ New Features
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
+  * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 73404308fe..367d350dde 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -339,6 +339,10 @@ int enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx);
 void enetc4_tx_queue_release(struct rte_eth_dev *dev, uint16_t qid);
 const uint32_t *enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 			size_t *no_of_elements);
+void enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_rxq_info *qinfo);
+void enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_txq_info *qinfo);
 
 /*
  * enetc4_vf function prototype
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 681bac3ecc..afee0b90a3 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1125,7 +1125,7 @@ enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx)
 	return 0;
 }
 
-static void
+void
 enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_rxq_info *qinfo)
 {
@@ -1139,7 +1139,7 @@ enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 	qinfo->conf.rx_drop_en = 0;
 }
 
-static void
+void
 enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_txq_info *qinfo)
 {
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 77bec0425a..90b7b99e9a 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1508,10 +1508,12 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
@@ -1537,10 +1539,12 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 08/14] net/enetc: refresh link speed on VF link-up interrupt
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (6 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 09/14] net/enetc: support stats reset for VF Gagandeep Singh
                         ` (7 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The interrupt-driven link-status path (enetc4_process_psi_msg) only
updated link_status when a PSI-to-VSI notification arrived; it never
re-queried the link speed from the PF.  As a result, after a link-down
followed by a link-up the cached link_speed showed the speed from the
previous session rather than the freshly negotiated one.

Fix this by:

1. Introducing enetc4_decode_link_speed() - a shared helper that maps
   a PF-to-VF speed status code to the corresponding RTE_ETH_SPEED_NUM_*
   / RTE_ETH_LINK_*_DUPLEX values, handling both the current and the
   legacy (vf_link_legacy) 4-bit message layout.

2. Calling enetc4_vf_get_link_speed() inside enetc4_process_psi_msg()
   on ENETC_LINK_UP so the negotiated speed is fetched from the PF and
   decoded immediately, before rte_eth_linkstatus_set() is called and
   the LSC callback is fired.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc4_vf.c          | 197 +++++++++++++------------
 2 files changed, 107 insertions(+), 91 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 9e333edf4f..eca0dc1dea 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -67,6 +67,7 @@ New Features
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
+  * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 90b7b99e9a..ede11a7b61 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -324,9 +324,99 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	reply_msg->status = status;
 }
 
+/* Forward declaration: defined later in this file */
+static int enetc4_vf_get_link_speed(struct rte_eth_dev *dev,
+				     struct enetc_psi_reply_msg *reply_msg);
+
+/*
+ * Decode a PF-to-VF link-speed status code into the link_speed and
+ * link_duplex fields of *link.  vf_link_legacy selects the older
+ * 4-bit code layout used by kernel PFs before v6.18.37.
+ */
+static void
+enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
+			 struct rte_eth_link *link)
+{
+	switch (status) {
+	case ENETC_SPEED_UNKNOWN:
+		ENETC_PMD_DEBUG("Speed unknown");
+		link->link_speed = RTE_ETH_SPEED_NUM_NONE;
+		break;
+	case ENETC_SPEED_10_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_10_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_100_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_100_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_1000:
+		link->link_speed = RTE_ETH_SPEED_NUM_1G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_2500:
+		link->link_speed = RTE_ETH_SPEED_NUM_2_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_5000:
+		link->link_speed = RTE_ETH_SPEED_NUM_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	default:
+		if (vf_link_legacy) {
+			/* Legacy PF-to-VF message layout (older kernel PF):
+			 * speeds above 5Gbps use fixed 4-bit class codes.
+			 */
+			switch (status) {
+			case ENETC_SPEED_LEGACY_10G:
+				link->link_speed = RTE_ETH_SPEED_NUM_10G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_25G:
+				link->link_speed = RTE_ETH_SPEED_NUM_25G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_50G:
+				link->link_speed = RTE_ETH_SPEED_NUM_50G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_100G:
+				link->link_speed = RTE_ETH_SPEED_NUM_100G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+				ENETC_PMD_DEBUG("Speed not supported");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			default:
+				ENETC_PMD_ERR("Unknown speed status");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			}
+			break;
+		}
+		/* Any status here is > ENETC_SPEED_5000.  Reverse the formula:
+		 *   SPEED = (status - ENETC_SPEED_5000) * 1000 + 5000  (in Mbps)
+		 */
+		link->link_speed = (status - ENETC_SPEED_5000) * 1000 + 5000;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	}
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
 	int ret = 0;
@@ -345,6 +435,20 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		case ENETC_LINK_UP:
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Re-query speed from PF so the cached value reflects
+			 * the current negotiated speed after link-up.
+			 */
+			rte_free(msg);
+			msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+			if (msg) {
+				if (!enetc4_vf_get_link_speed(eth_dev, msg) &&
+				    msg->class_id == ENETC_CLASS_ID_LINK_SPEED)
+					enetc4_decode_link_speed(msg->status,
+							hw->vf_link_legacy,
+							&link);
+			} else {
+				ENETC_PMD_WARN("Failed to alloc msg for speed query");
+			}
 			break;
 		case ENETC_LINK_DOWN:
 			ENETC_PMD_DEBUG("Link is down");
@@ -1048,97 +1152,8 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_SPEED) {
-		switch (reply_msg->status) {
-		case ENETC_SPEED_UNKNOWN:
-			ENETC_PMD_DEBUG("Speed unknown");
-			link.link_speed = RTE_ETH_SPEED_NUM_NONE;
-			break;
-		case ENETC_SPEED_10_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_10_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_100_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_1000:
-			link.link_speed = RTE_ETH_SPEED_NUM_1G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_2500:
-			link.link_speed = RTE_ETH_SPEED_NUM_2_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_5000:
-			link.link_speed = RTE_ETH_SPEED_NUM_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		default:
-			if (hw->vf_link_legacy) {
-				/* Legacy PF-to-VF message layout (older kernel
-				 * PF): speeds greater than 5Gbps are encoded
-				 * with fixed 4-bit class codes rather than the
-				 * formula below.
-				 */
-				switch (reply_msg->status) {
-				case ENETC_SPEED_LEGACY_10G:
-					link.link_speed = RTE_ETH_SPEED_NUM_10G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_25G:
-					link.link_speed = RTE_ETH_SPEED_NUM_25G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_50G:
-					link.link_speed = RTE_ETH_SPEED_NUM_50G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_100G:
-					link.link_speed = RTE_ETH_SPEED_NUM_100G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
-					ENETC_PMD_DEBUG("Speed not supported");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				default:
-					ENETC_PMD_ERR("Unknown speed status");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				}
-				break;
-			}
-
-			/* Any status reaching here is greater than
-			 * ENETC_SPEED_5000, as all values from 0x0 to
-			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
-			 * greater than 5Gbps are not enumerated and follow the
-			 * formula:
-			 *
-			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
-			 *
-			 * where link_speed is in Mbps. Reverse it here to get the
-			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
-			 *
-			 * The PF only reports speeds that map to a well-known
-			 * RTE_ETH_SPEED_NUM_* value, so the computed value is a
-			 * valid DPDK speed. If a future speed not yet defined in
-			 * DPDK needs to be supported, the corresponding
-			 * RTE_ETH_SPEED_NUM_* value must first be added upstream.
-			 */
-			link.link_speed = (reply_msg->status - ENETC_SPEED_5000)
-					  * 1000 + 5000;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		}
+		enetc4_decode_link_speed(reply_msg->status,
+					 hw->vf_link_legacy, &link);
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 09/14] net/enetc: support stats reset for VF
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (7 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
                         ` (6 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The NETC SI-level hardware counters (SIROCT0, SIRFRM0, SITOCT0,
SITFRM0, SITDFCR) are read-only for a VF and cannot be directly
zeroed. No VSI-PSI command class exists for stats reset, and using
FLR/soft-reset to clear counters is not reliable due to a known
hardware erratum on some NETC platforms.

Implement stats_reset for the enetc4 VF PMD using a software
snapshot/delta approach: on stats_reset, the current HW counter
values are captured as a baseline in a new per-device
enetc4_vf_stats_saved struct. stats_get then reports the delta
(current_hw_value - saved_baseline), so counters appear to start
from zero after each reset call. Per-ring software Rx error
accumulators (ierrors) are zeroed directly on reset.

Register the stats_reset callback in both VF ops tables
(enetc4_vf_ops and enetc4_vf_ops_no_vsi_m) so the feature is
available regardless of whether the VSI messaging path is enabled.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 17 +++++++
 drivers/net/enetc/enetc4_vf.c          | 61 +++++++++++++++++++++++---
 3 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index eca0dc1dea..b88e2825a6 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -68,6 +68,7 @@ New Features
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
+  * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 367d350dde..3590cf6639 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -105,6 +105,21 @@ struct enetc_bdr {
 	uint8_t rsc_enable;
 };
 
+/*
+ * Saved SI counter baseline for VF stats reset. Since the SI-level
+ * hardware counters (SIROCT0, SIRFRM0, SITOCT0, SITFRM0, SITDFCR)
+ * are read-only for a VF and cannot be directly zeroed, stats_reset
+ * captures the current counter values as a baseline. stats_get then
+ * reports the delta: current_hw_value - baseline.
+ */
+struct enetc4_vf_stats_saved {
+	uint64_t ipackets;
+	uint64_t opackets;
+	uint64_t ibytes;
+	uint64_t obytes;
+	uint64_t oerrors;
+};
+
 struct enetc_eth_hw {
 	struct rte_eth_dev *ndev;
 	struct enetc_hw hw;
@@ -124,6 +139,8 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	/* Baseline snapshot for VF stats reset (software delta approach). */
+	struct enetc4_vf_stats_saved vf_stats_saved;
 };
 
 /*
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index ede11a7b61..63ee9cb346 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -225,15 +225,64 @@ enetc4_vf_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
 	uint8_t i;
 
 	PMD_INIT_FUNC_TRACE();
-	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
-	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
-	stats->ibytes = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
-	stats->obytes = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
-	stats->oerrors = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/*
+	 * The SI-level counters are read-only for a VF; they cannot be
+	 * zeroed directly. Instead, stats_reset captures a baseline
+	 * snapshot, and stats_get reports the delta so that the reported
+	 * values appear to start from zero after each reset call.
+	 */
+	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0) -
+			  hw->vf_stats_saved.ipackets;
+	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0) -
+			  hw->vf_stats_saved.opackets;
+	stats->ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0) -
+			  hw->vf_stats_saved.ibytes;
+	stats->obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0) -
+			  hw->vf_stats_saved.obytes;
+	stats->oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR) -
+			  hw->vf_stats_saved.oerrors;
+
 	for (i = 0; i < dev->data->nb_rx_queues; i++) {
 		rx_ring = dev->data->rx_queues[i];
 		stats->ierrors += rx_ring->ierrors;
 	}
+
+	return 0;
+}
+
+/*
+ * Reset VF statistics by capturing a new baseline snapshot of the
+ * SI-level hardware counters. Because those counters are read-only
+ * for a VF (hardware erratum prevents reliable clear via FLR/soft
+ * reset too), the driver uses a software delta approach: every
+ * stats_get call reports current_hw_value - saved_baseline.
+ */
+static int
+enetc4_vf_stats_reset(struct rte_eth_dev *dev)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_bdr *rx_ring;
+	uint8_t i;
+
+	PMD_INIT_FUNC_TRACE();
+
+	/* Snapshot current HW SI counter values as the new zero baseline. */
+	hw->vf_stats_saved.ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
+	hw->vf_stats_saved.opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
+	hw->vf_stats_saved.ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
+	hw->vf_stats_saved.obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
+	hw->vf_stats_saved.oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/* Reset the per-ring software Rx error accumulators. */
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		rx_ring = dev->data->rx_queues[i];
+		if (rx_ring)
+			rx_ring->ierrors = 0;
+	}
+
 	return 0;
 }
 
@@ -1514,6 +1563,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
 	.get_reg              = enetc4_vf_get_regs,
@@ -1538,6 +1588,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 10/14] net/enetc4: add per-queue Rx interrupt support for VF
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (8 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 09/14] net/enetc: support stats reset for VF Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
                         ` (5 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add MSI-X per-queue Rx interrupt support to the ENETC4 VF PMD on the
cacheable (default) Rx path. This allows applications such as l3fwd-power
to block in epoll_wait when no traffic is present, reducing CPU utilization
to near zero.

MSI-X vector 0 is reserved for the PSI-to-VSI mailbox. Rx queue i is
mapped to MSI-X vector i + 1 via ENETC_SIMSIRRV. Per-ring coalescing is
enabled with ICPT=1 so the first arriving packet fires the interrupt
immediately. The SIRXIDR W1C detect bit is cleared before re-arming
ENETC_RBIER to prevent spurious interrupts after traffic stops.

The interrupt infrastructure (rte_intr_efd_enable +
rte_intr_vec_list_alloc) is set up before rte_intr_enable() so that
vfio-pci can wire each MSI-X vector to its eventfd when programming
the MSI-X table.

enetc4_dev_configure() is updated to trigger interrupt setup when
intr_conf.rxq is set (not only when intr_conf.lsc is set), as applications
like l3fwd-power set intr_conf.rxq without setting lsc.

Documentation is updated to describe the feature, list the kernel and
host setup steps, and provide an example l3fwd-power command for single
queue/core interrupt-driven operation.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             | 65 +++++++++++++++++++
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc4_hw.h     |  2 +
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      |  9 +--
 drivers/net/enetc/enetc4_vf.c          | 88 +++++++++++++++++++++++++-
 7 files changed, 162 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index cd757be12f..96bca8f0fb 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,10 @@ Key functionality includes:
   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.
+- Per-queue Rx interrupts on VFs (cacheable Rx path only), enabling
+  interrupt-driven receive with ``vfio-pci``. Applications set
+  ``intr_conf.rxq = 1`` in ``rte_eth_conf`` to activate this feature.
+  See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
@@ -156,3 +160,64 @@ PF/Common devargs
   Usage example::
 
     dpdk-testpmd -a 0000:00:00.0,nc=1 -- -i
+
+
+Rx Interrupt Mode (VF)
+----------------------
+
+The ENETC4 VF PMD supports per-queue MSI-X Rx interrupts on the cacheable
+(default) Rx path. This allows applications to block in ``epoll_wait``
+instead of busy-polling, reducing CPU utilization when traffic is absent.
+
+**MSI-X vector assignment**
+
+ENETC4 VF MSI-X vector 0 is reserved for the PSI-to-VSI mailbox interrupt
+(link status notifications). Rx queue ``i`` is mapped to vector ``i + 1``.
+The driver allocates all required eventfds before calling
+``rte_intr_enable()`` so that ``vfio-pci`` can wire each MSI-X vector to
+its eventfd when it programs the MSI-X table.
+
+**Kernel and driver requirements**
+
+- ``vfio-pci`` kernel module with no-IOMMU mode enabled (no SMMU required).
+- The non-cacheable memory mode (``nc=1`` devarg) does **not** support
+  Rx interrupts and returns ``-ENOTSUP`` from ``rx_queue_intr_enable``.
+
+**Host setup**
+
+.. code-block:: console
+
+   # Enable vfio-pci no-IOMMU mode (if SMMU is not available)
+   modprobe vfio enable_unsafe_noiommu_mode=1
+   modprobe vfio-pci
+
+   # Bind the VF to vfio-pci
+   echo vfio-pci > /sys/bus/pci/devices/<vf_pci_addr>/driver_override
+   echo <vf_pci_addr> > /sys/bus/pci/drivers_probe
+
+**Running l3fwd-power with a single queue and core**
+
+The ``l3fwd-power`` sample application demonstrates interrupt-driven Rx.
+It sets ``intr_conf.rxq = 1`` in ``rte_eth_conf``, which triggers the VF
+interrupt setup in the driver. The ``--vfio-intr=msix`` EAL flag instructs
+DPDK to use MSI-X eventfds for interrupt signalling.
+
+.. code-block:: console
+
+   ./dpdk-l3fwd-power -l 0-1 -n 1 --vfio-intr=msix \
+       -a <vf_pci_addr> -- \
+       -p 0x1 --config="(0,0,1)" --no-numa --interrupt-only
+
+Where:
+
+- ``-l 0-1`` assigns the main thread to core 0 and the forwarding lcore to
+  core 1.
+- ``-a <vf_pci_addr>`` specifies the VF PCI address (e.g. ``0000:01:00.1``).
+- ``--config="(0,0,1)"`` maps port 0, queue 0 to lcore 1.
+- ``--interrupt-only`` enables pure interrupt mode (no busy-poll fallback).
+
+With no incoming traffic the forwarding lcore sleeps in ``epoll_wait``;
+CPU utilization drops to near zero. On the first arriving packet the MSI-X
+interrupt fires, the lcore wakes, drains the ring, disables the interrupt,
+processes the burst, then re-enables and re-arms the interrupt before
+returning to sleep.
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 6540a43909..005c64a3ca 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -5,6 +5,7 @@
 ;
 [Features]
 Link status event    = Y
+Rx interrupt         = Y
 Speed capabilities   = Y
 Link status          = Y
 LRO                  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index b88e2825a6..9b39debc1d 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -69,6 +69,7 @@ New Features
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
+  * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 8ad8f169d2..9543e982a2 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -250,6 +250,8 @@ struct enetc_rx_bd_ext {
 #define ENETC4_VSIIDR            0xA08
 #define ENETC4_VSIIER_MRIE       BIT(9)
 #define ENETC4_SI_INT_IDX        0
+/* MSI-X vector base for per-Rx-queue interrupts; vector 0 is the mailbox. */
+#define ENETC4_VF_RX_VEC_BASE    1
 
 /* VSI Registers */
 #define ENETC4_VSIMSGSR  0x204   /* RO */
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 3590cf6639..903829323e 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -135,6 +135,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index afee0b90a3..d987f1288c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -839,7 +839,8 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		return 0;
 
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0)
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0)
 			enetc4_vf_dev_intr(dev, false);
 		ret = enetc4_vf_dev_stop(dev);
 	} else {
@@ -961,12 +962,12 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	if (hw->device_id != ENETC4_DEV_ID_VF)
 		enetc4_port_wr(enetc_hw, ENETC4_PARCSCR, checksum);
 
-	/* Enable interrupts */
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0) {
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0) {
 			ret = enetc4_vf_dev_intr(dev, true);
 			if (ret)
-				ENETC_PMD_WARN("Failed to setup link interrupts");
+				ENETC_PMD_WARN("Failed to setup VF interrupts: %d", ret);
 		}
 	}
 
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 63ee9cb346..8a57a8cad4 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1555,6 +1555,52 @@ static const struct rte_pci_id pci_vf_id_enetc4_map[] = {
 	{ .vendor_id = 0, /* sentinel */ },
 };
 
+static int
+enetc4_vf_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t vec;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return -ENOTSUP;
+
+	vec = queue_id + ENETC4_VF_RX_VEC_BASE;
+
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), vec);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR1, 0);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR0,
+			ENETC4_RBICR0_ICEN | ENETC4_RBICR0_ICPT(1));
+	enetc_wr(enetc_hw, ENETC_SIRXIDR, BIT(queue_id));
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, ENETC_RBIER_RXTIE);
+
+	return 0;
+}
+
+static int
+enetc4_vf_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return 0;
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, 0);
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), 0);
+
+	return 0;
+}
+
 /* Features supported by this driver */
 /* ops table used when VSI messaging is disabled */
 static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
@@ -1568,7 +1614,15 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.fw_version_get       = enetc4_vf_fw_version_get,
 	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
+	.mac_addr_set         = enetc4_vf_set_mac_addr,
+	.mac_addr_add	      = enetc4_vf_mac_addr_add,
+	.promiscuous_enable   = enetc4_vf_promisc_enable,
+	.promiscuous_disable  = enetc4_vf_promisc_disable,
+	.allmulticast_enable  = enetc4_vf_multicast_enable,
+	.allmulticast_disable = enetc4_vf_multicast_disable,
 	.link_update	      = enetc4_vf_link_update_dummy,
+	.vlan_filter_set      = enetc4_vf_vlan_filter_set,
+	.vlan_offload_set     = enetc4_vf_vlan_offload_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
@@ -1606,6 +1660,8 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
 	.rxq_info_get         = enetc4_rxq_info_get,
+	.rx_queue_intr_enable  = enetc4_vf_rx_queue_intr_enable,
+	.rx_queue_intr_disable = enetc4_vf_rx_queue_intr_disable,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
@@ -1851,6 +1907,35 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		/* Vector index 0 */
 		enetc_wr(enetc_hw, ENETC4_SIMSIVR, ENETC4_SI_INT_IDX);
 
+		if (rte_intr_cap_multiple(intr_handle) &&
+		    eth_dev->data->nb_rx_queues > 0) {
+			uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+			uint16_t i;
+
+			ret = rte_intr_efd_enable(intr_handle,
+					nb_rx + ENETC4_VF_RX_VEC_BASE);
+			if (ret) {
+				ENETC_PMD_WARN("Failed to enable per-queue Rx eventfds: %d",
+					       ret);
+				ret = 0;
+				hw->rxq_intr_en = 0;
+			} else {
+				ret = rte_intr_vec_list_alloc(intr_handle,
+						"enetc4_vf_rx_intr", nb_rx);
+				if (ret) {
+					ENETC_PMD_WARN("Failed to alloc intr vec list: %d",
+						       ret);
+					rte_intr_efd_disable(intr_handle);
+					hw->rxq_intr_en = 0;
+				} else {
+					for (i = 0; i < nb_rx; i++)
+						rte_intr_vec_list_index_set(intr_handle, i,
+							i + ENETC4_VF_RX_VEC_BASE);
+					hw->rxq_intr_en = 1;
+				}
+			}
+		}
+
 		/* enable uio/vfio intr/eventfd mapping */
 		ret = rte_intr_enable(intr_handle);
 		if (ret) {
@@ -1874,6 +1959,7 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		ENETC_PMD_WARN("Failed to un-register link notification %d", ret);
 disable:
 	enetc_vf_enable_mr_int(enetc_hw, false);
+	hw->rxq_intr_en = 0;
 	ret = rte_intr_disable(intr_handle);
 	if (ret)
 		ENETC_PMD_WARN("Failed to disable INTR %d", ret);
@@ -1893,7 +1979,7 @@ static struct rte_pci_driver rte_enetc4_vf_pmd = {
 
 RTE_PMD_REGISTER_PCI(net_enetc4_vf, rte_enetc4_vf_pmd);
 RTE_PMD_REGISTER_PCI_TABLE(net_enetc4_vf, pci_vf_id_enetc4_map);
-RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic");
+RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic | vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 11/14] net/enetc4: add SI-based port VLAN insertion and removal
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (9 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
                         ` (4 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement hardware VLAN tag insertion on Tx and removal on Rx
via the per-SI VLAN isolation (pvid) mechanism on ENETC4.

On a PF, the driver writes directly to PSIaVLANR(0) (VID + enable
bit) and PSIaCFGR0(0) (SIVIE for Tx insertion, VTE for Rx removal,
SIVC_CVLAN to allow C-VLAN 0x8100 TPID).

On a privileged VF, the request is forwarded to the kernel PF via
the VSI-PSI mailbox using a new command class 0x24 (SI VLAN
isolation, cmd ID 0x1). The kernel PF handler programs the same
registers on behalf of the VF's station interface. The class 0x24
reply is added to the pass-through list in enetc4_msg_vsi_send() so
it is handled correctly alongside the existing command classes.

New additions:
- ENETC4_PSIVLANR(a) and ENETC4_PSICFGR0(a) register macros with
  field definitions in enetc4_hw.h
- ENETC_CLASS_ID_SI_VLAN_ISO (0x24) and ENETC_CMD_ID_SET_SI_VLAN_ISO
  enum values, plus struct enetc_msg_si_vlan_iso message layout and
  bit definitions in enetc.h
- enetc4_vlan_pvid_set() for PF in enetc4_ethdev.c
- enetc4_vf_vlan_pvid_set() for VF in enetc4_vf.c, registered as
  .vlan_pvid_set in both enetc4_ops and enetc4_vf_ops

The feature is activated in testpmd with:
  tx_vlan set pvid <port_id> <vlan_id> on|off

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 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     | 14 +++++
 drivers/net/enetc/enetc.h              | 23 +++++++
 drivers/net/enetc/enetc4_ethdev.c      | 39 ++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 85 ++++++++++++++++++++++++++
 7 files changed, 167 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 96bca8f0fb..5ac1d03f74 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -65,6 +65,10 @@ Key functionality includes:
   See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
+- SI-based port VLAN (pvid): Hardware VLAN tag insertion on Tx and removal on Rx, configured
+  via ``rte_eth_dev_set_vlan_pvid``. On a PF the registers are written directly; on a privileged
+  VF the request is forwarded to the kernel PF through the VSI-PSI mailbox (class 0x24).
+  Use the testpmd command ``tx_vlan set pvid <port_id> <vlan_id> on|off`` to enable or disable.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 005c64a3ca..01b0dc5b80 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 9b39debc1d..e6d96b2a50 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -70,6 +70,7 @@ New Features
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
+  * Added SI-based port VLAN insertion (Tx) and removal (Rx) 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 9543e982a2..a9552f21f4 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -277,6 +277,20 @@ struct enetc_rx_bd_ext {
 #define ENETC4_SICTR0		0x18
 #define ENETC4_SICTR1		0x1c
 
+/* PSI SI VLAN register: per-SI VLAN tag for insertion/removal */
+#define ENETC4_PSIVLANR(a)		((a) * 0x80 + 0x2008)
+#define ENETC4_PSIVLANR_E		BIT(31)  /* enable SI VLAN processing */
+#define ENETC4_PSIVLANR_VTEA		BIT(30)  /* 0=strip tag, 1=zero VID */
+#define ENETC4_PSIVLANR_PCP(v)		(((uint32_t)(v) & 0x7) << 13)
+#define ENETC4_PSIVLANR_DEI		BIT(12)
+#define ENETC4_PSIVLANR_VID(v)		((uint32_t)(v) & 0xfff)
+
+/* PSI SI configuration register 0 */
+#define ENETC4_PSICFGR0(a)		((a) * 0x80 + 0x2010)
+#define ENETC4_PSICFGR0_SIVC_CVLAN	BIT(24)  /* allow C-VLAN 0x8100 */
+#define ENETC4_PSICFGR0_SIVIE		BIT(14)  /* SI VLAN insertion enable */
+#define ENETC4_PSICFGR0_VTE		BIT(12)  /* SI VLAN removal enable */
+
 /* general register accessors */
 #define enetc4_rd_reg(reg)	rte_read32((void *)(reg))
 #define enetc4_wr_reg(reg, val)  rte_write32((val), (void *)(reg))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 903829323e..72e43572f7 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -187,6 +187,8 @@ struct enetc_eth_adapter {
 enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
+	/* Configure SI VLAN isolation (insert / remove) */
+	ENETC_CLASS_ID_SI_VLAN_ISO = 0x24,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
 	ENETC_CLASS_ID_LINK_SPEED = 0x81,
 	ENETC_CLASS_ID_GET_IP_VER = 0xF0
@@ -212,6 +214,11 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* Command IDs for class ID 0x24 (SI VLAN isolation) */
+enum enetc_msg_si_vlan_cmd_id {
+	ENETC_CMD_ID_SET_SI_VLAN_ISO = 1,
+};
+
 /* IP_VER value returned when the version is not available */
 #define ENETC_IP_VER_NOT_AVAILABLE	0xFF
 
@@ -323,6 +330,22 @@ struct enetc_msg_vlan_exact_filter {
 	uint8_t reserved2;
 };
 
+/* VSI-PSI SI VLAN isolation message (class 0x24, cmd 0x1) */
+struct enetc_msg_si_vlan_iso {
+	struct enetc_msg_cmd_header header;
+	uint8_t ctrl;          /* E[7], VTEA[6], TPID[1:0] */
+	uint8_t pcp_dei_vid_hi; /* PCP[7:5], DEI[4], VID[11:8] */
+	uint8_t vid_lo;        /* VID[7:0] */
+	uint8_t reserved_0;
+	uint8_t flags;         /* SVIE[2], VTE[1] */
+	uint8_t reserved_1[3];
+};
+
+#define ENETC_SI_VLAN_ISO_E	BIT(7)  /* ctrl: enable SI VLAN */
+#define ENETC_SI_VLAN_ISO_VTEA	BIT(6)  /* ctrl: 0=strip, 1=zero VID */
+#define ENETC_SI_VLAN_ISO_SVIE	BIT(2)  /* flags: Tx insertion enable */
+#define ENETC_SI_VLAN_ISO_VTE	BIT(1)  /* flags: Rx removal enable */
+
 struct enetc_psi_reply_msg {
 	uint8_t class_id;
 	uint8_t status;
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index d987f1288c..ae40cc69e0 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -872,6 +872,44 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 	return ret;
 }
 
+/* Configure SI-based VLAN insertion (Tx) and removal (Rx) for the PF. */
+static int
+enetc4_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t psivlanr, psicfgr0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	psicfgr0 = enetc4_port_rd(enetc_hw, ENETC4_PSICFGR0(0));
+
+	if (on) {
+		/* Build the VLAN register: enable bit + VID (PCP/DEI left 0) */
+		psivlanr = (uint32_t)ENETC4_PSIVLANR_E |
+			   ENETC4_PSIVLANR_VID(vlan_id);
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), psivlanr);
+
+		/* Allow C-VLAN TPID, enable Tx insertion and Rx removal */
+		psicfgr0 |= ENETC4_PSICFGR0_SIVC_CVLAN |
+			    ENETC4_PSICFGR0_SIVIE |
+			    ENETC4_PSICFGR0_VTE;
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+	} else {
+		/* Clear Tx insertion, Rx removal and C-VLAN allow bits */
+		psicfgr0 &= ~(ENETC4_PSICFGR0_SIVC_CVLAN |
+			      ENETC4_PSICFGR0_SIVIE |
+			      ENETC4_PSICFGR0_VTE);
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+
+		/* Clear the VLAN register (disable SI VLAN processing) */
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), 0);
+	}
+
+	return 0;
+}
+
 static int
 enetc4_promiscuous_enable(struct rte_eth_dev *dev)
 {
@@ -1267,6 +1305,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
+	.vlan_pvid_set        = enetc4_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 8a57a8cad4..fc5ea4d609 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -589,8 +589,11 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
 		case ENETC_CLASS_ID_GET_IP_VER:
+		case ENETC_CLASS_ID_SI_VLAN_ISO:
 			break;
 		default:
+			ENETC_PMD_ERR("Unexpected reply class_id=0x%02x vsimsgsr=0x%08x",
+				      class_id, vsimsgsr);
 			err = -EIO;
 		}
 	}
@@ -1493,6 +1496,87 @@ static int enetc4_vf_vlan_offload_set(struct rte_eth_dev *dev, int mask __rte_un
 	return 0;
 }
 
+/* Configure SI-based VLAN insertion/removal via VSI-PSI mailbox (class 0x24). */
+static int
+enetc4_vf_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_si_vlan_iso *cmd;
+	struct enetc_msg_swbd *msg;
+	struct enetc_psi_reply_msg *reply_msg;
+	uint32_t msg_size;
+	int err = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	reply_msg = rte_zmalloc(NULL, sizeof(*reply_msg), RTE_CACHE_LINE_SIZE);
+	if (!reply_msg) {
+		ENETC_PMD_ERR("Failed to alloc memory for reply_msg");
+		return -ENOMEM;
+	}
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_si_vlan_iso),
+			     ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg body");
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	if (msg->dma == RTE_BAD_IOVA) {
+		ENETC_PMD_ERR("Failed to get IOVA for SI VLAN isolation msg body");
+		rte_free(msg->vaddr);
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->size = msg_size;
+
+	cmd = (struct enetc_msg_si_vlan_iso *)msg->vaddr;
+
+	if (on) {
+		cmd->ctrl = (uint8_t)ENETC_SI_VLAN_ISO_E;
+		cmd->pcp_dei_vid_hi = (uint8_t)((vlan_id >> 8) & 0x0f);
+		cmd->vid_lo = (uint8_t)(vlan_id & 0xff);
+		cmd->flags = (uint8_t)(ENETC_SI_VLAN_ISO_SVIE | ENETC_SI_VLAN_ISO_VTE);
+	}
+	/* on=0: all fields zero (rte_zmalloc cleared the buffer) */
+
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_SI_VLAN_ISO,
+				     ENETC_CMD_ID_SET_SI_VLAN_ISO, 0, 0, 0);
+
+	/* send the command and wait for PSI reply */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error for SI VLAN isolation");
+		goto end;
+	}
+
+	enetc4_msg_vsi_reply_msg(enetc_hw, reply_msg);
+
+	if (reply_msg->class_id != ENETC_MSG_CLASS_ID_CMD_SUCCESS) {
+		ENETC_PMD_ERR("SI VLAN isolation command failed: class_id=0x%x status=0x%x",
+			      reply_msg->class_id, reply_msg->status);
+		err = -EINVAL;
+	}
+
+end:
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	rte_free(reply_msg);
+	return err;
+}
+
 static int
 enetc4_vf_mtu_set(struct rte_eth_dev *dev __rte_unused, uint16_t mtu __rte_unused)
 {
@@ -1623,6 +1707,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.vlan_filter_set      = enetc4_vf_vlan_filter_set,
 	.vlan_offload_set     = enetc4_vf_vlan_offload_set,
+	.vlan_pvid_set        = enetc4_vf_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 12/14] net/enetc4: update VF link status to bitmask encoding
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (10 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
                         ` (3 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF link status notification code was previously a two-value
enum (UP=0x0, DOWN=0x1). Change this to a bitmask so future bits can
carry additional state alongside the link up/down indication:

  ENETC_LINK_DOWN  BIT(0)  -- set when link is down

Link up is now encoded as the DOWN bit being clear, which keeps the
wire value for link-down identical (0x1) and ensures backward
compatibility with older kernel PFs.

Changes:
- enetc.h: replace enum link_status with a #define bitmask;
  drop ENETC_LINK_UP (no longer a named constant).
- enetc4_vf.c enetc4_process_psi_msg(): replace switch/case on
  ENETC_LINK_UP/DOWN with bitmask decode.
- enetc4_vf.c enetc4_vf_link_update(): same bitmask decode.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              |  8 ++++----
 drivers/net/enetc/enetc4_vf.c          | 27 +++++++-------------------
 3 files changed, 12 insertions(+), 24 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index e6d96b2a50..5323f4fea3 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -71,6 +71,7 @@ New Features
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
+  * Updated ENETC4 VF link status reporting to use bitmask encoding.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 72e43572f7..40bad56341 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -236,10 +236,10 @@ enum vlan_status {
 	ENETC_VLAN_NO_RESOURCE = 0x3
 };
 
-enum link_status {
-	ENETC_LINK_UP = 0x0,
-	ENETC_LINK_DOWN = 0x1
-};
+/* Link status bitmask in PF-to-VF mailbox notification.
+ * Link up is encoded as the DOWN bit being clear.
+ */
+#define ENETC_LINK_DOWN  (1u << 0)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index fc5ea4d609..1b93835dd7 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -480,8 +480,10 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 	enetc4_msg_get_psi_msg(enetc_hw, msg);
 
 	if (msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (msg->status) {
-		case ENETC_LINK_UP:
+		if (msg->status & ENETC_LINK_DOWN) {
+			ENETC_PMD_DEBUG("Link is down");
+			link.link_status = RTE_ETH_LINK_DOWN;
+		} else {
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
 			/* Re-query speed from PF so the cached value reflects
@@ -498,14 +500,6 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 			} else {
 				ENETC_PMD_WARN("Failed to alloc msg for speed query");
 			}
-			break;
-		case ENETC_LINK_DOWN:
-			ENETC_PMD_DEBUG("Link is down");
-			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status 0x%x", msg->status);
-			break;
 		}
 		ret = rte_eth_linkstatus_set(eth_dev, &link);
 		if (!ret)
@@ -1180,17 +1174,10 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (reply_msg->status) {
-		case ENETC_LINK_UP:
-			link.link_status = RTE_ETH_LINK_UP;
-			break;
-		case ENETC_LINK_DOWN:
+		if (reply_msg->status & ENETC_LINK_DOWN)
 			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status");
-			break;
-		}
+		else
+			link.link_status = RTE_ETH_LINK_UP;
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (11 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-07 11:26       ` [PATCH v4 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
                         ` (2 subsequent siblings)
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

When the PF negotiates TX PAUSE on the port it signals this to the VF
via BIT(1) of the PF-to-VF link status mailbox message. The VF PMD must
respond by setting RBMR_CM (BIT(4)) on all active RX rings so the MAC
emits PAUSE frames on ingress pressure.

Add ENETC_RBMR_CM register definition, ENETC_LINK_TX_PAUSE bitmask,
and tx_pause_active state flag. Add enetc4_vf_set_congestion_mode() to
update all active RX rings and persist the state for rings started
later. Hook it into both the interrupt and poll link-update paths, and
apply the saved state in rx_queue_setup() and rx_queue_start().

RX PAUSE (honoring received PAUSE frames) is handled at the MAC level
by the PF and requires no VF PMD changes.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  1 +
 drivers/net/enetc/enetc.h              |  9 +++-
 drivers/net/enetc/enetc4_ethdev.c      | 13 +++++-
 drivers/net/enetc/enetc4_vf.c          | 59 ++++++++++++++++++++++++--
 6 files changed, 78 insertions(+), 6 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 01b0dc5b80..1f599dace7 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+Flow control         = Y
 VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 5323f4fea3..d69888aafb 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -72,6 +72,7 @@ New Features
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
+  * Added TX PAUSE support for the ENETC4 VF via RX congestion mode.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 6e96562850..33d075fe59 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -51,6 +51,7 @@ enum enetc_bdr_type {TX, RX};
 							+ (off))
 /* RX BDR reg offsets */
 #define ENETC_RBMR		0x0 /* RX BDR mode register*/
+#define ENETC_RBMR_CM		BIT(4)  /* congestion mode: assert congestion to emit TX PAUSE */
 #define ENETC_RBMR_EN		BIT(31)
 
 #define ENETC_BMR_RESET		0x0 /* BDR reset*/
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 40bad56341..8aaf8a7eb1 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -140,6 +140,10 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	/* 1 = TX PAUSE negotiated on port; VF RX rings must have RBMR_CM set.
+	 * Updated from the PF-to-VF link status mailbox message (BIT(1)).
+	 */
+	uint8_t tx_pause_active;
 	/* Baseline snapshot for VF stats reset (software delta approach). */
 	struct enetc4_vf_stats_saved vf_stats_saved;
 };
@@ -238,8 +242,11 @@ enum vlan_status {
 
 /* Link status bitmask in PF-to-VF mailbox notification.
  * Link up is encoded as the DOWN bit being clear.
+ * TX_PAUSE is set when the port has negotiated TX PAUSE; VF must enable
+ * congestion mode (ENETC_RBMR_CM) on its RX rings accordingly.
  */
-#define ENETC_LINK_DOWN  (1u << 0)
+#define ENETC_LINK_DOWN      (1u << 0)
+#define ENETC_LINK_TX_PAUSE  (1u << 1)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index ae40cc69e0..2355522515 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -712,8 +712,12 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	if (!rx_conf->rx_deferred_start) {
-		/* enable ring */
+		/* Enable ring; apply congestion mode if TX PAUSE is already active. */
 		rx_enable |= ENETC_RBMR_EN;
+		if (adapter->hw.tx_pause_active)
+			rx_enable |= ENETC_RBMR_CM;
+		else
+			rx_enable &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
@@ -1089,7 +1093,12 @@ enetc4_rx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->rx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		rx_data = enetc4_rxbdr_rd(&priv->hw.hw, rx_ring->index,
 					 ENETC_RBMR);
-		rx_data = rx_data | ENETC_RBMR_EN;
+		rx_data |= ENETC_RBMR_EN;
+		/* Restore congestion mode if TX PAUSE is active. */
+		if (priv->hw.tx_pause_active)
+			rx_data |= ENETC_RBMR_CM;
+		else
+			rx_data &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&priv->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_data);
 		dev->data->rx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 1b93835dd7..c68bc8521b 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -461,6 +461,37 @@ enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
 	}
 }
 
+/*
+ * Set or clear ENETC_RBMR_CM (congestion mode) on all active VF RX rings.
+ * When set, the ring signals congestion to the MAC, causing it to emit TX
+ * PAUSE frames on ingress pressure. hw->tx_pause_active is updated so rings
+ * started later inherit the correct state.
+ */
+static void
+enetc4_vf_set_congestion_mode(struct rte_eth_dev *eth_dev, bool enable)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+	uint16_t i;
+	uint32_t rbmr;
+
+	hw->tx_pause_active = enable ? 1 : 0;
+
+	for (i = 0; i < nb_rx; i++) {
+		rbmr = enetc4_rxbdr_rd(enetc_hw, i, ENETC_RBMR);
+		if (enable)
+			rbmr |= ENETC_RBMR_CM;
+		else
+			rbmr &= ~(uint32_t)ENETC_RBMR_CM;
+		enetc4_rxbdr_wr(enetc_hw, i, ENETC_RBMR, rbmr);
+	}
+
+	ENETC_PMD_DEBUG("VF congestion mode %s on %u RX rings",
+			enable ? "enabled" : "disabled", nb_rx);
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
@@ -468,6 +499,7 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
+	bool tx_pause;
 	int ret = 0;
 
 	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
@@ -483,9 +515,24 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		if (msg->status & ENETC_LINK_DOWN) {
 			ENETC_PMD_DEBUG("Link is down");
 			link.link_status = RTE_ETH_LINK_DOWN;
+			/* Clear congestion mode on link-down so VF rings do not
+			 * assert congestion while the port is offline.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, false);
 		} else {
-			ENETC_PMD_DEBUG("Link is up");
+			/* BIT(1) is set when the port has negotiated TX PAUSE.
+			 * Legacy PF does not set this bit so tx_pause stays false.
+			 */
+			tx_pause = !!(msg->status & ENETC_LINK_TX_PAUSE);
+			ENETC_PMD_DEBUG("Link is up, tx_pause=%d", tx_pause);
 			link.link_status = RTE_ETH_LINK_UP;
+
+			/* Apply congestion mode before raising the carrier so
+			 * the VF rings are ready to emit PAUSE before traffic
+			 * starts flowing.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, tx_pause);
+
 			/* Re-query speed from PF so the cached value reflects
 			 * the current negotiated speed after link-up.
 			 */
@@ -1174,10 +1221,16 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		if (reply_msg->status & ENETC_LINK_DOWN)
+		if (reply_msg->status & ENETC_LINK_DOWN) {
 			link.link_status = RTE_ETH_LINK_DOWN;
-		else
+			/* Link is down: disable congestion mode on all RX rings. */
+			enetc4_vf_set_congestion_mode(dev, false);
+		} else {
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Restore congestion mode from the TX PAUSE bit. */
+			enetc4_vf_set_congestion_mode(dev,
+				!!(reply_msg->status & ENETC_LINK_TX_PAUSE));
+		}
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v4 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (12 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
@ 2026-08-07 11:26       ` Gagandeep Singh
  2026-08-09 21:00       ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Stephen Hemminger
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
  15 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-07 11:26 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add enetc4_txq_wrr devarg to configure per-ring WRR weights in the
NETC LEAF-level Tx scheduler (TBaMR register, bits [6:4]).

The NETC Tx scheduler has three levels:
  - ROOT (port/TC): strict priority + CBS (PF/port space)
  - MID  (SI/VSI):  WBFS shaping (PF space)
  - LEAF (Tx BDR):  strict priority + frame-based WRR (VF/SI space)

TBaMR is in the VF own SI space, so no Linux PF involvement is
needed for PRIO or WRR configuration.

Changes:
- enetc_hw.h: add ENETC_TBMR_WRR_MASK, ENETC_TBMR_WRR(n) macros for
  TBaMR bits [6:4], and ENETC_TBMR_PRIO_MASK for bits [2:0]
- enetc.h: add txq_wrr pointer to enetc_eth_hw struct
- enetc4_ethdev.c: add parse_txq_wrr() and wire ENETC4_TXQ_WRR devarg
  through enetc4_get_devargs() and enetc4_dev_configure(); apply WRR
  bits in enetc4_tx_queue_setup() and enetc4_tx_queue_start()

Usage:
  # strict priority: ring 0 highest
  -a 0002:00:12.0,enetc4_txq_prior="3|2|1"

  # WRR 2:4:1 on same-priority rings
  -a 0002:00:12.0,enetc4_txq_prior="1|1|1",enetc4_txq_wrr="2|4|1"

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  5 ++
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      | 97 ++++++++++++++++++++------
 4 files changed, 82 insertions(+), 22 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index d69888aafb..3ea34523fe 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -73,6 +73,7 @@ New Features
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
   * Added TX PAUSE support for the ENETC4 VF via RX congestion mode.
+  * Added WRR Tx scheduler devarg (``enetc4_txq_wrr``) for ENETC4 VF ring weights.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 33d075fe59..5a71f01db9 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -86,6 +86,11 @@ enum enetc_bdr_type {TX, RX};
 
 #define ENETC_RTBLENR_LEN(n)		((n) & ~0x7)
 #define ENETC_TBMR_EN			BIT(31)
+/* TBaMR[WRR] bits [6:4]: weight for same-priority ring arbitration (0=1x .. 7=8x). */
+#define ENETC_TBMR_WRR_MASK		GENMASK(6, 4)
+#define ENETC_TBMR_WRR(n)		((((n) - 1) & 0x7) << 4)
+/* TBaMR[PRIO] bits [2:0]: strict priority (0=lowest, 7=highest). */
+#define ENETC_TBMR_PRIO_MASK		GENMASK(2, 0)
 
 /* Port regs, offset: 1_0000h */
 #define ENETC_PORT_BASE			0x10000
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 8aaf8a7eb1..e0943d093a 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -134,6 +134,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_timeout; /* VSI-PSI message wait timeout (iterations) */
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
+	uint32_t *txq_wrr;    /* per-queue TX WRR weight pre-shifted for TBMR[WRR] */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
 	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 2355522515..e490ecaf4c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -28,22 +28,26 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
+#define ENETC4_TXQ_WRR		"enetc4_txq_wrr"
 #define ENETC4_NC_MEMORY	"nc"
 
+
 static int
 parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 {
 	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
 	struct enetc_eth_hw *hw =
-		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
-	char *input_str = strdup(value);
+				ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
 	char *str;
 	uint32_t i = 0;
 
+	input_str = strdup(value);
 	if (!input_str)
-		return -ENOMEM;
+		return -1;
 
-	hw->txq_prior = calloc(hw->max_tx_queues, sizeof(uint32_t));
+	rte_free(hw->txq_prior);
+	hw->txq_prior = rte_zmalloc(NULL, hw->max_tx_queues * sizeof(uint32_t), 0);
 	if (!hw->txq_prior) {
 		free(input_str);
 		return -ENOMEM;
@@ -51,7 +55,46 @@ parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 
 	str = strtok(input_str, "|");
 	while (str != NULL && i < hw->max_tx_queues) {
-		hw->txq_prior[i++] = (uint32_t)atoi(str);
+		hw->txq_prior[i++] = atoi(str) & ENETC_TBMR_PRIO_MASK;
+		str = strtok(NULL, "|");
+	}
+
+	free(input_str);
+	return 0;
+}
+
+/* Parse enetc4_txq_wrr="w0|w1|..." devarg; weight 1..8 per ring. */
+static int parse_txq_wrr(const char *key __rte_unused, const char *value,
+			  void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
+	char *str;
+	uint32_t i = 0;
+	int w;
+
+	input_str = strdup(value);
+	if (!input_str)
+		return -1;
+
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = rte_zmalloc(NULL,
+			hw->max_tx_queues * sizeof(uint32_t), 0);
+	if (!hw->txq_wrr) {
+		free(input_str);
+		return -1;
+	}
+
+	str = strtok(input_str, "|");
+	while (str != NULL && i < hw->max_tx_queues) {
+		w = atoi(str);
+		if (w < 1)
+			w = 1;
+		if (w > 8)
+			w = 8;
+		hw->txq_wrr[i++] = ENETC_TBMR_WRR(w);
 		str = strtok(NULL, "|");
 	}
 
@@ -97,6 +140,13 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 			return 0;
 		}
 	}
+	if (!strcmp(key, ENETC4_TXQ_WRR)) {
+		if (rte_kvargs_process(kvlist, key,
+				       parse_txq_wrr, (void *)dev) < 0) {
+			rte_kvargs_free(kvlist);
+			return 0;
+		}
+	}
 	if (!strcmp(key, ENETC4_NC_MEMORY)) {
 		if (rte_kvargs_process(kvlist, key,
 				       parse_nc, (void *)dev) < 0) {
@@ -109,19 +159,6 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 	return 0;
 }
 
-/* Supported Rx offloads */
-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_SCATTER;
-
-/* Supported Tx offloads */
-static uint64_t dev_tx_offloads_sup =
-	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
-	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
 
 static int
 enetc4_dev_start(struct rte_eth_dev *dev)
@@ -458,7 +495,9 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 
 		/* apply TX queue priority if configured */
 		if (priv->hw.txq_prior)
-			tx_en |= priv->hw.txq_prior[tx_ring->index];
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		/* enable ring */
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index,
 			       ENETC_TBMR, tx_en);
@@ -613,7 +652,6 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			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;
@@ -869,7 +907,10 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		dev->data->tx_queues[i] = NULL;
 	}
 	dev->data->nb_tx_queues = 0;
-
+	rte_free(hw->txq_prior);
+	hw->txq_prior = NULL;
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = NULL;
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_depopulate();
 
@@ -1020,6 +1061,11 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	for (i = 0; i < dev->data->nb_tx_queues; i++)
 		enetc4_rxbdr_wr(enetc_hw, i, ENETC_TBMR, ENETC_BMR_RESET);
 
+	hw->nc_mode = 0;
+	enetc4_get_devargs(dev, ENETC4_TXQ_PRIORITIES);
+	enetc4_get_devargs(dev, ENETC4_TXQ_WRR);
+	enetc4_get_devargs(dev, ENETC4_NC_MEMORY);
+
 	if (dev->data->nb_rx_queues <= 1)
 		return 0;
 
@@ -1142,7 +1188,13 @@ enetc4_tx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->tx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		tx_data = enetc4_txbdr_rd(&priv->hw.hw, tx_ring->index,
 					 ENETC_TBMR);
-		tx_data = tx_data | ENETC_TBMR_EN;
+		/* Clear scheduler bits before applying fresh devarg values. */
+		tx_data &= ~(ENETC_TBMR_PRIO_MASK | ENETC_TBMR_WRR_MASK);
+		tx_data |= ENETC_TBMR_EN;
+		if (priv->hw.txq_prior)
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index, ENETC_TBMR,
 			       tx_data);
 		dev->data->tx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
@@ -1457,5 +1509,6 @@ RTE_PMD_REGISTER_PCI_TABLE(net_enetc4, pci_id_enetc4_map);
 RTE_PMD_REGISTER_KMOD_DEP(net_enetc4, "* vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4,
 			      ENETC4_TXQ_PRIORITIES "=<string> "
+			      ENETC4_TXQ_WRR "=<string> "
 			      ENETC4_NC_MEMORY "=<int>");
 RTE_LOG_REGISTER_DEFAULT(enetc4_logtype_pmd, NOTICE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* Re: [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (13 preceding siblings ...)
  2026-08-07 11:26       ` [PATCH v4 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
@ 2026-08-09 21:00       ` Stephen Hemminger
  2026-08-10 10:58         ` Gagandeep Singh
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
  15 siblings, 1 reply; 110+ messages in thread
From: Stephen Hemminger @ 2026-08-09 21:00 UTC (permalink / raw)
  To: Gagandeep Singh; +Cc: dev, hemant.agrawal

On Fri,  7 Aug 2026 16:56:09 +0530
Gagandeep Singh <g.singh@nxp.com> wrote:

> V4-changes:
>  - fix doc build issue: WARNING: undefined label: pmd_build_and_test
> 
> v3-changes:
>  - fix doc build issue.
>  - fix compilation issue on fedore:43-gcc-minsize
> 
> V2-changes:
>  - compilation fixes.
> 
> V1-changes:
> This series adds new PMD features to the ENETC4 driver targeting the
> NXP i.MX95 NETC IP.
> 
> The series covers:
> 
>  - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
>  - TSO: TCP Segmentation Offload for the VF Tx path.
>  - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
>  - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
>    8-bit to support speeds beyond 10G.
>  - Firmware version: report the NETC IP version via fw_version_get.
>  - Register dump: dump SI, port (PF) and BD ring registers.
>  - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
>  - Link-up interrupt: refresh the cached link speed on each VF link-up
>    interrupt so that link_update returns the current speed immediately.
>  - Stats reset: software snapshot/delta approach for VF counter reset.
>  - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
>    enabling interrupt-driven receive with l3fwd-power.
>  - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
>  - VF link status bitmask: switch VF link status to bitmask encoding
>    to align with the PF and newer kernel driver conventions.
>  - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
>    negotiated on the wire; adds Flow control = Y to enetc4.ini.
>  - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.
> 
> Gagandeep Singh (14):
>   net/enetc: add KEEP_CRC offload support for ENETC4
>   net/enetc: add TSO support for ENETC4 VF
>   net/enetc: add RSC (hardware LRO) support for ENETC4
>   net/enetc: extend link speed code field to 8-bit for PF-to-VF message
>   net/enetc: support firmware version get for VF
>   net/enetc: support registers dump
>   net/enetc: support ethtool ring parameters
>   net/enetc: refresh link speed on VF link-up interrupt
>   net/enetc: support stats reset for VF
>   net/enetc4: add per-queue Rx interrupt support for VF
>   net/enetc4: add SI-based port VLAN insertion and removal
>   net/enetc4: update VF link status to bitmask encoding
>   net/enetc4: enable TX PAUSE via VF RX congestion mode
>   net/enetc4: add WRR Tx scheduler devarg for VF rings
> 
>  doc/guides/nics/enetc4.rst             |  77 +++
>  doc/guides/nics/features/enetc4.ini    |   8 +
>  doc/guides/rel_notes/release_26_11.rst |  19 +
>  drivers/net/enetc/base/enetc4_hw.h     | 129 +++-
>  drivers/net/enetc/base/enetc_hw.h      |   6 +
>  drivers/net/enetc/enetc.h              | 135 ++++-
>  drivers/net/enetc/enetc4_ethdev.c      | 402 +++++++++++--
>  drivers/net/enetc/enetc4_vf.c          | 779 ++++++++++++++++++++++---
>  drivers/net/enetc/enetc_rxtx.c         | 529 ++++++++++++++++-
>  9 files changed, 1939 insertions(+), 145 deletions(-)
> 

Still lots of issues, the biggest one is that not each patch
builds; not bisectable.

More extensive AI review.

ENETC4 v4 series review - 14 patches

No Reviewed-by.  The headline finding from the previous round is
unchanged: 13 of the 14 commits fail to build.  Only the final commit
is clean, so git bisect is broken across essentially the whole series.

Build (applied on c1a46b9, gcc 13.3, -Dwerror=true):

  [1] 8a0a793 01/14 add KEEP_CRC offload support for ENETC4
  [1] 63cae7e 02/14 add TSO support for ENETC4 VF
  [1] b28e1a8 03/14 add RSC (hardware LRO) support
      ... all fail ...
  [1] e334f6c 13/14 enable TX PAUSE via VF RX congestion mode
  [0] b5e71d6 14/14 add WRR Tx scheduler devarg for VF rings

Three distinct causes, all introduced in patch 01.


Patch 01

Error: prev_seg used but never declared (fails commits 01-02).

  enetc_rxtx.c:597:25: error: 'prev_seg' undeclared
  enetc_rxtx.c:844:25: error: 'prev_seg' undeclared

Patch 01 assigns prev_seg and passes it to enetc_rx_crc_trim() in both
enetc_clean_rx_ring_nc() and enetc_clean_rx_ring_cacheable(), but the
declaration hunk only changes first_seg/cur_seg to = NULL.  The
*prev_seg = NULL declaration does not arrive until patch 03.  Move it
to patch 01.

Error: duplicate offload arrays (fails commits 01-13).

  enetc4_ethdev.c:110:17: error: redefinition of 'dev_rx_offloads_sup'
  enetc4_ethdev.c:117:17: error: redefinition of 'dev_tx_offloads_sup'

Patch 01 adds a second copy of both arrays at the top of
enetc4_ethdev.c; the pre-existing ones near line 97 are not deleted
until patch 14.  The deletion hunk belongs in patch 01.

Error: duplicate rx_enable declaration (fails commits 01-13).

  enetc4_ethdev.c:552:18: error: redeclaration of 'rx_enable' with no
  linkage

enetc4_rx_queue_setup() already declares uint32_t rx_enable upstream.
Patch 01 adds a second one; the removal is in patch 14.

This is the signature of building only the squashed series.  Please run
devtools/test-meson-builds.sh, or at minimum a -Dwerror=true build at
each commit, before v5.

Error: PF loses Scattered Rx and Multi-segment Tx.

The old arrays deleted in patch 14 contained
RTE_ETH_RX_OFFLOAD_SCATTER and RTE_ETH_TX_OFFLOAD_MULTI_SEGS.  The
replacements added in patch 01 do not.  Net result at the tip:

  upstream                      after the series
  RX: IPV4|UDP|TCP|SCATTER      RX: IPV4|UDP|TCP|KEEP_CRC|TCP_LRO
  TX: IPV4|UDP|TCP|MULTI_SEGS   TX: IPV4|UDP|TCP|TCP_TSO|UDP_TSO

Nothing in any commit message says this is intentional.  enetc4.ini
still declares "Scattered Rx = Y", and the new RSC path builds
multi-segment clusters, so the capability is still needed.  The VF
arrays keep both - only the PF regresses.


Patch 02

Error: mbuf leak in enetc_xmit_pkts_lso().

Two validation paths drop a TSO frame with "start++; continue;":

  - hdr_len >= pkt_len || hdr_len > data_len
  - tso_segsz == 0 || data_unit > ENETC4_LSO_MAX_DATA_UNIT ||
    hdr_len + tso_segsz > ENETC4_LSO_MAX_FRAME

Neither writes q_swbd[i].buffer_addr, and neither frees the mbuf, but
both count the packet in the burst return value.  The application
considers it transmitted; enetc_clean_tx_ring() never sees it.
Free-and-continue is the right shape here - add rte_pktmbuf_free(seg)
before each start++.

Error: 16-bit byte swap on a uint8_t field.

In the non-TSO path of the same function:

  txbd->flags |= rte_cpu_to_le_16(ENETC4_TXBD_FLAGS_F);

struct enetc_tx_bd.flags is uint8_t.  On big-endian this evaluates to
0x8000 and truncates to 0, so the frame-last flag is never set and Tx
stalls.  Upstream and the LSO path in this very function both use plain
"txbd->flags |= ENETC4_TXBD_FLAGS_F;".  Drop the conversion.

Warning: ring doubling bypasses MAX_BD_COUNT.

Both enetc4_alloc_txbdr() (LSO, patch 02) and enetc4_alloc_rxbdr()
(RSC, patch 03) double nb_desc, but the nb_desc > MAX_BD_COUNT check
runs before the doubling.  With MAX_BD_COUNT at 64000 the effective
ring reaches 128000 descriptors, and ENETC_RTBLENR_LEN(n) is just
((n) & ~0x7) with no upper clamp.  Validate the post-doubling count, or
halve the accepted nb_desc when LSO/RSC is on.


Patch 04

Warning: link speed decode has no upper bound.

  link.link_speed = (reply_msg->status - ENETC_SPEED_5000) * 1000 + 5000;

status is uint8_t and the default arm now catches everything from 8 to
255, so a garbage or unrecognized code yields a fabricated speed
(0xFF -> 253000 Mbps) rather than RTE_ETH_SPEED_NUM_UNKNOWN.  Note also
that 0xF, previously ENETC_SPEED_NOT_SUPPORTED, now decodes as 13000
Mbps in non-legacy mode.  Bound the result, or reject codes that do not
map to a defined RTE_ETH_SPEED_NUM_* value.

Warning: flipping the reply-status extraction from 4-bit to 8-bit by
default is a wire-protocol compatibility break.  Users on a PF kernel
older than 6.18.37 must add vf_link_legacy=1 or link reporting breaks
silently.  That deserves an explicit line in the release notes, not
just "extended the field to 8-bit".

Info: adds a second #include <rte_kvargs.h> immediately below the
existing one.


Series-wide

Warning: mailbox ops added to the no-VSI ops table.

enetc4_vf_ops_no_vsi_m is selected when the user passes
enetc4_vsi_disable, that is, explicitly asks for no VSI-PSI messaging.
The series adds fw_version_get, mac_addr_set, mac_addr_add,
promiscuous_*, allmulticast_*, vlan_filter_set, vlan_offload_set,
vlan_pvid_set and stats_reset to it.  Most of those reach
enetc4_msg_vsi_send() directly or through a helper, so they will now
attempt a transaction on the disabled mailbox and stall until
vsi_timeout.  Previously ethdev returned -ENOTSUP immediately.  Only
the genuinely register-local ops (get_reg, rxq_info_get, txq_info_get,
and stats_reset if it stays direct-register) belong in that table.

Warning: new devargs undocumented.

vf_link_legacy (patch 04) and enetc4_txq_wrr (patch 14) are both in
RTE_PMD_REGISTER_PARAM_STRING but absent from
doc/guides/nics/enetc4.rst, which documents every pre-existing devarg.
Same gap flagged in the previous round.

Info: patch 01 adds two stray blank lines in enetc4_ethdev.c (one
before enetc4_rx_queue_release, later removed in patch 03); patch 02
adds three more around enetc4_alloc_txbdr/enetc4_free_bdr and in
struct enetc_bdr.

Info: enetc_recv_pkts_rsc is assigned to dev->rx_pkt_burst from
enetc4_rx_queue_setup(), which will silently override the nc=1 burst
selection made in dev_init.  Worth a guard or at least a comment on
precedence.

I stopped short of a full read of patches 05-14 since the build
breakage has to be resolved first and several of these findings will
shift hunks around.  I can go back through 05-14 in detail once there
is a v5, or now if you would rather have the complete list before the
author respins.


^ permalink raw reply	[flat|nested] 110+ messages in thread

* [PATCH v5 00/14] net/enetc: add new features for ENETC4 on i.MX95
  2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                         ` (14 preceding siblings ...)
  2026-08-09 21:00       ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Stephen Hemminger
@ 2026-08-10 10:48       ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 01/14] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
                           ` (16 more replies)
  15 siblings, 17 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal

V4-changes:
 - fix doc build issue: WARNING: undefined label: pmd_build_and_test

v3-changes:
 - fix doc build issue.
 - fix compilation issue on fedore:43-gcc-minsize

V2-changes:
 - compilation fixes.

V1-changes:
This series adds new PMD features to the ENETC4 driver targeting the
NXP i.MX95 NETC IP.

The series covers:

 - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
 - TSO: TCP Segmentation Offload for the VF Tx path.
 - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
 - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
   8-bit to support speeds beyond 10G.
 - Firmware version: report the NETC IP version via fw_version_get.
 - Register dump: dump SI, port (PF) and BD ring registers.
 - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
 - Link-up interrupt: refresh the cached link speed on each VF link-up
   interrupt so that link_update returns the current speed immediately.
 - Stats reset: software snapshot/delta approach for VF counter reset.
 - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
   enabling interrupt-driven receive with l3fwd-power.
 - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
 - VF link status bitmask: switch VF link status to bitmask encoding
   to align with the PF and newer kernel driver conventions.
 - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
   negotiated on the wire; adds Flow control = Y to enetc4.ini.
 - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.

Gagandeep Singh (14):
  net/enetc: add keep-CRC Rx offload for ENETC4
  net/enetc: add TSO support for ENETC4 VF
  net/enetc: add RSC (hardware LRO) support for ENETC4
  net/enetc: extend PF-VF link speed field to 8 bits
  net/enetc: support firmware version get for VF
  net/enetc: support registers dump
  net/enetc: support ethtool ring parameters
  net/enetc: refresh link speed on VF link-up interrupt
  net/enetc: support stats reset for VF
  net/enetc4: add per-queue Rx interrupt support for VF
  net/enetc4: add SI-based port VLAN insertion and removal
  net/enetc4: update VF link status to bitmask encoding
  net/enetc4: enable Tx PAUSE via VF Rx congestion mode
  net/enetc4: add WRR Tx scheduler devarg for VF rings

 doc/guides/nics/enetc4.rst             | 100 +++
 doc/guides/nics/features/enetc4.ini    |   8 +
 doc/guides/rel_notes/release_26_11.rst |  21 +
 drivers/net/enetc/base/enetc4_hw.h     | 129 +++-
 drivers/net/enetc/base/enetc_hw.h      |   6 +
 drivers/net/enetc/enetc.h              | 134 ++++-
 drivers/net/enetc/enetc4_ethdev.c      | 413 +++++++++++--
 drivers/net/enetc/enetc4_vf.c          | 801 ++++++++++++++++++++++---
 drivers/net/enetc/enetc_rxtx.c         | 531 +++++++++++++++-
 9 files changed, 1998 insertions(+), 145 deletions(-)

-- 
2.25.1


^ permalink raw reply	[flat|nested] 110+ messages in thread

* [PATCH v5 01/14] net/enetc: add keep-CRC Rx offload for ENETC4
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
                           ` (15 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The legacy ENETC (LS1028A) driver already supports the
RTE_ETH_RX_OFFLOAD_KEEP_CRC offload, but ENETC4 (i.MX95 NETC) did not.

Add KEEP_CRC support for ENETC4 (both PF and VF):

- Advertise RTE_ETH_RX_OFFLOAD_KEEP_CRC in the supported Rx offloads
  for the ENETC4 PF and VF. The VF reuses the PF Rx queue setup, so the
  HW configuration and datapath handling apply to both.
- Configure the per-ring RBaMR[CRC] bit in the Rx queue setup so that
  HW preserves the Ethernet FCS in the receive buffer when the offload
  is requested (0 = FCS removed, 1 = FCS preserved).
- Follow the DPDK convention used by the legacy ENETC and ixgbe drivers:
  set crc_len to RTE_ETHER_CRC_LEN when KEEP_CRC is enabled and have the
  datapath subtract crc_len from pkt_len/data_len. ENETC and ENETC4 share
  the same datapath (enetc_rxtx.c), so the semantics stay consistent.
- Handle the scatter-gather boundary case where the 4-byte FCS straddles
  the last two segments: drop the trailing segment, decrement nb_segs and
  trim the carry-over from its predecessor.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  6 ++++
 drivers/net/enetc/base/enetc4_hw.h     |  4 +++
 drivers/net/enetc/enetc4_ethdev.c      | 45 ++++++++++++++++----------
 drivers/net/enetc/enetc4_vf.c          |  1 +
 drivers/net/enetc/enetc_rxtx.c         | 39 +++++++++++++++++++---
 6 files changed, 75 insertions(+), 21 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 698140e30b..91b18d979e 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ Packet type parsing  = Y
 Basic stats          = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
+CRC offload          = Y
 Queue start/stop     = Y
 Scattered Rx         = Y
 Linux                = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..3678dd6894 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -56,6 +56,12 @@ New Features
      =======================================================
 
 
+* **Updated NXP ENETC4 PMD.**
+
+  Updated the NXP ENETC4 poll mode driver for i.MX95:
+
+  * Added KEEP_CRC Rx offload support for the ENETC4 PMD to preserve the Ethernet FCS.
+
 Removed Items
 -------------
 
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 9685e7e1e5..0105549857 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -68,6 +68,10 @@ struct enetc_msg_swbd {
 #define PM_CMD_CFG_TX_EN		BIT(0)
 #define PM_CMD_CFG_RX_EN		BIT(1)
 
+/* RBaMR[CRC]: 0 = FCS removed, 1 = FCS preserved (KEEP_CRC) */
+#define ENETC4_RBMR_CRC			BIT(8)
+
+
 /* i.MX95 supports jumbo frame, but it is recommended to set the max frame
  * size to 2000 bytes.
  */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 2ddd63dafb..91e2cdb129 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -11,6 +11,21 @@
 #include "enetc_logs.h"
 #include "enetc.h"
 
+/* Supported Rx offloads */
+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_SCATTER |
+	RTE_ETH_RX_OFFLOAD_KEEP_CRC;
+
+/* Supported Tx offloads */
+static uint64_t dev_tx_offloads_sup =
+	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
+	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
+
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
 
@@ -93,20 +108,6 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 	return 0;
 }
 
-/* Supported Rx offloads */
-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_SCATTER;
-
-/* Supported Tx offloads */
-static uint64_t dev_tx_offloads_sup =
-	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
-	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
-
 static int
 enetc4_dev_start(struct rte_eth_dev *dev)
 {
@@ -536,6 +537,7 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			ENETC_DEV_PRIVATE(data->dev_private);
 	uint64_t rx_offloads = data->dev_conf.rxmode.offloads;
+	bool keep_crc;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_rx_desc > MAX_BD_COUNT)
@@ -549,6 +551,9 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	rx_ring->index = rx_queue_id;
+	keep_crc = !!(rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC);
+	rx_ring->crc_len = (uint8_t)(keep_crc ? RTE_ETHER_CRC_LEN : 0);
+
 	err = enetc4_alloc_rxbdr(rx_ring, nb_rx_desc);
 	if (err)
 		goto fail;
@@ -562,19 +567,25 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	data->rx_queues[rx_queue_id] = rx_ring;
 	rx_ring->rx_deferred_start = rx_conf->rx_deferred_start;
 
+	if (keep_crc)
+		rx_enable |= ENETC4_RBMR_CRC;
+	else
+		rx_enable &= ~ENETC4_RBMR_CRC;
+
 	if (!rx_conf->rx_deferred_start) {
 		/* enable ring */
+		rx_enable |= ENETC_RBMR_EN;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
-			       ENETC_RBMR_EN);
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STARTED;
 	} else {
+		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STOPPED;
 	}
 
-	rx_ring->crc_len = (uint8_t)((rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC) ?
-				     RTE_ETHER_CRC_LEN : 0);
 	return 0;
 fail:
 	rte_free(rx_ring);
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index ef5f1e6d66..83a1e4931f 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -52,6 +52,7 @@ static uint64_t dev_rx_offloads_sup =
 	RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_VLAN_FILTER |
+	RTE_ETH_RX_OFFLOAD_KEEP_CRC |
 	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 8678bafece..d5855e69a3 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -530,6 +530,28 @@ enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
 	return rx_frm_cnt;
 }
 
+/*
+ * Trim the Ethernet FCS from a received cluster when HW CRC strip is
+ * disabled. pkt_len is reduced by crc_len. If the FCS straddles the last
+ * two segments (last seg holds fewer bytes than crc_len), drop the trailing
+ * segment and trim the carry-over from its predecessor. prev_seg is the
+ * segment preceding last_seg in the chain (the caller already tracks it).
+ */
+static inline void
+enetc_rx_crc_trim(struct rte_mbuf *first_seg, struct rte_mbuf *prev_seg,
+		  struct rte_mbuf *last_seg, uint16_t crc_len)
+{
+	first_seg->pkt_len -= crc_len;
+	if (likely(last_seg->data_len > crc_len)) {
+		last_seg->data_len -= crc_len;
+	} else if (prev_seg != NULL) {
+		first_seg->nb_segs--;
+		prev_seg->data_len -= crc_len - last_seg->data_len;
+		prev_seg->next = NULL;
+		rte_pktmbuf_free_seg(last_seg);
+	}
+}
+
 static int
 enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		    struct rte_mbuf **rx_pkts,
@@ -539,7 +561,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL, *prev_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -572,6 +594,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg, rxbd_temp.r.parse_summary);
 			first_seg->hash.rss = rxbd_temp.r.rss_hash;
@@ -579,6 +602,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -590,7 +614,9 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
@@ -760,7 +786,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL, *prev_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -815,6 +841,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg,
 					   rxbd->r.parse_summary);
@@ -823,6 +850,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -838,7 +866,10 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
+
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 02/14] net/enetc: add TSO support for ENETC4 VF
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 01/14] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
                           ` (14 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add TCP and UDP segmentation offload (TSO) for the ENETC4 virtual
function. The offload is advertised through the VF Tx capabilities and
is enabled on a per-queue basis when an application requests the TSO
offload flags during Tx queue setup.

A dedicated Tx burst, enetc_xmit_pkts_lso(), builds the large-send
descriptor chain: a standard header BD, a 16B extension BD carrying the
segment size and frame length extension, and one BD per payload buffer
with the final-frame flag on the last BD. To make room for the extra
extension BD, an LSO-enabled Tx ring is allocated with twice the number
of descriptors. The existing non-TSO Tx paths are left unchanged.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   2 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/base/enetc4_hw.h     |  35 +++-
 drivers/net/enetc/enetc.h              |   4 +
 drivers/net/enetc/enetc4_ethdev.c      |  49 ++++-
 drivers/net/enetc/enetc4_vf.c          |   2 +
 drivers/net/enetc/enetc_rxtx.c         | 257 +++++++++++++++++++++++++
 8 files changed, 346 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index f0dd039f6e..4d27e4048e 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -53,6 +53,8 @@ Key functionality includes:
 - Receive processing: Upon packet reception, the BD Ring status bit is set,
   facilitating packet processing.
 - 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.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 91b18d979e..8ec1e8d00f 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
+TSO                  = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 3678dd6894..6a348ab4e0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -61,6 +61,7 @@ New Features
   Updated the NXP ENETC4 poll mode driver for i.MX95:
 
   * 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.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 0105549857..64c1a5e750 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2024 NXP
+ * Copyright 2024-2026 NXP
  *
  * This header file defines the register offsets and bit fields
  * of ENETC4 PF and VFs.
@@ -24,8 +24,14 @@ struct enetc_msg_swbd {
 
 /* enetc4 txbd flags */
 #define ENETC4_TXBD_FLAGS_L4CS		BIT(0)
+/* Request LSO (Large Send Offload) segmentation for this frame */
+#define ENETC4_TXBD_FLAGS_LSO		BIT(1)
+/* L4 checksum insertion (also used for checksum update on LSO) */
 #define ENETC4_TXBD_FLAGS_L_TX_CKSUM	BIT(3)
+/* Extended descriptor: the next 16B ring entry is an extension BD */
+#define ENETC4_TXBD_FLAGS_EXT		BIT(6)
 #define ENETC4_TXBD_FLAGS_F		BIT(7)
+
 /* L4 type */
 #define ENETC4_TXBD_L4T_UDP		BIT(0)
 #define ENETC4_TXBD_L4T_TCP		BIT(1)
@@ -34,6 +40,33 @@ struct enetc_msg_swbd {
 /* IPv4 checksum */
 #define ENETC4_TXBD_IPCS		1
 
+/*
+ * Extension Transmit Buffer Descriptor (16B). When the standard BD has the
+ * extended flag set, this occupies the next ring entry and carries the extra
+ * fields required by LSO.
+ */
+struct enetc_tx_bd_ext {
+	uint32_t timestamp;	/* PTP timestamp, unused for LSO */
+	uint32_t vlan;		/* VLAN insert, unused for LSO */
+	uint32_t lso;		/* LSO_MAX_SEG_SIZE and FRM_LEN_EXT */
+	uint32_t flags;		/* extension flags */
+};
+
+/* LSO_MAX_SEG_SIZE occupies bits 13-0 of the LSO word */
+#define ENETC4_TXBD_EXT_LSO_SEG_MASK	0x3fff
+#define ENETC4_TXBD_EXT_LSO_SEG(mss) \
+		((uint32_t)((mss) & ENETC4_TXBD_EXT_LSO_SEG_MASK))
+/* FRM_LEN_EXT occupies bits 19-16 of the LSO word (top 4 bits of data len) */
+#define ENETC4_TXBD_EXT_FRM_LEN_EXT(x) \
+		(((uint32_t)((x) & 0xf)) << 16)
+/* Final flag in the extension flags word (bit 127 -> local bit 31) */
+#define ENETC4_TXBD_EXT_FLAGS_F		BIT(31)
+
+/* NETC does not create LSO frames larger than this many bytes */
+#define ENETC4_LSO_MAX_FRAME		9600
+/* Maximum LSO data unit (payload to be segmented) supported by HW: 256KB */
+#define ENETC4_LSO_MAX_DATA_UNIT	(256 * 1024)
+
 /***************************ENETC port registers**************************/
 #define ENETC4_PMR		0x10
 #define ENETC4_PMR_EN		(BIT(16) | BIT(17) | BIT(18))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index d3a8b8e34a..62bedc2854 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -99,6 +99,7 @@ struct enetc_bdr {
 	uint64_t ierrors;
 	uint8_t rx_deferred_start;
 	uint8_t tx_deferred_start;
+	uint8_t lso_enable;
 };
 
 struct enetc_eth_hw {
@@ -312,6 +313,9 @@ uint16_t enetc_xmit_pkts(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
 uint16_t enetc_xmit_pkts_nc(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
+uint16_t enetc_xmit_pkts_lso(void *txq, struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts);
+
 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,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 91e2cdb129..24c454fe28 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -24,7 +24,9 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
+	RTE_ETH_TX_OFFLOAD_MULTI_SEGS |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
@@ -309,22 +311,37 @@ static int
 enetc4_alloc_txbdr(struct enetc_bdr *txr, uint16_t nb_desc)
 {
 	int size;
+	uint32_t ring_desc;
 
-	size = nb_desc * sizeof(struct enetc_swbd);
+	/*
+	 * On LSO-enabled rings each frame uses an extra 16B extension BD
+	 * (logical 32B descriptor) plus the payload BDs.  Size the ring with
+	 * twice the requested entries so the effective frame capacity is
+	 * preserved.  Non-LSO rings are sized exactly as requested.
+	 */
+	ring_desc = txr->lso_enable ? (uint32_t)nb_desc * 2 : (uint32_t)nb_desc;
+	if (ring_desc > MAX_BD_COUNT) {
+		ENETC_PMD_ERR("LSO ring_desc %u > MAX_BD_COUNT %u; "
+			      "reduce nb_desc to <= %u with LSO enabled",
+			      ring_desc, MAX_BD_COUNT, MAX_BD_COUNT / 2);
+		return -EINVAL;
+	}
+
+	size = ring_desc * sizeof(struct enetc_swbd);
 	/* Zero q_swbd so buffer_addr is NULL for all uninitialized slots. */
 	txr->q_swbd = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->q_swbd == NULL)
 		return -ENOMEM;
 
 	/* Allocate the TX BD ring: each BD is struct enetc_tx_bd (16 bytes). */
-	size = nb_desc * sizeof(struct enetc_tx_bd);
+	size = ring_desc * sizeof(struct enetc_tx_bd);
 	txr->bd_base = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->bd_base == NULL) {
 		rte_free(txr->q_swbd);
 		txr->q_swbd = NULL;
 		return -ENOMEM;
 	}
-	txr->bd_count = nb_desc;
+	txr->bd_count = ring_desc;
 	txr->next_to_clean = 0;
 	txr->next_to_use = 0;
 
@@ -376,6 +393,7 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	struct rte_eth_dev_data *data = dev->data;
 	struct enetc_eth_adapter *priv =
 			ENETC_DEV_PRIVATE(data->dev_private);
+	uint64_t tx_offloads;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_desc > MAX_BD_COUNT)
@@ -389,6 +407,29 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	tx_ring->index = queue_idx;
+	/*
+	 * LSO (TCP/UDP segmentation) is a port-level offload. The Tx burst is
+	 * a single device-level function pointer, so it must be consistent for
+	 * all queues; likewise every ring must be sized the same way. Decide
+	 * from the port offloads only (not the per-queue tx_conf) so that all
+	 * queues either use the LSO burst with a doubled ring, or none do.
+	 * The LSO burst also handles non-TSO packets, so it is safe on queues
+	 * that never carry TSO traffic. LSO needs HW FCS insertion, so it is
+	 * incompatible with KEEP_CRC on Rx.
+	 */
+	tx_offloads = data->dev_conf.txmode.offloads;
+	if (tx_offloads & (RTE_ETH_TX_OFFLOAD_TCP_TSO |
+			   RTE_ETH_TX_OFFLOAD_UDP_TSO)) {
+		if (data->dev_conf.rxmode.offloads &
+		    RTE_ETH_RX_OFFLOAD_KEEP_CRC) {
+			ENETC_PMD_ERR("LSO (TSO) is incompatible with KEEP_CRC");
+			rte_free(tx_ring);
+			return -EINVAL;
+		}
+		tx_ring->lso_enable = 1;
+		dev->tx_pkt_burst = &enetc_xmit_pkts_lso;
+	}
+
 	err = enetc4_alloc_txbdr(tx_ring, nb_desc);
 	if (err)
 		goto fail;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 83a1e4931f..3083a56335 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -60,6 +60,8 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO |
 	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
 
 static void
diff --git a/drivers/net/enetc/enetc_rxtx.c b/drivers/net/enetc/enetc_rxtx.c
index d5855e69a3..0bb5a86dd8 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -221,6 +221,263 @@ enetc_xmit_pkts_nc(void *tx_queue,
 	return start;
 }
 
+/*
+ * LSO (Large Send Offload) Tx burst.
+ *
+ * Dedicated burst used on Tx rings with LSO enabled (txr->lso_enable). It
+ * follows the same non-cache-coherent discipline as enetc_xmit_pkts_cacheable:
+ * payload lines are flushed with dcbf before handing the frame to HW and the
+ * written BD cache lines are flushed at the end of the batch.
+ *
+ * A TSO/USO frame uses an extended Tx descriptor: the standard BD points at
+ * the L2/L3/L4 header template (BUF_LEN = header length, FRM_LEN = payload
+ * length), followed by an extension BD in the next ring slot holding the
+ * segment size, then one BD per payload buffer with the F flag on the last.
+ * Non-TSO packets fall through to the normal encoding so mixed traffic works.
+ */
+uint16_t
+enetc_xmit_pkts_lso(void *tx_queue,
+		struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts)
+{
+	int i, start, bds_to_use;
+	struct enetc_tx_bd *txbd = NULL;
+	struct enetc_tx_bd_ext *txbd_ext;
+	struct enetc_bdr *tx_ring = (struct enetc_bdr *)tx_queue;
+	unsigned int j;
+	uint8_t *data;
+	struct rte_mbuf *seg;
+	uint16_t seg_len, segs_per_pkt;
+	bool is_first_seg;
+	int first_bd_idx, bd_count;
+
+	i = tx_ring->next_to_use;
+	bds_to_use = enetc_bd_unused(tx_ring);
+	bd_count = tx_ring->bd_count;
+	start = 0;
+
+	first_bd_idx = i;
+
+	while (start < nb_pkts) {
+		seg = tx_pkts[start];
+		segs_per_pkt = seg->nb_segs;
+
+		if (seg->ol_flags &
+		    (RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_UDP_SEG)) {
+			bool is_udp_seg =
+				!!(seg->ol_flags & RTE_MBUF_F_TX_UDP_SEG);
+			uint32_t hdr_len = seg->l2_len + seg->l3_len +
+					   seg->l4_len;
+			uint32_t data_unit;
+			uint16_t first_payload;
+			struct rte_mbuf *dseg;
+			int bds_needed;
+
+			/* Header BD + extension BD + one BD per segment. */
+			bds_needed = 2 + segs_per_pkt;
+			if (bds_to_use < bds_needed)
+				break;
+
+			/*
+			 * Skip frames with nothing to segment, or whose
+			 * headers are not fully contained in the first
+			 * segment. The first BD carries the header template
+			 * and first_payload is computed as (first segment
+			 * data_len - hdr_len), so the L2/L3/L4 headers must
+			 * reside contiguously in the first mbuf; otherwise the
+			 * unsigned subtraction would underflow.
+			 */
+			if (unlikely(hdr_len >= rte_pktmbuf_pkt_len(seg) ||
+				     hdr_len > rte_pktmbuf_data_len(seg))) {
+				rte_pktmbuf_free(seg);
+				start++;
+				continue;
+			}
+			data_unit = rte_pktmbuf_pkt_len(seg) - hdr_len;
+
+			/*
+			 * Skip frames that violate HW LSO limits: a zero
+			 * segment size, a payload larger than the HW data
+			 * unit, or a per-segment frame (headers + segment)
+			 * bigger than the maximum LSO frame size.
+			 */
+			if (unlikely(seg->tso_segsz == 0 ||
+				     data_unit > ENETC4_LSO_MAX_DATA_UNIT ||
+				     hdr_len + seg->tso_segsz >
+					     ENETC4_LSO_MAX_FRAME)) {
+				rte_pktmbuf_free(seg);
+				start++;
+				continue;
+			}
+
+			/* Standard (first) BD: header template only. */
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			seg_len = rte_pktmbuf_data_len(seg);
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			memset(txbd, 0, sizeof(*txbd));
+			tx_ring->q_swbd[i].buffer_addr = seg;
+
+			/* FRM_LEN low 16 bits = payload length, BUF_LEN = hdr. */
+			txbd->frm_len = rte_cpu_to_le_16(data_unit & 0xffff);
+			txbd->buf_len = rte_cpu_to_le_16((uint16_t)hdr_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+
+			/* Checksum control for the per-segment L3/L4 rewrite. */
+			txbd->l3_start = seg->l2_len;
+			txbd->l3_hdr_size = seg->l3_len / 4;
+			if (seg->ol_flags & RTE_MBUF_F_TX_IPV6) {
+				txbd->l3t = 1;
+			} else {
+				txbd->l3t = ENETC4_TXBD_L3T;
+				txbd->ipcs = ENETC4_TXBD_IPCS;
+			}
+			txbd->l4t = is_udp_seg ? ENETC4_TXBD_L4T_UDP :
+						 ENETC4_TXBD_L4T_TCP;
+			txbd->flags = ENETC4_TXBD_FLAGS_L_TX_CKSUM |
+				      ENETC4_TXBD_FLAGS_L4CS |
+				      ENETC4_TXBD_FLAGS_LSO |
+				      ENETC4_TXBD_FLAGS_EXT;
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/* Extension BD occupies the next ring slot. */
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			txbd_ext = (struct enetc_tx_bd_ext *)
+				   ENETC_TXBD(*tx_ring, i);
+			memset(txbd_ext, 0, sizeof(*txbd_ext));
+			txbd_ext->lso = rte_cpu_to_le_32(ENETC4_TXBD_EXT_LSO_SEG(seg->tso_segsz) |
+					ENETC4_TXBD_EXT_FRM_LEN_EXT(data_unit >> 16));
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/*
+			 * Payload BDs. The first segment's payload starts after
+			 * the header template; remaining segments are pure
+			 * payload.
+			 */
+			first_payload = (uint16_t)(seg_len - hdr_len);
+			dseg = seg;
+			is_first_seg = true;
+			while (dseg) {
+				uint16_t dlen;
+				uint64_t daddr;
+
+				if (is_first_seg) {
+					dlen = first_payload;
+					daddr = rte_mbuf_data_iova(dseg) +
+						hdr_len;
+					is_first_seg = false;
+				} else {
+					dlen = rte_pktmbuf_data_len(dseg);
+					data = rte_pktmbuf_mtod(dseg, void *);
+					for (j = 0; j < dlen;
+					     j += RTE_CACHE_LINE_SIZE)
+						dcbf(data + j);
+					dcbf(data + (dlen - 1));
+					daddr = rte_mbuf_data_iova(dseg);
+				}
+
+				if (dlen == 0) {
+					dseg = dseg->next;
+					continue;
+				}
+
+				tx_ring->q_swbd[i].buffer_addr = NULL;
+				txbd = ENETC_TXBD(*tx_ring, i);
+				memset(txbd, 0, sizeof(*txbd));
+				txbd->buf_len = rte_cpu_to_le_16(dlen);
+				txbd->addr = rte_cpu_to_le_64(daddr);
+				i++;
+				bds_to_use--;
+				if (unlikely(i == bd_count))
+					i = 0;
+				dseg = dseg->next;
+			}
+
+			/* Mark the last written BD as frame-last. */
+			txbd->flags |= ENETC4_TXBD_FLAGS_F;
+			start++;
+			continue;
+		}
+
+		/* Non-TSO packet: standard single/multi-seg encoding. */
+		if (bds_to_use < segs_per_pkt)
+			break;
+
+		is_first_seg = true;
+		while (seg) {
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			seg_len = rte_pktmbuf_data_len(seg);
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			txbd->flags = 0;
+			if (is_first_seg) {
+				tx_ring->q_swbd[i].buffer_addr = seg;
+				txbd->frm_len = rte_pktmbuf_pkt_len(seg);
+				if (seg->ol_flags & ENETC4_TX_CKSUM_OFFLOAD_MASK)
+					enetc4_tx_offload_checksum(seg, txbd);
+				is_first_seg = false;
+			}
+
+			txbd->buf_len = rte_cpu_to_le_16(seg_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+			seg = seg->next;
+			i++;
+			bds_to_use--;
+
+			if (unlikely(i == bd_count))
+				i = 0;
+		}
+
+		txbd->flags |= ENETC4_TXBD_FLAGS_F;
+		start++;
+	}
+
+	/*
+	 * Flush TX BDs to PoC so HW (non-cache-coherent i.MX95) can read the
+	 * descriptors from memory.  Same discipline as enetc_xmit_pkts_cacheable:
+	 * walk from the cache-line-aligned start of first_bd_idx to just past
+	 * the last written BD, one dcbf per 64-byte (4-BD) line.
+	 */
+	if (likely(start > 0)) {
+		int n = first_bd_idx & ~ENETC_BD_PER_CL_MASK;
+		int written = (i - n + bd_count) % bd_count;
+
+		if (written == 0)
+			written = bd_count;
+		written = (written + ENETC_BD_PER_CL_MASK) &
+			  ~ENETC_BD_PER_CL_MASK;
+
+		while (written > 0) {
+			dcbf((void *)ENETC_TXBD(*tx_ring, n));
+			n = (n + ENETC_BD_PER_CL) % bd_count;
+			written -= ENETC_BD_PER_CL;
+		}
+	}
+
+	enetc_clean_tx_ring(tx_ring);
+	tx_ring->next_to_use = i;
+	enetc_wr_reg(tx_ring->tcir, i);
+
+	return start;
+}
+
 int
 enetc_refill_rx_ring(struct enetc_bdr *rx_ring, const int buff_cnt)
 {
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 01/14] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 04/14] net/enetc: extend PF-VF link speed field to 8 bits Gagandeep Singh
                           ` (13 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

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 <g.singh@nxp.com>
---
 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      | 106 ++++++++++-
 drivers/net/enetc/enetc4_vf.c          |   1 +
 drivers/net/enetc/enetc_rxtx.c         | 235 +++++++++++++++++++++++++
 8 files changed, 421 insertions(+), 6 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 62bedc2854..2dc4ab2a0b 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -100,6 +100,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 {
@@ -320,12 +322,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 24c454fe28..b60c8f6f48 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -17,7 +17,8 @@ static uint64_t dev_rx_offloads_sup =
 	RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_SCATTER |
-	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 =
@@ -302,6 +303,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;
@@ -512,22 +515,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;
@@ -550,13 +560,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);
@@ -578,7 +605,9 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			ENETC_DEV_PRIVATE(data->dev_private);
 	uint64_t rx_offloads = data->dev_conf.rxmode.offloads;
+	uint32_t rsc_size;
 	bool keep_crc;
+	bool rsc_enable;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_rx_desc > MAX_BD_COUNT)
@@ -595,6 +624,28 @@ 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;
+		}
+		if (adapter->hw.nc_mode) {
+			ENETC_PMD_ERR("RSC (LRO) is incompatible with nc=1 (non-cacheable mode)");
+			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;
@@ -613,6 +664,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;
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 0bb5a86dd8..4301a7bf43 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -907,6 +907,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)
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 04/14] net/enetc: extend PF-VF link speed field to 8 bits
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (2 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
                           ` (12 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF message is only 16-bit wide, where the upper 8-bit is the
class_id and the lower 8-bit carries the message content. For the
link speed message, the lower 4-bit was previously occupied by the cookie
field, leaving only 4-bit for the speed code, which allows at most
15 distinct speed values.

As the NETC IP evolves and supports more and more link speeds, 15 speed
values are clearly insufficient. Since the cookie field is designed
for non-blocking messages and has no meaningful use in link speed
messages, remove it and expand the speed code field from 4-bit to 8-bit,
allowing up to 255 speed values (ENETC_SPEED_MAX = 0xff).

Instead of enumerating every speed value greater than 5Gbps
explicitly, introduce a formula-based approach:

	speed_code = (link_speed - 5000) / 1000 + ENETC_SPEED_5000

This removes the explicit enum entries for 10G, 25G, 50G and 100G,
and replaces the individual switch-case branches with a generic
implementation to get the speed, making it easy to support any
future high speed without modifying the enum or the switch statement.

For backward compatibility with a PF running an older kernel (before
6.18.37) that still uses the legacy 4-bit speed code / 4-bit cookie
message layout, add a "vf_link_legacy" devarg. When set to 1, the VF
extracts the message result from the upper 4 bits of the lower byte
and decodes speeds greater than 5Gbps using the legacy fixed class
codes (10G/25G/50G/100G). The default (0) uses the new 8-bit layout.

	Usage: -a <pci_addr>,vf_link_legacy=1

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |  11 ++
 doc/guides/rel_notes/release_26_11.rst |   3 +
 drivers/net/enetc/enetc.h              |  32 +++-
 drivers/net/enetc/enetc4_vf.c          | 203 +++++++++++++++++++++----
 4 files changed, 216 insertions(+), 33 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index e7f4348603..935cf90e6b 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -133,6 +133,17 @@ VF-specific devargs
 
     dpdk-testpmd -a 0000:00:01.0,enetc4_vsi_delay=10 -- -i
 
+``vf_link_legacy``
+  Select the legacy 4-bit PF-to-VF link speed code layout.
+  Set to ``1`` when the host PF is running a kernel older than v6.18.37.
+  Kernels before that release encode link speed in only 4 bits; without this
+  flag the driver interprets those codes as 8-bit values and reports incorrect
+  link speeds.
+
+  Usage example::
+
+    dpdk-testpmd -a 0000:00:01.0,vf_link_legacy=1 -- -i
+
 PF/Common devargs
 ~~~~~~~~~~~~~~~~~
 
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 0dd08e0259..ee4f455c67 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -63,6 +63,9 @@ 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.
+  * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
+    Users running a PF kernel older than 6.18.37 must pass ``vf_link_legacy=1``
+    as a device argument, otherwise link speed reporting will be incorrect.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 2dc4ab2a0b..75f3705c25 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -119,6 +119,10 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
+	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
+	 */
+	uint8_t vf_link_legacy;
 };
 
 /*
@@ -210,11 +214,29 @@ enum speed {
 	ENETC_SPEED_1000 = 0x5,
 	ENETC_SPEED_2500 = 0x6,
 	ENETC_SPEED_5000 = 0x7,
-	ENETC_SPEED_10G = 0x8,
-	ENETC_SPEED_25G = 0x9,
-	ENETC_SPEED_50G = 0xA,
-	ENETC_SPEED_100G = 0xB,
-	ENETC_SPEED_NOT_SUPPORTED = 0xF
+	/* Base speed class code used by the >5Gbps formula below */
+	/* Do not add enumeration values for any speed greater than
+	 * 5Gbps. For any speed greater than 5Gbps, its speed class
+	 * code should follow the formula below.
+	 *
+	 * SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+	 *
+	 * The unit of link_speed should be Mbps, the max SPEED
+	 * should <= ENETC_SPEED_MAX.
+	 */
+	ENETC_SPEED_MAX = 0xff,
+};
+
+/* Legacy speed class codes for backward compatibility with an older kernel PF
+ * (before 6.18.37) that encoded a 4-bit speed code and 4-bit cookie in the
+ * message's lower byte. Used only when the vf_link_legacy devarg is set.
+ */
+enum speed_legacy {
+	ENETC_SPEED_LEGACY_10G = 0x8,
+	ENETC_SPEED_LEGACY_25G = 0x9,
+	ENETC_SPEED_LEGACY_50G = 0xA,
+	ENETC_SPEED_LEGACY_100G = 0xB,
+	ENETC_SPEED_LEGACY_NOT_SUPPORTED = 0xF
 };
 
 /* PSI-VSI command header format */
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index be79a18a39..bbc531a4b0 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -43,6 +43,12 @@ enetc4_vf_get_devarg_nc(struct rte_eth_dev *dev)
 #define ENETC_BYTE_SIZE			8
 #define ENETC_MSB_BIT			0x8000
 
+/* Backward-compat devarg for a PF running a kernel before 6.18.37, which uses
+ * the legacy PF-to-VF link message layout (4-bit speed code + 4-bit cookie).
+ * Usage: -a <pci_addr>,vf_link_legacy=1
+ */
+#define ENETC_VF_LINK_LEGACY		"vf_link_legacy"
+
 uint16_t enetc_crc_table[ENETC_CRC_TABLE_SIZE];
 bool enetc_crc_gen;
 
@@ -101,6 +107,59 @@ enetc_crc_calc(uint16_t crc, const uint8_t *buffer, size_t len)
 	return crc;
 }
 
+static int
+parse_vf_link_legacy(const char *key __rte_unused, const char *value,
+		     void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *endptr;
+	unsigned long val;
+
+	if (!value || *value == '\0') {
+		ENETC_PMD_WARN("Empty value for devarg %s, ignoring",
+			       ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	errno = 0;
+	val = strtoul(value, &endptr, 0);
+	if (errno != 0 || *endptr != '\0' || val > 1) {
+		ENETC_PMD_WARN("Invalid value '%s' for devarg %s, expected 0 or 1",
+			       value, ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	hw->vf_link_legacy = (uint8_t)val;
+
+	return 0;
+}
+
+static void
+enetc4_vf_get_devargs(struct rte_eth_dev *dev)
+{
+	struct rte_devargs *devargs;
+	struct rte_kvargs *kvlist;
+
+	devargs = dev->device->devargs;
+	if (!devargs)
+		return;
+
+	kvlist = rte_kvargs_parse(devargs->args, NULL);
+	if (!kvlist)
+		return;
+
+	if (rte_kvargs_count(kvlist, ENETC_VF_LINK_LEGACY)) {
+		if (rte_kvargs_process(kvlist, ENETC_VF_LINK_LEGACY,
+				       parse_vf_link_legacy, (void *)dev) < 0)
+			ENETC_PMD_WARN("Failed to parse devarg %s",
+				       ENETC_VF_LINK_LEGACY);
+	}
+
+	rte_kvargs_free(kvlist);
+}
+
 static int
 enetc4_vf_dev_infos_get(struct rte_eth_dev *dev,
 			struct rte_eth_dev_info *dev_info)
@@ -212,6 +271,7 @@ enetc4_msg_vsi_write_msg(struct enetc_hw *hw,
 static void
 enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgsr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -221,8 +281,15 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgsr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -231,6 +298,7 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 static void
 enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgrr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -240,8 +308,15 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgrr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -731,6 +806,8 @@ enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 static int
 enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused)
 {
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
 	struct enetc_psi_reply_msg *reply_msg;
 	struct rte_eth_link link;
 	int err;
@@ -809,28 +886,91 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 			link.link_speed = RTE_ETH_SPEED_NUM_5G;
 			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 			break;
-		case ENETC_SPEED_10G:
-			link.link_speed = RTE_ETH_SPEED_NUM_10G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_25G:
-			link.link_speed = RTE_ETH_SPEED_NUM_25G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_50G:
-			link.link_speed = RTE_ETH_SPEED_NUM_50G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100G:
-			link.link_speed = RTE_ETH_SPEED_NUM_100G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_NOT_SUPPORTED:
-			ENETC_PMD_DEBUG("Speed not supported");
-			link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-			break;
 		default:
-			ENETC_PMD_ERR("Unknown speed status");
+			if (hw->vf_link_legacy) {
+				/* Legacy PF-to-VF message layout (older kernel
+				 * PF): speeds greater than 5Gbps are encoded
+				 * with fixed 4-bit class codes rather than the
+				 * formula below.
+				 */
+				switch (reply_msg->status) {
+				case ENETC_SPEED_LEGACY_10G:
+					link.link_speed = RTE_ETH_SPEED_NUM_10G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_25G:
+					link.link_speed = RTE_ETH_SPEED_NUM_25G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_50G:
+					link.link_speed = RTE_ETH_SPEED_NUM_50G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_100G:
+					link.link_speed = RTE_ETH_SPEED_NUM_100G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+					ENETC_PMD_DEBUG("Speed not supported");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				default:
+					ENETC_PMD_ERR("Unknown speed status");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				}
+				break;
+			}
+
+			/* Any status reaching here is greater than
+			 * ENETC_SPEED_5000, as all values from 0x0 to
+			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
+			 * greater than 5Gbps are not enumerated and follow the
+			 * formula:
+			 *
+			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+			 *
+			 * where link_speed is in Mbps. Reverse it here to get the
+			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
+			 *
+			 * Validate the computed value against the set of speeds
+			 * that the NETC IP is known to support (> 5Gbps).
+			 * An unrecognised code yields UNKNOWN rather than a
+			 * fabricated speed.
+			 */
+			switch ((reply_msg->status - ENETC_SPEED_5000)
+				* 1000 + 5000) {
+			case RTE_ETH_SPEED_NUM_10G:
+				link.link_speed = RTE_ETH_SPEED_NUM_10G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_25G:
+				link.link_speed = RTE_ETH_SPEED_NUM_25G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_40G:
+				link.link_speed = RTE_ETH_SPEED_NUM_40G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_50G:
+				link.link_speed = RTE_ETH_SPEED_NUM_50G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_100G:
+				link.link_speed = RTE_ETH_SPEED_NUM_100G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_200G:
+				link.link_speed = RTE_ETH_SPEED_NUM_200G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			default:
+				ENETC_PMD_WARN("Unrecognized speed code 0x%x, "
+					       "reporting unknown",
+					       reply_msg->status);
+				link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			}
 			break;
 		}
 	} else {
@@ -839,10 +979,9 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	link.link_autoneg = 1;
-
 	rte_eth_linkstatus_set(dev, &link);
-
 	rte_free(reply_msg);
+
 	return 0;
 }
 
@@ -1421,6 +1560,12 @@ enetc4_vf_dev_init(struct rte_eth_dev *eth_dev)
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_populate();
 
+	/* Parse VF specific devargs (e.g. vf_link_legacy) before the first
+	 * link update so that PF-to-VF link messages are interpreted using
+	 * the correct (legacy or current) layout.
+	 */
+	enetc4_vf_get_devargs(eth_dev);
+
 	ENETC_PMD_DEBUG("port_id %d vendorID=0x%x deviceID=0x%x",
 			eth_dev->data->port_id, pci_dev->id.vendor_id,
 			pci_dev->id.device_id);
@@ -1514,5 +1659,7 @@ RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
 			      ENETC4_VSI_DELAY "=<uint> "
-			      ENETC4_NC_MEMORY "=<int>");
+			      ENETC4_NC_MEMORY "=<int> "
+			      ENETC_VF_LINK_LEGACY "=<0|1>");
+
 RTE_LOG_REGISTER_DEFAULT(enetc4_vf_logtype_pmd, NOTICE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 05/14] net/enetc: support firmware version get for VF
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (3 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 04/14] net/enetc: extend PF-VF link speed field to 8 bits Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 06/14] net/enetc: support registers dump Gagandeep Singh
                           ` (11 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the ``.fw_version_get`` eth_dev op for the ENETC4 VF PMD to
report the NETC IP revision.

The NETC IP major revision is read from the PCI revision ID register,
while the IP minor revision is retrieved from the PSI over VSI-PSI
messaging using the "Get IP version" command class (class ID 0xF0,
command ID 0x1). The version is reported in the "<major>.<minor>"
format.

The PSI firmware only implements the IP_MN command of this class, so
the major revision is obtained from the PCI revision ID, matching the
behaviour of the kernel PF/VF drivers.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   1 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |  19 +++-
 drivers/net/enetc/enetc4_vf.c          | 135 +++++++++++++++++++++++++
 5 files changed, 155 insertions(+), 2 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 935cf90e6b..fa93f40b22 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,7 @@ Key functionality includes:
   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.
+- Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index aae398e210..eaf474e7ba 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ VLAN filter          = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
+FW version           = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index ee4f455c67..d212cce000 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -66,6 +66,7 @@ New Features
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
     Users running a PF kernel older than 6.18.37 must pass ``vf_link_legacy=1``
     as a device argument, otherwise link speed reporting will be incorrect.
+  * Added firmware version reporting for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 75f3705c25..27a617a62f 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -169,7 +169,8 @@ enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
-	ENETC_CLASS_ID_LINK_SPEED = 0x81
+	ENETC_CLASS_ID_LINK_SPEED = 0x81,
+	ENETC_CLASS_ID_GET_IP_VER = 0xF0
 };
 
 /* Enum for command IDs */
@@ -183,9 +184,18 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_LINK_STATUS = 0,
 	ENETC_CMD_ID_REGISTER_LINK_NOTIF = 1,
 	ENETC_CMD_ID_UNREGISTER_LINK_NOTIF = 2,
-	ENETC_CMD_ID_GET_LINK_SPEED = 0
+	ENETC_CMD_ID_GET_LINK_SPEED = 0,
+	/* Get IP version command IDs (class ID 0xF0) */
+	ENETC_CMD_ID_GET_IP_MJ = 0,
+	ENETC_CMD_ID_GET_IP_MN = 1,
+	ENETC_CMD_ID_GET_IP_INT = 2,
+	ENETC_CMD_ID_GET_IP_MNT = 3,
+	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* IP_VER value returned when the version is not available */
+#define ENETC_IP_VER_NOT_AVAILABLE	0xFF
+
 enum mac_addr_status {
 	ENETC_INVALID_MAC_ADDR = 0x0,
 	ENETC_DUPLICATE_MAC_ADDR = 0X1,
@@ -273,6 +283,11 @@ struct enetc_msg_cmd_get_link_speed {
 	struct enetc_msg_cmd_header header;
 };
 
+/* Get IP version command message format (class ID 0xF0) */
+struct enetc_msg_cmd_get_ip_ver {
+	struct enetc_msg_cmd_header header;
+};
+
 struct enetc_msg_cmd_set_vlan_promisc {
 	struct enetc_msg_cmd_header header;
 	uint8_t op;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index bbc531a4b0..50044eca3f 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -3,6 +3,7 @@
  */
 
 #include <stdbool.h>
+#include <rte_bus_pci.h>
 #include <rte_kvargs.h>
 #include <rte_random.h>
 #include <dpaax_iova_table.h>
@@ -433,6 +434,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_MAC_FILTER:
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
+		case ENETC_CLASS_ID_GET_IP_VER:
 			break;
 		default:
 			err = -EIO;
@@ -796,6 +798,138 @@ enetc4_vf_get_link_speed(struct rte_eth_dev *dev, struct enetc_psi_reply_msg *re
 	return err;
 }
 
+/*
+ * Get the NETC IP minor revision (IP_MN) from the PSI using the 'Get IP
+ * version' command class (class ID 0xF0, cmd_id 0x1). In the reply, the
+ * low 8 bits of the return code carry the minor revision and the upper 8
+ * bits carry the class code (0xF0). The PSI only implements the IP_MN
+ * command of this class; the major revision comes from the PCI revision.
+ */
+static int
+enetc4_vf_get_ip_minor_revision(struct rte_eth_dev *dev, uint8_t *ip_mn)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_swbd *msg;
+	uint32_t msg_size;
+	uint16_t mc;
+	uint8_t class_id;
+	int vsimsgsr;
+	int err = 0;
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_cmd_get_ip_ver),
+				ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg");
+		rte_free(msg);
+		return -ENOMEM;
+	}
+
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	msg->size = msg_size;
+
+	/* COOKIE is 0 so that the command is executed as blocking on PSI */
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_GET_IP_VER,
+			ENETC_CMD_ID_GET_IP_MN, 0, 0, 0);
+
+	/* send the command and wait */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error");
+		goto end;
+	}
+
+	/*
+	 * For the IP version command class the class-specific field is
+	 * reused to carry the 8-bit version value in the lower byte of the
+	 * return code, so parse the full low byte here instead of using the
+	 * generic reply parser.
+	 */
+	vsimsgsr = enetc4_rd(enetc_hw, ENETC4_VSIMSGSR);
+	mc = ENETC_SIMSGSR_GET_MC(vsimsgsr);
+	class_id = (mc >> 8) & 0xff;
+
+	if (class_id != ENETC_CLASS_ID_GET_IP_VER) {
+		ENETC_PMD_ERR("Wrong reply message 0x%x", class_id);
+		err = -EIO;
+		goto end;
+	}
+
+	*ip_mn = mc & 0xff;
+	if (*ip_mn == ENETC_IP_VER_NOT_AVAILABLE) {
+		ENETC_PMD_DEBUG("IP minor revision not available");
+		err = -ENOTSUP;
+	}
+
+end:
+	/* free memory no longer required */
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	return err;
+}
+
+/*
+ * Retrieve the NETC firmware/IP version and format it as
+ * "<major>.<minor>". The IP major revision is taken from the PCI
+ * revision ID register, and the IP minor revision is fetched from the
+ * PSI via VSI-PSI messaging ('Get IP version' command class). This
+ * mirrors the behaviour of the kernel PF/VF drivers, where the PSI only
+ * implements the IP_MN command of the 0xF0 class.
+ */
+static int
+enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_size)
+{
+	struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
+	uint8_t ip_mj = 0, ip_mn = 0;
+	int ret;
+
+	PMD_INIT_FUNC_TRACE();
+
+	if (fw_version == NULL)
+		return -EINVAL;
+
+	if (fw_size == 0)
+		return snprintf(NULL, 0, "%u.%u", ip_mj, ip_mn) + 1;
+
+	/* IP major revision is exposed through the PCI revision ID */
+	ret = rte_pci_read_config(pci_dev, &ip_mj, sizeof(ip_mj),
+			RTE_PCI_REVISION_ID);
+	if (ret != sizeof(ip_mj)) {
+		ENETC_PMD_ERR("Failed to read PCI revision ID");
+		return -EIO;
+	}
+
+	/* IP minor revision is fetched from the PSI via VSI-PSI messaging.
+	 * If the PSI reports the version as unavailable, fall back to a
+	 * partial version string so the caller still gets useful output.
+	 */
+	ret = enetc4_vf_get_ip_minor_revision(dev, &ip_mn);
+	if (ret && ret != -ENOTSUP) {
+		ENETC_PMD_ERR("Failed to get NETC minor revision");
+		return ret;
+	}
+
+	if (ret == -ENOTSUP)
+		ret = snprintf(fw_version, fw_size, "%u.unknown", ip_mj);
+	else
+		ret = snprintf(fw_version, fw_size, "%u.%u", ip_mj, ip_mn);
+	if (ret < 0)
+		return -EINVAL;
+
+	ret += 1; /* add trailing '\0' */
+	if ((size_t)ret > fw_size)
+		return ret;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1354,6 +1488,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.fw_version_get       = enetc4_vf_fw_version_get,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.mac_addr_set         = enetc4_vf_set_mac_addr,
 	.mac_addr_add	      = enetc4_vf_mac_addr_add,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 06/14] net/enetc: support registers dump
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (4 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
                           ` (10 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the .get_reg operation for the ENETC4 PF and VF PMDs to dump
the device registers via rte_eth_dev_get_reg_info.

The VF dumps the registers reachable by a virtual station interface
(VSI): the station interface and per-ring BD ring registers. Port
registers are not accessible by a VF.

The PF additionally dumps the port registers, which are only
accessible by the physical station interface (PSI).

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |  1 +
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 13 +++++
 drivers/net/enetc/enetc4_ethdev.c      | 70 ++++++++++++++++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 62 +++++++++++++++++++++++
 6 files changed, 148 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index fa93f40b22..16389aee5b 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -60,6 +60,7 @@ Key functionality includes:
   requested. RSC requires the FCS to be stripped, so it cannot be combined
   with the KEEP_CRC Rx offload.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
+- Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index eaf474e7ba..6540a43909 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -17,6 +17,7 @@ RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
 FW version           = Y
+Registers dump       = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index d212cce000..6fece98f28 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -67,6 +67,7 @@ New Features
     Users running a PF kernel older than 6.18.37 must pass ``vf_link_legacy=1``
     as a device argument, otherwise link speed reporting will be incorrect.
   * Added firmware version reporting for the ENETC4 VF.
+  * Added register dump support for ENETC4 PF and VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 27a617a62f..b70664eede 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -396,6 +396,19 @@ enetc_bd_unused(struct enetc_bdr *bdr)
 	return bdr->bd_count + bdr->next_to_clean - bdr->next_to_use - 1;
 }
 
+/* Per-ring Tx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_txbdr_regs[] = {
+	ENETC_TBMR, ENETC_TBSR, ENETC_TBBAR0, ENETC_TBBAR1,
+	ENETC_TBCIR, ENETC_TBLENR,
+};
+
+/* Per-ring Rx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_rxbdr_regs[] = {
+	ENETC_RBMR, ENETC_RBSR, ENETC_RBBSR, ENETC_RBCIR,
+	ENETC_RBBAR0, ENETC_RBBAR1, ENETC_RBPIR, ENETC_RBLENR,
+};
+
+
 /* CBDR prototypes */
 int enetc4_setup_cbdr(struct rte_eth_dev *dev, struct enetc_hw *hw,
 			int bd_count, struct netc_cbdr *cbdr);
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index b60c8f6f48..bccd418e32 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1172,6 +1172,75 @@ enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 	return ptypes;
 }
 
+/* Station interface registers dumped by .get_reg */
+static const uint32_t enetc4_pf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR,
+};
+
+/* Port registers dumped by .get_reg (accessible only by the PF) */
+static const uint32_t enetc4_pf_port_regs[] = {
+	ENETC4_PMR, ENETC4_PSIPMMR, ENETC4_PMAR0, ENETC4_PMAR1,
+	ENETC4_PM_CMD_CFG(0), ENETC4_PM_MAXFRM(0), ENETC4_PM_IF_MODE(0),
+	ENETC4_PM_IF_STATUS(0),
+};
+
+/*
+ * Dump the PF-accessible registers: station interface, port and per-ring
+ * BD ring registers. When info->data is NULL, only the register count and
+ * width are reported so the caller can size its buffer.
+ */
+static int
+enetc4_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_pf_si_regs);
+	count += RTE_DIM(enetc4_pf_port_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_si_regs); i++)
+		*buf++ = enetc4_rd(enetc_hw, enetc4_pf_si_regs[i]);
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_port_regs); i++)
+		*buf++ = enetc4_port_rd(enetc_hw, enetc4_pf_port_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 /*
  * The set of PCI devices this driver supports
  */
@@ -1190,6 +1259,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.link_update          = enetc4_link_update,
 	.stats_get            = enetc4_stats_get,
 	.stats_reset          = enetc4_stats_reset,
+	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 50044eca3f..bf7829ee00 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -930,6 +930,66 @@ enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_si
 	return 0;
 }
 
+/* VF station interface registers dumped by .get_reg */
+static const uint32_t enetc4_vf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR, ENETC4_SIMSIVR, ENETC4_VSIIER, ENETC4_VSIIDR,
+	ENETC4_VSIMSGSR, ENETC4_VSIMSGRR,
+};
+
+/*
+ * Dump the VF-accessible registers. Only station interface and per-ring
+ * BD ring registers are reachable by a VF; port registers are not.
+ * When info->data is NULL, only the register count and width are
+ * reported so the caller can size its buffer.
+ */
+static int
+enetc4_vf_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_vf_si_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_vf_si_regs); i++)
+		*buf++ = enetc_rd(enetc_hw, enetc4_vf_si_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1468,6 +1528,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
@@ -1489,6 +1550,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.mac_addr_set         = enetc4_vf_set_mac_addr,
 	.mac_addr_add	      = enetc4_vf_mac_addr_add,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 07/14] net/enetc: support ethtool ring parameters
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (5 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 06/14] net/enetc: support registers dump Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
                           ` (9 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The ethtool ring parameter query relies on the queue info ethdev
ops (.rxq_info_get/.txq_info_get) together with the descriptor
limits reported in dev_info. The PF already registered these ops,
but the VF ops tables did not, so ring parameter queries failed on
the VF.

Make enetc4_rxq_info_get() and enetc4_txq_info_get() non-static and
register them in both VF ops tables so ring parameters are reported
for both the PF and VF.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst | 1 +
 drivers/net/enetc/enetc.h              | 4 ++++
 drivers/net/enetc/enetc4_ethdev.c      | 4 ++--
 drivers/net/enetc/enetc4_vf.c          | 4 ++++
 4 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 6fece98f28..ec223a93ba 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -68,6 +68,7 @@ New Features
     as a device argument, otherwise link speed reporting will be incorrect.
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
+  * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index b70664eede..6ff438eff6 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -338,6 +338,10 @@ int enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx);
 void enetc4_tx_queue_release(struct rte_eth_dev *dev, uint16_t qid);
 const uint32_t *enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 			size_t *no_of_elements);
+void enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_rxq_info *qinfo);
+void enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_txq_info *qinfo);
 
 /*
  * enetc4_vf function prototype
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index bccd418e32..0295ed332b 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1121,7 +1121,7 @@ enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx)
 	return 0;
 }
 
-static void
+void
 enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_rxq_info *qinfo)
 {
@@ -1135,7 +1135,7 @@ enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 	qinfo->conf.rx_drop_en = 0;
 }
 
-static void
+void
 enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_txq_info *qinfo)
 {
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index bf7829ee00..406559094f 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1535,10 +1535,12 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
@@ -1565,10 +1567,12 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 08/14] net/enetc: refresh link speed on VF link-up interrupt
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (6 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 09/14] net/enetc: support stats reset for VF Gagandeep Singh
                           ` (8 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The interrupt-driven link-status path (enetc4_process_psi_msg) only
updated link_status when a PSI-to-VSI notification arrived; it never
re-queried the link speed from the PF.  As a result, after a link-down
followed by a link-up the cached link_speed showed the speed from the
previous session rather than the freshly negotiated one.

1. Introducing enetc4_decode_link_speed() - a shared helper that maps
   a PF-to-VF speed status code to the corresponding RTE_ETH_SPEED_NUM_*
   / RTE_ETH_LINK_*_DUPLEX values, handling both the current and the
   legacy (vf_link_legacy) 4-bit message layout.

2. Calling enetc4_vf_get_link_speed() inside enetc4_process_psi_msg()
   on ENETC_LINK_UP so the negotiated speed is fetched from the PF and
   decoded immediately, before rte_eth_linkstatus_set() is called and
   the LSC callback is fired.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc4_vf.c          | 257 +++++++++++++------------
 2 files changed, 138 insertions(+), 120 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index ec223a93ba..1765adc1bc 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -69,6 +69,7 @@ New Features
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
+  * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 406559094f..dae6e4c772 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -323,9 +323,130 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	reply_msg->status = status;
 }
 
+/* Forward declaration: defined later in this file */
+static int enetc4_vf_get_link_speed(struct rte_eth_dev *dev,
+				     struct enetc_psi_reply_msg *reply_msg);
+
+/*
+ * Decode a PF-to-VF link-speed status code into the link_speed and
+ * link_duplex fields of *link.  vf_link_legacy selects the older
+ * 4-bit code layout used by kernel PFs before v6.18.37.
+ */
+static void
+enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
+			 struct rte_eth_link *link)
+{
+	switch (status) {
+	case ENETC_SPEED_UNKNOWN:
+		ENETC_PMD_DEBUG("Speed unknown");
+		link->link_speed = RTE_ETH_SPEED_NUM_NONE;
+		break;
+	case ENETC_SPEED_10_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_10_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_100_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_100_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_1000:
+		link->link_speed = RTE_ETH_SPEED_NUM_1G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_2500:
+		link->link_speed = RTE_ETH_SPEED_NUM_2_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_5000:
+		link->link_speed = RTE_ETH_SPEED_NUM_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	default:
+		if (vf_link_legacy) {
+			/* Legacy PF-to-VF message layout (older kernel PF):
+			 * speeds above 5Gbps use fixed 4-bit class codes.
+			 */
+			switch (status) {
+			case ENETC_SPEED_LEGACY_10G:
+				link->link_speed = RTE_ETH_SPEED_NUM_10G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_25G:
+				link->link_speed = RTE_ETH_SPEED_NUM_25G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_50G:
+				link->link_speed = RTE_ETH_SPEED_NUM_50G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_100G:
+				link->link_speed = RTE_ETH_SPEED_NUM_100G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+				ENETC_PMD_DEBUG("Speed not supported");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			default:
+				ENETC_PMD_ERR("Unknown speed status");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			}
+			break;
+		}
+		/* Any status here is > ENETC_SPEED_5000. Validate against
+		 * the set of speeds that the NETC IP is known to support.
+		 * An unrecognised code yields UNKNOWN rather than a
+		 * fabricated speed.
+		 */
+		switch ((status - ENETC_SPEED_5000) * 1000 + 5000) {
+		case RTE_ETH_SPEED_NUM_10G:
+			link->link_speed = RTE_ETH_SPEED_NUM_10G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_25G:
+			link->link_speed = RTE_ETH_SPEED_NUM_25G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_40G:
+			link->link_speed = RTE_ETH_SPEED_NUM_40G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_50G:
+			link->link_speed = RTE_ETH_SPEED_NUM_50G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_100G:
+			link->link_speed = RTE_ETH_SPEED_NUM_100G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_200G:
+			link->link_speed = RTE_ETH_SPEED_NUM_200G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		default:
+			ENETC_PMD_WARN("Unrecognized speed code 0x%x, "
+				       "reporting unknown", status);
+			link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+			break;
+		}
+		break;
+	}
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
 	int ret = 0;
@@ -344,6 +465,20 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		case ENETC_LINK_UP:
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Re-query speed from PF so the cached value reflects
+			 * the current negotiated speed after link-up.
+			 */
+			rte_free(msg);
+			msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+			if (msg) {
+				if (!enetc4_vf_get_link_speed(eth_dev, msg) &&
+				    msg->class_id == ENETC_CLASS_ID_LINK_SPEED)
+					enetc4_decode_link_speed(msg->status,
+							hw->vf_link_legacy,
+							&link);
+			} else {
+				ENETC_PMD_WARN("Failed to alloc msg for speed query");
+			}
 			break;
 		case ENETC_LINK_DOWN:
 			ENETC_PMD_DEBUG("Link is down");
@@ -1047,126 +1182,8 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_SPEED) {
-		switch (reply_msg->status) {
-		case ENETC_SPEED_UNKNOWN:
-			ENETC_PMD_DEBUG("Speed unknown");
-			link.link_speed = RTE_ETH_SPEED_NUM_NONE;
-			break;
-		case ENETC_SPEED_10_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_10_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_100_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_1000:
-			link.link_speed = RTE_ETH_SPEED_NUM_1G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_2500:
-			link.link_speed = RTE_ETH_SPEED_NUM_2_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_5000:
-			link.link_speed = RTE_ETH_SPEED_NUM_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		default:
-			if (hw->vf_link_legacy) {
-				/* Legacy PF-to-VF message layout (older kernel
-				 * PF): speeds greater than 5Gbps are encoded
-				 * with fixed 4-bit class codes rather than the
-				 * formula below.
-				 */
-				switch (reply_msg->status) {
-				case ENETC_SPEED_LEGACY_10G:
-					link.link_speed = RTE_ETH_SPEED_NUM_10G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_25G:
-					link.link_speed = RTE_ETH_SPEED_NUM_25G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_50G:
-					link.link_speed = RTE_ETH_SPEED_NUM_50G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_100G:
-					link.link_speed = RTE_ETH_SPEED_NUM_100G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
-					ENETC_PMD_DEBUG("Speed not supported");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				default:
-					ENETC_PMD_ERR("Unknown speed status");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				}
-				break;
-			}
-
-			/* Any status reaching here is greater than
-			 * ENETC_SPEED_5000, as all values from 0x0 to
-			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
-			 * greater than 5Gbps are not enumerated and follow the
-			 * formula:
-			 *
-			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
-			 *
-			 * where link_speed is in Mbps. Reverse it here to get the
-			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
-			 *
-			 * Validate the computed value against the set of speeds
-			 * that the NETC IP is known to support (> 5Gbps).
-			 * An unrecognised code yields UNKNOWN rather than a
-			 * fabricated speed.
-			 */
-			switch ((reply_msg->status - ENETC_SPEED_5000)
-				* 1000 + 5000) {
-			case RTE_ETH_SPEED_NUM_10G:
-				link.link_speed = RTE_ETH_SPEED_NUM_10G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_25G:
-				link.link_speed = RTE_ETH_SPEED_NUM_25G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_40G:
-				link.link_speed = RTE_ETH_SPEED_NUM_40G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_50G:
-				link.link_speed = RTE_ETH_SPEED_NUM_50G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_100G:
-				link.link_speed = RTE_ETH_SPEED_NUM_100G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_200G:
-				link.link_speed = RTE_ETH_SPEED_NUM_200G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			default:
-				ENETC_PMD_WARN("Unrecognized speed code 0x%x, "
-					       "reporting unknown",
-					       reply_msg->status);
-				link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-				break;
-			}
-			break;
-		}
+		enetc4_decode_link_speed(reply_msg->status,
+					 hw->vf_link_legacy, &link);
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 09/14] net/enetc: support stats reset for VF
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (7 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
                           ` (7 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The NETC SI-level hardware counters (SIROCT0, SIRFRM0, SITOCT0,
SITFRM0, SITDFCR) are read-only for a VF and cannot be directly
zeroed. No VSI-PSI command class exists for stats reset, and using
FLR/soft-reset to clear counters is not reliable due to a known
hardware erratum on some NETC platforms.

Implement stats_reset for the enetc4 VF PMD using a software
snapshot/delta approach: on stats_reset, the current HW counter
values are captured as a baseline in a new per-device
enetc4_vf_stats_saved struct. stats_get then reports the delta
(current_hw_value - saved_baseline), so counters appear to start
from zero after each reset call. Per-ring software Rx error
accumulators (ierrors) are zeroed directly on reset.

Register the stats_reset callback in both VF ops tables
(enetc4_vf_ops and enetc4_vf_ops_no_vsi_m) so the feature is
available regardless of whether the VSI messaging path is enabled.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 17 +++++++
 drivers/net/enetc/enetc4_vf.c          | 61 +++++++++++++++++++++++---
 3 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 1765adc1bc..a42577c681 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -70,6 +70,7 @@ New Features
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
+  * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 6ff438eff6..3cddc4873f 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -104,6 +104,21 @@ struct enetc_bdr {
 	uint8_t rsc_enable;
 };
 
+/*
+ * Saved SI counter baseline for VF stats reset. Since the SI-level
+ * hardware counters (SIROCT0, SIRFRM0, SITOCT0, SITFRM0, SITDFCR)
+ * are read-only for a VF and cannot be directly zeroed, stats_reset
+ * captures the current counter values as a baseline. stats_get then
+ * reports the delta: current_hw_value - baseline.
+ */
+struct enetc4_vf_stats_saved {
+	uint64_t ipackets;
+	uint64_t opackets;
+	uint64_t ibytes;
+	uint64_t obytes;
+	uint64_t oerrors;
+};
+
 struct enetc_eth_hw {
 	struct rte_eth_dev *ndev;
 	struct enetc_hw hw;
@@ -123,6 +138,8 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	/* Baseline snapshot for VF stats reset (software delta approach). */
+	struct enetc4_vf_stats_saved vf_stats_saved;
 };
 
 /*
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index dae6e4c772..1399bfad31 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -224,15 +224,64 @@ enetc4_vf_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
 	uint8_t i;
 
 	PMD_INIT_FUNC_TRACE();
-	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
-	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
-	stats->ibytes = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
-	stats->obytes = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
-	stats->oerrors = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/*
+	 * The SI-level counters are read-only for a VF; they cannot be
+	 * zeroed directly. Instead, stats_reset captures a baseline
+	 * snapshot, and stats_get reports the delta so that the reported
+	 * values appear to start from zero after each reset call.
+	 */
+	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0) -
+			  hw->vf_stats_saved.ipackets;
+	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0) -
+			  hw->vf_stats_saved.opackets;
+	stats->ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0) -
+			  hw->vf_stats_saved.ibytes;
+	stats->obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0) -
+			  hw->vf_stats_saved.obytes;
+	stats->oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR) -
+			  hw->vf_stats_saved.oerrors;
+
 	for (i = 0; i < dev->data->nb_rx_queues; i++) {
 		rx_ring = dev->data->rx_queues[i];
 		stats->ierrors += rx_ring->ierrors;
 	}
+
+	return 0;
+}
+
+/*
+ * Reset VF statistics by capturing a new baseline snapshot of the
+ * SI-level hardware counters. Because those counters are read-only
+ * for a VF (hardware erratum prevents reliable clear via FLR/soft
+ * reset too), the driver uses a software delta approach: every
+ * stats_get call reports current_hw_value - saved_baseline.
+ */
+static int
+enetc4_vf_stats_reset(struct rte_eth_dev *dev)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_bdr *rx_ring;
+	uint8_t i;
+
+	PMD_INIT_FUNC_TRACE();
+
+	/* Snapshot current HW SI counter values as the new zero baseline. */
+	hw->vf_stats_saved.ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
+	hw->vf_stats_saved.opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
+	hw->vf_stats_saved.ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
+	hw->vf_stats_saved.obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
+	hw->vf_stats_saved.oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/* Reset the per-ring software Rx error accumulators. */
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		rx_ring = dev->data->rx_queues[i];
+		if (rx_ring)
+			rx_ring->ierrors = 0;
+	}
+
 	return 0;
 }
 
@@ -1544,6 +1593,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
@@ -1567,6 +1617,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
 	.get_reg              = enetc4_vf_get_regs,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 10/14] net/enetc4: add per-queue Rx interrupt support for VF
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (8 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 09/14] net/enetc: support stats reset for VF Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
                           ` (6 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add MSI-X per-queue Rx interrupt support to the ENETC4 VF PMD on the
cacheable (default) Rx path. This allows applications such as l3fwd-power
to block in epoll_wait when no traffic is present, reducing CPU utilization
to near zero.

MSI-X vector 0 is reserved for the PSI-to-VSI mailbox. Rx queue i is
mapped to MSI-X vector i + 1 via ENETC_SIMSIRRV. Per-ring coalescing is
enabled with ICPT=1 so the first arriving packet fires the interrupt
immediately. The SIRXIDR W1C detect bit is cleared before re-arming
ENETC_RBIER to prevent spurious interrupts after traffic stops.

The interrupt infrastructure (rte_intr_efd_enable +
rte_intr_vec_list_alloc) is set up before rte_intr_enable() so that
vfio-pci can wire each MSI-X vector to its eventfd when programming
the MSI-X table.

enetc4_dev_configure() is updated to trigger interrupt setup when
intr_conf.rxq is set (not only when intr_conf.lsc is set), as applications
like l3fwd-power set intr_conf.rxq without setting lsc.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             | 65 ++++++++++++++++++++
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc4_hw.h     |  2 +
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      |  9 +--
 drivers/net/enetc/enetc4_vf.c          | 82 +++++++++++++++++++++++++-
 7 files changed, 156 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 16389aee5b..2b73916682 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,10 @@ Key functionality includes:
   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.
+- Per-queue Rx interrupts on VFs (cacheable Rx path only), enabling
+  interrupt-driven receive with ``vfio-pci``. Applications set
+  ``intr_conf.rxq = 1`` in ``rte_eth_conf`` to activate this feature.
+  See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
@@ -167,3 +171,64 @@ PF/Common devargs
   Usage example::
 
     dpdk-testpmd -a 0000:00:00.0,nc=1 -- -i
+
+
+Rx Interrupt Mode (VF)
+----------------------
+
+The ENETC4 VF PMD supports per-queue MSI-X Rx interrupts on the cacheable
+(default) Rx path. This allows applications to block in ``epoll_wait``
+instead of busy-polling, reducing CPU utilization when traffic is absent.
+
+**MSI-X vector assignment**
+
+ENETC4 VF MSI-X vector 0 is reserved for the PSI-to-VSI mailbox interrupt
+(link status notifications). Rx queue ``i`` is mapped to vector ``i + 1``.
+The driver allocates all required eventfds before calling
+``rte_intr_enable()`` so that ``vfio-pci`` can wire each MSI-X vector to
+its eventfd when it programs the MSI-X table.
+
+**Kernel and driver requirements**
+
+- ``vfio-pci`` kernel module with no-IOMMU mode enabled (no SMMU required).
+- The non-cacheable memory mode (``nc=1`` devarg) does **not** support
+  Rx interrupts and returns ``-ENOTSUP`` from ``rx_queue_intr_enable``.
+
+**Host setup**
+
+.. code-block:: console
+
+   # Enable vfio-pci no-IOMMU mode (if SMMU is not available)
+   modprobe vfio enable_unsafe_noiommu_mode=1
+   modprobe vfio-pci
+
+   # Bind the VF to vfio-pci
+   echo vfio-pci > /sys/bus/pci/devices/<vf_pci_addr>/driver_override
+   echo <vf_pci_addr> > /sys/bus/pci/drivers_probe
+
+**Running l3fwd-power with a single queue and core**
+
+The ``l3fwd-power`` sample application demonstrates interrupt-driven Rx.
+It sets ``intr_conf.rxq = 1`` in ``rte_eth_conf``, which triggers the VF
+interrupt setup in the driver. The ``--vfio-intr=msix`` EAL flag instructs
+DPDK to use MSI-X eventfds for interrupt signalling.
+
+.. code-block:: console
+
+   ./dpdk-l3fwd-power -l 0-1 -n 1 --vfio-intr=msix \
+       -a <vf_pci_addr> -- \
+       -p 0x1 --config="(0,0,1)" --no-numa --interrupt-only
+
+Where:
+
+- ``-l 0-1`` assigns the main thread to core 0 and the forwarding lcore to
+  core 1.
+- ``-a <vf_pci_addr>`` specifies the VF PCI address (e.g. ``0000:01:00.1``).
+- ``--config="(0,0,1)"`` maps port 0, queue 0 to lcore 1.
+- ``--interrupt-only`` enables pure interrupt mode (no busy-poll fallback).
+
+With no incoming traffic the forwarding lcore sleeps in ``epoll_wait``;
+CPU utilization drops to near zero. On the first arriving packet the MSI-X
+interrupt fires, the lcore wakes, drains the ring, disables the interrupt,
+processes the burst, then re-enables and re-arms the interrupt before
+returning to sleep.
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 6540a43909..005c64a3ca 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -5,6 +5,7 @@
 ;
 [Features]
 Link status event    = Y
+Rx interrupt         = Y
 Speed capabilities   = Y
 Link status          = Y
 LRO                  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index a42577c681..c182495118 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -71,6 +71,7 @@ New Features
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
+  * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 8ad8f169d2..9543e982a2 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -250,6 +250,8 @@ struct enetc_rx_bd_ext {
 #define ENETC4_VSIIDR            0xA08
 #define ENETC4_VSIIER_MRIE       BIT(9)
 #define ENETC4_SI_INT_IDX        0
+/* MSI-X vector base for per-Rx-queue interrupts; vector 0 is the mailbox. */
+#define ENETC4_VF_RX_VEC_BASE    1
 
 /* VSI Registers */
 #define ENETC4_VSIMSGSR  0x204   /* RO */
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 3cddc4873f..b86193da90 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -134,6 +134,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 0295ed332b..b0c20e3b15 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -835,7 +835,8 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		return 0;
 
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0)
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0)
 			enetc4_vf_dev_intr(dev, false);
 		ret = enetc4_vf_dev_stop(dev);
 	} else {
@@ -957,12 +958,12 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	if (hw->device_id != ENETC4_DEV_ID_VF)
 		enetc4_port_wr(enetc_hw, ENETC4_PARCSCR, checksum);
 
-	/* Enable interrupts */
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0) {
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0) {
 			ret = enetc4_vf_dev_intr(dev, true);
 			if (ret)
-				ENETC_PMD_WARN("Failed to setup link interrupts");
+				ENETC_PMD_WARN("Failed to setup VF interrupts: %d", ret);
 		}
 	}
 
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 1399bfad31..ac61a46c9a 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1585,6 +1585,52 @@ static const struct rte_pci_id pci_vf_id_enetc4_map[] = {
 	{ .vendor_id = 0, /* sentinel */ },
 };
 
+static int
+enetc4_vf_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t vec;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return -ENOTSUP;
+
+	vec = queue_id + ENETC4_VF_RX_VEC_BASE;
+
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), vec);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR1, 0);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR0,
+			ENETC4_RBICR0_ICEN | ENETC4_RBICR0_ICPT(1));
+	enetc_wr(enetc_hw, ENETC_SIRXIDR, BIT(queue_id));
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, ENETC_RBIER_RXTIE);
+
+	return 0;
+}
+
+static int
+enetc4_vf_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return 0;
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, 0);
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), 0);
+
+	return 0;
+}
+
 /* Features supported by this driver */
 /* ops table used when VSI messaging is disabled */
 static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
@@ -1603,6 +1649,8 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
 	.rxq_info_get         = enetc4_rxq_info_get,
+	.rx_queue_intr_enable  = enetc4_vf_rx_queue_intr_enable,
+	.rx_queue_intr_disable = enetc4_vf_rx_queue_intr_disable,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
@@ -1636,6 +1684,8 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
 	.rxq_info_get         = enetc4_rxq_info_get,
+	.rx_queue_intr_enable  = enetc4_vf_rx_queue_intr_enable,
+	.rx_queue_intr_disable = enetc4_vf_rx_queue_intr_disable,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
@@ -1881,6 +1931,35 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		/* Vector index 0 */
 		enetc_wr(enetc_hw, ENETC4_SIMSIVR, ENETC4_SI_INT_IDX);
 
+		if (rte_intr_cap_multiple(intr_handle) &&
+		    eth_dev->data->nb_rx_queues > 0) {
+			uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+			uint16_t i;
+
+			ret = rte_intr_efd_enable(intr_handle,
+					nb_rx + ENETC4_VF_RX_VEC_BASE);
+			if (ret) {
+				ENETC_PMD_WARN("Failed to enable per-queue Rx eventfds: %d",
+					       ret);
+				ret = 0;
+				hw->rxq_intr_en = 0;
+			} else {
+				ret = rte_intr_vec_list_alloc(intr_handle,
+						"enetc4_vf_rx_intr", nb_rx);
+				if (ret) {
+					ENETC_PMD_WARN("Failed to alloc intr vec list: %d",
+						       ret);
+					rte_intr_efd_disable(intr_handle);
+					hw->rxq_intr_en = 0;
+				} else {
+					for (i = 0; i < nb_rx; i++)
+						rte_intr_vec_list_index_set(intr_handle, i,
+							i + ENETC4_VF_RX_VEC_BASE);
+					hw->rxq_intr_en = 1;
+				}
+			}
+		}
+
 		/* enable uio/vfio intr/eventfd mapping */
 		ret = rte_intr_enable(intr_handle);
 		if (ret) {
@@ -1904,6 +1983,7 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		ENETC_PMD_WARN("Failed to un-register link notification %d", ret);
 disable:
 	enetc_vf_enable_mr_int(enetc_hw, false);
+	hw->rxq_intr_en = 0;
 	ret = rte_intr_disable(intr_handle);
 	if (ret)
 		ENETC_PMD_WARN("Failed to disable INTR %d", ret);
@@ -1923,7 +2003,7 @@ static struct rte_pci_driver rte_enetc4_vf_pmd = {
 
 RTE_PMD_REGISTER_PCI(net_enetc4_vf, rte_enetc4_vf_pmd);
 RTE_PMD_REGISTER_PCI_TABLE(net_enetc4_vf, pci_vf_id_enetc4_map);
-RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic");
+RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic | vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 11/14] net/enetc4: add SI-based port VLAN insertion and removal
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (9 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
                           ` (5 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement hardware VLAN tag insertion on Tx and removal on Rx
via the per-SI VLAN isolation (pvid) mechanism on ENETC4.

On a PF, the driver writes directly to PSIaVLANR(0) (VID + enable
bit) and PSIaCFGR0(0) (SIVIE for Tx insertion, VTE for Rx removal,
SIVC_CVLAN to allow C-VLAN 0x8100 TPID).

On a privileged VF, the request is forwarded to the kernel PF via
the VSI-PSI mailbox using a new command class 0x24 (SI VLAN
isolation, cmd ID 0x1). The kernel PF handler programs the same
registers on behalf of the VF's station interface. The class 0x24
reply is added to the pass-through list in enetc4_msg_vsi_send() so
it is handled correctly alongside the existing command classes

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 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     | 14 +++++
 drivers/net/enetc/enetc.h              | 23 +++++++
 drivers/net/enetc/enetc4_ethdev.c      | 39 ++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 83 ++++++++++++++++++++++++++
 7 files changed, 165 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 2b73916682..262e2fe703 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -65,6 +65,10 @@ Key functionality includes:
   See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
+- SI-based port VLAN (pvid): Hardware VLAN tag insertion on Tx and removal on Rx, configured
+  via ``rte_eth_dev_set_vlan_pvid``. On a PF the registers are written directly; on a privileged
+  VF the request is forwarded to the kernel PF through the VSI-PSI mailbox (class 0x24).
+  Use the testpmd command ``tx_vlan set pvid <port_id> <vlan_id> on|off`` to enable or disable.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 005c64a3ca..01b0dc5b80 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c182495118..190d77ecee 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -72,6 +72,7 @@ New Features
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
+  * Added SI-based port VLAN insertion (Tx) and removal (Rx) 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 9543e982a2..a9552f21f4 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -277,6 +277,20 @@ struct enetc_rx_bd_ext {
 #define ENETC4_SICTR0		0x18
 #define ENETC4_SICTR1		0x1c
 
+/* PSI SI VLAN register: per-SI VLAN tag for insertion/removal */
+#define ENETC4_PSIVLANR(a)		((a) * 0x80 + 0x2008)
+#define ENETC4_PSIVLANR_E		BIT(31)  /* enable SI VLAN processing */
+#define ENETC4_PSIVLANR_VTEA		BIT(30)  /* 0=strip tag, 1=zero VID */
+#define ENETC4_PSIVLANR_PCP(v)		(((uint32_t)(v) & 0x7) << 13)
+#define ENETC4_PSIVLANR_DEI		BIT(12)
+#define ENETC4_PSIVLANR_VID(v)		((uint32_t)(v) & 0xfff)
+
+/* PSI SI configuration register 0 */
+#define ENETC4_PSICFGR0(a)		((a) * 0x80 + 0x2010)
+#define ENETC4_PSICFGR0_SIVC_CVLAN	BIT(24)  /* allow C-VLAN 0x8100 */
+#define ENETC4_PSICFGR0_SIVIE		BIT(14)  /* SI VLAN insertion enable */
+#define ENETC4_PSICFGR0_VTE		BIT(12)  /* SI VLAN removal enable */
+
 /* general register accessors */
 #define enetc4_rd_reg(reg)	rte_read32((void *)(reg))
 #define enetc4_wr_reg(reg, val)  rte_write32((val), (void *)(reg))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index b86193da90..6cabef000e 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -186,6 +186,8 @@ struct enetc_eth_adapter {
 enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
+	/* Configure SI VLAN isolation (insert / remove) */
+	ENETC_CLASS_ID_SI_VLAN_ISO = 0x24,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
 	ENETC_CLASS_ID_LINK_SPEED = 0x81,
 	ENETC_CLASS_ID_GET_IP_VER = 0xF0
@@ -211,6 +213,11 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* Command IDs for class ID 0x24 (SI VLAN isolation) */
+enum enetc_msg_si_vlan_cmd_id {
+	ENETC_CMD_ID_SET_SI_VLAN_ISO = 1,
+};
+
 /* IP_VER value returned when the version is not available */
 #define ENETC_IP_VER_NOT_AVAILABLE	0xFF
 
@@ -322,6 +329,22 @@ struct enetc_msg_vlan_exact_filter {
 	uint8_t reserved2;
 };
 
+/* VSI-PSI SI VLAN isolation message (class 0x24, cmd 0x1) */
+struct enetc_msg_si_vlan_iso {
+	struct enetc_msg_cmd_header header;
+	uint8_t ctrl;          /* E[7], VTEA[6], TPID[1:0] */
+	uint8_t pcp_dei_vid_hi; /* PCP[7:5], DEI[4], VID[11:8] */
+	uint8_t vid_lo;        /* VID[7:0] */
+	uint8_t reserved_0;
+	uint8_t flags;         /* SVIE[2], VTE[1] */
+	uint8_t reserved_1[3];
+};
+
+#define ENETC_SI_VLAN_ISO_E	BIT(7)  /* ctrl: enable SI VLAN */
+#define ENETC_SI_VLAN_ISO_VTEA	BIT(6)  /* ctrl: 0=strip, 1=zero VID */
+#define ENETC_SI_VLAN_ISO_SVIE	BIT(2)  /* flags: Tx insertion enable */
+#define ENETC_SI_VLAN_ISO_VTE	BIT(1)  /* flags: Rx removal enable */
+
 struct enetc_psi_reply_msg {
 	uint8_t class_id;
 	uint8_t status;
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index b0c20e3b15..b4e793e63c 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -868,6 +868,44 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 	return ret;
 }
 
+/* Configure SI-based VLAN insertion (Tx) and removal (Rx) for the PF. */
+static int
+enetc4_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t psivlanr, psicfgr0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	psicfgr0 = enetc4_port_rd(enetc_hw, ENETC4_PSICFGR0(0));
+
+	if (on) {
+		/* Build the VLAN register: enable bit + VID (PCP/DEI left 0) */
+		psivlanr = (uint32_t)ENETC4_PSIVLANR_E |
+			   ENETC4_PSIVLANR_VID(vlan_id);
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), psivlanr);
+
+		/* Allow C-VLAN TPID, enable Tx insertion and Rx removal */
+		psicfgr0 |= ENETC4_PSICFGR0_SIVC_CVLAN |
+			    ENETC4_PSICFGR0_SIVIE |
+			    ENETC4_PSICFGR0_VTE;
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+	} else {
+		/* Clear Tx insertion, Rx removal and C-VLAN allow bits */
+		psicfgr0 &= ~(ENETC4_PSICFGR0_SIVC_CVLAN |
+			      ENETC4_PSICFGR0_SIVIE |
+			      ENETC4_PSICFGR0_VTE);
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+
+		/* Clear the VLAN register (disable SI VLAN processing) */
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), 0);
+	}
+
+	return 0;
+}
+
 static int
 enetc4_promiscuous_enable(struct rte_eth_dev *dev)
 {
@@ -1263,6 +1301,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
+	.vlan_pvid_set        = enetc4_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index ac61a46c9a..9b03d91ca4 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -619,6 +619,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
 		case ENETC_CLASS_ID_GET_IP_VER:
+		case ENETC_CLASS_ID_SI_VLAN_ISO:
 			break;
 		default:
 			err = -EIO;
@@ -1523,6 +1524,87 @@ static int enetc4_vf_vlan_offload_set(struct rte_eth_dev *dev, int mask __rte_un
 	return 0;
 }
 
+/* Configure SI-based VLAN insertion/removal via VSI-PSI mailbox (class 0x24). */
+static int
+enetc4_vf_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_si_vlan_iso *cmd;
+	struct enetc_msg_swbd *msg;
+	struct enetc_psi_reply_msg *reply_msg;
+	uint32_t msg_size;
+	int err = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	reply_msg = rte_zmalloc(NULL, sizeof(*reply_msg), RTE_CACHE_LINE_SIZE);
+	if (!reply_msg) {
+		ENETC_PMD_ERR("Failed to alloc memory for reply_msg");
+		return -ENOMEM;
+	}
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_si_vlan_iso),
+			     ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg body");
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	if (msg->dma == RTE_BAD_IOVA) {
+		ENETC_PMD_ERR("Failed to get IOVA for SI VLAN isolation msg body");
+		rte_free(msg->vaddr);
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->size = msg_size;
+
+	cmd = (struct enetc_msg_si_vlan_iso *)msg->vaddr;
+
+	if (on) {
+		cmd->ctrl = (uint8_t)ENETC_SI_VLAN_ISO_E;
+		cmd->pcp_dei_vid_hi = (uint8_t)((vlan_id >> 8) & 0x0f);
+		cmd->vid_lo = (uint8_t)(vlan_id & 0xff);
+		cmd->flags = (uint8_t)(ENETC_SI_VLAN_ISO_SVIE | ENETC_SI_VLAN_ISO_VTE);
+	}
+	/* on=0: all fields zero (rte_zmalloc cleared the buffer) */
+
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_SI_VLAN_ISO,
+				     ENETC_CMD_ID_SET_SI_VLAN_ISO, 0, 0, 0);
+
+	/* send the command and wait for PSI reply */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error for SI VLAN isolation");
+		goto end;
+	}
+
+	enetc4_msg_vsi_reply_msg(enetc_hw, reply_msg);
+
+	if (reply_msg->class_id != ENETC_MSG_CLASS_ID_CMD_SUCCESS) {
+		ENETC_PMD_ERR("SI VLAN isolation command failed: class_id=0x%x status=0x%x",
+			      reply_msg->class_id, reply_msg->status);
+		err = -EINVAL;
+	}
+
+end:
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	rte_free(reply_msg);
+	return err;
+}
+
 static int
 enetc4_vf_mtu_set(struct rte_eth_dev *dev __rte_unused, uint16_t mtu __rte_unused)
 {
@@ -1679,6 +1761,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.link_update	      = enetc4_vf_link_update,
 	.vlan_filter_set      = enetc4_vf_vlan_filter_set,
 	.vlan_offload_set     = enetc4_vf_vlan_offload_set,
+	.vlan_pvid_set        = enetc4_vf_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 12/14] net/enetc4: update VF link status to bitmask encoding
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (10 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 13/14] net/enetc4: enable Tx PAUSE via VF Rx congestion mode Gagandeep Singh
                           ` (4 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF link status notification code was previously a two-value
enum (UP=0x0, DOWN=0x1). Change this to a bitmask so future bits can
carry additional state alongside the link up/down indication:

  ENETC_LINK_DOWN  BIT(0)  -- set when link is down

Link up is now encoded as the DOWN bit being clear, which keeps the
wire value for link-down identical (0x1) and ensures backward
compatibility with older kernel PFs.

Changes:
- enetc.h: replace enum link_status with a #define bitmask;
  drop ENETC_LINK_UP (no longer a named constant).
- enetc4_vf.c enetc4_process_psi_msg(): replace switch/case on
  ENETC_LINK_UP/DOWN with bitmask decode.
- enetc4_vf.c enetc4_vf_link_update(): same bitmask decode.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              |  8 ++++----
 drivers/net/enetc/enetc4_vf.c          | 27 +++++++-------------------
 3 files changed, 12 insertions(+), 24 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 190d77ecee..47090068d0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -73,6 +73,7 @@ New Features
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
+  * Updated ENETC4 VF link status reporting to use bitmask encoding.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 6cabef000e..d4d39737ec 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -235,10 +235,10 @@ enum vlan_status {
 	ENETC_VLAN_NO_RESOURCE = 0x3
 };
 
-enum link_status {
-	ENETC_LINK_UP = 0x0,
-	ENETC_LINK_DOWN = 0x1
-};
+/* Link status bitmask in PF-to-VF mailbox notification.
+ * Link up is encoded as the DOWN bit being clear.
+ */
+#define ENETC_LINK_DOWN  (1u << 0)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 9b03d91ca4..3bdd032dba 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -510,8 +510,10 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 	enetc4_msg_get_psi_msg(enetc_hw, msg);
 
 	if (msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (msg->status) {
-		case ENETC_LINK_UP:
+		if (msg->status & ENETC_LINK_DOWN) {
+			ENETC_PMD_DEBUG("Link is down");
+			link.link_status = RTE_ETH_LINK_DOWN;
+		} else {
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
 			/* Re-query speed from PF so the cached value reflects
@@ -528,14 +530,6 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 			} else {
 				ENETC_PMD_WARN("Failed to alloc msg for speed query");
 			}
-			break;
-		case ENETC_LINK_DOWN:
-			ENETC_PMD_DEBUG("Link is down");
-			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status 0x%x", msg->status);
-			break;
 		}
 		ret = rte_eth_linkstatus_set(eth_dev, &link);
 		if (!ret)
@@ -1208,17 +1202,10 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (reply_msg->status) {
-		case ENETC_LINK_UP:
-			link.link_status = RTE_ETH_LINK_UP;
-			break;
-		case ENETC_LINK_DOWN:
+		if (reply_msg->status & ENETC_LINK_DOWN)
 			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status");
-			break;
-		}
+		else
+			link.link_status = RTE_ETH_LINK_UP;
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 13/14] net/enetc4: enable Tx PAUSE via VF Rx congestion mode
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (11 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 10:48         ` [PATCH v5 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
                           ` (3 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

When the PF negotiates TX PAUSE on the port it signals this to the VF
via BIT(1) of the PF-to-VF link status mailbox message. The VF PMD must
respond by setting RBMR_CM (BIT(4)) on all active RX rings so the MAC
emits PAUSE frames on ingress pressure.

Add ENETC_RBMR_CM register definition, ENETC_LINK_TX_PAUSE bitmask,
and tx_pause_active state flag. Add enetc4_vf_set_congestion_mode() to
update all active RX rings and persist the state for rings started
later. Hook it into both the interrupt and poll link-update paths, and
apply the saved state in rx_queue_setup() and rx_queue_start().

RX PAUSE (honoring received PAUSE frames) is handled at the MAC level
by the PF and requires no VF PMD changes.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  1 +
 drivers/net/enetc/enetc.h              |  9 +++-
 drivers/net/enetc/enetc4_ethdev.c      | 13 +++++-
 drivers/net/enetc/enetc4_vf.c          | 59 ++++++++++++++++++++++++--
 6 files changed, 78 insertions(+), 6 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 01b0dc5b80..1f599dace7 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+Flow control         = Y
 VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 47090068d0..555ac65ae8 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -74,6 +74,7 @@ New Features
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
+  * Added TX PAUSE support for the ENETC4 VF via RX congestion mode.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 6e96562850..33d075fe59 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -51,6 +51,7 @@ enum enetc_bdr_type {TX, RX};
 							+ (off))
 /* RX BDR reg offsets */
 #define ENETC_RBMR		0x0 /* RX BDR mode register*/
+#define ENETC_RBMR_CM		BIT(4)  /* congestion mode: assert congestion to emit TX PAUSE */
 #define ENETC_RBMR_EN		BIT(31)
 
 #define ENETC_BMR_RESET		0x0 /* BDR reset*/
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index d4d39737ec..e47d208538 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -139,6 +139,10 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	/* 1 = TX PAUSE negotiated on port; VF RX rings must have RBMR_CM set.
+	 * Updated from the PF-to-VF link status mailbox message (BIT(1)).
+	 */
+	uint8_t tx_pause_active;
 	/* Baseline snapshot for VF stats reset (software delta approach). */
 	struct enetc4_vf_stats_saved vf_stats_saved;
 };
@@ -237,8 +241,11 @@ enum vlan_status {
 
 /* Link status bitmask in PF-to-VF mailbox notification.
  * Link up is encoded as the DOWN bit being clear.
+ * TX_PAUSE is set when the port has negotiated TX PAUSE; VF must enable
+ * congestion mode (ENETC_RBMR_CM) on its RX rings accordingly.
  */
-#define ENETC_LINK_DOWN  (1u << 0)
+#define ENETC_LINK_DOWN      (1u << 0)
+#define ENETC_LINK_TX_PAUSE  (1u << 1)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index b4e793e63c..9f75bc07c9 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -708,8 +708,12 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	if (!rx_conf->rx_deferred_start) {
-		/* enable ring */
+		/* Enable ring; apply congestion mode if TX PAUSE is already active. */
 		rx_enable |= ENETC_RBMR_EN;
+		if (adapter->hw.tx_pause_active)
+			rx_enable |= ENETC_RBMR_CM;
+		else
+			rx_enable &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
@@ -1085,7 +1089,12 @@ enetc4_rx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->rx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		rx_data = enetc4_rxbdr_rd(&priv->hw.hw, rx_ring->index,
 					 ENETC_RBMR);
-		rx_data = rx_data | ENETC_RBMR_EN;
+		rx_data |= ENETC_RBMR_EN;
+		/* Restore congestion mode if TX PAUSE is active. */
+		if (priv->hw.tx_pause_active)
+			rx_data |= ENETC_RBMR_CM;
+		else
+			rx_data &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&priv->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_data);
 		dev->data->rx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 3bdd032dba..b9a4f45a3a 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -491,6 +491,37 @@ enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
 	}
 }
 
+/*
+ * Set or clear ENETC_RBMR_CM (congestion mode) on all active VF RX rings.
+ * When set, the ring signals congestion to the MAC, causing it to emit TX
+ * PAUSE frames on ingress pressure. hw->tx_pause_active is updated so rings
+ * started later inherit the correct state.
+ */
+static void
+enetc4_vf_set_congestion_mode(struct rte_eth_dev *eth_dev, bool enable)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+	uint16_t i;
+	uint32_t rbmr;
+
+	hw->tx_pause_active = enable ? 1 : 0;
+
+	for (i = 0; i < nb_rx; i++) {
+		rbmr = enetc4_rxbdr_rd(enetc_hw, i, ENETC_RBMR);
+		if (enable)
+			rbmr |= ENETC_RBMR_CM;
+		else
+			rbmr &= ~(uint32_t)ENETC_RBMR_CM;
+		enetc4_rxbdr_wr(enetc_hw, i, ENETC_RBMR, rbmr);
+	}
+
+	ENETC_PMD_DEBUG("VF congestion mode %s on %u RX rings",
+			enable ? "enabled" : "disabled", nb_rx);
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
@@ -498,6 +529,7 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
+	bool tx_pause;
 	int ret = 0;
 
 	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
@@ -513,9 +545,24 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		if (msg->status & ENETC_LINK_DOWN) {
 			ENETC_PMD_DEBUG("Link is down");
 			link.link_status = RTE_ETH_LINK_DOWN;
+			/* Clear congestion mode on link-down so VF rings do not
+			 * assert congestion while the port is offline.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, false);
 		} else {
-			ENETC_PMD_DEBUG("Link is up");
+			/* BIT(1) is set when the port has negotiated TX PAUSE.
+			 * Legacy PF does not set this bit so tx_pause stays false.
+			 */
+			tx_pause = !!(msg->status & ENETC_LINK_TX_PAUSE);
+			ENETC_PMD_DEBUG("Link is up, tx_pause=%d", tx_pause);
 			link.link_status = RTE_ETH_LINK_UP;
+
+			/* Apply congestion mode before raising the carrier so
+			 * the VF rings are ready to emit PAUSE before traffic
+			 * starts flowing.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, tx_pause);
+
 			/* Re-query speed from PF so the cached value reflects
 			 * the current negotiated speed after link-up.
 			 */
@@ -1202,10 +1249,16 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		if (reply_msg->status & ENETC_LINK_DOWN)
+		if (reply_msg->status & ENETC_LINK_DOWN) {
 			link.link_status = RTE_ETH_LINK_DOWN;
-		else
+			/* Link is down: disable congestion mode on all RX rings. */
+			enetc4_vf_set_congestion_mode(dev, false);
+		} else {
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Restore congestion mode from the TX PAUSE bit. */
+			enetc4_vf_set_congestion_mode(dev,
+				!!(reply_msg->status & ENETC_LINK_TX_PAUSE));
+		}
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v5 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (12 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 13/14] net/enetc4: enable Tx PAUSE via VF Rx congestion mode Gagandeep Singh
@ 2026-08-10 10:48         ` Gagandeep Singh
  2026-08-10 15:25         ` [PATCH v5 00/14] net/enetc: add new features for ENETC4 on i.MX95 Stephen Hemminger
                           ` (2 subsequent siblings)
  16 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:48 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add enetc4_txq_wrr devarg to configure per-ring WRR weights in the
NETC LEAF-level Tx scheduler (TBaMR register, bits [6:4]).

The NETC Tx scheduler has three levels:
  - ROOT (port/TC): strict priority + CBS (PF/port space)
  - MID  (SI/VSI):  WBFS shaping (PF space)
  - LEAF (Tx BDR):  strict priority + frame-based WRR (VF/SI space)

TBaMR is in the VF own SI space, so no Linux PF involvement is
needed for PRIO or WRR configuration.

Changes:
- enetc_hw.h: add ENETC_TBMR_WRR_MASK, ENETC_TBMR_WRR(n) macros for
  TBaMR bits [6:4], and ENETC_TBMR_PRIO_MASK for bits [2:0]
- enetc.h: add txq_wrr pointer to enetc_eth_hw struct
- enetc4_ethdev.c: add parse_txq_wrr() and wire ENETC4_TXQ_WRR devarg
  through enetc4_get_devargs() and enetc4_dev_configure(); apply WRR
  bits in enetc4_tx_queue_setup() and enetc4_tx_queue_start()

Usage:
  # strict priority: ring 0 highest
  -a 0002:00:12.0,enetc4_txq_prior="3|2|1"

  # WRR 2:4:1 on same-priority rings
  -a 0002:00:12.0,enetc4_txq_prior="1|1|1",enetc4_txq_wrr="2|4|1"

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             | 12 ++++
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  5 ++
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      | 84 +++++++++++++++++++++++---
 5 files changed, 95 insertions(+), 8 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 262e2fe703..7399a416d3 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -166,6 +166,18 @@ PF/Common devargs
 
     dpdk-testpmd -a 0000:00:00.0,enetc4_txq_prior=1|2|3 -- -i
 
+``enetc4_txq_wrr``
+  Set per-queue WRR weight for the LEAF-level Tx scheduler (TBaMR bits [6:4]).
+  The value is a ``|``-separated list of WRR weights, one per Tx queue.
+  Meaningful only when the corresponding queues share the same strict-priority
+  level via ``enetc4_txq_prior``; queues with different priorities are
+  scheduled strictly regardless of their WRR weight.
+  Values beyond the maximum supported Tx queue count are discarded.
+
+  Usage example (WRR 2:4:1 on three equal-priority rings)::
+
+    dpdk-testpmd -a 0000:00:00.0,enetc4_txq_prior="1|1|1",enetc4_txq_wrr="2|4|1" -- -i
+
 ``nc``
   Select non-cacheable Rx/Tx ops (BD rings mapped as non-cacheable memory).
   Set to ``1`` to use non-cacheable descriptor ring operations.
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 555ac65ae8..b80c8ebda6 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -75,6 +75,7 @@ New Features
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
   * Added TX PAUSE support for the ENETC4 VF via RX congestion mode.
+  * Added WRR Tx scheduler devarg (``enetc4_txq_wrr``) for ENETC4 VF ring weights.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 33d075fe59..5a71f01db9 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -86,6 +86,11 @@ enum enetc_bdr_type {TX, RX};
 
 #define ENETC_RTBLENR_LEN(n)		((n) & ~0x7)
 #define ENETC_TBMR_EN			BIT(31)
+/* TBaMR[WRR] bits [6:4]: weight for same-priority ring arbitration (0=1x .. 7=8x). */
+#define ENETC_TBMR_WRR_MASK		GENMASK(6, 4)
+#define ENETC_TBMR_WRR(n)		((((n) - 1) & 0x7) << 4)
+/* TBaMR[PRIO] bits [2:0]: strict priority (0=lowest, 7=highest). */
+#define ENETC_TBMR_PRIO_MASK		GENMASK(2, 0)
 
 /* Port regs, offset: 1_0000h */
 #define ENETC_PORT_BASE			0x10000
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index e47d208538..c1a345e379 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -133,6 +133,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_timeout; /* VSI-PSI message wait timeout (iterations) */
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
+	uint32_t *txq_wrr;    /* per-queue TX WRR weight pre-shifted for TBMR[WRR] */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
 	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 9f75bc07c9..14c8dfbe8e 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -30,22 +30,26 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
+#define ENETC4_TXQ_WRR		"enetc4_txq_wrr"
 #define ENETC4_NC_MEMORY	"nc"
 
+
 static int
 parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 {
 	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
 	struct enetc_eth_hw *hw =
-		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
-	char *input_str = strdup(value);
+				ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
 	char *str;
 	uint32_t i = 0;
 
+	input_str = strdup(value);
 	if (!input_str)
-		return -ENOMEM;
+		return -1;
 
-	hw->txq_prior = calloc(hw->max_tx_queues, sizeof(uint32_t));
+	rte_free(hw->txq_prior);
+	hw->txq_prior = rte_zmalloc(NULL, hw->max_tx_queues * sizeof(uint32_t), 0);
 	if (!hw->txq_prior) {
 		free(input_str);
 		return -ENOMEM;
@@ -53,7 +57,46 @@ parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 
 	str = strtok(input_str, "|");
 	while (str != NULL && i < hw->max_tx_queues) {
-		hw->txq_prior[i++] = (uint32_t)atoi(str);
+		hw->txq_prior[i++] = atoi(str) & ENETC_TBMR_PRIO_MASK;
+		str = strtok(NULL, "|");
+	}
+
+	free(input_str);
+	return 0;
+}
+
+/* Parse enetc4_txq_wrr="w0|w1|..." devarg; weight 1..8 per ring. */
+static int parse_txq_wrr(const char *key __rte_unused, const char *value,
+			  void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
+	char *str;
+	uint32_t i = 0;
+	int w;
+
+	input_str = strdup(value);
+	if (!input_str)
+		return -1;
+
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = rte_zmalloc(NULL,
+			hw->max_tx_queues * sizeof(uint32_t), 0);
+	if (!hw->txq_wrr) {
+		free(input_str);
+		return -1;
+	}
+
+	str = strtok(input_str, "|");
+	while (str != NULL && i < hw->max_tx_queues) {
+		w = atoi(str);
+		if (w < 1)
+			w = 1;
+		if (w > 8)
+			w = 8;
+		hw->txq_wrr[i++] = ENETC_TBMR_WRR(w);
 		str = strtok(NULL, "|");
 	}
 
@@ -99,6 +142,13 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 			return 0;
 		}
 	}
+	if (!strcmp(key, ENETC4_TXQ_WRR)) {
+		if (rte_kvargs_process(kvlist, key,
+				       parse_txq_wrr, (void *)dev) < 0) {
+			rte_kvargs_free(kvlist);
+			return 0;
+		}
+	}
 	if (!strcmp(key, ENETC4_NC_MEMORY)) {
 		if (rte_kvargs_process(kvlist, key,
 				       parse_nc, (void *)dev) < 0) {
@@ -111,6 +161,7 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 	return 0;
 }
 
+
 static int
 enetc4_dev_start(struct rte_eth_dev *dev)
 {
@@ -450,7 +501,9 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 
 		/* apply TX queue priority if configured */
 		if (priv->hw.txq_prior)
-			tx_en |= priv->hw.txq_prior[tx_ring->index];
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		/* enable ring */
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index,
 			       ENETC_TBMR, tx_en);
@@ -865,7 +918,10 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		dev->data->tx_queues[i] = NULL;
 	}
 	dev->data->nb_tx_queues = 0;
-
+	rte_free(hw->txq_prior);
+	hw->txq_prior = NULL;
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = NULL;
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_depopulate();
 
@@ -1016,6 +1072,11 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	for (i = 0; i < dev->data->nb_tx_queues; i++)
 		enetc4_rxbdr_wr(enetc_hw, i, ENETC_TBMR, ENETC_BMR_RESET);
 
+	hw->nc_mode = 0;
+	enetc4_get_devargs(dev, ENETC4_TXQ_PRIORITIES);
+	enetc4_get_devargs(dev, ENETC4_TXQ_WRR);
+	enetc4_get_devargs(dev, ENETC4_NC_MEMORY);
+
 	if (dev->data->nb_rx_queues <= 1)
 		return 0;
 
@@ -1138,7 +1199,13 @@ enetc4_tx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->tx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		tx_data = enetc4_txbdr_rd(&priv->hw.hw, tx_ring->index,
 					 ENETC_TBMR);
-		tx_data = tx_data | ENETC_TBMR_EN;
+		/* Clear scheduler bits before applying fresh devarg values. */
+		tx_data &= ~(ENETC_TBMR_PRIO_MASK | ENETC_TBMR_WRR_MASK);
+		tx_data |= ENETC_TBMR_EN;
+		if (priv->hw.txq_prior)
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index, ENETC_TBMR,
 			       tx_data);
 		dev->data->tx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
@@ -1453,5 +1520,6 @@ RTE_PMD_REGISTER_PCI_TABLE(net_enetc4, pci_id_enetc4_map);
 RTE_PMD_REGISTER_KMOD_DEP(net_enetc4, "* vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4,
 			      ENETC4_TXQ_PRIORITIES "=<string> "
+			      ENETC4_TXQ_WRR "=<string> "
 			      ENETC4_NC_MEMORY "=<int>");
 RTE_LOG_REGISTER_DEFAULT(enetc4_logtype_pmd, NOTICE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* RE: [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95
  2026-08-09 21:00       ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Stephen Hemminger
@ 2026-08-10 10:58         ` Gagandeep Singh
  0 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-10 10:58 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev@dpdk.org, Hemant Agrawal

Hi

> -----Original Message-----
> From: Stephen Hemminger <stephen@networkplumber.org>
> Sent: Monday, August 10, 2026 2:30 AM
> To: Gagandeep Singh <G.Singh@nxp.com>
> Cc: dev@dpdk.org; Hemant Agrawal <hemant.agrawal@nxp.com>
> Subject: Re: [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95
> 
> On Fri,  7 Aug 2026 16:56:09 +0530
> Gagandeep Singh <g.singh@nxp.com> wrote:
> 
> > V4-changes:
> >  - fix doc build issue: WARNING: undefined label: pmd_build_and_test
> >
> > v3-changes:
> >  - fix doc build issue.
> >  - fix compilation issue on fedore:43-gcc-minsize
> >
> > V2-changes:
> >  - compilation fixes.
> >
> > V1-changes:
> > This series adds new PMD features to the ENETC4 driver targeting the
> > NXP i.MX95 NETC IP.
> >
> > The series covers:
> >
> >  - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
> >  - TSO: TCP Segmentation Offload for the VF Tx path.
> >  - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
> >  - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
> >    8-bit to support speeds beyond 10G.
> >  - Firmware version: report the NETC IP version via fw_version_get.
> >  - Register dump: dump SI, port (PF) and BD ring registers.
> >  - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
> >  - Link-up interrupt: refresh the cached link speed on each VF link-up
> >    interrupt so that link_update returns the current speed immediately.
> >  - Stats reset: software snapshot/delta approach for VF counter reset.
> >  - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
> >    enabling interrupt-driven receive with l3fwd-power.
> >  - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
> >  - VF link status bitmask: switch VF link status to bitmask encoding
> >    to align with the PF and newer kernel driver conventions.
> >  - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
> >    negotiated on the wire; adds Flow control = Y to enetc4.ini.
> >  - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.
> >
> > Gagandeep Singh (14):
> >   net/enetc: add KEEP_CRC offload support for ENETC4
> >   net/enetc: add TSO support for ENETC4 VF
> >   net/enetc: add RSC (hardware LRO) support for ENETC4
> >   net/enetc: extend link speed code field to 8-bit for PF-to-VF message
> >   net/enetc: support firmware version get for VF
> >   net/enetc: support registers dump
> >   net/enetc: support ethtool ring parameters
> >   net/enetc: refresh link speed on VF link-up interrupt
> >   net/enetc: support stats reset for VF
> >   net/enetc4: add per-queue Rx interrupt support for VF
> >   net/enetc4: add SI-based port VLAN insertion and removal
> >   net/enetc4: update VF link status to bitmask encoding
> >   net/enetc4: enable TX PAUSE via VF RX congestion mode
> >   net/enetc4: add WRR Tx scheduler devarg for VF rings
> >
> >  doc/guides/nics/enetc4.rst             |  77 +++
> >  doc/guides/nics/features/enetc4.ini    |   8 +
> >  doc/guides/rel_notes/release_26_11.rst |  19 +
> >  drivers/net/enetc/base/enetc4_hw.h     | 129 +++-
> >  drivers/net/enetc/base/enetc_hw.h      |   6 +
> >  drivers/net/enetc/enetc.h              | 135 ++++-
> >  drivers/net/enetc/enetc4_ethdev.c      | 402 +++++++++++--
> >  drivers/net/enetc/enetc4_vf.c          | 779 ++++++++++++++++++++++---
> >  drivers/net/enetc/enetc_rxtx.c         | 529 ++++++++++++++++-
> >  9 files changed, 1939 insertions(+), 145 deletions(-)
> >
> 
> Still lots of issues, the biggest one is that not each patch builds; not bisectable.
> 
> More extensive AI review.
> 
> ENETC4 v4 series review - 14 patches
> 
> No Reviewed-by.  The headline finding from the previous round is
> unchanged: 13 of the 14 commits fail to build.  Only the final commit is clean, so
> git bisect is broken across essentially the whole series.
> 
> Build (applied on c1a46b9, gcc 13.3, -Dwerror=true):
> 
>   [1] 8a0a793 01/14 add KEEP_CRC offload support for ENETC4
>   [1] 63cae7e 02/14 add TSO support for ENETC4 VF
>   [1] b28e1a8 03/14 add RSC (hardware LRO) support
>       ... all fail ...
>   [1] e334f6c 13/14 enable TX PAUSE via VF RX congestion mode
>   [0] b5e71d6 14/14 add WRR Tx scheduler devarg for VF rings
> 
> Three distinct causes, all introduced in patch 01.
> 
> 
> Patch 01
> 
> Error: prev_seg used but never declared (fails commits 01-02).
> 
>   enetc_rxtx.c:597:25: error: 'prev_seg' undeclared
>   enetc_rxtx.c:844:25: error: 'prev_seg' undeclared
> 
> Patch 01 assigns prev_seg and passes it to enetc_rx_crc_trim() in both
> enetc_clean_rx_ring_nc() and enetc_clean_rx_ring_cacheable(), but the
> declaration hunk only changes first_seg/cur_seg to = NULL.  The *prev_seg =
> NULL declaration does not arrive until patch 03.  Move it to patch 01.
> 
> Error: duplicate offload arrays (fails commits 01-13).
> 
>   enetc4_ethdev.c:110:17: error: redefinition of 'dev_rx_offloads_sup'
>   enetc4_ethdev.c:117:17: error: redefinition of 'dev_tx_offloads_sup'
> 
> Patch 01 adds a second copy of both arrays at the top of enetc4_ethdev.c; the
> pre-existing ones near line 97 are not deleted until patch 14.  The deletion hunk
> belongs in patch 01.
> 
> Error: duplicate rx_enable declaration (fails commits 01-13).
> 
>   enetc4_ethdev.c:552:18: error: redeclaration of 'rx_enable' with no
>   linkage
> 
> enetc4_rx_queue_setup() already declares uint32_t rx_enable upstream.
> Patch 01 adds a second one; the removal is in patch 14.
> 
> This is the signature of building only the squashed series.  Please run
> devtools/test-meson-builds.sh, or at minimum a -Dwerror=true build at each
> commit, before v5.
> 
> Error: PF loses Scattered Rx and Multi-segment Tx.
> 
> The old arrays deleted in patch 14 contained RTE_ETH_RX_OFFLOAD_SCATTER
> and RTE_ETH_TX_OFFLOAD_MULTI_SEGS.  The replacements added in patch 01
> do not.  Net result at the tip:
> 
>   upstream                      after the series
>   RX: IPV4|UDP|TCP|SCATTER      RX: IPV4|UDP|TCP|KEEP_CRC|TCP_LRO
>   TX: IPV4|UDP|TCP|MULTI_SEGS   TX: IPV4|UDP|TCP|TCP_TSO|UDP_TSO
> 
> Nothing in any commit message says this is intentional.  enetc4.ini still declares
> "Scattered Rx = Y", and the new RSC path builds multi-segment clusters, so the
> capability is still needed.  The VF arrays keep both - only the PF regresses.
> 
> 
> Patch 02
> 
> Error: mbuf leak in enetc_xmit_pkts_lso().
> 
> Two validation paths drop a TSO frame with "start++; continue;":
> 
>   - hdr_len >= pkt_len || hdr_len > data_len
>   - tso_segsz == 0 || data_unit > ENETC4_LSO_MAX_DATA_UNIT ||
>     hdr_len + tso_segsz > ENETC4_LSO_MAX_FRAME
> 
> Neither writes q_swbd[i].buffer_addr, and neither frees the mbuf, but both count
> the packet in the burst return value.  The application considers it transmitted;
> enetc_clean_tx_ring() never sees it.
> Free-and-continue is the right shape here - add rte_pktmbuf_free(seg) before
> each start++.
> 
> Error: 16-bit byte swap on a uint8_t field.
> 
> In the non-TSO path of the same function:
> 
>   txbd->flags |= rte_cpu_to_le_16(ENETC4_TXBD_FLAGS_F);
> 
> struct enetc_tx_bd.flags is uint8_t.  On big-endian this evaluates to
> 0x8000 and truncates to 0, so the frame-last flag is never set and Tx stalls.
> Upstream and the LSO path in this very function both use plain "txbd->flags |=
> ENETC4_TXBD_FLAGS_F;".  Drop the conversion.
> 
> Warning: ring doubling bypasses MAX_BD_COUNT.
> 
> Both enetc4_alloc_txbdr() (LSO, patch 02) and enetc4_alloc_rxbdr() (RSC, patch
> 03) double nb_desc, but the nb_desc > MAX_BD_COUNT check runs before the
> doubling.  With MAX_BD_COUNT at 64000 the effective ring reaches 128000
> descriptors, and ENETC_RTBLENR_LEN(n) is just
> ((n) & ~0x7) with no upper clamp.  Validate the post-doubling count, or halve the
> accepted nb_desc when LSO/RSC is on.
> 
> 
> Patch 04
> 
> Warning: link speed decode has no upper bound.
> 
>   link.link_speed = (reply_msg->status - ENETC_SPEED_5000) * 1000 + 5000;
> 
> status is uint8_t and the default arm now catches everything from 8 to 255, so a
> garbage or unrecognized code yields a fabricated speed (0xFF -> 253000 Mbps)
> rather than RTE_ETH_SPEED_NUM_UNKNOWN.  Note also that 0xF, previously
> ENETC_SPEED_NOT_SUPPORTED, now decodes as 13000 Mbps in non-legacy
> mode.  Bound the result, or reject codes that do not map to a defined
> RTE_ETH_SPEED_NUM_* value.
> 
> Warning: flipping the reply-status extraction from 4-bit to 8-bit by default is a
> wire-protocol compatibility break.  Users on a PF kernel older than 6.18.37 must
> add vf_link_legacy=1 or link reporting breaks silently.  That deserves an explicit
> line in the release notes, not just "extended the field to 8-bit".
> 
> Info: adds a second #include <rte_kvargs.h> immediately below the existing one.
> 
> 
> Series-wide
> 
> Warning: mailbox ops added to the no-VSI ops table.
> 
> enetc4_vf_ops_no_vsi_m is selected when the user passes enetc4_vsi_disable,
> that is, explicitly asks for no VSI-PSI messaging.
> The series adds fw_version_get, mac_addr_set, mac_addr_add, promiscuous_*,
> allmulticast_*, vlan_filter_set, vlan_offload_set, vlan_pvid_set and stats_reset to
> it.  Most of those reach
> enetc4_msg_vsi_send() directly or through a helper, so they will now attempt a
> transaction on the disabled mailbox and stall until vsi_timeout.  Previously ethdev
> returned -ENOTSUP immediately.  Only the genuinely register-local ops (get_reg,
> rxq_info_get, txq_info_get, and stats_reset if it stays direct-register) belong in that
> table.
> 
> Warning: new devargs undocumented.
> 
> vf_link_legacy (patch 04) and enetc4_txq_wrr (patch 14) are both in
> RTE_PMD_REGISTER_PARAM_STRING but absent from
> doc/guides/nics/enetc4.rst, which documents every pre-existing devarg.
> Same gap flagged in the previous round.
> 
> Info: patch 01 adds two stray blank lines in enetc4_ethdev.c (one before
> enetc4_rx_queue_release, later removed in patch 03); patch 02 adds three more
> around enetc4_alloc_txbdr/enetc4_free_bdr and in struct enetc_bdr.
> 
> Info: enetc_recv_pkts_rsc is assigned to dev->rx_pkt_burst from
> enetc4_rx_queue_setup(), which will silently override the nc=1 burst selection
> made in dev_init.  Worth a guard or at least a comment on precedence.
> 
> I stopped short of a full read of patches 05-14 since the build breakage has to be
> resolved first and several of these findings will shift hunks around.  I can go back
> through 05-14 in detail once there is a v5, or now if you would rather have the
> complete list before the author respins.

V5 sent. I missed to mention V5-changes in the cover letter.
The changes are:
- Patch 1 Build fixes which includes:
    'prev_seg' undeclared fixed.
    redefinition of 'dev_rx_offloads_sup' fixed.
    Error: duplicate rx_enable declaration fixed.
   PF loses Scattered Rx and Multi-segment Tx
- mbuf leak in enetc_xmit_pkts_lso() fixed.
- fixed conversion on ENETC4_TXBD_FLAGS_F issue.
- fixed link speed decode has no upper bound.
- fixed mailbox ops added to the no-VSI ops table.
- new devargs documented.

Regards,
Gagan

^ permalink raw reply	[flat|nested] 110+ messages in thread

* Re: [PATCH v5 00/14] net/enetc: add new features for ENETC4 on i.MX95
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (13 preceding siblings ...)
  2026-08-10 10:48         ` [PATCH v5 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
@ 2026-08-10 15:25         ` Stephen Hemminger
  2026-08-10 15:40         ` Stephen Hemminger
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
  16 siblings, 0 replies; 110+ messages in thread
From: Stephen Hemminger @ 2026-08-10 15:25 UTC (permalink / raw)
  To: Gagandeep Singh; +Cc: dev, hemant.agrawal

On Mon, 10 Aug 2026 16:18:01 +0530
Gagandeep Singh <g.singh@nxp.com> wrote:

> V4-changes:
>  - fix doc build issue: WARNING: undefined label: pmd_build_and_test
> 
> v3-changes:
>  - fix doc build issue.
>  - fix compilation issue on fedore:43-gcc-minsize
> 
> V2-changes:
>  - compilation fixes.
> 
> V1-changes:
> This series adds new PMD features to the ENETC4 driver targeting the
> NXP i.MX95 NETC IP.
> 
> The series covers:
> 
>  - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
>  - TSO: TCP Segmentation Offload for the VF Tx path.
>  - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
>  - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
>    8-bit to support speeds beyond 10G.
>  - Firmware version: report the NETC IP version via fw_version_get.
>  - Register dump: dump SI, port (PF) and BD ring registers.
>  - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
>  - Link-up interrupt: refresh the cached link speed on each VF link-up
>    interrupt so that link_update returns the current speed immediately.
>  - Stats reset: software snapshot/delta approach for VF counter reset.
>  - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
>    enabling interrupt-driven receive with l3fwd-power.
>  - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
>  - VF link status bitmask: switch VF link status to bitmask encoding
>    to align with the PF and newer kernel driver conventions.
>  - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
>    negotiated on the wire; adds Flow control = Y to enetc4.ini.
>  - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.
> 
> Gagandeep Singh (14):
>   net/enetc: add keep-CRC Rx offload for ENETC4
>   net/enetc: add TSO support for ENETC4 VF
>   net/enetc: add RSC (hardware LRO) support for ENETC4
>   net/enetc: extend PF-VF link speed field to 8 bits
>   net/enetc: support firmware version get for VF
>   net/enetc: support registers dump
>   net/enetc: support ethtool ring parameters
>   net/enetc: refresh link speed on VF link-up interrupt
>   net/enetc: support stats reset for VF
>   net/enetc4: add per-queue Rx interrupt support for VF
>   net/enetc4: add SI-based port VLAN insertion and removal
>   net/enetc4: update VF link status to bitmask encoding
>   net/enetc4: enable Tx PAUSE via VF Rx congestion mode
>   net/enetc4: add WRR Tx scheduler devarg for VF rings
> 
>  doc/guides/nics/enetc4.rst             | 100 +++
>  doc/guides/nics/features/enetc4.ini    |   8 +
>  doc/guides/rel_notes/release_26_11.rst |  21 +
>  drivers/net/enetc/base/enetc4_hw.h     | 129 +++-
>  drivers/net/enetc/base/enetc_hw.h      |   6 +
>  drivers/net/enetc/enetc.h              | 134 ++++-
>  drivers/net/enetc/enetc4_ethdev.c      | 413 +++++++++++--
>  drivers/net/enetc/enetc4_vf.c          | 801 ++++++++++++++++++++++---
>  drivers/net/enetc/enetc_rxtx.c         | 531 +++++++++++++++-
>  9 files changed, 1998 insertions(+), 145 deletions(-)
> 

Better but still has AI review issues:

ENETC4 v5 series review - 14 patches

No Reviewed-by; two errors remain.

Build status is now correct. All 14 commits build clean individually
on top of c1a46b9 with gcc 13.3 and -Dwerror=true:

[0] d657b18 01/14 add keep-CRC Rx offload for ENETC4
[0] 410e8f0 02/14 add TSO support for ENETC4 VF
[0] 1dc5e61 03/14 add RSC (hardware LRO) support for ENETC4
[0] 3dc3726 04/14 extend PF-VF link speed field to 8 bits
[0] e1ba634 05/14 support firmware version get for VF
[0] f3772c4 06/14 support registers dump
[0] 4c51974 07/14 support ethtool ring parameters
[0] 663e4ac 08/14 refresh link speed on VF link-up interrupt
[0] d9c0dfa 09/14 support stats reset for VF
[0] 3b7ef0c 10/14 add per-queue Rx interrupt support for VF
[0] 131a052 11/14 add SI-based port VLAN insertion and removal
[0] ea47dc2 12/14 update VF link status to bitmask encoding
[0] ffaa8a6 13/14 enable Tx PAUSE via VF Rx congestion mode
[0] 57ac69c 14/14 add WRR Tx scheduler devarg for VF rings

check-git-log.sh: 14/14 valid patches. No trailing whitespace.

Confirmed fixed from v4: prev_seg declaration moved to patch 01; the
duplicate offload arrays and duplicate rx_enable removed at the point
of introduction; SCATTER and MULTI_SEGS restored to the PF capability
lists; both LSO validation paths now free the mbuf before start++; the
rte_cpu_to_le_16() on the uint8_t flags field dropped; Tx ring doubling
validated post-multiply; mailbox-dependent ops removed from the no-VSI
table; link speed decode bounded to defined RTE_ETH_SPEED_NUM_* values;
duplicate rte_kvargs.h include removed; RSC now explicitly rejects nc=1
rather than silently overriding the burst selection; vf_link_legacy and
enetc4_txq_wrr documented, and the release note calls out the 6.18.37
PF requirement.

This round I read patches 05-14 in full, which I had deferred.

Patch 14

Error: free() on memory allocated with rte_zmalloc().

parse_txq_prior() was converted from calloc() to rte_zmalloc(), and
enetc4_dev_close() was updated to rte_free(). enetc4_dev_uninit() was
not:

static int
enetc4_dev_uninit(struct rte_eth_dev eth_dev)
{
...
if (hw->txq_prior) {
free(hw->txq_prior); / rte_zmalloc'd */
hw->txq_prior = NULL;
}

    return enetc4_dev_close(eth_dev);

}

uninit runs before close, so on any port using enetc4_txq_prior the
pointer is still non-NULL here and libc free() is handed a pointer from
the DPDK heap. txq_wrr is not freed on this path at all. Both
allocations are already released correctly in enetc4_dev_close(), so
the simplest fix is to delete the block from enetc4_dev_uninit().

Patch 10

Error: eventfd and vector-list leak on interrupt teardown.

enetc4_vf_dev_intr(dev, true) calls rte_intr_efd_enable() and
rte_intr_vec_list_alloc(). The disable path clears rxq_intr_en, calls
rte_intr_disable() and unregisters the callback, but never calls
rte_intr_efd_disable() or rte_intr_vec_list_free(). The eventfds and
the vector list survive dev_close, and a subsequent configure/close
cycle allocates a new set each time.

The intr_enable_fail: label has the same gap: if rte_intr_enable()
fails after efd_enable() succeeded, both resources are dropped.

disable:
enetc_vf_enable_mr_int(enetc_hw, false);
hw->rxq_intr_en = 0;

rte_intr_vec_list_free(intr_handle);

    rte_intr_efd_disable(intr_handle);
    ret = rte_intr_disable(intr_handle);

Patch 03

Warning: Rx ring doubling still bypasses MAX_BD_COUNT.

The Tx side now validates after the multiply, but enetc4_alloc_rxbdr()
does not:

ring_desc = rxr->rsc_enable ? (uint32_t)nb_desc * 2 : (uint32_t)nb_desc;

size = ring_desc * sizeof(struct enetc_swbd);

The only bound is nb_rx_desc > MAX_BD_COUNT in enetc4_rx_queue_setup(),
checked before the doubling, so an RSC ring can reach 128000 entries.
ENETC_RTBLENR_LEN(n) is ((n) & ~0x7) with no upper clamp. Mirror the
check that enetc4_alloc_txbdr() already has.

Patch 08

Warning: VSI-PSI transaction issued from the interrupt handler.

enetc4_process_psi_msg() runs from enetc4_dev_interrupt_handler(), and
now issues a fresh mailbox round trip:

    rte_free(msg);
    msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
    if (msg) {
            if (!enetc4_vf_get_link_speed(eth_dev, msg) && ...

There are eleven enetc4_msg_vsi_send() call sites and no lock anywhere
in the file. Until this patch every one of them ran on the control
thread, so they were serialized by convention. This is the first that
runs on the interrupt thread, so it can interleave with a concurrent
mac_addr_set, vlan_pvid_set, mtu_set or link_update: both writers touch
VSIMSGSNDAR/VSIMSGSR and one consumes the other's reply.

A mutex around the send/poll sequence in enetc4_msg_vsi_send() would
cover all callers at once. Note it would need to be a sleeping lock,
not rte_spinlock_t, since the poll loop waits up to vsi_timeout.

Info: the free-and-reallocate of msg is unnecessary churn; memset of
the existing buffer would do, and would remove the NULL branch.

Patch 13

Warning: unsynchronized access to tx_pause_active and RBMR.

hw->tx_pause_active is written by enetc4_vf_set_congestion_mode() on
the interrupt thread and read by enetc4_rx_queue_setup() and
enetc4_rx_queue_start() on the control thread as a plain uint8_t. Use
rte_atomic_store_explicit()/rte_atomic_load_explicit() with
rte_memory_order_release/acquire; relaxed is not enough here because
the flag publishes the RBMR writes that precede it.

The RBMR read-modify-write is the larger half of the same problem:

    rbmr = enetc4_rxbdr_rd(enetc_hw, i, ENETC_RBMR);
    if (enable)
            rbmr |= ENETC_RBMR_CM;
    ...
    enetc4_rxbdr_wr(enetc_hw, i, ENETC_RBMR, rbmr);

enetc4_rx_queue_start() and _stop() perform the same read-modify-write
on ENETC_RBMR_EN from the control thread. A link-status interrupt
arriving mid-sequence can lose either update, leaving a ring enabled
without congestion mode or disabled with it.

Patch 06

Warning: signed shift overflow in the register-dump version field.

regs->version = hw->device_id << 16 | hw->revision_id;

device_id is uint16_t, so it promotes to int before the shift. Both
ENETC4_DEV_ID (0xe101) and ENETC4_DEV_ID_VF (0xef00) have bit 15 set,
so the shift result is not representable in int -- undefined behaviour,
and UBSan will flag it. The same line appears in both
enetc4_get_regs() and enetc4_vf_get_regs().

regs->version = (uint32_t)hw->device_id << 16 | hw->revision_id;

Info: enetc4_txbdr_regs[] and enetc4_rxbdr_regs[] are static const
arrays defined in enetc.h, so every translation unit that includes the
header gets its own copy. Moving them to one .c file with an extern
declaration, or making them macros, avoids the duplication.

Patch 05

Info: the fw_size == 0 early return reports the length of "0.0" rather
than of the real version string, since ip_mj and ip_mn are still zero
at that point. Callers using the query-then-allocate idiom get a
buffer that is too small, though the second call returns the correct
size so it does self-correct.

Series-wide

Info: four new consecutive-blank-line pairs -- enetc.h:464,
enetc4_ethdev.c:36 and :164, enetc_rxtx.c:1144.

Info: the release note entry uses "TX PAUSE" and "RX congestion mode";
the commit subjects were corrected to Tx/Rx in this revision but the
note was not.

Info: parse_txq_prior() returns -1 for strdup failure but -ENOMEM for
the rte_zmalloc failure; parse_txq_wrr() returns -1 for both. Worth
making consistent.



^ permalink raw reply	[flat|nested] 110+ messages in thread

* Re: [PATCH v5 00/14] net/enetc: add new features for ENETC4 on i.MX95
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (14 preceding siblings ...)
  2026-08-10 15:25         ` [PATCH v5 00/14] net/enetc: add new features for ENETC4 on i.MX95 Stephen Hemminger
@ 2026-08-10 15:40         ` Stephen Hemminger
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
  16 siblings, 0 replies; 110+ messages in thread
From: Stephen Hemminger @ 2026-08-10 15:40 UTC (permalink / raw)
  To: Gagandeep Singh; +Cc: dev, hemant.agrawal

On Mon, 10 Aug 2026 16:18:01 +0530
Gagandeep Singh <g.singh@nxp.com> wrote:

> V4-changes:
>  - fix doc build issue: WARNING: undefined label: pmd_build_and_test
> 
> v3-changes:
>  - fix doc build issue.
>  - fix compilation issue on fedore:43-gcc-minsize
> 
> V2-changes:
>  - compilation fixes.
> 
> V1-changes:
> This series adds new PMD features to the ENETC4 driver targeting the
> NXP i.MX95 NETC IP.
> 
> The series covers:
> 
>  - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
>  - TSO: TCP Segmentation Offload for the VF Tx path.
>  - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
>  - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
>    8-bit to support speeds beyond 10G.
>  - Firmware version: report the NETC IP version via fw_version_get.
>  - Register dump: dump SI, port (PF) and BD ring registers.
>  - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
>  - Link-up interrupt: refresh the cached link speed on each VF link-up
>    interrupt so that link_update returns the current speed immediately.
>  - Stats reset: software snapshot/delta approach for VF counter reset.
>  - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
>    enabling interrupt-driven receive with l3fwd-power.
>  - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
>  - VF link status bitmask: switch VF link status to bitmask encoding
>    to align with the PF and newer kernel driver conventions.
>  - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
>    negotiated on the wire; adds Flow control = Y to enetc4.ini.
>  - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.
> 
> Gagandeep Singh (14):
>   net/enetc: add keep-CRC Rx offload for ENETC4
>   net/enetc: add TSO support for ENETC4 VF
>   net/enetc: add RSC (hardware LRO) support for ENETC4
>   net/enetc: extend PF-VF link speed field to 8 bits
>   net/enetc: support firmware version get for VF
>   net/enetc: support registers dump
>   net/enetc: support ethtool ring parameters
>   net/enetc: refresh link speed on VF link-up interrupt
>   net/enetc: support stats reset for VF
>   net/enetc4: add per-queue Rx interrupt support for VF
>   net/enetc4: add SI-based port VLAN insertion and removal
>   net/enetc4: update VF link status to bitmask encoding
>   net/enetc4: enable Tx PAUSE via VF Rx congestion mode
>   net/enetc4: add WRR Tx scheduler devarg for VF rings
> 
>  doc/guides/nics/enetc4.rst             | 100 +++
>  doc/guides/nics/features/enetc4.ini    |   8 +
>  doc/guides/rel_notes/release_26_11.rst |  21 +
>  drivers/net/enetc/base/enetc4_hw.h     | 129 +++-
>  drivers/net/enetc/base/enetc_hw.h      |   6 +
>  drivers/net/enetc/enetc.h              | 134 ++++-
>  drivers/net/enetc/enetc4_ethdev.c      | 413 +++++++++++--
>  drivers/net/enetc/enetc4_vf.c          | 801 ++++++++++++++++++++++---
>  drivers/net/enetc/enetc_rxtx.c         | 531 +++++++++++++++-
>  9 files changed, 1998 insertions(+), 145 deletions(-)
> 

More AI review feedback.
ENETC4 v5 series review - 14 patches

No Reviewed-by; two errors remain.

Build status is now correct.  All 14 commits build clean individually
on top of c1a46b9 with gcc 13.3 and -Dwerror=true:

  [0] d657b18 01/14 add keep-CRC Rx offload for ENETC4
  [0] 410e8f0 02/14 add TSO support for ENETC4 VF
  [0] 1dc5e61 03/14 add RSC (hardware LRO) support for ENETC4
  [0] 3dc3726 04/14 extend PF-VF link speed field to 8 bits
  [0] e1ba634 05/14 support firmware version get for VF
  [0] f3772c4 06/14 support registers dump
  [0] 4c51974 07/14 support ethtool ring parameters
  [0] 663e4ac 08/14 refresh link speed on VF link-up interrupt
  [0] d9c0dfa 09/14 support stats reset for VF
  [0] 3b7ef0c 10/14 add per-queue Rx interrupt support for VF
  [0] 131a052 11/14 add SI-based port VLAN insertion and removal
  [0] ea47dc2 12/14 update VF link status to bitmask encoding
  [0] ffaa8a6 13/14 enable Tx PAUSE via VF Rx congestion mode
  [0] 57ac69c 14/14 add WRR Tx scheduler devarg for VF rings

check-git-log.sh: 14/14 valid patches.  No trailing whitespace.

Confirmed fixed from v4: prev_seg declaration moved to patch 01; the
duplicate offload arrays and duplicate rx_enable removed at the point
of introduction; SCATTER and MULTI_SEGS restored to the PF capability
lists; both LSO validation paths now free the mbuf before start++; the
rte_cpu_to_le_16() on the uint8_t flags field dropped; Tx ring doubling
validated post-multiply; mailbox-dependent ops removed from the no-VSI
table; link speed decode bounded to defined RTE_ETH_SPEED_NUM_* values;
duplicate rte_kvargs.h include removed; RSC now explicitly rejects nc=1
rather than silently overriding the burst selection; vf_link_legacy and
enetc4_txq_wrr documented, and the release note calls out the 6.18.37
PF requirement.

This round I read patches 05-14 in full, which I had deferred.


Patch 14

Error: free() on memory allocated with rte_zmalloc().

parse_txq_prior() was converted from calloc() to rte_zmalloc(), and
enetc4_dev_close() was updated to rte_free().  enetc4_dev_uninit() was
not:

  static int
  enetc4_dev_uninit(struct rte_eth_dev *eth_dev)
  {
        ...
        if (hw->txq_prior) {
                free(hw->txq_prior);        /* rte_zmalloc'd */
                hw->txq_prior = NULL;
        }

        return enetc4_dev_close(eth_dev);
  }

uninit runs before close, so on any port using enetc4_txq_prior the
pointer is still non-NULL here and libc free() is handed a pointer from
the DPDK heap.  txq_wrr is not freed on this path at all.  Both
allocations are already released correctly in enetc4_dev_close(), so
the simplest fix is to delete the block from enetc4_dev_uninit().


Patch 10

Error: eventfd and vector-list leak on interrupt teardown.

enetc4_vf_dev_intr(dev, true) calls rte_intr_efd_enable() and
rte_intr_vec_list_alloc().  The disable path clears rxq_intr_en, calls
rte_intr_disable() and unregisters the callback, but never calls
rte_intr_efd_disable() or rte_intr_vec_list_free().  The eventfds and
the vector list survive dev_close, and a subsequent configure/close
cycle allocates a new set each time.

The intr_enable_fail: label has the same gap: if rte_intr_enable()
fails after efd_enable() succeeded, both resources are dropped.

  disable:
        enetc_vf_enable_mr_int(enetc_hw, false);
        hw->rxq_intr_en = 0;
  +     rte_intr_vec_list_free(intr_handle);
  +     rte_intr_efd_disable(intr_handle);
        ret = rte_intr_disable(intr_handle);


Patch 03

Warning: Rx ring doubling still bypasses MAX_BD_COUNT.

The Tx side now validates after the multiply, but enetc4_alloc_rxbdr()
does not:

  ring_desc = rxr->rsc_enable ? (uint32_t)nb_desc * 2 : (uint32_t)nb_desc;

  size = ring_desc * sizeof(struct enetc_swbd);

The only bound is nb_rx_desc > MAX_BD_COUNT in enetc4_rx_queue_setup(),
checked before the doubling, so an RSC ring can reach 128000 entries.
ENETC_RTBLENR_LEN(n) is ((n) & ~0x7) with no upper clamp.  Mirror the
check that enetc4_alloc_txbdr() already has.


Patch 08

Warning: VSI-PSI transaction issued from the interrupt handler.

enetc4_process_psi_msg() runs from enetc4_dev_interrupt_handler(), and
now issues a fresh mailbox round trip:

        rte_free(msg);
        msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
        if (msg) {
                if (!enetc4_vf_get_link_speed(eth_dev, msg) && ...

There are eleven enetc4_msg_vsi_send() call sites and no lock anywhere
in the file.  Until this patch every one of them ran on the control
thread, so they were serialized by convention.  This is the first that
runs on the interrupt thread, so it can interleave with a concurrent
mac_addr_set, vlan_pvid_set, mtu_set or link_update: both writers touch
VSIMSGSNDAR/VSIMSGSR and one consumes the other's reply.

A mutex around the send/poll sequence in enetc4_msg_vsi_send() would
cover all callers at once.  Note it would need to be a sleeping lock,
not rte_spinlock_t, since the poll loop waits up to vsi_timeout.

Info: the free-and-reallocate of msg is unnecessary churn; memset of
the existing buffer would do, and would remove the NULL branch.


Patch 13

Warning: unsynchronized access to tx_pause_active and RBMR.

hw->tx_pause_active is written by enetc4_vf_set_congestion_mode() on
the interrupt thread and read by enetc4_rx_queue_setup() and
enetc4_rx_queue_start() on the control thread as a plain uint8_t.  Use
rte_atomic_store_explicit()/rte_atomic_load_explicit() with
rte_memory_order_release/acquire; relaxed is not enough here because
the flag publishes the RBMR writes that precede it.

The RBMR read-modify-write is the larger half of the same problem:

        rbmr = enetc4_rxbdr_rd(enetc_hw, i, ENETC_RBMR);
        if (enable)
                rbmr |= ENETC_RBMR_CM;
        ...
        enetc4_rxbdr_wr(enetc_hw, i, ENETC_RBMR, rbmr);

enetc4_rx_queue_start() and _stop() perform the same read-modify-write
on ENETC_RBMR_EN from the control thread.  A link-status interrupt
arriving mid-sequence can lose either update, leaving a ring enabled
without congestion mode or disabled with it.


Patch 06

Warning: signed shift overflow in the register-dump version field.

  regs->version = hw->device_id << 16 | hw->revision_id;

device_id is uint16_t, so it promotes to int before the shift.  Both
ENETC4_DEV_ID (0xe101) and ENETC4_DEV_ID_VF (0xef00) have bit 15 set,
so the shift result is not representable in int -- undefined behaviour,
and UBSan will flag it.  The same line appears in both
enetc4_get_regs() and enetc4_vf_get_regs().

  regs->version = (uint32_t)hw->device_id << 16 | hw->revision_id;

Info: enetc4_txbdr_regs[] and enetc4_rxbdr_regs[] are static const
arrays defined in enetc.h, so every translation unit that includes the
header gets its own copy.  Moving them to one .c file with an extern
declaration, or making them macros, avoids the duplication.


Patch 05

Info: the fw_size == 0 early return reports the length of "0.0" rather
than of the real version string, since ip_mj and ip_mn are still zero
at that point.  Callers using the query-then-allocate idiom get a
buffer that is too small, though the second call returns the correct
size so it does self-correct.


Series-wide

Info: four new consecutive-blank-line pairs -- enetc.h:464,
enetc4_ethdev.c:36 and :164, enetc_rxtx.c:1144.

Info: the release note entry uses "TX PAUSE" and "RX congestion mode";
the commit subjects were corrected to Tx/Rx in this revision but the
note was not.

Info: parse_txq_prior() returns -1 for strdup failure but -ENOMEM for
the rte_zmalloc failure; parse_txq_wrr() returns -1 for both.  Worth
making consistent.

^ permalink raw reply	[flat|nested] 110+ messages in thread

* [PATCH v6 00/13] net/enetc: add new features for ENETC4 on i.MX95
  2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
                           ` (15 preceding siblings ...)
  2026-08-10 15:40         ` Stephen Hemminger
@ 2026-08-11  7:40         ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 01/13] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
                             ` (13 more replies)
  16 siblings, 14 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal

V6-changes:
 - fixed free() on memory allocated with rte_zmalloc().
 - fixed eventfd and vector-list leak on interrupt teardown
 - fixed Rx ring doubling still bypasses
 - fixed VSI-PSI transaction issued from the interrupt handler
 - fixed the free-and-reallocate of msg is unnecessary churn
 - fixed unsynchronized access to tx_pause_active and RBMR
 - fixed signed shift overflow in the register-dump version field
 - fixed the fw_size == 0 early return reports the length of "0.0"
 - unwanted blank lines removed.
 - parse_txq_prior() returns updated.

V5-changes:
 - Patch 1 Build fixes which includes:
    'prev_seg' undeclared fixed.
    redefinition of 'dev_rx_offloads_sup' fixed.
    Error: duplicate rx_enable declaration fixed.
   PF loses Scattered Rx and Multi-segment Tx
 - mbuf leak in enetc_xmit_pkts_lso() fixed.
 - fixed conversion on ENETC4_TXBD_FLAGS_F issue.
 - fixed link speed decode has no upper bound.
 - fixed mailbox ops added to the no-VSI ops table.
 - new devargs documented.

V4-changes:
 - fix doc build issue: WARNING: undefined label: pmd_build_and_test

v3-changes:
 - fix doc build issue.
 - fix compilation issue on fedore:43-gcc-minsize

V2-changes:
 - compilation fixes.

V1-changes:
This series adds new PMD features to the ENETC4 driver targeting the
NXP i.MX95 NETC IP.

The series covers:

 - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
 - TSO: TCP Segmentation Offload for the VF Tx path.
 - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
 - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
   8-bit to support speeds beyond 10G.
 - Firmware version: report the NETC IP version via fw_version_get.
 - Register dump: dump SI, port (PF) and BD ring registers.
 - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
 - Link-up interrupt: refresh the cached link speed on each VF link-up
   interrupt so that link_update returns the current speed immediately.
 - Stats reset: software snapshot/delta approach for VF counter reset.
 - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
   enabling interrupt-driven receive with l3fwd-power.
 - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
 - VF link status bitmask: switch VF link status to bitmask encoding
   to align with the PF and newer kernel driver conventions.
 - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
   negotiated on the wire; adds Flow control = Y to enetc4.ini.
 - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.

Gagandeep Singh (13):
  net/enetc: add keep-CRC Rx offload for ENETC4
  net/enetc: add TSO support for ENETC4 VF
  net/enetc: add RSC (hardware LRO) support for ENETC4
  net/enetc: extend PF-VF link speed field to 8 bits
  net/enetc: support firmware version get for VF
  net/enetc: support registers dump
  net/enetc: support ethtool ring parameters
  net/enetc: refresh link speed on VF link-up interrupt
  net/enetc: support stats reset for VF
  net/enetc4: add per-queue Rx interrupt support for VF
  net/enetc4: add SI-based port VLAN insertion and removal
  net/enetc4: update VF link status to bitmask encoding
  net/enetc4: enable Tx PAUSE via VF Rx congestion mode

 doc/guides/nics/enetc4.rst             |  88 +++
 doc/guides/nics/features/enetc4.ini    |   8 +
 doc/guides/rel_notes/release_26_11.rst |  20 +
 drivers/net/enetc/base/enetc4_hw.h     | 128 +++-
 drivers/net/enetc/base/enetc_hw.h      |   1 +
 drivers/net/enetc/enetc.h              | 135 ++++-
 drivers/net/enetc/enetc4_ethdev.c      | 340 ++++++++++-
 drivers/net/enetc/enetc4_vf.c          | 807 ++++++++++++++++++++++---
 drivers/net/enetc/enetc_rxtx.c         | 530 +++++++++++++++-
 9 files changed, 1919 insertions(+), 138 deletions(-)

-- 
2.25.1


^ permalink raw reply	[flat|nested] 110+ messages in thread

* [PATCH v6 01/13] net/enetc: add keep-CRC Rx offload for ENETC4
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 02/13] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
                             ` (12 subsequent siblings)
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The legacy ENETC (LS1028A) driver already supports the
RTE_ETH_RX_OFFLOAD_KEEP_CRC offload, but ENETC4 (i.MX95 NETC) did not.

Add KEEP_CRC support for ENETC4 (both PF and VF):

- Advertise RTE_ETH_RX_OFFLOAD_KEEP_CRC in the supported Rx offloads
  for the ENETC4 PF and VF. The VF reuses the PF Rx queue setup, so the
  HW configuration and datapath handling apply to both.
- Configure the per-ring RBaMR[CRC] bit in the Rx queue setup so that
  HW preserves the Ethernet FCS in the receive buffer when the offload
  is requested (0 = FCS removed, 1 = FCS preserved).
- Follow the DPDK convention used by the legacy ENETC and ixgbe drivers:
  set crc_len to RTE_ETHER_CRC_LEN when KEEP_CRC is enabled and have the
  datapath subtract crc_len from pkt_len/data_len. ENETC and ENETC4 share
  the same datapath (enetc_rxtx.c), so the semantics stay consistent.
- Handle the scatter-gather boundary case where the 4-byte FCS straddles
  the last two segments: drop the trailing segment, decrement nb_segs and
  trim the carry-over from its predecessor.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  6 ++++
 drivers/net/enetc/base/enetc4_hw.h     |  3 ++
 drivers/net/enetc/enetc4_ethdev.c      | 45 ++++++++++++++++----------
 drivers/net/enetc/enetc4_vf.c          |  1 +
 drivers/net/enetc/enetc_rxtx.c         | 39 +++++++++++++++++++---
 6 files changed, 74 insertions(+), 21 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 698140e30b..91b18d979e 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ Packet type parsing  = Y
 Basic stats          = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
+CRC offload          = Y
 Queue start/stop     = Y
 Scattered Rx         = Y
 Linux                = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..3678dd6894 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -56,6 +56,12 @@ New Features
      =======================================================
 
 
+* **Updated NXP ENETC4 PMD.**
+
+  Updated the NXP ENETC4 poll mode driver for i.MX95:
+
+  * Added KEEP_CRC Rx offload support for the ENETC4 PMD to preserve the Ethernet FCS.
+
 Removed Items
 -------------
 
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 9685e7e1e5..c81ac00ec6 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -68,6 +68,9 @@ struct enetc_msg_swbd {
 #define PM_CMD_CFG_TX_EN		BIT(0)
 #define PM_CMD_CFG_RX_EN		BIT(1)
 
+/* RBaMR[CRC]: 0 = FCS removed, 1 = FCS preserved (KEEP_CRC) */
+#define ENETC4_RBMR_CRC			BIT(8)
+
 /* i.MX95 supports jumbo frame, but it is recommended to set the max frame
  * size to 2000 bytes.
  */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 2ddd63dafb..91e2cdb129 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -11,6 +11,21 @@
 #include "enetc_logs.h"
 #include "enetc.h"
 
+/* Supported Rx offloads */
+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_SCATTER |
+	RTE_ETH_RX_OFFLOAD_KEEP_CRC;
+
+/* Supported Tx offloads */
+static uint64_t dev_tx_offloads_sup =
+	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
+	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
+
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
 
@@ -93,20 +108,6 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 	return 0;
 }
 
-/* Supported Rx offloads */
-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_SCATTER;
-
-/* Supported Tx offloads */
-static uint64_t dev_tx_offloads_sup =
-	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
-	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
-
 static int
 enetc4_dev_start(struct rte_eth_dev *dev)
 {
@@ -536,6 +537,7 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			ENETC_DEV_PRIVATE(data->dev_private);
 	uint64_t rx_offloads = data->dev_conf.rxmode.offloads;
+	bool keep_crc;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_rx_desc > MAX_BD_COUNT)
@@ -549,6 +551,9 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	rx_ring->index = rx_queue_id;
+	keep_crc = !!(rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC);
+	rx_ring->crc_len = (uint8_t)(keep_crc ? RTE_ETHER_CRC_LEN : 0);
+
 	err = enetc4_alloc_rxbdr(rx_ring, nb_rx_desc);
 	if (err)
 		goto fail;
@@ -562,19 +567,25 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	data->rx_queues[rx_queue_id] = rx_ring;
 	rx_ring->rx_deferred_start = rx_conf->rx_deferred_start;
 
+	if (keep_crc)
+		rx_enable |= ENETC4_RBMR_CRC;
+	else
+		rx_enable &= ~ENETC4_RBMR_CRC;
+
 	if (!rx_conf->rx_deferred_start) {
 		/* enable ring */
+		rx_enable |= ENETC_RBMR_EN;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
-			       ENETC_RBMR_EN);
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STARTED;
 	} else {
+		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STOPPED;
 	}
 
-	rx_ring->crc_len = (uint8_t)((rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC) ?
-				     RTE_ETHER_CRC_LEN : 0);
 	return 0;
 fail:
 	rte_free(rx_ring);
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index ef5f1e6d66..83a1e4931f 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -52,6 +52,7 @@ static uint64_t dev_rx_offloads_sup =
 	RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_VLAN_FILTER |
+	RTE_ETH_RX_OFFLOAD_KEEP_CRC |
 	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 8678bafece..d5855e69a3 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -530,6 +530,28 @@ enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
 	return rx_frm_cnt;
 }
 
+/*
+ * Trim the Ethernet FCS from a received cluster when HW CRC strip is
+ * disabled. pkt_len is reduced by crc_len. If the FCS straddles the last
+ * two segments (last seg holds fewer bytes than crc_len), drop the trailing
+ * segment and trim the carry-over from its predecessor. prev_seg is the
+ * segment preceding last_seg in the chain (the caller already tracks it).
+ */
+static inline void
+enetc_rx_crc_trim(struct rte_mbuf *first_seg, struct rte_mbuf *prev_seg,
+		  struct rte_mbuf *last_seg, uint16_t crc_len)
+{
+	first_seg->pkt_len -= crc_len;
+	if (likely(last_seg->data_len > crc_len)) {
+		last_seg->data_len -= crc_len;
+	} else if (prev_seg != NULL) {
+		first_seg->nb_segs--;
+		prev_seg->data_len -= crc_len - last_seg->data_len;
+		prev_seg->next = NULL;
+		rte_pktmbuf_free_seg(last_seg);
+	}
+}
+
 static int
 enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		    struct rte_mbuf **rx_pkts,
@@ -539,7 +561,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL, *prev_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -572,6 +594,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg, rxbd_temp.r.parse_summary);
 			first_seg->hash.rss = rxbd_temp.r.rss_hash;
@@ -579,6 +602,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -590,7 +614,9 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
@@ -760,7 +786,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL, *prev_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -815,6 +841,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg,
 					   rxbd->r.parse_summary);
@@ -823,6 +850,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -838,7 +866,10 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
+
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 02/13] net/enetc: add TSO support for ENETC4 VF
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 01/13] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 03/13] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
                             ` (11 subsequent siblings)
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add TCP and UDP segmentation offload (TSO) for the ENETC4 virtual
function. The offload is advertised through the VF Tx capabilities and
is enabled on a per-queue basis when an application requests the TSO
offload flags during Tx queue setup.

A dedicated Tx burst, enetc_xmit_pkts_lso(), builds the large-send
descriptor chain: a standard header BD, a 16B extension BD carrying the
segment size and frame length extension, and one BD per payload buffer
with the final-frame flag on the last BD. To make room for the extra
extension BD, an LSO-enabled Tx ring is allocated with twice the number
of descriptors. The existing non-TSO Tx paths are left unchanged.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   2 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/base/enetc4_hw.h     |  34 +++-
 drivers/net/enetc/enetc.h              |   3 +
 drivers/net/enetc/enetc4_ethdev.c      |  49 ++++-
 drivers/net/enetc/enetc4_vf.c          |   2 +
 drivers/net/enetc/enetc_rxtx.c         | 257 +++++++++++++++++++++++++
 8 files changed, 344 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index f0dd039f6e..4d27e4048e 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -53,6 +53,8 @@ Key functionality includes:
 - Receive processing: Upon packet reception, the BD Ring status bit is set,
   facilitating packet processing.
 - 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.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 91b18d979e..8ec1e8d00f 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
+TSO                  = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 3678dd6894..6a348ab4e0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -61,6 +61,7 @@ New Features
   Updated the NXP ENETC4 poll mode driver for i.MX95:
 
   * 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.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index c81ac00ec6..6bc462a93e 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2024 NXP
+ * Copyright 2024-2026 NXP
  *
  * This header file defines the register offsets and bit fields
  * of ENETC4 PF and VFs.
@@ -24,7 +24,12 @@ struct enetc_msg_swbd {
 
 /* enetc4 txbd flags */
 #define ENETC4_TXBD_FLAGS_L4CS		BIT(0)
+/* Request LSO (Large Send Offload) segmentation for this frame */
+#define ENETC4_TXBD_FLAGS_LSO		BIT(1)
+/* L4 checksum insertion (also used for checksum update on LSO) */
 #define ENETC4_TXBD_FLAGS_L_TX_CKSUM	BIT(3)
+/* Extended descriptor: the next 16B ring entry is an extension BD */
+#define ENETC4_TXBD_FLAGS_EXT		BIT(6)
 #define ENETC4_TXBD_FLAGS_F		BIT(7)
 /* L4 type */
 #define ENETC4_TXBD_L4T_UDP		BIT(0)
@@ -34,6 +39,33 @@ struct enetc_msg_swbd {
 /* IPv4 checksum */
 #define ENETC4_TXBD_IPCS		1
 
+/*
+ * Extension Transmit Buffer Descriptor (16B). When the standard BD has the
+ * extended flag set, this occupies the next ring entry and carries the extra
+ * fields required by LSO.
+ */
+struct enetc_tx_bd_ext {
+	uint32_t timestamp;	/* PTP timestamp, unused for LSO */
+	uint32_t vlan;		/* VLAN insert, unused for LSO */
+	uint32_t lso;		/* LSO_MAX_SEG_SIZE and FRM_LEN_EXT */
+	uint32_t flags;		/* extension flags */
+};
+
+/* LSO_MAX_SEG_SIZE occupies bits 13-0 of the LSO word */
+#define ENETC4_TXBD_EXT_LSO_SEG_MASK	0x3fff
+#define ENETC4_TXBD_EXT_LSO_SEG(mss) \
+		((uint32_t)((mss) & ENETC4_TXBD_EXT_LSO_SEG_MASK))
+/* FRM_LEN_EXT occupies bits 19-16 of the LSO word (top 4 bits of data len) */
+#define ENETC4_TXBD_EXT_FRM_LEN_EXT(x) \
+		(((uint32_t)((x) & 0xf)) << 16)
+/* Final flag in the extension flags word (bit 127 -> local bit 31) */
+#define ENETC4_TXBD_EXT_FLAGS_F		BIT(31)
+
+/* NETC does not create LSO frames larger than this many bytes */
+#define ENETC4_LSO_MAX_FRAME		9600
+/* Maximum LSO data unit (payload to be segmented) supported by HW: 256KB */
+#define ENETC4_LSO_MAX_DATA_UNIT	(256 * 1024)
+
 /***************************ENETC port registers**************************/
 #define ENETC4_PMR		0x10
 #define ENETC4_PMR_EN		(BIT(16) | BIT(17) | BIT(18))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index d3a8b8e34a..6cd031f269 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -99,6 +99,7 @@ struct enetc_bdr {
 	uint64_t ierrors;
 	uint8_t rx_deferred_start;
 	uint8_t tx_deferred_start;
+	uint8_t lso_enable;
 };
 
 struct enetc_eth_hw {
@@ -312,6 +313,8 @@ uint16_t enetc_xmit_pkts(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
 uint16_t enetc_xmit_pkts_nc(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
+uint16_t enetc_xmit_pkts_lso(void *txq, struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts);
 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,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 91e2cdb129..24c454fe28 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -24,7 +24,9 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
+	RTE_ETH_TX_OFFLOAD_MULTI_SEGS |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
@@ -309,22 +311,37 @@ static int
 enetc4_alloc_txbdr(struct enetc_bdr *txr, uint16_t nb_desc)
 {
 	int size;
+	uint32_t ring_desc;
 
-	size = nb_desc * sizeof(struct enetc_swbd);
+	/*
+	 * On LSO-enabled rings each frame uses an extra 16B extension BD
+	 * (logical 32B descriptor) plus the payload BDs.  Size the ring with
+	 * twice the requested entries so the effective frame capacity is
+	 * preserved.  Non-LSO rings are sized exactly as requested.
+	 */
+	ring_desc = txr->lso_enable ? (uint32_t)nb_desc * 2 : (uint32_t)nb_desc;
+	if (ring_desc > MAX_BD_COUNT) {
+		ENETC_PMD_ERR("LSO ring_desc %u > MAX_BD_COUNT %u; "
+			      "reduce nb_desc to <= %u with LSO enabled",
+			      ring_desc, MAX_BD_COUNT, MAX_BD_COUNT / 2);
+		return -EINVAL;
+	}
+
+	size = ring_desc * sizeof(struct enetc_swbd);
 	/* Zero q_swbd so buffer_addr is NULL for all uninitialized slots. */
 	txr->q_swbd = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->q_swbd == NULL)
 		return -ENOMEM;
 
 	/* Allocate the TX BD ring: each BD is struct enetc_tx_bd (16 bytes). */
-	size = nb_desc * sizeof(struct enetc_tx_bd);
+	size = ring_desc * sizeof(struct enetc_tx_bd);
 	txr->bd_base = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->bd_base == NULL) {
 		rte_free(txr->q_swbd);
 		txr->q_swbd = NULL;
 		return -ENOMEM;
 	}
-	txr->bd_count = nb_desc;
+	txr->bd_count = ring_desc;
 	txr->next_to_clean = 0;
 	txr->next_to_use = 0;
 
@@ -376,6 +393,7 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	struct rte_eth_dev_data *data = dev->data;
 	struct enetc_eth_adapter *priv =
 			ENETC_DEV_PRIVATE(data->dev_private);
+	uint64_t tx_offloads;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_desc > MAX_BD_COUNT)
@@ -389,6 +407,29 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	tx_ring->index = queue_idx;
+	/*
+	 * LSO (TCP/UDP segmentation) is a port-level offload. The Tx burst is
+	 * a single device-level function pointer, so it must be consistent for
+	 * all queues; likewise every ring must be sized the same way. Decide
+	 * from the port offloads only (not the per-queue tx_conf) so that all
+	 * queues either use the LSO burst with a doubled ring, or none do.
+	 * The LSO burst also handles non-TSO packets, so it is safe on queues
+	 * that never carry TSO traffic. LSO needs HW FCS insertion, so it is
+	 * incompatible with KEEP_CRC on Rx.
+	 */
+	tx_offloads = data->dev_conf.txmode.offloads;
+	if (tx_offloads & (RTE_ETH_TX_OFFLOAD_TCP_TSO |
+			   RTE_ETH_TX_OFFLOAD_UDP_TSO)) {
+		if (data->dev_conf.rxmode.offloads &
+		    RTE_ETH_RX_OFFLOAD_KEEP_CRC) {
+			ENETC_PMD_ERR("LSO (TSO) is incompatible with KEEP_CRC");
+			rte_free(tx_ring);
+			return -EINVAL;
+		}
+		tx_ring->lso_enable = 1;
+		dev->tx_pkt_burst = &enetc_xmit_pkts_lso;
+	}
+
 	err = enetc4_alloc_txbdr(tx_ring, nb_desc);
 	if (err)
 		goto fail;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 83a1e4931f..3083a56335 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -60,6 +60,8 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO |
 	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
 
 static void
diff --git a/drivers/net/enetc/enetc_rxtx.c b/drivers/net/enetc/enetc_rxtx.c
index d5855e69a3..0bb5a86dd8 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -221,6 +221,263 @@ enetc_xmit_pkts_nc(void *tx_queue,
 	return start;
 }
 
+/*
+ * LSO (Large Send Offload) Tx burst.
+ *
+ * Dedicated burst used on Tx rings with LSO enabled (txr->lso_enable). It
+ * follows the same non-cache-coherent discipline as enetc_xmit_pkts_cacheable:
+ * payload lines are flushed with dcbf before handing the frame to HW and the
+ * written BD cache lines are flushed at the end of the batch.
+ *
+ * A TSO/USO frame uses an extended Tx descriptor: the standard BD points at
+ * the L2/L3/L4 header template (BUF_LEN = header length, FRM_LEN = payload
+ * length), followed by an extension BD in the next ring slot holding the
+ * segment size, then one BD per payload buffer with the F flag on the last.
+ * Non-TSO packets fall through to the normal encoding so mixed traffic works.
+ */
+uint16_t
+enetc_xmit_pkts_lso(void *tx_queue,
+		struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts)
+{
+	int i, start, bds_to_use;
+	struct enetc_tx_bd *txbd = NULL;
+	struct enetc_tx_bd_ext *txbd_ext;
+	struct enetc_bdr *tx_ring = (struct enetc_bdr *)tx_queue;
+	unsigned int j;
+	uint8_t *data;
+	struct rte_mbuf *seg;
+	uint16_t seg_len, segs_per_pkt;
+	bool is_first_seg;
+	int first_bd_idx, bd_count;
+
+	i = tx_ring->next_to_use;
+	bds_to_use = enetc_bd_unused(tx_ring);
+	bd_count = tx_ring->bd_count;
+	start = 0;
+
+	first_bd_idx = i;
+
+	while (start < nb_pkts) {
+		seg = tx_pkts[start];
+		segs_per_pkt = seg->nb_segs;
+
+		if (seg->ol_flags &
+		    (RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_UDP_SEG)) {
+			bool is_udp_seg =
+				!!(seg->ol_flags & RTE_MBUF_F_TX_UDP_SEG);
+			uint32_t hdr_len = seg->l2_len + seg->l3_len +
+					   seg->l4_len;
+			uint32_t data_unit;
+			uint16_t first_payload;
+			struct rte_mbuf *dseg;
+			int bds_needed;
+
+			/* Header BD + extension BD + one BD per segment. */
+			bds_needed = 2 + segs_per_pkt;
+			if (bds_to_use < bds_needed)
+				break;
+
+			/*
+			 * Skip frames with nothing to segment, or whose
+			 * headers are not fully contained in the first
+			 * segment. The first BD carries the header template
+			 * and first_payload is computed as (first segment
+			 * data_len - hdr_len), so the L2/L3/L4 headers must
+			 * reside contiguously in the first mbuf; otherwise the
+			 * unsigned subtraction would underflow.
+			 */
+			if (unlikely(hdr_len >= rte_pktmbuf_pkt_len(seg) ||
+				     hdr_len > rte_pktmbuf_data_len(seg))) {
+				rte_pktmbuf_free(seg);
+				start++;
+				continue;
+			}
+			data_unit = rte_pktmbuf_pkt_len(seg) - hdr_len;
+
+			/*
+			 * Skip frames that violate HW LSO limits: a zero
+			 * segment size, a payload larger than the HW data
+			 * unit, or a per-segment frame (headers + segment)
+			 * bigger than the maximum LSO frame size.
+			 */
+			if (unlikely(seg->tso_segsz == 0 ||
+				     data_unit > ENETC4_LSO_MAX_DATA_UNIT ||
+				     hdr_len + seg->tso_segsz >
+					     ENETC4_LSO_MAX_FRAME)) {
+				rte_pktmbuf_free(seg);
+				start++;
+				continue;
+			}
+
+			/* Standard (first) BD: header template only. */
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			seg_len = rte_pktmbuf_data_len(seg);
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			memset(txbd, 0, sizeof(*txbd));
+			tx_ring->q_swbd[i].buffer_addr = seg;
+
+			/* FRM_LEN low 16 bits = payload length, BUF_LEN = hdr. */
+			txbd->frm_len = rte_cpu_to_le_16(data_unit & 0xffff);
+			txbd->buf_len = rte_cpu_to_le_16((uint16_t)hdr_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+
+			/* Checksum control for the per-segment L3/L4 rewrite. */
+			txbd->l3_start = seg->l2_len;
+			txbd->l3_hdr_size = seg->l3_len / 4;
+			if (seg->ol_flags & RTE_MBUF_F_TX_IPV6) {
+				txbd->l3t = 1;
+			} else {
+				txbd->l3t = ENETC4_TXBD_L3T;
+				txbd->ipcs = ENETC4_TXBD_IPCS;
+			}
+			txbd->l4t = is_udp_seg ? ENETC4_TXBD_L4T_UDP :
+						 ENETC4_TXBD_L4T_TCP;
+			txbd->flags = ENETC4_TXBD_FLAGS_L_TX_CKSUM |
+				      ENETC4_TXBD_FLAGS_L4CS |
+				      ENETC4_TXBD_FLAGS_LSO |
+				      ENETC4_TXBD_FLAGS_EXT;
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/* Extension BD occupies the next ring slot. */
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			txbd_ext = (struct enetc_tx_bd_ext *)
+				   ENETC_TXBD(*tx_ring, i);
+			memset(txbd_ext, 0, sizeof(*txbd_ext));
+			txbd_ext->lso = rte_cpu_to_le_32(ENETC4_TXBD_EXT_LSO_SEG(seg->tso_segsz) |
+					ENETC4_TXBD_EXT_FRM_LEN_EXT(data_unit >> 16));
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/*
+			 * Payload BDs. The first segment's payload starts after
+			 * the header template; remaining segments are pure
+			 * payload.
+			 */
+			first_payload = (uint16_t)(seg_len - hdr_len);
+			dseg = seg;
+			is_first_seg = true;
+			while (dseg) {
+				uint16_t dlen;
+				uint64_t daddr;
+
+				if (is_first_seg) {
+					dlen = first_payload;
+					daddr = rte_mbuf_data_iova(dseg) +
+						hdr_len;
+					is_first_seg = false;
+				} else {
+					dlen = rte_pktmbuf_data_len(dseg);
+					data = rte_pktmbuf_mtod(dseg, void *);
+					for (j = 0; j < dlen;
+					     j += RTE_CACHE_LINE_SIZE)
+						dcbf(data + j);
+					dcbf(data + (dlen - 1));
+					daddr = rte_mbuf_data_iova(dseg);
+				}
+
+				if (dlen == 0) {
+					dseg = dseg->next;
+					continue;
+				}
+
+				tx_ring->q_swbd[i].buffer_addr = NULL;
+				txbd = ENETC_TXBD(*tx_ring, i);
+				memset(txbd, 0, sizeof(*txbd));
+				txbd->buf_len = rte_cpu_to_le_16(dlen);
+				txbd->addr = rte_cpu_to_le_64(daddr);
+				i++;
+				bds_to_use--;
+				if (unlikely(i == bd_count))
+					i = 0;
+				dseg = dseg->next;
+			}
+
+			/* Mark the last written BD as frame-last. */
+			txbd->flags |= ENETC4_TXBD_FLAGS_F;
+			start++;
+			continue;
+		}
+
+		/* Non-TSO packet: standard single/multi-seg encoding. */
+		if (bds_to_use < segs_per_pkt)
+			break;
+
+		is_first_seg = true;
+		while (seg) {
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			seg_len = rte_pktmbuf_data_len(seg);
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			txbd->flags = 0;
+			if (is_first_seg) {
+				tx_ring->q_swbd[i].buffer_addr = seg;
+				txbd->frm_len = rte_pktmbuf_pkt_len(seg);
+				if (seg->ol_flags & ENETC4_TX_CKSUM_OFFLOAD_MASK)
+					enetc4_tx_offload_checksum(seg, txbd);
+				is_first_seg = false;
+			}
+
+			txbd->buf_len = rte_cpu_to_le_16(seg_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+			seg = seg->next;
+			i++;
+			bds_to_use--;
+
+			if (unlikely(i == bd_count))
+				i = 0;
+		}
+
+		txbd->flags |= ENETC4_TXBD_FLAGS_F;
+		start++;
+	}
+
+	/*
+	 * Flush TX BDs to PoC so HW (non-cache-coherent i.MX95) can read the
+	 * descriptors from memory.  Same discipline as enetc_xmit_pkts_cacheable:
+	 * walk from the cache-line-aligned start of first_bd_idx to just past
+	 * the last written BD, one dcbf per 64-byte (4-BD) line.
+	 */
+	if (likely(start > 0)) {
+		int n = first_bd_idx & ~ENETC_BD_PER_CL_MASK;
+		int written = (i - n + bd_count) % bd_count;
+
+		if (written == 0)
+			written = bd_count;
+		written = (written + ENETC_BD_PER_CL_MASK) &
+			  ~ENETC_BD_PER_CL_MASK;
+
+		while (written > 0) {
+			dcbf((void *)ENETC_TXBD(*tx_ring, n));
+			n = (n + ENETC_BD_PER_CL) % bd_count;
+			written -= ENETC_BD_PER_CL;
+		}
+	}
+
+	enetc_clean_tx_ring(tx_ring);
+	tx_ring->next_to_use = i;
+	enetc_wr_reg(tx_ring->tcir, i);
+
+	return start;
+}
+
 int
 enetc_refill_rx_ring(struct enetc_bdr *rx_ring, const int buff_cnt)
 {
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 03/13] net/enetc: add RSC (hardware LRO) support for ENETC4
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 01/13] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 02/13] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 04/13] net/enetc: extend PF-VF link speed field to 8 bits Gagandeep Singh
                             ` (10 subsequent siblings)
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

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 <g.singh@nxp.com>
---
 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     |  75 ++++++++
 drivers/net/enetc/enetc.h              |   5 +
 drivers/net/enetc/enetc4_ethdev.c      | 112 +++++++++++-
 drivers/net/enetc/enetc4_vf.c          |   1 +
 drivers/net/enetc/enetc_rxtx.c         | 234 +++++++++++++++++++++++++
 8 files changed, 427 insertions(+), 6 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 6bc462a93e..d42c04df4b 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -102,6 +102,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 6cd031f269..145ebbbb00 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -100,6 +100,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 {
@@ -319,12 +321,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 24c454fe28..925da3f68e 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -17,7 +17,8 @@ static uint64_t dev_rx_offloads_sup =
 	RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_SCATTER |
-	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 =
@@ -302,6 +303,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;
@@ -512,22 +515,35 @@ 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;
+	if (ring_desc > MAX_BD_COUNT) {
+		ENETC_PMD_ERR("RSC ring_desc %u > MAX_BD_COUNT %u; "
+			      "reduce nb_desc to <= %u with RSC enabled",
+			      ring_desc, MAX_BD_COUNT, MAX_BD_COUNT / 2);
+		return -EINVAL;
+	}
 
-	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;
@@ -550,13 +566,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);
@@ -578,7 +611,9 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			ENETC_DEV_PRIVATE(data->dev_private);
 	uint64_t rx_offloads = data->dev_conf.rxmode.offloads;
+	uint32_t rsc_size;
 	bool keep_crc;
+	bool rsc_enable;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_rx_desc > MAX_BD_COUNT)
@@ -595,6 +630,28 @@ 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;
+		}
+		if (adapter->hw.nc_mode) {
+			ENETC_PMD_ERR("RSC (LRO) is incompatible with nc=1 (non-cacheable mode)");
+			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;
@@ -613,6 +670,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;
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 0bb5a86dd8..8d3008843c 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -907,6 +907,240 @@ 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)
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 04/13] net/enetc: extend PF-VF link speed field to 8 bits
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
                             ` (2 preceding siblings ...)
  2026-08-11  7:40           ` [PATCH v6 03/13] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 05/13] net/enetc: support firmware version get for VF Gagandeep Singh
                             ` (9 subsequent siblings)
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF message is only 16-bit wide, where the upper 8-bit is the
class_id and the lower 8-bit carries the message content. For the
link speed message, the lower 4-bit was previously occupied by the cookie
field, leaving only 4-bit for the speed code, which allows at most
15 distinct speed values.

As the NETC IP evolves and supports more and more link speeds, 15 speed
values are clearly insufficient. Since the cookie field is designed
for non-blocking messages and has no meaningful use in link speed
messages, remove it and expand the speed code field from 4-bit to 8-bit,
allowing up to 255 speed values (ENETC_SPEED_MAX = 0xff).

Instead of enumerating every speed value greater than 5Gbps
explicitly, introduce a formula-based approach:

	speed_code = (link_speed - 5000) / 1000 + ENETC_SPEED_5000

This removes the explicit enum entries for 10G, 25G, 50G and 100G,
and replaces the individual switch-case branches with a generic
implementation to get the speed, making it easy to support any
future high speed without modifying the enum or the switch statement.

For backward compatibility with a PF running an older kernel (before
6.18.37) that still uses the legacy 4-bit speed code / 4-bit cookie
message layout, add a "vf_link_legacy" devarg. When set to 1, the VF
extracts the message result from the upper 4 bits of the lower byte
and decodes speeds greater than 5Gbps using the legacy fixed class
codes (10G/25G/50G/100G). The default (0) uses the new 8-bit layout.

	Usage: -a <pci_addr>,vf_link_legacy=1

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |  11 ++
 doc/guides/rel_notes/release_26_11.rst |   3 +
 drivers/net/enetc/enetc.h              |  32 +++-
 drivers/net/enetc/enetc4_vf.c          | 202 +++++++++++++++++++++----
 4 files changed, 215 insertions(+), 33 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index e7f4348603..935cf90e6b 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -133,6 +133,17 @@ VF-specific devargs
 
     dpdk-testpmd -a 0000:00:01.0,enetc4_vsi_delay=10 -- -i
 
+``vf_link_legacy``
+  Select the legacy 4-bit PF-to-VF link speed code layout.
+  Set to ``1`` when the host PF is running a kernel older than v6.18.37.
+  Kernels before that release encode link speed in only 4 bits; without this
+  flag the driver interprets those codes as 8-bit values and reports incorrect
+  link speeds.
+
+  Usage example::
+
+    dpdk-testpmd -a 0000:00:01.0,vf_link_legacy=1 -- -i
+
 PF/Common devargs
 ~~~~~~~~~~~~~~~~~
 
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 0dd08e0259..ee4f455c67 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -63,6 +63,9 @@ 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.
+  * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
+    Users running a PF kernel older than 6.18.37 must pass ``vf_link_legacy=1``
+    as a device argument, otherwise link speed reporting will be incorrect.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 145ebbbb00..d6ae54a132 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -119,6 +119,10 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
+	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
+	 */
+	uint8_t vf_link_legacy;
 };
 
 /*
@@ -210,11 +214,29 @@ enum speed {
 	ENETC_SPEED_1000 = 0x5,
 	ENETC_SPEED_2500 = 0x6,
 	ENETC_SPEED_5000 = 0x7,
-	ENETC_SPEED_10G = 0x8,
-	ENETC_SPEED_25G = 0x9,
-	ENETC_SPEED_50G = 0xA,
-	ENETC_SPEED_100G = 0xB,
-	ENETC_SPEED_NOT_SUPPORTED = 0xF
+	/* Base speed class code used by the >5Gbps formula below */
+	/* Do not add enumeration values for any speed greater than
+	 * 5Gbps. For any speed greater than 5Gbps, its speed class
+	 * code should follow the formula below.
+	 *
+	 * SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+	 *
+	 * The unit of link_speed should be Mbps, the max SPEED
+	 * should <= ENETC_SPEED_MAX.
+	 */
+	ENETC_SPEED_MAX = 0xff,
+};
+
+/* Legacy speed class codes for backward compatibility with an older kernel PF
+ * (before 6.18.37) that encoded a 4-bit speed code and 4-bit cookie in the
+ * message's lower byte. Used only when the vf_link_legacy devarg is set.
+ */
+enum speed_legacy {
+	ENETC_SPEED_LEGACY_10G = 0x8,
+	ENETC_SPEED_LEGACY_25G = 0x9,
+	ENETC_SPEED_LEGACY_50G = 0xA,
+	ENETC_SPEED_LEGACY_100G = 0xB,
+	ENETC_SPEED_LEGACY_NOT_SUPPORTED = 0xF
 };
 
 /* PSI-VSI command header format */
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index be79a18a39..9c6070b6d4 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -43,6 +43,12 @@ enetc4_vf_get_devarg_nc(struct rte_eth_dev *dev)
 #define ENETC_BYTE_SIZE			8
 #define ENETC_MSB_BIT			0x8000
 
+/* Backward-compat devarg for a PF running a kernel before 6.18.37, which uses
+ * the legacy PF-to-VF link message layout (4-bit speed code + 4-bit cookie).
+ * Usage: -a <pci_addr>,vf_link_legacy=1
+ */
+#define ENETC_VF_LINK_LEGACY		"vf_link_legacy"
+
 uint16_t enetc_crc_table[ENETC_CRC_TABLE_SIZE];
 bool enetc_crc_gen;
 
@@ -101,6 +107,59 @@ enetc_crc_calc(uint16_t crc, const uint8_t *buffer, size_t len)
 	return crc;
 }
 
+static int
+parse_vf_link_legacy(const char *key __rte_unused, const char *value,
+		     void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *endptr;
+	unsigned long val;
+
+	if (!value || *value == '\0') {
+		ENETC_PMD_WARN("Empty value for devarg %s, ignoring",
+			       ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	errno = 0;
+	val = strtoul(value, &endptr, 0);
+	if (errno != 0 || *endptr != '\0' || val > 1) {
+		ENETC_PMD_WARN("Invalid value '%s' for devarg %s, expected 0 or 1",
+			       value, ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	hw->vf_link_legacy = (uint8_t)val;
+
+	return 0;
+}
+
+static void
+enetc4_vf_get_devargs(struct rte_eth_dev *dev)
+{
+	struct rte_devargs *devargs;
+	struct rte_kvargs *kvlist;
+
+	devargs = dev->device->devargs;
+	if (!devargs)
+		return;
+
+	kvlist = rte_kvargs_parse(devargs->args, NULL);
+	if (!kvlist)
+		return;
+
+	if (rte_kvargs_count(kvlist, ENETC_VF_LINK_LEGACY)) {
+		if (rte_kvargs_process(kvlist, ENETC_VF_LINK_LEGACY,
+				       parse_vf_link_legacy, (void *)dev) < 0)
+			ENETC_PMD_WARN("Failed to parse devarg %s",
+				       ENETC_VF_LINK_LEGACY);
+	}
+
+	rte_kvargs_free(kvlist);
+}
+
 static int
 enetc4_vf_dev_infos_get(struct rte_eth_dev *dev,
 			struct rte_eth_dev_info *dev_info)
@@ -212,6 +271,7 @@ enetc4_msg_vsi_write_msg(struct enetc_hw *hw,
 static void
 enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgsr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -221,8 +281,15 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgsr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -231,6 +298,7 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 static void
 enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgrr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -240,8 +308,15 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgrr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -731,6 +806,8 @@ enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 static int
 enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused)
 {
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
 	struct enetc_psi_reply_msg *reply_msg;
 	struct rte_eth_link link;
 	int err;
@@ -809,28 +886,91 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 			link.link_speed = RTE_ETH_SPEED_NUM_5G;
 			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 			break;
-		case ENETC_SPEED_10G:
-			link.link_speed = RTE_ETH_SPEED_NUM_10G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_25G:
-			link.link_speed = RTE_ETH_SPEED_NUM_25G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_50G:
-			link.link_speed = RTE_ETH_SPEED_NUM_50G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100G:
-			link.link_speed = RTE_ETH_SPEED_NUM_100G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_NOT_SUPPORTED:
-			ENETC_PMD_DEBUG("Speed not supported");
-			link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-			break;
 		default:
-			ENETC_PMD_ERR("Unknown speed status");
+			if (hw->vf_link_legacy) {
+				/* Legacy PF-to-VF message layout (older kernel
+				 * PF): speeds greater than 5Gbps are encoded
+				 * with fixed 4-bit class codes rather than the
+				 * formula below.
+				 */
+				switch (reply_msg->status) {
+				case ENETC_SPEED_LEGACY_10G:
+					link.link_speed = RTE_ETH_SPEED_NUM_10G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_25G:
+					link.link_speed = RTE_ETH_SPEED_NUM_25G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_50G:
+					link.link_speed = RTE_ETH_SPEED_NUM_50G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_100G:
+					link.link_speed = RTE_ETH_SPEED_NUM_100G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+					ENETC_PMD_DEBUG("Speed not supported");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				default:
+					ENETC_PMD_ERR("Unknown speed status");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				}
+				break;
+			}
+
+			/* Any status reaching here is greater than
+			 * ENETC_SPEED_5000, as all values from 0x0 to
+			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
+			 * greater than 5Gbps are not enumerated and follow the
+			 * formula:
+			 *
+			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+			 *
+			 * where link_speed is in Mbps. Reverse it here to get the
+			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
+			 *
+			 * Validate the computed value against the set of speeds
+			 * that the NETC IP is known to support (> 5Gbps).
+			 * An unrecognised code yields UNKNOWN rather than a
+			 * fabricated speed.
+			 */
+			switch ((reply_msg->status - ENETC_SPEED_5000)
+				* 1000 + 5000) {
+			case RTE_ETH_SPEED_NUM_10G:
+				link.link_speed = RTE_ETH_SPEED_NUM_10G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_25G:
+				link.link_speed = RTE_ETH_SPEED_NUM_25G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_40G:
+				link.link_speed = RTE_ETH_SPEED_NUM_40G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_50G:
+				link.link_speed = RTE_ETH_SPEED_NUM_50G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_100G:
+				link.link_speed = RTE_ETH_SPEED_NUM_100G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_200G:
+				link.link_speed = RTE_ETH_SPEED_NUM_200G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			default:
+				ENETC_PMD_WARN("Unrecognized speed code 0x%x, "
+					       "reporting unknown",
+					       reply_msg->status);
+				link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			}
 			break;
 		}
 	} else {
@@ -839,10 +979,9 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	link.link_autoneg = 1;
-
 	rte_eth_linkstatus_set(dev, &link);
-
 	rte_free(reply_msg);
+
 	return 0;
 }
 
@@ -1421,6 +1560,12 @@ enetc4_vf_dev_init(struct rte_eth_dev *eth_dev)
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_populate();
 
+	/* Parse VF specific devargs (e.g. vf_link_legacy) before the first
+	 * link update so that PF-to-VF link messages are interpreted using
+	 * the correct (legacy or current) layout.
+	 */
+	enetc4_vf_get_devargs(eth_dev);
+
 	ENETC_PMD_DEBUG("port_id %d vendorID=0x%x deviceID=0x%x",
 			eth_dev->data->port_id, pci_dev->id.vendor_id,
 			pci_dev->id.device_id);
@@ -1514,5 +1659,6 @@ RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
 			      ENETC4_VSI_DELAY "=<uint> "
-			      ENETC4_NC_MEMORY "=<int>");
+			      ENETC4_NC_MEMORY "=<int> "
+			      ENETC_VF_LINK_LEGACY "=<0|1>");
 RTE_LOG_REGISTER_DEFAULT(enetc4_vf_logtype_pmd, NOTICE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 05/13] net/enetc: support firmware version get for VF
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
                             ` (3 preceding siblings ...)
  2026-08-11  7:40           ` [PATCH v6 04/13] net/enetc: extend PF-VF link speed field to 8 bits Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 06/13] net/enetc: support registers dump Gagandeep Singh
                             ` (8 subsequent siblings)
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the ``.fw_version_get`` eth_dev op for the ENETC4 VF PMD to
report the NETC IP revision.

The NETC IP major revision is read from the PCI revision ID register,
while the IP minor revision is retrieved from the PSI over VSI-PSI
messaging using the "Get IP version" command class (class ID 0xF0,
command ID 0x1). The version is reported in the "<major>.<minor>"
format.

The PSI firmware only implements the IP_MN command of this class, so
the major revision is obtained from the PCI revision ID, matching the
behaviour of the kernel PF/VF drivers.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   1 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |  19 +++-
 drivers/net/enetc/enetc4_vf.c          | 139 +++++++++++++++++++++++++
 5 files changed, 159 insertions(+), 2 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 935cf90e6b..fa93f40b22 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,7 @@ Key functionality includes:
   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.
+- Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index aae398e210..eaf474e7ba 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ VLAN filter          = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
+FW version           = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index ee4f455c67..d212cce000 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -66,6 +66,7 @@ New Features
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
     Users running a PF kernel older than 6.18.37 must pass ``vf_link_legacy=1``
     as a device argument, otherwise link speed reporting will be incorrect.
+  * Added firmware version reporting for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index d6ae54a132..c4a55626f8 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -169,7 +169,8 @@ enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
-	ENETC_CLASS_ID_LINK_SPEED = 0x81
+	ENETC_CLASS_ID_LINK_SPEED = 0x81,
+	ENETC_CLASS_ID_GET_IP_VER = 0xF0
 };
 
 /* Enum for command IDs */
@@ -183,9 +184,18 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_LINK_STATUS = 0,
 	ENETC_CMD_ID_REGISTER_LINK_NOTIF = 1,
 	ENETC_CMD_ID_UNREGISTER_LINK_NOTIF = 2,
-	ENETC_CMD_ID_GET_LINK_SPEED = 0
+	ENETC_CMD_ID_GET_LINK_SPEED = 0,
+	/* Get IP version command IDs (class ID 0xF0) */
+	ENETC_CMD_ID_GET_IP_MJ = 0,
+	ENETC_CMD_ID_GET_IP_MN = 1,
+	ENETC_CMD_ID_GET_IP_INT = 2,
+	ENETC_CMD_ID_GET_IP_MNT = 3,
+	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* IP_VER value returned when the version is not available */
+#define ENETC_IP_VER_NOT_AVAILABLE	0xFF
+
 enum mac_addr_status {
 	ENETC_INVALID_MAC_ADDR = 0x0,
 	ENETC_DUPLICATE_MAC_ADDR = 0X1,
@@ -273,6 +283,11 @@ struct enetc_msg_cmd_get_link_speed {
 	struct enetc_msg_cmd_header header;
 };
 
+/* Get IP version command message format (class ID 0xF0) */
+struct enetc_msg_cmd_get_ip_ver {
+	struct enetc_msg_cmd_header header;
+};
+
 struct enetc_msg_cmd_set_vlan_promisc {
 	struct enetc_msg_cmd_header header;
 	uint8_t op;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 9c6070b6d4..41f915be18 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -3,6 +3,7 @@
  */
 
 #include <stdbool.h>
+#include <rte_bus_pci.h>
 #include <rte_kvargs.h>
 #include <rte_random.h>
 #include <dpaax_iova_table.h>
@@ -433,6 +434,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_MAC_FILTER:
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
+		case ENETC_CLASS_ID_GET_IP_VER:
 			break;
 		default:
 			err = -EIO;
@@ -796,6 +798,142 @@ enetc4_vf_get_link_speed(struct rte_eth_dev *dev, struct enetc_psi_reply_msg *re
 	return err;
 }
 
+/*
+ * Get the NETC IP minor revision (IP_MN) from the PSI using the 'Get IP
+ * version' command class (class ID 0xF0, cmd_id 0x1). In the reply, the
+ * low 8 bits of the return code carry the minor revision and the upper 8
+ * bits carry the class code (0xF0). The PSI only implements the IP_MN
+ * command of this class; the major revision comes from the PCI revision.
+ */
+static int
+enetc4_vf_get_ip_minor_revision(struct rte_eth_dev *dev, uint8_t *ip_mn)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_swbd *msg;
+	uint32_t msg_size;
+	uint16_t mc;
+	uint8_t class_id;
+	int vsimsgsr;
+	int err = 0;
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_cmd_get_ip_ver),
+				ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg");
+		rte_free(msg);
+		return -ENOMEM;
+	}
+
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	msg->size = msg_size;
+
+	/* COOKIE is 0 so that the command is executed as blocking on PSI */
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_GET_IP_VER,
+			ENETC_CMD_ID_GET_IP_MN, 0, 0, 0);
+
+	/* send the command and wait */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error");
+		goto end;
+	}
+
+	/*
+	 * For the IP version command class the class-specific field is
+	 * reused to carry the 8-bit version value in the lower byte of the
+	 * return code, so parse the full low byte here instead of using the
+	 * generic reply parser.
+	 */
+	vsimsgsr = enetc4_rd(enetc_hw, ENETC4_VSIMSGSR);
+	mc = ENETC_SIMSGSR_GET_MC(vsimsgsr);
+	class_id = (mc >> 8) & 0xff;
+
+	if (class_id != ENETC_CLASS_ID_GET_IP_VER) {
+		ENETC_PMD_ERR("Wrong reply message 0x%x", class_id);
+		err = -EIO;
+		goto end;
+	}
+
+	*ip_mn = mc & 0xff;
+	if (*ip_mn == ENETC_IP_VER_NOT_AVAILABLE) {
+		ENETC_PMD_DEBUG("IP minor revision not available");
+		err = -ENOTSUP;
+	}
+
+end:
+	/* free memory no longer required */
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	return err;
+}
+
+/*
+ * Retrieve the NETC firmware/IP version and format it as
+ * "<major>.<minor>". The IP major revision is taken from the PCI
+ * revision ID register, and the IP minor revision is fetched from the
+ * PSI via VSI-PSI messaging ('Get IP version' command class). This
+ * mirrors the behaviour of the kernel PF/VF drivers, where the PSI only
+ * implements the IP_MN command of the 0xF0 class.
+ */
+static int
+enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_size)
+{
+	struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
+	uint8_t ip_mj = 0, ip_mn = 0;
+	int ret;
+
+	PMD_INIT_FUNC_TRACE();
+
+	if (fw_version == NULL)
+		return -EINVAL;
+
+	/* IP major revision is exposed through the PCI revision ID */
+	ret = rte_pci_read_config(pci_dev, &ip_mj, sizeof(ip_mj),
+			RTE_PCI_REVISION_ID);
+	if (ret != sizeof(ip_mj)) {
+		ENETC_PMD_ERR("Failed to read PCI revision ID");
+		return -EIO;
+	}
+
+	/* IP minor revision is fetched from the PSI via VSI-PSI messaging.
+	 * If the PSI reports the version as unavailable, fall back to a
+	 * partial version string so the caller still gets useful output.
+	 */
+	ret = enetc4_vf_get_ip_minor_revision(dev, &ip_mn);
+	if (ret && ret != -ENOTSUP) {
+		ENETC_PMD_ERR("Failed to get NETC minor revision");
+		return ret;
+	}
+
+	if (fw_size == 0) {
+		if (ret == -ENOTSUP)
+			return snprintf(NULL, 0, "%u.unknown", ip_mj) + 1;
+		else
+			return snprintf(NULL, 0, "%u.%u", ip_mj, ip_mn) + 1;
+	}
+
+	if (ret == -ENOTSUP)
+		ret = snprintf(fw_version, fw_size, "%u.unknown", ip_mj);
+	else
+		ret = snprintf(fw_version, fw_size, "%u.%u", ip_mj, ip_mn);
+	if (ret < 0)
+		return -EINVAL;
+
+	ret += 1; /* add trailing '\0' */
+	if ((size_t)ret > fw_size)
+		return ret;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1354,6 +1492,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.fw_version_get       = enetc4_vf_fw_version_get,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.mac_addr_set         = enetc4_vf_set_mac_addr,
 	.mac_addr_add	      = enetc4_vf_mac_addr_add,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 06/13] net/enetc: support registers dump
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
                             ` (4 preceding siblings ...)
  2026-08-11  7:40           ` [PATCH v6 05/13] net/enetc: support firmware version get for VF Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 07/13] net/enetc: support ethtool ring parameters Gagandeep Singh
                             ` (7 subsequent siblings)
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the .get_reg operation for the ENETC4 PF and VF PMDs to dump
the device registers via rte_eth_dev_get_reg_info.

The VF dumps the registers reachable by a virtual station interface
(VSI): the station interface and per-ring BD ring registers. Port
registers are not accessible by a VF.

The PF additionally dumps the port registers, which are only
accessible by the physical station interface (PSI).

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |  1 +
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 12 +++++
 drivers/net/enetc/enetc4_ethdev.c      | 70 ++++++++++++++++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 62 +++++++++++++++++++++++
 6 files changed, 147 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index fa93f40b22..16389aee5b 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -60,6 +60,7 @@ Key functionality includes:
   requested. RSC requires the FCS to be stripped, so it cannot be combined
   with the KEEP_CRC Rx offload.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
+- Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index eaf474e7ba..6540a43909 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -17,6 +17,7 @@ RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
 FW version           = Y
+Registers dump       = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index d212cce000..6fece98f28 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -67,6 +67,7 @@ New Features
     Users running a PF kernel older than 6.18.37 must pass ``vf_link_legacy=1``
     as a device argument, otherwise link speed reporting will be incorrect.
   * Added firmware version reporting for the ENETC4 VF.
+  * Added register dump support for ENETC4 PF and VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index c4a55626f8..f0d64cfb29 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -395,6 +395,18 @@ enetc_bd_unused(struct enetc_bdr *bdr)
 	return bdr->bd_count + bdr->next_to_clean - bdr->next_to_use - 1;
 }
 
+/* Per-ring Tx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_txbdr_regs[] = {
+	ENETC_TBMR, ENETC_TBSR, ENETC_TBBAR0, ENETC_TBBAR1,
+	ENETC_TBCIR, ENETC_TBLENR,
+};
+
+/* Per-ring Rx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_rxbdr_regs[] = {
+	ENETC_RBMR, ENETC_RBSR, ENETC_RBBSR, ENETC_RBCIR,
+	ENETC_RBBAR0, ENETC_RBBAR1, ENETC_RBPIR, ENETC_RBLENR,
+};
+
 /* CBDR prototypes */
 int enetc4_setup_cbdr(struct rte_eth_dev *dev, struct enetc_hw *hw,
 			int bd_count, struct netc_cbdr *cbdr);
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 925da3f68e..7a296a8d07 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1178,6 +1178,75 @@ enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 	return ptypes;
 }
 
+/* Station interface registers dumped by .get_reg */
+static const uint32_t enetc4_pf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR,
+};
+
+/* Port registers dumped by .get_reg (accessible only by the PF) */
+static const uint32_t enetc4_pf_port_regs[] = {
+	ENETC4_PMR, ENETC4_PSIPMMR, ENETC4_PMAR0, ENETC4_PMAR1,
+	ENETC4_PM_CMD_CFG(0), ENETC4_PM_MAXFRM(0), ENETC4_PM_IF_MODE(0),
+	ENETC4_PM_IF_STATUS(0),
+};
+
+/*
+ * Dump the PF-accessible registers: station interface, port and per-ring
+ * BD ring registers. When info->data is NULL, only the register count and
+ * width are reported so the caller can size its buffer.
+ */
+static int
+enetc4_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_pf_si_regs);
+	count += RTE_DIM(enetc4_pf_port_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_si_regs); i++)
+		*buf++ = enetc4_rd(enetc_hw, enetc4_pf_si_regs[i]);
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_port_regs); i++)
+		*buf++ = enetc4_port_rd(enetc_hw, enetc4_pf_port_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = (uint32_t)hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 /*
  * The set of PCI devices this driver supports
  */
@@ -1196,6 +1265,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.link_update          = enetc4_link_update,
 	.stats_get            = enetc4_stats_get,
 	.stats_reset          = enetc4_stats_reset,
+	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 41f915be18..ec9a2e2bf8 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -934,6 +934,66 @@ enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_si
 	return 0;
 }
 
+/* VF station interface registers dumped by .get_reg */
+static const uint32_t enetc4_vf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR, ENETC4_SIMSIVR, ENETC4_VSIIER, ENETC4_VSIIDR,
+	ENETC4_VSIMSGSR, ENETC4_VSIMSGRR,
+};
+
+/*
+ * Dump the VF-accessible registers. Only station interface and per-ring
+ * BD ring registers are reachable by a VF; port registers are not.
+ * When info->data is NULL, only the register count and width are
+ * reported so the caller can size its buffer.
+ */
+static int
+enetc4_vf_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_vf_si_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_vf_si_regs); i++)
+		*buf++ = enetc_rd(enetc_hw, enetc4_vf_si_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = (uint32_t)hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1472,6 +1532,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
@@ -1493,6 +1554,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.mac_addr_set         = enetc4_vf_set_mac_addr,
 	.mac_addr_add	      = enetc4_vf_mac_addr_add,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 07/13] net/enetc: support ethtool ring parameters
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
                             ` (5 preceding siblings ...)
  2026-08-11  7:40           ` [PATCH v6 06/13] net/enetc: support registers dump Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 08/13] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
                             ` (6 subsequent siblings)
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The ethtool ring parameter query relies on the queue info ethdev
ops (.rxq_info_get/.txq_info_get) together with the descriptor
limits reported in dev_info. The PF already registered these ops,
but the VF ops tables did not, so ring parameter queries failed on
the VF.

Make enetc4_rxq_info_get() and enetc4_txq_info_get() non-static and
register them in both VF ops tables so ring parameters are reported
for both the PF and VF.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst | 1 +
 drivers/net/enetc/enetc.h              | 4 ++++
 drivers/net/enetc/enetc4_ethdev.c      | 4 ++--
 drivers/net/enetc/enetc4_vf.c          | 4 ++++
 4 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 6fece98f28..ec223a93ba 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -68,6 +68,7 @@ New Features
     as a device argument, otherwise link speed reporting will be incorrect.
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
+  * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index f0d64cfb29..4b563851b8 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -338,6 +338,10 @@ int enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx);
 void enetc4_tx_queue_release(struct rte_eth_dev *dev, uint16_t qid);
 const uint32_t *enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 			size_t *no_of_elements);
+void enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_rxq_info *qinfo);
+void enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_txq_info *qinfo);
 
 /*
  * enetc4_vf function prototype
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 7a296a8d07..ad6ef84214 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1127,7 +1127,7 @@ enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx)
 	return 0;
 }
 
-static void
+void
 enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_rxq_info *qinfo)
 {
@@ -1141,7 +1141,7 @@ enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 	qinfo->conf.rx_drop_en = 0;
 }
 
-static void
+void
 enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_txq_info *qinfo)
 {
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index ec9a2e2bf8..3da84941a2 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1539,10 +1539,12 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
@@ -1569,10 +1571,12 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 08/13] net/enetc: refresh link speed on VF link-up interrupt
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
                             ` (6 preceding siblings ...)
  2026-08-11  7:40           ` [PATCH v6 07/13] net/enetc: support ethtool ring parameters Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 09/13] net/enetc: support stats reset for VF Gagandeep Singh
                             ` (5 subsequent siblings)
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The interrupt-driven link-status path (enetc4_process_psi_msg) only
updated link_status when a PSI-to-VSI notification arrived; it never
re-queried the link speed from the PF.  As a result, after a link-down
followed by a link-up the cached link_speed showed the speed from the
previous session rather than the freshly negotiated one.

1. Introducing enetc4_decode_link_speed() - a shared helper that maps
   a PF-to-VF speed status code to the corresponding RTE_ETH_SPEED_NUM_*
   / RTE_ETH_LINK_*_DUPLEX values, handling both the current and the
   legacy (vf_link_legacy) 4-bit message layout.

2. Calling enetc4_vf_get_link_speed() inside enetc4_process_psi_msg()
   on ENETC_LINK_UP so the negotiated speed is fetched from the PF and
   decoded immediately, before rte_eth_linkstatus_set() is called and
   the LSC callback is fired.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |   2 +
 drivers/net/enetc/enetc4_ethdev.c      |   1 +
 drivers/net/enetc/enetc4_vf.c          | 257 +++++++++++++------------
 4 files changed, 141 insertions(+), 120 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index ec223a93ba..1765adc1bc 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -69,6 +69,7 @@ New Features
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
+  * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 4b563851b8..185fd8c4ee 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -5,6 +5,7 @@
 #ifndef _ENETC_H_
 #define _ENETC_H_
 
+#include <pthread.h>
 #include <rte_time.h>
 #include <ethdev_pci.h>
 
@@ -123,6 +124,7 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	pthread_mutex_t vsi_lock; /* serializes all VSI-PSI mailbox transactions */
 };
 
 /*
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index ad6ef84214..3d86072593 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -844,6 +844,7 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		if (dev->data->dev_conf.intr_conf.lsc != 0)
 			enetc4_vf_dev_intr(dev, false);
 		ret = enetc4_vf_dev_stop(dev);
+		pthread_mutex_destroy(&hw->vsi_lock);
 	} else {
 		ret = enetc4_dev_stop(dev);
 	}
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 3da84941a2..da41b999cb 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -323,9 +323,130 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	reply_msg->status = status;
 }
 
+/* Forward declaration: defined later in this file */
+static int enetc4_vf_get_link_speed(struct rte_eth_dev *dev,
+				     struct enetc_psi_reply_msg *reply_msg);
+
+/*
+ * Decode a PF-to-VF link-speed status code into the link_speed and
+ * link_duplex fields of *link.  vf_link_legacy selects the older
+ * 4-bit code layout used by kernel PFs before v6.18.37.
+ */
+static void
+enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
+			 struct rte_eth_link *link)
+{
+	switch (status) {
+	case ENETC_SPEED_UNKNOWN:
+		ENETC_PMD_DEBUG("Speed unknown");
+		link->link_speed = RTE_ETH_SPEED_NUM_NONE;
+		break;
+	case ENETC_SPEED_10_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_10_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_100_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_100_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_1000:
+		link->link_speed = RTE_ETH_SPEED_NUM_1G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_2500:
+		link->link_speed = RTE_ETH_SPEED_NUM_2_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_5000:
+		link->link_speed = RTE_ETH_SPEED_NUM_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	default:
+		if (vf_link_legacy) {
+			/* Legacy PF-to-VF message layout (older kernel PF):
+			 * speeds above 5Gbps use fixed 4-bit class codes.
+			 */
+			switch (status) {
+			case ENETC_SPEED_LEGACY_10G:
+				link->link_speed = RTE_ETH_SPEED_NUM_10G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_25G:
+				link->link_speed = RTE_ETH_SPEED_NUM_25G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_50G:
+				link->link_speed = RTE_ETH_SPEED_NUM_50G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_100G:
+				link->link_speed = RTE_ETH_SPEED_NUM_100G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+				ENETC_PMD_DEBUG("Speed not supported");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			default:
+				ENETC_PMD_ERR("Unknown speed status");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			}
+			break;
+		}
+		/* Any status here is > ENETC_SPEED_5000. Validate against
+		 * the set of speeds that the NETC IP is known to support.
+		 * An unrecognised code yields UNKNOWN rather than a
+		 * fabricated speed.
+		 */
+		switch ((status - ENETC_SPEED_5000) * 1000 + 5000) {
+		case RTE_ETH_SPEED_NUM_10G:
+			link->link_speed = RTE_ETH_SPEED_NUM_10G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_25G:
+			link->link_speed = RTE_ETH_SPEED_NUM_25G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_40G:
+			link->link_speed = RTE_ETH_SPEED_NUM_40G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_50G:
+			link->link_speed = RTE_ETH_SPEED_NUM_50G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_100G:
+			link->link_speed = RTE_ETH_SPEED_NUM_100G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_200G:
+			link->link_speed = RTE_ETH_SPEED_NUM_200G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		default:
+			ENETC_PMD_WARN("Unrecognized speed code 0x%x, "
+				       "reporting unknown", status);
+			link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+			break;
+		}
+		break;
+	}
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
 	int ret = 0;
@@ -344,6 +465,15 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		case ENETC_LINK_UP:
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Re-query speed from PF so the cached value reflects
+			 * the current negotiated speed after link-up.
+			 */
+			memset(msg, 0, sizeof(*msg));
+			if (!enetc4_vf_get_link_speed(eth_dev, msg) &&
+			    msg->class_id == ENETC_CLASS_ID_LINK_SPEED)
+				enetc4_decode_link_speed(msg->status,
+							hw->vf_link_legacy,
+							&link);
 			break;
 		case ENETC_LINK_DOWN:
 			ENETC_PMD_DEBUG("Link is down");
@@ -379,6 +509,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 	int err = 0;
 	int vsimsgsr;
 
+	pthread_mutex_lock(&hw->vsi_lock);
 	enetc4_msg_vsi_write_msg(enetc_hw, msg);
 
 	do {
@@ -390,11 +521,13 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 
 	if (!timeout) {
 		ENETC_PMD_ERR("Message not processed by PSI");
+		pthread_mutex_unlock(&hw->vsi_lock);
 		return -ETIMEDOUT;
 	}
 	/* check for message delivery error */
 	if (vsimsgsr & ENETC4_VSIMSGSR_MS) {
 		ENETC_PMD_ERR("Transfer error when copying the data");
+		pthread_mutex_unlock(&hw->vsi_lock);
 		return -EIO;
 	}
 
@@ -441,6 +574,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		}
 	}
 
+	pthread_mutex_unlock(&hw->vsi_lock);
 	return err;
 }
 
@@ -1051,126 +1185,8 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_SPEED) {
-		switch (reply_msg->status) {
-		case ENETC_SPEED_UNKNOWN:
-			ENETC_PMD_DEBUG("Speed unknown");
-			link.link_speed = RTE_ETH_SPEED_NUM_NONE;
-			break;
-		case ENETC_SPEED_10_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_10_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_100_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_1000:
-			link.link_speed = RTE_ETH_SPEED_NUM_1G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_2500:
-			link.link_speed = RTE_ETH_SPEED_NUM_2_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_5000:
-			link.link_speed = RTE_ETH_SPEED_NUM_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		default:
-			if (hw->vf_link_legacy) {
-				/* Legacy PF-to-VF message layout (older kernel
-				 * PF): speeds greater than 5Gbps are encoded
-				 * with fixed 4-bit class codes rather than the
-				 * formula below.
-				 */
-				switch (reply_msg->status) {
-				case ENETC_SPEED_LEGACY_10G:
-					link.link_speed = RTE_ETH_SPEED_NUM_10G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_25G:
-					link.link_speed = RTE_ETH_SPEED_NUM_25G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_50G:
-					link.link_speed = RTE_ETH_SPEED_NUM_50G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_100G:
-					link.link_speed = RTE_ETH_SPEED_NUM_100G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
-					ENETC_PMD_DEBUG("Speed not supported");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				default:
-					ENETC_PMD_ERR("Unknown speed status");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				}
-				break;
-			}
-
-			/* Any status reaching here is greater than
-			 * ENETC_SPEED_5000, as all values from 0x0 to
-			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
-			 * greater than 5Gbps are not enumerated and follow the
-			 * formula:
-			 *
-			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
-			 *
-			 * where link_speed is in Mbps. Reverse it here to get the
-			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
-			 *
-			 * Validate the computed value against the set of speeds
-			 * that the NETC IP is known to support (> 5Gbps).
-			 * An unrecognised code yields UNKNOWN rather than a
-			 * fabricated speed.
-			 */
-			switch ((reply_msg->status - ENETC_SPEED_5000)
-				* 1000 + 5000) {
-			case RTE_ETH_SPEED_NUM_10G:
-				link.link_speed = RTE_ETH_SPEED_NUM_10G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_25G:
-				link.link_speed = RTE_ETH_SPEED_NUM_25G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_40G:
-				link.link_speed = RTE_ETH_SPEED_NUM_40G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_50G:
-				link.link_speed = RTE_ETH_SPEED_NUM_50G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_100G:
-				link.link_speed = RTE_ETH_SPEED_NUM_100G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_200G:
-				link.link_speed = RTE_ETH_SPEED_NUM_200G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			default:
-				ENETC_PMD_WARN("Unrecognized speed code 0x%x, "
-					       "reporting unknown",
-					       reply_msg->status);
-				link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-				break;
-			}
-			break;
-		}
+		enetc4_decode_link_speed(reply_msg->status,
+					 hw->vf_link_legacy, &link);
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
@@ -1741,6 +1757,7 @@ enetc4_vf_dev_init(struct rte_eth_dev *eth_dev)
 	}
 
 	enetc4_dev_hw_init(eth_dev);
+	pthread_mutex_init(&hw->vsi_lock, NULL);
 
 	hw->nc_mode = 0;
 	enetc4_vf_get_devarg_nc(eth_dev);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 09/13] net/enetc: support stats reset for VF
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
                             ` (7 preceding siblings ...)
  2026-08-11  7:40           ` [PATCH v6 08/13] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 10/13] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
                             ` (4 subsequent siblings)
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The NETC SI-level hardware counters (SIROCT0, SIRFRM0, SITOCT0,
SITFRM0, SITDFCR) are read-only for a VF and cannot be directly
zeroed. No VSI-PSI command class exists for stats reset, and using
FLR/soft-reset to clear counters is not reliable due to a known
hardware erratum on some NETC platforms.

Implement stats_reset for the enetc4 VF PMD using a software
snapshot/delta approach: on stats_reset, the current HW counter
values are captured as a baseline in a new per-device
enetc4_vf_stats_saved struct. stats_get then reports the delta
(current_hw_value - saved_baseline), so counters appear to start
from zero after each reset call. Per-ring software Rx error
accumulators (ierrors) are zeroed directly on reset.

Register the stats_reset callback in both VF ops tables
(enetc4_vf_ops and enetc4_vf_ops_no_vsi_m) so the feature is
available regardless of whether the VSI messaging path is enabled.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 17 +++++++
 drivers/net/enetc/enetc4_vf.c          | 61 +++++++++++++++++++++++---
 3 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 1765adc1bc..a42577c681 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -70,6 +70,7 @@ New Features
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
+  * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 185fd8c4ee..96e18028a6 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -105,6 +105,21 @@ struct enetc_bdr {
 	uint8_t rsc_enable;
 };
 
+/*
+ * Saved SI counter baseline for VF stats reset. Since the SI-level
+ * hardware counters (SIROCT0, SIRFRM0, SITOCT0, SITFRM0, SITDFCR)
+ * are read-only for a VF and cannot be directly zeroed, stats_reset
+ * captures the current counter values as a baseline. stats_get then
+ * reports the delta: current_hw_value - baseline.
+ */
+struct enetc4_vf_stats_saved {
+	uint64_t ipackets;
+	uint64_t opackets;
+	uint64_t ibytes;
+	uint64_t obytes;
+	uint64_t oerrors;
+};
+
 struct enetc_eth_hw {
 	struct rte_eth_dev *ndev;
 	struct enetc_hw hw;
@@ -125,6 +140,8 @@ struct enetc_eth_hw {
 	 */
 	uint8_t vf_link_legacy;
 	pthread_mutex_t vsi_lock; /* serializes all VSI-PSI mailbox transactions */
+	/* Baseline snapshot for VF stats reset (software delta approach). */
+	struct enetc4_vf_stats_saved vf_stats_saved;
 };
 
 /*
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index da41b999cb..fb777bbeac 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -224,15 +224,64 @@ enetc4_vf_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
 	uint8_t i;
 
 	PMD_INIT_FUNC_TRACE();
-	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
-	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
-	stats->ibytes = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
-	stats->obytes = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
-	stats->oerrors = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/*
+	 * The SI-level counters are read-only for a VF; they cannot be
+	 * zeroed directly. Instead, stats_reset captures a baseline
+	 * snapshot, and stats_get reports the delta so that the reported
+	 * values appear to start from zero after each reset call.
+	 */
+	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0) -
+			  hw->vf_stats_saved.ipackets;
+	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0) -
+			  hw->vf_stats_saved.opackets;
+	stats->ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0) -
+			  hw->vf_stats_saved.ibytes;
+	stats->obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0) -
+			  hw->vf_stats_saved.obytes;
+	stats->oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR) -
+			  hw->vf_stats_saved.oerrors;
+
 	for (i = 0; i < dev->data->nb_rx_queues; i++) {
 		rx_ring = dev->data->rx_queues[i];
 		stats->ierrors += rx_ring->ierrors;
 	}
+
+	return 0;
+}
+
+/*
+ * Reset VF statistics by capturing a new baseline snapshot of the
+ * SI-level hardware counters. Because those counters are read-only
+ * for a VF (hardware erratum prevents reliable clear via FLR/soft
+ * reset too), the driver uses a software delta approach: every
+ * stats_get call reports current_hw_value - saved_baseline.
+ */
+static int
+enetc4_vf_stats_reset(struct rte_eth_dev *dev)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_bdr *rx_ring;
+	uint8_t i;
+
+	PMD_INIT_FUNC_TRACE();
+
+	/* Snapshot current HW SI counter values as the new zero baseline. */
+	hw->vf_stats_saved.ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
+	hw->vf_stats_saved.opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
+	hw->vf_stats_saved.ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
+	hw->vf_stats_saved.obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
+	hw->vf_stats_saved.oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/* Reset the per-ring software Rx error accumulators. */
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		rx_ring = dev->data->rx_queues[i];
+		if (rx_ring)
+			rx_ring->ierrors = 0;
+	}
+
 	return 0;
 }
 
@@ -1547,6 +1596,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
@@ -1570,6 +1620,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
 	.get_reg              = enetc4_vf_get_regs,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 10/13] net/enetc4: add per-queue Rx interrupt support for VF
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
                             ` (8 preceding siblings ...)
  2026-08-11  7:40           ` [PATCH v6 09/13] net/enetc: support stats reset for VF Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 11/13] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
                             ` (3 subsequent siblings)
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add MSI-X per-queue Rx interrupt support to the ENETC4 VF PMD on the
cacheable (default) Rx path. This allows applications such as l3fwd-power
to block in epoll_wait when no traffic is present, reducing CPU utilization
to near zero.

MSI-X vector 0 is reserved for the PSI-to-VSI mailbox. Rx queue i is
mapped to MSI-X vector i + 1 via ENETC_SIMSIRRV. Per-ring coalescing is
enabled with ICPT=1 so the first arriving packet fires the interrupt
immediately. The SIRXIDR W1C detect bit is cleared before re-arming
ENETC_RBIER to prevent spurious interrupts after traffic stops.

The interrupt infrastructure (rte_intr_efd_enable +
rte_intr_vec_list_alloc) is set up before rte_intr_enable() so that
vfio-pci can wire each MSI-X vector to its eventfd when programming
the MSI-X table.

enetc4_dev_configure() is updated to trigger interrupt setup when
intr_conf.rxq is set (not only when intr_conf.lsc is set), as applications
like l3fwd-power set intr_conf.rxq without setting lsc.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             | 65 ++++++++++++++++++++
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc4_hw.h     |  2 +
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      |  9 +--
 drivers/net/enetc/enetc4_vf.c          | 84 +++++++++++++++++++++++++-
 7 files changed, 158 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 16389aee5b..2b73916682 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,10 @@ Key functionality includes:
   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.
+- Per-queue Rx interrupts on VFs (cacheable Rx path only), enabling
+  interrupt-driven receive with ``vfio-pci``. Applications set
+  ``intr_conf.rxq = 1`` in ``rte_eth_conf`` to activate this feature.
+  See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
@@ -167,3 +171,64 @@ PF/Common devargs
   Usage example::
 
     dpdk-testpmd -a 0000:00:00.0,nc=1 -- -i
+
+
+Rx Interrupt Mode (VF)
+----------------------
+
+The ENETC4 VF PMD supports per-queue MSI-X Rx interrupts on the cacheable
+(default) Rx path. This allows applications to block in ``epoll_wait``
+instead of busy-polling, reducing CPU utilization when traffic is absent.
+
+**MSI-X vector assignment**
+
+ENETC4 VF MSI-X vector 0 is reserved for the PSI-to-VSI mailbox interrupt
+(link status notifications). Rx queue ``i`` is mapped to vector ``i + 1``.
+The driver allocates all required eventfds before calling
+``rte_intr_enable()`` so that ``vfio-pci`` can wire each MSI-X vector to
+its eventfd when it programs the MSI-X table.
+
+**Kernel and driver requirements**
+
+- ``vfio-pci`` kernel module with no-IOMMU mode enabled (no SMMU required).
+- The non-cacheable memory mode (``nc=1`` devarg) does **not** support
+  Rx interrupts and returns ``-ENOTSUP`` from ``rx_queue_intr_enable``.
+
+**Host setup**
+
+.. code-block:: console
+
+   # Enable vfio-pci no-IOMMU mode (if SMMU is not available)
+   modprobe vfio enable_unsafe_noiommu_mode=1
+   modprobe vfio-pci
+
+   # Bind the VF to vfio-pci
+   echo vfio-pci > /sys/bus/pci/devices/<vf_pci_addr>/driver_override
+   echo <vf_pci_addr> > /sys/bus/pci/drivers_probe
+
+**Running l3fwd-power with a single queue and core**
+
+The ``l3fwd-power`` sample application demonstrates interrupt-driven Rx.
+It sets ``intr_conf.rxq = 1`` in ``rte_eth_conf``, which triggers the VF
+interrupt setup in the driver. The ``--vfio-intr=msix`` EAL flag instructs
+DPDK to use MSI-X eventfds for interrupt signalling.
+
+.. code-block:: console
+
+   ./dpdk-l3fwd-power -l 0-1 -n 1 --vfio-intr=msix \
+       -a <vf_pci_addr> -- \
+       -p 0x1 --config="(0,0,1)" --no-numa --interrupt-only
+
+Where:
+
+- ``-l 0-1`` assigns the main thread to core 0 and the forwarding lcore to
+  core 1.
+- ``-a <vf_pci_addr>`` specifies the VF PCI address (e.g. ``0000:01:00.1``).
+- ``--config="(0,0,1)"`` maps port 0, queue 0 to lcore 1.
+- ``--interrupt-only`` enables pure interrupt mode (no busy-poll fallback).
+
+With no incoming traffic the forwarding lcore sleeps in ``epoll_wait``;
+CPU utilization drops to near zero. On the first arriving packet the MSI-X
+interrupt fires, the lcore wakes, drains the ring, disables the interrupt,
+processes the burst, then re-enables and re-arms the interrupt before
+returning to sleep.
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 6540a43909..005c64a3ca 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -5,6 +5,7 @@
 ;
 [Features]
 Link status event    = Y
+Rx interrupt         = Y
 Speed capabilities   = Y
 Link status          = Y
 LRO                  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index a42577c681..c182495118 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -71,6 +71,7 @@ New Features
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
+  * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index d42c04df4b..dff6b8ddf2 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -249,6 +249,8 @@ struct enetc_rx_bd_ext {
 #define ENETC4_VSIIDR            0xA08
 #define ENETC4_VSIIER_MRIE       BIT(9)
 #define ENETC4_SI_INT_IDX        0
+/* MSI-X vector base for per-Rx-queue interrupts; vector 0 is the mailbox. */
+#define ENETC4_VF_RX_VEC_BASE    1
 
 /* VSI Registers */
 #define ENETC4_VSIMSGSR  0x204   /* RO */
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 96e18028a6..9ef493888c 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -135,6 +135,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 3d86072593..53d201d62b 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -841,7 +841,8 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		return 0;
 
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0)
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0)
 			enetc4_vf_dev_intr(dev, false);
 		ret = enetc4_vf_dev_stop(dev);
 		pthread_mutex_destroy(&hw->vsi_lock);
@@ -964,12 +965,12 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	if (hw->device_id != ENETC4_DEV_ID_VF)
 		enetc4_port_wr(enetc_hw, ENETC4_PARCSCR, checksum);
 
-	/* Enable interrupts */
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0) {
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0) {
 			ret = enetc4_vf_dev_intr(dev, true);
 			if (ret)
-				ENETC_PMD_WARN("Failed to setup link interrupts");
+				ENETC_PMD_WARN("Failed to setup VF interrupts: %d", ret);
 		}
 	}
 
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index fb777bbeac..b1e7162321 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1588,6 +1588,52 @@ static const struct rte_pci_id pci_vf_id_enetc4_map[] = {
 	{ .vendor_id = 0, /* sentinel */ },
 };
 
+static int
+enetc4_vf_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t vec;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return -ENOTSUP;
+
+	vec = queue_id + ENETC4_VF_RX_VEC_BASE;
+
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), vec);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR1, 0);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR0,
+			ENETC4_RBICR0_ICEN | ENETC4_RBICR0_ICPT(1));
+	enetc_wr(enetc_hw, ENETC_SIRXIDR, BIT(queue_id));
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, ENETC_RBIER_RXTIE);
+
+	return 0;
+}
+
+static int
+enetc4_vf_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return 0;
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, 0);
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), 0);
+
+	return 0;
+}
+
 /* Features supported by this driver */
 /* ops table used when VSI messaging is disabled */
 static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
@@ -1606,6 +1652,8 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
 	.rxq_info_get         = enetc4_rxq_info_get,
+	.rx_queue_intr_enable  = enetc4_vf_rx_queue_intr_enable,
+	.rx_queue_intr_disable = enetc4_vf_rx_queue_intr_disable,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
@@ -1639,6 +1687,8 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
 	.rxq_info_get         = enetc4_rxq_info_get,
+	.rx_queue_intr_enable  = enetc4_vf_rx_queue_intr_enable,
+	.rx_queue_intr_disable = enetc4_vf_rx_queue_intr_disable,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
@@ -1885,6 +1935,35 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		/* Vector index 0 */
 		enetc_wr(enetc_hw, ENETC4_SIMSIVR, ENETC4_SI_INT_IDX);
 
+		if (rte_intr_cap_multiple(intr_handle) &&
+		    eth_dev->data->nb_rx_queues > 0) {
+			uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+			uint16_t i;
+
+			ret = rte_intr_efd_enable(intr_handle,
+					nb_rx + ENETC4_VF_RX_VEC_BASE);
+			if (ret) {
+				ENETC_PMD_WARN("Failed to enable per-queue Rx eventfds: %d",
+					       ret);
+				ret = 0;
+				hw->rxq_intr_en = 0;
+			} else {
+				ret = rte_intr_vec_list_alloc(intr_handle,
+						"enetc4_vf_rx_intr", nb_rx);
+				if (ret) {
+					ENETC_PMD_WARN("Failed to alloc intr vec list: %d",
+						       ret);
+					rte_intr_efd_disable(intr_handle);
+					hw->rxq_intr_en = 0;
+				} else {
+					for (i = 0; i < nb_rx; i++)
+						rte_intr_vec_list_index_set(intr_handle, i,
+							i + ENETC4_VF_RX_VEC_BASE);
+					hw->rxq_intr_en = 1;
+				}
+			}
+		}
+
 		/* enable uio/vfio intr/eventfd mapping */
 		ret = rte_intr_enable(intr_handle);
 		if (ret) {
@@ -1908,10 +1987,13 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		ENETC_PMD_WARN("Failed to un-register link notification %d", ret);
 disable:
 	enetc_vf_enable_mr_int(enetc_hw, false);
+	hw->rxq_intr_en = 0;
 	ret = rte_intr_disable(intr_handle);
 	if (ret)
 		ENETC_PMD_WARN("Failed to disable INTR %d", ret);
 intr_enable_fail:
+	rte_intr_vec_list_free(intr_handle);
+	rte_intr_efd_disable(intr_handle);
 	rte_intr_callback_unregister(intr_handle,
 			enetc4_dev_interrupt_handler, eth_dev);
 
@@ -1927,7 +2009,7 @@ static struct rte_pci_driver rte_enetc4_vf_pmd = {
 
 RTE_PMD_REGISTER_PCI(net_enetc4_vf, rte_enetc4_vf_pmd);
 RTE_PMD_REGISTER_PCI_TABLE(net_enetc4_vf, pci_vf_id_enetc4_map);
-RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic");
+RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic | vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 11/13] net/enetc4: add SI-based port VLAN insertion and removal
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
                             ` (9 preceding siblings ...)
  2026-08-11  7:40           ` [PATCH v6 10/13] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 12/13] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
                             ` (2 subsequent siblings)
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement hardware VLAN tag insertion on Tx and removal on Rx
via the per-SI VLAN isolation (pvid) mechanism on ENETC4.

On a PF, the driver writes directly to PSIaVLANR(0) (VID + enable
bit) and PSIaCFGR0(0) (SIVIE for Tx insertion, VTE for Rx removal,
SIVC_CVLAN to allow C-VLAN 0x8100 TPID).

On a privileged VF, the request is forwarded to the kernel PF via
the VSI-PSI mailbox using a new command class 0x24 (SI VLAN
isolation, cmd ID 0x1). The kernel PF handler programs the same
registers on behalf of the VF's station interface. The class 0x24
reply is added to the pass-through list in enetc4_msg_vsi_send() so
it is handled correctly alongside the existing command classes

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 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     | 14 +++++
 drivers/net/enetc/enetc.h              | 23 +++++++
 drivers/net/enetc/enetc4_ethdev.c      | 39 ++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 83 ++++++++++++++++++++++++++
 7 files changed, 165 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 2b73916682..262e2fe703 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -65,6 +65,10 @@ Key functionality includes:
   See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
+- SI-based port VLAN (pvid): Hardware VLAN tag insertion on Tx and removal on Rx, configured
+  via ``rte_eth_dev_set_vlan_pvid``. On a PF the registers are written directly; on a privileged
+  VF the request is forwarded to the kernel PF through the VSI-PSI mailbox (class 0x24).
+  Use the testpmd command ``tx_vlan set pvid <port_id> <vlan_id> on|off`` to enable or disable.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 005c64a3ca..01b0dc5b80 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c182495118..190d77ecee 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -72,6 +72,7 @@ New Features
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
+  * Added SI-based port VLAN insertion (Tx) and removal (Rx) 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 dff6b8ddf2..cddfa6e0ae 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -276,6 +276,20 @@ struct enetc_rx_bd_ext {
 #define ENETC4_SICTR0		0x18
 #define ENETC4_SICTR1		0x1c
 
+/* PSI SI VLAN register: per-SI VLAN tag for insertion/removal */
+#define ENETC4_PSIVLANR(a)		((a) * 0x80 + 0x2008)
+#define ENETC4_PSIVLANR_E		BIT(31)  /* enable SI VLAN processing */
+#define ENETC4_PSIVLANR_VTEA		BIT(30)  /* 0=strip tag, 1=zero VID */
+#define ENETC4_PSIVLANR_PCP(v)		(((uint32_t)(v) & 0x7) << 13)
+#define ENETC4_PSIVLANR_DEI		BIT(12)
+#define ENETC4_PSIVLANR_VID(v)		((uint32_t)(v) & 0xfff)
+
+/* PSI SI configuration register 0 */
+#define ENETC4_PSICFGR0(a)		((a) * 0x80 + 0x2010)
+#define ENETC4_PSICFGR0_SIVC_CVLAN	BIT(24)  /* allow C-VLAN 0x8100 */
+#define ENETC4_PSICFGR0_SIVIE		BIT(14)  /* SI VLAN insertion enable */
+#define ENETC4_PSICFGR0_VTE		BIT(12)  /* SI VLAN removal enable */
+
 /* general register accessors */
 #define enetc4_rd_reg(reg)	rte_read32((void *)(reg))
 #define enetc4_wr_reg(reg, val)  rte_write32((val), (void *)(reg))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 9ef493888c..8189dc3ea8 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -188,6 +188,8 @@ struct enetc_eth_adapter {
 enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
+	/* Configure SI VLAN isolation (insert / remove) */
+	ENETC_CLASS_ID_SI_VLAN_ISO = 0x24,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
 	ENETC_CLASS_ID_LINK_SPEED = 0x81,
 	ENETC_CLASS_ID_GET_IP_VER = 0xF0
@@ -213,6 +215,11 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* Command IDs for class ID 0x24 (SI VLAN isolation) */
+enum enetc_msg_si_vlan_cmd_id {
+	ENETC_CMD_ID_SET_SI_VLAN_ISO = 1,
+};
+
 /* IP_VER value returned when the version is not available */
 #define ENETC_IP_VER_NOT_AVAILABLE	0xFF
 
@@ -324,6 +331,22 @@ struct enetc_msg_vlan_exact_filter {
 	uint8_t reserved2;
 };
 
+/* VSI-PSI SI VLAN isolation message (class 0x24, cmd 0x1) */
+struct enetc_msg_si_vlan_iso {
+	struct enetc_msg_cmd_header header;
+	uint8_t ctrl;          /* E[7], VTEA[6], TPID[1:0] */
+	uint8_t pcp_dei_vid_hi; /* PCP[7:5], DEI[4], VID[11:8] */
+	uint8_t vid_lo;        /* VID[7:0] */
+	uint8_t reserved_0;
+	uint8_t flags;         /* SVIE[2], VTE[1] */
+	uint8_t reserved_1[3];
+};
+
+#define ENETC_SI_VLAN_ISO_E	BIT(7)  /* ctrl: enable SI VLAN */
+#define ENETC_SI_VLAN_ISO_VTEA	BIT(6)  /* ctrl: 0=strip, 1=zero VID */
+#define ENETC_SI_VLAN_ISO_SVIE	BIT(2)  /* flags: Tx insertion enable */
+#define ENETC_SI_VLAN_ISO_VTE	BIT(1)  /* flags: Rx removal enable */
+
 struct enetc_psi_reply_msg {
 	uint8_t class_id;
 	uint8_t status;
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 53d201d62b..59632cc790 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -875,6 +875,44 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 	return ret;
 }
 
+/* Configure SI-based VLAN insertion (Tx) and removal (Rx) for the PF. */
+static int
+enetc4_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t psivlanr, psicfgr0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	psicfgr0 = enetc4_port_rd(enetc_hw, ENETC4_PSICFGR0(0));
+
+	if (on) {
+		/* Build the VLAN register: enable bit + VID (PCP/DEI left 0) */
+		psivlanr = (uint32_t)ENETC4_PSIVLANR_E |
+			   ENETC4_PSIVLANR_VID(vlan_id);
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), psivlanr);
+
+		/* Allow C-VLAN TPID, enable Tx insertion and Rx removal */
+		psicfgr0 |= ENETC4_PSICFGR0_SIVC_CVLAN |
+			    ENETC4_PSICFGR0_SIVIE |
+			    ENETC4_PSICFGR0_VTE;
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+	} else {
+		/* Clear Tx insertion, Rx removal and C-VLAN allow bits */
+		psicfgr0 &= ~(ENETC4_PSICFGR0_SIVC_CVLAN |
+			      ENETC4_PSICFGR0_SIVIE |
+			      ENETC4_PSICFGR0_VTE);
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+
+		/* Clear the VLAN register (disable SI VLAN processing) */
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), 0);
+	}
+
+	return 0;
+}
+
 static int
 enetc4_promiscuous_enable(struct rte_eth_dev *dev)
 {
@@ -1270,6 +1308,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
+	.vlan_pvid_set        = enetc4_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index b1e7162321..eab4f3ed65 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -617,6 +617,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
 		case ENETC_CLASS_ID_GET_IP_VER:
+		case ENETC_CLASS_ID_SI_VLAN_ISO:
 			break;
 		default:
 			err = -EIO;
@@ -1526,6 +1527,87 @@ static int enetc4_vf_vlan_offload_set(struct rte_eth_dev *dev, int mask __rte_un
 	return 0;
 }
 
+/* Configure SI-based VLAN insertion/removal via VSI-PSI mailbox (class 0x24). */
+static int
+enetc4_vf_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_si_vlan_iso *cmd;
+	struct enetc_msg_swbd *msg;
+	struct enetc_psi_reply_msg *reply_msg;
+	uint32_t msg_size;
+	int err = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	reply_msg = rte_zmalloc(NULL, sizeof(*reply_msg), RTE_CACHE_LINE_SIZE);
+	if (!reply_msg) {
+		ENETC_PMD_ERR("Failed to alloc memory for reply_msg");
+		return -ENOMEM;
+	}
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_si_vlan_iso),
+			     ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg body");
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	if (msg->dma == RTE_BAD_IOVA) {
+		ENETC_PMD_ERR("Failed to get IOVA for SI VLAN isolation msg body");
+		rte_free(msg->vaddr);
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->size = msg_size;
+
+	cmd = (struct enetc_msg_si_vlan_iso *)msg->vaddr;
+
+	if (on) {
+		cmd->ctrl = (uint8_t)ENETC_SI_VLAN_ISO_E;
+		cmd->pcp_dei_vid_hi = (uint8_t)((vlan_id >> 8) & 0x0f);
+		cmd->vid_lo = (uint8_t)(vlan_id & 0xff);
+		cmd->flags = (uint8_t)(ENETC_SI_VLAN_ISO_SVIE | ENETC_SI_VLAN_ISO_VTE);
+	}
+	/* on=0: all fields zero (rte_zmalloc cleared the buffer) */
+
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_SI_VLAN_ISO,
+				     ENETC_CMD_ID_SET_SI_VLAN_ISO, 0, 0, 0);
+
+	/* send the command and wait for PSI reply */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error for SI VLAN isolation");
+		goto end;
+	}
+
+	enetc4_msg_vsi_reply_msg(enetc_hw, reply_msg);
+
+	if (reply_msg->class_id != ENETC_MSG_CLASS_ID_CMD_SUCCESS) {
+		ENETC_PMD_ERR("SI VLAN isolation command failed: class_id=0x%x status=0x%x",
+			      reply_msg->class_id, reply_msg->status);
+		err = -EINVAL;
+	}
+
+end:
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	rte_free(reply_msg);
+	return err;
+}
+
 static int
 enetc4_vf_mtu_set(struct rte_eth_dev *dev __rte_unused, uint16_t mtu __rte_unused)
 {
@@ -1682,6 +1764,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.link_update	      = enetc4_vf_link_update,
 	.vlan_filter_set      = enetc4_vf_vlan_filter_set,
 	.vlan_offload_set     = enetc4_vf_vlan_offload_set,
+	.vlan_pvid_set        = enetc4_vf_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 12/13] net/enetc4: update VF link status to bitmask encoding
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
                             ` (10 preceding siblings ...)
  2026-08-11  7:40           ` [PATCH v6 11/13] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:40           ` [PATCH v6 13/13] net/enetc4: enable Tx PAUSE via VF Rx congestion mode Gagandeep Singh
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF link status notification code was previously a two-value
enum (UP=0x0, DOWN=0x1). Change this to a bitmask so future bits can
carry additional state alongside the link up/down indication:

  ENETC_LINK_DOWN  BIT(0)  -- set when link is down

Link up is now encoded as the DOWN bit being clear, which keeps the
wire value for link-down identical (0x1) and ensures backward
compatibility with older kernel PFs.

Changes:
- enetc.h: replace enum link_status with a #define bitmask;
  drop ENETC_LINK_UP (no longer a named constant).
- enetc4_vf.c enetc4_process_psi_msg(): replace switch/case on
  ENETC_LINK_UP/DOWN with bitmask decode.
- enetc4_vf.c enetc4_vf_link_update(): same bitmask decode.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              |  8 ++++----
 drivers/net/enetc/enetc4_vf.c          | 27 +++++++-------------------
 3 files changed, 12 insertions(+), 24 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 190d77ecee..47090068d0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -73,6 +73,7 @@ New Features
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
+  * Updated ENETC4 VF link status reporting to use bitmask encoding.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 8189dc3ea8..4ad6af777b 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -237,10 +237,10 @@ enum vlan_status {
 	ENETC_VLAN_NO_RESOURCE = 0x3
 };
 
-enum link_status {
-	ENETC_LINK_UP = 0x0,
-	ENETC_LINK_DOWN = 0x1
-};
+/* Link status bitmask in PF-to-VF mailbox notification.
+ * Link up is encoded as the DOWN bit being clear.
+ */
+#define ENETC_LINK_DOWN  (1u << 0)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index eab4f3ed65..af196900cb 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -510,8 +510,10 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 	enetc4_msg_get_psi_msg(enetc_hw, msg);
 
 	if (msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (msg->status) {
-		case ENETC_LINK_UP:
+		if (msg->status & ENETC_LINK_DOWN) {
+			ENETC_PMD_DEBUG("Link is down");
+			link.link_status = RTE_ETH_LINK_DOWN;
+		} else {
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
 			/* Re-query speed from PF so the cached value reflects
@@ -523,14 +525,6 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 				enetc4_decode_link_speed(msg->status,
 							hw->vf_link_legacy,
 							&link);
-			break;
-		case ENETC_LINK_DOWN:
-			ENETC_PMD_DEBUG("Link is down");
-			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status 0x%x", msg->status);
-			break;
 		}
 		ret = rte_eth_linkstatus_set(eth_dev, &link);
 		if (!ret)
@@ -1211,17 +1205,10 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (reply_msg->status) {
-		case ENETC_LINK_UP:
-			link.link_status = RTE_ETH_LINK_UP;
-			break;
-		case ENETC_LINK_DOWN:
+		if (reply_msg->status & ENETC_LINK_DOWN)
 			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status");
-			break;
-		}
+		else
+			link.link_status = RTE_ETH_LINK_UP;
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v6 13/13] net/enetc4: enable Tx PAUSE via VF Rx congestion mode
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
                             ` (11 preceding siblings ...)
  2026-08-11  7:40           ` [PATCH v6 12/13] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
@ 2026-08-11  7:40           ` Gagandeep Singh
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
  13 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:40 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

When the PF negotiates TX PAUSE on the port it signals this to the VF
via BIT(1) of the PF-to-VF link status mailbox message. The VF PMD must
respond by setting RBMR_CM (BIT(4)) on all active RX rings so the MAC
emits PAUSE frames on ingress pressure.

Add ENETC_RBMR_CM register definition, ENETC_LINK_TX_PAUSE bitmask,
and tx_pause_active state flag. Add enetc4_vf_set_congestion_mode() to
update all active RX rings and persist the state for rings started
later. Hook it into both the interrupt and poll link-update paths, and
apply the saved state in rx_queue_setup() and rx_queue_start().

RX PAUSE (honoring received PAUSE frames) is handled at the MAC level
by the PF and requires no VF PMD changes.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  1 +
 drivers/net/enetc/enetc.h              | 11 ++++-
 drivers/net/enetc/enetc4_ethdev.c      | 15 ++++++-
 drivers/net/enetc/enetc4_vf.c          | 60 ++++++++++++++++++++++++--
 6 files changed, 83 insertions(+), 6 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 01b0dc5b80..1f599dace7 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+Flow control         = Y
 VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 47090068d0..f287390db7 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -74,6 +74,7 @@ New Features
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
+  * Added Tx PAUSE support for the ENETC4 VF via Rx congestion mode.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 6e96562850..33d075fe59 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -51,6 +51,7 @@ enum enetc_bdr_type {TX, RX};
 							+ (off))
 /* RX BDR reg offsets */
 #define ENETC_RBMR		0x0 /* RX BDR mode register*/
+#define ENETC_RBMR_CM		BIT(4)  /* congestion mode: assert congestion to emit TX PAUSE */
 #define ENETC_RBMR_EN		BIT(31)
 
 #define ENETC_BMR_RESET		0x0 /* BDR reset*/
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 4ad6af777b..7f67b2a0b3 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -6,6 +6,7 @@
 #define _ENETC_H_
 
 #include <pthread.h>
+#include <rte_stdatomic.h>
 #include <rte_time.h>
 #include <ethdev_pci.h>
 
@@ -141,6 +142,11 @@ struct enetc_eth_hw {
 	 */
 	uint8_t vf_link_legacy;
 	pthread_mutex_t vsi_lock; /* serializes all VSI-PSI mailbox transactions */
+	/* 1 = TX PAUSE negotiated on port; VF RX rings must have RBMR_CM set.
+	 * Updated from the PF-to-VF link status mailbox message (BIT(1)).
+	 * Written on the interrupt thread, read on the control thread.
+	 */
+	RTE_ATOMIC(uint8_t)tx_pause_active;
 	/* Baseline snapshot for VF stats reset (software delta approach). */
 	struct enetc4_vf_stats_saved vf_stats_saved;
 };
@@ -239,8 +245,11 @@ enum vlan_status {
 
 /* Link status bitmask in PF-to-VF mailbox notification.
  * Link up is encoded as the DOWN bit being clear.
+ * TX_PAUSE is set when the port has negotiated TX PAUSE; VF must enable
+ * congestion mode (ENETC_RBMR_CM) on its RX rings accordingly.
  */
-#define ENETC_LINK_DOWN  (1u << 0)
+#define ENETC_LINK_DOWN      (1u << 0)
+#define ENETC_LINK_TX_PAUSE  (1u << 1)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 59632cc790..df22ad0c7b 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -714,8 +714,13 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	if (!rx_conf->rx_deferred_start) {
-		/* enable ring */
+		/* Enable ring; apply congestion mode if TX PAUSE is already active. */
 		rx_enable |= ENETC_RBMR_EN;
+		if (rte_atomic_load_explicit(&adapter->hw.tx_pause_active,
+					     rte_memory_order_acquire))
+			rx_enable |= ENETC_RBMR_CM;
+		else
+			rx_enable &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
@@ -1092,7 +1097,13 @@ enetc4_rx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->rx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		rx_data = enetc4_rxbdr_rd(&priv->hw.hw, rx_ring->index,
 					 ENETC_RBMR);
-		rx_data = rx_data | ENETC_RBMR_EN;
+		rx_data |= ENETC_RBMR_EN;
+		/* Restore congestion mode if TX PAUSE is active. */
+		if (rte_atomic_load_explicit(&priv->hw.tx_pause_active,
+					     rte_memory_order_acquire))
+			rx_data |= ENETC_RBMR_CM;
+		else
+			rx_data &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&priv->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_data);
 		dev->data->rx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index af196900cb..7155a60ad7 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -491,6 +491,38 @@ enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
 	}
 }
 
+/*
+ * Set or clear ENETC_RBMR_CM (congestion mode) on all active VF RX rings.
+ * When set, the ring signals congestion to the MAC, causing it to emit TX
+ * PAUSE frames on ingress pressure. hw->tx_pause_active is updated so rings
+ * started later inherit the correct state.
+ */
+static void
+enetc4_vf_set_congestion_mode(struct rte_eth_dev *eth_dev, bool enable)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+	uint16_t i;
+	uint32_t rbmr;
+
+	rte_atomic_store_explicit(&hw->tx_pause_active, enable ? 1 : 0,
+				  rte_memory_order_release);
+
+	for (i = 0; i < nb_rx; i++) {
+		rbmr = enetc4_rxbdr_rd(enetc_hw, i, ENETC_RBMR);
+		if (enable)
+			rbmr |= ENETC_RBMR_CM;
+		else
+			rbmr &= ~(uint32_t)ENETC_RBMR_CM;
+		enetc4_rxbdr_wr(enetc_hw, i, ENETC_RBMR, rbmr);
+	}
+
+	ENETC_PMD_DEBUG("VF congestion mode %s on %u RX rings",
+			enable ? "enabled" : "disabled", nb_rx);
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
@@ -498,6 +530,7 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
+	bool tx_pause;
 	int ret = 0;
 
 	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
@@ -513,9 +546,24 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		if (msg->status & ENETC_LINK_DOWN) {
 			ENETC_PMD_DEBUG("Link is down");
 			link.link_status = RTE_ETH_LINK_DOWN;
+			/* Clear congestion mode on link-down so VF rings do not
+			 * assert congestion while the port is offline.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, false);
 		} else {
-			ENETC_PMD_DEBUG("Link is up");
+			/* BIT(1) is set when the port has negotiated TX PAUSE.
+			 * Legacy PF does not set this bit so tx_pause stays false.
+			 */
+			tx_pause = !!(msg->status & ENETC_LINK_TX_PAUSE);
+			ENETC_PMD_DEBUG("Link is up, tx_pause=%d", tx_pause);
 			link.link_status = RTE_ETH_LINK_UP;
+
+			/* Apply congestion mode before raising the carrier so
+			 * the VF rings are ready to emit PAUSE before traffic
+			 * starts flowing.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, tx_pause);
+
 			/* Re-query speed from PF so the cached value reflects
 			 * the current negotiated speed after link-up.
 			 */
@@ -1205,10 +1253,16 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		if (reply_msg->status & ENETC_LINK_DOWN)
+		if (reply_msg->status & ENETC_LINK_DOWN) {
 			link.link_status = RTE_ETH_LINK_DOWN;
-		else
+			/* Link is down: disable congestion mode on all RX rings. */
+			enetc4_vf_set_congestion_mode(dev, false);
+		} else {
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Restore congestion mode from the TX PAUSE bit. */
+			enetc4_vf_set_congestion_mode(dev,
+				!!(reply_msg->status & ENETC_LINK_TX_PAUSE));
+		}
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95
  2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
                             ` (12 preceding siblings ...)
  2026-08-11  7:40           ` [PATCH v6 13/13] net/enetc4: enable Tx PAUSE via VF Rx congestion mode Gagandeep Singh
@ 2026-08-11  7:47           ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 01/14] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
                               ` (14 more replies)
  13 siblings, 15 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal

V7-changes:
 - added missing 14th patch.

V6-changes:
 - fixed free() on memory allocated with rte_zmalloc().
 - fixed eventfd and vector-list leak on interrupt teardown
 - fixed Rx ring doubling still bypasses
 - fixed VSI-PSI transaction issued from the interrupt handler
 - fixed the free-and-reallocate of msg is unnecessary churn
 - fixed unsynchronized access to tx_pause_active and RBMR
 - fixed signed shift overflow in the register-dump version field
 - fixed the fw_size == 0 early return reports the length of "0.0"
 - unwanted blank lines removed.
 - parse_txq_prior() returns updated.

V5-changes:
 - Patch 1 Build fixes which includes:
    'prev_seg' undeclared fixed.
    redefinition of 'dev_rx_offloads_sup' fixed.
    Error: duplicate rx_enable declaration fixed.
   PF loses Scattered Rx and Multi-segment Tx
 - mbuf leak in enetc_xmit_pkts_lso() fixed.
 - fixed conversion on ENETC4_TXBD_FLAGS_F issue.
 - fixed link speed decode has no upper bound.
 - fixed mailbox ops added to the no-VSI ops table.
 - new devargs documented.

V4-changes:
 - fix doc build issue: WARNING: undefined label: pmd_build_and_test

v3-changes:
 - fix doc build issue.
 - fix compilation issue on fedore:43-gcc-minsize

V2-changes:
 - compilation fixes.

V1-changes:
This series adds new PMD features to the ENETC4 driver targeting the
NXP i.MX95 NETC IP.

The series covers:

 - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
 - TSO: TCP Segmentation Offload for the VF Tx path.
 - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
 - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
   8-bit to support speeds beyond 10G.
 - Firmware version: report the NETC IP version via fw_version_get.
 - Register dump: dump SI, port (PF) and BD ring registers.
 - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
 - Link-up interrupt: refresh the cached link speed on each VF link-up
   interrupt so that link_update returns the current speed immediately.
 - Stats reset: software snapshot/delta approach for VF counter reset.
 - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
   enabling interrupt-driven receive with l3fwd-power.
 - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
 - VF link status bitmask: switch VF link status to bitmask encoding
   to align with the PF and newer kernel driver conventions.
 - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
   negotiated on the wire; adds Flow control = Y to enetc4.ini.
 - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.

Gagandeep Singh (14):
  net/enetc: add keep-CRC Rx offload for ENETC4
  net/enetc: add TSO support for ENETC4 VF
  net/enetc: add RSC (hardware LRO) support for ENETC4
  net/enetc: extend PF-VF link speed field to 8 bits
  net/enetc: support firmware version get for VF
  net/enetc: support registers dump
  net/enetc: support ethtool ring parameters
  net/enetc: refresh link speed on VF link-up interrupt
  net/enetc: support stats reset for VF
  net/enetc4: add per-queue Rx interrupt support for VF
  net/enetc4: add SI-based port VLAN insertion and removal
  net/enetc4: update VF link status to bitmask encoding
  net/enetc4: enable Tx PAUSE via VF Rx congestion mode
  net/enetc4: add WRR Tx scheduler devarg for VF rings

 doc/guides/nics/enetc4.rst             | 100 +++
 doc/guides/nics/features/enetc4.ini    |   8 +
 doc/guides/rel_notes/release_26_11.rst |  21 +
 drivers/net/enetc/base/enetc4_hw.h     | 128 +++-
 drivers/net/enetc/base/enetc_hw.h      |   6 +
 drivers/net/enetc/enetc.h              | 136 ++++-
 drivers/net/enetc/enetc4_ethdev.c      | 428 +++++++++++--
 drivers/net/enetc/enetc4_vf.c          | 807 ++++++++++++++++++++++---
 drivers/net/enetc/enetc_rxtx.c         | 530 +++++++++++++++-
 9 files changed, 2011 insertions(+), 153 deletions(-)

-- 
2.25.1


^ permalink raw reply	[flat|nested] 110+ messages in thread

* [PATCH v7 01/14] net/enetc: add keep-CRC Rx offload for ENETC4
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
                               ` (13 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The legacy ENETC (LS1028A) driver already supports the
RTE_ETH_RX_OFFLOAD_KEEP_CRC offload, but ENETC4 (i.MX95 NETC) did not.

Add KEEP_CRC support for ENETC4 (both PF and VF):

- Advertise RTE_ETH_RX_OFFLOAD_KEEP_CRC in the supported Rx offloads
  for the ENETC4 PF and VF. The VF reuses the PF Rx queue setup, so the
  HW configuration and datapath handling apply to both.
- Configure the per-ring RBaMR[CRC] bit in the Rx queue setup so that
  HW preserves the Ethernet FCS in the receive buffer when the offload
  is requested (0 = FCS removed, 1 = FCS preserved).
- Follow the DPDK convention used by the legacy ENETC and ixgbe drivers:
  set crc_len to RTE_ETHER_CRC_LEN when KEEP_CRC is enabled and have the
  datapath subtract crc_len from pkt_len/data_len. ENETC and ENETC4 share
  the same datapath (enetc_rxtx.c), so the semantics stay consistent.
- Handle the scatter-gather boundary case where the 4-byte FCS straddles
  the last two segments: drop the trailing segment, decrement nb_segs and
  trim the carry-over from its predecessor.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  6 ++++
 drivers/net/enetc/base/enetc4_hw.h     |  3 ++
 drivers/net/enetc/enetc4_ethdev.c      | 45 ++++++++++++++++----------
 drivers/net/enetc/enetc4_vf.c          |  1 +
 drivers/net/enetc/enetc_rxtx.c         | 39 +++++++++++++++++++---
 6 files changed, 74 insertions(+), 21 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 698140e30b..91b18d979e 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ Packet type parsing  = Y
 Basic stats          = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
+CRC offload          = Y
 Queue start/stop     = Y
 Scattered Rx         = Y
 Linux                = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..3678dd6894 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -56,6 +56,12 @@ New Features
      =======================================================
 
 
+* **Updated NXP ENETC4 PMD.**
+
+  Updated the NXP ENETC4 poll mode driver for i.MX95:
+
+  * Added KEEP_CRC Rx offload support for the ENETC4 PMD to preserve the Ethernet FCS.
+
 Removed Items
 -------------
 
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index 9685e7e1e5..c81ac00ec6 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -68,6 +68,9 @@ struct enetc_msg_swbd {
 #define PM_CMD_CFG_TX_EN		BIT(0)
 #define PM_CMD_CFG_RX_EN		BIT(1)
 
+/* RBaMR[CRC]: 0 = FCS removed, 1 = FCS preserved (KEEP_CRC) */
+#define ENETC4_RBMR_CRC			BIT(8)
+
 /* i.MX95 supports jumbo frame, but it is recommended to set the max frame
  * size to 2000 bytes.
  */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 2ddd63dafb..91e2cdb129 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -11,6 +11,21 @@
 #include "enetc_logs.h"
 #include "enetc.h"
 
+/* Supported Rx offloads */
+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_SCATTER |
+	RTE_ETH_RX_OFFLOAD_KEEP_CRC;
+
+/* Supported Tx offloads */
+static uint64_t dev_tx_offloads_sup =
+	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
+	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
+
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
 
@@ -93,20 +108,6 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 	return 0;
 }
 
-/* Supported Rx offloads */
-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_SCATTER;
-
-/* Supported Tx offloads */
-static uint64_t dev_tx_offloads_sup =
-	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
-	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
-
 static int
 enetc4_dev_start(struct rte_eth_dev *dev)
 {
@@ -536,6 +537,7 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			ENETC_DEV_PRIVATE(data->dev_private);
 	uint64_t rx_offloads = data->dev_conf.rxmode.offloads;
+	bool keep_crc;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_rx_desc > MAX_BD_COUNT)
@@ -549,6 +551,9 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	rx_ring->index = rx_queue_id;
+	keep_crc = !!(rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC);
+	rx_ring->crc_len = (uint8_t)(keep_crc ? RTE_ETHER_CRC_LEN : 0);
+
 	err = enetc4_alloc_rxbdr(rx_ring, nb_rx_desc);
 	if (err)
 		goto fail;
@@ -562,19 +567,25 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	data->rx_queues[rx_queue_id] = rx_ring;
 	rx_ring->rx_deferred_start = rx_conf->rx_deferred_start;
 
+	if (keep_crc)
+		rx_enable |= ENETC4_RBMR_CRC;
+	else
+		rx_enable &= ~ENETC4_RBMR_CRC;
+
 	if (!rx_conf->rx_deferred_start) {
 		/* enable ring */
+		rx_enable |= ENETC_RBMR_EN;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
-			       ENETC_RBMR_EN);
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STARTED;
 	} else {
+		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
+			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
 			       RTE_ETH_QUEUE_STATE_STOPPED;
 	}
 
-	rx_ring->crc_len = (uint8_t)((rx_offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC) ?
-				     RTE_ETHER_CRC_LEN : 0);
 	return 0;
 fail:
 	rte_free(rx_ring);
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index ef5f1e6d66..83a1e4931f 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -52,6 +52,7 @@ static uint64_t dev_rx_offloads_sup =
 	RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_VLAN_FILTER |
+	RTE_ETH_RX_OFFLOAD_KEEP_CRC |
 	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 8678bafece..d5855e69a3 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -530,6 +530,28 @@ enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
 	return rx_frm_cnt;
 }
 
+/*
+ * Trim the Ethernet FCS from a received cluster when HW CRC strip is
+ * disabled. pkt_len is reduced by crc_len. If the FCS straddles the last
+ * two segments (last seg holds fewer bytes than crc_len), drop the trailing
+ * segment and trim the carry-over from its predecessor. prev_seg is the
+ * segment preceding last_seg in the chain (the caller already tracks it).
+ */
+static inline void
+enetc_rx_crc_trim(struct rte_mbuf *first_seg, struct rte_mbuf *prev_seg,
+		  struct rte_mbuf *last_seg, uint16_t crc_len)
+{
+	first_seg->pkt_len -= crc_len;
+	if (likely(last_seg->data_len > crc_len)) {
+		last_seg->data_len -= crc_len;
+	} else if (prev_seg != NULL) {
+		first_seg->nb_segs--;
+		prev_seg->data_len -= crc_len - last_seg->data_len;
+		prev_seg->next = NULL;
+		rte_pktmbuf_free_seg(last_seg);
+	}
+}
+
 static int
 enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		    struct rte_mbuf **rx_pkts,
@@ -539,7 +561,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL, *prev_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -572,6 +594,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg, rxbd_temp.r.parse_summary);
 			first_seg->hash.rss = rxbd_temp.r.rss_hash;
@@ -579,6 +602,7 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -590,7 +614,9 @@ enetc_clean_rx_ring_nc(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
@@ -760,7 +786,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, *cur_seg;
+	struct rte_mbuf *first_seg = NULL, *cur_seg = NULL, *prev_seg = NULL;
 	uint32_t bd_status;
 	uint8_t *data;
 	uint32_t j;
@@ -815,6 +841,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 		if (!first_seg) {
 			first_seg = seg;
 			cur_seg = seg;
+			prev_seg = NULL;
 			first_seg->pkt_len = data_len;
 			enetc_dev_rx_parse(first_seg,
 					   rxbd->r.parse_summary);
@@ -823,6 +850,7 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 			first_seg->pkt_len += data_len;
 			first_seg->nb_segs++;
 			cur_seg->next = seg;
+			prev_seg = cur_seg;
 			cur_seg = seg;
 		}
 
@@ -838,7 +866,10 @@ enetc_clean_rx_ring_cacheable(struct enetc_bdr *rx_ring,
 
 		if (bd_status & ENETC_RXBD_LSTATUS_F) {
 			seg->next = NULL;
-			first_seg->pkt_len -= rx_ring->crc_len;
+			if (rx_ring->crc_len)
+				enetc_rx_crc_trim(first_seg, prev_seg, seg,
+						  rx_ring->crc_len);
+
 			rx_pkts[rx_frm_cnt] = first_seg;
 			rx_frm_cnt++;
 			first_seg = NULL;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 02/14] net/enetc: add TSO support for ENETC4 VF
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 01/14] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
                               ` (12 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add TCP and UDP segmentation offload (TSO) for the ENETC4 virtual
function. The offload is advertised through the VF Tx capabilities and
is enabled on a per-queue basis when an application requests the TSO
offload flags during Tx queue setup.

A dedicated Tx burst, enetc_xmit_pkts_lso(), builds the large-send
descriptor chain: a standard header BD, a 16B extension BD carrying the
segment size and frame length extension, and one BD per payload buffer
with the final-frame flag on the last BD. To make room for the extra
extension BD, an LSO-enabled Tx ring is allocated with twice the number
of descriptors. The existing non-TSO Tx paths are left unchanged.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   2 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/base/enetc4_hw.h     |  34 +++-
 drivers/net/enetc/enetc.h              |   3 +
 drivers/net/enetc/enetc4_ethdev.c      |  49 ++++-
 drivers/net/enetc/enetc4_vf.c          |   2 +
 drivers/net/enetc/enetc_rxtx.c         | 257 +++++++++++++++++++++++++
 8 files changed, 344 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index f0dd039f6e..4d27e4048e 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -53,6 +53,8 @@ Key functionality includes:
 - Receive processing: Upon packet reception, the BD Ring status bit is set,
   facilitating packet processing.
 - 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.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 91b18d979e..8ec1e8d00f 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
+TSO                  = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 3678dd6894..6a348ab4e0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -61,6 +61,7 @@ New Features
   Updated the NXP ENETC4 poll mode driver for i.MX95:
 
   * 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.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index c81ac00ec6..6bc462a93e 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2024 NXP
+ * Copyright 2024-2026 NXP
  *
  * This header file defines the register offsets and bit fields
  * of ENETC4 PF and VFs.
@@ -24,7 +24,12 @@ struct enetc_msg_swbd {
 
 /* enetc4 txbd flags */
 #define ENETC4_TXBD_FLAGS_L4CS		BIT(0)
+/* Request LSO (Large Send Offload) segmentation for this frame */
+#define ENETC4_TXBD_FLAGS_LSO		BIT(1)
+/* L4 checksum insertion (also used for checksum update on LSO) */
 #define ENETC4_TXBD_FLAGS_L_TX_CKSUM	BIT(3)
+/* Extended descriptor: the next 16B ring entry is an extension BD */
+#define ENETC4_TXBD_FLAGS_EXT		BIT(6)
 #define ENETC4_TXBD_FLAGS_F		BIT(7)
 /* L4 type */
 #define ENETC4_TXBD_L4T_UDP		BIT(0)
@@ -34,6 +39,33 @@ struct enetc_msg_swbd {
 /* IPv4 checksum */
 #define ENETC4_TXBD_IPCS		1
 
+/*
+ * Extension Transmit Buffer Descriptor (16B). When the standard BD has the
+ * extended flag set, this occupies the next ring entry and carries the extra
+ * fields required by LSO.
+ */
+struct enetc_tx_bd_ext {
+	uint32_t timestamp;	/* PTP timestamp, unused for LSO */
+	uint32_t vlan;		/* VLAN insert, unused for LSO */
+	uint32_t lso;		/* LSO_MAX_SEG_SIZE and FRM_LEN_EXT */
+	uint32_t flags;		/* extension flags */
+};
+
+/* LSO_MAX_SEG_SIZE occupies bits 13-0 of the LSO word */
+#define ENETC4_TXBD_EXT_LSO_SEG_MASK	0x3fff
+#define ENETC4_TXBD_EXT_LSO_SEG(mss) \
+		((uint32_t)((mss) & ENETC4_TXBD_EXT_LSO_SEG_MASK))
+/* FRM_LEN_EXT occupies bits 19-16 of the LSO word (top 4 bits of data len) */
+#define ENETC4_TXBD_EXT_FRM_LEN_EXT(x) \
+		(((uint32_t)((x) & 0xf)) << 16)
+/* Final flag in the extension flags word (bit 127 -> local bit 31) */
+#define ENETC4_TXBD_EXT_FLAGS_F		BIT(31)
+
+/* NETC does not create LSO frames larger than this many bytes */
+#define ENETC4_LSO_MAX_FRAME		9600
+/* Maximum LSO data unit (payload to be segmented) supported by HW: 256KB */
+#define ENETC4_LSO_MAX_DATA_UNIT	(256 * 1024)
+
 /***************************ENETC port registers**************************/
 #define ENETC4_PMR		0x10
 #define ENETC4_PMR_EN		(BIT(16) | BIT(17) | BIT(18))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index d3a8b8e34a..6cd031f269 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -99,6 +99,7 @@ struct enetc_bdr {
 	uint64_t ierrors;
 	uint8_t rx_deferred_start;
 	uint8_t tx_deferred_start;
+	uint8_t lso_enable;
 };
 
 struct enetc_eth_hw {
@@ -312,6 +313,8 @@ uint16_t enetc_xmit_pkts(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
 uint16_t enetc_xmit_pkts_nc(void *txq, struct rte_mbuf **tx_pkts,
 		uint16_t nb_pkts);
+uint16_t enetc_xmit_pkts_lso(void *txq, struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts);
 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,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 91e2cdb129..24c454fe28 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -24,7 +24,9 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
-	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
+	RTE_ETH_TX_OFFLOAD_MULTI_SEGS |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
 #define ENETC4_NC_MEMORY	"nc"
@@ -309,22 +311,37 @@ static int
 enetc4_alloc_txbdr(struct enetc_bdr *txr, uint16_t nb_desc)
 {
 	int size;
+	uint32_t ring_desc;
 
-	size = nb_desc * sizeof(struct enetc_swbd);
+	/*
+	 * On LSO-enabled rings each frame uses an extra 16B extension BD
+	 * (logical 32B descriptor) plus the payload BDs.  Size the ring with
+	 * twice the requested entries so the effective frame capacity is
+	 * preserved.  Non-LSO rings are sized exactly as requested.
+	 */
+	ring_desc = txr->lso_enable ? (uint32_t)nb_desc * 2 : (uint32_t)nb_desc;
+	if (ring_desc > MAX_BD_COUNT) {
+		ENETC_PMD_ERR("LSO ring_desc %u > MAX_BD_COUNT %u; "
+			      "reduce nb_desc to <= %u with LSO enabled",
+			      ring_desc, MAX_BD_COUNT, MAX_BD_COUNT / 2);
+		return -EINVAL;
+	}
+
+	size = ring_desc * sizeof(struct enetc_swbd);
 	/* Zero q_swbd so buffer_addr is NULL for all uninitialized slots. */
 	txr->q_swbd = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->q_swbd == NULL)
 		return -ENOMEM;
 
 	/* Allocate the TX BD ring: each BD is struct enetc_tx_bd (16 bytes). */
-	size = nb_desc * sizeof(struct enetc_tx_bd);
+	size = ring_desc * sizeof(struct enetc_tx_bd);
 	txr->bd_base = rte_zmalloc(NULL, size, ENETC_BD_RING_ALIGN);
 	if (txr->bd_base == NULL) {
 		rte_free(txr->q_swbd);
 		txr->q_swbd = NULL;
 		return -ENOMEM;
 	}
-	txr->bd_count = nb_desc;
+	txr->bd_count = ring_desc;
 	txr->next_to_clean = 0;
 	txr->next_to_use = 0;
 
@@ -376,6 +393,7 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	struct rte_eth_dev_data *data = dev->data;
 	struct enetc_eth_adapter *priv =
 			ENETC_DEV_PRIVATE(data->dev_private);
+	uint64_t tx_offloads;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_desc > MAX_BD_COUNT)
@@ -389,6 +407,29 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	tx_ring->index = queue_idx;
+	/*
+	 * LSO (TCP/UDP segmentation) is a port-level offload. The Tx burst is
+	 * a single device-level function pointer, so it must be consistent for
+	 * all queues; likewise every ring must be sized the same way. Decide
+	 * from the port offloads only (not the per-queue tx_conf) so that all
+	 * queues either use the LSO burst with a doubled ring, or none do.
+	 * The LSO burst also handles non-TSO packets, so it is safe on queues
+	 * that never carry TSO traffic. LSO needs HW FCS insertion, so it is
+	 * incompatible with KEEP_CRC on Rx.
+	 */
+	tx_offloads = data->dev_conf.txmode.offloads;
+	if (tx_offloads & (RTE_ETH_TX_OFFLOAD_TCP_TSO |
+			   RTE_ETH_TX_OFFLOAD_UDP_TSO)) {
+		if (data->dev_conf.rxmode.offloads &
+		    RTE_ETH_RX_OFFLOAD_KEEP_CRC) {
+			ENETC_PMD_ERR("LSO (TSO) is incompatible with KEEP_CRC");
+			rte_free(tx_ring);
+			return -EINVAL;
+		}
+		tx_ring->lso_enable = 1;
+		dev->tx_pkt_burst = &enetc_xmit_pkts_lso;
+	}
+
 	err = enetc4_alloc_txbdr(tx_ring, nb_desc);
 	if (err)
 		goto fail;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 83a1e4931f..3083a56335 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -60,6 +60,8 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
 	RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+	RTE_ETH_TX_OFFLOAD_TCP_TSO |
+	RTE_ETH_TX_OFFLOAD_UDP_TSO |
 	RTE_ETH_TX_OFFLOAD_MULTI_SEGS;
 
 static void
diff --git a/drivers/net/enetc/enetc_rxtx.c b/drivers/net/enetc/enetc_rxtx.c
index d5855e69a3..0bb5a86dd8 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -221,6 +221,263 @@ enetc_xmit_pkts_nc(void *tx_queue,
 	return start;
 }
 
+/*
+ * LSO (Large Send Offload) Tx burst.
+ *
+ * Dedicated burst used on Tx rings with LSO enabled (txr->lso_enable). It
+ * follows the same non-cache-coherent discipline as enetc_xmit_pkts_cacheable:
+ * payload lines are flushed with dcbf before handing the frame to HW and the
+ * written BD cache lines are flushed at the end of the batch.
+ *
+ * A TSO/USO frame uses an extended Tx descriptor: the standard BD points at
+ * the L2/L3/L4 header template (BUF_LEN = header length, FRM_LEN = payload
+ * length), followed by an extension BD in the next ring slot holding the
+ * segment size, then one BD per payload buffer with the F flag on the last.
+ * Non-TSO packets fall through to the normal encoding so mixed traffic works.
+ */
+uint16_t
+enetc_xmit_pkts_lso(void *tx_queue,
+		struct rte_mbuf **tx_pkts,
+		uint16_t nb_pkts)
+{
+	int i, start, bds_to_use;
+	struct enetc_tx_bd *txbd = NULL;
+	struct enetc_tx_bd_ext *txbd_ext;
+	struct enetc_bdr *tx_ring = (struct enetc_bdr *)tx_queue;
+	unsigned int j;
+	uint8_t *data;
+	struct rte_mbuf *seg;
+	uint16_t seg_len, segs_per_pkt;
+	bool is_first_seg;
+	int first_bd_idx, bd_count;
+
+	i = tx_ring->next_to_use;
+	bds_to_use = enetc_bd_unused(tx_ring);
+	bd_count = tx_ring->bd_count;
+	start = 0;
+
+	first_bd_idx = i;
+
+	while (start < nb_pkts) {
+		seg = tx_pkts[start];
+		segs_per_pkt = seg->nb_segs;
+
+		if (seg->ol_flags &
+		    (RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_UDP_SEG)) {
+			bool is_udp_seg =
+				!!(seg->ol_flags & RTE_MBUF_F_TX_UDP_SEG);
+			uint32_t hdr_len = seg->l2_len + seg->l3_len +
+					   seg->l4_len;
+			uint32_t data_unit;
+			uint16_t first_payload;
+			struct rte_mbuf *dseg;
+			int bds_needed;
+
+			/* Header BD + extension BD + one BD per segment. */
+			bds_needed = 2 + segs_per_pkt;
+			if (bds_to_use < bds_needed)
+				break;
+
+			/*
+			 * Skip frames with nothing to segment, or whose
+			 * headers are not fully contained in the first
+			 * segment. The first BD carries the header template
+			 * and first_payload is computed as (first segment
+			 * data_len - hdr_len), so the L2/L3/L4 headers must
+			 * reside contiguously in the first mbuf; otherwise the
+			 * unsigned subtraction would underflow.
+			 */
+			if (unlikely(hdr_len >= rte_pktmbuf_pkt_len(seg) ||
+				     hdr_len > rte_pktmbuf_data_len(seg))) {
+				rte_pktmbuf_free(seg);
+				start++;
+				continue;
+			}
+			data_unit = rte_pktmbuf_pkt_len(seg) - hdr_len;
+
+			/*
+			 * Skip frames that violate HW LSO limits: a zero
+			 * segment size, a payload larger than the HW data
+			 * unit, or a per-segment frame (headers + segment)
+			 * bigger than the maximum LSO frame size.
+			 */
+			if (unlikely(seg->tso_segsz == 0 ||
+				     data_unit > ENETC4_LSO_MAX_DATA_UNIT ||
+				     hdr_len + seg->tso_segsz >
+					     ENETC4_LSO_MAX_FRAME)) {
+				rte_pktmbuf_free(seg);
+				start++;
+				continue;
+			}
+
+			/* Standard (first) BD: header template only. */
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			seg_len = rte_pktmbuf_data_len(seg);
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			memset(txbd, 0, sizeof(*txbd));
+			tx_ring->q_swbd[i].buffer_addr = seg;
+
+			/* FRM_LEN low 16 bits = payload length, BUF_LEN = hdr. */
+			txbd->frm_len = rte_cpu_to_le_16(data_unit & 0xffff);
+			txbd->buf_len = rte_cpu_to_le_16((uint16_t)hdr_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+
+			/* Checksum control for the per-segment L3/L4 rewrite. */
+			txbd->l3_start = seg->l2_len;
+			txbd->l3_hdr_size = seg->l3_len / 4;
+			if (seg->ol_flags & RTE_MBUF_F_TX_IPV6) {
+				txbd->l3t = 1;
+			} else {
+				txbd->l3t = ENETC4_TXBD_L3T;
+				txbd->ipcs = ENETC4_TXBD_IPCS;
+			}
+			txbd->l4t = is_udp_seg ? ENETC4_TXBD_L4T_UDP :
+						 ENETC4_TXBD_L4T_TCP;
+			txbd->flags = ENETC4_TXBD_FLAGS_L_TX_CKSUM |
+				      ENETC4_TXBD_FLAGS_L4CS |
+				      ENETC4_TXBD_FLAGS_LSO |
+				      ENETC4_TXBD_FLAGS_EXT;
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/* Extension BD occupies the next ring slot. */
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			txbd_ext = (struct enetc_tx_bd_ext *)
+				   ENETC_TXBD(*tx_ring, i);
+			memset(txbd_ext, 0, sizeof(*txbd_ext));
+			txbd_ext->lso = rte_cpu_to_le_32(ENETC4_TXBD_EXT_LSO_SEG(seg->tso_segsz) |
+					ENETC4_TXBD_EXT_FRM_LEN_EXT(data_unit >> 16));
+
+			i++;
+			bds_to_use--;
+			if (unlikely(i == bd_count))
+				i = 0;
+
+			/*
+			 * Payload BDs. The first segment's payload starts after
+			 * the header template; remaining segments are pure
+			 * payload.
+			 */
+			first_payload = (uint16_t)(seg_len - hdr_len);
+			dseg = seg;
+			is_first_seg = true;
+			while (dseg) {
+				uint16_t dlen;
+				uint64_t daddr;
+
+				if (is_first_seg) {
+					dlen = first_payload;
+					daddr = rte_mbuf_data_iova(dseg) +
+						hdr_len;
+					is_first_seg = false;
+				} else {
+					dlen = rte_pktmbuf_data_len(dseg);
+					data = rte_pktmbuf_mtod(dseg, void *);
+					for (j = 0; j < dlen;
+					     j += RTE_CACHE_LINE_SIZE)
+						dcbf(data + j);
+					dcbf(data + (dlen - 1));
+					daddr = rte_mbuf_data_iova(dseg);
+				}
+
+				if (dlen == 0) {
+					dseg = dseg->next;
+					continue;
+				}
+
+				tx_ring->q_swbd[i].buffer_addr = NULL;
+				txbd = ENETC_TXBD(*tx_ring, i);
+				memset(txbd, 0, sizeof(*txbd));
+				txbd->buf_len = rte_cpu_to_le_16(dlen);
+				txbd->addr = rte_cpu_to_le_64(daddr);
+				i++;
+				bds_to_use--;
+				if (unlikely(i == bd_count))
+					i = 0;
+				dseg = dseg->next;
+			}
+
+			/* Mark the last written BD as frame-last. */
+			txbd->flags |= ENETC4_TXBD_FLAGS_F;
+			start++;
+			continue;
+		}
+
+		/* Non-TSO packet: standard single/multi-seg encoding. */
+		if (bds_to_use < segs_per_pkt)
+			break;
+
+		is_first_seg = true;
+		while (seg) {
+			tx_ring->q_swbd[i].buffer_addr = NULL;
+			seg_len = rte_pktmbuf_data_len(seg);
+			data = rte_pktmbuf_mtod(seg, void *);
+
+			for (j = 0; j < seg_len; j += RTE_CACHE_LINE_SIZE)
+				dcbf(data + j);
+			dcbf(data + (seg_len - 1));
+
+			txbd = ENETC_TXBD(*tx_ring, i);
+			txbd->flags = 0;
+			if (is_first_seg) {
+				tx_ring->q_swbd[i].buffer_addr = seg;
+				txbd->frm_len = rte_pktmbuf_pkt_len(seg);
+				if (seg->ol_flags & ENETC4_TX_CKSUM_OFFLOAD_MASK)
+					enetc4_tx_offload_checksum(seg, txbd);
+				is_first_seg = false;
+			}
+
+			txbd->buf_len = rte_cpu_to_le_16(seg_len);
+			txbd->addr = rte_cpu_to_le_64(rte_mbuf_data_iova(seg));
+			seg = seg->next;
+			i++;
+			bds_to_use--;
+
+			if (unlikely(i == bd_count))
+				i = 0;
+		}
+
+		txbd->flags |= ENETC4_TXBD_FLAGS_F;
+		start++;
+	}
+
+	/*
+	 * Flush TX BDs to PoC so HW (non-cache-coherent i.MX95) can read the
+	 * descriptors from memory.  Same discipline as enetc_xmit_pkts_cacheable:
+	 * walk from the cache-line-aligned start of first_bd_idx to just past
+	 * the last written BD, one dcbf per 64-byte (4-BD) line.
+	 */
+	if (likely(start > 0)) {
+		int n = first_bd_idx & ~ENETC_BD_PER_CL_MASK;
+		int written = (i - n + bd_count) % bd_count;
+
+		if (written == 0)
+			written = bd_count;
+		written = (written + ENETC_BD_PER_CL_MASK) &
+			  ~ENETC_BD_PER_CL_MASK;
+
+		while (written > 0) {
+			dcbf((void *)ENETC_TXBD(*tx_ring, n));
+			n = (n + ENETC_BD_PER_CL) % bd_count;
+			written -= ENETC_BD_PER_CL;
+		}
+	}
+
+	enetc_clean_tx_ring(tx_ring);
+	tx_ring->next_to_use = i;
+	enetc_wr_reg(tx_ring->tcir, i);
+
+	return start;
+}
+
 int
 enetc_refill_rx_ring(struct enetc_bdr *rx_ring, const int buff_cnt)
 {
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 01/14] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 04/14] net/enetc: extend PF-VF link speed field to 8 bits Gagandeep Singh
                               ` (11 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

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 <g.singh@nxp.com>
---
 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     |  75 ++++++++
 drivers/net/enetc/enetc.h              |   5 +
 drivers/net/enetc/enetc4_ethdev.c      | 112 +++++++++++-
 drivers/net/enetc/enetc4_vf.c          |   1 +
 drivers/net/enetc/enetc_rxtx.c         | 234 +++++++++++++++++++++++++
 8 files changed, 427 insertions(+), 6 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 6bc462a93e..d42c04df4b 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -102,6 +102,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 6cd031f269..145ebbbb00 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -100,6 +100,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 {
@@ -319,12 +321,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 24c454fe28..925da3f68e 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -17,7 +17,8 @@ static uint64_t dev_rx_offloads_sup =
 	RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
 	RTE_ETH_RX_OFFLOAD_SCATTER |
-	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 =
@@ -302,6 +303,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;
@@ -512,22 +515,35 @@ 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;
+	if (ring_desc > MAX_BD_COUNT) {
+		ENETC_PMD_ERR("RSC ring_desc %u > MAX_BD_COUNT %u; "
+			      "reduce nb_desc to <= %u with RSC enabled",
+			      ring_desc, MAX_BD_COUNT, MAX_BD_COUNT / 2);
+		return -EINVAL;
+	}
 
-	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;
@@ -550,13 +566,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);
@@ -578,7 +611,9 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	struct enetc_eth_adapter *adapter =
 			ENETC_DEV_PRIVATE(data->dev_private);
 	uint64_t rx_offloads = data->dev_conf.rxmode.offloads;
+	uint32_t rsc_size;
 	bool keep_crc;
+	bool rsc_enable;
 
 	PMD_INIT_FUNC_TRACE();
 	if (nb_rx_desc > MAX_BD_COUNT)
@@ -595,6 +630,28 @@ 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;
+		}
+		if (adapter->hw.nc_mode) {
+			ENETC_PMD_ERR("RSC (LRO) is incompatible with nc=1 (non-cacheable mode)");
+			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;
@@ -613,6 +670,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;
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 0bb5a86dd8..8d3008843c 100644
--- a/drivers/net/enetc/enetc_rxtx.c
+++ b/drivers/net/enetc/enetc_rxtx.c
@@ -907,6 +907,240 @@ 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)
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 04/14] net/enetc: extend PF-VF link speed field to 8 bits
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (2 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
                               ` (10 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF message is only 16-bit wide, where the upper 8-bit is the
class_id and the lower 8-bit carries the message content. For the
link speed message, the lower 4-bit was previously occupied by the cookie
field, leaving only 4-bit for the speed code, which allows at most
15 distinct speed values.

As the NETC IP evolves and supports more and more link speeds, 15 speed
values are clearly insufficient. Since the cookie field is designed
for non-blocking messages and has no meaningful use in link speed
messages, remove it and expand the speed code field from 4-bit to 8-bit,
allowing up to 255 speed values (ENETC_SPEED_MAX = 0xff).

Instead of enumerating every speed value greater than 5Gbps
explicitly, introduce a formula-based approach:

	speed_code = (link_speed - 5000) / 1000 + ENETC_SPEED_5000

This removes the explicit enum entries for 10G, 25G, 50G and 100G,
and replaces the individual switch-case branches with a generic
implementation to get the speed, making it easy to support any
future high speed without modifying the enum or the switch statement.

For backward compatibility with a PF running an older kernel (before
6.18.37) that still uses the legacy 4-bit speed code / 4-bit cookie
message layout, add a "vf_link_legacy" devarg. When set to 1, the VF
extracts the message result from the upper 4 bits of the lower byte
and decodes speeds greater than 5Gbps using the legacy fixed class
codes (10G/25G/50G/100G). The default (0) uses the new 8-bit layout.

	Usage: -a <pci_addr>,vf_link_legacy=1

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |  11 ++
 doc/guides/rel_notes/release_26_11.rst |   3 +
 drivers/net/enetc/enetc.h              |  32 +++-
 drivers/net/enetc/enetc4_vf.c          | 202 +++++++++++++++++++++----
 4 files changed, 215 insertions(+), 33 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index e7f4348603..935cf90e6b 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -133,6 +133,17 @@ VF-specific devargs
 
     dpdk-testpmd -a 0000:00:01.0,enetc4_vsi_delay=10 -- -i
 
+``vf_link_legacy``
+  Select the legacy 4-bit PF-to-VF link speed code layout.
+  Set to ``1`` when the host PF is running a kernel older than v6.18.37.
+  Kernels before that release encode link speed in only 4 bits; without this
+  flag the driver interprets those codes as 8-bit values and reports incorrect
+  link speeds.
+
+  Usage example::
+
+    dpdk-testpmd -a 0000:00:01.0,vf_link_legacy=1 -- -i
+
 PF/Common devargs
 ~~~~~~~~~~~~~~~~~
 
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 0dd08e0259..ee4f455c67 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -63,6 +63,9 @@ 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.
+  * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
+    Users running a PF kernel older than 6.18.37 must pass ``vf_link_legacy=1``
+    as a device argument, otherwise link speed reporting will be incorrect.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 145ebbbb00..d6ae54a132 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -119,6 +119,10 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
+	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
+	 */
+	uint8_t vf_link_legacy;
 };
 
 /*
@@ -210,11 +214,29 @@ enum speed {
 	ENETC_SPEED_1000 = 0x5,
 	ENETC_SPEED_2500 = 0x6,
 	ENETC_SPEED_5000 = 0x7,
-	ENETC_SPEED_10G = 0x8,
-	ENETC_SPEED_25G = 0x9,
-	ENETC_SPEED_50G = 0xA,
-	ENETC_SPEED_100G = 0xB,
-	ENETC_SPEED_NOT_SUPPORTED = 0xF
+	/* Base speed class code used by the >5Gbps formula below */
+	/* Do not add enumeration values for any speed greater than
+	 * 5Gbps. For any speed greater than 5Gbps, its speed class
+	 * code should follow the formula below.
+	 *
+	 * SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+	 *
+	 * The unit of link_speed should be Mbps, the max SPEED
+	 * should <= ENETC_SPEED_MAX.
+	 */
+	ENETC_SPEED_MAX = 0xff,
+};
+
+/* Legacy speed class codes for backward compatibility with an older kernel PF
+ * (before 6.18.37) that encoded a 4-bit speed code and 4-bit cookie in the
+ * message's lower byte. Used only when the vf_link_legacy devarg is set.
+ */
+enum speed_legacy {
+	ENETC_SPEED_LEGACY_10G = 0x8,
+	ENETC_SPEED_LEGACY_25G = 0x9,
+	ENETC_SPEED_LEGACY_50G = 0xA,
+	ENETC_SPEED_LEGACY_100G = 0xB,
+	ENETC_SPEED_LEGACY_NOT_SUPPORTED = 0xF
 };
 
 /* PSI-VSI command header format */
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index be79a18a39..9c6070b6d4 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -43,6 +43,12 @@ enetc4_vf_get_devarg_nc(struct rte_eth_dev *dev)
 #define ENETC_BYTE_SIZE			8
 #define ENETC_MSB_BIT			0x8000
 
+/* Backward-compat devarg for a PF running a kernel before 6.18.37, which uses
+ * the legacy PF-to-VF link message layout (4-bit speed code + 4-bit cookie).
+ * Usage: -a <pci_addr>,vf_link_legacy=1
+ */
+#define ENETC_VF_LINK_LEGACY		"vf_link_legacy"
+
 uint16_t enetc_crc_table[ENETC_CRC_TABLE_SIZE];
 bool enetc_crc_gen;
 
@@ -101,6 +107,59 @@ enetc_crc_calc(uint16_t crc, const uint8_t *buffer, size_t len)
 	return crc;
 }
 
+static int
+parse_vf_link_legacy(const char *key __rte_unused, const char *value,
+		     void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *endptr;
+	unsigned long val;
+
+	if (!value || *value == '\0') {
+		ENETC_PMD_WARN("Empty value for devarg %s, ignoring",
+			       ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	errno = 0;
+	val = strtoul(value, &endptr, 0);
+	if (errno != 0 || *endptr != '\0' || val > 1) {
+		ENETC_PMD_WARN("Invalid value '%s' for devarg %s, expected 0 or 1",
+			       value, ENETC_VF_LINK_LEGACY);
+		return -EINVAL;
+	}
+
+	hw->vf_link_legacy = (uint8_t)val;
+
+	return 0;
+}
+
+static void
+enetc4_vf_get_devargs(struct rte_eth_dev *dev)
+{
+	struct rte_devargs *devargs;
+	struct rte_kvargs *kvlist;
+
+	devargs = dev->device->devargs;
+	if (!devargs)
+		return;
+
+	kvlist = rte_kvargs_parse(devargs->args, NULL);
+	if (!kvlist)
+		return;
+
+	if (rte_kvargs_count(kvlist, ENETC_VF_LINK_LEGACY)) {
+		if (rte_kvargs_process(kvlist, ENETC_VF_LINK_LEGACY,
+				       parse_vf_link_legacy, (void *)dev) < 0)
+			ENETC_PMD_WARN("Failed to parse devarg %s",
+				       ENETC_VF_LINK_LEGACY);
+	}
+
+	rte_kvargs_free(kvlist);
+}
+
 static int
 enetc4_vf_dev_infos_get(struct rte_eth_dev *dev,
 			struct rte_eth_dev_info *dev_info)
@@ -212,6 +271,7 @@ enetc4_msg_vsi_write_msg(struct enetc_hw *hw,
 static void
 enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgsr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -221,8 +281,15 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgsr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgsr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -231,6 +298,7 @@ enetc4_msg_vsi_reply_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *
 static void
 enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *reply_msg)
 {
+	struct enetc_eth_hw *hw = container_of(enetc_hw, struct enetc_eth_hw, hw);
 	int vsimsgrr;
 	int8_t class_id = 0;
 	uint8_t status = 0;
@@ -240,8 +308,15 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	/* Extracting 8 bits of message result in class_id */
 	class_id |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 8) & 0xff);
 
-	/* Extracting 4 bits of message result in status */
-	status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	/* Extracting message result in status. With an older kernel PF
+	 * (vf_link_legacy set) the lower byte holds a 4-bit cookie in the
+	 * low nibble and a 4-bit result in the high nibble, so extract the
+	 * upper 4 bits. Otherwise the full lower byte carries the result.
+	 */
+	if (hw->vf_link_legacy)
+		status |= ((ENETC_SIMSGSR_GET_MC(vsimsgrr) >> 4) & 0xf);
+	else
+		status |= (ENETC_SIMSGSR_GET_MC(vsimsgrr) & 0xff);
 
 	reply_msg->class_id = class_id;
 	reply_msg->status = status;
@@ -731,6 +806,8 @@ enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 static int
 enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused)
 {
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
 	struct enetc_psi_reply_msg *reply_msg;
 	struct rte_eth_link link;
 	int err;
@@ -809,28 +886,91 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 			link.link_speed = RTE_ETH_SPEED_NUM_5G;
 			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
 			break;
-		case ENETC_SPEED_10G:
-			link.link_speed = RTE_ETH_SPEED_NUM_10G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_25G:
-			link.link_speed = RTE_ETH_SPEED_NUM_25G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_50G:
-			link.link_speed = RTE_ETH_SPEED_NUM_50G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100G:
-			link.link_speed = RTE_ETH_SPEED_NUM_100G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_NOT_SUPPORTED:
-			ENETC_PMD_DEBUG("Speed not supported");
-			link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-			break;
 		default:
-			ENETC_PMD_ERR("Unknown speed status");
+			if (hw->vf_link_legacy) {
+				/* Legacy PF-to-VF message layout (older kernel
+				 * PF): speeds greater than 5Gbps are encoded
+				 * with fixed 4-bit class codes rather than the
+				 * formula below.
+				 */
+				switch (reply_msg->status) {
+				case ENETC_SPEED_LEGACY_10G:
+					link.link_speed = RTE_ETH_SPEED_NUM_10G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_25G:
+					link.link_speed = RTE_ETH_SPEED_NUM_25G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_50G:
+					link.link_speed = RTE_ETH_SPEED_NUM_50G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_100G:
+					link.link_speed = RTE_ETH_SPEED_NUM_100G;
+					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+					break;
+				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+					ENETC_PMD_DEBUG("Speed not supported");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				default:
+					ENETC_PMD_ERR("Unknown speed status");
+					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+					break;
+				}
+				break;
+			}
+
+			/* Any status reaching here is greater than
+			 * ENETC_SPEED_5000, as all values from 0x0 to
+			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
+			 * greater than 5Gbps are not enumerated and follow the
+			 * formula:
+			 *
+			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
+			 *
+			 * where link_speed is in Mbps. Reverse it here to get the
+			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
+			 *
+			 * Validate the computed value against the set of speeds
+			 * that the NETC IP is known to support (> 5Gbps).
+			 * An unrecognised code yields UNKNOWN rather than a
+			 * fabricated speed.
+			 */
+			switch ((reply_msg->status - ENETC_SPEED_5000)
+				* 1000 + 5000) {
+			case RTE_ETH_SPEED_NUM_10G:
+				link.link_speed = RTE_ETH_SPEED_NUM_10G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_25G:
+				link.link_speed = RTE_ETH_SPEED_NUM_25G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_40G:
+				link.link_speed = RTE_ETH_SPEED_NUM_40G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_50G:
+				link.link_speed = RTE_ETH_SPEED_NUM_50G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_100G:
+				link.link_speed = RTE_ETH_SPEED_NUM_100G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case RTE_ETH_SPEED_NUM_200G:
+				link.link_speed = RTE_ETH_SPEED_NUM_200G;
+				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			default:
+				ENETC_PMD_WARN("Unrecognized speed code 0x%x, "
+					       "reporting unknown",
+					       reply_msg->status);
+				link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			}
 			break;
 		}
 	} else {
@@ -839,10 +979,9 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	link.link_autoneg = 1;
-
 	rte_eth_linkstatus_set(dev, &link);
-
 	rte_free(reply_msg);
+
 	return 0;
 }
 
@@ -1421,6 +1560,12 @@ enetc4_vf_dev_init(struct rte_eth_dev *eth_dev)
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_populate();
 
+	/* Parse VF specific devargs (e.g. vf_link_legacy) before the first
+	 * link update so that PF-to-VF link messages are interpreted using
+	 * the correct (legacy or current) layout.
+	 */
+	enetc4_vf_get_devargs(eth_dev);
+
 	ENETC_PMD_DEBUG("port_id %d vendorID=0x%x deviceID=0x%x",
 			eth_dev->data->port_id, pci_dev->id.vendor_id,
 			pci_dev->id.device_id);
@@ -1514,5 +1659,6 @@ RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
 			      ENETC4_VSI_DELAY "=<uint> "
-			      ENETC4_NC_MEMORY "=<int>");
+			      ENETC4_NC_MEMORY "=<int> "
+			      ENETC_VF_LINK_LEGACY "=<0|1>");
 RTE_LOG_REGISTER_DEFAULT(enetc4_vf_logtype_pmd, NOTICE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 05/14] net/enetc: support firmware version get for VF
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (3 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 04/14] net/enetc: extend PF-VF link speed field to 8 bits Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 06/14] net/enetc: support registers dump Gagandeep Singh
                               ` (9 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the ``.fw_version_get`` eth_dev op for the ENETC4 VF PMD to
report the NETC IP revision.

The NETC IP major revision is read from the PCI revision ID register,
while the IP minor revision is retrieved from the PSI over VSI-PSI
messaging using the "Get IP version" command class (class ID 0xF0,
command ID 0x1). The version is reported in the "<major>.<minor>"
format.

The PSI firmware only implements the IP_MN command of this class, so
the major revision is obtained from the PCI revision ID, matching the
behaviour of the kernel PF/VF drivers.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |   1 +
 doc/guides/nics/features/enetc4.ini    |   1 +
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |  19 +++-
 drivers/net/enetc/enetc4_vf.c          | 139 +++++++++++++++++++++++++
 5 files changed, 159 insertions(+), 2 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 935cf90e6b..fa93f40b22 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,7 @@ Key functionality includes:
   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.
+- Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index aae398e210..eaf474e7ba 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -16,6 +16,7 @@ VLAN filter          = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
+FW version           = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index ee4f455c67..d212cce000 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -66,6 +66,7 @@ New Features
   * Extended the PF-to-VF link speed code field from 4-bit to 8-bit in ENETC4.
     Users running a PF kernel older than 6.18.37 must pass ``vf_link_legacy=1``
     as a device argument, otherwise link speed reporting will be incorrect.
+  * Added firmware version reporting for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index d6ae54a132..c4a55626f8 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -169,7 +169,8 @@ enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
-	ENETC_CLASS_ID_LINK_SPEED = 0x81
+	ENETC_CLASS_ID_LINK_SPEED = 0x81,
+	ENETC_CLASS_ID_GET_IP_VER = 0xF0
 };
 
 /* Enum for command IDs */
@@ -183,9 +184,18 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_LINK_STATUS = 0,
 	ENETC_CMD_ID_REGISTER_LINK_NOTIF = 1,
 	ENETC_CMD_ID_UNREGISTER_LINK_NOTIF = 2,
-	ENETC_CMD_ID_GET_LINK_SPEED = 0
+	ENETC_CMD_ID_GET_LINK_SPEED = 0,
+	/* Get IP version command IDs (class ID 0xF0) */
+	ENETC_CMD_ID_GET_IP_MJ = 0,
+	ENETC_CMD_ID_GET_IP_MN = 1,
+	ENETC_CMD_ID_GET_IP_INT = 2,
+	ENETC_CMD_ID_GET_IP_MNT = 3,
+	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* IP_VER value returned when the version is not available */
+#define ENETC_IP_VER_NOT_AVAILABLE	0xFF
+
 enum mac_addr_status {
 	ENETC_INVALID_MAC_ADDR = 0x0,
 	ENETC_DUPLICATE_MAC_ADDR = 0X1,
@@ -273,6 +283,11 @@ struct enetc_msg_cmd_get_link_speed {
 	struct enetc_msg_cmd_header header;
 };
 
+/* Get IP version command message format (class ID 0xF0) */
+struct enetc_msg_cmd_get_ip_ver {
+	struct enetc_msg_cmd_header header;
+};
+
 struct enetc_msg_cmd_set_vlan_promisc {
 	struct enetc_msg_cmd_header header;
 	uint8_t op;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 9c6070b6d4..41f915be18 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -3,6 +3,7 @@
  */
 
 #include <stdbool.h>
+#include <rte_bus_pci.h>
 #include <rte_kvargs.h>
 #include <rte_random.h>
 #include <dpaax_iova_table.h>
@@ -433,6 +434,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_MAC_FILTER:
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
+		case ENETC_CLASS_ID_GET_IP_VER:
 			break;
 		default:
 			err = -EIO;
@@ -796,6 +798,142 @@ enetc4_vf_get_link_speed(struct rte_eth_dev *dev, struct enetc_psi_reply_msg *re
 	return err;
 }
 
+/*
+ * Get the NETC IP minor revision (IP_MN) from the PSI using the 'Get IP
+ * version' command class (class ID 0xF0, cmd_id 0x1). In the reply, the
+ * low 8 bits of the return code carry the minor revision and the upper 8
+ * bits carry the class code (0xF0). The PSI only implements the IP_MN
+ * command of this class; the major revision comes from the PCI revision.
+ */
+static int
+enetc4_vf_get_ip_minor_revision(struct rte_eth_dev *dev, uint8_t *ip_mn)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_swbd *msg;
+	uint32_t msg_size;
+	uint16_t mc;
+	uint8_t class_id;
+	int vsimsgsr;
+	int err = 0;
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_cmd_get_ip_ver),
+				ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg");
+		rte_free(msg);
+		return -ENOMEM;
+	}
+
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	msg->size = msg_size;
+
+	/* COOKIE is 0 so that the command is executed as blocking on PSI */
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_GET_IP_VER,
+			ENETC_CMD_ID_GET_IP_MN, 0, 0, 0);
+
+	/* send the command and wait */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error");
+		goto end;
+	}
+
+	/*
+	 * For the IP version command class the class-specific field is
+	 * reused to carry the 8-bit version value in the lower byte of the
+	 * return code, so parse the full low byte here instead of using the
+	 * generic reply parser.
+	 */
+	vsimsgsr = enetc4_rd(enetc_hw, ENETC4_VSIMSGSR);
+	mc = ENETC_SIMSGSR_GET_MC(vsimsgsr);
+	class_id = (mc >> 8) & 0xff;
+
+	if (class_id != ENETC_CLASS_ID_GET_IP_VER) {
+		ENETC_PMD_ERR("Wrong reply message 0x%x", class_id);
+		err = -EIO;
+		goto end;
+	}
+
+	*ip_mn = mc & 0xff;
+	if (*ip_mn == ENETC_IP_VER_NOT_AVAILABLE) {
+		ENETC_PMD_DEBUG("IP minor revision not available");
+		err = -ENOTSUP;
+	}
+
+end:
+	/* free memory no longer required */
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	return err;
+}
+
+/*
+ * Retrieve the NETC firmware/IP version and format it as
+ * "<major>.<minor>". The IP major revision is taken from the PCI
+ * revision ID register, and the IP minor revision is fetched from the
+ * PSI via VSI-PSI messaging ('Get IP version' command class). This
+ * mirrors the behaviour of the kernel PF/VF drivers, where the PSI only
+ * implements the IP_MN command of the 0xF0 class.
+ */
+static int
+enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_size)
+{
+	struct rte_pci_device *pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
+	uint8_t ip_mj = 0, ip_mn = 0;
+	int ret;
+
+	PMD_INIT_FUNC_TRACE();
+
+	if (fw_version == NULL)
+		return -EINVAL;
+
+	/* IP major revision is exposed through the PCI revision ID */
+	ret = rte_pci_read_config(pci_dev, &ip_mj, sizeof(ip_mj),
+			RTE_PCI_REVISION_ID);
+	if (ret != sizeof(ip_mj)) {
+		ENETC_PMD_ERR("Failed to read PCI revision ID");
+		return -EIO;
+	}
+
+	/* IP minor revision is fetched from the PSI via VSI-PSI messaging.
+	 * If the PSI reports the version as unavailable, fall back to a
+	 * partial version string so the caller still gets useful output.
+	 */
+	ret = enetc4_vf_get_ip_minor_revision(dev, &ip_mn);
+	if (ret && ret != -ENOTSUP) {
+		ENETC_PMD_ERR("Failed to get NETC minor revision");
+		return ret;
+	}
+
+	if (fw_size == 0) {
+		if (ret == -ENOTSUP)
+			return snprintf(NULL, 0, "%u.unknown", ip_mj) + 1;
+		else
+			return snprintf(NULL, 0, "%u.%u", ip_mj, ip_mn) + 1;
+	}
+
+	if (ret == -ENOTSUP)
+		ret = snprintf(fw_version, fw_size, "%u.unknown", ip_mj);
+	else
+		ret = snprintf(fw_version, fw_size, "%u.%u", ip_mj, ip_mn);
+	if (ret < 0)
+		return -EINVAL;
+
+	ret += 1; /* add trailing '\0' */
+	if ((size_t)ret > fw_size)
+		return ret;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1354,6 +1492,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.fw_version_get       = enetc4_vf_fw_version_get,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.mac_addr_set         = enetc4_vf_set_mac_addr,
 	.mac_addr_add	      = enetc4_vf_mac_addr_add,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 06/14] net/enetc: support registers dump
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (4 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
                               ` (8 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement the .get_reg operation for the ENETC4 PF and VF PMDs to dump
the device registers via rte_eth_dev_get_reg_info.

The VF dumps the registers reachable by a virtual station interface
(VSI): the station interface and per-ring BD ring registers. Port
registers are not accessible by a VF.

The PF additionally dumps the port registers, which are only
accessible by the physical station interface (PSI).

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             |  1 +
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 12 +++++
 drivers/net/enetc/enetc4_ethdev.c      | 70 ++++++++++++++++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 62 +++++++++++++++++++++++
 6 files changed, 147 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index fa93f40b22..16389aee5b 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -60,6 +60,7 @@ Key functionality includes:
   requested. RSC requires the FCS to be stripped, so it cannot be combined
   with the KEEP_CRC Rx offload.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
+- Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index eaf474e7ba..6540a43909 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -17,6 +17,7 @@ RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
 FW version           = Y
+Registers dump       = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 CRC offload          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index d212cce000..6fece98f28 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -67,6 +67,7 @@ New Features
     Users running a PF kernel older than 6.18.37 must pass ``vf_link_legacy=1``
     as a device argument, otherwise link speed reporting will be incorrect.
   * Added firmware version reporting for the ENETC4 VF.
+  * Added register dump support for ENETC4 PF and VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index c4a55626f8..f0d64cfb29 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -395,6 +395,18 @@ enetc_bd_unused(struct enetc_bdr *bdr)
 	return bdr->bd_count + bdr->next_to_clean - bdr->next_to_use - 1;
 }
 
+/* Per-ring Tx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_txbdr_regs[] = {
+	ENETC_TBMR, ENETC_TBSR, ENETC_TBBAR0, ENETC_TBBAR1,
+	ENETC_TBCIR, ENETC_TBLENR,
+};
+
+/* Per-ring Rx BDR registers dumped by .get_reg (shared by PF and VF) */
+static const uint32_t enetc4_rxbdr_regs[] = {
+	ENETC_RBMR, ENETC_RBSR, ENETC_RBBSR, ENETC_RBCIR,
+	ENETC_RBBAR0, ENETC_RBBAR1, ENETC_RBPIR, ENETC_RBLENR,
+};
+
 /* CBDR prototypes */
 int enetc4_setup_cbdr(struct rte_eth_dev *dev, struct enetc_hw *hw,
 			int bd_count, struct netc_cbdr *cbdr);
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 925da3f68e..7a296a8d07 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1178,6 +1178,75 @@ enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 	return ptypes;
 }
 
+/* Station interface registers dumped by .get_reg */
+static const uint32_t enetc4_pf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR,
+};
+
+/* Port registers dumped by .get_reg (accessible only by the PF) */
+static const uint32_t enetc4_pf_port_regs[] = {
+	ENETC4_PMR, ENETC4_PSIPMMR, ENETC4_PMAR0, ENETC4_PMAR1,
+	ENETC4_PM_CMD_CFG(0), ENETC4_PM_MAXFRM(0), ENETC4_PM_IF_MODE(0),
+	ENETC4_PM_IF_STATUS(0),
+};
+
+/*
+ * Dump the PF-accessible registers: station interface, port and per-ring
+ * BD ring registers. When info->data is NULL, only the register count and
+ * width are reported so the caller can size its buffer.
+ */
+static int
+enetc4_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_pf_si_regs);
+	count += RTE_DIM(enetc4_pf_port_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_si_regs); i++)
+		*buf++ = enetc4_rd(enetc_hw, enetc4_pf_si_regs[i]);
+
+	for (i = 0; i < RTE_DIM(enetc4_pf_port_regs); i++)
+		*buf++ = enetc4_port_rd(enetc_hw, enetc4_pf_port_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc4_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = (uint32_t)hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 /*
  * The set of PCI devices this driver supports
  */
@@ -1196,6 +1265,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.link_update          = enetc4_link_update,
 	.stats_get            = enetc4_stats_get,
 	.stats_reset          = enetc4_stats_reset,
+	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 41f915be18..ec9a2e2bf8 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -934,6 +934,66 @@ enetc4_vf_fw_version_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_si
 	return 0;
 }
 
+/* VF station interface registers dumped by .get_reg */
+static const uint32_t enetc4_vf_si_regs[] = {
+	ENETC_SIMR, ENETC_SICAPR0, ENETC_SIPMAR0, ENETC_SIPMAR1,
+	ENETC4_SIROCT0, ENETC4_SIRFRM0, ENETC4_SITOCT0, ENETC4_SITFRM0,
+	ENETC4_SITDFCR, ENETC4_SIMSIVR, ENETC4_VSIIER, ENETC4_VSIIDR,
+	ENETC4_VSIMSGSR, ENETC4_VSIMSGRR,
+};
+
+/*
+ * Dump the VF-accessible registers. Only station interface and per-ring
+ * BD ring registers are reachable by a VF; port registers are not.
+ * When info->data is NULL, only the register count and width are
+ * reported so the caller can size its buffer.
+ */
+static int
+enetc4_vf_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t count, addr;
+	uint32_t *buf;
+	uint16_t i, j;
+
+	count = RTE_DIM(enetc4_vf_si_regs);
+	count += RTE_DIM(enetc4_txbdr_regs) * dev->data->nb_tx_queues;
+	count += RTE_DIM(enetc4_rxbdr_regs) * dev->data->nb_rx_queues;
+
+	if (regs->data == NULL) {
+		regs->length = count;
+		regs->width = sizeof(uint32_t);
+		return 0;
+	}
+
+	if (regs->length && regs->length < count)
+		return -ENOTSUP;
+
+	buf = regs->data;
+
+	for (i = 0; i < RTE_DIM(enetc4_vf_si_regs); i++)
+		*buf++ = enetc_rd(enetc_hw, enetc4_vf_si_regs[i]);
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_txbdr_regs); j++) {
+			addr = ENETC_BDR(TX, i, enetc4_txbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		for (j = 0; j < RTE_DIM(enetc4_rxbdr_regs); j++) {
+			addr = ENETC_BDR(RX, i, enetc4_rxbdr_regs[j]);
+			*buf++ = enetc_rd(enetc_hw, addr);
+		}
+	}
+
+	regs->version = (uint32_t)hw->device_id << 16 | hw->revision_id;
+
+	return 0;
+}
+
 static int
 enetc4_vf_link_update_dummy(struct rte_eth_dev *dev __rte_unused,
 			    int wait_to_complete __rte_unused)
@@ -1472,6 +1532,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.link_update	      = enetc4_vf_link_update_dummy,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
@@ -1493,6 +1554,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.stats_get            = enetc4_vf_stats_get,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
+	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
 	.mac_addr_set         = enetc4_vf_set_mac_addr,
 	.mac_addr_add	      = enetc4_vf_mac_addr_add,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 07/14] net/enetc: support ethtool ring parameters
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (5 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 06/14] net/enetc: support registers dump Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
                               ` (7 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The ethtool ring parameter query relies on the queue info ethdev
ops (.rxq_info_get/.txq_info_get) together with the descriptor
limits reported in dev_info. The PF already registered these ops,
but the VF ops tables did not, so ring parameter queries failed on
the VF.

Make enetc4_rxq_info_get() and enetc4_txq_info_get() non-static and
register them in both VF ops tables so ring parameters are reported
for both the PF and VF.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst | 1 +
 drivers/net/enetc/enetc.h              | 4 ++++
 drivers/net/enetc/enetc4_ethdev.c      | 4 ++--
 drivers/net/enetc/enetc4_vf.c          | 4 ++++
 4 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 6fece98f28..ec223a93ba 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -68,6 +68,7 @@ New Features
     as a device argument, otherwise link speed reporting will be incorrect.
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
+  * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index f0d64cfb29..4b563851b8 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -338,6 +338,10 @@ int enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx);
 void enetc4_tx_queue_release(struct rte_eth_dev *dev, uint16_t qid);
 const uint32_t *enetc4_supported_ptypes_get(struct rte_eth_dev *dev __rte_unused,
 			size_t *no_of_elements);
+void enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_rxq_info *qinfo);
+void enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
+			 struct rte_eth_txq_info *qinfo);
 
 /*
  * enetc4_vf function prototype
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 7a296a8d07..ad6ef84214 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -1127,7 +1127,7 @@ enetc4_tx_queue_stop(struct rte_eth_dev *dev, uint16_t qidx)
 	return 0;
 }
 
-static void
+void
 enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_rxq_info *qinfo)
 {
@@ -1141,7 +1141,7 @@ enetc4_rxq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 	qinfo->conf.rx_drop_en = 0;
 }
 
-static void
+void
 enetc4_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id,
 			struct rte_eth_txq_info *qinfo)
 {
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index ec9a2e2bf8..3da84941a2 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1539,10 +1539,12 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
@@ -1569,10 +1571,12 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
+	.rxq_info_get         = enetc4_rxq_info_get,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
 	.tx_queue_release     = enetc4_tx_queue_release,
+	.txq_info_get         = enetc4_txq_info_get,
 	.dev_supported_ptypes_get = enetc4_supported_ptypes_get,
 };
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 08/14] net/enetc: refresh link speed on VF link-up interrupt
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (6 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 09/14] net/enetc: support stats reset for VF Gagandeep Singh
                               ` (6 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The interrupt-driven link-status path (enetc4_process_psi_msg) only
updated link_status when a PSI-to-VSI notification arrived; it never
re-queried the link speed from the PF.  As a result, after a link-down
followed by a link-up the cached link_speed showed the speed from the
previous session rather than the freshly negotiated one.

1. Introducing enetc4_decode_link_speed() - a shared helper that maps
   a PF-to-VF speed status code to the corresponding RTE_ETH_SPEED_NUM_*
   / RTE_ETH_LINK_*_DUPLEX values, handling both the current and the
   legacy (vf_link_legacy) 4-bit message layout.

2. Calling enetc4_vf_get_link_speed() inside enetc4_process_psi_msg()
   on ENETC_LINK_UP so the negotiated speed is fetched from the PF and
   decoded immediately, before rte_eth_linkstatus_set() is called and
   the LSC callback is fired.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |   1 +
 drivers/net/enetc/enetc.h              |   2 +
 drivers/net/enetc/enetc4_ethdev.c      |   1 +
 drivers/net/enetc/enetc4_vf.c          | 257 +++++++++++++------------
 4 files changed, 141 insertions(+), 120 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index ec223a93ba..1765adc1bc 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -69,6 +69,7 @@ New Features
   * Added firmware version reporting for the ENETC4 VF.
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
+  * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 4b563851b8..185fd8c4ee 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -5,6 +5,7 @@
 #ifndef _ENETC_H_
 #define _ENETC_H_
 
+#include <pthread.h>
 #include <rte_time.h>
 #include <ethdev_pci.h>
 
@@ -123,6 +124,7 @@ struct enetc_eth_hw {
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
 	uint8_t vf_link_legacy;
+	pthread_mutex_t vsi_lock; /* serializes all VSI-PSI mailbox transactions */
 };
 
 /*
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index ad6ef84214..3d86072593 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -844,6 +844,7 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		if (dev->data->dev_conf.intr_conf.lsc != 0)
 			enetc4_vf_dev_intr(dev, false);
 		ret = enetc4_vf_dev_stop(dev);
+		pthread_mutex_destroy(&hw->vsi_lock);
 	} else {
 		ret = enetc4_dev_stop(dev);
 	}
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index 3da84941a2..da41b999cb 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -323,9 +323,130 @@ enetc4_msg_get_psi_msg(struct enetc_hw *enetc_hw, struct enetc_psi_reply_msg *re
 	reply_msg->status = status;
 }
 
+/* Forward declaration: defined later in this file */
+static int enetc4_vf_get_link_speed(struct rte_eth_dev *dev,
+				     struct enetc_psi_reply_msg *reply_msg);
+
+/*
+ * Decode a PF-to-VF link-speed status code into the link_speed and
+ * link_duplex fields of *link.  vf_link_legacy selects the older
+ * 4-bit code layout used by kernel PFs before v6.18.37.
+ */
+static void
+enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
+			 struct rte_eth_link *link)
+{
+	switch (status) {
+	case ENETC_SPEED_UNKNOWN:
+		ENETC_PMD_DEBUG("Speed unknown");
+		link->link_speed = RTE_ETH_SPEED_NUM_NONE;
+		break;
+	case ENETC_SPEED_10_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_10_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_10M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_100_HALF_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
+		break;
+	case ENETC_SPEED_100_FULL_DUPLEX:
+		link->link_speed = RTE_ETH_SPEED_NUM_100M;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_1000:
+		link->link_speed = RTE_ETH_SPEED_NUM_1G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_2500:
+		link->link_speed = RTE_ETH_SPEED_NUM_2_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	case ENETC_SPEED_5000:
+		link->link_speed = RTE_ETH_SPEED_NUM_5G;
+		link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+		break;
+	default:
+		if (vf_link_legacy) {
+			/* Legacy PF-to-VF message layout (older kernel PF):
+			 * speeds above 5Gbps use fixed 4-bit class codes.
+			 */
+			switch (status) {
+			case ENETC_SPEED_LEGACY_10G:
+				link->link_speed = RTE_ETH_SPEED_NUM_10G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_25G:
+				link->link_speed = RTE_ETH_SPEED_NUM_25G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_50G:
+				link->link_speed = RTE_ETH_SPEED_NUM_50G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_100G:
+				link->link_speed = RTE_ETH_SPEED_NUM_100G;
+				link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+				break;
+			case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
+				ENETC_PMD_DEBUG("Speed not supported");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			default:
+				ENETC_PMD_ERR("Unknown speed status");
+				link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+				break;
+			}
+			break;
+		}
+		/* Any status here is > ENETC_SPEED_5000. Validate against
+		 * the set of speeds that the NETC IP is known to support.
+		 * An unrecognised code yields UNKNOWN rather than a
+		 * fabricated speed.
+		 */
+		switch ((status - ENETC_SPEED_5000) * 1000 + 5000) {
+		case RTE_ETH_SPEED_NUM_10G:
+			link->link_speed = RTE_ETH_SPEED_NUM_10G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_25G:
+			link->link_speed = RTE_ETH_SPEED_NUM_25G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_40G:
+			link->link_speed = RTE_ETH_SPEED_NUM_40G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_50G:
+			link->link_speed = RTE_ETH_SPEED_NUM_50G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_100G:
+			link->link_speed = RTE_ETH_SPEED_NUM_100G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		case RTE_ETH_SPEED_NUM_200G:
+			link->link_speed = RTE_ETH_SPEED_NUM_200G;
+			link->link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
+			break;
+		default:
+			ENETC_PMD_WARN("Unrecognized speed code 0x%x, "
+				       "reporting unknown", status);
+			link->link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
+			break;
+		}
+		break;
+	}
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
 	int ret = 0;
@@ -344,6 +465,15 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		case ENETC_LINK_UP:
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Re-query speed from PF so the cached value reflects
+			 * the current negotiated speed after link-up.
+			 */
+			memset(msg, 0, sizeof(*msg));
+			if (!enetc4_vf_get_link_speed(eth_dev, msg) &&
+			    msg->class_id == ENETC_CLASS_ID_LINK_SPEED)
+				enetc4_decode_link_speed(msg->status,
+							hw->vf_link_legacy,
+							&link);
 			break;
 		case ENETC_LINK_DOWN:
 			ENETC_PMD_DEBUG("Link is down");
@@ -379,6 +509,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 	int err = 0;
 	int vsimsgsr;
 
+	pthread_mutex_lock(&hw->vsi_lock);
 	enetc4_msg_vsi_write_msg(enetc_hw, msg);
 
 	do {
@@ -390,11 +521,13 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 
 	if (!timeout) {
 		ENETC_PMD_ERR("Message not processed by PSI");
+		pthread_mutex_unlock(&hw->vsi_lock);
 		return -ETIMEDOUT;
 	}
 	/* check for message delivery error */
 	if (vsimsgsr & ENETC4_VSIMSGSR_MS) {
 		ENETC_PMD_ERR("Transfer error when copying the data");
+		pthread_mutex_unlock(&hw->vsi_lock);
 		return -EIO;
 	}
 
@@ -441,6 +574,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		}
 	}
 
+	pthread_mutex_unlock(&hw->vsi_lock);
 	return err;
 }
 
@@ -1051,126 +1185,8 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_SPEED) {
-		switch (reply_msg->status) {
-		case ENETC_SPEED_UNKNOWN:
-			ENETC_PMD_DEBUG("Speed unknown");
-			link.link_speed = RTE_ETH_SPEED_NUM_NONE;
-			break;
-		case ENETC_SPEED_10_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_10_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_10M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_100_HALF_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_HALF_DUPLEX;
-			break;
-		case ENETC_SPEED_100_FULL_DUPLEX:
-			link.link_speed = RTE_ETH_SPEED_NUM_100M;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_1000:
-			link.link_speed = RTE_ETH_SPEED_NUM_1G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_2500:
-			link.link_speed = RTE_ETH_SPEED_NUM_2_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		case ENETC_SPEED_5000:
-			link.link_speed = RTE_ETH_SPEED_NUM_5G;
-			link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-			break;
-		default:
-			if (hw->vf_link_legacy) {
-				/* Legacy PF-to-VF message layout (older kernel
-				 * PF): speeds greater than 5Gbps are encoded
-				 * with fixed 4-bit class codes rather than the
-				 * formula below.
-				 */
-				switch (reply_msg->status) {
-				case ENETC_SPEED_LEGACY_10G:
-					link.link_speed = RTE_ETH_SPEED_NUM_10G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_25G:
-					link.link_speed = RTE_ETH_SPEED_NUM_25G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_50G:
-					link.link_speed = RTE_ETH_SPEED_NUM_50G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_100G:
-					link.link_speed = RTE_ETH_SPEED_NUM_100G;
-					link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-					break;
-				case ENETC_SPEED_LEGACY_NOT_SUPPORTED:
-					ENETC_PMD_DEBUG("Speed not supported");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				default:
-					ENETC_PMD_ERR("Unknown speed status");
-					link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-					break;
-				}
-				break;
-			}
-
-			/* Any status reaching here is greater than
-			 * ENETC_SPEED_5000, as all values from 0x0 to
-			 * ENETC_SPEED_5000 are handled by the cases above. Speeds
-			 * greater than 5Gbps are not enumerated and follow the
-			 * formula:
-			 *
-			 *   SPEED = (link_speed - 5000) / 1000 + ENETC_SPEED_5000
-			 *
-			 * where link_speed is in Mbps. Reverse it here to get the
-			 * actual link speed (RTE_ETH_SPEED_NUM_* values are in Mbps).
-			 *
-			 * Validate the computed value against the set of speeds
-			 * that the NETC IP is known to support (> 5Gbps).
-			 * An unrecognised code yields UNKNOWN rather than a
-			 * fabricated speed.
-			 */
-			switch ((reply_msg->status - ENETC_SPEED_5000)
-				* 1000 + 5000) {
-			case RTE_ETH_SPEED_NUM_10G:
-				link.link_speed = RTE_ETH_SPEED_NUM_10G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_25G:
-				link.link_speed = RTE_ETH_SPEED_NUM_25G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_40G:
-				link.link_speed = RTE_ETH_SPEED_NUM_40G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_50G:
-				link.link_speed = RTE_ETH_SPEED_NUM_50G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_100G:
-				link.link_speed = RTE_ETH_SPEED_NUM_100G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			case RTE_ETH_SPEED_NUM_200G:
-				link.link_speed = RTE_ETH_SPEED_NUM_200G;
-				link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX;
-				break;
-			default:
-				ENETC_PMD_WARN("Unrecognized speed code 0x%x, "
-					       "reporting unknown",
-					       reply_msg->status);
-				link.link_speed = RTE_ETH_SPEED_NUM_UNKNOWN;
-				break;
-			}
-			break;
-		}
+		enetc4_decode_link_speed(reply_msg->status,
+					 hw->vf_link_legacy, &link);
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
@@ -1741,6 +1757,7 @@ enetc4_vf_dev_init(struct rte_eth_dev *eth_dev)
 	}
 
 	enetc4_dev_hw_init(eth_dev);
+	pthread_mutex_init(&hw->vsi_lock, NULL);
 
 	hw->nc_mode = 0;
 	enetc4_vf_get_devarg_nc(eth_dev);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 09/14] net/enetc: support stats reset for VF
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (7 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
                               ` (5 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The NETC SI-level hardware counters (SIROCT0, SIRFRM0, SITOCT0,
SITFRM0, SITDFCR) are read-only for a VF and cannot be directly
zeroed. No VSI-PSI command class exists for stats reset, and using
FLR/soft-reset to clear counters is not reliable due to a known
hardware erratum on some NETC platforms.

Implement stats_reset for the enetc4 VF PMD using a software
snapshot/delta approach: on stats_reset, the current HW counter
values are captured as a baseline in a new per-device
enetc4_vf_stats_saved struct. stats_get then reports the delta
(current_hw_value - saved_baseline), so counters appear to start
from zero after each reset call. Per-ring software Rx error
accumulators (ierrors) are zeroed directly on reset.

Register the stats_reset callback in both VF ops tables
(enetc4_vf_ops and enetc4_vf_ops_no_vsi_m) so the feature is
available regardless of whether the VSI messaging path is enabled.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              | 17 +++++++
 drivers/net/enetc/enetc4_vf.c          | 61 +++++++++++++++++++++++---
 3 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 1765adc1bc..a42577c681 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -70,6 +70,7 @@ New Features
   * Added register dump support for ENETC4 PF and VF.
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
+  * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 185fd8c4ee..96e18028a6 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -105,6 +105,21 @@ struct enetc_bdr {
 	uint8_t rsc_enable;
 };
 
+/*
+ * Saved SI counter baseline for VF stats reset. Since the SI-level
+ * hardware counters (SIROCT0, SIRFRM0, SITOCT0, SITFRM0, SITDFCR)
+ * are read-only for a VF and cannot be directly zeroed, stats_reset
+ * captures the current counter values as a baseline. stats_get then
+ * reports the delta: current_hw_value - baseline.
+ */
+struct enetc4_vf_stats_saved {
+	uint64_t ipackets;
+	uint64_t opackets;
+	uint64_t ibytes;
+	uint64_t obytes;
+	uint64_t oerrors;
+};
+
 struct enetc_eth_hw {
 	struct rte_eth_dev *ndev;
 	struct enetc_hw hw;
@@ -125,6 +140,8 @@ struct enetc_eth_hw {
 	 */
 	uint8_t vf_link_legacy;
 	pthread_mutex_t vsi_lock; /* serializes all VSI-PSI mailbox transactions */
+	/* Baseline snapshot for VF stats reset (software delta approach). */
+	struct enetc4_vf_stats_saved vf_stats_saved;
 };
 
 /*
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index da41b999cb..fb777bbeac 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -224,15 +224,64 @@ enetc4_vf_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
 	uint8_t i;
 
 	PMD_INIT_FUNC_TRACE();
-	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
-	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
-	stats->ibytes = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
-	stats->obytes = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
-	stats->oerrors = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/*
+	 * The SI-level counters are read-only for a VF; they cannot be
+	 * zeroed directly. Instead, stats_reset captures a baseline
+	 * snapshot, and stats_get reports the delta so that the reported
+	 * values appear to start from zero after each reset call.
+	 */
+	stats->ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0) -
+			  hw->vf_stats_saved.ipackets;
+	stats->opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0) -
+			  hw->vf_stats_saved.opackets;
+	stats->ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0) -
+			  hw->vf_stats_saved.ibytes;
+	stats->obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0) -
+			  hw->vf_stats_saved.obytes;
+	stats->oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR) -
+			  hw->vf_stats_saved.oerrors;
+
 	for (i = 0; i < dev->data->nb_rx_queues; i++) {
 		rx_ring = dev->data->rx_queues[i];
 		stats->ierrors += rx_ring->ierrors;
 	}
+
+	return 0;
+}
+
+/*
+ * Reset VF statistics by capturing a new baseline snapshot of the
+ * SI-level hardware counters. Because those counters are read-only
+ * for a VF (hardware erratum prevents reliable clear via FLR/soft
+ * reset too), the driver uses a software delta approach: every
+ * stats_get call reports current_hw_value - saved_baseline.
+ */
+static int
+enetc4_vf_stats_reset(struct rte_eth_dev *dev)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_bdr *rx_ring;
+	uint8_t i;
+
+	PMD_INIT_FUNC_TRACE();
+
+	/* Snapshot current HW SI counter values as the new zero baseline. */
+	hw->vf_stats_saved.ipackets = enetc4_rd(enetc_hw, ENETC4_SIRFRM0);
+	hw->vf_stats_saved.opackets = enetc4_rd(enetc_hw, ENETC4_SITFRM0);
+	hw->vf_stats_saved.ibytes   = enetc4_rd(enetc_hw, ENETC4_SIROCT0);
+	hw->vf_stats_saved.obytes   = enetc4_rd(enetc_hw, ENETC4_SITOCT0);
+	hw->vf_stats_saved.oerrors  = enetc4_rd(enetc_hw, ENETC4_SITDFCR);
+
+	/* Reset the per-ring software Rx error accumulators. */
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		rx_ring = dev->data->rx_queues[i];
+		if (rx_ring)
+			rx_ring->ierrors = 0;
+	}
+
 	return 0;
 }
 
@@ -1547,6 +1596,7 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.get_reg              = enetc4_vf_get_regs,
 	.mtu_set              = enetc4_vf_mtu_set,
@@ -1570,6 +1620,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.dev_stop             = enetc4_vf_dev_stop,
 	.dev_close            = enetc4_dev_close,
 	.stats_get            = enetc4_vf_stats_get,
+	.stats_reset          = enetc4_vf_stats_reset,
 	.dev_infos_get        = enetc4_vf_dev_infos_get,
 	.fw_version_get       = enetc4_vf_fw_version_get,
 	.get_reg              = enetc4_vf_get_regs,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 10/14] net/enetc4: add per-queue Rx interrupt support for VF
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (8 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 09/14] net/enetc: support stats reset for VF Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
                               ` (4 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add MSI-X per-queue Rx interrupt support to the ENETC4 VF PMD on the
cacheable (default) Rx path. This allows applications such as l3fwd-power
to block in epoll_wait when no traffic is present, reducing CPU utilization
to near zero.

MSI-X vector 0 is reserved for the PSI-to-VSI mailbox. Rx queue i is
mapped to MSI-X vector i + 1 via ENETC_SIMSIRRV. Per-ring coalescing is
enabled with ICPT=1 so the first arriving packet fires the interrupt
immediately. The SIRXIDR W1C detect bit is cleared before re-arming
ENETC_RBIER to prevent spurious interrupts after traffic stops.

The interrupt infrastructure (rte_intr_efd_enable +
rte_intr_vec_list_alloc) is set up before rte_intr_enable() so that
vfio-pci can wire each MSI-X vector to its eventfd when programming
the MSI-X table.

enetc4_dev_configure() is updated to trigger interrupt setup when
intr_conf.rxq is set (not only when intr_conf.lsc is set), as applications
like l3fwd-power set intr_conf.rxq without setting lsc.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             | 65 ++++++++++++++++++++
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc4_hw.h     |  2 +
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      |  9 +--
 drivers/net/enetc/enetc4_vf.c          | 84 +++++++++++++++++++++++++-
 7 files changed, 158 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 16389aee5b..2b73916682 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -59,6 +59,10 @@ Key functionality includes:
   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.
+- Per-queue Rx interrupts on VFs (cacheable Rx path only), enabling
+  interrupt-driven receive with ``vfio-pci``. Applications set
+  ``intr_conf.rxq = 1`` in ``rte_eth_conf`` to activate this feature.
+  See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
 
@@ -167,3 +171,64 @@ PF/Common devargs
   Usage example::
 
     dpdk-testpmd -a 0000:00:00.0,nc=1 -- -i
+
+
+Rx Interrupt Mode (VF)
+----------------------
+
+The ENETC4 VF PMD supports per-queue MSI-X Rx interrupts on the cacheable
+(default) Rx path. This allows applications to block in ``epoll_wait``
+instead of busy-polling, reducing CPU utilization when traffic is absent.
+
+**MSI-X vector assignment**
+
+ENETC4 VF MSI-X vector 0 is reserved for the PSI-to-VSI mailbox interrupt
+(link status notifications). Rx queue ``i`` is mapped to vector ``i + 1``.
+The driver allocates all required eventfds before calling
+``rte_intr_enable()`` so that ``vfio-pci`` can wire each MSI-X vector to
+its eventfd when it programs the MSI-X table.
+
+**Kernel and driver requirements**
+
+- ``vfio-pci`` kernel module with no-IOMMU mode enabled (no SMMU required).
+- The non-cacheable memory mode (``nc=1`` devarg) does **not** support
+  Rx interrupts and returns ``-ENOTSUP`` from ``rx_queue_intr_enable``.
+
+**Host setup**
+
+.. code-block:: console
+
+   # Enable vfio-pci no-IOMMU mode (if SMMU is not available)
+   modprobe vfio enable_unsafe_noiommu_mode=1
+   modprobe vfio-pci
+
+   # Bind the VF to vfio-pci
+   echo vfio-pci > /sys/bus/pci/devices/<vf_pci_addr>/driver_override
+   echo <vf_pci_addr> > /sys/bus/pci/drivers_probe
+
+**Running l3fwd-power with a single queue and core**
+
+The ``l3fwd-power`` sample application demonstrates interrupt-driven Rx.
+It sets ``intr_conf.rxq = 1`` in ``rte_eth_conf``, which triggers the VF
+interrupt setup in the driver. The ``--vfio-intr=msix`` EAL flag instructs
+DPDK to use MSI-X eventfds for interrupt signalling.
+
+.. code-block:: console
+
+   ./dpdk-l3fwd-power -l 0-1 -n 1 --vfio-intr=msix \
+       -a <vf_pci_addr> -- \
+       -p 0x1 --config="(0,0,1)" --no-numa --interrupt-only
+
+Where:
+
+- ``-l 0-1`` assigns the main thread to core 0 and the forwarding lcore to
+  core 1.
+- ``-a <vf_pci_addr>`` specifies the VF PCI address (e.g. ``0000:01:00.1``).
+- ``--config="(0,0,1)"`` maps port 0, queue 0 to lcore 1.
+- ``--interrupt-only`` enables pure interrupt mode (no busy-poll fallback).
+
+With no incoming traffic the forwarding lcore sleeps in ``epoll_wait``;
+CPU utilization drops to near zero. On the first arriving packet the MSI-X
+interrupt fires, the lcore wakes, drains the ring, disables the interrupt,
+processes the burst, then re-enables and re-arms the interrupt before
+returning to sleep.
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 6540a43909..005c64a3ca 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -5,6 +5,7 @@
 ;
 [Features]
 Link status event    = Y
+Rx interrupt         = Y
 Speed capabilities   = Y
 Link status          = Y
 LRO                  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index a42577c681..c182495118 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -71,6 +71,7 @@ New Features
   * Added ring parameters support for the ENETC4 VF (rxq_info_get / txq_info_get).
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
+  * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc4_hw.h b/drivers/net/enetc/base/enetc4_hw.h
index d42c04df4b..dff6b8ddf2 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -249,6 +249,8 @@ struct enetc_rx_bd_ext {
 #define ENETC4_VSIIDR            0xA08
 #define ENETC4_VSIIER_MRIE       BIT(9)
 #define ENETC4_SI_INT_IDX        0
+/* MSI-X vector base for per-Rx-queue interrupts; vector 0 is the mailbox. */
+#define ENETC4_VF_RX_VEC_BASE    1
 
 /* VSI Registers */
 #define ENETC4_VSIMSGSR  0x204   /* RO */
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 96e18028a6..9ef493888c 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -135,6 +135,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
+	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
 	 * for PF kernel versions before 6.18.37. Set via vf_link_legacy devarg.
 	 */
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 3d86072593..53d201d62b 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -841,7 +841,8 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		return 0;
 
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0)
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0)
 			enetc4_vf_dev_intr(dev, false);
 		ret = enetc4_vf_dev_stop(dev);
 		pthread_mutex_destroy(&hw->vsi_lock);
@@ -964,12 +965,12 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	if (hw->device_id != ENETC4_DEV_ID_VF)
 		enetc4_port_wr(enetc_hw, ENETC4_PARCSCR, checksum);
 
-	/* Enable interrupts */
 	if (hw->device_id == ENETC4_DEV_ID_VF) {
-		if (dev->data->dev_conf.intr_conf.lsc != 0) {
+		if (dev->data->dev_conf.intr_conf.lsc != 0 ||
+		    dev->data->dev_conf.intr_conf.rxq != 0) {
 			ret = enetc4_vf_dev_intr(dev, true);
 			if (ret)
-				ENETC_PMD_WARN("Failed to setup link interrupts");
+				ENETC_PMD_WARN("Failed to setup VF interrupts: %d", ret);
 		}
 	}
 
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index fb777bbeac..b1e7162321 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -1588,6 +1588,52 @@ static const struct rte_pci_id pci_vf_id_enetc4_map[] = {
 	{ .vendor_id = 0, /* sentinel */ },
 };
 
+static int
+enetc4_vf_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t vec;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return -ENOTSUP;
+
+	vec = queue_id + ENETC4_VF_RX_VEC_BASE;
+
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), vec);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR1, 0);
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC4_RBICR0,
+			ENETC4_RBICR0_ICEN | ENETC4_RBICR0_ICPT(1));
+	enetc_wr(enetc_hw, ENETC_SIRXIDR, BIT(queue_id));
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, ENETC_RBIER_RXTIE);
+
+	return 0;
+}
+
+static int
+enetc4_vf_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+
+	if (hw->nc_mode)
+		return -ENOTSUP;
+
+	if (!hw->rxq_intr_en)
+		return 0;
+
+	enetc4_rxbdr_wr(enetc_hw, queue_id, ENETC_RBIER, 0);
+	enetc_wr(enetc_hw, ENETC_SIMSIRRV(queue_id), 0);
+
+	return 0;
+}
+
 /* Features supported by this driver */
 /* ops table used when VSI messaging is disabled */
 static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
@@ -1606,6 +1652,8 @@ static const struct eth_dev_ops enetc4_vf_ops_no_vsi_m = {
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
 	.rxq_info_get         = enetc4_rxq_info_get,
+	.rx_queue_intr_enable  = enetc4_vf_rx_queue_intr_enable,
+	.rx_queue_intr_disable = enetc4_vf_rx_queue_intr_disable,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
@@ -1639,6 +1687,8 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.rx_queue_stop        = enetc4_rx_queue_stop,
 	.rx_queue_release     = enetc4_rx_queue_release,
 	.rxq_info_get         = enetc4_rxq_info_get,
+	.rx_queue_intr_enable  = enetc4_vf_rx_queue_intr_enable,
+	.rx_queue_intr_disable = enetc4_vf_rx_queue_intr_disable,
 	.tx_queue_setup       = enetc4_tx_queue_setup,
 	.tx_queue_start       = enetc4_tx_queue_start,
 	.tx_queue_stop        = enetc4_tx_queue_stop,
@@ -1885,6 +1935,35 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		/* Vector index 0 */
 		enetc_wr(enetc_hw, ENETC4_SIMSIVR, ENETC4_SI_INT_IDX);
 
+		if (rte_intr_cap_multiple(intr_handle) &&
+		    eth_dev->data->nb_rx_queues > 0) {
+			uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+			uint16_t i;
+
+			ret = rte_intr_efd_enable(intr_handle,
+					nb_rx + ENETC4_VF_RX_VEC_BASE);
+			if (ret) {
+				ENETC_PMD_WARN("Failed to enable per-queue Rx eventfds: %d",
+					       ret);
+				ret = 0;
+				hw->rxq_intr_en = 0;
+			} else {
+				ret = rte_intr_vec_list_alloc(intr_handle,
+						"enetc4_vf_rx_intr", nb_rx);
+				if (ret) {
+					ENETC_PMD_WARN("Failed to alloc intr vec list: %d",
+						       ret);
+					rte_intr_efd_disable(intr_handle);
+					hw->rxq_intr_en = 0;
+				} else {
+					for (i = 0; i < nb_rx; i++)
+						rte_intr_vec_list_index_set(intr_handle, i,
+							i + ENETC4_VF_RX_VEC_BASE);
+					hw->rxq_intr_en = 1;
+				}
+			}
+		}
+
 		/* enable uio/vfio intr/eventfd mapping */
 		ret = rte_intr_enable(intr_handle);
 		if (ret) {
@@ -1908,10 +1987,13 @@ enetc4_vf_dev_intr(struct rte_eth_dev *eth_dev, bool enable)
 		ENETC_PMD_WARN("Failed to un-register link notification %d", ret);
 disable:
 	enetc_vf_enable_mr_int(enetc_hw, false);
+	hw->rxq_intr_en = 0;
 	ret = rte_intr_disable(intr_handle);
 	if (ret)
 		ENETC_PMD_WARN("Failed to disable INTR %d", ret);
 intr_enable_fail:
+	rte_intr_vec_list_free(intr_handle);
+	rte_intr_efd_disable(intr_handle);
 	rte_intr_callback_unregister(intr_handle,
 			enetc4_dev_interrupt_handler, eth_dev);
 
@@ -1927,7 +2009,7 @@ static struct rte_pci_driver rte_enetc4_vf_pmd = {
 
 RTE_PMD_REGISTER_PCI(net_enetc4_vf, rte_enetc4_vf_pmd);
 RTE_PMD_REGISTER_PCI_TABLE(net_enetc4_vf, pci_vf_id_enetc4_map);
-RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic");
+RTE_PMD_REGISTER_KMOD_DEP(net_enetc4_vf, "* igb_uio | uio_pci_generic | vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4_vf,
 			      ENETC4_VSI_DISABLE "=<any> "
 			      ENETC4_VSI_TIMEOUT "=<uint> "
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 11/14] net/enetc4: add SI-based port VLAN insertion and removal
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (9 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
                               ` (3 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Implement hardware VLAN tag insertion on Tx and removal on Rx
via the per-SI VLAN isolation (pvid) mechanism on ENETC4.

On a PF, the driver writes directly to PSIaVLANR(0) (VID + enable
bit) and PSIaCFGR0(0) (SIVIE for Tx insertion, VTE for Rx removal,
SIVC_CVLAN to allow C-VLAN 0x8100 TPID).

On a privileged VF, the request is forwarded to the kernel PF via
the VSI-PSI mailbox using a new command class 0x24 (SI VLAN
isolation, cmd ID 0x1). The kernel PF handler programs the same
registers on behalf of the VF's station interface. The class 0x24
reply is added to the pass-through list in enetc4_msg_vsi_send() so
it is handled correctly alongside the existing command classes

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 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     | 14 +++++
 drivers/net/enetc/enetc.h              | 23 +++++++
 drivers/net/enetc/enetc4_ethdev.c      | 39 ++++++++++++
 drivers/net/enetc/enetc4_vf.c          | 83 ++++++++++++++++++++++++++
 7 files changed, 165 insertions(+)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 2b73916682..262e2fe703 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -65,6 +65,10 @@ Key functionality includes:
   See `Rx Interrupt Mode (VF)`_ for setup details.
 - Firmware version: The NETC IP version is reported via ``rte_eth_dev_fw_version_get``.
 - Registers dump: The station interface, port (PF only) and BD ring registers are dumped via ``rte_eth_dev_get_reg_info``.
+- SI-based port VLAN (pvid): Hardware VLAN tag insertion on Tx and removal on Rx, configured
+  via ``rte_eth_dev_set_vlan_pvid``. On a PF the registers are written directly; on a privileged
+  VF the request is forwarded to the kernel PF through the VSI-PSI mailbox (class 0x24).
+  Use the testpmd command ``tx_vlan set pvid <port_id> <vlan_id> on|off`` to enable or disable.
 
 
 Prerequisites
diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 005c64a3ca..01b0dc5b80 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
 Basic stats          = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c182495118..190d77ecee 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -72,6 +72,7 @@ New Features
   * Refreshed VF link speed on the link-up interrupt in the ENETC4 VF driver.
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
+  * Added SI-based port VLAN insertion (Tx) and removal (Rx) 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 dff6b8ddf2..cddfa6e0ae 100644
--- a/drivers/net/enetc/base/enetc4_hw.h
+++ b/drivers/net/enetc/base/enetc4_hw.h
@@ -276,6 +276,20 @@ struct enetc_rx_bd_ext {
 #define ENETC4_SICTR0		0x18
 #define ENETC4_SICTR1		0x1c
 
+/* PSI SI VLAN register: per-SI VLAN tag for insertion/removal */
+#define ENETC4_PSIVLANR(a)		((a) * 0x80 + 0x2008)
+#define ENETC4_PSIVLANR_E		BIT(31)  /* enable SI VLAN processing */
+#define ENETC4_PSIVLANR_VTEA		BIT(30)  /* 0=strip tag, 1=zero VID */
+#define ENETC4_PSIVLANR_PCP(v)		(((uint32_t)(v) & 0x7) << 13)
+#define ENETC4_PSIVLANR_DEI		BIT(12)
+#define ENETC4_PSIVLANR_VID(v)		((uint32_t)(v) & 0xfff)
+
+/* PSI SI configuration register 0 */
+#define ENETC4_PSICFGR0(a)		((a) * 0x80 + 0x2010)
+#define ENETC4_PSICFGR0_SIVC_CVLAN	BIT(24)  /* allow C-VLAN 0x8100 */
+#define ENETC4_PSICFGR0_SIVIE		BIT(14)  /* SI VLAN insertion enable */
+#define ENETC4_PSICFGR0_VTE		BIT(12)  /* SI VLAN removal enable */
+
 /* general register accessors */
 #define enetc4_rd_reg(reg)	rte_read32((void *)(reg))
 #define enetc4_wr_reg(reg, val)  rte_write32((val), (void *)(reg))
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 9ef493888c..8189dc3ea8 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -188,6 +188,8 @@ struct enetc_eth_adapter {
 enum enetc_msg_cmd_class_id {
 	ENETC_CLASS_ID_MAC_FILTER = 0x20,
 	ENETC_CLASS_ID_VLAN_FILTER = 0x21,
+	/* Configure SI VLAN isolation (insert / remove) */
+	ENETC_CLASS_ID_SI_VLAN_ISO = 0x24,
 	ENETC_CLASS_ID_LINK_STATUS = 0x80,
 	ENETC_CLASS_ID_LINK_SPEED = 0x81,
 	ENETC_CLASS_ID_GET_IP_VER = 0xF0
@@ -213,6 +215,11 @@ enum enetc_msg_cmd_id {
 	ENETC_CMD_ID_GET_IP_CFG = 4
 };
 
+/* Command IDs for class ID 0x24 (SI VLAN isolation) */
+enum enetc_msg_si_vlan_cmd_id {
+	ENETC_CMD_ID_SET_SI_VLAN_ISO = 1,
+};
+
 /* IP_VER value returned when the version is not available */
 #define ENETC_IP_VER_NOT_AVAILABLE	0xFF
 
@@ -324,6 +331,22 @@ struct enetc_msg_vlan_exact_filter {
 	uint8_t reserved2;
 };
 
+/* VSI-PSI SI VLAN isolation message (class 0x24, cmd 0x1) */
+struct enetc_msg_si_vlan_iso {
+	struct enetc_msg_cmd_header header;
+	uint8_t ctrl;          /* E[7], VTEA[6], TPID[1:0] */
+	uint8_t pcp_dei_vid_hi; /* PCP[7:5], DEI[4], VID[11:8] */
+	uint8_t vid_lo;        /* VID[7:0] */
+	uint8_t reserved_0;
+	uint8_t flags;         /* SVIE[2], VTE[1] */
+	uint8_t reserved_1[3];
+};
+
+#define ENETC_SI_VLAN_ISO_E	BIT(7)  /* ctrl: enable SI VLAN */
+#define ENETC_SI_VLAN_ISO_VTEA	BIT(6)  /* ctrl: 0=strip, 1=zero VID */
+#define ENETC_SI_VLAN_ISO_SVIE	BIT(2)  /* flags: Tx insertion enable */
+#define ENETC_SI_VLAN_ISO_VTE	BIT(1)  /* flags: Rx removal enable */
+
 struct enetc_psi_reply_msg {
 	uint8_t class_id;
 	uint8_t status;
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 53d201d62b..59632cc790 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -875,6 +875,44 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 	return ret;
 }
 
+/* Configure SI-based VLAN insertion (Tx) and removal (Rx) for the PF. */
+static int
+enetc4_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint32_t psivlanr, psicfgr0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	psicfgr0 = enetc4_port_rd(enetc_hw, ENETC4_PSICFGR0(0));
+
+	if (on) {
+		/* Build the VLAN register: enable bit + VID (PCP/DEI left 0) */
+		psivlanr = (uint32_t)ENETC4_PSIVLANR_E |
+			   ENETC4_PSIVLANR_VID(vlan_id);
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), psivlanr);
+
+		/* Allow C-VLAN TPID, enable Tx insertion and Rx removal */
+		psicfgr0 |= ENETC4_PSICFGR0_SIVC_CVLAN |
+			    ENETC4_PSICFGR0_SIVIE |
+			    ENETC4_PSICFGR0_VTE;
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+	} else {
+		/* Clear Tx insertion, Rx removal and C-VLAN allow bits */
+		psicfgr0 &= ~(ENETC4_PSICFGR0_SIVC_CVLAN |
+			      ENETC4_PSICFGR0_SIVIE |
+			      ENETC4_PSICFGR0_VTE);
+		enetc4_port_wr(enetc_hw, ENETC4_PSICFGR0(0), psicfgr0);
+
+		/* Clear the VLAN register (disable SI VLAN processing) */
+		enetc4_port_wr(enetc_hw, ENETC4_PSIVLANR(0), 0);
+	}
+
+	return 0;
+}
+
 static int
 enetc4_promiscuous_enable(struct rte_eth_dev *dev)
 {
@@ -1270,6 +1308,7 @@ static const struct eth_dev_ops enetc4_ops = {
 	.get_reg              = enetc4_get_regs,
 	.promiscuous_enable   = enetc4_promiscuous_enable,
 	.promiscuous_disable  = enetc4_promiscuous_disable,
+	.vlan_pvid_set        = enetc4_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index b1e7162321..eab4f3ed65 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -617,6 +617,7 @@ enetc4_msg_vsi_send(struct enetc_eth_hw *hw, struct enetc_msg_swbd *msg)
 		case ENETC_CLASS_ID_LINK_STATUS:
 		case ENETC_CLASS_ID_LINK_SPEED:
 		case ENETC_CLASS_ID_GET_IP_VER:
+		case ENETC_CLASS_ID_SI_VLAN_ISO:
 			break;
 		default:
 			err = -EIO;
@@ -1526,6 +1527,87 @@ static int enetc4_vf_vlan_offload_set(struct rte_eth_dev *dev, int mask __rte_un
 	return 0;
 }
 
+/* Configure SI-based VLAN insertion/removal via VSI-PSI mailbox (class 0x24). */
+static int
+enetc4_vf_vlan_pvid_set(struct rte_eth_dev *dev, uint16_t vlan_id, int on)
+{
+	struct enetc_eth_hw *hw = ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	struct enetc_msg_si_vlan_iso *cmd;
+	struct enetc_msg_swbd *msg;
+	struct enetc_psi_reply_msg *reply_msg;
+	uint32_t msg_size;
+	int err = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	reply_msg = rte_zmalloc(NULL, sizeof(*reply_msg), RTE_CACHE_LINE_SIZE);
+	if (!reply_msg) {
+		ENETC_PMD_ERR("Failed to alloc memory for reply_msg");
+		return -ENOMEM;
+	}
+
+	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
+	if (!msg) {
+		ENETC_PMD_ERR("Failed to alloc msg");
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+
+	msg_size = RTE_ALIGN(sizeof(struct enetc_msg_si_vlan_iso),
+			     ENETC_VSI_PSI_MSG_SIZE);
+	msg->vaddr = rte_zmalloc(NULL, msg_size, 0);
+	if (!msg->vaddr) {
+		ENETC_PMD_ERR("Failed to alloc memory for msg body");
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->dma = rte_mem_virt2iova((const void *)msg->vaddr);
+	if (msg->dma == RTE_BAD_IOVA) {
+		ENETC_PMD_ERR("Failed to get IOVA for SI VLAN isolation msg body");
+		rte_free(msg->vaddr);
+		rte_free(msg);
+		rte_free(reply_msg);
+		return -ENOMEM;
+	}
+	msg->size = msg_size;
+
+	cmd = (struct enetc_msg_si_vlan_iso *)msg->vaddr;
+
+	if (on) {
+		cmd->ctrl = (uint8_t)ENETC_SI_VLAN_ISO_E;
+		cmd->pcp_dei_vid_hi = (uint8_t)((vlan_id >> 8) & 0x0f);
+		cmd->vid_lo = (uint8_t)(vlan_id & 0xff);
+		cmd->flags = (uint8_t)(ENETC_SI_VLAN_ISO_SVIE | ENETC_SI_VLAN_ISO_VTE);
+	}
+	/* on=0: all fields zero (rte_zmalloc cleared the buffer) */
+
+	enetc_msg_vf_fill_common_hdr(msg, ENETC_CLASS_ID_SI_VLAN_ISO,
+				     ENETC_CMD_ID_SET_SI_VLAN_ISO, 0, 0, 0);
+
+	/* send the command and wait for PSI reply */
+	err = enetc4_msg_vsi_send(hw, msg);
+	if (err) {
+		ENETC_PMD_ERR("VSI message send error for SI VLAN isolation");
+		goto end;
+	}
+
+	enetc4_msg_vsi_reply_msg(enetc_hw, reply_msg);
+
+	if (reply_msg->class_id != ENETC_MSG_CLASS_ID_CMD_SUCCESS) {
+		ENETC_PMD_ERR("SI VLAN isolation command failed: class_id=0x%x status=0x%x",
+			      reply_msg->class_id, reply_msg->status);
+		err = -EINVAL;
+	}
+
+end:
+	rte_free(msg->vaddr);
+	rte_free(msg);
+	rte_free(reply_msg);
+	return err;
+}
+
 static int
 enetc4_vf_mtu_set(struct rte_eth_dev *dev __rte_unused, uint16_t mtu __rte_unused)
 {
@@ -1682,6 +1764,7 @@ static const struct eth_dev_ops enetc4_vf_ops = {
 	.link_update	      = enetc4_vf_link_update,
 	.vlan_filter_set      = enetc4_vf_vlan_filter_set,
 	.vlan_offload_set     = enetc4_vf_vlan_offload_set,
+	.vlan_pvid_set        = enetc4_vf_vlan_pvid_set,
 	.rx_queue_setup       = enetc4_rx_queue_setup,
 	.rx_queue_start       = enetc4_rx_queue_start,
 	.rx_queue_stop        = enetc4_rx_queue_stop,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 12/14] net/enetc4: update VF link status to bitmask encoding
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (10 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 13/14] net/enetc4: enable Tx PAUSE via VF Rx congestion mode Gagandeep Singh
                               ` (2 subsequent siblings)
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

The PF-to-VF link status notification code was previously a two-value
enum (UP=0x0, DOWN=0x1). Change this to a bitmask so future bits can
carry additional state alongside the link up/down indication:

  ENETC_LINK_DOWN  BIT(0)  -- set when link is down

Link up is now encoded as the DOWN bit being clear, which keeps the
wire value for link-down identical (0x1) and ensures backward
compatibility with older kernel PFs.

Changes:
- enetc.h: replace enum link_status with a #define bitmask;
  drop ENETC_LINK_UP (no longer a named constant).
- enetc4_vf.c enetc4_process_psi_msg(): replace switch/case on
  ENETC_LINK_UP/DOWN with bitmask decode.
- enetc4_vf.c enetc4_vf_link_update(): same bitmask decode.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/enetc.h              |  8 ++++----
 drivers/net/enetc/enetc4_vf.c          | 27 +++++++-------------------
 3 files changed, 12 insertions(+), 24 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 190d77ecee..47090068d0 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -73,6 +73,7 @@ New Features
   * Added stats reset for the ENETC4 VF using a software snapshot/delta approach.
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
+  * Updated ENETC4 VF link status reporting to use bitmask encoding.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 8189dc3ea8..4ad6af777b 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -237,10 +237,10 @@ enum vlan_status {
 	ENETC_VLAN_NO_RESOURCE = 0x3
 };
 
-enum link_status {
-	ENETC_LINK_UP = 0x0,
-	ENETC_LINK_DOWN = 0x1
-};
+/* Link status bitmask in PF-to-VF mailbox notification.
+ * Link up is encoded as the DOWN bit being clear.
+ */
+#define ENETC_LINK_DOWN  (1u << 0)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index eab4f3ed65..af196900cb 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -510,8 +510,10 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 	enetc4_msg_get_psi_msg(enetc_hw, msg);
 
 	if (msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (msg->status) {
-		case ENETC_LINK_UP:
+		if (msg->status & ENETC_LINK_DOWN) {
+			ENETC_PMD_DEBUG("Link is down");
+			link.link_status = RTE_ETH_LINK_DOWN;
+		} else {
 			ENETC_PMD_DEBUG("Link is up");
 			link.link_status = RTE_ETH_LINK_UP;
 			/* Re-query speed from PF so the cached value reflects
@@ -523,14 +525,6 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 				enetc4_decode_link_speed(msg->status,
 							hw->vf_link_legacy,
 							&link);
-			break;
-		case ENETC_LINK_DOWN:
-			ENETC_PMD_DEBUG("Link is down");
-			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status 0x%x", msg->status);
-			break;
 		}
 		ret = rte_eth_linkstatus_set(eth_dev, &link);
 		if (!ret)
@@ -1211,17 +1205,10 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		switch (reply_msg->status) {
-		case ENETC_LINK_UP:
-			link.link_status = RTE_ETH_LINK_UP;
-			break;
-		case ENETC_LINK_DOWN:
+		if (reply_msg->status & ENETC_LINK_DOWN)
 			link.link_status = RTE_ETH_LINK_DOWN;
-			break;
-		default:
-			ENETC_PMD_ERR("Unknown link status");
-			break;
-		}
+		else
+			link.link_status = RTE_ETH_LINK_UP;
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 13/14] net/enetc4: enable Tx PAUSE via VF Rx congestion mode
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (11 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11  7:47             ` [PATCH v7 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
  2026-08-11 15:39             ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Stephen Hemminger
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

When the PF negotiates TX PAUSE on the port it signals this to the VF
via BIT(1) of the PF-to-VF link status mailbox message. The VF PMD must
respond by setting RBMR_CM (BIT(4)) on all active RX rings so the MAC
emits PAUSE frames on ingress pressure.

Add ENETC_RBMR_CM register definition, ENETC_LINK_TX_PAUSE bitmask,
and tx_pause_active state flag. Add enetc4_vf_set_congestion_mode() to
update all active RX rings and persist the state for rings started
later. Hook it into both the interrupt and poll link-update paths, and
apply the saved state in rx_queue_setup() and rx_queue_start().

RX PAUSE (honoring received PAUSE frames) is handled at the MAC level
by the PF and requires no VF PMD changes.

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/features/enetc4.ini    |  1 +
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  1 +
 drivers/net/enetc/enetc.h              | 11 ++++-
 drivers/net/enetc/enetc4_ethdev.c      | 15 ++++++-
 drivers/net/enetc/enetc4_vf.c          | 60 ++++++++++++++++++++++++--
 6 files changed, 83 insertions(+), 6 deletions(-)

diff --git a/doc/guides/nics/features/enetc4.ini b/doc/guides/nics/features/enetc4.ini
index 01b0dc5b80..1f599dace7 100644
--- a/doc/guides/nics/features/enetc4.ini
+++ b/doc/guides/nics/features/enetc4.ini
@@ -14,6 +14,7 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 VLAN filter          = Y
+Flow control         = Y
 VLAN offload         = Y
 RSS hash             = Y
 Packet type parsing  = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 47090068d0..f287390db7 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -74,6 +74,7 @@ New Features
   * Added per-queue MSI-X Rx interrupt support for the ENETC4 VF.
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
+  * Added Tx PAUSE support for the ENETC4 VF via Rx congestion mode.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 6e96562850..33d075fe59 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -51,6 +51,7 @@ enum enetc_bdr_type {TX, RX};
 							+ (off))
 /* RX BDR reg offsets */
 #define ENETC_RBMR		0x0 /* RX BDR mode register*/
+#define ENETC_RBMR_CM		BIT(4)  /* congestion mode: assert congestion to emit TX PAUSE */
 #define ENETC_RBMR_EN		BIT(31)
 
 #define ENETC_BMR_RESET		0x0 /* BDR reset*/
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 4ad6af777b..7f67b2a0b3 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -6,6 +6,7 @@
 #define _ENETC_H_
 
 #include <pthread.h>
+#include <rte_stdatomic.h>
 #include <rte_time.h>
 #include <ethdev_pci.h>
 
@@ -141,6 +142,11 @@ struct enetc_eth_hw {
 	 */
 	uint8_t vf_link_legacy;
 	pthread_mutex_t vsi_lock; /* serializes all VSI-PSI mailbox transactions */
+	/* 1 = TX PAUSE negotiated on port; VF RX rings must have RBMR_CM set.
+	 * Updated from the PF-to-VF link status mailbox message (BIT(1)).
+	 * Written on the interrupt thread, read on the control thread.
+	 */
+	RTE_ATOMIC(uint8_t)tx_pause_active;
 	/* Baseline snapshot for VF stats reset (software delta approach). */
 	struct enetc4_vf_stats_saved vf_stats_saved;
 };
@@ -239,8 +245,11 @@ enum vlan_status {
 
 /* Link status bitmask in PF-to-VF mailbox notification.
  * Link up is encoded as the DOWN bit being clear.
+ * TX_PAUSE is set when the port has negotiated TX PAUSE; VF must enable
+ * congestion mode (ENETC_RBMR_CM) on its RX rings accordingly.
  */
-#define ENETC_LINK_DOWN  (1u << 0)
+#define ENETC_LINK_DOWN      (1u << 0)
+#define ENETC_LINK_TX_PAUSE  (1u << 1)
 
 enum speed {
 	ENETC_SPEED_UNKNOWN = 0x0,
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index 59632cc790..df22ad0c7b 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -714,8 +714,13 @@ enetc4_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	if (!rx_conf->rx_deferred_start) {
-		/* enable ring */
+		/* Enable ring; apply congestion mode if TX PAUSE is already active. */
 		rx_enable |= ENETC_RBMR_EN;
+		if (rte_atomic_load_explicit(&adapter->hw.tx_pause_active,
+					     rte_memory_order_acquire))
+			rx_enable |= ENETC_RBMR_CM;
+		else
+			rx_enable &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&adapter->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_enable);
 		dev->data->rx_queue_state[rx_ring->index] =
@@ -1092,7 +1097,13 @@ enetc4_rx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->rx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		rx_data = enetc4_rxbdr_rd(&priv->hw.hw, rx_ring->index,
 					 ENETC_RBMR);
-		rx_data = rx_data | ENETC_RBMR_EN;
+		rx_data |= ENETC_RBMR_EN;
+		/* Restore congestion mode if TX PAUSE is active. */
+		if (rte_atomic_load_explicit(&priv->hw.tx_pause_active,
+					     rte_memory_order_acquire))
+			rx_data |= ENETC_RBMR_CM;
+		else
+			rx_data &= ~(uint32_t)ENETC_RBMR_CM;
 		enetc4_rxbdr_wr(&priv->hw.hw, rx_ring->index, ENETC_RBMR,
 			       rx_data);
 		dev->data->rx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
diff --git a/drivers/net/enetc/enetc4_vf.c b/drivers/net/enetc/enetc4_vf.c
index af196900cb..7155a60ad7 100644
--- a/drivers/net/enetc/enetc4_vf.c
+++ b/drivers/net/enetc/enetc4_vf.c
@@ -491,6 +491,38 @@ enetc4_decode_link_speed(uint8_t status, bool vf_link_legacy,
 	}
 }
 
+/*
+ * Set or clear ENETC_RBMR_CM (congestion mode) on all active VF RX rings.
+ * When set, the ring signals congestion to the MAC, causing it to emit TX
+ * PAUSE frames on ingress pressure. hw->tx_pause_active is updated so rings
+ * started later inherit the correct state.
+ */
+static void
+enetc4_vf_set_congestion_mode(struct rte_eth_dev *eth_dev, bool enable)
+{
+	struct enetc_eth_hw *hw =
+		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
+	struct enetc_hw *enetc_hw = &hw->hw;
+	uint16_t nb_rx = eth_dev->data->nb_rx_queues;
+	uint16_t i;
+	uint32_t rbmr;
+
+	rte_atomic_store_explicit(&hw->tx_pause_active, enable ? 1 : 0,
+				  rte_memory_order_release);
+
+	for (i = 0; i < nb_rx; i++) {
+		rbmr = enetc4_rxbdr_rd(enetc_hw, i, ENETC_RBMR);
+		if (enable)
+			rbmr |= ENETC_RBMR_CM;
+		else
+			rbmr &= ~(uint32_t)ENETC_RBMR_CM;
+		enetc4_rxbdr_wr(enetc_hw, i, ENETC_RBMR, rbmr);
+	}
+
+	ENETC_PMD_DEBUG("VF congestion mode %s on %u RX rings",
+			enable ? "enabled" : "disabled", nb_rx);
+}
+
 static void
 enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 {
@@ -498,6 +530,7 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
 	struct enetc_psi_reply_msg *msg;
 	struct rte_eth_link link;
+	bool tx_pause;
 	int ret = 0;
 
 	msg = rte_zmalloc(NULL, sizeof(*msg), RTE_CACHE_LINE_SIZE);
@@ -513,9 +546,24 @@ enetc4_process_psi_msg(struct rte_eth_dev *eth_dev, struct enetc_hw *enetc_hw)
 		if (msg->status & ENETC_LINK_DOWN) {
 			ENETC_PMD_DEBUG("Link is down");
 			link.link_status = RTE_ETH_LINK_DOWN;
+			/* Clear congestion mode on link-down so VF rings do not
+			 * assert congestion while the port is offline.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, false);
 		} else {
-			ENETC_PMD_DEBUG("Link is up");
+			/* BIT(1) is set when the port has negotiated TX PAUSE.
+			 * Legacy PF does not set this bit so tx_pause stays false.
+			 */
+			tx_pause = !!(msg->status & ENETC_LINK_TX_PAUSE);
+			ENETC_PMD_DEBUG("Link is up, tx_pause=%d", tx_pause);
 			link.link_status = RTE_ETH_LINK_UP;
+
+			/* Apply congestion mode before raising the carrier so
+			 * the VF rings are ready to emit PAUSE before traffic
+			 * starts flowing.
+			 */
+			enetc4_vf_set_congestion_mode(eth_dev, tx_pause);
+
 			/* Re-query speed from PF so the cached value reflects
 			 * the current negotiated speed after link-up.
 			 */
@@ -1205,10 +1253,16 @@ enetc4_vf_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused
 	}
 
 	if (reply_msg->class_id == ENETC_CLASS_ID_LINK_STATUS) {
-		if (reply_msg->status & ENETC_LINK_DOWN)
+		if (reply_msg->status & ENETC_LINK_DOWN) {
 			link.link_status = RTE_ETH_LINK_DOWN;
-		else
+			/* Link is down: disable congestion mode on all RX rings. */
+			enetc4_vf_set_congestion_mode(dev, false);
+		} else {
 			link.link_status = RTE_ETH_LINK_UP;
+			/* Restore congestion mode from the TX PAUSE bit. */
+			enetc4_vf_set_congestion_mode(dev,
+				!!(reply_msg->status & ENETC_LINK_TX_PAUSE));
+		}
 	} else {
 		ENETC_PMD_ERR("Wrong reply message");
 		return -1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* [PATCH v7 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (12 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 13/14] net/enetc4: enable Tx PAUSE via VF Rx congestion mode Gagandeep Singh
@ 2026-08-11  7:47             ` Gagandeep Singh
  2026-08-11 15:39             ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Stephen Hemminger
  14 siblings, 0 replies; 110+ messages in thread
From: Gagandeep Singh @ 2026-08-11  7:47 UTC (permalink / raw)
  To: dev; +Cc: hemant.agrawal, Gagandeep Singh

Add enetc4_txq_wrr devarg to configure per-ring WRR weights in the
NETC LEAF-level Tx scheduler (TBaMR register, bits [6:4]).

The NETC Tx scheduler has three levels:
  - ROOT (port/TC): strict priority + CBS (PF/port space)
  - MID  (SI/VSI):  WBFS shaping (PF space)
  - LEAF (Tx BDR):  strict priority + frame-based WRR (VF/SI space)

TBaMR is in the VF own SI space, so no Linux PF involvement is
needed for PRIO or WRR configuration.

Changes:
- enetc_hw.h: add ENETC_TBMR_WRR_MASK, ENETC_TBMR_WRR(n) macros for
  TBaMR bits [6:4], and ENETC_TBMR_PRIO_MASK for bits [2:0]
- enetc.h: add txq_wrr pointer to enetc_eth_hw struct
- enetc4_ethdev.c: add parse_txq_wrr() and wire ENETC4_TXQ_WRR devarg
  through enetc4_get_devargs() and enetc4_dev_configure(); apply WRR
  bits in enetc4_tx_queue_setup() and enetc4_tx_queue_start()

Usage:
  # strict priority: ring 0 highest
  -a 0002:00:12.0,enetc4_txq_prior="3|2|1"

  # WRR 2:4:1 on same-priority rings
  -a 0002:00:12.0,enetc4_txq_prior="1|1|1",enetc4_txq_wrr="2|4|1"

Signed-off-by: Gagandeep Singh <g.singh@nxp.com>
---
 doc/guides/nics/enetc4.rst             | 12 ++++
 doc/guides/rel_notes/release_26_11.rst |  1 +
 drivers/net/enetc/base/enetc_hw.h      |  5 ++
 drivers/net/enetc/enetc.h              |  1 +
 drivers/net/enetc/enetc4_ethdev.c      | 88 +++++++++++++++++++++-----
 5 files changed, 92 insertions(+), 15 deletions(-)

diff --git a/doc/guides/nics/enetc4.rst b/doc/guides/nics/enetc4.rst
index 262e2fe703..7399a416d3 100644
--- a/doc/guides/nics/enetc4.rst
+++ b/doc/guides/nics/enetc4.rst
@@ -166,6 +166,18 @@ PF/Common devargs
 
     dpdk-testpmd -a 0000:00:00.0,enetc4_txq_prior=1|2|3 -- -i
 
+``enetc4_txq_wrr``
+  Set per-queue WRR weight for the LEAF-level Tx scheduler (TBaMR bits [6:4]).
+  The value is a ``|``-separated list of WRR weights, one per Tx queue.
+  Meaningful only when the corresponding queues share the same strict-priority
+  level via ``enetc4_txq_prior``; queues with different priorities are
+  scheduled strictly regardless of their WRR weight.
+  Values beyond the maximum supported Tx queue count are discarded.
+
+  Usage example (WRR 2:4:1 on three equal-priority rings)::
+
+    dpdk-testpmd -a 0000:00:00.0,enetc4_txq_prior="1|1|1",enetc4_txq_wrr="2|4|1" -- -i
+
 ``nc``
   Select non-cacheable Rx/Tx ops (BD rings mapped as non-cacheable memory).
   Set to ``1`` to use non-cacheable descriptor ring operations.
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index f287390db7..2be5357af3 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -75,6 +75,7 @@ New Features
   * Added SI-based port VLAN insertion (Tx) and removal (Rx) for ENETC4 PF and VF.
   * Updated ENETC4 VF link status reporting to use bitmask encoding.
   * Added Tx PAUSE support for the ENETC4 VF via Rx congestion mode.
+  * Added WRR Tx scheduler devarg (``enetc4_txq_wrr``) for ENETC4 VF ring weights.
 
 Removed Items
 -------------
diff --git a/drivers/net/enetc/base/enetc_hw.h b/drivers/net/enetc/base/enetc_hw.h
index 33d075fe59..5a71f01db9 100644
--- a/drivers/net/enetc/base/enetc_hw.h
+++ b/drivers/net/enetc/base/enetc_hw.h
@@ -86,6 +86,11 @@ enum enetc_bdr_type {TX, RX};
 
 #define ENETC_RTBLENR_LEN(n)		((n) & ~0x7)
 #define ENETC_TBMR_EN			BIT(31)
+/* TBaMR[WRR] bits [6:4]: weight for same-priority ring arbitration (0=1x .. 7=8x). */
+#define ENETC_TBMR_WRR_MASK		GENMASK(6, 4)
+#define ENETC_TBMR_WRR(n)		((((n) - 1) & 0x7) << 4)
+/* TBaMR[PRIO] bits [2:0]: strict priority (0=lowest, 7=highest). */
+#define ENETC_TBMR_PRIO_MASK		GENMASK(2, 0)
 
 /* Port regs, offset: 1_0000h */
 #define ENETC_PORT_BASE			0x10000
diff --git a/drivers/net/enetc/enetc.h b/drivers/net/enetc/enetc.h
index 7f67b2a0b3..f6a69b3cf7 100644
--- a/drivers/net/enetc/enetc.h
+++ b/drivers/net/enetc/enetc.h
@@ -135,6 +135,7 @@ struct enetc_eth_hw {
 	uint32_t vsi_timeout; /* VSI-PSI message wait timeout (iterations) */
 	uint32_t vsi_delay;   /* VSI-PSI message wait delay (us) */
 	uint32_t *txq_prior;  /* per-queue TX priority (TBMR priority bits) */
+	uint32_t *txq_wrr;    /* per-queue TX WRR weight pre-shifted for TBMR[WRR] */
 	uint8_t nc_mode;      /* 1 = non-cacheable BD memory, use _nc ops */
 	uint8_t rxq_intr_en;  /* 1 = per-queue Rx MSI-X interrupts enabled */
 	/* 1 = legacy PF-to-VF link message layout (4-bit speed / 4-bit cookie),
diff --git a/drivers/net/enetc/enetc4_ethdev.c b/drivers/net/enetc/enetc4_ethdev.c
index df22ad0c7b..82420aeeb7 100644
--- a/drivers/net/enetc/enetc4_ethdev.c
+++ b/drivers/net/enetc/enetc4_ethdev.c
@@ -30,6 +30,7 @@ static uint64_t dev_tx_offloads_sup =
 	RTE_ETH_TX_OFFLOAD_UDP_TSO;
 
 #define ENETC4_TXQ_PRIORITIES	"enetc4_txq_prior"
+#define ENETC4_TXQ_WRR		"enetc4_txq_wrr"
 #define ENETC4_NC_MEMORY	"nc"
 
 static int
@@ -37,15 +38,17 @@ parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 {
 	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
 	struct enetc_eth_hw *hw =
-		ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
-	char *input_str = strdup(value);
+				ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
 	char *str;
 	uint32_t i = 0;
 
+	input_str = strdup(value);
 	if (!input_str)
 		return -ENOMEM;
 
-	hw->txq_prior = calloc(hw->max_tx_queues, sizeof(uint32_t));
+	rte_free(hw->txq_prior);
+	hw->txq_prior = rte_zmalloc(NULL, hw->max_tx_queues * sizeof(uint32_t), 0);
 	if (!hw->txq_prior) {
 		free(input_str);
 		return -ENOMEM;
@@ -53,7 +56,46 @@ parse_txq_prior(const char *key __rte_unused, const char *value, void *opaque)
 
 	str = strtok(input_str, "|");
 	while (str != NULL && i < hw->max_tx_queues) {
-		hw->txq_prior[i++] = (uint32_t)atoi(str);
+		hw->txq_prior[i++] = atoi(str) & ENETC_TBMR_PRIO_MASK;
+		str = strtok(NULL, "|");
+	}
+
+	free(input_str);
+	return 0;
+}
+
+/* Parse enetc4_txq_wrr="w0|w1|..." devarg; weight 1..8 per ring. */
+static int parse_txq_wrr(const char *key __rte_unused, const char *value,
+			  void *opaque)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)opaque;
+	struct enetc_eth_hw *hw =
+			ENETC_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	char *input_str;
+	char *str;
+	uint32_t i = 0;
+	int w;
+
+	input_str = strdup(value);
+	if (!input_str)
+		return -ENOMEM;
+
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = rte_zmalloc(NULL,
+			hw->max_tx_queues * sizeof(uint32_t), 0);
+	if (!hw->txq_wrr) {
+		free(input_str);
+		return -ENOMEM;
+	}
+
+	str = strtok(input_str, "|");
+	while (str != NULL && i < hw->max_tx_queues) {
+		w = atoi(str);
+		if (w < 1)
+			w = 1;
+		if (w > 8)
+			w = 8;
+		hw->txq_wrr[i++] = ENETC_TBMR_WRR(w);
 		str = strtok(NULL, "|");
 	}
 
@@ -99,6 +141,13 @@ enetc4_get_devargs(struct rte_eth_dev *dev, const char *key)
 			return 0;
 		}
 	}
+	if (!strcmp(key, ENETC4_TXQ_WRR)) {
+		if (rte_kvargs_process(kvlist, key,
+				       parse_txq_wrr, (void *)dev) < 0) {
+			rte_kvargs_free(kvlist);
+			return 0;
+		}
+	}
 	if (!strcmp(key, ENETC4_NC_MEMORY)) {
 		if (rte_kvargs_process(kvlist, key,
 				       parse_nc, (void *)dev) < 0) {
@@ -450,7 +499,9 @@ enetc4_tx_queue_setup(struct rte_eth_dev *dev,
 
 		/* apply TX queue priority if configured */
 		if (priv->hw.txq_prior)
-			tx_en |= priv->hw.txq_prior[tx_ring->index];
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		/* enable ring */
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index,
 			       ENETC_TBMR, tx_en);
@@ -873,7 +924,10 @@ enetc4_dev_close(struct rte_eth_dev *dev)
 		dev->data->tx_queues[i] = NULL;
 	}
 	dev->data->nb_tx_queues = 0;
-
+	rte_free(hw->txq_prior);
+	hw->txq_prior = NULL;
+	rte_free(hw->txq_wrr);
+	hw->txq_wrr = NULL;
 	if (rte_eal_iova_mode() == RTE_IOVA_PA)
 		dpaax_iova_table_depopulate();
 
@@ -1024,6 +1078,11 @@ enetc4_dev_configure(struct rte_eth_dev *dev)
 	for (i = 0; i < dev->data->nb_tx_queues; i++)
 		enetc4_rxbdr_wr(enetc_hw, i, ENETC_TBMR, ENETC_BMR_RESET);
 
+	hw->nc_mode = 0;
+	enetc4_get_devargs(dev, ENETC4_TXQ_PRIORITIES);
+	enetc4_get_devargs(dev, ENETC4_TXQ_WRR);
+	enetc4_get_devargs(dev, ENETC4_NC_MEMORY);
+
 	if (dev->data->nb_rx_queues <= 1)
 		return 0;
 
@@ -1147,7 +1206,13 @@ enetc4_tx_queue_start(struct rte_eth_dev *dev, uint16_t qidx)
 	if (dev->data->tx_queue_state[qidx] == RTE_ETH_QUEUE_STATE_STOPPED) {
 		tx_data = enetc4_txbdr_rd(&priv->hw.hw, tx_ring->index,
 					 ENETC_TBMR);
-		tx_data = tx_data | ENETC_TBMR_EN;
+		/* Clear scheduler bits before applying fresh devarg values. */
+		tx_data &= ~(ENETC_TBMR_PRIO_MASK | ENETC_TBMR_WRR_MASK);
+		tx_data |= ENETC_TBMR_EN;
+		if (priv->hw.txq_prior)
+			tx_data |= priv->hw.txq_prior[tx_ring->index];
+		if (priv->hw.txq_wrr)
+			tx_data |= priv->hw.txq_wrr[tx_ring->index];
 		enetc4_txbdr_wr(&priv->hw.hw, tx_ring->index, ENETC_TBMR,
 			       tx_data);
 		dev->data->tx_queue_state[qidx] = RTE_ETH_QUEUE_STATE_STARTED;
@@ -1422,16 +1487,8 @@ enetc4_dev_init(struct rte_eth_dev *eth_dev)
 static int
 enetc4_dev_uninit(struct rte_eth_dev *eth_dev)
 {
-	struct enetc_eth_hw *hw =
-		ENETC_DEV_PRIVATE_TO_HW(eth_dev->data->dev_private);
-
 	PMD_INIT_FUNC_TRACE();
 
-	if (hw->txq_prior) {
-		free(hw->txq_prior);
-		hw->txq_prior = NULL;
-	}
-
 	return enetc4_dev_close(eth_dev);
 }
 
@@ -1462,5 +1519,6 @@ RTE_PMD_REGISTER_PCI_TABLE(net_enetc4, pci_id_enetc4_map);
 RTE_PMD_REGISTER_KMOD_DEP(net_enetc4, "* vfio-pci");
 RTE_PMD_REGISTER_PARAM_STRING(net_enetc4,
 			      ENETC4_TXQ_PRIORITIES "=<string> "
+			      ENETC4_TXQ_WRR "=<string> "
 			      ENETC4_NC_MEMORY "=<int>");
 RTE_LOG_REGISTER_DEFAULT(enetc4_logtype_pmd, NOTICE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 110+ messages in thread

* Re: [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95
  2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
                               ` (13 preceding siblings ...)
  2026-08-11  7:47             ` [PATCH v7 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
@ 2026-08-11 15:39             ` Stephen Hemminger
  14 siblings, 0 replies; 110+ messages in thread
From: Stephen Hemminger @ 2026-08-11 15:39 UTC (permalink / raw)
  To: Gagandeep Singh; +Cc: dev, hemant.agrawal

On Tue, 11 Aug 2026 13:17:31 +0530
Gagandeep Singh <g.singh@nxp.com> wrote:

> V7-changes:
>  - added missing 14th patch.
> 
> V6-changes:
>  - fixed free() on memory allocated with rte_zmalloc().
>  - fixed eventfd and vector-list leak on interrupt teardown
>  - fixed Rx ring doubling still bypasses
>  - fixed VSI-PSI transaction issued from the interrupt handler
>  - fixed the free-and-reallocate of msg is unnecessary churn
>  - fixed unsynchronized access to tx_pause_active and RBMR
>  - fixed signed shift overflow in the register-dump version field
>  - fixed the fw_size == 0 early return reports the length of "0.0"
>  - unwanted blank lines removed.
>  - parse_txq_prior() returns updated.
> 
> V5-changes:
>  - Patch 1 Build fixes which includes:
>     'prev_seg' undeclared fixed.
>     redefinition of 'dev_rx_offloads_sup' fixed.
>     Error: duplicate rx_enable declaration fixed.
>    PF loses Scattered Rx and Multi-segment Tx
>  - mbuf leak in enetc_xmit_pkts_lso() fixed.
>  - fixed conversion on ENETC4_TXBD_FLAGS_F issue.
>  - fixed link speed decode has no upper bound.
>  - fixed mailbox ops added to the no-VSI ops table.
>  - new devargs documented.
> 
> V4-changes:
>  - fix doc build issue: WARNING: undefined label: pmd_build_and_test
> 
> v3-changes:
>  - fix doc build issue.
>  - fix compilation issue on fedore:43-gcc-minsize
> 
> V2-changes:
>  - compilation fixes.
> 
> V1-changes:
> This series adds new PMD features to the ENETC4 driver targeting the
> NXP i.MX95 NETC IP.
> 
> The series covers:
> 
>  - KEEP_CRC Rx offload: preserve the Ethernet FCS in the receive buffer.
>  - TSO: TCP Segmentation Offload for the VF Tx path.
>  - RSC/LRO: hardware Receive Segment Coalesce for PF and VF Rx paths.
>  - Link speed code: extend the PF-to-VF mailbox field from 4-bit to
>    8-bit to support speeds beyond 10G.
>  - Firmware version: report the NETC IP version via fw_version_get.
>  - Register dump: dump SI, port (PF) and BD ring registers.
>  - Ring parameters: implement rxq_info_get / txq_info_get for the VF.
>  - Link-up interrupt: refresh the cached link speed on each VF link-up
>    interrupt so that link_update returns the current speed immediately.
>  - Stats reset: software snapshot/delta approach for VF counter reset.
>  - Per-queue Rx interrupt: MSI-X per-queue Rx interrupts for the VF,
>    enabling interrupt-driven receive with l3fwd-power.
>  - SI VLAN: hardware port VLAN insertion/removal for PF and VF.
>  - VF link status bitmask: switch VF link status to bitmask encoding
>    to align with the PF and newer kernel driver conventions.
>  - TX PAUSE: VF sets Rx congestion mode when the PF signals TX PAUSE
>    negotiated on the wire; adds Flow control = Y to enetc4.ini.
>  - WRR Tx scheduler: per-ring WRR weights via enetc4_txq_wrr devarg.
> 
> Gagandeep Singh (14):
>   net/enetc: add keep-CRC Rx offload for ENETC4
>   net/enetc: add TSO support for ENETC4 VF
>   net/enetc: add RSC (hardware LRO) support for ENETC4
>   net/enetc: extend PF-VF link speed field to 8 bits
>   net/enetc: support firmware version get for VF
>   net/enetc: support registers dump
>   net/enetc: support ethtool ring parameters
>   net/enetc: refresh link speed on VF link-up interrupt
>   net/enetc: support stats reset for VF
>   net/enetc4: add per-queue Rx interrupt support for VF
>   net/enetc4: add SI-based port VLAN insertion and removal
>   net/enetc4: update VF link status to bitmask encoding
>   net/enetc4: enable Tx PAUSE via VF Rx congestion mode
>   net/enetc4: add WRR Tx scheduler devarg for VF rings
> 
>  doc/guides/nics/enetc4.rst             | 100 +++
>  doc/guides/nics/features/enetc4.ini    |   8 +
>  doc/guides/rel_notes/release_26_11.rst |  21 +
>  drivers/net/enetc/base/enetc4_hw.h     | 128 +++-
>  drivers/net/enetc/base/enetc_hw.h      |   6 +
>  drivers/net/enetc/enetc.h              | 136 ++++-
>  drivers/net/enetc/enetc4_ethdev.c      | 428 +++++++++++--
>  drivers/net/enetc/enetc4_vf.c          | 807 ++++++++++++++++++++++---
>  drivers/net/enetc/enetc_rxtx.c         | 530 +++++++++++++++-
>  9 files changed, 2011 insertions(+), 153 deletions(-)
> 

Still lots of issues found by AI review

ENETC4 v7 series review - 14 patches

No Reviewed-by; one error and two warnings remain.

All 14 commits build clean individually on top of c1a46b9 with gcc 13.3
and -Dwerror=true. check-git-log.sh: 14/14 valid. No trailing
whitespace. Consecutive-blank-line counts are back to the upstream
values in all four touched files.

Confirmed fixed from v5: the free()/rte_free() mismatch is gone --
enetc4_dev_uninit() no longer touches txq_prior at all and both arrays
are released in enetc4_dev_close(); rte_intr_vec_list_free() and
rte_intr_efd_disable() added at the intr_enable_fail: label so both the
teardown and the error path are covered; enetc4_alloc_rxbdr() now
validates ring_desc against MAX_BD_COUNT the same way the Tx side does;
tx_pause_active is RTE_ATOMIC with acquire/release accessors; the
register-dump version field is cast before the shift in both
enetc4_get_regs() and enetc4_vf_get_regs(); fw_version_get() computes
the fw_size == 0 length from the real values; release note reads "Tx
PAUSE ... Rx congestion mode"; parse_txq_prior() and parse_txq_wrr()
both return -ENOMEM consistently.

Patch 08

Error: pthread mutex in shared memory without PTHREAD_PROCESS_SHARED.

The new vsi_lock lives in struct enetc_eth_hw, which sits in
eth_dev->data->dev_private. ethdev allocates that with
rte_zmalloc_socket(), so it is hugepage memory visible to secondary
processes:

drivers/net/enetc/enetc.h:145
pthread_mutex_t vsi_lock; /* serializes all VSI-PSI mailbox ... */

drivers/net/enetc/enetc4_vf.c:1985
pthread_mutex_init(&hw->vsi_lock, NULL);

Two problems follow. A mutex initialized with NULL attributes is
undefined behaviour if it is ever locked from a second process. And
enetc4_vf_dev_init() has no primary-process guard -- enetc4_dev_close()
is the only place in the driver that checks rte_eal_process_type() --
so a secondary attaching to the same VF re-runs pthread_mutex_init() on
a mutex the primary may be holding.

pthread_mutexattr_t attr;

pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&hw->vsi_lock, &attr);
pthread_mutexattr_destroy(&attr);

rte_spinlock_t would sidestep the initialization problem but is the
wrong primitive here, since the poll loop under the lock waits up to
vsi_timeout iterations.

Warning: the reply status is still read outside the lock.

enetc4_msg_vsi_send() now holds vsi_lock across the write, the MB poll
and the completion-status check, which closes most of the window. But
every caller re-reads ENETC4_VSIMSGSR after the function has returned
and dropped the lock:

err = enetc4_msg_vsi_send(hw, msg);
if (err) {
ENETC_PMD_ERR("VSI message send error");
goto end;
}

enetc4_msg_vsi_reply_msg(enetc_hw, reply_msg); /* lock released */

Six call sites go through enetc4_msg_vsi_reply_msg() (lines 727, 973,
1019, 1404, 1504, 1637) and enetc4_vf_get_ip_minor_revision() reads the
register directly at line 1081. A second thread -- in particular the
interrupt thread doing the patch 08 speed re-query -- can take the lock
and start a new transaction between the unlock and that read, so the
caller parses someone else's reply.

enetc4_msg_vsi_send() already has the value in its local vsimsgsr.
Returning it through an out-parameter, and having callers parse from
that instead of re-reading the register, removes the window entirely.

Patch 13

Warning: RBMR read-modify-write still races between threads.

The atomic on tx_pause_active covers the flag but not the register.
enetc4_vf_set_congestion_mode() runs on the interrupt thread:

rbmr = enetc4_rxbdr_rd(enetc_hw, i, ENETC_RBMR);
if (enable)
rbmr |= ENETC_RBMR_CM;
else
rbmr &= ~(uint32_t)ENETC_RBMR_CM;
enetc4_rxbdr_wr(enetc_hw, i, ENETC_RBMR, rbmr);

enetc4_rx_queue_start() and enetc4_rx_queue_stop() perform the same
unguarded read-modify-write on ENETC_RBMR_EN from the control thread.
A link-status interrupt landing mid-sequence still loses one of the two
updates, leaving a ring enabled without congestion mode or stopped with
it set. Serializing the RBMR accesses -- vsi_lock would do, or a
dedicated per-device lock -- is what is missing.

Info: the release store on tx_pause_active is placed before the RBMR
writes it is described as publishing, so the ordering does not do what
the comment implies. Nothing else is published through this flag, so
rte_memory_order_relaxed on both sides would be correct and clearer.

Patch 06

Info: enetc4_txbdr_regs[] and enetc4_rxbdr_regs[] are still static const
arrays defined in enetc.h, so every translation unit including the
header gets a private copy. Moving them into one .c file with an extern
declaration avoids the duplication.

^ permalink raw reply	[flat|nested] 110+ messages in thread

end of thread, other threads:[~2026-08-11 15:40 UTC | newest]

Thread overview: 110+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06 11:28 [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Gagandeep Singh
2026-08-06 11:28 ` [PATCH 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
2026-08-06 11:28 ` [PATCH 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
2026-08-06 11:28 ` [PATCH 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
2026-08-06 11:28 ` [PATCH 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
2026-08-06 11:28 ` [PATCH 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
2026-08-06 11:28 ` [PATCH 06/14] net/enetc: support registers dump Gagandeep Singh
2026-08-06 11:28 ` [PATCH 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
2026-08-06 11:28 ` [PATCH 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
2026-08-06 11:28 ` [PATCH 09/14] net/enetc: support stats reset for VF Gagandeep Singh
2026-08-06 11:28 ` [PATCH 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
2026-08-06 11:28 ` [PATCH 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
2026-08-06 11:28 ` [PATCH 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
2026-08-06 11:28 ` [PATCH 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
2026-08-06 11:28 ` [PATCH 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
2026-08-06 17:00 ` [PATCH 00/14] net/enetc: add new features for ENETC4 VF on i.MX95 Stephen Hemminger
2026-08-07  3:48 ` [PATCH v2 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 06/14] net/enetc: support registers dump Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 09/14] net/enetc: support stats reset for VF Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
2026-08-07  3:48   ` [PATCH v2 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
2026-08-07  7:02   ` [PATCH v3 00/14] net/enetc: add new features for ENETC4 VF on i.MX9 Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 06/14] net/enetc: support registers dump Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 09/14] net/enetc: support stats reset for VF Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
2026-08-07  7:02     ` [PATCH v3 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
2026-08-07 11:26     ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 01/14] net/enetc: add KEEP_CRC offload support for ENETC4 Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 04/14] net/enetc: extend link speed code field to 8-bit for PF-to-VF message Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 06/14] net/enetc: support registers dump Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 09/14] net/enetc: support stats reset for VF Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 13/14] net/enetc4: enable TX PAUSE via VF RX congestion mode Gagandeep Singh
2026-08-07 11:26       ` [PATCH v4 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
2026-08-09 21:00       ` [PATCH v4 00/14] net/enetc: add new features for ENETC4 on i.MX95 Stephen Hemminger
2026-08-10 10:58         ` Gagandeep Singh
2026-08-10 10:48       ` [PATCH v5 " Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 01/14] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 04/14] net/enetc: extend PF-VF link speed field to 8 bits Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 06/14] net/enetc: support registers dump Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 09/14] net/enetc: support stats reset for VF Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 13/14] net/enetc4: enable Tx PAUSE via VF Rx congestion mode Gagandeep Singh
2026-08-10 10:48         ` [PATCH v5 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
2026-08-10 15:25         ` [PATCH v5 00/14] net/enetc: add new features for ENETC4 on i.MX95 Stephen Hemminger
2026-08-10 15:40         ` Stephen Hemminger
2026-08-11  7:40         ` [PATCH v6 00/13] " Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 01/13] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 02/13] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 03/13] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 04/13] net/enetc: extend PF-VF link speed field to 8 bits Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 05/13] net/enetc: support firmware version get for VF Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 06/13] net/enetc: support registers dump Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 07/13] net/enetc: support ethtool ring parameters Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 08/13] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 09/13] net/enetc: support stats reset for VF Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 10/13] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 11/13] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 12/13] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
2026-08-11  7:40           ` [PATCH v6 13/13] net/enetc4: enable Tx PAUSE via VF Rx congestion mode Gagandeep Singh
2026-08-11  7:47           ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 01/14] net/enetc: add keep-CRC Rx offload for ENETC4 Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 02/14] net/enetc: add TSO support for ENETC4 VF Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 03/14] net/enetc: add RSC (hardware LRO) support for ENETC4 Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 04/14] net/enetc: extend PF-VF link speed field to 8 bits Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 05/14] net/enetc: support firmware version get for VF Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 06/14] net/enetc: support registers dump Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 07/14] net/enetc: support ethtool ring parameters Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 08/14] net/enetc: refresh link speed on VF link-up interrupt Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 09/14] net/enetc: support stats reset for VF Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 10/14] net/enetc4: add per-queue Rx interrupt support " Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 11/14] net/enetc4: add SI-based port VLAN insertion and removal Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 12/14] net/enetc4: update VF link status to bitmask encoding Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 13/14] net/enetc4: enable Tx PAUSE via VF Rx congestion mode Gagandeep Singh
2026-08-11  7:47             ` [PATCH v7 14/14] net/enetc4: add WRR Tx scheduler devarg for VF rings Gagandeep Singh
2026-08-11 15:39             ` [PATCH v7 00/14] net/enetc: add new features for ENETC4 on i.MX95 Stephen Hemminger

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