git: 7868958f5c79 - main - ix(4): Support Total Port Shutdown on E610

From: Krzysztof Galazka <kgalazka_at_FreeBSD.org>
Date: Mon, 02 Nov 2026 11:20:15 UTC
The branch main has been updated by kgalazka:

URL: https://cgit.FreeBSD.org/src/commit/?id=7868958f5c79eb986d067e02ca8239aa7491dfac

commit 7868958f5c79eb986d067e02ca8239aa7491dfac
Author:     Krzysztof Galazka <kgalazka@FreeBSD.org>
AuthorDate: 2026-11-02 11:17:08 +0000
Commit:     Krzysztof Galazka <kgalazka@FreeBSD.org>
CommitDate: 2026-11-02 11:18:31 +0000

    ix(4): Support Total Port Shutdown on E610
    
    Firmware controls the physical link state on E610 adapters, similarly to
    X700 and E800 adapters. As a result, running "ifconfig down" does not
    disable the physical link unless Total Port Shutdown is enabled.
    
    Read the Link Default Override TLV from NVM to detect when Total
    Port Shutdown was enabled for given port in system setup. In that
    case, force the link down when the interface is stopped and restore
    it during link configuration. Leave link state management unchanged
    when the TLV is unavailable or the feature is not enabled.
    
    Co-authored-by: MiƂosz Linkiewicz <milosz.linkiewicz@intel.com>
    Co-authored-by: Tomasz Lichwala <tomasz.lichwala@intel.com>
    Signed-off-by: Krzysztof Galazka <krzysztof.galazka@intel.com>
    
    Tested by:      Mateusz Moga <mateusz.moga@intel.com>
    MFC after:      1 week
    Sponsored by:   Intel Corporation
    Differential Revision:  https://reviews.freebsd.org/D60643
---
 sys/dev/ixgbe/if_ix.c           | 83 ++++++++++++++++++++++++++++++++++++++++-
 sys/dev/ixgbe/ixgbe.h           |  2 +
 sys/dev/ixgbe/ixgbe_e610.c      | 79 +++++++++++++++++++++++++++++++++++++++
 sys/dev/ixgbe/ixgbe_e610.h      |  4 ++
 sys/dev/ixgbe/ixgbe_type_e610.h | 17 +++++++++
 5 files changed, 184 insertions(+), 1 deletion(-)

diff --git a/sys/dev/ixgbe/if_ix.c b/sys/dev/ixgbe/if_ix.c
index 74ccfb7f629f..bbce4012ca53 100644
--- a/sys/dev/ixgbe/if_ix.c
+++ b/sys/dev/ixgbe/if_ix.c
@@ -307,6 +307,9 @@ static u32  ixgbe_handle_fw_event(void *);
 static int ixgbe_enable_lse(struct ixgbe_softc *sc);
 static int ixgbe_disable_lse(struct ixgbe_softc *sc);
 
+static void ixgbe_cache_ldo_policy(struct ixgbe_softc *sc);
+static void ixgbe_apply_ldo_link_state(struct ixgbe_softc *sc, bool link_up);
+
 /************************************************************************
  *  FreeBSD Device Interface Entry Points
  ************************************************************************/
@@ -361,6 +364,62 @@ ixgbe_device_iov_init(device_t dev, uint16_t num_vfs,
 }
 #endif
 
+/************************************************************************
+ * ixgbe_cache_ldo_policy
+ ************************************************************************/
+static void
+ixgbe_cache_ldo_policy(struct ixgbe_softc *sc)
+{
+	struct ixgbe_link_default_override_tlv ldo = {0};
+	struct ixgbe_hw *hw = &sc->hw;
+	s32 error;
+
+	sc->ldo_valid = false;
+	sc->ldo_port_disabled = false;
+
+	error = ixgbe_get_link_default_override(hw, &ldo);
+	if (error) {
+		device_printf(sc->dev,
+		    "Failed to read E610 Link Default Override TLV, error: %d\n",
+		    error);
+		return;
+	}
+
+	sc->ldo_valid = true;
+	sc->ldo_port_disabled =
+	    !!(ldo.link_options & IXGBE_LINK_OVERRIDE_PORT_DIS);
+} /* ixgbe_cache_ldo_policy */
+
+/************************************************************************
+ * ixgbe_apply_ldo_link_state
+ ************************************************************************/
+static void
+ixgbe_apply_ldo_link_state(struct ixgbe_softc *sc, bool link_up)
+{
+	struct ixgbe_hw *hw = &sc->hw;
+	s32 error;
+
+	if (!sc->ldo_valid || !sc->ldo_port_disabled)
+		return;
+
+	error = ixgbe_force_link_state(hw, link_up);
+	if (!error)
+		return;
+
+	if (!link_up && error == IXGBE_ERR_ACI_ERROR &&
+	    hw->aci.last_status == IXGBE_ACI_RC_EMODE) {
+		device_printf(sc->dev,
+		    "Firmware vetoed link shutdown, "
+		    "last ACI status: %d\n", hw->aci.last_status);
+		return;
+	}
+
+	device_printf(sc->dev,
+	    "Failed to force link %s, error: %d, "
+	    "last aci status: %d\n", link_up ? "up" : "down", error,
+	    hw->aci.last_status);
+} /* ixgbe_apply_ldo_link_state */
+
 static device_method_t ixgbe_if_methods[] = {
 	DEVMETHOD(ifdi_attach_pre, ixgbe_if_attach_pre),
 	DEVMETHOD(ifdi_attach_post, ixgbe_if_attach_post),
@@ -1175,6 +1234,7 @@ ixgbe_if_attach_pre(if_ctx_t ctx)
 		hw->phy.smart_speed = ixgbe_smart_speed;
 
 	ixgbe_init_device_features(sc);
+	ixgbe_cache_ldo_policy(sc);
 
 	/* Verify adapter fan is still functional (if applicable) */
 	if (sc->feat_en & IXGBE_FEATURE_FAN_FAIL) {
@@ -2061,9 +2121,14 @@ ixgbe_config_link(if_ctx_t ctx)
 				    "error: %d", err);
 		}
 
-		if (hw->mac.ops.setup_link)
+		if (hw->mac.ops.setup_link) {
 			err = hw->mac.ops.setup_link(hw, autoneg,
 			    sc->link_up);
+			if (err)
+				return;
+		}
+
+		ixgbe_apply_ldo_link_state(sc, true);
 	}
 } /* ixgbe_config_link */
 
@@ -3730,6 +3795,20 @@ ixgbe_add_debug_sysctls(struct ixgbe_softc *sc)
 
 	if (sc->feat_en & IXGBE_FEATURE_FW_LOGGING)
 		ixgbe_add_fw_logging_tunables(sc, sc->debug_sysctls);
+
+	if (sc->hw.mac.type == ixgbe_mac_E610) {
+		struct sysctl_oid_list *debug_list;
+		debug_list = SYSCTL_CHILDREN(sc->debug_sysctls);
+
+		SYSCTL_ADD_BOOL(ctx, debug_list, OID_AUTO, "ldo_valid",
+		    CTLFLAG_RD, &sc->ldo_valid, 0,
+		    "E610 Link Default Override policy valid");
+
+		SYSCTL_ADD_BOOL(ctx, debug_list, OID_AUTO,
+		    "ldo_port_disabled", CTLFLAG_RD, &sc->ldo_port_disabled,
+		    0, "E610 Link Default Override PORT_DIS set");
+	}
+
 } /* ixgbe_add_debug_sysctls */
 
 /************************************************************************
@@ -5120,6 +5199,8 @@ ixgbe_if_stop(if_ctx_t ctx)
 		iflib_link_state_change(ctx, LINK_STATE_DOWN, 0);
 		sc->link_active = false;
 	}
+	ixgbe_if_update_admin_status(ctx);
+	ixgbe_apply_ldo_link_state(sc, false);
 
 	/* reprogram the RAR[0] in case user changed it. */
 	ixgbe_set_rar(&sc->hw, 0, sc->hw.mac.addr, 0, IXGBE_RAH_AV);
diff --git a/sys/dev/ixgbe/ixgbe.h b/sys/dev/ixgbe/ixgbe.h
index 28d9adeeb7a5..41beaeaec7d7 100644
--- a/sys/dev/ixgbe/ixgbe.h
+++ b/sys/dev/ixgbe/ixgbe.h
@@ -443,6 +443,8 @@ struct ixgbe_softc {
 	int			advertise;	/* link speeds */
 	int			enable_aim;	/* adaptive interrupt moderation */
 	bool			link_active;
+	bool			ldo_valid;
+	bool			ldo_port_disabled;
 	u16			num_segs;
 	u32			link_speed;
 	bool			link_up;
diff --git a/sys/dev/ixgbe/ixgbe_e610.c b/sys/dev/ixgbe/ixgbe_e610.c
index 058c79928cd3..507dc7d16920 100644
--- a/sys/dev/ixgbe/ixgbe_e610.c
+++ b/sys/dev/ixgbe/ixgbe_e610.c
@@ -1643,6 +1643,38 @@ s32 ixgbe_aci_get_link_info(struct ixgbe_hw *hw, bool ena_lse,
 	return IXGBE_SUCCESS;
 }
 
+/**
+ * ixgbe_force_link_state - force phy link to get up/down
+ * @hw: pointer to hardware structure
+ * @link_up: if true: force link up, if false: force link down
+ *
+ * Send 0x0601 with the IXGBE_ACI_PHY_ENA_LINK bit set/cleared.
+ *
+ * Return: the exit code of the operation.
+ */
+s32 ixgbe_force_link_state(struct ixgbe_hw *hw, bool link_up)
+{
+	struct ixgbe_aci_cmd_set_phy_cfg_data pcfg = {0};
+	struct ixgbe_aci_cmd_get_phy_caps_data pcaps;
+	s32 err;
+
+	err = ixgbe_aci_get_phy_caps(hw, false, IXGBE_ACI_REPORT_ACTIVE_CFG,
+				     &pcaps);
+	if (err) {
+		return err;
+	}
+
+	ixgbe_copy_phy_caps_to_cfg(&pcaps, &pcfg);
+
+	if (link_up)
+		pcfg.caps |= IXGBE_ACI_PHY_ENA_LINK;
+	else
+		pcfg.caps &= ~IXGBE_ACI_PHY_ENA_LINK;
+	pcfg.caps |= IXGBE_ACI_PHY_ENA_AUTO_LINK_UPDT;
+
+	return ixgbe_aci_set_phy_cfg(hw, &pcfg);
+}
+
 /**
  * ixgbe_aci_set_event_mask - set event mask
  * @hw: pointer to the HW struct
@@ -5973,6 +6005,53 @@ static s32 ixgbe_get_pfa_module_tlv(struct ixgbe_hw *hw, u16 *module_tlv,
 	return IXGBE_ERR_DOES_NOT_EXIST;
 }
 
+/**
+ * ixgbe_get_link_default_override - Get link default override from NVM
+ * @hw: pointer to hardware structure
+ * @ldo: pointer to link default override struct to populate
+ *
+ * Read the Link Default Override TLV (0x134) from the NVM PFA and return
+ * the full per-port override configuration. The caller must consume only
+ * IXGBE_LINK_OVERRIDE_PORT_DIS from link_options for force-link policy.
+ * All port data is read in a single bulk NVM access.
+ *
+ * Return: the exit code of the operation.
+ */
+s32 ixgbe_get_link_default_override(struct ixgbe_hw *hw,
+				    struct ixgbe_link_default_override_tlv *ldo)
+{
+	u16 min_tlv_len;
+	u16 tlv, tlv_len, tlv_start;
+	s32 status;
+
+	DEBUGFUNC("ixgbe_get_link_default_override");
+
+	if (!ldo)
+		return IXGBE_ERR_PARAM;
+
+	status = ixgbe_get_pfa_module_tlv(hw, &tlv, &tlv_len,
+					  E610_SR_LINK_DEFAULT_OVERRIDE_PTR);
+	if (status)
+		return status;
+
+	/* tlv_len covers value words only, don't add header offset. */
+	min_tlv_len = (hw->bus.func + 1) * E610_SR_PFA_LINK_OVERRIDE_WORDS;
+	if (tlv_len < min_tlv_len)
+		return IXGBE_ERR_INVAL_SIZE;
+
+	tlv_start = tlv + E610_SR_PFA_LINK_OVERRIDE_OFFSET +
+		    hw->bus.func * E610_SR_PFA_LINK_OVERRIDE_WORDS;
+
+	/* Read all port-specific words in a single NVM access */
+	status = ixgbe_read_ee_aci_buffer_E610(hw, tlv_start,
+					       E610_SR_PFA_LINK_OVERRIDE_WORDS,
+					       (u16 *)ldo);
+	if (status)
+		return status;
+
+	return IXGBE_SUCCESS;
+}
+
 /**
  * ixgbe_read_pba_string_E610 - Reads part number string from NVM
  * @hw: pointer to hardware structure
diff --git a/sys/dev/ixgbe/ixgbe_e610.h b/sys/dev/ixgbe/ixgbe_e610.h
index 7af5506d85e8..a9e496098e52 100644
--- a/sys/dev/ixgbe/ixgbe_e610.h
+++ b/sys/dev/ixgbe/ixgbe_e610.h
@@ -75,6 +75,7 @@ s32 ixgbe_update_link_info(struct ixgbe_hw *hw);
 s32 ixgbe_get_link_status(struct ixgbe_hw *hw, bool *link_up);
 s32 ixgbe_aci_get_link_info(struct ixgbe_hw *hw, bool ena_lse,
 			    struct ixgbe_link_status *link);
+s32 ixgbe_force_link_state(struct ixgbe_hw *hw, bool link_up);
 s32 ixgbe_aci_set_event_mask(struct ixgbe_hw *hw, u8 port_num, u16 mask);
 s32 ixgbe_configure_lse(struct ixgbe_hw *hw, bool activate, u16 mask);
 
@@ -232,6 +233,9 @@ s32 ixgbe_write_ee_aci_buffer_E610(struct ixgbe_hw *hw, u16 offset,
 s32 ixgbe_calc_eeprom_checksum_E610(struct ixgbe_hw *hw);
 s32 ixgbe_update_eeprom_checksum_E610(struct ixgbe_hw *hw);
 s32 ixgbe_validate_eeprom_checksum_E610(struct ixgbe_hw *hw, u16 *checksum_val);
+/* Reads LDO TLV 0x134; callers consume only IXGBE_LINK_OVERRIDE_PORT_DIS. */
+s32 ixgbe_get_link_default_override(struct ixgbe_hw *hw,
+				    struct ixgbe_link_default_override_tlv *ldo);
 s32 ixgbe_read_pba_string_E610(struct ixgbe_hw *hw, u8 *pba_num, u32 pba_num_size);
 
 #endif /* _IXGBE_E610_H_ */
diff --git a/sys/dev/ixgbe/ixgbe_type_e610.h b/sys/dev/ixgbe/ixgbe_type_e610.h
index da46e503f660..005fec423a20 100644
--- a/sys/dev/ixgbe/ixgbe_type_e610.h
+++ b/sys/dev/ixgbe/ixgbe_type_e610.h
@@ -140,6 +140,23 @@
 #define E610_SR_NVM_CTRL_WORD		0x00
 #define E610_SR_PBA_BLOCK_PTR		0x16
 
+/* Link Default Override TLV in PFA */
+#define E610_SR_LINK_DEFAULT_OVERRIDE_PTR	0x134
+#define E610_SR_PFA_LINK_OVERRIDE_WORDS		10
+#define E610_SR_PFA_LINK_OVERRIDE_PHY_WORDS	8
+#define E610_SR_PFA_LINK_OVERRIDE_OFFSET	2
+
+/* Link Options bits */
+#define IXGBE_LINK_OVERRIDE_PORT_DIS		BIT(2)
+
+#pragma pack(1)
+struct ixgbe_link_default_override_tlv {
+	u16 link_options;	/* Port Options 0: only PORT_DIS is consumed */
+	u16 phy_options;	/* Port Options 1 */
+	u16 phy_types[E610_SR_PFA_LINK_OVERRIDE_PHY_WORDS];	/* words 2-9 */
+};
+#pragma pack()
+
 /* The Orom version topology */
 #define IXGBE_OROM_VER_PATCH_SHIFT	0
 #define IXGBE_OROM_VER_PATCH_MASK	(0xff << IXGBE_OROM_VER_PATCH_SHIFT)