git: 87406f835166 - main - ixgbe: Enable per-pool RSS on X550 family devices

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Sun, 16 Aug 2026 01:33:33 UTC
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=87406f8351664aa444851499cda83b00fdc07662

commit 87406f8351664aa444851499cda83b00fdc07662
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-10 15:55:41 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-16 01:31:37 +0000

    ixgbe: Enable per-pool RSS on X550 family devices
    
    X550 family devices provide a separate RSS key, redirection table,
    and MRQC register for every VMDq pool.  With SR-IOV enabled, the
    driver continued programming only the global RSS state and never
    selected MRQC.MULTIPLE_RSS.  VF-local RSS programming was therefore
    ineffective.
    
    Enable multiple-RSS mode for X550, X552, X553, and E610.  Initialize
    the PF pool's 64-entry key, redirection table, and RSS hash controls.
    Leave each VF pool untouched so its driver retains ownership of its
    RSS key and mapping.
    
    E610 folds IPv6 extension-header traffic into its base RSS selectors
    and reserves the legacy EX selector bits.  Translate those requested
    hash types rather than programming reserved bits.
    
    With two E610 VFs active and four PF queue sets, eight fixed TCP flows
    distributed across all four PF receive queues.
    
    E610 uses the same per-pool mode according to the E610 Datasheet,
    sections 7.1.3.6.2 and 8.2.2.8.20-21.
    
    MFC after:      2 weeks
    Sponsored by:   Dirk-Willem van Gulik from Web Weaving (E610 hardware)
    Sponsored by:   BBOX.io
---
 sys/dev/ixgbe/if_ix.c | 89 +++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 69 insertions(+), 20 deletions(-)

diff --git a/sys/dev/ixgbe/if_ix.c b/sys/dev/ixgbe/if_ix.c
index f581de8d7abc..25e7e9bdf913 100644
--- a/sys/dev/ixgbe/if_ix.c
+++ b/sys/dev/ixgbe/if_ix.c
@@ -662,10 +662,13 @@ static void
 ixgbe_initialize_rss_mapping(struct ixgbe_softc *sc)
 {
 	struct ixgbe_hw *hw = &sc->hw;
-	u32 reta = 0, mrqc, rss_key[10];
-	int queue_id, reta_queues, table_size, index_mult;
-	int i, j;
+	bool per_pool_rss;
+	u32 reta = 0, mrqc, rss_fields, rss_key[10];
+	int i, index_mult, j, queue_id, reta_queues, table_size;
 	u32 rss_hash_config;
+#ifdef PCI_IOV
+	u32 pfmrqc;
+#endif
 
 	if (sc->feat_en & IXGBE_FEATURE_RSS) {
 		/* Fetch the configured RSS key */
@@ -677,6 +680,7 @@ ixgbe_initialize_rss_mapping(struct ixgbe_softc *sc)
 
 	/* Set multiplier for RETA setup and table size based on MAC */
 	index_mult = 0x1;
+	per_pool_rss = false;
 	table_size = 128;
 	switch (sc->hw.mac.type) {
 	case ixgbe_mac_82598EB:
@@ -687,6 +691,12 @@ ixgbe_initialize_rss_mapping(struct ixgbe_softc *sc)
 	case ixgbe_mac_X550EM_a:
 	case ixgbe_mac_E610:
 		table_size = 512;
+#ifdef PCI_IOV
+		if (sc->iov_mode != IXGBE_NO_VM) {
+			per_pool_rss = true;
+			table_size = 64;
+		}
+#endif
 		break;
 	default:
 		break;
@@ -694,13 +704,16 @@ ixgbe_initialize_rss_mapping(struct ixgbe_softc *sc)
 
 	/*
 	 * The global RETA is shared by the PF and VFs on 82599 and X540.
-	 * Program all four queue indices while SR-IOV is active so a VF can
-	 * use its full queue grant even when the PF uses fewer queues.
-	 * PSRTYPE.RQPL limits the subset selected within each pool.
+	 * X550-family devices instead use per-pool tables in multiple-RSS
+	 * mode; initialize only the PF pool here because each VF owns and
+	 * programs its own key and redirection table.
+	 * On the shared tables, program all four queue indices while SR-IOV
+	 * is active so a VF can use its full queue grant even when the PF uses
+	 * fewer queues.  PSRTYPE.RQPL limits the subset selected in each pool.
 	 */
 	reta_queues = sc->num_rx_queues;
 #ifdef PCI_IOV
-	if (sc->iov_mode != IXGBE_NO_VM)
+	if (sc->iov_mode != IXGBE_NO_VM && !per_pool_rss)
 		reta_queues = MAX(reta_queues, 4);
 #endif
 
@@ -727,7 +740,12 @@ ixgbe_initialize_rss_mapping(struct ixgbe_softc *sc)
 		reta = reta >> 8;
 		reta = reta | (((uint32_t)queue_id) << 24);
 		if ((i & 3) == 3) {
-			if (i < 128)
+			if (per_pool_rss) {
+#ifdef PCI_IOV
+				IXGBE_WRITE_REG(hw,
+				    IXGBE_PFVFRETA(i >> 2, sc->pool), reta);
+#endif
+			} else if (i < 128)
 				IXGBE_WRITE_REG(hw, IXGBE_RETA(i >> 2), reta);
 			else
 				IXGBE_WRITE_REG(hw,
@@ -737,8 +755,15 @@ ixgbe_initialize_rss_mapping(struct ixgbe_softc *sc)
 	}
 
 	/* Now fill our hash function seeds */
-	for (i = 0; i < 10; i++)
-		IXGBE_WRITE_REG(hw, IXGBE_RSSRK(i), rss_key[i]);
+	for (i = 0; i < 10; i++) {
+		if (per_pool_rss) {
+#ifdef PCI_IOV
+			IXGBE_WRITE_REG(hw, IXGBE_PFVFRSSRK(i, sc->pool),
+			    rss_key[i]);
+#endif
+		} else
+			IXGBE_WRITE_REG(hw, IXGBE_RSSRK(i), rss_key[i]);
+	}
 
 	/* Perform hash on these packet types */
 	if (sc->feat_en & IXGBE_FEATURE_RSS)
@@ -758,26 +783,50 @@ ixgbe_initialize_rss_mapping(struct ixgbe_softc *sc)
 	}
 
 	mrqc = ixgbe_get_mrqc(sc->iov_mode);
+	rss_fields = 0;
 	if (rss_hash_config & RSS_HASHTYPE_RSS_IPV4)
-		mrqc |= IXGBE_MRQC_RSS_FIELD_IPV4;
+		rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV4;
 	if (rss_hash_config & RSS_HASHTYPE_RSS_TCP_IPV4)
-		mrqc |= IXGBE_MRQC_RSS_FIELD_IPV4_TCP;
+		rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV4_TCP;
 	if (rss_hash_config & RSS_HASHTYPE_RSS_IPV6)
-		mrqc |= IXGBE_MRQC_RSS_FIELD_IPV6;
+		rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV6;
 	if (rss_hash_config & RSS_HASHTYPE_RSS_TCP_IPV6)
-		mrqc |= IXGBE_MRQC_RSS_FIELD_IPV6_TCP;
+		rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV6_TCP;
 	if (rss_hash_config & RSS_HASHTYPE_RSS_IPV6_EX)
-		mrqc |= IXGBE_MRQC_RSS_FIELD_IPV6_EX;
+		rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV6_EX;
 	if (rss_hash_config & RSS_HASHTYPE_RSS_TCP_IPV6_EX)
-		mrqc |= IXGBE_MRQC_RSS_FIELD_IPV6_EX_TCP;
+		rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV6_EX_TCP;
 	if (rss_hash_config & RSS_HASHTYPE_RSS_UDP_IPV4)
-		mrqc |= IXGBE_MRQC_RSS_FIELD_IPV4_UDP;
+		rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV4_UDP;
 	if (rss_hash_config & RSS_HASHTYPE_RSS_UDP_IPV6)
-		mrqc |= IXGBE_MRQC_RSS_FIELD_IPV6_UDP;
+		rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV6_UDP;
 	if (rss_hash_config & RSS_HASHTYPE_RSS_UDP_IPV6_EX)
-		mrqc |= IXGBE_MRQC_RSS_FIELD_IPV6_EX_UDP;
+		rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV6_EX_UDP;
+	if (hw->mac.type == ixgbe_mac_E610) {
+		/* E610 folds IPv6 extension headers into the base selectors. */
+		if (rss_fields & IXGBE_MRQC_RSS_FIELD_IPV6_EX)
+			rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV6;
+		if (rss_fields & IXGBE_MRQC_RSS_FIELD_IPV6_EX_TCP)
+			rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV6_TCP;
+		if (rss_fields & IXGBE_MRQC_RSS_FIELD_IPV6_EX_UDP)
+			rss_fields |= IXGBE_MRQC_RSS_FIELD_IPV6_UDP;
+		rss_fields &= ~(IXGBE_MRQC_RSS_FIELD_IPV6_EX |
+		    IXGBE_MRQC_RSS_FIELD_IPV6_EX_TCP |
+		    IXGBE_MRQC_RSS_FIELD_IPV6_EX_UDP);
+	}
+
+	if (per_pool_rss) {
+#ifdef PCI_IOV
+		mrqc |= IXGBE_MRQC_MULTIPLE_RSS;
+		IXGBE_WRITE_REG(hw, IXGBE_MRQC, mrqc);
 
-	IXGBE_WRITE_REG(hw, IXGBE_MRQC, mrqc);
+		pfmrqc = IXGBE_MRQC_RSSEN | rss_fields;
+		IXGBE_WRITE_REG(hw, IXGBE_PFVFMRQC(sc->pool), pfmrqc);
+#endif
+	} else {
+		mrqc |= rss_fields;
+		IXGBE_WRITE_REG(hw, IXGBE_MRQC, mrqc);
+	}
 } /* ixgbe_initialize_rss_mapping */
 
 /************************************************************************