git: 40367d1f3b8f - main - ixv: Support E610 mailbox API 1.6

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

URL: https://cgit.FreeBSD.org/src/commit/?id=40367d1f3b8ff694ffc3e4bf3278ec2a762648d2

commit 40367d1f3b8ff694ffc3e4bf3278ec2a762648d2
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-01 02:41:56 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-16 01:06:42 +0000

    ixv: Support E610 mailbox API 1.6
    
    E610 VFs no longer report the actual PF link state and speed through
    VFLINKS.  They can consequently report the default 10 Gb/s speed even
    when the physical link uses another rate.
    
    Negotiate mailbox API 1.6 on E610 and request the PF link state with its
    three-dword operation.  Retain VFLINKS as the fallback when an older PF
    rejects API 1.6.  Permit API 1.6 in the inherited xcast and queue
    discovery helpers so negotiating the newer revision does not disable
    existing operations.
    
    Use GET_QUEUES to replace E610's one-queue fallback with the grant from
    the PF.  The common path continues to use one iflib queue set per data
    MSI-X vector and caps the result at two queue pairs.
    
    Preserve mailbox transport errors so the driver can distinguish an
    explicit PF NACK from a transient timeout.  A NACK means clear-to-send
    state was lost and requires a VF reset.  Preserve the last confirmed
    link state across brief transport failures and publish link down after
    three consecutive failures.
    
    Poll E610 link state every two seconds, matching Intel's ixgbevf service
    timer, and phase VFs across the intervening iflib timer ticks.  This
    avoids a mailbox polling herd when many VFs share a PF.  Media-status
    queries return the cached state instead of starting another synchronous
    exchange.  An admin interrupt caused by a mailbox reply only checks for
    an unsolicited PF reset, preventing a request/reply interrupt loop.
    
    Hardware validation on an E610 10GBASE-T PF exercised 63 VFs.  Each VF
    negotiated API 1.6, two queue pairs, and three MSI-X vectors.  Phased
    polling kept 31 active VFs idle, and all 63 recovered after a PF
    down/up cycle without watchdogs.
    
    Adapt the API 1.6 link-state operation from DPDK shared ixgbe code.  The
    timeout and NACK distinction follows Intel ixgbevf 5.3.25.
    
    MFC after:      2 weeks
    Sponsored by:   Dirk-Willem van Gulik from Web Weaving (E610 hardware)
    Sponsored by:   BBOX.io
---
 sys/dev/ixgbe/if_ixv.c    | 64 +++++++++++++++++++++++++++++++++++++++++++----
 sys/dev/ixgbe/ixgbe.h     |  3 +++
 sys/dev/ixgbe/ixgbe_mbx.h |  3 +++
 sys/dev/ixgbe/ixgbe_vf.c  | 60 ++++++++++++++++++++++++++++++++++++++++++++
 sys/dev/ixgbe/ixgbe_vf.h  |  2 ++
 5 files changed, 127 insertions(+), 5 deletions(-)

diff --git a/sys/dev/ixgbe/if_ixv.c b/sys/dev/ixgbe/if_ixv.c
index 93af5daa01d3..99d1169254c5 100644
--- a/sys/dev/ixgbe/if_ixv.c
+++ b/sys/dev/ixgbe/if_ixv.c
@@ -56,6 +56,11 @@ static const sbintime_t ixv_mbx_retry_delay[] = {
 
 static const struct timeval ixv_mbx_log_interval = { 60, 0 };
 
+/* Bound stale carrier state without flapping on a busy PF mailbox. */
+#define IXV_LINK_MBX_FAILURE_LIMIT	3
+/* Match Intel's two-second VF service timer and spread PF mailbox load. */
+#define IXV_LINK_POLL_TICKS		4
+
 /************************************************************************
  * PCI Device ID Table
  *
@@ -448,6 +453,7 @@ ixv_if_attach_pre(if_ctx_t ctx)
 	/* Determine hardware revision */
 	ixv_identify_hardware(ctx);
 	ixv_init_device_features(sc);
+	sc->vf_link_poll_tick = device_get_unit(dev) % IXV_LINK_POLL_TICKS;
 
 	/* Initialize the shared code */
 	error = ixgbe_init_ops_vf(hw);
@@ -896,6 +902,9 @@ ixv_mbx_retry_succeeded(struct ixgbe_softc *sc)
 	if (sc->vf_mbx_retry_initialized)
 		callout_stop(&sc->vf_mbx_retry);
 	sc->vf_mbx_retry_stage = 0;
+	sc->vf_link_mbx_failures = 0;
+	sc->vf_link_poll_tick =
+	    device_get_unit(sc->dev) % IXV_LINK_POLL_TICKS;
 	sc->vf_mbx_last_log.tv_sec = 0;
 	sc->vf_mbx_last_log.tv_usec = 0;
 }
@@ -982,7 +991,12 @@ ixv_if_media_status(if_ctx_t ctx, struct ifmediareq * ifmr)
 
 	INIT_DEBUGOUT("ixv_media_status: begin");
 
-	iflib_admin_intr_deferred(ctx);
+	/* E610 link state is refreshed by the phased service timer. */
+	if (sc->hw.mac.type != ixgbe_mac_E610_vf ||
+	    sc->hw.api_version != ixgbe_mbox_api_16) {
+		atomic_set_32(&sc->vf_link_update, 1);
+		iflib_admin_intr_deferred(ctx);
+	}
 
 	ifmr->ifm_status = IFM_AVALID;
 	ifmr->ifm_active = IFM_ETHER;
@@ -1062,6 +1076,10 @@ ixv_negotiate_api(struct ixgbe_softc *sc)
 	};
 	int i = 0;
 
+	if (hw->mac.type == ixgbe_mac_E610_vf &&
+	    ixgbevf_negotiate_api_version(hw, ixgbe_mbox_api_16) == 0)
+		return (0);
+
 	while (mbx_api[i] != ixgbe_mbox_api_unknown) {
 		if (ixgbevf_negotiate_api_version(hw, mbx_api[i]) == 0)
 			return (0);
@@ -1095,6 +1113,9 @@ ixv_queue_limit(struct ixgbe_softc *sc, bool mailbox_ready)
 	case ixgbe_mac_X550EM_a_vf:
 		limit = 2;
 		break;
+	case ixgbe_mac_E610_vf:
+		limit = 1;
+		break;
 	default:
 		return (1);
 	}
@@ -1105,6 +1126,7 @@ ixv_queue_limit(struct ixgbe_softc *sc, bool mailbox_ready)
 		case ixgbe_mbox_api_11:
 		case ixgbe_mbox_api_12:
 		case ixgbe_mbox_api_13:
+		case ixgbe_mbox_api_16:
 			num_tcs = default_tc = 0;
 			if (ixgbevf_get_queues(hw, &num_tcs, &default_tc) == 0) {
 				limit = imin(hw->mac.max_tx_queues,
@@ -1248,6 +1270,12 @@ ixv_if_local_timer(if_ctx_t ctx, uint16_t qid)
 
 	sc = iflib_get_softc(ctx);
 	atomic_set_32(&sc->vf_vlan_retry_tick, 1);
+	if (sc->hw.mac.type != ixgbe_mac_E610_vf ||
+	    sc->hw.api_version != ixgbe_mbox_api_16 ||
+	    ++sc->vf_link_poll_tick == IXV_LINK_POLL_TICKS) {
+		sc->vf_link_poll_tick = 0;
+		atomic_set_32(&sc->vf_link_update, 1);
+	}
 
 	/* Fire off the adminq task */
 	iflib_admin_intr_deferred(ctx);
@@ -1266,6 +1294,7 @@ ixv_if_update_admin_status(if_ctx_t ctx)
 	struct ixgbe_softc *sc = iflib_get_softc(ctx);
 	device_t dev = iflib_get_dev(ctx);
 	if_t ifp = iflib_get_ifp(ctx);
+	bool check_link;
 	s32 status;
 	uint64_t baudrate;
 
@@ -1278,10 +1307,34 @@ ixv_if_update_admin_status(if_ctx_t ctx)
 		return;
 	}
 
-	sc->hw.mac.get_link_status = true;
-
-	status = ixgbe_check_link(&sc->hw, &sc->link_speed,
-	    &sc->link_up, false);
+	check_link = atomic_readandclear_32(&sc->vf_link_update) != 0;
+	if (sc->hw.mac.type != ixgbe_mac_E610_vf ||
+	    sc->hw.api_version != ixgbe_mbox_api_16)
+		check_link = true;
+	if (check_link) {
+		sc->hw.mac.get_link_status = true;
+		status = ixgbe_check_link(&sc->hw, &sc->link_speed,
+		    &sc->link_up, false);
+	} else if (ixgbe_check_for_rst(&sc->hw, 0) == IXGBE_SUCCESS) {
+		/* Process an unsolicited PF reset without issuing another query. */
+		status = IXGBE_ERR_MBX;
+	} else
+		status = IXGBE_SUCCESS;
+	if (sc->hw.mac.type == ixgbe_mac_E610_vf &&
+	    sc->hw.api_version == ixgbe_mbox_api_16 &&
+	    status != IXGBE_SUCCESS && status != IXGBE_ERR_MBX) {
+		/*
+		 * A busy PF can miss an individual link-state request.  Preserve
+		 * the last confirmed state across brief transport failures, but
+		 * bound how long stale carrier can remain visible if the PF is gone.
+		 */
+		if (sc->vf_link_mbx_failures < IXV_LINK_MBX_FAILURE_LIMIT)
+			sc->vf_link_mbx_failures++;
+		if (sc->vf_link_mbx_failures == IXV_LINK_MBX_FAILURE_LIMIT)
+			sc->link_up = false;
+		status = IXGBE_SUCCESS;
+	} else if (status == IXGBE_SUCCESS)
+		sc->vf_link_mbx_failures = 0;
 
 	if (status != IXGBE_SUCCESS && sc->hw.adapter_stopped == false) {
 		/* Mailbox's Clear To Send status is lost or timeout occurred.
@@ -1345,6 +1398,7 @@ ixv_if_stop(if_ctx_t ctx)
 	if (mailbox_ready && (if_getflags(ifp) & IFF_UP) == 0)
 		hw->mac.ops.reset_hw(hw);
 	atomic_store_rel_32(&sc->vf_mbx_ready, 0);
+	sc->vf_link_mbx_failures = 0;
 	sc->hw.adapter_stopped = false;
 	hw->mac.ops.stop_adapter(hw);
 
diff --git a/sys/dev/ixgbe/ixgbe.h b/sys/dev/ixgbe/ixgbe.h
index 8ded6fa41c65..f4a49516c76e 100644
--- a/sys/dev/ixgbe/ixgbe.h
+++ b/sys/dev/ixgbe/ixgbe.h
@@ -428,10 +428,13 @@ struct ixgbe_softc {
 	u32			vf_mbx_ready;
 	u32			vf_mbx_retry_pending;
 	u32			vf_vlan_retry_tick;
+	u32			vf_link_update;
 	u16			vf_vlan_retry_cursor;
 	u8			vf_mbx_retry_stage;
 	bool			vf_mbx_retry_initialized;
 	bool			vf_mcast_overflow_warned;
+	u8			vf_link_mbx_failures;
+	u8			vf_link_poll_tick;
 
 	/* Info about the interface */
 	int			advertise;	/* link speeds */
diff --git a/sys/dev/ixgbe/ixgbe_mbx.h b/sys/dev/ixgbe/ixgbe_mbx.h
index 827e74a2e397..b719d0c83c1a 100644
--- a/sys/dev/ixgbe/ixgbe_mbx.h
+++ b/sys/dev/ixgbe/ixgbe_mbx.h
@@ -153,6 +153,9 @@ enum ixgbe_pfvf_api_rev {
 #define IXGBE_VF_UPDATE_XCAST_MODE	0x0c
 #define IXGBE_VF_GET_LINK_STATE  0x10
 
+/* mailbox API, version 1.6 VF requests */
+#define IXGBE_VF_GET_PF_LINK_STATE 0x11 /* get status of the link on PF */
+
 /* mode choices for IXGBE_VF_UPDATE_XCAST_MODE */
 enum ixgbevf_xcast_modes {
 	IXGBEVF_XCAST_MODE_NONE = 0,
diff --git a/sys/dev/ixgbe/ixgbe_vf.c b/sys/dev/ixgbe/ixgbe_vf.c
index 00f0cb131cbf..78588c63030b 100644
--- a/sys/dev/ixgbe/ixgbe_vf.c
+++ b/sys/dev/ixgbe/ixgbe_vf.c
@@ -444,6 +444,7 @@ s32 ixgbevf_update_xcast_mode(struct ixgbe_hw *hw, int xcast_mode)
 		/* Fall through */
 	case ixgbe_mbox_api_13:
 	case ixgbe_mbox_api_15:
+	case ixgbe_mbox_api_16:
 		break;
 	default:
 		return IXGBE_ERR_FEATURE_NOT_SUPPORTED;
@@ -490,6 +491,47 @@ s32 ixgbe_get_link_state_vf(struct ixgbe_hw *hw, bool *link_state)
 	return ret_val;
 }
 
+/**
+ * ixgbevf_get_pf_link_state - Get PF's link status
+ * @hw: pointer to the HW structure
+ * @speed: link speed
+ * @link_up: indicate if link is up/down
+ *
+ * Ask PF to provide link_up state and speed of the link.
+ *
+ * Return: IXGBE_ERR_MBX in the case of mailbox error,
+ * IXGBE_ERR_FEATURE_NOT_SUPPORTED if the op is not supported or 0 on success.
+ */
+int ixgbevf_get_pf_link_state(struct ixgbe_hw *hw, ixgbe_link_speed *speed,
+			      bool *link_up)
+{
+	u32 msgbuf[3] = {};
+	int err;
+
+	if (hw->api_version != ixgbe_mbox_api_16)
+		return IXGBE_ERR_FEATURE_NOT_SUPPORTED;
+
+	msgbuf[0] = IXGBE_VF_GET_PF_LINK_STATE;
+	err = ixgbevf_write_msg_read_ack(hw, msgbuf, msgbuf, 3);
+	if (err)
+		return (err);
+	if (msgbuf[0] & IXGBE_VT_MSGTYPE_FAILURE) {
+		*speed = IXGBE_LINK_SPEED_UNKNOWN;
+		return (IXGBE_ERR_MBX);
+	}
+	if ((msgbuf[0] & IXGBE_VT_MSGTYPE_CTS) == 0 ||
+	    (msgbuf[0] & ~IXGBE_VT_MSGTYPE_CTS) !=
+	    (IXGBE_VF_GET_PF_LINK_STATE | IXGBE_VT_MSGTYPE_SUCCESS)) {
+		*speed = IXGBE_LINK_SPEED_UNKNOWN;
+		return (IXGBE_ERR_MBX);
+	}
+
+	*speed = msgbuf[1];
+	*link_up = msgbuf[2];
+
+	return err;
+}
+
 /**
  * ixgbe_set_vfta_vf - Set/Unset vlan filter table address
  * @hw: pointer to the HW structure
@@ -623,12 +665,29 @@ s32 ixgbe_check_mac_link_vf(struct ixgbe_hw *hw, ixgbe_link_speed *speed,
 
 	UNREFERENCED_1PARAMETER(autoneg_wait_to_complete);
 
+	if (link_up == NULL)
+		return (IXGBE_ERR_PARAM);
+
 	/* If we were hit with a reset drop the link */
 	if (!mbx->ops[0].check_for_rst(hw, 0) || !mbx->timeout)
 		mac->get_link_status = true;
 
 	if (!mac->get_link_status)
 		goto out;
+	if (mac->type == ixgbe_mac_E610_vf &&
+	    hw->api_version == ixgbe_mbox_api_16) {
+		ret_val = ixgbevf_get_pf_link_state(hw, speed, link_up);
+		if (ret_val == IXGBE_ERR_MBX) {
+			/* A PF NACK means the VF has lost clear-to-send state. */
+			*link_up = false;
+			return (ret_val);
+		} else if (ret_val != IXGBE_SUCCESS) {
+			/* Let the driver debounce transient transport failures. */
+			return (ret_val);
+		}
+		mac->get_link_status = !*link_up;
+		return (IXGBE_SUCCESS);
+	}
 
 	/* if link status is down no point in checking to see if pf is up */
 	links_reg = IXGBE_READ_REG(hw, IXGBE_VFLINKS);
@@ -777,6 +836,7 @@ int ixgbevf_get_queues(struct ixgbe_hw *hw, unsigned int *num_tcs,
 	case ixgbe_mbox_api_12:
 	case ixgbe_mbox_api_13:
 	case ixgbe_mbox_api_15:
+	case ixgbe_mbox_api_16:
 		break;
 	default:
 		return 0;
diff --git a/sys/dev/ixgbe/ixgbe_vf.h b/sys/dev/ixgbe/ixgbe_vf.h
index 2e8921a21fed..7f5a8c2e85cf 100644
--- a/sys/dev/ixgbe/ixgbe_vf.h
+++ b/sys/dev/ixgbe/ixgbe_vf.h
@@ -135,6 +135,8 @@ s32 ixgbe_update_mc_addr_list_vf(struct ixgbe_hw *hw, u8 *mc_addr_list,
 				 bool clear);
 s32 ixgbevf_update_xcast_mode(struct ixgbe_hw *hw, int xcast_mode);
 s32 ixgbe_get_link_state_vf(struct ixgbe_hw *hw, bool *link_state);
+int ixgbevf_get_pf_link_state(struct ixgbe_hw *hw, ixgbe_link_speed *speed,
+			      bool *link_up);
 s32 ixgbe_set_vfta_vf(struct ixgbe_hw *hw, u32 vlan, u32 vind,
 		      bool vlan_on, bool vlvf_bypass);
 s32 ixgbevf_rlpml_set_vf(struct ixgbe_hw *hw, u16 max_size);